Sign in

    moonrockz/gleam-parser/syntax does not have a README file

    EventSource

    pub(open) trait EventSource {
    fn next(Self) -> Event?
    fn skip_children(Self) -> Unit
    }

    A pull source of events in walk order. skip_children is valid right after an Enter: the next event is then that node's Leave. Elsewhere it does nothing.

    Handler

    pub(open) trait Handler {
    fn on_enter(Self, EnterEvent) -> Control = _
    fn on_leave(Self, LeaveEvent) -> Unit = _
    }

    Receives pushed events. on_enter steers the traversal like a walk's enter; both methods have defaults (Continue, nothing). See push_events for an example.

    Visitor

    pub(open) trait Visitor {
    fn visit_definition(Self, NodeRef) -> Control = _
    fn visit_statement(Self, NodeRef) -> Control = _
    fn visit_expression(Self, NodeRef) -> Control = _
    fn visit_pattern(Self, NodeRef) -> Control = _
    fn visit_type(Self, NodeRef) -> Control = _
    fn visit_constant(Self, NodeRef) -> Control = _
    fn visit_guard(Self, NodeRef) -> Control = _
    fn visit_clause(Self, NodeRef) -> Control = _
    fn visit_other(Self, NodeRef) -> Control = _
    fn leave(Self, NodeRef) -> Unit = _
    }

    A visitor over the node model: one method per group of node types (see accept for which method sees which node). Every method has a default: visit_* returns Continue and leave does nothing, so a visitor implements only what it needs. The visitor's state belongs to the caller; for a traversal that returns a value, see reduce and fold.

    Control

    pub(all) enum Control {
    Continue
    SkipChildren
    Stop
    } derive(Eq,
    Debug
    )

    What a traversal does after it enters a node.

    EnterEvent

    pub struct EnterEvent {
    category : String
    kind : String
    field : String?
    start : Int
    depth : Int
    path : NodePath
    } derive(Eq,
    Debug
    )

    What an Enter event tells: only what a streaming parser knows when a node starts. field is the node's field in its parent and path the steps from the start node (None and the empty path for the start node); depth is path.depth(); start is the node's first byte.

    EnterEvent::new

    fn EnterEvent::new(category~ : String, kind~ : String, field~ : String?, start~ : Int, path~ : NodePath) -> EnterEvent

    An EnterEvent; its depth is path.depth(). Event sources outside this package build events with new, so fields added later need not break them.

    test {
    let path = @syntax.NodePath::root().child("definitions", 0)
    let e = @syntax.EnterEvent::new(
    category="definition",
    kind="function",
    field=Some("definitions"),
    start=0,
    path~,
    )
    inspect(e.depth, content="1")
    inspect(e.path, content="definitions[0]")
    }

    Event

    pub(all) enum Event {
    Enter(EnterEvent)
    Leave(LeaveEvent)
    } derive(Eq,
    Debug
    )

    A traversal event, in walk order.

    EventReader

    pub struct EventReader {
    // private fields
    }

    Reads the events of a tree one at a time (the walk engine, paused between next calls). Each reader owns its state; readers over one tree do not affect each other. To stop early, stop calling next.

    test {
    let root = @syntax.NodeRef::of_result(
    @parser.parse_text("pub fn f(x) {\n x\n}\n"),
    )
    let reader = @syntax.EventReader::new(root)
    let log = []
    let mut body = ""
    while reader.next() is Some(event) {
    match event {
    Enter(e) => {
    log.push("+" + e.kind)
    if e.category == "expression" {
    body = "\{e.field.unwrap()} at depth \{e.depth}: \{e.path}"
    }
    // Skip the arguments: their `Leave` comes next.
    if e.category == "arg" {
    reader.skip_children()
    }
    }
    Leave(e) => log.push("-" + e.node.kind())
    }
    }
    inspect(
    log.join(" "),
    content="+module +function +arg -arg +expression +var -var -expression -function -module",
    )
    inspect(
    body,
    content="expression at depth 3: definitions[0].body[0].expression[0]",
    )
    }

    EventReader::iter

    fn EventReader::iter(self : EventReader) -> Iter[Event]

    The reader's remaining events as an Iter. The iterator calls next on this reader, so it shares the reader's state: call skip_children on the reader right after an Enter to skip that node's children, and break out of the loop to stop.

    test {
    let root = @syntax.NodeRef::of_result(
    @parser.parse_text("pub fn add(a, b) {\n a + b\n}\n"),
    )
    let reader = @syntax.EventReader::new(root)
    let args = []
    for event in reader.iter() {
    if event is Enter(e) {
    if e.category == "expression" {
    reader.skip_children()
    }
    if e.category == "arg" {
    args.push(e.path.to_string())
    }
    }
    }
    debug_inspect(
    args,
    content="[\"definitions[0].arguments[0]\", \"definitions[0].arguments[1]\"]",
    )
    }

    EventReader::new

    fn EventReader::new(root : NodeRef) -> EventReader

    A reader positioned before root's Enter.

    EventReader::next

    fn EventReader::next(self : EventReader) -> Event?

    EventReader::skip_children

    fn EventReader::skip_children(self : EventReader) -> Unit

    LeaveEvent

    pub struct LeaveEvent {
    node : NodeRef
    depth : Int
    path : NodePath
    } derive(Eq,
    Debug
    )

    What a Leave event tells: the finished node, with the depth and path of its Enter.

    LeaveEvent::new

    fn LeaveEvent::new(node~ : NodeRef, path~ : NodePath) -> LeaveEvent

    A LeaveEvent; its depth is path.depth().

    test {
    let root = @syntax.NodeRef::of_result(@parser.parse_text("const x = 1\n"))
    let e = @syntax.LeaveEvent::new(node=root, path=@syntax.NodePath::root())
    inspect(e.depth, content="0")
    inspect(e.node.kind(), content="module")
    }

    NodePath

    pub struct NodePath {
    // private fields
    } derive(Eq, Hash)

    Where a node sits below a start node: the steps from the start node, each a field and an index, like a path into the AST JSON (definitions[0].body[1].value[0]). Two differences from the JSON path: a field that holds one node gets index 0 (body[1].value[0], not body[1].value), and the definition wrapper of a targeted definition is not a step (definitions[0] is the definition). A path is immutable; a child's path shares its parent's steps. Get one from Tree::node_path, an EnterEvent or TreeCursor::path.

    test {
    let root = @syntax.NodeRef::of_result(
    @parser.parse_text("pub fn add(a, b) {\n a + b\n}\n"),
    )
    let path = @syntax.NodePath::root()
    .child("definitions", 0)
    .child("body", 0)
    .child("expression", 0)
    .child("right", 0)
    inspect(path, content="definitions[0].body[0].expression[0].right[0]")
    inspect(path.depth(), content="4")
    debug_inspect(path.last(), content="Some({ field: \"right\", index: 0 })")
    debug_inspect(path.resolve(root).map(n => n.kind()), content="Some(\"var\")")
    let up = path.parent().unwrap()
    inspect(up, content="definitions[0].body[0].expression[0]")
    inspect(up.resolve(root).unwrap().kind(), content="bin_op")
    let missing = @syntax.NodePath::root().child("definitions", 5)
    inspect(missing.resolve(root) is None, content="true")
    }
    impl Show for NodePath
    impl Debug for NodePath

    NodePath::append

    fn NodePath::append(self : NodePath, rest : NodePath) -> NodePath

    This path followed by rest: when this path leads from start to n and rest leads from n on, the result leads from start through n. The empty path is the identity on both sides. It takes time linear in rest.depth() and shares this path's steps.

    test {
    let to_body = @syntax.NodePath::root()
    .child("definitions", 0)
    .child("body", 0)
    let below = @syntax.NodePath::root().child("expression", 0).child("right", 0)
    inspect(
    to_body.append(below),
    content="definitions[0].body[0].expression[0].right[0]",
    )
    inspect(to_body.append(@syntax.NodePath::root()) == to_body, content="true")
    }

    NodePath::child

    fn NodePath::child(self : NodePath, field : String, index : Int) -> NodePath

    The path of the node at index in this node's field.

    NodePath::depth

    fn NodePath::depth(self : NodePath) -> Int

    The number of steps (0 for the start node).

    NodePath::from_steps

    fn NodePath::from_steps(steps : ArrayView[PathStep]) -> NodePath

    The path with these steps, from the start node down. from_steps(p.steps()) equals p.

    test {
    let path = @syntax.NodePath::from_steps([
    { field: "definitions", index: 0, },
    { field: "body", index: 1, },
    ])
    inspect(path, content="definitions[0].body[1]")
    debug_inspect(
    path.steps().map(s => s.field),
    content="[\"definitions\", \"body\"]",
    )
    inspect(@syntax.NodePath::from_steps(path.steps()) == path, content="true")
    }

    NodePath::last

    fn NodePath::last(self : NodePath) -> PathStep?

    The last step: the node's field and index in its parent.

    NodePath::parent

    fn NodePath::parent(self : NodePath) -> NodePath?

    The parent's path, or None for the start node.

    NodePath::resolve

    fn NodePath::resolve(self : NodePath, start : NodeRef) -> NodeRef?

    The node this path leads to from start, or None when a step does not exist there.

    NodePath::root

    fn NodePath::root() -> NodePath

    The path of the start node: no steps, depth 0.

    NodePath::steps

    fn NodePath::steps(self : NodePath) -> Array[PathStep]

    The steps, from the start node down (a new array).

    NodeRef

    pub(all) enum NodeRef {
    Module(
    Module
    ,
    ModuleCst
    )
    Definition(
    TargetedDefinition
    )
    Statement(
    Statement
    )
    Expr(
    Expr
    )
    Pattern(
    Pattern
    )
    TypeAst(
    TypeAst
    )
    Constant(
    Constant
    )
    ClauseGuard(
    ClauseGuard
    )
    BitArraySize(
    BitArraySize
    )
    Clause(
    Clause
    )
    Arg(
    Arg
    )
    RecordConstructor(
    RecordConstructor
    )
    RecordConstructorArg(
    RecordConstructorArg
    )
    UseAssignment(
    UseAssignment
    )
    TailPattern(
    TailPattern
    )
    ExprArg(
    CallArg
    [
    Expr
    ])
    PatternArg(
    CallArg
    [
    Pattern
    ])
    ConstantArg(
    CallArg
    [
    Constant
    ])
    ExprUpdateArg(
    RecordUpdateArg
    [
    Expr
    ])
    ConstantUpdateArg(
    RecordUpdateArg
    [
    Constant
    ])
    ExprRecord(
    RecordBeingUpdated
    [
    Expr
    ])
    ConstantRecord(
    RecordBeingUpdated
    [
    Constant
    ])
    ExprSegment(
    BitArraySegment
    [
    Expr
    ])
    PatternSegment(
    BitArraySegment
    [
    Pattern
    ])
    ConstantSegment(
    BitArraySegment
    [
    Constant
    ])
    ExprOption(
    BitArrayOption
    [
    Expr
    ])
    PatternOption(
    BitArrayOption
    [
    Pattern
    ])
    ConstantOption(
    BitArrayOption
    [
    Constant
    ])
    } derive(Eq,
    Debug
    )

    A read-only reference to one node of a parse result. Each case points into the typed AST (nothing is copied). Generic code uses category, kind, span, children and field; typed code matches the cases.

    Kinds and field names follow the JSON that @ast.encode_* writes (docs/ast-json.md); (category, kind) identifies a node type. The generic wrappers of the AST (call arguments, record update arguments, the record of a record update, bit array segments and options) are one case per payload type, with one category for all three.

    test {
    let r = @parser.parse_text("pub fn main() {\n 1\n}\n")
    let root = @syntax.NodeRef::of_result(r)
    let def = root.children()[0]
    inspect("\{def.category()}/\{def.kind()}", content="definition/function")
    match def {
    Definition(t) =>
    match t.definition {
    Function(f) =>
    debug_inspect(f.name.map(n => n.name), content="Some(\"main\")")
    _ => fail("not a function")
    }
    _ => fail("not a definition")
    }
    }

    NodeRef::category

    fn NodeRef::category(self : NodeRef) -> String

    The node type: "module", "definition", "statement", "expression", "pattern", "type", "constant", "clause_guard", "bit_array_size", "clause", "arg", "record_constructor", "record_constructor_arg", "use_assignment", "tail_pattern", "call_arg", "record_update_arg", "record_being_updated", "bit_array_segment", "bit_array_option".

    NodeRef::children

    fn NodeRef::children(self : NodeRef) -> Array[NodeRef]

    All child nodes in source order (a new array). Use field_of to get the field of one child, and children_with_fields (or Tree::field_of) to get the field of each child.

    test {
    let call = @syntax.NodeRef::Expr(@parser.parse_expression_text("f(x, y)"))
    debug_inspect(
    call.children().map(c => c.kind()),
    content="[\"var\", \"call_arg\", \"call_arg\"]",
    )
    }

    NodeRef::children_with_fields

    fn NodeRef::children_with_fields(self : NodeRef) -> Array[(PathStep, NodeRef)]

    All child nodes in source order (a new array), each with its step: its field and its index in that field. Children that start at the same place keep their field order. A clause's alternative_patterns is a list of lists in the JSON; here its patterns are flattened, so the index is the flat position.

    test {
    let sum = @syntax.NodeRef::Expr(@parser.parse_expression_text("a + b"))
    let labels = sum
    .children_with_fields()
    .map(x => "\{x.0.field}[\{x.0.index}]:\{x.1.kind()}")
    inspect(labels.join(" "), content="left[0]:var right[0]:var")
    }

    NodeRef::field

    fn NodeRef::field(self : NodeRef, name : String) -> Array[NodeRef]

    The nodes in field name (a new array; empty for an absent or unknown field).

    test {
    let r = @parser.parse_text("pub fn f(a, b) {\n a\n}\n")
    let f = @syntax.NodeRef::of_result(r).field("definitions")[0]
    inspect(f.field("arguments").length(), content="2")
    inspect(f.field("no_such_field").length(), content="0")
    }

    NodeRef::field_of

    fn NodeRef::field_of(self : NodeRef, child : NodeRef) -> String?

    The field of this node that holds child, or None when child is not a child of this node. It looks through all the fields on each call; to get the field of every child, use children_with_fields or Tree::field_of.

    test {
    let sum = @syntax.NodeRef::Expr(@parser.parse_expression_text("a + b"))
    let right = sum.field("right")[0]
    debug_inspect(sum.field_of(right), content="Some(\"right\")")
    debug_inspect(right.field_of(sum), content="None")
    }

    NodeRef::fields

    fn NodeRef::fields(self : NodeRef) -> Array[String]

    The names of this node's fields that can hold nodes, in JSON order. A field is listed also when it holds no node (a function with no return annotation has an empty return_annotation field).

    test {
    let r = @parser.parse_text("pub fn f(a) {\n a\n}\n")
    let f = @syntax.NodeRef::of_result(r).field("definitions")[0]
    debug_inspect(
    f.fields(),
    content="[\"arguments\", \"body\", \"return_annotation\"]",
    )
    inspect(f.field("return_annotation").length(), content="0")
    }

    NodeRef::kind

    fn NodeRef::kind(self : NodeRef) -> String

    The JSON kind of the node's value, or its category for a value with no kind (a clause, an argument, ...). A kind alone does not identify a node type (list is an expression, a pattern and a constant): compare category() too.

    test {
    let e = @syntax.NodeRef::Expr(@parser.parse_expression_text("[1, 2]"))
    let p = @syntax.NodeRef::Pattern(@parser.parse_pattern_text("[a, b]"))
    inspect("\{e.category()}/\{e.kind()}", content="expression/list")
    inspect("\{p.category()}/\{p.kind()}", content="pattern/list")
    }

    NodeRef::of_result

    The root node of result: its module. A parse always gives a module (a definition with a syntax error is skipped), so there is always a root. The AST is shared with result; nothing is copied.

    test {
    let root = @syntax.NodeRef::of_result(@parser.parse_text("const x = 1\n"))
    inspect(root.kind(), content="module")
    inspect(root.children().length(), content="1")
    }

    NodeRef::span

    The node's span, in UTF-8 byte offsets (the end is exclusive). A node's span contains its children's spans. The module spans from 0 to the end of its last token. A definition spans from its head to its last child: end_position for a function or a custom type, the value of a constant, the type of an alias (the compiler's location covers the head only). A clause guard starts at the leftmost node of its left spine (the compiler locates a TupleIndex guard at .index only). Every other node has the AST's location. A pipeline runs from its first to its last expression. A pattern as name runs from the pattern's start to the name's end.

    test {
    let r = @parser.parse_text("pub fn main() {\n 1\n}\n")
    let def = @syntax.NodeRef::of_result(r).children()[0]
    debug_inspect(def.span(), content="{ start: 0, end: 21 }")
    }

    PathStep

    pub(all) struct PathStep {
    field : String
    index : Int
    } derive(Eq, Hash,
    Debug
    )

    One step of a node path: a field of the parent and the node's index in that field (0 for a field that holds one node).

    Tree

    pub struct Tree {
    // private fields
    }

    A parse result as a tree with random access: parents, ancestors, paths, the node at a byte offset and the tokens in a span. It is built once and never changes; its indexes are private.

    Tree::ancestors

    fn Tree::ancestors(self : Tree, n : NodeRef) -> Array[NodeRef]

    The parents of n, nearest first, ending at the root (a new array).

    Tree::field_of

    fn Tree::field_of(self : Tree, n : NodeRef) -> String?

    The field of the parent of n that holds n, or None for the root and for nodes not in the tree. It gives what parent.field_of(n) gives, but reads the tree's index, so a walk can call it for every node.

    Tree::new

    The tree of result. It walks the whole tree once to index the parents (an explicit stack), so build it once and keep it.

    test {
    let tree = @syntax.Tree::new(
    @parser.parse_text("pub fn add(a, b) {\n a + b\n}\n"),
    )
    let body = tree.root().field("definitions")[0].field("body")[0]
    inspect(tree.parent(body).unwrap().kind(), content="function")
    inspect(tree.parent(tree.root()) is None, content="true")
    debug_inspect(
    tree.ancestors(body).map(n => n.kind()),
    content="[\"function\", \"module\"]",
    )
    debug_inspect(
    tree.path(body).map(n => n.kind()),
    content="[\"module\", \"function\", \"expression\"]",
    )
    }

    Tree::node_at

    fn Tree::node_at(self : Tree, p : Int) -> NodeRef?

    The innermost node whose span contains byte offset p, or None outside the text. Every branch that contains p is searched; the smallest span wins; for equal spans the deeper node wins, then the first in source order. See node_path for an example.

    Tree::node_path

    fn Tree::node_path(self : Tree, n : NodeRef) -> NodePath?

    The steps from the root to n, or None when n is not in the tree. With node_at, this takes an editor from an offset to a path.

    test {
    let tree = @syntax.Tree::new(
    @parser.parse_text("pub fn add(a, b) {\n a + b\n}\n"),
    )
    // Byte 25 is the `b` in `a + b`.
    let b = tree.node_at(25).unwrap()
    inspect("\{b.category()}/\{b.kind()}", content="expression/var")
    inspect(
    tree.node_path(b).unwrap(),
    content="definitions[0].body[0].expression[0].right[0]",
    )
    }

    Tree::parent

    fn Tree::parent(self : Tree, n : NodeRef) -> NodeRef?

    The parent of n, or None for the root and for nodes not in the tree.

    Tree::path

    fn Tree::path(self : Tree, n : NodeRef) -> Array[NodeRef]

    The nodes from the root down to n, both included (a new array).

    Tree::root

    fn Tree::root(self : Tree) -> NodeRef

    The module node at the top of the tree.

    Tree::step

    fn Tree::step(self : Tree, n : NodeRef) -> PathStep?

    The step from the parent of n to n (its field and its index in that field), or None for the root and for nodes not in the tree. It reads the tree's index, so it does not look through the parent's fields.

    test {
    let tree = @syntax.Tree::new(
    @parser.parse_text("pub fn add(a, b) {\n a + b\n}\n"),
    )
    let b = tree.root().field("definitions")[0].field("arguments")[1]
    debug_inspect(
    tree.step(b),
    content="Some({ field: \"arguments\", index: 1 })",
    )
    debug_inspect(tree.field_of(b), content="Some(\"arguments\")")
    debug_inspect(tree.field_of(tree.root()), content="None")
    }

    Tree::tokens_in

    The tokens that lie inside s, in source order (a new array; the tokens are shared with the parse result). A token that lies only partly inside s is left out.

    test {
    let tree = @syntax.Tree::new(
    @parser.parse_text("pub fn f() {\n 1\n}\n\nconst x = 2\n"),
    )
    let f = tree.root().field("definitions")[0]
    debug_inspect(
    tree.tokens_in(f.span()).map(t => t.lexeme),
    content="[\"pub\", \"fn\", \"f\", \"(\", \")\", \"{\", \"1\", \"}\"]",
    )
    }

    TreeCursor

    pub struct TreeCursor {
    // private fields
    }

    A tree-sitter-style cursor: the caller moves it with the goto_* methods. Each goto_* returns false and leaves the cursor in place when there is nowhere to go. Its state is a stack of frames, not recursion. A cursor belongs to its caller; copy gives an independent cursor.

    test {
    let root = @syntax.NodeRef::of_result(
    @parser.parse_text("pub fn add(a, b) {\n a + b\n}\n"),
    )
    let cursor = @syntax.TreeCursor::new(root)
    inspect(cursor.goto_first_child(), content="true")
    inspect(cursor.node().kind(), content="function")
    inspect(cursor.goto_next_sibling(), content="false")
    // Down to the arguments of `add`.
    ignore(cursor.goto_first_child())
    inspect(cursor.goto_next_sibling(), content="true")
    debug_inspect(cursor.field_name(), content="Some(\"arguments\")")
    inspect(cursor.depth(), content="2")
    inspect(cursor.path(), content="definitions[0].arguments[1]")
    // A copy moves on its own.
    let other = cursor.copy()
    ignore(other.goto_parent())
    inspect(cursor.node().kind(), content="arg")
    }

    TreeCursor::copy

    fn TreeCursor::copy(self : TreeCursor) -> TreeCursor

    An independent cursor at the same place.

    TreeCursor::depth

    fn TreeCursor::depth(self : TreeCursor) -> Int

    The number of goto_* steps below the start node (0 at the start node).

    TreeCursor::field_name

    fn TreeCursor::field_name(self : TreeCursor) -> String?

    The node's field in its parent ("arguments", ...); None at the start node.

    TreeCursor::goto_first_child

    fn TreeCursor::goto_first_child(self : TreeCursor) -> Bool

    Move to the node's first child in source order. Return false and stay when the node has no children.

    TreeCursor::goto_first_child_for

    fn TreeCursor::goto_first_child_for(self : TreeCursor, p : Int) -> Bool

    Move to the child whose span contains byte offset p, or else to the first child that starts at or after p. Where children overlap, the innermost one that contains p wins, and the first in source order among equals.

    goto_node_at(p) moves to Tree::node_at(p) in one call. By hand, descend while the new node contains p, and step back with goto_parent from the first one that does not: a loop that only calls goto_first_child_for goes one node too far whenever node_at(p) has children after p. One place where the descent does not reach node_at(p): a function capture f(_) gives a generated argument whose span is the hole's, so the descent stops at that argument while node_at gives the hole's variable inside the call; goto_node_at reaches it.

    test {
    let tree = @syntax.Tree::new(
    @parser.parse_text("pub fn add(a, b) {\n a + b\n}\n"),
    )
    let p = 23 // the `+` in `a + b`
    let contains = (s : @span.SrcSpan) => s.start <= p && p < s.end
    let cursor = @syntax.TreeCursor::new(tree.root())
    while cursor.goto_first_child_for(p) {
    if !contains(cursor.node().span()) {
    ignore(cursor.goto_parent())
    break
    }
    }
    inspect(cursor.node().kind(), content="bin_op")
    inspect(tree.node_at(p) == Some(cursor.node()), content="true")
    // A plain loop goes one node too far, to `b` after the `+`.
    let naive = @syntax.TreeCursor::new(tree.root())
    while naive.goto_first_child_for(p) {

    }
    inspect(naive.path(), content="definitions[0].body[0].expression[0].right[0]")
    }

    TreeCursor::goto_last_child

    fn TreeCursor::goto_last_child(self : TreeCursor) -> Bool

    Move to the node's last child in source order. Return false and stay when the node has no children.

    TreeCursor::goto_next_sibling

    fn TreeCursor::goto_next_sibling(self : TreeCursor) -> Bool

    Move to the next child of the same parent, in source order. Return false and stay at the last child and at the start node.

    TreeCursor::goto_node_at

    fn TreeCursor::goto_node_at(self : TreeCursor, p : Int) -> Bool

    Move to Tree::node_at(p) within the current node: the innermost node at or below it whose span contains p (the smallest span; for equal spans the deeper node, then the first in source order). goto_parent then goes back up the way it came. Return false and stay when the current node does not contain p; stay and return true when no child does.

    test {
    let tree = @syntax.Tree::new(
    @parser.parse_text("pub fn add(a, b) {\n a + b\n}\n"),
    )
    let cursor = @syntax.TreeCursor::new(tree.root())
    inspect(cursor.goto_node_at(23), content="true")
    inspect(cursor.node().kind(), content="bin_op")
    inspect(cursor.path(), content="definitions[0].body[0].expression[0]")
    // `pub` is outside the current node: the cursor stays.
    inspect(cursor.goto_node_at(0), content="false")
    inspect(cursor.node().kind(), content="bin_op")
    }

    TreeCursor::goto_parent

    fn TreeCursor::goto_parent(self : TreeCursor) -> Bool

    Move to the parent. Return false and stay at the start node: the cursor never goes above the node it started at.

    TreeCursor::goto_previous_sibling

    fn TreeCursor::goto_previous_sibling(self : TreeCursor) -> Bool

    Move to the previous child of the same parent, in source order. Return false and stay at the first child and at the start node.

    TreeCursor::new

    fn TreeCursor::new(root : NodeRef) -> TreeCursor

    A cursor at root (depth 0, no field).

    TreeCursor::node

    fn TreeCursor::node(self : TreeCursor) -> NodeRef

    The node at the cursor.

    TreeCursor::path

    fn TreeCursor::path(self : TreeCursor) -> NodePath

    The steps from the start node to the node (each frame keeps its parent's path, so this is O(1)).

    TreeCursor::reset

    fn TreeCursor::reset(self : TreeCursor, node : NodeRef) -> Unit

    Move the cursor to a new start node (depth 0).

    accept

    fn[V : Visitor] accept(root : NodeRef, visitor : V) -> Unit

    Walk root (see walk) and call one visitor method per node: definitions → visit_definition; statements → visit_statement; expressions, patterns (tail patterns included), types, constants → visit_expression, visit_pattern, visit_type, visit_constant; clause guards → visit_guard; clauses → visit_clause; every other node → visit_other. The method's Control steers the walk; leave runs after each node's children.

    A visitor needs its own type, so this example is not a doc test (a doc test holds only test blocks). The cookbook article "Choose a traversal" has a tested visitor: https://github.com/moonrockz/gleam-parser/blob/main/docs/cookbook/traverse.mbt.md

    struct Names {
    functions : Array[String]
    }

    impl @gleam.Visitor for Names with fn visit_definition(self, n) {
    match n {
    Definition({ definition: Function(f), .. }) =>
    self.functions.push(f.name.map(x => x.name).unwrap_or("?"))
    _ => ()
    }
    Continue
    }

    test {
    let root = @gleam.NodeRef::of_result(
    @gleam.parse_text("pub fn add(a, b) {\n a + b\n}\n"),
    )
    let names : Names = { functions: [], }
    @gleam.accept(root, names)
    debug_inspect(names.functions, content="[\"add\"]")
    }

    fold

    fn[A] fold(root : NodeRef, init : A, enter : (A, NodeRef) -> (A, Control), leave? : (A, NodeRef) -> A) -> A

    Walk root (see walk) with an accumulator: enter and leave take the current value and return the next one, in walk's callback order. The result is the value after the last callback. The accumulator belongs to the caller; fold does not copy it.

    test {
    let root = @syntax.NodeRef::of_result(
    @parser.parse_text("pub fn f() {\n [[1]]\n}\n"),
    )
    // Count the expressions and find the deepest nesting.
    let (count, _, deepest) = @syntax.fold(
    root,
    (0, 0, 0),
    (acc, n) => {
    let (count, depth, deepest) = acc
    let count = count + (if n.category() == "expression" { 1 } else { 0 })
    let depth = depth + 1
    let deepest = if depth > deepest { depth } else { deepest }
    ((count, depth, deepest), Continue)
    },
    leave=(acc, _) => (acc.0, acc.1 - 1, acc.2),
    )
    inspect(count, content="3")
    inspect(deepest, content="6")
    }

    kind_table

    fn kind_table() -> Array[(String, String, Array[String])]

    Every node type: (category, kind, fields), one row per kind, the fields in JSON order. The kinds and field names are the vocabulary of docs/ast-json.md; a struct without a kind uses its category.

    test {
    let rows = @syntax.kind_table()
    let row = rows.filter(r => r.0 == "expression" && r.1 == "call")[0]
    debug_inspect(row.2, content="[\"fun\", \"arguments\"]")
    }

    push_events

    fn[S : EventSource, H : Handler] push_events(source : S, handler : H) -> Unit

    Pull every event from source and push it to handler: on_enter for Enter (its SkipChildren skips the node's children, its Stop ends the run) and on_leave for Leave. The source is usually an EventReader; a streaming parser can implement EventSource too, and handlers do not change.

    A handler needs its own type, so this example is not a doc test (a doc test holds only test blocks). The cookbook article "Choose a traversal" has a tested handler: https://github.com/moonrockz/gleam-parser/blob/main/docs/cookbook/traverse.mbt.md

    struct Paths {
    found : Array[String]
    }

    impl @gleam.Handler for Paths with fn on_enter(self, e) {
    if e.category == "arg" {
    self.found.push(e.path.to_string())
    }
    Continue
    }

    test {
    let root = @gleam.NodeRef::of_result(
    @gleam.parse_text("pub fn add(a, b) {\n a + b\n}\n"),
    )
    let paths : Paths = { found: [], }
    @gleam.push_events(@gleam.EventReader::new(root), paths)
    inspect(paths.found[1], content="definitions[0].arguments[1]")
    }

    reduce

    fn[A] reduce(root : NodeRef, f : (NodeRef, ArrayView[A]) -> A) -> A

    Reduce the tree below root to one value, bottom up: f gets a node and its children's results in children() order and returns the node's result; the root's result is the answer. This is the visitor that returns a value: a trait cannot carry the result type, a closure can. An explicit stack, not recursion, holds the open nodes, so trees of any depth work on every target.

    test {
    let r = @parser.parse_text("pub fn f() {\n [[1]]\n}\n")
    let root = @syntax.NodeRef::of_result(r)
    let nodes = @syntax.reduce(root, (_, kids) => {
    let mut n = 1
    for k in kids {
    n += k
    }
    n
    })
    inspect(nodes, content="6")
    }

    reduce_with_fields

    fn[A] reduce_with_fields(root : NodeRef, f : (NodeRef, ArrayView[(PathStep, A)]) -> A) -> A

    reduce with each child's step (its field and index) next to its result, so f can tell left from right.

    test {
    let sum = @syntax.NodeRef::Expr(@parser.parse_expression_text("a + b"))
    let shown = @syntax.reduce_with_fields(sum, (n, kids) => {
    let parts = kids.to_owned().map(k => "\{k.0.field}=\{k.1}")
    if parts.length() == 0 {
    n.kind()
    } else {
    n.kind() + "{" + parts.join(" ") + "}"
    }
    })
    inspect(shown, content="bin_op{left=var right=var}")
    }

    walk

    fn walk(root : NodeRef, enter : (NodeRef) -> Control, leave? : (NodeRef) -> Unit) -> Unit

    Visit root and every node below it in pre-order (source order). enter runs when a node is reached and decides what happens next; leave runs after all of a node's children, with the same node object. An explicit stack, not recursion, holds the open nodes, so trees of any depth work on every target and no fuel is spent. All traversal state belongs to this call.

    test {
    let root = @syntax.NodeRef::of_result(
    @parser.parse_text("pub fn f(x) {\n x\n}\n"),
    )
    // Log enter and leave; skip the arguments.
    let log = []
    @syntax.walk(
    root,
    n => {
    log.push("+" + n.kind())
    if n.category() == "arg" {
    SkipChildren
    } else {
    Continue
    }
    },
    leave=n => log.push("-" + n.kind()),
    )
    inspect(
    log.join(" "),
    content="+module +function +arg -arg +expression +var -var -expression -function -module",
    )
    // Stop at the first `var`: the walk ends there, with no more `leave`.
    let seen = []
    @syntax.walk(
    root,
    n => {
    seen.push("+" + n.kind())
    if n.kind() == "var" {
    Stop
    } else {
    Continue
    }
    },
    leave=n => seen.push("-" + n.kind()),
    )
    inspect(
    seen.join(" "),
    content="+module +function +arg -arg +expression +var",
    )
    }