moonrockz/krueger/syntax does not have a README file
pub(open) trait Handler {
fn on_enter(Self, EnterEvent) -> Control = _
fn on_leave(Self, LeaveEvent) -> Unit = _
}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 = _
}fn EnterEvent::new(category~ : String, kind~ : String, field~ : String?, start~ : Location, path~ : NodePath) -> EnterEventpub struct EventReader {
// private fields
}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]",
)
}impl EventSource for EventReadertest {
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]",
#|]
),
)
}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")
}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]",
)
}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)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")
}
}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",
)
}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",
)
}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\"]")
}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")
}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")
}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")
}pub struct Tree {
// private 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 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\"]",
)
}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]",
)
}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")
}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\"]",
)
}pub struct TreeCursor {
// private 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 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")
}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]",
)
}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")
}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")
}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")
}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\"]")
}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]")
}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",
)
}Install
Download zipParser and parsing utilities for Elm and Elm-like dialects (e.g. Morphir) in MoonBit
Dependencies