Node-based syntax
Before continuing to next lessons, we must first explain something regarding Orb’s syntax.
Let’s look at a simple Orb program.
import "std/io.orb";
fnc foo (x:u32) u32 {
ret (+ (* 31 x) 17);
};
fnc main () () {
std.println (foo (std.scanU32));
};
In Orb, there is no difference between the two types of parenthesis - there never was. Furthermore, semicolons simply group up what was placed between them and the closest previous unclosed parenthesis.
So, the program from above could equivalently be rewritten as:
(import "std/io.orb")
(fnc foo (x:u32) u32 (
(ret (+ (* 31 x) 17))
))
(fnc main () () (
(std.println (foo (std.scanU32)))
))
Back when we talked about primitive types, we mentioned raw
but never demonstrated it. As it turns out, we’ve been using this type the whole time - each parenthesised group we’ve written was actually a raw
value.
Like with many other languages, Orb code can be represented as an Abstract Syntax Tree (AST). Unlike most languages, the code is a direct representation of that AST.
Orb ASTs consist of nodes. There are two types of nodes. Non-leaf nodes are parenthesised groups, which are also raw
values. Leaf nodes are literals (remember that identifiers are also literals).
When a non-leaf is processed (either compiled or evaluated), the value of its starting element is processed first. How the compiler processes the node as a whole depends on what the starting element resolved to. For example, if it resolved to a function, the node represents a call to that function. If it resolved to a special form, the node represents usage of that special form.
This is the reason why Orb uses prefix notation, even in expressions - operators are just special forms (and macros), and they are processed in the same way as all other nodes.
It also explains why you are required to terminate every non-parenthesised instruction with a semicolon - this is to group its individual nodes into a single one.