Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

zuri

import zuri

Exposes Zuri’s own compiler pipeline as a library: lexing, parsing, and compiling a Zuri source file, plus a reflect submodule for inspecting live functions, classes, and modules at runtime.

  • tokenize(source) returns every lexical token in source, in order; see zuri.token.
  • parse(source) returns source’s AST as a list of nodes, with every comment and doc block preserved in place; see zuri.ast.
  • compile(source) returns source’s compiled VM instructions; see zuri.compile.
  • parse_partial(source) reads as much of source as it can and returns the tree along with every syntax error, and check(source) returns every problem compiling source would report, both as zuri.Diagnostics and neither raising on bad source; see zuri.ast and zuri.compile.
  • zuri.doc reads documentation comments: the prose and @tags of a doc block, and which declaration each block documents; see zuri.doc.
  • zuri.reflect inspects live functions, classes and modules, reads or updates an instance’s own properties, and runs the garbage collector on demand; see zuri.reflect.

The ordinary starting point in practice is a real file on disk, not a source string already sitting in a variable, so each of the above also has a _file counterpart that reads path first: tokenize_file(path), parse_file(path), compile_file(path), parse_partial_file(path) and check_file(path). There’s one more, dump_file(path), with no bare-source equivalent of its own; it reads and parses path, then returns the dump() of every top-level node, which is usually the single fastest way to answer “what’s actually in this file”.

The mental model

tokenize()/parse()/compile() all return the same SHAPE of thing: a list of small, uniformly-tagged records. A token has a kind; an AST node has a kind; an instruction has an op. Whichever one it is, everything specific to that particular kind lives in one place: token.value, node.fields, instr.fields. There’s a Token class, a Node class, and an Instr class, not one class per token kind / grammar rule / opcode. Zuri’s grammar alone has around fifty expression/statement/declaration shapes, and the instruction set has roughly sixty opcodes; a dedicated class for each would be well over a hundred near-identical classes to keep in sync with the compiler forever after, for what would ultimately still be a name + a handful of fields each. This is the same tradeoff tools like Python’s ast module or Babel’s ESTree make, for the same reason.

The real cost of that choice: nothing points you at what fields a given kind/op carries the way a typed class would. You can’t autocomplete your way to a Binary node’s left/op/right. So this module leans hard on a different way of finding out: run something and look, rather than read a spec first.

import zuri

var nodes = zuri.parse('def add(a, b) { return a + b }')
echo nodes[0].dump()
Function@1:1
  name: 'add'
  name_span: 1:5-1:8
  parameters:
    Argument@1:9
      name: 'a'
      name_span: 1:9-1:10
      type_hint:
        TypeHint@1:10
          types:
            Any@1:10
              class_name: nil
          nullable: true
    ...
  body:
    Block@1:15
      statements:
        Return@1:17
          value:
            Binary@1:24
              left:
                Identifier@1:24
                  name: 'a'
              op: 'Plus'
              right:
                Identifier@1:28
                  name: 'b'
  is_variadic: false

That single dump() call answers “what does a Function node look like” far more completely than a paragraph of prose would, and it answers it for whatever kind you’re actually holding, not just the ones someone thought to write up. zuri.ast.Node.dump and zuri.compile.Instr.dump both work this way; zuri.token.Token doesn’t need the same treatment, since a token’s own fields (kind, line, column, start, end, text, value) are always the same seven, spelled out in zuri.token’s own docs.

Once you know the kind/op you’re after, walk_nodes()/ walk_instrs() (and their shorthand, find_nodes()/ find_instrs()) do the actual work. Almost nothing real gets done by manually indexing into fields node by node; the normal shape of a task is “for every node/instruction of kind X, do Y”, regardless of how deep in the tree it sits (inside a nested function, inside an if, wherever), which is exactly what these two functions are for, so reach for them first.

import zuri

var nodes = zuri.parse(source)

# every function name declared anywhere in the file, including
# nested/inner functions
var names = []
zuri.walk_nodes(nodes, {
  Function: @(node) { names.append(node.fields.name) },
})

# shorthand for the common "just collect them" case
var comments = zuri.find_nodes(nodes, 'Comment')

So, end to end, the actual workflow is:

  1. Call parse_file()/compile_file() on something real (or parse()/compile(), if you already have the source as a string). echo zuri.dump_file(path) first, if you just want to look before writing any real code against it. 2. dump() the specific node/instruction you care about, to see its actual shape. 3. walk_nodes()/walk_instrs() (or find_nodes()/ find_instrs()) to do something with every one of that kind, wherever it appears. 4. Reach for zuri.ast’s or zuri.compile’s own doc comments only for the parts exploration doesn’t answer on its own: what a node’s position covers, how a Closure instruction’s resolved value nests a whole other instruction list inside it, and so on.

zuri.reflect doesn’t need any of this: it’s ordinary named functions (function_info(f), has_method(object, name), …) with ordinary parameters, not a generic tagged-record system, so its own doc comments are the whole story; see zuri.reflect.

import zuri

for t in zuri.tokenize('var x = 1') {
  echo t.kind
}

for node in zuri.parse('var x = 1') {
  echo node.kind
}

for instr in zuri.compile('var x = 1') {
  echo instr.op
}

def add(a, b) {
  return a + b
}
echo zuri.reflect.function_info(add).arity

The zuri API

Every public name in zuri, wherever it is declared. Each links to the page that documents it.

