Types
Declared Types#
These are the types you can write in annotations:
int- 64-bit integerfloat- 64-bit floating pointstring- UTF-8 textbool- true/falsearr- ordered arrayobj- key/value objectfn- function/lambda value type in declarations
Note: these type names are reserved keywords. You cannot use a type name as a variable identifier
(for example let obj: obj = ...) because the lexer will treat obj as a type token,
not an identifier.
// Bad (variable name collides with the obj type keyword)
let obj: obj = { b: 1 };
// Good
let headers: obj = { b: 1 };
How The Type System Works#
Zekken is explicitly typed: declarations require a type annotation, and values are checked against that type during execution. This is different from traditional compile-time static typing.
- No inferred typing:
let x = 10is a syntax error. - Declaration checks: the initializer value must match the declared type.
- Assignment checks: reassigning a variable must keep the same declared type.
- Function parameter checks: argument values are validated against parameter types at call time.
- Containers are not generic:
arrandobjcan hold mixed runtime values.
// 1) Declaration type check
let n: int = 10;
let bad: int = 3.14; // Type Error (int <- float)
// 2) Assignment type check
let x: string = "ABC123";
x = 123 // Type Error (string <- int)
// 3) Mixed containers (no generics like arr<int>)
let mixed: arr = [1, "two", true, {a: 3}];
@println => |mixed|
Runtime Value Variants#
Runtime also includes internal/native values:
Function,NativeFunctionComplex,Vector,Matrixruntime valuesvoid
Type Methods#
Methods are called with value.method => |args|. Some methods return a new value, while others mutate the receiver
(noted below).
In the signatures below, value means “an arbitrary runtime value” (it is not a declared type you can use in annotations).
Universal Methods#
These methods exist on every runtime value.
value.format => || -> string- Pretty-print a value (multi-line for arrays/objects) for debugging/logging.
format is intended for readable output. If you need a compact single-line representation, cast to string instead
(.cast => |"string"|).
let a: arr = ["hi"];
let compact: string = a.cast => |"string"|;
let pretty: string = a.format => ||;
@println => |compact|
@println => |pretty|
String Methods#
String methods never mutate the original string (strings are values). Most return a new string.
s.length => || -> int- Character count.s.toUpper => || -> string- Uppercase conversion.s.toLower => || -> string- Lowercase conversion.s.trim => || -> string- Remove leading and trailing whitespace.s.split => |delimiter: string| -> arr- Split into an array of strings.
let s: string = " Hello World ";
let n: int = s.length => ||;
let upper: string = s.toUpper => ||;
let lower: string = s.toLower => ||;
let trimmed: string = s.trim => ||;
let parts: arr = s.split => |" "|;
Array Methods#
Array methods are a mix of read-only queries and mutating operations. Mutating methods change the array in-place.
a.length => || -> int- Number of elements.a.first => || -> value- First element (runtime error if empty).a.last => || -> value- Last element (runtime error if empty).a.join => |sep: string| -> string- Join elements with a separator (elements are stringified).a.remove => |value: value| -> value- Remove the first matching element (mutates, runtime error if not found).a.push => |value: value| -> void- Append (mutates).a.pop => || -> value- Remove last (mutates, runtime error if empty).a.unshift => |value: value| -> void- Insert at front (mutates).a.shift => || -> value- Remove first (mutates, runtime error if empty).
let nums: arr = [2, 4, 6];
let len: int = nums.length => ||;
let first: int = nums.first => ||;
let last: int = nums.last => ||;
nums.push => |8|
let removed: int = nums.pop => ||;
let front: int = nums.shift => ||;
nums.unshift => |1|
let removed_first_4: int = nums.remove => |4|;
let joined: string = nums.join => |"-"|;
Note: push, pop, shift, unshift, and remove mutate the array variable in-place, so call them on a named array (not a temporary expression).
Because arrays can contain mixed runtime values, you should annotate based on what you expect at runtime:
let a: arr = [1, "two"];
let v: int = a.first => ||; // OK (first is 1)
let w: int = a.last => ||; // Type Error (last is "two")
Object Methods#
Objects map string keys to values. These methods help you inspect and safely read fields.
o.keys => || -> arr- Array of keys.o.values => || -> arr- Array of values.o.entries => || -> arr- Array of[key, value]pairs.o.hasKey => |key: string| -> bool- Whether a key exists.o.get => |key: string, default: value| -> value- Get a key, or returndefaultif missing.
let user: obj = { name: "RAGE", score: 10 };
let keys: arr = user.keys => ||;
let values: arr = user.values => ||;
let entries: arr = user.entries => ||;
let has_name: bool = user.hasKey => |"name"|;
let title: string = user.get => |"title", "Untitled"|;
entries returns an array of [key, value] pairs.
Number Methods#
Number methods apply to int and float. Rounding methods return an int.
n.isOdd => || -> bool- True if the value is odd.n.isEven => || -> bool- True if the value is even.f.round => || -> int- Round to nearest integer.f.floor => || -> int- Round down.f.ceil => || -> int- Round up.
let a: int = 5;
let odd: bool = a.isOdd => ||;
let even: bool = a.isEven => ||;
let b: float = 7.9;
let r: int = b.round => ||;
let f: int = b.floor => ||;
let c: int = b.ceil => ||;
float also supports isOdd and isEven (based on the numeric value).
Casting#
All values support .cast => |"type"| for supported conversions.
let i: int = "42".cast => |"int"|;
let f: float = "3.14".cast => |"float"|;
let b: bool = "true".cast => |"bool"|;
let s: string = 99.cast => |"string"|;
Supported cast targets: "int", "float", "bool", "string".
Note: casting from string to a numeric/bool type only works for valid values (e.g. "10", "3.14", "true").