///|
test "fields and children" {
let r = @parser.parse_text(
(
#|$version: "2"
#|namespace a
#|
#|/// A person.
#|structure Person {
#| name: String
#|}
#|
),
)
let root = @traversal.NodeRef::of_result(r).unwrap()
let person = root.field("shapes")[0]
inspect("\{person.category()}/\{person.kind()}", content="shape/structure")
inspect(
person.children().map(c => c.category()).join(" "),
content="doc name member",
)
}///|
test "walk, fold and reduce" {
let r = @parser.parse_text("namespace a\n\n@t\nstring S\n")
let root = @traversal.NodeRef::of_result(r).unwrap()
let log = []
@traversal.walk(root, n => {
log.push(n.category())
if n.category() == "trait" {
SkipChildren
} else {
Continue
}
})
inspect(log.join(" "), content="file namespace name shape trait name")
let count = @traversal.fold(root, 0, (a, _) => (a + 1, Continue))
inspect(count, content="7")
// Bottom up: each node with the results of its children.
let leaves = @traversal.reduce(root, (_, rs : ArrayView[Int32]) => {
if rs.length() == 0 {
1
} else {
rs.iter().fold(init=0, (a, b) => a + b)
}
})
inspect(leaves, content="3")
// reduce_pruned can skip a subtree, or stop with None.
let pruned = @traversal.reduce_pruned(
root,
n => if n.category() == "trait" { SkipChildren } else { Continue },
(_, rs : ArrayView[Int32]) => 1 + rs.iter().fold(init=0, (a, b) => a + b),
)
debug_inspect(pruned, content="Some(6)")
}///|
test "pull events" {
let r = @parser.parse_text("namespace a\n\nstring S\n")
let root = @traversal.NodeRef::of_result(r).unwrap()
let reader = @traversal.EventReader::new(root)
let log = []
while reader.next() is Some(e) {
match e {
Enter(e) => {
log.push("+\{e.path}")
if e.category == "namespace" {
reader.skip_children()
}
}
Leave(_) => ()
}
}
inspect(
log.join(" "),
content="+ +namespace[0] +shapes[0] +shapes[0].name[0]",
)
}///|
struct TraitIds(Array[String])
///|
impl @traversal.Visitor for TraitIds with fn visit_trait(self, n) {
if n is Trait(t) {
self.0.push(t.id.value.to_string())
}
SkipChildren
}
///|
test "a visitor" {
let r = @parser.parse_text(
"namespace a\n\n@required\n@length(min: 1)\nstring S\n",
)
let ids = TraitIds([])
@traversal.accept(@traversal.NodeRef::of_result(r).unwrap(), ids)
debug_inspect(ids.0, content="[\"required\", \"length\"]")
}///|
struct Paths(Array[String])
///|
impl @traversal.Handler for Paths with fn on_enter(self, e) {
self.0.push(e.path.to_string())
if e.category == "shape" {
SkipChildren
} else {
Continue
}
}
///|
test "push events into a handler" {
let r = @parser.parse_text("namespace a\n\nstring S\n")
let root = @traversal.NodeRef::of_result(r).unwrap()
let paths = Paths([])
@traversal.push_events(@traversal.EventReader::new(root), paths)
debug_inspect(
paths.0,
content="[\"\", \"namespace[0]\", \"namespace[0].name[0]\", \"shapes[0]\"]",
)
}///|
test "tree, cursor and path" {
let text =
#|namespace a
#|
#|structure Foo {
#| x: String
#|}
#|
let tree = @traversal.Tree::new(@parser.parse_text(text)).unwrap()
let at = @span.Offset::new(text.find("x:").unwrap()).unwrap()
let name = tree.node_at(at).unwrap()
inspect(tree.node_path(name).unwrap(), content="shapes[0].members[0].name[0]")
let c = @traversal.TreeCursor::new(tree.root())
inspect(c.goto_node_at(at), content="true")
inspect(c.path(), content="shapes[0].members[0].name[0]")
}| Category | Kind | Fields |
|---|---|---|
| file | file | control, metadata, namespace, uses, shapes, comments |
| control | control | key, value |
| metadata | metadata | key, value |
| namespace | namespace | name |
| use | use | target |
| shape | blob | docs, traits, name, mixins |
| shape | boolean | docs, traits, name, mixins |
| shape | document | docs, traits, name, mixins |
| shape | string | docs, traits, name, mixins |
| shape | byte | docs, traits, name, mixins |
| shape | short | docs, traits, name, mixins |
| shape | integer | docs, traits, name, mixins |
| shape | long | docs, traits, name, mixins |
| shape | float | docs, traits, name, mixins |
| shape | double | docs, traits, name, mixins |
| shape | bigInteger | docs, traits, name, mixins |
| shape | bigDecimal | docs, traits, name, mixins |
| shape | timestamp | docs, traits, name, mixins |
| shape | enum | docs, traits, name, mixins, members |
| shape | intEnum | docs, traits, name, mixins, members |
| shape | list | docs, traits, name, for, mixins, members |
| shape | map | docs, traits, name, for, mixins, members |
| shape | union | docs, traits, name, for, mixins, members |
| shape | structure | docs, traits, name, for, mixins, members |
| shape | set | docs, traits, name, for, mixins, members |
| shape | service | docs, traits, name, mixins, value |
| shape | resource | docs, traits, name, mixins, value |
| shape | operation | docs, traits, name, mixins, properties |
| shape | apply | target, traits |
| shape | inline | docs, traits, for, mixins, members |
| mixins | mixins | targets |
| member | member | docs, traits, name, target, default |
| member | enumMember | docs, traits, name, value |
| operation_property | input | target |
| operation_property | output | target |
| operation_property | errors | targets |
| trait | trait | id, value, entries |
| value | array | elements |
| value | object | entries |
| value | number | — |
| value | boolean | — |
| value | null | — |
| value | string | — |
| value | textBlock | — |
| value | shapeId | — |
| entry | entry | key, value |
| shape_id | shape_id | — |
| name | name | — |
| doc | doc | — |
| comment | line | — |
| comment | doc | — |
pub(open) trait Handler {
fn on_enter(Self, EnterEvent) -> Control = _
fn on_leave(Self, LeaveEvent) -> Unit = _
}pub(open) trait Visitor {
fn visit_file(Self, NodeRef) -> Control = _
fn visit_statement(Self, NodeRef) -> Control = _
fn visit_shape(Self, NodeRef) -> Control = _
fn visit_member(Self, NodeRef) -> Control = _
fn visit_trait(Self, NodeRef) -> Control = _
fn visit_value(Self, NodeRef) -> Control = _
fn visit_comment(Self, NodeRef) -> Control = _
fn visit_other(Self, NodeRef) -> Control = _
fn leave(Self, NodeRef) -> Unit = _
}fn EnterEvent::new(category~ : String, kind~ : String, field~ : String?, start~ : Offset, path~ : NodePath) -> EnterEventpub struct EventReader {
// private fields
}test {
let r = @parser.parse_text("namespace a\n\nstring S\n")
let root = @traversal.NodeRef::of_result(r).unwrap()
let reader = @traversal.EventReader::new(root)
let log = []
for e in reader.iter() {
match e {
Enter(e) => log.push("+" + e.category)
Leave(e) => log.push("-" + e.node.category())
}
}
inspect(
log.join(" "),
content="+file +namespace +name -name -namespace +shape +name -name -shape -file",
)
}impl EventSource for EventReadertest {
let r = @parser.parse_text(
(
#|namespace a
#|
#|structure Foo {
#| x: String
#|}
#|
),
)
let root = @traversal.NodeRef::of_result(r).unwrap()
let path = @traversal.NodePath::root()
.child("shapes", 0)
.child("members", 0)
.child("target", 0)
inspect(path, content="shapes[0].members[0].target[0]")
debug_inspect(
path.resolve(root).map(n => n.kind()),
content="Some(\"shape_id\")",
)
}pub(all) enum NodeRef {
File(IdlFile, ArrayView[Comment])
Control(ControlStatement)
Metadata(MetadataStatement)
Namespace(NamespaceStatement)
Use(UseStatement)
Shape(ShapeStatement)
Apply(ApplyStatement)
Inline(InlineShape)
Mixins(Mixins)
Member(Member)
EnumMember(EnumMember)
Property(OperationProperty)
Trait(Trait)
Value(NodeValue)
Entry(NodeKvp)
ShapeIdRef(Node[ShapeId])
Name(Node[String])
Doc(DocComment)
Comment(Comment)
} derive(Eq, Debug)test {
let r = @parser.parse_text(
(
#|$version: "2"
#|namespace a
#|
#|string Name
#|
),
)
let root = @traversal.NodeRef::of_result(r).unwrap()
inspect("\{root.category()}/\{root.kind()}", content="file/file")
guard root is File(file, _) else { fail("not a file") }
inspect(file.statements.length(), content="1")
}test {
let r = @parser.parse_text(
(
#|$version: "2"
#|namespace a
#|
#|structure Foo {
#| a: String
#| b: String
#|}
#|
),
)
let shape = @traversal.NodeRef::of_result(r).unwrap().field("shapes")[0]
let labels = shape
.children_with_fields()
.map(x => "\{x.0.field}[\{x.0.index}]:\{x.1.kind()}")
inspect(
labels.join(" "),
content="name[0]:name members[0]:member members[1]:member",
)
}test {
let r = @parser.parse_text(
(
#|$version: "2"
#|namespace a
#|
#|structure Foo {
#| bar: String
#|}
#|
),
)
let shape = @traversal.NodeRef::of_result(r).unwrap().field("shapes")[0]
inspect(
shape.fields().join(" "),
content="docs traits name for mixins members",
)
inspect(shape.field("for").length(), content="0")
let member = shape.field("members")[0]
inspect(member.field("target")[0].kind(), content="shape_id")
}pub struct Tree {
// private fields
}test {
let r = @parser.parse_text(
(
#|namespace a
#|
#|structure Foo {
#| x: String
#|}
#|
),
)
let tree = @traversal.Tree::new(r).unwrap()
let member = tree.root().field("shapes")[0].field("members")[0]
inspect(tree.parent(member).unwrap().kind(), content="structure")
debug_inspect(
tree.path(member).map(n => n.kind()),
content="[\"file\", \"structure\", \"member\"]",
)
inspect(tree.node_path(member).unwrap(), content="shapes[0].members[0]")
}pub struct TreeCursor {
// private fields
}test {
let r = @parser.parse_text("namespace a\n\nstring S\n")
let c = @traversal.TreeCursor::new(@traversal.NodeRef::of_result(r).unwrap())
inspect(c.goto_first_child(), content="true")
debug_inspect(c.field_name(), content="Some(\"namespace\")")
inspect(c.goto_next_sibling(), content="true")
inspect(c.path(), content="shapes[0]")
inspect(c.goto_next_sibling(), content="false")
}test {
let r = @parser.parse_text("namespace a\n\nstring S\n")
let root = @traversal.NodeRef::of_result(r).unwrap()
let names = @traversal.fold(root, [], (acc, n) => {
if n is Name(name) {
acc.push(name.value)
}
(acc, Continue)
})
debug_inspect(names, content="[\"a\", \"S\"]")
}test {
let rows = @traversal.kind_table()
let row = rows.filter(r => r.0 == "member" && r.1 == "member")[0]
debug_inspect(
row.2,
content="[\"docs\", \"traits\", \"name\", \"target\", \"default\"]",
)
// `boolean` is a shape kind and a value kind.
let booleans = rows.filter(r => r.1 == "boolean").map(r => r.0)
debug_inspect(booleans, content="[\"shape\", \"value\"]")
}test {
let r = @parser.parse_text(
(
#|namespace a
#|
#|structure Foo {
#| a: String
#|}
#|
),
)
let root = @traversal.NodeRef::of_result(r).unwrap()
let outline = @traversal.reduce(root, (n, rs) => {
if rs.length() == 0 {
n.kind()
} else {
n.kind() + "(" + rs.iter().join(",") + ")"
}
})
inspect(
outline,
content="file(namespace(name),structure(name,member(name,shape_id)))",
)
}test {
let r = @parser.parse_text(
(
#|namespace a
#|
#|@t
#|string S
#|
),
)
let root = @traversal.NodeRef::of_result(r).unwrap()
// Log enter and leave; skip the children of the trait.
let log = []
@traversal.walk(
root,
n => {
log.push("+" + n.category())
if n.category() == "trait" {
SkipChildren
} else {
Continue
}
},
leave=n => log.push("-" + n.category()),
)
inspect(
log.join(" "),
content="+file +namespace +name -name -namespace +shape +trait -trait +name -name -shape -file",
)
}Install
Download zipSmithy IDL syntax for MoonBit: lossless scanner, typed AST, CST, parser and diagnostics
Dependencies