Zekken Documentation

Core syntax reference for declarations, control flow, modules, and calls.

Section 2 of 8

Syntax Basics

Quick Rules#

  • No inferred typing: let/const require an explicit type annotation.
  • Blocks use braces: { ... } for if, while, for, try, catch, and functions.
  • Calls use pipes: callee => |arg1, arg2| and zero-arg calls use ||.
  • Declarations end with ;: variable declarations and some import/export forms require semicolons.
  • Most other statements do not use semicolons (assignments, updates, @println, loops).

Zekken is intentionally small and consistent: you write typed declarations up front, then use expression-style statements inside blocks. Whitespace and newlines are flexible; the main “hard” separators are braces for blocks and semicolons for declarations (and return).

Comments#

Comments are ignored by the runtime and are useful for documenting intent. Use // for single-line notes and /* ... */ to comment out larger regions.

// Single-line comment

/*
  Multi-line comment
*/

Variables And Constants#

Zekken requires explicit types for variables and constants. Use let for mutable bindings and const for values that should never be reassigned. Reassignments must keep the same declared type.

let x: int = 45;
const pi: float = 3.14;
let name: string = "RAGE";
let ok: bool = true;

let nums: arr = [1, 2, 3];
let profile: obj = { name: "RAGE", level: 5 };

Gotcha: type keywords are reserved and cannot be used as variable names.

// Invalid: `obj` is a reserved type keyword
let obj: obj = { b: 1 };
Syntax Error: Type names are reserved and cannot be used as variable identifiers
     | main.zk -> [Ln: 1, Col: 5]
     |
   1 | let obj: obj = { b: 1 };
     |     ^~~
  expected: Identifier
  found:    DataType(Object) (obj)

Invalid (inferred typing is not supported):

let y = 10

Literals#

Literals are the simplest values in the language: numbers, booleans, strings, arrays, and objects. Arrays and objects can be nested to represent structured data.

let i: int = 123;
let f: float = 3.14;
let b: bool = false;
let s: string = "hello";

let a: arr = [1, 2, 3];
let o: obj = { hello: "world" };

// Object keys can be identifiers or strings.
let headers: obj = { "content-type": "text/plain" };

Strings support escape sequences such as \n (newline), \t (tab), \xNN (byte), and \e (escape).

Member Access And Indexing#

Use dot access (obj.key) for identifier keys, and bracket access (obj["key"]) for string keys. Brackets also allow dynamic access via variables or indices. Arrays are indexed with brackets starting at 0.

use math;

@println => |math.PI|

let array: arr = [10, 20, 30];
@println => |array[0]|

let object: obj = { a: 1, "b-key": 2, c: 3 };
let key: string = "c";

@println => |object.a|
@println => |object["b-key"]|
@println => |object[key]|

Imports#

Use use to bring a standard library module into scope as an object. You can also import specific members for convenience. Use include to pull in another Zekken file (and optionally import only its exported values).

use math;
use fs;
use { read_file, write_file } from fs;

include "other.zk";
include { some_value, helper } from "other.zk";

Imported members are called like normal functions. Built-ins are native functions prefixed with @.

Export#

Export makes values available to other files via include { ... } from "file.zk". It’s common to export constants and helper functions from a module-style file.

export x, name, nums;

Functions#

Functions declare typed parameters and use return to produce a value. The return statement ends with a semicolon. Functions are values: you can store them in variables, pass them around, and call them with => |...|.

func add |a: int, b: int| {
  return a + b;
}

let sum: int = add => |1, 2|;

Control Flow#

Conditionals use if/else. Loops use while for repetition and for for iterating arrays/objects. In a for loop, you can bind one variable (value/item) or two variables (index+value for arrays, key+value for objects). Use _ to ignore a binding.

Zekken also supports the in operator inside conditions and expressions: it checks membership in arrays, key existence in objects, and substring existence in strings.

let tasks: arr = ["read", "ship"];
let task: string = "ship";

if task in tasks {
  @println => |"task exists"|
}

let profile: obj = { name: "RAGE" };
if "name" in profile {
  @println => |profile["name"]|
}
if x == 45 {
  @println => |"x matched"|
} else {
  @println => |"no match"|
}

while x > 0 {
  x -= 1
}

for |key, value| in profile {
  @println => |key|
  @println => |value|
}

for |item| in nums {
  @println => |item|
}

Array loops can also capture the index:

for |i, _| in nums {
  @println => |nums[i]|
}

Errors With Try/Catch#

try/catch is for handling runtime errors (for example, file IO failures). The catch binding receives an error message you can log or format.

try {
  let content: string = fs.read_file => |"missing.txt"|;
} catch |e| {
  @println => |"caught: " + e|
}

Call Syntax#

Zekken calls use => |...|. Zero-arg calls use ||. For readability, it’s common to assign intermediate results instead of heavily nesting call syntax.

@println => |"hello"|
let sum: int = add => |1, 2|;
let first: int = nums.first => ||;
@println => |sum|

Statement Terminators#

  • let/const declarations end with ;.
  • use/include/export statements end with ;.
  • return ends with ;.
  • Assignments, updates, calls, and control-flow blocks typically do not use semicolons.