moonrockz/krueger/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_declaration(Self, NodeRef) -> Control = _
    fn visit_function(Self, NodeRef) -> Control = _
    fn visit_expression(Self, NodeRef) -> Control = _
    fn visit_pattern(Self, NodeRef) -> Control = _
    fn visit_type(Self, NodeRef) -> Control = _
    fn visit_case(Self, NodeRef) -> Control = _
    fn visit_import(Self, NodeRef) -> Control = _
    fn visit_comment(Self, NodeRef) -> Control = _
    fn visit_attribute(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. See accept for an example.

    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 :
    Location

    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().

    EnterEvent::new

    fn EnterEvent::new(category~ : String, kind~ : String, field~ : String?, start~ :
    Location
    , 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.

    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 src = "module Main exposing (..)\n\nf x = x\n"
    let result = @parser.parse_module(
    @scanner.SourceText::new(src),
    @scanner.DefaultScanner::new(),
    )
    let root = @syntax.NodeRef::of_result(result).unwrap()
    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 module header: its `Leave` comes next.
    if e.category == "module" {
    reader.skip_children()
    }
    }
    Leave(e) => log.push("-" + e.node.kind())
    }
    }
    inspect(
    log.join(" "),
    content="+file +normal -normal +function +implementation +name -name +var -var +functionOrValue -functionOrValue -implementation -function -file",
    )
    inspect(
    body,
    content="expression at depth 3: declarations[0].declaration[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 src = "module Main exposing (..)\n\nadd x y =\n x + y\n"
    let result = @parser.parse_module(
    @scanner.SourceText::new(src),
    @scanner.DefaultScanner::new(),
    )
    let root = @syntax.NodeRef::of_result(result).unwrap()
    let reader = @syntax.EventReader::new(root)
    let patterns = []
    for event in reader.iter() {
    if event is Enter(e) {
    if e.category == "module" {
    reader.skip_children()
    }
    if e.category == "pattern" {
    patterns.push(e.path.to_string())
    }
    }
    }
    debug_inspect(
    patterns,
    content=(
    #|[
    #| "declarations[0].declaration[0].arguments[0]",
    #| "declarations[0].declaration[0].arguments[1]",
    #|]
    ),
    )
    }

    EventReader::new

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

    A reader positioned before root's Enter.

    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().

    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 elm-syntax's JSON. 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 src = "module Main exposing (..)\n\nadd x y =\n x + y\n"
    let result = @parser.parse_module(
    @scanner.SourceText::new(src),
    @scanner.DefaultScanner::new(),
    )
    let root = @syntax.NodeRef::of_result(result).unwrap()
    let tree = @syntax.Tree::new(result).unwrap()
    let x = tree.node_at({ row: 4, column: 5, }).unwrap()
    let path = tree.node_path(x).unwrap()
    inspect(path, content="declarations[0].declaration[0].expression[0].left[0]")
    inspect(path.depth(), content="4")
    debug_inspect(path.last(), content="Some({ field: \"left\", index: 0 })")
    debug_inspect(
    path.steps().map(s => s.field),
    content="[\"declarations\", \"declaration\", \"expression\", \"left\"]",
    )
    // `resolve` follows the steps from a start node.
    inspect(path.resolve(root) == Some(x), content="true")
    let up = path.parent().unwrap()
    inspect(up, content="declarations[0].declaration[0].expression[0]")
    inspect(up.resolve(root).unwrap().kind(), content="operatorapplication")
    let missing = @syntax.NodePath::root().child("declarations", 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_expression = @syntax.NodePath::from_steps([
    { field: "declarations", index: 0, },
    { field: "declaration", index: 0, },
    { field: "expression", index: 0, },
    ])
    let below = @syntax.NodePath::root().child("right", 0)
    inspect(
    to_expression.append(below),
    content="declarations[0].declaration[0].expression[0].right[0]",
    )
    }

    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.

    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 {
    File(
    File
    , ArrayView[
    AttributeGroup
    ])
    Module(
    Node
    [
    Module
    ])
    ModuleName(
    Node
    [ArrayView[String]])
    Exposing(
    Node
    [
    Exposing
    ])
    Expose(
    Node
    [
    TopLevelExpose
    ])
    Import(
    Node
    [
    Import
    ])
    Declaration(
    Node
    [
    Declaration
    ], ArrayView[
    DocAttribute
    ])
    Documentation(
    Node
    [String])
    Signature(
    Node
    [
    Signature
    ])
    Implementation(
    Node
    [
    FunctionImplementation
    ])
    Constructor(
    Node
    [
    ValueConstructor
    ])
    Expression(
    Node
    [
    Expression
    ])
    LetDeclaration(
    Node
    [
    LetDeclaration
    ])
    Case(
    Case
    )
    RecordSetter(
    Node
    [
    RecordSetter
    ])
    Pattern(
    Node
    [
    Pattern
    ])
    TypeAnnotation(
    Node
    [
    TypeAnnotation
    ])
    RecordField(
    Node
    [
    RecordField
    ])
    Name(
    Node
    [String])
    Comment(
    Node
    [String])
    Attribute(
    DocAttribute
    )
    } 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, range, children and field; typed code matches the cases.

    Kinds and field names follow the JSON that elm-syntax (and @ast.encode_*) writes. elm-syntax reuses tags across node types (record, list, unit, …), so (category, kind) identifies a node type.

    Get the root with NodeRef::of_result (or Tree::root), then go down with field or children:

    test {
    let src = "module Main exposing (..)\n\nadd x y =\n x + y\n"
    let result = @parser.parse_module(
    @scanner.SourceText::new(src),
    @scanner.DefaultScanner::new(),
    )
    let root = @syntax.NodeRef::of_result(result).unwrap()
    let decl = root.field("declarations")[0]
    inspect("\{decl.category()}/\{decl.kind()}", content="declaration/function")
    // Typed code matches the case and reads the AST value.
    match decl {
    Declaration(node, _) => inspect(node.range.start.row, content="3")
    _ => fail("not a declaration")
    }
    }

    NodeRef::category

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

    The node type: "expression", "pattern", "type", "declaration", …

    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 src = "module Main exposing (..)\n\nadd x y =\n x + y\n"
    let result = @parser.parse_module(
    @scanner.SourceText::new(src),
    @scanner.DefaultScanner::new(),
    )
    let root = @syntax.NodeRef::of_result(result).unwrap()
    let implementation = root.field("declarations")[0].field("declaration")[0]
    let labels = implementation
    .children()
    .map(c => "\{implementation.field_of(c).unwrap()}:\{c.kind()}")
    inspect(
    labels.join(" "),
    content="name:name arguments:var arguments:var expression:operatorapplication",
    )
    }

    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. The nodes are the ones that children gives.

    Use it to get the field of every child in one call. field_of looks through all the fields on each call, so calling it for each child of a wide node (a file with many declarations) is quadratic.

    test {
    let src = "module Main exposing (..)\n\nadd x y =\n x + y\n"
    let result = @parser.parse_module(
    @scanner.SourceText::new(src),
    @scanner.DefaultScanner::new(),
    )
    let root = @syntax.NodeRef::of_result(result).unwrap()
    let implementation = root.field("declarations")[0].field("declaration")[0]
    let labels = implementation
    .children_with_fields()
    .map(x => "\{x.0.field}[\{x.0.index}]:\{x.1.kind()}")
    inspect(
    labels.join(" "),
    content="name[0]:name arguments[0]:var arguments[1]:var expression[0]:operatorapplication",
    )
    }

    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).

    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. See children for an example.

    It looks through all the fields on each call (linear in the number of children). To get the field of every child, use children_with_fields, or Tree::field_of, which reads the tree's index.

    NodeRef::fields

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

    The names of this node's fields, in elm-syntax JSON order. A field is listed also when it holds no node (a function with no signature has an empty signature field).

    test {
    let src = "module Main exposing (..)\n\nadd x y =\n x + y\n"
    let result = @parser.parse_module(
    @scanner.SourceText::new(src),
    @scanner.DefaultScanner::new(),
    )
    let root = @syntax.NodeRef::of_result(result).unwrap()
    let decl = root.field("declarations")[0]
    inspect(
    decl.fields().join(" "),
    content="documentation signature declaration attributes",
    )
    inspect(decl.field("signature").length(), content="0")
    inspect(decl.field("no-such-field").length(), content="0")
    let arguments = decl.field("declaration")[0].field("arguments")
    debug_inspect(arguments.map(a => a.kind()), content="[\"var\", \"var\"]")
    }

    NodeRef::kind

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

    The elm-syntax JSON tag of the node, or its category when elm-syntax has no tag for it. A kind alone does not identify a node type: compare category() too.

    test {
    let src = "module Main exposing (..)\n\nf [] = []\n"
    let result = @parser.parse_module(
    @scanner.SourceText::new(src),
    @scanner.DefaultScanner::new(),
    )
    let root = @syntax.NodeRef::of_result(result).unwrap()
    let implementation = root.field("declarations")[0].field("declaration")[0]
    let argument = implementation.field("arguments")[0]
    let body = implementation.field("expression")[0]
    inspect("\{argument.category()}/\{argument.kind()}", content="pattern/list")
    inspect("\{body.category()}/\{body.kind()}", content="expression/list")
    }

    NodeRef::of_result

    The root node of result: its file with the doc attributes, or None when the parse produced no AST.

    The attribute groups are copied; the AST is shared with result, so neither may be changed while nodes are in use.

    test {
    let scanner = @scanner.DefaultScanner::new()
    let ok = @parser.parse_module(
    @scanner.SourceText::new("module Main exposing (..)\n\nx = 1\n"),
    scanner,
    )
    let kind = @syntax.NodeRef::of_result(ok).map(n => n.kind())
    debug_inspect(kind, content="Some(\"file\")")
    // A missing module header gives no AST, so there is no root.
    let bad = @parser.parse_module(@scanner.SourceText::new("x = 1\n"), scanner)
    inspect(@syntax.NodeRef::of_result(bad) is None, content="true")
    }

    NodeRef::range

    The node's range. A case branch runs from its pattern to its expression; the file from line 1, column 1 to the end of its last node. A declaration with doc attributes starts no later than its first attribute (a port's doc comment is not part of its elm-syntax range). Rows and columns start at 1; the end is exclusive.

    test {
    let src = "module Main exposing (..)\n\nadd x y =\n x + y\n"
    let result = @parser.parse_module(
    @scanner.SourceText::new(src),
    @scanner.DefaultScanner::new(),
    )
    let root = @syntax.NodeRef::of_result(result).unwrap()
    let show = (r : @ast.Range) => {
    "\{r.start.row}:\{r.start.column}-\{r.end.row}:\{r.end.column}"
    }
    inspect(show(root.range()), content="1:1-4:10")
    inspect(show(root.field("declarations")[0].range()), content="3:1-4:10")
    }

    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 position and the tokens in a range. 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. See step for an example.

    Tree::new

    The tree of result, or None when the parse produced no AST. It walks the whole tree once to index the parents, so build it once and keep it.

    test {
    let src = "module Main exposing (..)\n\nadd x y =\n x + y\n"
    let result = @parser.parse_module(
    @scanner.SourceText::new(src),
    @scanner.DefaultScanner::new(),
    )
    let tree = @syntax.Tree::new(result).unwrap()
    let decl = tree.root().field("declarations")[0]
    let body = decl.field("declaration")[0].field("expression")[0]
    inspect(tree.parent(body).unwrap().kind(), content="implementation")
    inspect(tree.parent(tree.root()) is None, content="true")
    debug_inspect(
    tree.ancestors(body).map(n => n.kind()),
    content="[\"implementation\", \"function\", \"file\"]",
    )
    debug_inspect(
    tree.path(body).map(n => n.kind()),
    content="[\"file\", \"function\", \"implementation\", \"operatorapplication\"]",
    )
    }

    Tree::node_at

    The innermost node whose range contains p, or None outside the file. Every branch that contains p is searched, because siblings can overlap: a comment (a child of the file) lies inside a declaration, and a port's doc comment holds the port's attributes. The smallest range wins; for equal ranges 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 a position to a path.

    test {
    let src = "module Main exposing (..)\n\nadd x y =\n x + y\n"
    let result = @parser.parse_module(
    @scanner.SourceText::new(src),
    @scanner.DefaultScanner::new(),
    )
    let tree = @syntax.Tree::new(result).unwrap()
    // Row 4, column 9 is the `y` in `x + y`.
    let y = tree.node_at({ row: 4, column: 9, }).unwrap()
    inspect("\{y.category()}/\{y.kind()}", content="expression/functionOrValue")
    inspect(
    tree.node_path(y).unwrap(),
    content="declarations[0].declaration[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 file 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 src = "module Main exposing (..)\n\nadd x y =\n x + y\n"
    let result = @parser.parse_module(
    @scanner.SourceText::new(src),
    @scanner.DefaultScanner::new(),
    )
    let tree = @syntax.Tree::new(result).unwrap()
    let implementation = tree.root().field("declarations")[0].field("declaration")[0]
    let y = implementation.field("arguments")[1]
    debug_inspect(
    tree.step(y),
    content="Some({ field: \"arguments\", index: 1 })",
    )
    debug_inspect(tree.field_of(y), content="Some(\"arguments\")")
    debug_inspect(tree.field_of(tree.root()), content="None")
    }

    Tree::tokens_in

    The tokens that lie inside r, in source order: new tokens with new trivia arrays, so changing them changes nothing in the tree. A token that lies only partly inside r is left out. The tree has no tokens when the parse produced no CST.

    test {
    let src = "module Main exposing (..)\n\nadd x y =\n x + y\n"
    let result = @parser.parse_module(
    @scanner.SourceText::new(src),
    @scanner.DefaultScanner::new(),
    )
    let tree = @syntax.Tree::new(result).unwrap()
    let decl = tree.root().field("declarations")[0]
    let lexemes = tree.tokens_in(decl.range()).map(t => t.lexeme)
    debug_inspect(
    lexemes,
    content="[\"add\", \"x\", \"y\", \"=\", \"x\", \"+\", \"y\"]",
    )
    }

    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 src = "module Main exposing (..)\n\nadd x y =\n x + y\n"
    let result = @parser.parse_module(
    @scanner.SourceText::new(src),
    @scanner.DefaultScanner::new(),
    )
    let root = @syntax.NodeRef::of_result(result).unwrap()
    let cursor = @syntax.TreeCursor::new(root)
    // To the module header, back to the file, then to the function.
    inspect(cursor.goto_first_child(), content="true")
    inspect(cursor.node().kind(), content="normal")
    inspect(cursor.goto_previous_sibling(), content="false")
    inspect(cursor.goto_parent(), content="true")
    inspect(cursor.goto_last_child(), content="true")
    inspect(cursor.node().kind(), content="function")
    // Down to the arguments of `add`.
    ignore(cursor.goto_first_child()) // implementation
    ignore(cursor.goto_first_child()) // name `add`
    inspect(cursor.goto_next_sibling(), content="true")
    debug_inspect(cursor.field_name(), content="Some(\"arguments\")")
    inspect(cursor.depth(), content="3")
    inspect(cursor.path(), content="declarations[0].declaration[0].arguments[0]")
    // A copy moves on its own.
    let other = cursor.copy()
    ignore(other.goto_parent())
    inspect(cursor.node().kind(), content="var")
    }

    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 :
    Location
    ) -> Bool

    Move to the child whose range contains p, or else to the first child that starts after p. Where children overlap (a doc comment and its attributes), 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. (Where a port's doc comment with attributes overlaps the port declaration only in part, the descent stops at the comment, which contains node_at(p).)

    test {
    let src = "module Main exposing (..)\n\nadd x y =\n x + y\n"
    let result = @parser.parse_module(
    @scanner.SourceText::new(src),
    @scanner.DefaultScanner::new(),
    )
    let tree = @syntax.Tree::new(result).unwrap()
    let p : @ast.Location = { row: 4, column: 7, } // the `+` in `x + y`
    let contains = (r : @ast.Range) => {
    let after_start = r.start.row < p.row ||
    (r.start.row == p.row && r.start.column <= p.column)
    let before_end = p.row < r.end.row ||
    (p.row == r.end.row && p.column < r.end.column)
    after_start && before_end
    }
    // The recipe: descend while the new node contains `p`.
    let cursor = @syntax.TreeCursor::new(tree.root())
    while cursor.goto_first_child_for(p) {
    if !contains(cursor.node().range()) {
    ignore(cursor.goto_parent())
    break
    }
    }
    inspect(cursor.node().kind(), content="operatorapplication")
    inspect(tree.node_at(p) == Some(cursor.node()), content="true")
    // A plain loop goes one node too far, to `y` after the `+`.
    let naive = @syntax.TreeCursor::new(tree.root())
    while naive.goto_first_child_for(p) {

    }
    inspect(
    naive.path(),
    content="declarations[0].declaration[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 :
    Location
    ) -> Bool

    Move to Tree::node_at(p) within the current node: the innermost node at or below it whose range contains p (the smallest range; for equal ranges 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.

    Unlike a descent with goto_first_child_for, it searches every child that contains p, so it also reaches node_at(p) where children overlap (a port's doc comment and the port).

    test {
    let src = "module Main exposing (..)\n\nadd x y =\n x + y\n"
    let result = @parser.parse_module(
    @scanner.SourceText::new(src),
    @scanner.DefaultScanner::new(),
    )
    let tree = @syntax.Tree::new(result).unwrap()
    let p : @ast.Location = { row: 4, column: 7, } // the `+` in `x + y`
    let cursor = @syntax.TreeCursor::new(tree.root())
    inspect(cursor.goto_node_at(p), content="true")
    inspect(cursor.node().kind(), content="operatorapplication")
    inspect(tree.node_at(p) == Some(cursor.node()), content="true")
    inspect(cursor.path(), content="declarations[0].declaration[0].expression[0]")
    // `add` is outside the current node: the cursor stays.
    inspect(cursor.goto_node_at({ row: 3, column: 1, }), content="false")
    inspect(cursor.node().kind(), content="operatorapplication")
    }

    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: functions (top-level or let) → visit_function; other declarations → visit_declaration; expressions, patterns, types → visit_expression, visit_pattern, visit_type; case branches → visit_case; imports, comments, doc attributes → visit_import, visit_comment, visit_attribute; every other node (doc comments included) → 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/krueger/blob/main/docs/cookbook/traversal.mbt.md

    struct Names {
    functions : Array[String]
    mut patterns : Int
    }

    impl @syntax.Visitor for Names with fn visit_function(self, n) {
    match n {
    Declaration({ value: FunctionDeclaration(f), .. }, _) =>
    self.functions.push(f.declaration.value.name.value)
    _ => ()
    }
    Continue
    }

    impl @syntax.Visitor for Names with fn visit_pattern(self, _) {
    self.patterns 1
    Continue
    }

    test {
    let src = "module Main exposing (..)\n\nadd x y =\n x + y\n\nz = 0\n"
    let result = @parser.parse_module(
    @scanner.SourceText::new(src),
    @scanner.DefaultScanner::new(),
    )
    let root = @syntax.NodeRef::of_result(result).unwrap()
    let names = { functions: [], patterns: 0, }
    @syntax.accept(root, names)
    debug_inspect(names.functions, content="[\"add\", \"z\"]")
    inspect(names.patterns, content="2")
    }

    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 src = "module Main exposing (..)\n\nadd x y =\n x + y\n"
    let result = @parser.parse_module(
    @scanner.SourceText::new(src),
    @scanner.DefaultScanner::new(),
    )
    let root = @syntax.NodeRef::of_result(result).unwrap()
    // 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="5")
    }

    kind_table

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

    Every node type: (category, kind, fields), one row per kind. The kinds and field names are the elm-syntax JSON vocabulary.

    test {
    let rows = @syntax.kind_table()
    let row = rows.filter(r => r.0 == "expression" && r.1 == "ifBlock")[0]
    debug_inspect(row.2, content="[\"clause\", \"then\", \"else\"]")
    // `list` is a kind of both expressions and patterns.
    let lists = rows.filter(r => r.1 == "list").map(r => r.0)
    debug_inspect(lists, content="[\"expression\", \"pattern\"]")
    }

    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/krueger/blob/main/docs/cookbook/traversal.mbt.md

    struct Paths {
    found : Array[String]
    }

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

    test {
    let src = "module Main exposing (..)\n\nadd x y =\n x + y\n"
    let result = @parser.parse_module(
    @scanner.SourceText::new(src),
    @scanner.DefaultScanner::new(),
    )
    let root = @syntax.NodeRef::of_result(result).unwrap()
    let paths = { found: [], }
    @syntax.push_events(@syntax.EventReader::new(root), paths)
    inspect(paths.found.length(), content="2")
    inspect(paths.found[1], content="declarations[0].declaration[0].arguments[1]")
    }

    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. All traversal state belongs to this call.

    test {
    let src = "module Main exposing (..)\n\nf x = x\n"
    let result = @parser.parse_module(
    @scanner.SourceText::new(src),
    @scanner.DefaultScanner::new(),
    )
    let root = @syntax.NodeRef::of_result(result).unwrap()
    // Log enter and leave; skip the module header.
    let log = []
    @syntax.walk(
    root,
    n => {
    log.push("+" + n.kind())
    if n.category() == "module" {
    SkipChildren
    } else {
    Continue
    }
    },
    leave=n => log.push("-" + n.kind()),
    )
    inspect(
    log.join(" "),
    content="+file +normal -normal +function +implementation +name -name +var -var +functionOrValue -functionOrValue -implementation -function -file",
    )
    // Stop at the first pattern: the walk ends there, with no more `leave`.
    let seen = []
    @syntax.walk(
    root,
    n => {
    seen.push("+" + n.kind())
    if n.category() == "pattern" {
    Stop
    } else {
    Continue
    }
    },
    leave=n => seen.push("-" + n.kind()),
    )
    inspect(
    seen.join(" "),
    content="+file +normal +module_name -module_name +all -all -normal +function +implementation +name -name +var",
    )
    }