Syntax Reference
Primitives, operators, types, and rules across all three lenses.
1Primitives
| # | Primitive | Real | Dev | Normie |
|---|---|---|---|---|
| 1 | Function declaration | :: add(ℤ a, ℤ b) -> ℤ {} | fn add(int a, int b) -> int {} | function add(integer a, integer b) returnType integer {} |
| 2 | Variable declaration experimental | § ℤ x | var: int x | container: integer x |
| 3 | Assignment experimental | § ℤ x <- 5; | var: int x <- 5; | container: integer x <- 5; |
| 4 | Function call | ↯ add(1, 2); | call add(1, 2); | runFunction add(1, 2); |
| 5 | Return type | -> ℤ | -> int | returnType integer |
| 6 | Return value | ↵ x; | ret x; | return x; |
| 7 | Conditional | ? (x > 0) : {} ⋮ {} | if (x > 0) then {} else {} | if (x > 0) then {} else {} |
| 8 | Pattern match | ⋈ x { 1 → ..., _ → ... } | match x { 1 -> ..., _ -> ... } | check x { 1 means ..., anything means ... } |
| 9 | Loop | ↻ i 0..10 {} | for i 0..10 {} | loop i 0..10 {} |
| 10 | Record declaration | ⊞ User { 𝕊 name, ℤ age } | rec User { str name, int age } | record User { string name, integer age } |
| 11 | Union declaration | ⊎ Status { Active, Banned(𝕊) } | union Status { Active, Banned(str) } | either Status { Active, Banned(string) } |
| 12 | Array experimental | ⟦τ ℤ, σ 100⟧ arr; | arr[type: int, size: 100] arr; | list[type: integer, size: 100]; |
| 13 | Comment (single-line) | // comment | // comment | // comment |
| 14 | Block body | {} | {} | {} |
| 15 | Arguments | () | () | () |
| 16 | Field access | user.age | user.age | user.age |
| 17 | Array element access | users @ i | users at i | users at i |
| 18 | Import | ※ utils; | import utils; | import utils; |
| 19 | Unary negation / not | -x / !flag | -x / !flag | -x / !flag |
| 20 | Record equality | equals User(a, b) -> 𝔹 { ↵ a.name = b.name; } | equals User(a, b) -> bool { ret a.name = b.name; } | equals User(a, b) returnType boolean { return a.name = b.name; } |
| 21 | String concatenation | join(a, b) | join(a, b) | join(a, b) |
| 22 | Record construction | ↳ User { name: "Sam", age: 25 }; | new User { name: "Sam", age: 25 }; | new User { name: "Sam", age: 25 }; |
| 23 | Capability token | ⊙ FileReadCapability cap | cap: FileReadCapability cap | capability: FileReadCapability cap |
| 24 | Array literal | [90, 85, 76] | [90, 85, 76] | [90, 85, 76] |
| 25 | Separator | , | , | , |
| 26 | Block comment | /* comment */ | /* comment */ | /* comment */ |
| 27 | Break | ⏹; | break; | break; |
| 28 | Continue | ▶; | continue; | continue; |
| 29 | While loop (unbounded) | ⥁ (x > 0) {} | while (x > 0) {} | whileLoop (x > 0) {} |
| 30 | Void / no return value | ▢ greet() {} | void greet() {} | void greet() {} |
| 31 | Char type usage | § ℂ c <- 'a'; | var: char c <- 'a'; | container: character c <- 'a'; |
| 32 | Statement terminator | ; | ; | ; |
| 33 | String escapes | "line\n\"quoted\"" | "line\n\"quoted\"" | "line\n\"quoted\"" |
| 34 | Scientific notation | 1e10 | 1e10 | 1e10 |
| 35 | Hex / binary / octal | 0x1F / 0b1010 / 0o17 | 0x1F / 0b1010 / 0o17 | 0x1F / 0b1010 / 0o17 |
| 36 | Inline conditional expression | § ℤ max <- ? (a > b) : { ↵ a } ⋮ { ↵ b }; | var: int max <- if (a > b) then { ret a } else { ret b }; | container: integer max <- if (a > b) then { return a } else { return b }; |
| 37 | Multi-dimensional array | ⟦τ ⟦τ ℤ, σ 10⟧, σ 10⟧ grid; | arr[type: arr[type: int, size: 10], size: 10] grid; | list[type: list[type: integer, size: 10], size: 10] grid; |
| 38 | Vector (growable) experimental | ⦃τ ℤ⦄ nums; | vec[type: int] nums; | vector[type: integer] nums; |
2Operators
| Operator | Real | Dev | Normie |
|---|---|---|---|
| Add | + | + | + |
| Subtract | - | - | - |
| Multiply | * | * | * |
| Divide | / | / | / |
| Modulo | % | % | % |
| Equal to | = | = | = |
| Not equal | != alt: ≠ | != | != |
| Greater than | > | > | > |
| Less than | < | < | < |
| Greater or equal | >= | >= | >= |
| Less or equal | <= | <= | <= |
| And | & alt: ∧ | && experimental | & |
| Or | | alt: ∨ | || experimental | | |
| Not | ! | ! | ! |
| Assignment | <- | <- | <- |
| Bitwise — experimental | |||
| Bitwise AND | ∩ | & | bitwiseAnd |
| Bitwise OR | ∪ | | | bitwiseOr |
| Bitwise XOR | ⊕ | ^ | bitwiseXor |
| Bitwise NOT | ∼ | ~ | bitwiseNot |
| Left shift | ≪ | << | shiftLeft |
| Right shift | ≫ | >> | shiftRight |
3Primitive Types
| Type | Real | Dev | Normie |
|---|---|---|---|
| Integer (signed, default 32-bit) | ℤ (= ℤ32) | int (= i32) | integer (= integer32) |
| Integer (signed, explicit width) | ℤ8 ℤ16 ℤ32 ℤ64 | i8 i16 i32 i64 | integer8 integer16 integer32 integer64 |
| Integer (unsigned, default 32-bit) | ℕ (= ℕ32) | u32 | wholeNumber (= wholeNumber32) |
| Integer (unsigned, explicit width) | ℕ8 ℕ16 ℕ32 ℕ64 | u8 u16 u32 u64 | wholeNumber8 … wholeNumber64 |
| Float (default 32-bit) | ℝ (= ℝ32) | float (= f32) | float (= float32) |
| Float (explicit width) | ℝ32 ℝ64 | f32 f64 | float32 float64 |
| Boolean | 𝔹 | bool | boolean |
| String | 𝕊 | str | string |
| Char | ℂ | char | character |
| Null | ∅ | null | null |
| Array (fixed, bounded) experimental | ⟦ τ type, σ size ⟧ | arr[type: t, size: n] | list[type: T, size: N] |
| Vector (growable, audited) experimental | ⦃ τ type ⦄ | vec[type: t] | vector[type: T] |
4Master Symbol Table
| Meaning | Real | Dev | Normie |
|---|---|---|---|
| Function declaration | :: | fn | function |
| Variable declaration experimental | § | var: | container: |
| Type label (in array) experimental | τ | type: | type: |
| Size label (in array) experimental | σ | size: | size: |
| Assignment | <- | <- | <- |
| Return type in signature | -> | -> | returnType |
| Return value from body | ↵ | ret | return |
| Function call | ↯ | call | runFunction |
| Conditional | ? | if | if |
| Then (conditional branch) | : | then | then |
| Else (conditional branch) | ⋮ | else | else |
| Statement terminator | ; | ; | ; |
| Pattern match | ⋈ | match | check |
| Loop (bounded) | ↻ | for | loop |
| While loop (unbounded) | ⥁ | while | whileLoop |
| Break | ⏹ | break | break |
| Continue | ▶ | continue | continue |
| Record declaration | ⊞ | rec | record |
| Union declaration | ⊎ | union | either |
| Record construction | ↳ | new | new |
| Outcome handling | ⚑ | verify | verify |
| Capability token | ⊙ | cap: | capability: |
| Import | ※ | import | import |
| Void / no return value | ▢ | void | void |
| Array | ⟦ ⟧ | arr[] | list[] |
| Vector | ⦃ ⦄ | vec[] | vector[] |
| Field access | . | . | . |
| Array element access | @ | at | at |
| Separator | , | , | , |
| Block body | {} | {} | {} |
| Arguments | () | () | () |
| Comment | // | // | // |
| Block comment | /* */ | /* */ | /* */ |
| True experimental | ⊤ | true | true |
| False experimental | ⊥ | false | false |
| Integer type | ℤ | int | integer |
| Unsigned integer type | ℕ | u32 | wholeNumber |
| Float type | ℝ | float | float |
| Boolean type | 𝔹 | bool | boolean |
| String type | 𝕊 | str | string |
| Char type | ℂ | char | character |
| Null | ∅ | null | null |
5Outcome Handling
A fallible operation returns a plain two-variant union (Success / Failure), using the existing union declaration — no generics, no angle brackets. The caller handles both branches with the dedicated verify construct.
Declaring the union:
⊎ Outcome { Success(𝕊), Failure(FileNotFound) }
⚑ ↯ readFile(path) {
success(contents) → ↵ contents;
failure(err) → ↵ "error occurred";
}
verify call readFile(path) {
success(contents) -> ret contents;
failure(err) -> ret "error occurred";
}
verify runFunction readFile(path) {
success(contents) means return contents;
failure(err) means return "error occurred";
}
success(...) and failure(...) are fixed labels, identical across all three lenses.
6Operator Precedence
Ek has no implicit operator precedence table. Any expression mixing more than one category of operator (arithmetic, comparison, logical) must use explicit parentheses. This rule is identical across all three lenses.
(a + b) > c // valid
a + b > c // rejected — ambiguous, no implicit precedence
(a & b) | c // valid
a & b | c // rejected — ambiguous
7Worked Example
Full-coverage example showing every major construct across all three lenses.
※ utils;
/* User record: stores a name and an age,
used across the file read and greeting logic below */
⊞ User { 𝕊 name, ℤ age };
⊎ Outcome { Success(𝕊), Failure(FileNotFound) };
equals User(a, b) -> 𝔹 {
↵ a.name = b.name;
}
:: greet(§ User u) -> 𝕊 {
§ 𝕊 greeting <- join("Hello, ", u.name);
↵ greeting;
}
:: readUserFile(§ User u, ⊙ FileReadCapability cap) -> 𝕊 {
⚑ ↯ readFile(u.name, cap) {
success(contents) → ↵ contents;
failure(err) → ↵ "could not read file";
}
}
:: isAdult(§ User u) -> 𝔹 {
? (u.age >= 18) : {
↵ ⊤;
} ⋮ {
↵ ⊥;
}
}
:: printVoid() -> ▢ {
↯ logMessage("done");
}
:: firstAdultName(§ ⟦τ User, σ 5⟧ users) -> 𝕊 {
§ ℤ i <- 0;
⥁ (i < 5) {
? (↯ isAdult(users @ i)) : {
↵ (users @ i).name;
} ⋮ {
i <- i + 1;
}
}
↵ "no adult found";
}
:: countVowels(§ 𝕊 word) -> ℤ {
§ ℤ count <- 0;
§ ℤ i <- 0;
↻ i 0..10 {
? ((word @ i) = 'a') : {
count <- count + 1;
} ⋮ {
count <- count;
}
? (i = 9) : {
⏹;
} ⋮ {
▶;
}
}
↵ count;
}
:: oldestUser(§ ⟦τ User, σ 3⟧ users) -> User {
§ ℤ oldestIndex <- 0;
§ ℤ i <- 0;
↻ i 0..3 {
? ((users @ i).age > (users @ oldestIndex).age) : {
oldestIndex <- i;
} ⋮ {
oldestIndex <- oldestIndex;
}
}
↵ users @ oldestIndex;
}
:: main(⊙ FileReadCapability cap) -> ℤ {
§ User sam <- ↳ User { name: "Sam", age: 25 };
§ User alex <- ↳ User { name: "Alex", age: 31 };
§ User priya <- ↳ User { name: "Priya", age: 42 };
§ ⟦τ User, σ 3⟧ users <- [sam, alex, priya];
§ ⟦τ ⟦τ ℤ, σ 3⟧, σ 3⟧ grid <- [[1, 2, 3], [4, 5, 6], [7, 8, 9]];
§ ℂ initial <- 'S';
§ ℤ hexValue <- 0x1F;
§ ℝ bigNumber <- 1e10;
§ 𝕊 greeting <- ↯ greet(sam);
§ 𝔹 samIsAdult <- ↯ isAdult(sam);
§ User oldest <- ↯ oldestUser(users);
§ ℤ max <- ? (sam.age > alex.age) : { ↵ sam.age; } ⋮ { ↵ alex.age; };
⋈ oldest.name {
"Priya" → ↵ 1;
_ → ↵ 0;
}
}
8Code Rules
These rules cover both lens-specific conventions and behaviors that apply identically across all three lenses.
- 1Variables are called containers — declared as
container: Type name - 2experimental
container:,type:, andsize:map to Real Code symbols (§,τ,σ) and Dev keywords (var:,type:,size:) — fully translatable across all three lenses - 3Assignment uses
<-; functions usereturnTypeandreturn; calls userunFunction - 4Conditionals use
thenbefore the block —if (x > 0) then {} else {}— Dev Lens shares this samethen/elsestructure - 5Types are full English words —
integer,string,float,boolean,null,character - 6Capability tokens use the label
capability:(Dev uses the shortercap:) - 7
whileLoopis the Normie keyword for the unbounded loop;loopremains the keyword for bounded range iteration
- 8
{},(), and//are kept as-is across all lenses - 9No
anyor wildcard types — unknown types are a compile error, not a wildcard - 10Bitwise operators are experimental, pending final design and model testing
- 11No implicit operator precedence — mixed-category expressions (
a + b > c) require explicit parentheses ((a + b) > c) in every lens - 12
-and!double as unary prefix operators (-x,!flag) in addition to their binary forms — unambiguous from grammatical position - 13Imports are resolved at compile time, never as text substitution
- 14Record construction uses
new TypeName { field: value, ... }(↳in Real Code) — named fields, mandatory declaration order, no positional shorthand - 15Capability tokens are declared like any typed parameter, but with their own dedicated label/symbol (
⊙in Real Code) to flag that the function touches the outside world - 16Array literals use plain
[ ]with comma-separated values — identical across all three lenses - 17Block comments use
/* ... */, identical across all three lenses;//remains for single-line comments - 18Every statement ends with
;— the universal statement terminator, identical across all three lenses. Two constructs are exempt because they are definitions, not statements: a function declaration (::/fn/function) and a standalone control-flow block (if/while/for/verify/match) used in statement position — both self-terminate at}with no additional;. Everything else that is a statement ends in;, includingrecord/uniondeclarations and any statement whose right-hand side happens to end in}such as an inline conditional used as a value. - 19
&and|short-circuit, matching C/Rust convention — the right side is skipped once the left side already determines the result - 20
breakandcontinueonly affect the single nearest enclosing loop — no labeled/multi-level jump - 21
voidmarks a function that returns nothing (▢in Real Code) — a deliberate, familiar exception to the "no special cases" rule - 22
character(ℂ) is a distinct type fromstring— needed for kernel/firmware/device-driver layer coverage where single-byte precision matters - 23No function overloading — one name, one function, always
- 24
mainis the required, fixed-name program entry point, identical across all three lenses - 25Declaration before use is required — a function or type must be declared before any point in the file where it is referenced, unlike Rust's order-independence
- 26Whitespace and indentation are purely cosmetic and never affect meaning — block delimiters (
{}) already make indentation-based structure unnecessary - 27A declared array size of
0is a compile error - 28String literals support backslash escapes (
\n,\t,\",\\), scientific notation (1e10), and hex/binary/octal integer literals (0x1F,0b1010,0o17) - 29String literals may span multiple lines directly in the source
- 30Inline conditional expressions reuse the existing
if/then/elseconstruct as a value — no separate ternary operator - 31There is no separate mutable/immutable distinction — linear types mean every value is used exactly once and never reassigned
- 32Multi-dimensional arrays and nested records require no new syntax
- 33Recursion requires no new syntax — a function calling itself is an ordinary function call
- 34Integers default to signed, 32-bit (
ℤ=ℤ32); unsigned integers use a distinct base symbol (ℕ); floats default to 32-bit (ℝ=ℝ32) - 35Vector (
⦃ ⦄) is the one place unbounded allocation is permitted. A record's "next" or "children" field is a plain integer index into a Vector, not a pointer. Growth happens through one audited reallocation mechanism in the standard library.
- 36Capturing closures. Whether a closure can capture a variable from its enclosing scope is undecided. The core difficulty is the interaction with linear types and capability tokens — it's unclear whether a captured linear value or capability token should be considered consumed when the closure is created, or each time the closure is called. Deferred pending further design work.