GizStack
View .mdOpen in ChatGPTOpen in Claude

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:

gizlang
function main(): void {
    print("Hello, Gizlang.");
}

Run it:

sh
giz hello.giz
text
Hello, Gizlang.
  • main is the entry point.
  • : void tells that the function gives no result.
  • print writes a line to the console.

Step 2: A function and a type

Make a file with the name cart.giz:

gizlang
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)));
}
sh
giz cart.giz
text
pen: 6
  • type Item = { ... } declares a record. A record is a value with fixed fields.
  • i64 is 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" + 1 is a compile error.

Step 3: A list of items

Replace main and add cartTotal:

gizlang
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)));
}
text
2 items, total 11
  • Vec<Item> is a list of Item values. You can also write Item[].
  • let declares a variable that you can change. const declares a value that you cannot change.
  • items.push(x) adds x to a let list 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:

gizlang
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);
}
text
paid, 9 left
refused: over budget by 1
  • : i64 throws string means the function gives an i64 or fails with a string. throw fails it.
  • A call gives the i64. When the call fails, the try block goes to its catch. A function that does not catch the failure must be a throws function itself, so the failure goes on to its caller.
  • Under the signature, the function returns a Result<i64, string>. A place that expects a Result takes it as it is, and match reads its cases. A match must 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.

  1. Add the import of gizlang:test as line 2 of cart.giz, and two test functions at the end. The file is now:

    gizlang
    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);
    }
  2. Run the tests:

    sh
    giz test cart.giz
    text
    0 cached, 2 run, 0 failed
  3. Run the tests again:

    text
    2 cached, 0 run, 0 failed

    The 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:

text
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 replay line 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.giz builds and runs the program.
  • giz test cart.giz runs the tests of the file.
  • giz check cart.giz checks the file for errors without a run of main. 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

gizlang
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));
}
text
3
3.5
18446744073709551615
true
Gizlang has 7 bytes
3
6
TypeMeaning
i64, u64Checked 64-bit integers. Write a u64 literal with the suffix u.
f64A 64-bit IEEE float. number is the same type.
booltrue or false. boolean is the same type.
stringUTF-8 text. .length and indexes count bytes, not characters.
bytesImmutable bytes. Write a literal as b"...".
  • Integer / truncates.
  • There are no implicit conversions. Use i64(x), u64(x), f64(x) and string(x).
  • Integer overflow, division by zero and a bad index are faults. A fault stops the program. It is not a value.
gizlang
function double(x: i64): i64 {
    return x * 2;
}
function main(): void {
    print(string(double(9223372036854775807)));
}
text
gizlang: R003 in double line 2 (x = 9223372036854775807) <- main line 2
gizlang: replay: function testReplayDouble(): void { double(9223372036854775807); }
runtime R003

The fault report names the function, its arguments and the call path. The program exits with status 1.

Variables

gizlang
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));
}
text
30 2
  • const declares an immutable binding. let declares a mutable binding.
  • A module-level const must be a literal. Module-level let is not available.
  • You cannot assign to a const:
gizlang
function main(): void {
    const total = 1;
    total = 2;
    print(string(total));
}
sh
giz check const_error.giz
text
const_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:
gizlang
function main(): void {
    const total = 1;
    if (total > 0) {
        const total = 5;
        print(string(total));
    }
}
text
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

gizlang
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));
}
text
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) => R is a value type. A parameter, a result, a field or a Vec element can have it.
  • A closure copies the values that it captures. It cannot capture a let local:
gizlang
function main(): void {
    let count = 0;
    const next = (): i64 => { return count + 1; };
    print(string(next()));
}
text
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

gizlang
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"));
}
text
1 15 16,4 0
true
3.14159
6
true
  • Records are values. let b = a copies a. A change to b does not change a.
  • A let local changes in place at any field path, for example b.x = 10.
  • A function changes a value of its caller only through a mut parameter.
  • function Point.add(self: Point, ...) declares a method. Call it as a.add(b).
  • An operator on a record is its method of that name: a + b is a.add(b). The others are - * / (sub, mul, div), < <= > >= (lt, le, gt, ge) and unary - (neg).
  • { ...c, y: 0 } copies c and 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 kind names the case. Make a case with Shape.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

gizlang
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);
}
text
user ana
team core (4)
TEAM CORE (4)
boxed
  • An interface is a set of methods. Each method has self: Self as 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 toUpperCase come from gizlang:text, which every program has.

Collections

gizlang
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")}`);
}
text
3 17 1
2 true 1
2
2 0
the,cat,saw,dog
1 true
  • Vec<T> is a value. v.map(f), v.slice(start, end), v.length and v.push(x) are built in.
  • filter, find, findIndex, some, every, reduce, includes, indexOf, at, concat, sorted and count come from gizlang:collections, which every program has: call them as methods with no import.
  • findIndex and indexOf give -1 when no element matches. find and at give an optional value.
  • new Map<K, V>() and new Set<K>() make a map and a set. They keep insertion order.
  • m.get(k) gives an optional value. m.get(k) ?? fallback reads it with a default.

