Learn Hilde

This tour shows complete programmes from the examples/ directory of the Hilde repository, each with what it printed when it was built and run for this page. For the rules behind them, read the basics; for every error code, the error codes.

Hello, world

File: HelloWorld.hih

module Example.HelloWorld is
    """
    No contract: the exit code of the programme, the result of what it did.
    """
    function main(): ExitCode;
end module.

File: HelloWorld.hiq

module body Example.HelloWorld is
    function main(): ExitCode is
        printLn("Hello, world!");
        return ExitSuccess();
    end;
end module body.

Output of ./main (exit status 0):

Hello, world!
Source: example examples/hello-world in the Hilde repository, hilde 1.0.0

Records with an invariant

Interval is a record with an invariant, checked whenever a value leaves its module. makeInterval answers with an Option: an interval whose low end is above its high end is refused with None, not with a crash.

File: Interval.hih

"""
A closed interval of whole numbers whose low end is never above its high
end.  The type is opaque, so only this module builds and changes one, and
its invariant is checked whenever a value leaves the module.
"""
module Example.Interval is
    """
    An interval from low to high, both included.
    """
    type Interval: Linear;

    """
    The interval from low to high, or None when low is above high.
    No contract: an Option result, which an ensures cannot look into.
    """
    function makeInterval(low: Nat32, high: Nat32): Option[Interval];

    """
    How many numbers the interval holds: high minus low plus one.
    No contract: a number read from its argument; its type gives its range, and nothing cheaper holds.
    """
    generic [R: Region]
    function width(interval: &[Interval, R]): Nat32;

    """
    Moves the high end up by amount.  Returns false and leaves the interval
    as it was when the high end would pass the largest Nat32.
    No contract: the answer is a Bool, nothing cheaper holds.
    """
    generic [R: Region]
    function stretch(interval: &![Interval, R], amount: Nat32): Bool;

    """
    The high end of the interval.
    No contract: a number read from its argument; its type gives its range, and nothing cheaper holds.
    """
    generic [R: Region]
    function highEnd(interval: &[Interval, R]): Nat32;

    """
    Ends the interval.
    No contract: it only frees or closes what it is given.
    """
    function destroyInterval(interval: Interval): Unit;
end module.

File: LearnRecords.hiq

import Example.Interval (
    Interval,
    makeInterval,
    width,
    stretch,
    highEnd,
    destroyInterval
);

module body Example.LearnRecords is
    record Day: Linear is
        morning: Interval;
        evening: Interval;
    end;

    -- A write reference to the evening alone, from a write reference to
    -- the whole day.  It uses up the reference to the day.
    generic [R: Region]
    function stretchEvening(day: &![Day, R], amount: Nat32): Bool is
        return stretch(&!(day->evening), amount);
    end;

    generic [R: Region]
    function hours(day: &[Day, R]): Nat32 is
        return width(&(day->morning)) + width(&(day->evening));
    end;

    function makeDay(morning: Interval, evening: Interval): Day is
        return Day(morning => morning, evening => evening);
    end;

    function main(): ExitCode is
        case makeInterval(9, 5) of
            when Some(value as wrong: Interval) do
                printLn("an interval from 9 to 5, which must not be");
                destroyInterval(wrong);
            when None do
                printLn("an interval from 9 to 5: refused, the low end is above the high end");
        end case;
        case makeInterval(8, 11) of
            when Some(value as morning: Interval) do
                case makeInterval(17, 19) of
                    when Some(value as evening: Interval) do
                        var day: Day := makeDay(morning, evening);
                        print("hours in the day: ");
                        printLn(hours(&day));
                        print("the evening stretched by two: ");
                        printLn(stretchEvening(&!day, 2));
                        print("stretched past the largest number: ");
                        printLn(stretchEvening(&!day, maximum_nat32));
                        borrow view: &[Day, V] := &day do
                            print("the evening ends at: ");
                            printLn(highEnd(&(view->evening)));
                            print("hours now: ");
                            printLn(hours(view));
                        end borrow;
                        let { morning as m: Interval, evening as e: Interval } := day;
                        destroyInterval(m);
                        destroyInterval(e);
                    when None do
                        printLn("no evening");
                        destroyInterval(morning);
                end case;
            when None do
                printLn("no morning");
        end case;
        return ExitSuccess();
    end;
end module body.

Output of ./main (exit status 0):