NameKindSummary
zuri.DiagnosticclassOne problem in a piece of Zuri source: what is wrong and exactly where.
zuri.InstrclassOne VM instruction.
zuri.NodeclassOne AST node: an expression, statement, declaration, type hint, or a standalone comment/doc block sitting…
zuri.ParseResultclassWhat parse_partial() read from a piece of source: the tree it built and every syntax error it found on the…
zuri.TokenclassOne lexical token from a Zuri source file.
zuri.checkfunctionEvery problem compiling source would report, as a list of zuri.Diagnostic in the order they occur,…
zuri.check_filefunctionReads the file at path and checks it; exactly check(file(path).read()).
zuri.compilefunctionCompiles source as a standalone script and returns its instructions as a flat list of Instr.
zuri.compile_filefunctionReads the file at path and compiles its contents; exactly compile(file(path).read()), for the common case…
zuri.doc.AttachedclassOne node from a list of siblings, with the doc block written directly above it, as attach() pairs them.
zuri.doc.BlockclassA parsed doc block: body, its markdown prose as a list of lines, and tags, every @tag in the order it…
zuri.doc.TagclassOne @tag from a doc block, its wrapped lines already joined into one string.
zuri.doc.attachfunctionPairs every node in a list of siblings with the doc block written directly above it.
zuri.doc.emptyfunctionA block with neither prose nor tags, for a declaration that has no documentation.
zuri.doc.module_blockfunctionThe module’s own documentation among the top-level nodes of a file: the first doc block carrying an @module…
zuri.doc.parsefunctionParses the text of a doc block, the text a DocBlock node carries, into its prose and tags.
zuri.doc.parse_linesfunctionParses the lines of a doc block into its prose and tags; the same as parse() for text already split into…
zuri.dump_filefunctionReads, parses, and dumps the file at path in one call: the fastest way to answer “what does this file’s AST…
zuri.find_instrsfunctionEvery instruction reachable from instrs whose op is op, in the order they appear.
zuri.find_nodesfunctionEvery node reachable from nodes whose kind is kind, in the order they appear.
zuri.parsefunctionParses source in full and returns its top-level declarations as a list of Node.
zuri.parse_filefunctionReads the file at path and parses its contents; exactly parse(file(path).read()), for the common case of…
zuri.parse_partialfunctionParses source as far as it can and returns everything it read along with every syntax error it found,…
zuri.parse_partial_filefunctionReads the file at path and parses as much of it as it can; exactly parse_partial(file(path).read()).
zuri.reflect.bind_methodfunctionMethod name on object’s class, bound to object itself as the receiver; the result can be called…
zuri.reflect.class_infofunctionMetadata for a class: { name, superclass_name, methods, fields, statics }.
zuri.reflect.del_propfunctionResets object’s existing property name back to nil.
zuri.reflect.function_infofunctionMetadata for a function-like value: { name, arity, variadic, is_method, owning_class_name, source_path }.
zuri.reflect.gcfunctionRuns a full garbage collection now, instead of waiting for the heap to cross its threshold.
zuri.reflect.get_decoratorfunctionThe decorator function named name (excluding the leading @) on the class behind object, bound to…
zuri.reflect.get_methodfunctionThe raw (unbound) closure for method name on the class behind object, or nil if it declares no such…
zuri.reflect.get_propfunctionThe current value of object’s (an instance or a module) property/member named name, or nil if it has…
zuri.reflect.get_propsfunctionEvery property/member name object (an instance or a module) has, as a list of strings, or an empty list if…
zuri.reflect.has_decoratorfunctionDoes the class behind object implement the decorator named name? A decorator is just a method whose own…
zuri.reflect.has_methodfunctionDoes the class behind object (an instance, or a class used directly) declare or inherit a method named…
zuri.reflect.has_propfunctionDoes object (an instance or a module) have a property/member named name?
zuri.reflect.infofunctionDispatches to function_info/class_info/module_info based on kind(value).
zuri.reflect.kindfunctionThis value’s runtime type tag: 'nil', 'bool', 'number', 'string', 'bytes', 'bigint', 'list',…
zuri.reflect.module_infofunctionMetadata for a module: { name, path, loaded, members }.
zuri.reflect.pointer_typefunctionThe registered resource tag a pointer value was allocated with (e.g. a native socket handle’s own internal…
zuri.reflect.set_propfunctionOverwrites object’s existing property name with value.
zuri.tokenizefunctionLexes source in full and returns every token it contains, in source order, as a list of Token.
zuri.tokenize_filefunctionReads the file at path and lexes its contents; exactly tokenize(file(path).read()), for the common case…
zuri.walk_instrsfunctionRecursively visits every instruction reachable from instrs (typically zuri.compile()’s own result, or any…
zuri.walk_nodesfunctionRecursively visits every node reachable from nodes (typically zuri.parse()’s own result, or any single…

Submodules

ModuleReached asSummary
zuri.astzuri.*The Node type zuri.parse() returns: a generic representation of one AST node, tagged by kind…
zuri.compilezuri.*The Instr type zuri.compile() returns: a generic representation of one VM instruction, tagged by op…
zuri.diagnosticzuri.*The Diagnostic type: one problem found in a piece of Zuri source by reading it rather than running it, as…
zuri.doczuri.doc.*Reads Zuri’s documentation comments: the doc blocks a zuri.parse() tree keeps as DocBlock nodes, turned…
zuri.reflectzuri.reflect.*Runtime introspection: metadata about a live function, class, or module, and property/method access on an…
zuri.tokenzuri.*The Token type zuri.tokenize() returns: one entry per lexical token the lexer found in a source file, in…

2026, Richard Ore and Zuri contributors