Control flow

gizlang
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));
}
text
1 2 Fizz 4 Buzz Fizz 7 Fizz Buzz 11 Fizz 13 14 FizzBuzz 
steps: 111
  • if, else if, while, break and continue work as in TypeScript.
  • for (const i of range(lo, hi)) counts from lo to hi - 1.
  • for (const x of v) reads each element of a Vec.
  • match reads a union. Refer to Records and unions.
  • The ? : operator and switch are not available. Use if and match.

Errors: Result, Option and try

gizlang
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"])!);
}
text
next year: 42
error: negative: -2
error: not a number: abc
0
tree
none
house
  • Result<T, E> has the cases ok(value) and err(error). Option<T> has the cases some(value) and none(). Import them from gizlang:core.
  • try e gives the value of ok or some. For any other case, try returns that case from the function. The result type of the function must have the same case.
  • result.valueOr(fallback) and option ?? fallback give a default.
  • x! gives the value. When there is no value, it faults.
  • A match arm binds the whole payload of its case. IntegerParse.invalid has a payload (the byte position), so the arm is IntegerParse.invalid(at).
  • throw and try-catch work with throws: function f(): T throws E returns a Result that its callers take as T (see Step 4: Errors). finally runs 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:

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:

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, " "));
}
sh
giz main.giz
text
...7 steps

geometry.test.giz:

geometry.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);
}
sh
giz test geometry.test.giz
text
0 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.
  • abs has no export. Other modules cannot import it.
  • Put the tests of X.giz in X.test.giz beside it. giz check X.giz also 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:

gizlang
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:

json
{
  "name": "shapes",
  "private": true,
  "gizlang": {
    "entry": { "path": "main.giz", "sha256": "" },
    "modules": {
      "geometry": { "path": "geometry.giz", "sha256": "" }
    }
  }
}

Write the pins, then run the package:

sh
giz pin .
giz package.json
text
distance: 7
  • giz pin DIR writes the sha256 of each source into package.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 DIR names the file:
sh
giz get --frozen .
text
gizlang: geometry.giz is missing or does not match its sha256

Run 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.

gizlang
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]));
}
text
fast: 55
slow: 6765
got one
got two
got three
squares: 9
  • Task.spawn(f) starts a task. Task.join(t) waits for it and gives a Result.
  • Task.channel<T>(capacity) makes a bounded queue. send waits while the queue is full. receive gives Option.none() when the queue is closed and empty.
  • Task.parallel(n, body) runs body(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:

gizlang
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");
}
text
task fault: 2
main continues

Fault 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.

gizlang
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()); }
    }
}
sh
giz files.giz alpha beta
text
arguments: 2
lines: 2
io error: no such file or directory
  • sys.files() gives the working directory as a Dir. A Dir opens only paths below it: no .., no absolute paths.
  • readFile gives bytes. string(data) converts valid UTF-8 to a string. utf8(text) converts a string to bytes.
  • 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:

gizlang
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()); }
    }
}
text
server got: ping
  • sys.listen(host, 0) selects a free port. listener.port() gives it.
  • sys.connect(host, port) opens a TCP Stream.
  • accept and readAll suspend only the calling task.
  • For HTTP, use gizlang:http. For TLS, use gizlang:tls.

Markup

Markup is TSX in ordinary .giz files. It compiles to an Html value from gizlang:html. Text in holes is escaped.

gizlang
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());
}
text
<section><h2>Today</h2><ul><li data-id="1">Write &lt;docs&gt;<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 Html argument.
  • Markup has no spread, &&, ? : or .map. Use when(cond, f), each(items, f) and raw(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.

gizlang
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));
}
text
n=3 is big
1 2
3 3.5
TypeScriptGizlang
numberi64 or f64. number is f64. Integer / truncates.
Implicit conversion, "n=" + n"n=" + string(n), or a template string `n=${n}`.
null, undefined, ?., ??, optional fieldsThe same, on Option<T>: x?: T and T | undefined are Option<T>.
throw, try-catchThe 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, inheritanceNot available. Use records, methods (function T.m(self: T)) and interfaces.
+, < and the other operators on objectsAn operator on a record calls its method: a + b is a.add(b).
Objects shared by referenceRecords, Vec, strings and bytes are values. Use a mut parameter to change a value of the caller.
async, awaitNot available. Use gizlang:task.
enumNot available. Use a union.
Array, Map, Set and string methodsThe 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, evalCompile errors.
Module-level letNot available.
A name declared again in an inner scopeCompile error E_SHADOW.
A closure that changes an outer letCompile error E_ESCAPE.

Next steps