The whole of it: forty-one keywords, one way to say each thing.
This page is form only: how to write Wantzel. What it means is the language reference, which is binding. For idioms and pitfalls see Writing Wantzel; for project-level agreements (entry points, name prefixes) see the Conventions section of the how-to.
The forty-one keywords
and array begin bool break char const continue
div do downto else end false for forward
function if import include int local mod not
of or procedure real record return schema shl
shr str then to tools true type var
whileCase does not distinguish names: Foo, foo and FOO are one name, and so are STORE.SET and store.set — a constant in capitals beside a routine in lower case is a collision, not a convention.
local in front of a top-level var, const, procedure or function keeps that name inside its file; nothing outside can see it. Public is still the default.
A program
import io; // a module of the standard library: a name, no quotes
// (a file of your own is `include "shapes.wz";`: in quotes)
const
MAX = 100;
GREETING = "hello";
type
Point = record
x: int;
y: int;
end;
var
count: int;
grid: array[0..MAX - 1] of Point;
function twice(n: int): int;
begin
return n * 2;
end;
procedure greet(who: str);
begin
io.puts(STDOUT, who);
end;
begin // the main block, last
count := twice(21);
greet(GREETING);
end. // a full stop, not a semicolonFixed order: imports and includes, then declarations, then the main block. No header line — a file starts with its first declaration. import names a module of the library inside the compiler; include names a file, relative to the file it stands in — the rules are in the language reference. A routine must be declared before use, or forward.
Types
int | 64-bit signed integer |
real | 64-bit float |
bool | true or false |
char | one byte |
str | a string literal; not a buffer you build |
array[a..b] of T | fixed size, known at compile time |
record ... end | fields, no methods, no inheritance |
One type per concept: no integer beside int, no single beside real. Text you assemble lives in an array of char; str is what you write between quotes.
var
n: int;
ratio: real;
ok: bool;
c: char;
name: str;
buf: array[0..255] of char;
grid: array[0..799] of char; // 10 rows of 80: row * 80 + col, no nested arrayDeclaring
const
MAX = 100; // the type follows from the value
PI = 3.14159;
type
Colour = record
r, g, b: int; // several names, one type
end;
var
i, j, k: int; // the same
p: Colour;schema and tools
type ConvertArgs = schema
celsius: real;
end;
tools
convert(ConvertArgs): ConvertResult "Convert Celsius to Fahrenheit." readonly idempotent;
end;A schema is a JSON-shaped record type; a tools ... end; block declares an MCP tool table, one line per tool, and the compiler generates its tools/list, argument parsing and dispatch. Form only, here — what each generates is the language reference §7 and §7b, which are binding.
Routines
A function returns a value; a procedure does not. Parameters are by value, except an array of T, passed as a reference with its length.
function area(w: int; h: int): int;
begin
return w * h;
end;
procedure fill(a: array of char; c: char);
var i: int; // locals go here, before begin
begin
i := 0;
while i < len(a) do begin a[i] := c; i := i + 1; end;
end;
function later(n: int): int; forward; // declared now, defined belowA function that reaches end without return is a runtime error, not a silent zero.
Statements
x := 1; // assignment
greet("hi"); // a call
if x > 0 then y := 1; // one statement
if x > 0 then y := 1 else y := 2;
if x > 0 then // several: begin ... end
begin
y := 1;
z := 2;
end;
while x < 10 do x := x + 1;
for i := 1 to 10 do total := total + i;
for i := 10 downto 1 do total := total + i;
while true do
begin
if done then break; // leave the loop
if skip then continue; // next turn
end;
return v; // from a function
return; // from a procedure
halt(1); // end the program with an exit codeSemicolons separate statements; the one before end is optional. end. closes the program.
Operators
| arithmetic | + - * / div mod |
| comparison | = <> < <= > >= |
| logical | and or not |
| bitwise | shl shr, and band bor bxor as builtins |
/ is real division, div is integer division; mixing them by accident is refused, not rounded. There is no ++, no +=, no ?: and no assignment inside an expression.
Comments
// to the end of the lineThat is the only form. { } was removed in 0.2.0: it ended at the first }, so a brace inside the comment text turned the rest of the sentence into code.
Literals
42 -1 1000000 // integers
3.14 1.0e-9 // reals
'a' '\n' '\x41' // chars
"text" "line\n" "\x00\xff" // strings
true false // bools\xHH takes exactly two hex digits, unlike C — so "\x41BC" is three characters, and a generator can write a byte followed by a literal hex character without ambiguity.
Names
A name starts with a letter and may contain letters, digits, underscores and dots — the dot is an ordinary character, used to group names that belong together:
calc.reset;
io.puts(STDOUT, "hi");
store.set(k, v);That is a naming convention, not a module system: one flat namespace, and calc.reset is simply a name with a dot in it. A part after a dot may start with a digit (Reading.level.1); the first part may not.
What is not here
No pointers, no heap, no new, no garbage collector. No classes, no inheritance, no interfaces. No generics, no overloading, no operator overloading. No exceptions, no threads, no closures, no lambdas. No implicit conversion between types.
Each is a decision, not an omission; the design document argues them. Short version: a construct that costs more than it gives is left out, because the reader of this code is usually a generator in a loop, and every extra way to say something is another way to get it wrong.