Skip to content

How a program runs

You have written programs now. This page says what happens to one, in enough detail to predict what the language will do and no more.

  1. source textwhat you wrote
  2. lexthe language's symbols
  3. tokenswords and symbols, no meaning yet
  4. parse
  5. raw programbindings and outputs, untyped
  6. composethe language and the port layers
  7. raw program + descriptorand everything it may use
  8. analyse
  9. core programtyped, checked, pruned
  10. evaluatethe input values
  11. valuesone per output

Five steps. Each takes what is on its left and produces what is on its right, and none of them can reach backwards.

The lexer cuts your text into tokens: names, numbers, strings and symbols. It knows which symbols the language has, so >= comes out as one token rather than two characters, and that is all it knows. A word is a word to it, whether it is let, If or a name of yours.

What you can rely on: a string with no closing quote, or a character the language has no use for, is reported here, with a position.

The parser turns the tokens into bindings and outputs. This step knows the shape of the language and nothing else: it does not know which ops exist, which types exist, or what your application declared. It does rewrite every symbol into the op it stands for as it reads, so $price * $quantity is already Multiply($price, $quantity) when it leaves.

That is why $n is an input here purely because of the $ (no table is consulted), and why a misspelled op name gets through this step without complaint. What you can rely on: a syntax error is reported here, with a position, and nothing further happens.

Checking a program needs a list of everything it may use: the ops and types of the language, and the inputs and outputs your application declared. That list is not fixed by the language. It arrives in layers, one from the language and more from your application, and composing them into one is this step.

It reads what was declared, never what you wrote, so a clash between declarations (two layers claiming the same name) is reported here, before your code is looked at. Ports and layers is where the layers are explained.

This is where almost everything happens. The analyser walks the bindings in dependency order and works out the type of each one, and it checks every op call against what that op actually takes.

It also decides what is worth keeping. A binding no output can reach is dropped. An output whose working failed is dropped, and if your application had marked that output required, the whole program fails to load; if not, the rest carries on without it.

Two things to rely on. Errors are collected, not thrown at the first one, so you get the full list rather than peeling them off one at a time. And soundness is per output: one broken result does not stop the others, which is why a half-finished program keeps producing the parts that work.

The evaluator does not run your program top to bottom. Outputs pull: asking for an output walks only the nodes that output depends on, and each node remembers its last value.

So when an input changes, the recompute covers exactly the nodes that read it, directly or through other bindings. Everything else keeps what it had. You saw this on Inputs: move $price and the shipping calculation is not touched.

Two things to rely on. A binding is computed at most once per evaluation however many names read it. And the last good program keeps running: while your text does not compile, the values on screen are the ones from the program that did, marked stale rather than hidden, so whatever is acting on them has something to act on.

Worth stating as plainly as the rest, because it is what makes the chain predictable:

  • Lexing may not decide what a word means. let and If are both just words.
  • Parsing may not resolve a name or infer a type. It does not know what exists.
  • Composing may not read your code. It sees what was declared, never what was written.
  • Analysis may not compute a value. It works with types, not numbers.
  • Evaluation may not report a type error. Everything checkable was checked already; what reaches it is a runtime failure, and it arrives on the outputs rather than as a diagnostic.

You can write programs and predict what they do. Two ways on from here:

  • The standard library: every op, with its signature and a worked example, generated from the language itself.
  • How it works: the same chain again, one page per step, with the reasons. Learn told you what you can rely on; that section tells you why, and in what order.

And if you want the language inside an application of your own, Host developers starts there.