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 insource, in order; seezuri.token.parse(source)returnssource’s AST as a list of nodes, with every comment and doc block preserved in place; seezuri.ast.compile(source)returnssource’s compiled VM instructions; seezuri.compile.parse_partial(source)reads as much ofsourceas it can and returns the tree along with every syntax error, andcheck(source)returns every problem compilingsourcewould report, both aszuri.Diagnostics and neither raising on bad source; seezuri.astandzuri.compile.zuri.docreads documentation comments: the prose and@tags of a doc block, and which declaration each block documents; seezuri.doc.zuri.reflectinspects live functions, classes and modules, reads or updates an instance’s own properties, and runs the garbage collector on demand; seezuri.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:
- Call
parse_file()/compile_file()on something real (orparse()/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()(orfind_nodes()/find_instrs()) to do something with every one of that kind, wherever it appears. 4. Reach forzuri.ast’s orzuri.compile’s own doc comments only for the parts exploration doesn’t answer on its own: what a node’s position covers, how aClosureinstruction’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.
| Name | Kind | Summary |
|---|---|---|
zuri.Diagnostic | class | One problem in a piece of Zuri source: what is wrong and exactly where. |
zuri.Instr | class | One VM instruction. |
zuri.Node | class | One AST node: an expression, statement, declaration, type hint, or a standalone comment/doc block sitting… |
zuri.ParseResult | class | What parse_partial() read from a piece of source: the tree it built and every syntax error it found on the… |
zuri.Token | class | One lexical token from a Zuri source file. |
zuri.check | function | Every problem compiling source would report, as a list of zuri.Diagnostic in the order they occur,… |
zuri.check_file | function | Reads the file at path and checks it; exactly check(file(path).read()). |
zuri.compile | function | Compiles source as a standalone script and returns its instructions as a flat list of Instr. |
zuri.compile_file | function | Reads the file at path and compiles its contents; exactly compile(file(path).read()), for the common case… |
zuri.doc.Attached | class | One node from a list of siblings, with the doc block written directly above it, as attach() pairs them. |
zuri.doc.Block | class | A parsed doc block: body, its markdown prose as a list of lines, and tags, every @tag in the order it… |
zuri.doc.Tag | class | One @tag from a doc block, its wrapped lines already joined into one string. |
zuri.doc.attach | function | Pairs every node in a list of siblings with the doc block written directly above it. |
zuri.doc.empty | function | A block with neither prose nor tags, for a declaration that has no documentation. |
zuri.doc.module_block | function | The module’s own documentation among the top-level nodes of a file: the first doc block carrying an @module… |
zuri.doc.parse | function | Parses the text of a doc block, the text a DocBlock node carries, into its prose and tags. |
zuri.doc.parse_lines | function | Parses the lines of a doc block into its prose and tags; the same as parse() for text already split into… |
zuri.dump_file | function | Reads, parses, and dumps the file at path in one call: the fastest way to answer “what does this file’s AST… |
zuri.find_instrs | function | Every instruction reachable from instrs whose op is op, in the order they appear. |
zuri.find_nodes | function | Every node reachable from nodes whose kind is kind, in the order they appear. |
zuri.parse | function | Parses source in full and returns its top-level declarations as a list of Node. |
zuri.parse_file | function | Reads the file at path and parses its contents; exactly parse(file(path).read()), for the common case of… |
zuri.parse_partial | function | Parses source as far as it can and returns everything it read along with every syntax error it found,… |
zuri.parse_partial_file | function | Reads the file at path and parses as much of it as it can; exactly parse_partial(file(path).read()). |
zuri.reflect.bind_method | function | Method name on object’s class, bound to object itself as the receiver; the result can be called… |
zuri.reflect.class_info | function | Metadata for a class: { name, superclass_name, methods, fields, statics }. |
zuri.reflect.del_prop | function | Resets object’s existing property name back to nil. |
zuri.reflect.function_info | function | Metadata for a function-like value: { name, arity, variadic, is_method, owning_class_name, source_path }. |
zuri.reflect.gc | function | Runs a full garbage collection now, instead of waiting for the heap to cross its threshold. |
zuri.reflect.get_decorator | function | The decorator function named name (excluding the leading @) on the class behind object, bound to… |
zuri.reflect.get_method | function | The raw (unbound) closure for method name on the class behind object, or nil if it declares no such… |
zuri.reflect.get_prop | function | The current value of object’s (an instance or a module) property/member named name, or nil if it has… |
zuri.reflect.get_props | function | Every property/member name object (an instance or a module) has, as a list of strings, or an empty list if… |
zuri.reflect.has_decorator | function | Does the class behind object implement the decorator named name? A decorator is just a method whose own… |
zuri.reflect.has_method | function | Does the class behind object (an instance, or a class used directly) declare or inherit a method named… |
zuri.reflect.has_prop | function | Does object (an instance or a module) have a property/member named name? |
zuri.reflect.info | function | Dispatches to function_info/class_info/module_info based on kind(value). |
zuri.reflect.kind | function | This value’s runtime type tag: 'nil', 'bool', 'number', 'string', 'bytes', 'bigint', 'list',… |
zuri.reflect.module_info | function | Metadata for a module: { name, path, loaded, members }. |
zuri.reflect.pointer_type | function | The registered resource tag a pointer value was allocated with (e.g. a native socket handle’s own internal… |
zuri.reflect.set_prop | function | Overwrites object’s existing property name with value. |
zuri.tokenize | function | Lexes source in full and returns every token it contains, in source order, as a list of Token. |
zuri.tokenize_file | function | Reads the file at path and lexes its contents; exactly tokenize(file(path).read()), for the common case… |
zuri.walk_instrs | function | Recursively visits every instruction reachable from instrs (typically zuri.compile()’s own result, or any… |
zuri.walk_nodes | function | Recursively visits every node reachable from nodes (typically zuri.parse()’s own result, or any single… |
Submodules
| Module | Reached as | Summary |
|---|---|---|
zuri.ast | zuri.* | The Node type zuri.parse() returns: a generic representation of one AST node, tagged by kind… |
zuri.compile | zuri.* | The Instr type zuri.compile() returns: a generic representation of one VM instruction, tagged by op… |
zuri.diagnostic | zuri.* | The Diagnostic type: one problem found in a piece of Zuri source by reading it rather than running it, as… |
zuri.doc | zuri.doc.* | Reads Zuri’s documentation comments: the doc blocks a zuri.parse() tree keeps as DocBlock nodes, turned… |
zuri.reflect | zuri.reflect.* | Runtime introspection: metadata about a live function, class, or module, and property/method access on an… |
zuri.token | zuri.* | 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