an interval from 9 to 5: refused, the low end is above the high end
hours in the day: 7
the evening stretched by two: true
stretched past the largest number: false
the evening ends at: 21
hours now: 9
Source: example examples/learn-records in the Hilde repository, hilde 1.0.0

Templates

A template in Jinja2 syntax beside the module. The compiler checks it against the record it is given and translates it into Hilde code; nothing is parsed at run time.

File: order.jinja

Order for {{ customer|upper }}
{%- for line in lines %}
{{ loop.index }}. {{ line.name }}, {{ line.count }} of them{% if loop.last %} (the last line){% endif %}
{%- endfor %}
{% if rush %}Ship today.{% else %}Ship this week.{% endif %}

File: LearnTemplate.hiq

import Standard.Buffer (
    Buffer,
    allocateEmpty,
    insertBack,
    removeLast,
    destroyEmpty,
    length
);
import Standard.String (
    String,
    fromLiteral,
    destroyString,
    length as textLength,
    getSpan
);
import Standard.StringBuilder (
    StringBuilder,
    makeStringBuilder,
    finalize
);
import Standard.Template (
    Render
);

module body Example.LearnTemplate is
    record Line: Linear is
        name: String;
        count: Int32;
    end;

    record Order: Linear is
        customer: String;
        lines: Buffer[Line];
        rush: Bool;
    end;

    -- The template order.jinja, in the syntax of Jinja2, is read by the
    -- compiler and turned into code here: a name in the template that the
    -- record does not have, or a filter that does not exist, is a compile
    -- error, not a surprise at run time.
    generic [R: Region]
    function render(order: &[Order, R]): String is
        var out: StringBuilder := makeStringBuilder();
        @template("order.jinja", order, out);
        return finalize(out);
    end;

    function destroyLines(lines: Buffer[Line]): Unit is
        var rest: Buffer[Line] := lines;
        while length(&rest) > 0 do
            let line: Line := removeLast(&!rest);
            let { name: String, count: Int32 } := line;
            destroyString(name);
        end while;
        destroyEmpty(rest);
        return nil;
    end;

    function main(): ExitCode is
        var lines: Buffer[Line] := allocateEmpty();
        insertBack(&!lines, Line(name => fromLiteral("apples"), count => 6));
        insertBack(&!lines, Line(name => fromLiteral("pears"), count => 2));
        insertBack(&!lines, Line(name => fromLiteral("plums"), count => 12));
        let order: Order := Order(
            customer => fromLiteral("Ann"),
            lines => lines,
            rush => true
        );
        let text: String := render(&order);
        printLn(getSpan(&text, 0, textLength(&text) - 1));
        destroyString(text);
        let { customer: String, lines as rest: Buffer[Line], rush: Bool } := order;
        destroyString(customer);
        destroyLines(rest);
        return ExitSuccess();
    end;
end module body.

Output of ./main (exit status 0):

Order for ANN
1. apples, 6 of them
2. pears, 2 of them
3. plums, 12 of them (the last line)
Ship today.
Source: example examples/learn-template in the Hilde repository, hilde 1.0.0

A graph

Standard.Graph is a directed graph with values on nodes and edges. The example finds the way with the fewest roads and the one with the fewest kilometres through a small map (source in examples/learn-graph).

Output of ./main (exit status 0):

fewest roads: Aachen Cologne Frankfurt
fewest kilometres: Aachen Bonn Frankfurt, 265 km
Source: example examples/learn-graph in the Hilde repository, hilde 1.0.0

Tasks

Four tasks in a scope and a fifth with an arena of its own; the output is the same on every run (source in examples/learn-tasks).

Output of ./main (exit status 0):

5239625
36552125
99114625
192927125
333833500
338350
Source: example examples/learn-tasks in the Hilde repository, hilde 1.0.0

More examples

The repository has sixty example directories; the learn-* directories each show one feature or one module: regular expressions, the sandbox, SQLite and SQLCipher, passwords, sessions, a FastCGI server, files, time, archives and more. Each has a README.md that says how to build it and what comes out. The guide Learning Hilde leads from the first programme on, and the traps lists the traps met in writing Hilde, with the wrong and the right code.

Playground

A playground in the browser is planned on top of Hilde's WebAssembly target: a module for wasm32-wasi is built with zig cc, and the repository has examples for a browser and for the command line (examples/wasm-browser, examples/wasm-cli). There is no playground on this site yet, and the site works without JavaScript.