Gizlang language guide
This guide is for developers who know TypeScript, JavaScript or Go. You do not need to know Gizlang.
- Part 1 is a tutorial. In approximately 15 minutes, you write, run and test a small program.
- Part 2 is a tour of the main features. Each section is short and has one example.
Every example on this page was compiled and run with the Gizlang toolchain. The output under each example is the real output.
Tutorial: your first Gizlang program
In this tutorial, you write a small shopping cart program. It calculates a total and refuses a payment that is over a budget. Then you add tests.
Before you start
You need the giz command. To get it, refer to the installation guide. To try a program without an installation, use the Playground.
Step 1: Hello, world
Make a file with the name hello.giz:
function main(): void {
print("Hello, Gizlang.");
}Run it:
giz hello.gizHello, Gizlang.mainis the entry point.: voidtells that the function gives no result.printwrites a line to the console.
Step 2: A function and a type
Make a file with the name cart.giz:
type Item = { name: string; price: i64; qty: i64; };
function lineTotal(item: Item): i64 {
return item.price * item.qty;
}
function main(): void {
const pen: Item = { name: "pen", price: 3, qty: 2 };
print(pen.name + ": " + string(lineTotal(pen)));
}giz cart.gizpen: 6type Item = { ... }declares a record. A record is a value with fixed fields.i64is a 64-bit integer.- Each parameter and each result has a type.
string(x)converts a number to text. Gizlang does not convert types automatically."a" + 1is a compile error.
Step 3: A list of items
Replace main and add cartTotal:
type Item = { name: string; price: i64; qty: i64; };
function lineTotal(item: Item): i64 {
return item.price * item.qty;
}
function cartTotal(items: Vec<Item>): i64 {
let sum = 0;
for (const item of items) {
sum += lineTotal(item);
}
return sum;
}
function main(): void {
let items: Vec<Item> = [{ name: "pen", price: 3, qty: 2 }];
items.push({ name: "pad", price: 5, qty: 1 });
print(string(items.length) + " items, total " + string(cartTotal(items)));
}2 items, total 11Vec<Item>is a list ofItemvalues. You can also writeItem[].letdeclares a variable that you can change.constdeclares a value that you cannot change.items.push(x)addsxto aletlist in place.
Step 4: Errors
A function that can fail says so in its signature: throws and the type of the error. Add a checkout function and a report function:
import {Result} from "gizlang:core";
type Item = { name: string; price: i64; qty: i64; };
function lineTotal(item: Item): i64 {
return item.price * item.qty;
}
function cartTotal(items: Vec<Item>): i64 {
let sum = 0;
for (const item of items) {
sum += lineTotal(item);
}
return sum;
}
function checkout(items: Vec<Item>, budget: i64): i64 throws string {
const total = cartTotal(items);
if (total > budget) {
throw "over budget by " + string(total - budget);
}
return budget - total;
}
function report(items: Vec<Item>, budget: i64): void {
try {
const left = checkout(items, budget);
print("paid, " + string(left) + " left");
} catch (message) {
print("refused: " + message);
}
}
function main(): void {
const items: Vec<Item> = [
{ name: "pen", price: 3, qty: 2 },
{ name: "pad", price: 5, qty: 1 },
];
report(items, 20);
report(items, 10);
}paid, 9 left
refused: over budget by 1: i64 throws stringmeans the function gives ani64or fails with astring.throwfails it.- A call gives the
i64. When the call fails, thetryblock goes to itscatch. A function that does not catch the failure must be athrowsfunction itself, so the failure goes on to its caller. - Under the signature, the function returns a
Result<i64, string>. A place that expects aResulttakes it as it is, andmatchreads its cases. Amatchmust write one arm for each case.
Step 5: Tests
A test is a function with a name that starts with test. It has no parameters and the result void.
Add the import of
gizlang:testas line 2 ofcart.giz, and two test functions at the end. The file is now:import {Result} from "gizlang:core"; import * as Test from "gizlang:test"; type Item = { name: string; price: i64; qty: i64; }; function lineTotal(item: Item): i64 { return item.price * item.qty; } function cartTotal(items: Vec<Item>): i64 { let sum = 0; for (const item of items) { sum += lineTotal(item); } return sum; } function checkout(items: Vec<Item>, budget: i64): i64 throws string { const total = cartTotal(items); if (total > budget) { throw "over budget by " + string(total - budget); } return budget - total; } function report(items: Vec<Item>, budget: i64): void { try { const left = checkout(items, budget); print("paid, " + string(left) + " left"); } catch (message) { print("refused: " + message); } } function main(): void { const items: Vec<Item> = [ { name: "pen", price: 3, qty: 2 }, { name: "pad", price: 5, qty: 1 }, ]; report(items, 20); report(items, 10); } function testCartTotal(): void { const items: Vec<Item> = [{ name: "pen", price: 3, qty: 4 }, { name: "pad", price: 5, qty: 1 }]; Test.equal(cartTotal(items), 17); } function testCheckoutRefusesOverBudget(): void { const items: Vec<Item> = [{ name: "pen", price: 3, qty: 2 }]; const refused: Result<i64, string> = Result.err("over budget by 2"); const answer: Result<i64, string> = checkout(items, 4); Test.equal(answer, refused); }Run the tests:
giz test cart.giz0 cached, 2 run, 0 failedRun the tests again:
2 cached, 0 run, 0 failedThe runner records each pass. It runs a test again only when the code that the test uses changes.
To see a failure, change 17 to 18 and run the tests:
fail testCartTotal R004
gizlang: R004 in equal line 2 (actual = 17, expected = 18) <- testCartTotal (cart.giz:44)
gizlang: replay: function testReplayEqual(): void { equal(17, 18); }
test runner failed or returned an incomplete verdict (1/1)
1 cached, 1 run, 2 failed- The failure shows the actual and expected values, and the line of the test.
- The
replayline is a test that repeats the failed call. - The command exits with status 1.
Change 18 back to 17.
Step 6: Run and check
giz cart.gizbuilds and runs the program.giz test cart.gizruns the tests of the file.giz check cart.gizchecks the file for errors without a run ofmain. When the check passes, it also runs the tests that changed.
You wrote a program with a record type, a list, a loop, error handling and tests. The tour that follows shows more of the language.
Language tour
Values and types
function main(): void {
const count: i64 = 7;
const ratio: f64 = 0.5;
const big: u64 = 18446744073709551615u;
const ready: bool = true;
const name: string = "Gizlang";
const raw: bytes = b"GIZ";
print(string(count / 2));
print(string(f64(count) * ratio));
print(string(big));
print(string(ready));
print(name + " has " + string(name.length) + " bytes");
print(string(raw.length));
print(string("한글".length));
}3
3.5
18446744073709551615
true
Gizlang has 7 bytes
3
6| Type | Meaning |
|---|---|
i64, u64 | Checked 64-bit integers. Write a u64 literal with the suffix u. |
f64 | A 64-bit IEEE float. number is the same type. |
bool | true or false. boolean is the same type. |
string | UTF-8 text. .length and indexes count bytes, not characters. |
bytes | Immutable bytes. Write a literal as b"...". |
- Integer
/truncates. - There are no implicit conversions. Use
i64(x),u64(x),f64(x)andstring(x). - Integer overflow, division by zero and a bad index are faults. A fault stops the program. It is not a value.
function double(x: i64): i64 {
return x * 2;
}
function main(): void {
print(string(double(9223372036854775807)));
}gizlang: R003 in double line 2 (x = 9223372036854775807) <- main line 2
gizlang: replay: function testReplayDouble(): void { double(9223372036854775807); }
runtime R003The fault report names the function, its arguments and the call path. The program exits with status 1.
Variables
const LIMIT = 3;
function main(): void {
const greeting = "hi";
let total = 0;
total += LIMIT;
total = total * 10;
let words: Vec<string> = [];
words.push(greeting);
words.push("there");
print(string(total) + " " + string(words.length));
}30 2constdeclares an immutable binding.letdeclares a mutable binding.- A module-level
constmust be a literal. Module-levelletis not available. - You cannot assign to a
const:
function main(): void {
const total = 1;
total = 2;
print(string(total));
}giz check const_error.gizconst_error.giz:3:5: error E_IMMUTABLE in main: cannot assign to, or pass as mut, a const binding or an immutable value: declare it with let
total = 2;
^^^^^- An inner scope cannot declare a name again:
function main(): void {
const total = 1;
if (total > 0) {
const total = 5;
print(string(total));
}
}shadow.giz:4:15: error E_SHADOW in main: a local cannot reuse a name already in scope; rename it
const total = 5;
^^^^^Functions and closures
function add(a: i64, b: i64): i64 {
return a + b;
}
function apply(f: (x: i64) => i64, value: i64): i64 {
return f(value);
}
function makeAdder(n: i64): (x: i64) => i64 {
return (x: i64): i64 => { return x + n; };
}
function main(): void {
const addTen = makeAdder(10);
const double = (x: i64): i64 => { return x * 2; };
print(string(add(2, 3)));
print(string(apply(addTen, 5)));
print(string(apply(double, 5)));
const steps: Vec<(x: i64) => i64> = [addTen, double];
let value = 1;
for (const step of steps) {
value = step(value);
}
print(string(value));
print(string([1, 2, 3].map(double).length));
}5
15
10
22
3- Declare a function with
function name(param: Type): Result. - An arrow function
(x: T): R => { ... }is a closure. - A function type
(x: T) => Ris a value type. A parameter, a result, a field or aVecelement can have it. - A closure copies the values that it captures. It cannot capture a
letlocal:
function main(): void {
let count = 0;
const next = (): i64 => { return count + 1; };
print(string(next()));
}capture.giz:3:38: error E_ESCAPE in main: a closure cannot capture a mutable local or a view `count`: bind a const copy before the closure (const copy = count;) and use that
const next = (): i64 => { return count + 1; };
^^^^^Records and unions
type Point = { x: i64; y: i64; };
type Shape =
| { kind: "circle"; radius: f64; }
| { kind: "rect"; width: f64; height: f64; };
function Point.add(self: Point, other: Point): Point {
return { x: self.x + other.x, y: self.y + other.y };
}
function moveRight(p: mut Point, dx: i64): void {
p.x += dx;
}
function area(shape: Shape): f64 {
match (shape) {
Shape.circle(radius) => { return 3.14159 * radius * radius; }
Shape.rect(width, height) => { return width * height; }
}
}
function main(): void {
const a: Point = { x: 1, y: 2 };
let b = a;
b.x = 10;
moveRight(b, 5);
const c = a + b;
const d = { ...c, y: 0 };
print(string(a.x) + " " + string(b.x) + " " + string(c.x) + "," + string(c.y) + " " + string(d.y));
const same: Point = { x: 1, y: 2 };
print(string(a === same));
const shapes: Vec<Shape> = [Shape.circle(1.0), { kind: "rect", width: 2.0, height: 3.0 }];
for (const s of shapes) {
print(string(area(s)));
}
print(string(shapes[1].kind === "rect"));
}1 15 16,4 0
true
3.14159
6
true- Records are values.
let b = acopiesa. A change tobdoes not changea. - A
letlocal changes in place at any field path, for exampleb.x = 10. - A function changes a value of its caller only through a
mutparameter. function Point.add(self: Point, ...)declares a method. Call it asa.add(b).- An operator on a record is its method of that name:
a + bisa.add(b). The others are- * /(sub, mul, div),< <= > >=(lt, le, gt, ge) and unary-(neg). { ...c, y: 0 }copiescand replaces one field. The spread source must be a name, a field or an element, not a call.- A union is a set of cases. The string field
kindnames the case. Make a case withShape.circle(1.0)or with an object literal that has an expected type. ===compares records by their fields.- There are no classes and no inheritance.
Interfaces and generics
import * as Text from "gizlang:text";
interface Named {
label(self: Self): string;
}
type User = { name: string; };
type Team = { name: string; size: i64; };
function User.label(self: User): string {
return "user " + self.name;
}
function Team.label(self: Team): string {
return "team " + self.name + " (" + string(self.size) + ")";
}
function shout<T extends Named>(item: T): string {
return item.label().toUpperCase();
}
type Box<T> = { value: T; };
function main(): void {
const u: User = { name: "ana" };
const t: Team = { name: "core", size: 4 };
const all: Vec<Named> = [u, t];
for (const n of all) {
print(n.label());
}
print(shout(t));
const b: Box<string> = { value: "boxed" };
print(b.value);
}user ana
team core (4)
TEAM CORE (4)
boxed- An interface is a set of methods. Each method has
self: Selfas its first parameter. - A type implements an interface when it has those methods. You do not declare
implements. - Use an interface as a bound (
<T extends Named>) or as a type (Vec<Named>). - The compiler specializes generics for each type argument.
- String methods such as
toUpperCasecome fromgizlang:text, which every program has.
Collections
function main(): void {
const nums: Vec<i64> = [5, 3, 8, 1];
const big = nums.filter(n => n > 2);
const total = nums.reduce((acc, n) => acc + n, 0);
const order = nums.toSorted((a, b) => a - b);
print(`${big.length} ${total} ${order[0]}`);
print(`${nums.findIndex(n => n === 8)} ${nums.includes(3)} ${nums.at(-1) ?? 0}`);
print(string(nums.slice(1, 3).length));
const words = "the cat saw the dog".split(" ");
const counts = new Map<string, i64>();
for (const w of words) {
counts.merge(w, 1, (a, b) => a + b);
}
print(`${counts.get("the") ?? 0} ${counts.get("cow") ?? 0}`);
print(counts.keys().join(","));
const seen = new Set<string>();
seen.add("a");
seen.add("a");
print(`${seen.size} ${seen.has("a")}`);
}3 17 1
2 true 1
2
2 0
the,cat,saw,dog
1 trueVec<T>is a value.v.map(f),v.slice(start, end),v.lengthandv.push(x)are built in.filter,find,findIndex,some,every,reduce,includes,indexOf,at,concat,sortedandcountcome fromgizlang:collections, which every program has: call them as methods with no import.findIndexandindexOfgive -1 when no element matches.findandatgive an optional value.new Map<K, V>()andnew Set<K>()make a map and a set. They keep insertion order.m.get(k)gives an optional value.m.get(k) ?? fallbackreads it with a default.
Control flow
function label(n: i64): string {
if (n % 15 === 0) {
return "FizzBuzz";
} else if (n % 3 === 0) {
return "Fizz";
} else if (n % 5 === 0) {
return "Buzz";
}
return string(n);
}
function main(): void {
let line = "";
for (const i of range(1, 16)) {
if (i === 8) {
continue;
}
line = line + label(i) + " ";
}
print(line);
let n = 27;
let steps = 0;
while (n !== 1) {
let next = n / 2;
if (n % 2 === 1) {
next = 3 * n + 1;
}
n = next;
steps += 1;
if (steps > 1000) {
break;
}
}
print("steps: " + string(steps));
}1 2 Fizz 4 Buzz Fizz 7 Fizz Buzz 11 Fizz 13 14 FizzBuzz
steps: 111if,else if,while,breakandcontinuework as in TypeScript.for (const i of range(lo, hi))counts fromlotohi - 1.for (const x of v)reads each element of aVec.matchreads a union. Refer to Records and unions.- The
? :operator andswitchare not available. Useifandmatch.
Errors: Result, Option and try
import {Result, Option} from "gizlang:core";
import {parseI64, IntegerParse} from "gizlang:text";
function parseAge(text: string): Result<i64, string> {
match (parseI64(text)) {
IntegerParse.ok(n) => {
if (n < 0) {
return Result.err("negative: " + text);
}
return Result.ok(n);
}
IntegerParse.invalid(at) => { return Result.err("not a number: " + text); }
IntegerParse.overflow(at) => { return Result.err("too large: " + text); }
}
}
function ageNextYear(text: string): Result<i64, string> {
const age = try parseAge(text);
return Result.ok(age + 1);
}
function firstLong(words: Vec<string>): Option<string> {
for (const w of words) {
if (w.length > 3) {
return Option.some(w);
}
}
return Option.none();
}
function main(): void {
for (const input of ["41", "-2", "abc"]) {
match (ageNextYear(input)) {
Result.ok(n) => { print("next year: " + string(n)); }
Result.err(e) => { print("error: " + e); }
}
}
print(string(ageNextYear("x").valueOr(0)));
print(firstLong(["a", "tree"]) ?? "none");
print(firstLong(["a"]) ?? "none");
print(firstLong(["house"])!);
}next year: 42
error: negative: -2
error: not a number: abc
0
tree
none
houseResult<T, E>has the casesok(value)anderr(error).Option<T>has the casessome(value)andnone(). Import them fromgizlang:core.try egives the value ofokorsome. For any other case,tryreturns that case from the function. The result type of the function must have the same case.result.valueOr(fallback)andoption ?? fallbackgive a default.x!gives the value. When there is no value, it faults.- A match arm binds the whole payload of its case.
IntegerParse.invalidhas a payload (the byte position), so the arm isIntegerParse.invalid(at). throwandtry-catchwork withthrows:function f(): T throws Ereturns aResultthat its callers take asT(see Step 4: Errors).finallyruns when the statement ends.
Modules and imports
A file is a module. Use export to make a declaration visible to other modules. Make a folder with these three files.
geometry.giz:
/** A point on a grid. */
export type Point = { x: i64; y: i64; };
/** The Manhattan distance between two points. */
export function distance(a: Point, b: Point): i64 {
return abs(a.x - b.x) + abs(a.y - b.y);
}
function abs(n: i64): i64 {
if (n < 0) {
return -n;
}
return n;
}main.giz:
import {Point, distance} from "./geometry.giz";
import * as Text from "gizlang:text";
function main(): void {
const a: Point = { x: 0, y: 0 };
const b: Point = { x: 3, y: -4 };
const parts = [string(distance(a, b)).padStart(4, "."), "steps"];
print(Text.join(parts, " "));
}giz main.giz...7 stepsgeometry.test.giz:
import {Point, distance} from "./geometry.giz";
import * as Test from "gizlang:test";
function testDistance(): void {
const a: Point = { x: 1, y: 1 };
const b: Point = { x: 4, y: 5 };
Test.equal(distance(a, b), 7);
Test.equal(distance(b, a), 7);
}giz test geometry.test.giz0 cached, 1 run, 0 failed- Import a file of your project by a relative path:
"./geometry.giz". - Import an SDK module by name:
"gizlang:text","gizlang:collections","gizlang:core". - Named imports (
{a, b}), namespace imports (* as Text) and aliases work as in TypeScript. abshas noexport. Other modules cannot import it.- Put the tests of
X.gizinX.test.gizbeside it.giz check X.gizalso runs them. /** ... */before a declaration is a doc comment. Tools read it.
Packages
A package is a folder with a package.json. The manifest names the entry and the modules, and pins the sha256 of each source. Use the same geometry.giz and this main.giz:
import {Point, distance} from "geometry";
function main(): void {
const a: Point = { x: 0, y: 0 };
const b: Point = { x: 2, y: 5 };
print("distance: " + string(distance(a, b)));
}package.json:
{
"name": "shapes",
"private": true,
"gizlang": {
"entry": { "path": "main.giz", "sha256": "" },
"modules": {
"geometry": { "path": "geometry.giz", "sha256": "" }
}
}
}Write the pins, then run the package:
giz pin .
giz package.jsondistance: 7giz pin DIRwrites the sha256 of each source intopackage.json.- Inside a package, a module imports another module by its manifest name:
"geometry". - When a source changes after the pin, the package does not build.
giz get --frozen DIRnames the file:
giz get --frozen .gizlang: geometry.giz is missing or does not match its sha256Run giz pin . again after each intended change. For dependencies and registries, refer to the package guide.
Tasks and channels
Gizlang has no async or await. A task is a closure that runs concurrently. sleep, join and I/O suspend only the calling task.
import * as Task from "gizlang:task";
import {Result, Option} from "gizlang:core";
function fib(n: i64): i64 {
if (n < 2) {
return n;
}
return fib(n - 1) + fib(n - 2);
}
function main(): void {
const slow = Task.spawn(() => { Task.sleep(20); return fib(20); });
const fast = Task.spawn(() => { return fib(10); });
match (Task.join(fast)) {
Result.ok(n) => { print("fast: " + string(n)); }
Result.err(f) => { print("fast failed: " + string(f.code)); }
}
match (Task.join(slow)) {
Result.ok(n) => { print("slow: " + string(n)); }
Result.err(f) => { print("slow failed: " + string(f.code)); }
}
const lines = Task.channel<string>(4);
Task.spawn(() => {
for (const word of ["one", "two", "three"]) {
Task.send(lines, word);
}
Task.close(lines);
return 0;
});
let more = true;
while (more) {
match (Task.receive(lines)) {
Option.some(word) => { print("got " + word); }
Option.none() => { more = false; }
}
}
const squares = Task.parallel(4, (i: i64): i64 => { return i * i; });
print("squares: " + string(squares[3]));
}fast: 55
slow: 6765
got one
got two
got three
squares: 9Task.spawn(f)starts a task.Task.join(t)waits for it and gives aResult.Task.channel<T>(capacity)makes a bounded queue.sendwaits while the queue is full.receivegivesOption.none()when the queue is closed and empty.Task.parallel(n, body)runsbody(i)on the CPU threads and gives the results in index order. The body can only compute. It cannot do I/O or print.- A task cannot live longer than the task that started it.
A fault in a task stops only that task. The parent gets it as Result.err:
import * as Task from "gizlang:task";
import {Result} from "gizlang:core";
function divide(a: i64, b: i64): i64 {
return a / b;
}
function main(): void {
const bad = Task.spawn(() => { return divide(1, 0); });
match (Task.join(bad)) {
Result.ok(n) => { print("value: " + string(n)); }
Result.err(f) => { print("task fault: " + string(f.code)); }
}
print("main continues");
}task fault: 2
main continuesFault 2 is division by zero.
Files and the network
A program gets access to the outside world through one value: main(sys: System). Library code cannot reach files, the network, the clock or the environment unless the program gives it the System value or a value made from it.
import {System, IoError} from "gizlang:system";
import {Result} from "gizlang:core";
function saveAndCount(sys: System, name: string): Result<i64, IoError> {
const dir = try sys.files();
try dir.writeFile(name, utf8("line one\nline two\n"));
const data = try dir.readFile(name);
const text = string(data);
try dir.remove(name);
return Result.ok(text.split("\n").length - 1);
}
function main(sys: System): void {
const args = sys.arguments();
print("arguments: " + string(args.length));
match (saveAndCount(sys, "notes.txt")) {
Result.ok(lines) => { print("lines: " + string(lines)); }
Result.err(e) => { print("io error: " + e.message()); }
}
match (sys.files()) {
Result.ok(dir) => {
match (dir.readFile("missing.txt")) {
Result.ok(data) => { print("read " + string(data.length)); }
Result.err(e) => { print("io error: " + e.message()); }
}
}
Result.err(e) => { print("io error: " + e.message()); }
}
}giz files.giz alpha betaarguments: 2
lines: 2
io error: no such file or directorysys.files()gives the working directory as aDir. ADiropens only paths below it: no.., no absolute paths.readFilegivesbytes.string(data)converts valid UTF-8 to astring.utf8(text)converts astringtobytes.- Each operation gives a
Result<T, IoError>.e.message()gives the text of the error. sys.arguments()gives the command-line arguments after the file name.
A TCP server and a client in one program:
import {System, IoError} from "gizlang:system";
import {Result} from "gizlang:core";
import * as Task from "gizlang:task";
function serveOne(sys: System): Result<string, IoError> {
const listener = try sys.listen("127.0.0.1", 0);
const port = listener.port();
const client = Task.spawn(() => {
match (sys.connect("127.0.0.1", port)) {
Result.ok(stream) => {
stream.write(utf8("ping"));
stream.close();
}
Result.err(e) => { print("connect failed: " + e.message()); }
}
return 0;
});
const conn = try listener.accept();
const data = try conn.readAll();
conn.close();
listener.close();
Task.join(client);
return Result.ok(string(data));
}
function main(sys: System): void {
match (serveOne(sys)) {
Result.ok(text) => { print("server got: " + text); }
Result.err(e) => { print("io error: " + e.message()); }
}
}server got: pingsys.listen(host, 0)selects a free port.listener.port()gives it.sys.connect(host, port)opens a TCPStream.acceptandreadAllsuspend only the calling task.- For HTTP, use
gizlang:http. For TLS, usegizlang:tls.
Markup
Markup is TSX in ordinary .giz files. It compiles to an Html value from gizlang:html. Text in holes is escaped.
import {Html, each, when} from "gizlang:html";
type Todo = { id: i64; title: string; done: bool; };
type ListProps = { title: string; };
function Row(t: Todo): Html {
return <li data-id={t.id}>{t.title}{when(t.done, () => <b> (done)</b>)}</li>;
}
function TodoList(p: ListProps, children: Html): Html {
return <section><h2>{p.title}</h2>{children}</section>;
}
function main(): void {
const todos: Vec<Todo> = [
{ id: 1, title: "Write <docs>", done: true },
{ id: 2, title: "Ship", done: false },
];
const page = <TodoList title="Today"><ul>{each(todos, (t: Todo) => Row(t))}</ul></TodoList>;
print(page.text());
}<section><h2>Today</h2><ul><li data-id="1">Write <docs><b> (done)</b></li><li data-id="2">Ship</li></ul></section>- A lowercase tag writes an HTML element.
- A tag that starts with an uppercase letter calls a component function. The attributes are one record argument. The children are one
Htmlargument. - Markup has no spread,
&&,? :or.map. Usewhen(cond, f),each(items, f)andraw(trustedText). page.text()gives the HTML text.
To serve pages, routes and static files, use Gizlang Web (gizlang:http). Refer to the Gizlang Web guide.
What differs from TypeScript
Gizlang uses the spelling of TypeScript. A TypeScript feature that Gizlang does not have is a compile error. It never gets a different meaning.
type Order = { id: string; qty: i64; };
function bump(o: mut Order): void {
o.qty += 1;
}
function main(): void {
const n = 3;
let size = "small";
if (n > 2) {
size = "big";
}
print("n=" + string(n) + " is " + size);
const first: Order = { id: "a1", qty: 1 };
let second = first;
bump(second);
print(string(first.qty) + " " + string(second.qty));
print(string(7 / 2) + " " + string(7.0 / 2.0));
}n=3 is big
1 2
3 3.5| TypeScript | Gizlang |
|---|---|
number | i64 or f64. number is f64. Integer / truncates. |
Implicit conversion, "n=" + n | "n=" + string(n), or a template string `n=${n}`. |
null, undefined, ?., ??, optional fields | The same, on Option<T>: x?: T and T | undefined are Option<T>. |
throw, try-catch | The same, with throws E on the function: function f(): T throws E. A call to it gives the T and a failure returns from the caller. finally runs when the statement ends. |
class, inheritance | Not available. Use records, methods (function T.m(self: T)) and interfaces. |
+, < and the other operators on objects | An operator on a record calls its method: a + b is a.add(b). |
| Objects shared by reference | Records, Vec, strings and bytes are values. Use a mut parameter to change a value of the caller. |
async, await | Not available. Use gizlang:task. |
enum | Not available. Use a union. |
Array, Map, Set and string methods | The same names, with no import (v.filter(f), new Map<K, V>()). v.sort takes a before function: (a, b) => a < b. |
any, untyped values, eval | Compile errors. |
Module-level let | Not available. |
| A name declared again in an inner scope | Compile error E_SHADOW. |
A closure that changes an outer let | Compile error E_ESCAPE. |
Next steps
- Use the Playground to run programs without an installation.
- Read the package guide for dependencies and pins.
- Read the Gizlang Web guide to serve a site.