Skip to content

API Reference

Eric San edited this page Jun 15, 2026 · 2 revisions

API Reference

This is the whole supported surface: the lexer, the parser, the Ast accessors, and the semantic facade. Everything is reached through const es = @import("es_parser").

Lexing

es.Lexer.tokenize(alloc, src)                     // .js, script mode    (lexer.zig:24)
es.Lexer.tokenizeWithLanguage(alloc, src, lang)   // lang ∈ .js .jsx .ts .tsx .dts (lexer.zig:27)
es.Lexer.tokenizeWithOptions(alloc, src, lang, is_module)  //           (lexer.zig:30)

Each returns a TokenizeResult you deinit separately from the AST — the parser borrows its token slice but does not own it:

var lr = try es.Lexer.tokenizeWithLanguage(allocator, source, .ts);
defer lr.deinit(allocator);

Parsing

es.Parser.parse(alloc, src, tokens)                            // emit_events on (parser.zig:458)
es.Parser.parseWithOptions(alloc, src, tokens, opts)           //                (parser.zig:526)
es.Parser.parseWithLanguage(alloc, src, tokens, lang, is_mod)  //                (parser.zig:534)

ParseOptions (parser.zig:466): language (default .js), is_module (false), global_return (false), is_strict (null → follows is_module), annex_b (true), experimental_decorators (false), emit_events (false, but parse sets it true), and events_out. The returned Ast owns its nodes, extra_data, and event buffers; tree.deinit(allocator) frees them (but not the tokens). tree.errors is []const Diagnostic.

Reading the AST

Ast exposes O(1) typed accessors (ast.zig:810):

nodeTag(i)        nodeMainToken(i)     nodeData(i) → .{ lhs, rhs }
tokenTag(t)       tokenStart(t)        tokenText(t)
extraData(T, i)   extraSlice(range)    nodeName(i)

NodeIndex is enum(u32) with root = 0 and none = maxInt. For the encoding of lhs/rhs per tag, the extra_data payload structs, span recovery via node_end_toks, and the ESTree name table, see AST and Memory Layout.

Semantic analysis

es.semantic.SemanticAnalyzer.analyze(alloc, &tree)                  // module mode (semantic.zig:180)
es.semantic.SemanticAnalyzer.analyzeModule(alloc, &tree, is_module)
es.semantic.SemanticAnalyzer.analyzeWithGlobals(alloc, &tree, globals)
es.semantic.SemanticAnalyzer.analyzeWithOptions(alloc, &tree, opts)  //            (semantic.zig:203)

Options (semantic.zig:141): is_module (true), globals (null-separated names), build_parents (false), build_ref_ranges (true), need_cfg (true), diagnose_redeclare (false), annex_b (true). The result (SemanticResult, semantic.zig:24) carries scopes, symbols, references, diagnostics, node_reachable, loop_exit_reachable, code_path_result, and — when requested — parent_indices and ref_by_sym. deinit(allocator) frees it.

Two caller obligations: the AST must have been parsed with events on (else error.MissingScopeEvents), and the allocator must zero-fill — an arena over page_allocator, or semantic.ZeroingAllocator (semantic.zig:94). See Semantic Analysis.

Diagnostics

Diagnostic { message, span, severity } with a four-tier Severity.@"error", .warning, .info, .hint (diagnostic.zig:5). Filter by severity; parse errors and semantic diagnostics are separate lists.

End to end

const es = @import("es_parser");

var lr = try es.Lexer.tokenizeWithLanguage(allocator, source, .ts);
defer lr.deinit(allocator);

var tree = try es.Parser.parseWithOptions(allocator, source, lr.tokens.slice(), .{
    .language = .ts, .is_module = true, .emit_events = true,
});
defer tree.deinit(allocator);

for (tree.errors) |d|
    if (d.severity == .@"error") std.debug.print("{s}\n", .{d.message});

var sem = try es.semantic.SemanticAnalyzer.analyze(allocator, &tree);  // zeroing allocator
defer sem.deinit(allocator);

Clone this wiki locally