# Education & PROcrastination Language inspiration: ALGOL/Pascal family FORTH/B (no/limited types, very low abstraction, raw pointers...) WASM itself ``` (* Yes, the keywords MUST BE UPPERCASE, fight me! END always ends statement, unless block returns value (maybe use DONE if returns instead??) *) ENUM EntityTypes : en player, enemy1, enemy2 END (* # used to mark hex number 0x also works. 0b for binary, 0o for octal*) (* underscores are allowed in numbers *) ENUM Colors : col green = #00FF00_FF, red = #FF0000_FF, blue = #0000FF_FF, yellow = #FFFF00_FF, END ``` record name is used as a constructor function it must also be provided (not necessarily unique) namespace, under which it will create accessors (described later) for it's attributes record's actual data order is the same as it is written records length in bytes can also be accessed at compile-time with `$name`, in this case `$Entity` returns 16 ``` RECORD Entity : en i32 type, (* types probably case-insensitive, we aren't Holy C after all *) color, (* trailing comma allowed *) f32 x, z, END RECORD Player : pl EXTENDS Entity i32 ammo, lives END (* same as RECORD Player : pl i32 type, color f32 x, z i32 ammo, lives END but accessors pl.type, pl.color, pl.x and pl.z are not created *) RECORD Enemy : enm EXTENDS Entity i32 hp END ``` procedures can also have a namespace (but they don't have to) all local variables must be declared (and possibly initialised) before the procedure body ``` PROC make : en (i32: type, f32: x, y): i32 i32 ret := Entity(type, col.yellow, x, y) DO en.init(ret); RETURN ret END PROC init : en (i32 ret) DO en.color|ret := CASE:i32 en.type|ret (* if block has return value, GIVE acts as return *) OF en.player : GIVE col.green; OF en.enemy1 : GIVE col.red; OF en.enemy2 : GIVE col.blue; ELSE : GIVE col.yellow; (* else optional *) END; END PROC make : pl (f32: x, y, i32: ammo, lives): i32 DO RETURN Player(en.player, col.green, x, y, ammo, lives); END (* or use named return value (streatch goal feature) *) PROC make : enm (i32 type, f32: x, y, i32 hp): i32 AS ret DO ret := Enemy(en.player, col.yellow, x, y, hp); en.init(ret); END (* ret automatically returned *) (* all namespace, argument list and return type can be ommited *) PROC main i32 entities := a.make32(3), (* make an array of 3 32-bit values *) i DO entities[0] := pl .make(0.0, 0.0, 37, 3); entities[1] := enm.make(en.enemy1, 6.9, 4.20, 20); entities[2] := enm.make(en.enemy2, 12.34, 56.78, 37); LOOP FOR i FROM 0 TO a.len|entities (* a.len is actually stored before the array*) DO IF en.type|entities[i] = en.player DO (* [] has precedence over | *) pl .handle(entries[i]); (* example procedure call *) ELSE enm.handle(entries[i]); (* example procedure call *) END END END END ``` by default, `[]` accesses `i32` arrays, other vales must be specified like `[u8:1]` to access one byte at offset 1 of the type (byte) or `[f32::3]` to access one `f32` at offset of 3 bytes accessors like 'en.type' are just compile-time representation of said index syntax custom ones can be defined like so: ``` ACCESSOR red : en [u8::4] ACCESSOR green : en [u8::5] ACCESSOR blue : en [u8::6] ACCESSOR alpha : en [u8::7] ``` now `en.red|entity` is the same as `entity[i8:4:1]` note that negative numbers in indexing are allowed, but they do not go from the end, but just subtract from the pointer instead, for example 'a.len' could be implemented as such: ``` ACCESSOR len : a [-1] ``` file imports: import all symbols directly `USE "path"` import under namespace `USE "path" : namespace` implicitly imported by all files: files placed in `./lib/` name format `number.other.identifiers.namespace.epr` number to set order identifiers to explain contents namespace under which it will be imported `.epr` extension default stuff like `a` (arrays) `mem` (memory) etc user can change, if they want only one required is `00.mem.epr` and it only needs to have `alloc(i32): i32` (used with records) global variables: ``` VAR (* : namespace *) i32 x := a, y := b, i64 a := c, b := d, END CONST (* : namespace *) (* consts only exist at compile-time *) PI = 3.1415926, HELLO = "Hello, World!" (* string exists ar runtime, but HELLO does not *) END ``` full procedure options: `VAR`, `CONST` and `STATIC` can appear in any order multiple times `VAR` is implicitly open by default ``` PROC name : namespace (args...): ret_type AS ret_name EXPORT "export_name" ... vars ... CONST ... consts ... VAR ... more vars depending on consts ... STATIC ... static variables ... DO ... body ... END ``` interactions outside of WASM: ``` VAR a := 4, b := 2 EXPORT "b", c := 0 END PROC example(i32 a, b): i32 EXPORT "example" DO ... END IMPORT "outer_name" AS name (* : namespace *) i32 IMPORT "console" "log" AS log : console (i32 str, len) ``` control structures: ``` LOOP (* WHILE TRUE *) (* optional DO *) END WHILE expr DO END (* TO exclusive?*) FOR I FROM expr TO expr STEP expr (* default STEP 1 *) DO END FOR I FROM expr DOWNTO expr STEP expr (* default STEP -1 *) DO END FOR I FROM expr UNTIL expr STEP expr (* more like C for, but only increment *) DO END FOR I IN expr OF i32 (* default OF i32*) DO END IF (* : type *) expr DO ELIF expr DO ELIF expr DO ELSE (* optional DO *) END CASE (* : type *) expr (* optional DO *) OF val, val... : block... OF val, val... : block... OF val, val... : block... ELSE: block... END DO : type (* only makes sense it returns value *) END (* might change to BEGIN or BLOCK if causes parsing problems in parsing *) ``` Some control structures can return a value, such structures require `GIVE` followed by expression to return a value in all branches. `IF` and `CASE` requires an `ELSE` branch if returning a value. `NEXT` can also be used in loops to skip current iteration, but continue the next. `BREAK` can be used to exit the loop all together. Both can take an index starting at one to signify depth. ``` WHILE expr DO FOR I FROM a TO b DO IF I > 9 DO NEXT 2; (* continue one loop up, that is the WHILE loop *) END END END ``` operators: ``` < - lesser than > - greater than <= - lesser or equal than >= - greater or equal than = - equal to <> - not equal NOT - (prefix) bitwise negation AND - bitwise and OR - bitwise or XOR - bitwise xor NAND - bitwise nand NOR - bitwise nor NXOR - bitwise NXOR BOOL - (prefix) 0 if 0, else 1, | useful to get canonic bool value, as all NBOOL - (prefix) 1 if 0, else 0 | operators are bitwise, not logical + - addition - - subtractoin * - multiplication / - division % - modulo - - (infix) 0 - right ``` comparison and arithmetic operators can have a dot at each of it's sides to signal that said side is to be treated as unsigned: ``` s < s u .< s s <. u u .<. u ``` in with arithmetic operators, the result is of whatever type is the left side `(s32 -. u64 → s32)`. If other result type is required, specify with type notation after the operator `(s32 -.:u64 u64 → u64)`. arithmetic operators also have corresponding assignments possible syntax: ``` foo +:= bar (* feels long and awkward to write *) foo :+= bar (* still long, but even weirder *) foo += bar (* works, but asymetrical with <= and >= *) foo :+ bar (* is symetrical, but looks weird *) ``` pointers: ``` PROC addTo(i32 ptr_a, b) DO @ptr_a +:= b (* default reference to i32 to reference different type like i64@ptr_a. Having three syntaxes for dereferencing seems a bit too much, so perhaps it will just use the index syntax, but I think having separate syntaxes for dereferencing and indexed access would make the code more readable *) END PROC main i32 foo := 3, bar := 7, DO addTo(^foo, bar) (* foo is now 10 *) END ``` arrays: most array operations will be located in implicit `a` namespace ``` a := i32:{1,2,4,5, "Hello, World!"}; (* again, i32 type is the default *) ``` this array is statically placed in the memory. Some values might not be known at compile-time however, so they will be filled in at runtime each time ``` PROC foo(i32: bar): i32 i32 bar := {1,2, bar+1, 2+1}, I, DO FOR I FROM 0 TO a.len|bar DO bar[I] +:= 1 END RETURN sum(bar); END ``` In this example, parts of the array know at compile-time will increase each loop, while those not known at compile-time will get initialised each iteration anew. It is not specified what is considered 'known at compile-time', so it is better to avoid this. (or I might just only allow this syntax for compile-time-know values, IDK)