Parser

(parser is a piece of the loft-in-loft front end that lives in loft's own lib/, @F121; it is not a registry library yet, so a program outside this tree cannot use it.) The parser library lets a Loft program read and validate other Loft source code at runtime. This is useful when you want to:

Together the lexer and parser libraries are designed as a reusable foundation. You can use them to build entirely new languages: feed the lexer your own token rules, then layer a custom parser on top. This keeps the tokeniser and grammar logic separate from the language runtime so you only bring in what you need.

parse(name, source) is the single entry point. It takes a display name (used in error messages) and a Loft source string. The name does not need to correspond to a real file — it is only used when reporting parse errors.

parse() answers how many errors the source contained — 0 for a snippet that parsed cleanly. The diagnostics themselves go to the log as it reads; the count is what a caller can branch on.

What the parser understands

The parser handles most of Loft's syntax:

It reads an index expression (v[0]), the null-coalescing operator (a ?? 0) and a formatted string literal ("v={n}") as the compiler does.

Outside its grammar, so a snippet using one answers a non-zero count although the compiler accepts it: const fields and top-level const, **, /=, hex and f-suffixed literals, generic functions (fn f<T>), filtered loops (for e in v if e > 1), is patterns, slices (v[1..]) and backtick strings.

use parser;
fn main() {

Parsing a minimal function

The double braces {{ and }} produce literal { and } inside a Loft format string — needed here because the source code itself contains braces.

  assert(parser::parse("hello", "fn main() {{ println(\"Hello World\"); }}") == 0,
         "a minimal function parses cleanly");

Parsing a struct and function together

The parser validates the entire source snippet as one unit. Both the struct definition and the function body are checked.

  assert(parser::parse("point", "struct Point {{ x: integer, y: integer }} fn origin() -> Point {{ Point {{ x: 0, y: 0 }} }}") == 0,
         "a struct and a function in one unit parse cleanly");

Parsing an enum

Both a plain variant and a struct variant (with fields) are recognised.

  assert(parser::parse("shape", "enum Shape {{ Circle {{ radius: float }}, Rectangle {{ w: float, h: float }} }}") == 0,
         "both a plain and a struct variant parse cleanly");

Parsing several statements in a body

A body is a sequence of statements; each call below is one.

  assert(parser::parse("body", "fn m() {{ println(\"a\"); println(\"b\"); }}") == 0,
         "a body of several statements parses cleanly");

Parsing assignments and operators

Binary operators parse with their normal precedence, and so does assignment.

  assert(parser::parse("assign", "fn f() {{ t = 0; t += 1; }}") == 0,
         "assignment and compound assignment parse cleanly");

Parsing a for loop and arithmetic

This exercises the expression parser, range syntax, and a tail expression after a loop body — the shape where a speculative struct-literal parse has to give its tokens back so the loop can claim them.

  assert(parser::parse("loop", "fn sum(n: integer) -> integer {{ t = 0; for i in 1..=n {{ t += i; }} t }}") == 0,
         "a for loop over a range parses cleanly");

Parsing while, match and lambdas

Each of these is a value as well as a statement: a match or a lambda can stand on the right of =.

  assert(parser::parse("while", "fn f() {{ n = 0; while n < 3 {{ n += 1; }} }}") == 0,
         "a while loop parses cleanly");
  assert(parser::parse("match", "fn f(x: integer) -> integer {{ match x {{ 0 => 1, n if n > 9 => 2, 2 | 3 => 3, _ => 0 }} }}") == 0,
         "a match with a guard and alternatives parses cleanly");
  assert(parser::parse("lambdas", "fn f() {{ g = |x| {{ x * 2 }}; h = fn(x: integer) -> integer {{ x + 1 }}; }}") == 0,
         "both lambda forms parse cleanly");

Practical use: validating user-supplied code

If your application lets users write Loft snippets (for scripting or configuration), you can parse them before executing:

snippet = read_user_input();
if parser::parse("user_code", snippet) > 0 {
  // The snippet was invalid — the count is how many errors it had.
}

Combine this with the lexer (see the Lexer page) when you need to extract individual tokens from the source rather than validate all of the syntax.

What a failed parse looks like

The count is the whole point: a snippet that does not parse answers a NON-zero number, so a caller can tell the two apart. Watching the call not crash cannot.

  assert(parser::parse("broken", "fn main() {{ println(\"hi\");") > 0,
         "an unterminated body reports at least one error");

A missing operand is an error too, and so is text after the last definition.

  assert(parser::parse("no_value", "fn f() {{ x = ; }}") > 0, "a missing value is reported");
  assert(parser::parse("stray", "fn f() {{ }} }}") > 0, "a stray brace is reported");

What it does not parse

Each construct in the list at the top of this page answers a non-zero count here.

  outside = [
    "const K = 3;",
    "struct S {{ const k: integer }}",
    "fn f() {{ x = 2 ** 3; }}",
    "fn f() {{ x = 6; x /= 2; }}",
    "fn f() {{ x = 0xff; }}",
    "fn f() {{ x = 1.5f; }}",
    "fn f<T>(x: T) -> T {{ x }}",
    "fn f(v: vector<integer>) {{ for e in v if e > 1 {{ }} }}",
    "enum Dir {{ North, South }} fn f(e: Dir) {{ if e is North {{ }} }}",
    "fn f(v: vector<integer>) {{ w = v[1..]; }}",
    "fn f() {{ x = `a`; }}"
  ];
  for snippet in outside {
    assert(parser::parse("outside", snippet) > 0, "{snippet} is outside the grammar");
  }
  println("parser test passed");
}