pslang/spec.txt
2026-04-03 00:15:02 +03:00

242 lines
6.4 KiB
Text

======== 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.)
Ternary if operator:
if condition then true_value else false_value
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
Special built-ins:
typeof(value)
sizeof(type)
sizeof(value) // same as sizeof(typeof(value))
alignof(type)
alignof(value) // same as alignof(typeof(value))
offsetof(struct type, field)
offsetof(field access expression)
======== FLOW CONTROL ========
Flow control:
if condition:
statements
else if condition:
statements
else:
statements
while condition:
statements
if x:
break
if y:
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)
TODO: alternative - Rust-like traits? More powerful, but complicates the language
======== 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?