======== TYPES ========

Built-in types:
   unit
   bool
   u8
   u16
   u32
   u64
   i8
   i16
   i32
   i64
   f16
   f32
   f64

N.B.: there is no dedicated 'char' type, strings operate on u8 (utf-8) or u32 (utf-32)

Pointer types:
   T*              (pointer to const T)
   T mut*          (pointer to mutable T)
   T**             (pointer to const pointer to const T)
   T* mut*         (pointer to mutable pointer to const T)
   T mut**         (pointer to const pointer to mutable T)
   T mut* mut*     (pointer to mutable pointer to mutable T)

Array types:
   T[N]        array of N elements of type T (N must be a compile-time value)

Function types:
   (arg1, arg2, arg3) -> result
   (arg1, arg2) -> unit            // no return value
   arg -> result                   // simplified syntax for single-argument case

======== LITERALS ========

Literals:
    56b    -> i8
    42ub   -> u8
    456s   -> i16
    456us  -> u16
    98765  -> i32
    98765u -> u32
    123l   -> i64
    123ul  -> u64
    3.14h  -> f16
    3.14   -> f32
    3.14d  -> f64
    'a'    -> u8 (ascii only?)
    '猫'u  -> u32

TODO: string literals? fixed-size arrays? built-in spans? Probably built-in spans (potentially defined in prelude.psl)
    "hello, world" -> utf-8 string
    "здарова, братки"u -> utf-32 string

======== VARIABLES ========

Variable declaration:
    const x = ...        compile-time value, type inferred
    const x: T = ...    compile-time value of type T
    let x = ...        immutable value, type inferred
    let x: T = ... immutable value of type T
    mut x = ...     mutable, ...
    mut x: T = ...

Array declaration:
    let arr: i32[4] = [12, 15, 65, 42]
    let arr = [2, 5, 6] // size and type inferred as i32[3]
    let arr: i32[0] = [] // need special empty array literal, type cannot be inferred

Null pointer literal:
    let p: u32* = null      // special like empty array literal, type cannot be inferred

Variables must always be initialized. (TODO: really? What about arrays? Maybe need special syntax for zero-initialization or mass-initialization. Alternative: default to zero-initialization)
Const variables must be initialized with a const expression (any expression that doesn't include non-const values).

======== OPERATORS ========

Logical (only bool type):
    !x
    x & y
    x | y
    x && y // short-circuit
    x || y // short-circuit
    x ^ y

Equality (all built-in types, all pointer types, all array/struct types, only same type unless integers):
    x == y
    x != y

Comparison (all built-in types, all pointer types, all array/struct types, only same type unless integers):
    x < y
    x > y
    x <= y
    x >= y

Bitwise (integer types, only same type):
    !x
    x & y
    x | y
    x && y // short-circuit
    x || y // short-circuit
    x ^ y

Bitwise shift (any integer + any unsigned integer type):
    x >> y
    x << y

Arithmetic (only same integer/floating-point type):
    -x
    x + y
    x * y
    x / y
    x % y

Pointer arithmetic (any pointer type + any integer type):
    p + x
    p - x
    p - q // returns i64

Pointer arithmetic works element-wise (like C or C++), i.e. p + n advances by n * sizeof(T) when typeof(p) is *T

Casting:
    x as u32 // always explicit, no implicit casts allowed

The only implicit casting allowed is T mut* -> T* (maybe?)
Any integer/floating-point types can be cast to each other.
Any pointer types can be cast to each other (TODO: alignment? UB or safe fallback? Probably UB.)

Address:
    &x // returns *T, fails if x is a const variable
    &mut x // returns *mut T, fails if x is non-mut variable

Assignment:
    x = 15 // requires x to be a mut variable
    *p = 15 // p must be a pointer to mut

======== FLOW CONTROL ========

Flow control:
    if condition:
        statements
    else if condition:
        statements
    else:
        statements

    while condition:
        statements

    TODO: break/continue?
    TODO: for loops? iterator/range interface?

======== STRUCTS ========

Struct types:
    struct rect:
        width: u32
        height: u32

Creating a struct value:
    let x = rect(10u, 20u)
    let y = rect(width = 10u, height = 20u) // named function arguments in general?

Struct field access:
    let r = rect(1u, 2u)
    let x = r.width
    let p = &r
    let y = p.height // field access through pointer is the same

TODO: inner struct functions maybe? to act as namespace/module containers

======== FUNCTIONS ========

Function definition:
    func foo(x: i32, y: i32) -> i32:
        return x * y

    func bar(x: f32): // deduced return type unit
        print(x)

    // External function: name taken literally as `powf`
    // and C calling convention assumed
    foreign func powf(x: f32, y: f32) -> f32 // no implementation

TODO: function overloading? Probably requires selecting a specific overload using `as` operator to save to a value (but not on call site)

======== TYPE OF TYPES ========

Types are also considered to be values. The keyword `type` denotes the type of all types.
I.e. `typeof(16) == i32` and `typeof(i32) == type`. Incidentally, `typeof(type) == type` as well; there are no type kinds or etc.
`type` can be used in any place where a type is required (variable types, function arguments, function return value, etc).
E.g.
    func foo(x: type) -> type:
        return x[4] // type of arrays of 4 elements of type x

    let y: type = u32
    if foo(y) == u32[4]:
        do_smth()

======== CONST EXPRESSIONS ========

// TODO
// Auto-upgrading values to compile-time when a pure function is executed from const-only values?

======== METAPROGRAMMING ========

// TODO
// Functions returning functions/structs
// Syntactic sugar for common cases
// Figure out: max(a,b) - how to deduce type parameters? How does it play with overloading?
// func max(t: type):
//     return func(x : t, y : t):
//         if x > y:
//             return x
//         else:
//             return y

======== MODULES AND IMPORTS ========

// TODO

======== STANDARD LIBRARY ========

// TODO: containers, memory management, strings?
