The Parser as a Runtime Library

· 4 min read

Today we shipped something special: Koru’s parser is now available as a runtime library.

This isn’t a “lite” parser or a reimplementation. It’s the exact same parser used in:

  • The compiler frontend (parsing your .kz files)
  • The compiler backend (our metacircular architecture)
  • And now your programs at runtime

One parser. Battle-tested. Available everywhere.

This means Koru programs can parse Koru source code at runtime, inspect the AST, and act on it. No external tools. No shelling out. Just pure Koru.

The Code

Here’s a complete program that parses Koru source and dumps the AST as JSON:

~import "$std/parser"
~import "$std/io"

const std = @import("std");

const SAMPLE =
    \\~event hello {}
    \\| done {}
;

~std.parser:parse.source(source: SAMPLE, file_name: "test.kz", allocator: std.heap.page_allocator)
| parsed p |> std.parser:dump.ast(ast: p.ast, allocator: std.heap.page_allocator)
    | dumped d |> std.io:print.ln("{{d.json:s}}")
    | dump_error e |> std.io:print.ln("Dump error: {{e.message}}")
| parse_error e |> std.io:print.ln("Parse error: {{e.message}}")

Zero custom procs. Just flow composition with standard library events.

The Output

{
    "module_annotations": [],
    "items": [
        {
            "type": "event_decl",
            "name": "hello",
            "path": ["hello"],
            "module": "test",
            "input": { "fields": [] },
            "branches": [
                {
                    "name": "done",
                    "payload": { "fields": [] }
                }
            ],
            "location": {
                "file": "test.kz",
                "line": 2,
                "col": 0
            }
        }
    ]
}

What This Enables

With the parser available at runtime, you can build:

  • REPLs that parse and execute Koru interactively
  • AI agents that understand Koru structure natively
  • Wire protocols that accept Koru as input format
  • Dynamic code loading for plugin systems
  • Tooling like formatters, linters, and refactoring tools

How It Works

The $std/parser module uses ~std.build:requires to inject the parser’s Zig modules into the output binary’s build configuration:

~std.build:requires {
    const parser_module = b.createModule(.{
        .root_source_file = .{ .cwd_relative = REL_TO_ROOT ++ "/src/parser.zig" },
        // ... dependencies
    });
    exe.root_module.addImport("parser", parser_module);
}

When you import $std/parser, the parser and all its dependencies are automatically included in your binary. No manual build configuration.

Pure Koru

The most satisfying part? The test program has no custom procs. It’s pure flow composition:

~std.parser:parse.source(...)
| parsed p |> std.parser:dump.ast(...)
    | dumped d |> std.io:print.ln("{{d.json:s}}")

Parse → Dump → Print. That’s it. The standard library handles everything.

What’s Next

The parser library opens the door to metaprogramming at runtime. Imagine:

  • A REPL that hot-reloads event definitions
  • An AI assistant that modifies its own event handlers
  • A visual editor that manipulates AST nodes directly

Koru is becoming a language that can reason about itself.


The parser runtime library is available now in the $std/parser module.