// The node model: a read-only view over the typed AST of a parse result.
// Every traversal in this package walks `children()` with an explicit
// stack, so no node depth overflows the native stack and no traversal
// spends fuel.
///|
/// 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.
///
/// ```mbt check
/// 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")
/// }
/// }
/// ```
pub(all) enum NodeRef {
/// The root: the module with its CST (the CST gives the end offset).
Module(@ast.Module, @cst.ModuleCst)
Definition(@ast.TargetedDefinition)
Statement(@ast.Statement)
Expr(@ast.Expr)
Pattern(@ast.Pattern)
TypeAst(@ast.TypeAst)
Constant(@ast.Constant)
ClauseGuard(@ast.ClauseGuard)
BitArraySize(@ast.BitArraySize)
Clause(@ast.Clause)
Arg(@ast.Arg)
RecordConstructor(@ast.RecordConstructor)
RecordConstructorArg(@ast.RecordConstructorArg)
UseAssignment(@ast.UseAssignment)
TailPattern(@ast.TailPattern)
ExprArg(@ast.CallArg[@ast.Expr])
PatternArg(@ast.CallArg[@ast.Pattern])
ConstantArg(@ast.CallArg[@ast.Constant])
ExprUpdateArg(@ast.RecordUpdateArg[@ast.Expr])
ConstantUpdateArg(@ast.RecordUpdateArg[@ast.Constant])
ExprRecord(@ast.RecordBeingUpdated[@ast.Expr])
ConstantRecord(@ast.RecordBeingUpdated[@ast.Constant])
ExprSegment(@ast.BitArraySegment[@ast.Expr])
PatternSegment(@ast.BitArraySegment[@ast.Pattern])
ConstantSegment(@ast.BitArraySegment[@ast.Constant])
ExprOption(@ast.BitArrayOption[@ast.Expr])
PatternOption(@ast.BitArrayOption[@ast.Pattern])
ConstantOption(@ast.BitArrayOption[@ast.Constant])
} derive(Eq, Debug)
///|
fn[T : Eq] same_value(x : T, y : T) -> Bool {
physical_equal(x, y) || x == y
}
///|
/// `a == b`, but fast for nodes taken from the same tree: nodes wrap the
/// AST values themselves, so the same node is usually the same object and
/// the structural comparison of whole subtrees is not needed.
fn same(a : NodeRef, b : NodeRef) -> Bool {
if physical_equal(a, b) {
return true
}
match (a, b) {
(Module(x, _), Module(y, _)) => same_value(x, y)
(Definition(x), Definition(y)) => same_value(x, y)
(Statement(x), Statement(y)) => same_value(x, y)
(Expr(x), Expr(y)) => same_value(x, y)
(Pattern(x), Pattern(y)) => same_value(x, y)
(TypeAst(x), TypeAst(y)) => same_value(x, y)
(Constant(x), Constant(y)) => same_value(x, y)
(ClauseGuard(x), ClauseGuard(y)) => same_value(x, y)
(BitArraySize(x), BitArraySize(y)) => same_value(x, y)
(Clause(x), Clause(y)) => same_value(x, y)
(Arg(x), Arg(y)) => same_value(x, y)
(RecordConstructor(x), RecordConstructor(y)) => same_value(x, y)
(RecordConstructorArg(x), RecordConstructorArg(y)) => same_value(x, y)
(UseAssignment(x), UseAssignment(y)) => same_value(x, y)
(TailPattern(x), TailPattern(y)) => same_value(x, y)
(ExprArg(x), ExprArg(y)) => same_value(x, y)
(PatternArg(x), PatternArg(y)) => same_value(x, y)
(ConstantArg(x), ConstantArg(y)) => same_value(x, y)
(ExprUpdateArg(x), ExprUpdateArg(y)) => same_value(x, y)
(ConstantUpdateArg(x), ConstantUpdateArg(y)) => same_value(x, y)
(ExprRecord(x), ExprRecord(y)) => same_value(x, y)
(ConstantRecord(x), ConstantRecord(y)) => same_value(x, y)
(ExprSegment(x), ExprSegment(y)) => same_value(x, y)
(PatternSegment(x), PatternSegment(y)) => same_value(x, y)
(ConstantSegment(x), ConstantSegment(y)) => same_value(x, y)
(ExprOption(x), ExprOption(y)) => same_value(x, y)
(PatternOption(x), PatternOption(y)) => same_value(x, y)
(ConstantOption(x), ConstantOption(y)) => same_value(x, y)
_ => false
}
}
///|
/// 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.
///
/// ```mbt check
/// 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")
/// }
/// ```
pub fn NodeRef::of_result(result : @parser.ParseResult) -> NodeRef {
Module(result.module_, result.cst)
}
///|
/// 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"`.
pub fn NodeRef::category(self : NodeRef) -> String {
match self {
Module(_, _) => "module"
Definition(_) => "definition"
Statement(_) => "statement"
Expr(_) => "expression"
Pattern(_) => "pattern"
TypeAst(_) => "type"
Constant(_) => "constant"
ClauseGuard(_) => "clause_guard"
BitArraySize(_) => "bit_array_size"
Clause(_) => "clause"
Arg(_) => "arg"
RecordConstructor(_) => "record_constructor"
RecordConstructorArg(_) => "record_constructor_arg"
UseAssignment(_) => "use_assignment"
TailPattern(_) => "tail_pattern"
ExprArg(_) | PatternArg(_) | ConstantArg(_) => "call_arg"
ExprUpdateArg(_) | ConstantUpdateArg(_) => "record_update_arg"
ExprRecord(_) | ConstantRecord(_) => "record_being_updated"
ExprSegment(_) | PatternSegment(_) | ConstantSegment(_) =>
"bit_array_segment"
ExprOption(_) | PatternOption(_) | ConstantOption(_) => "bit_array_option"
}
}
///|
/// 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.
///
/// ```mbt check
/// 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")
/// }
/// ```
pub fn NodeRef::kind(self : NodeRef) -> String {
match self {
Definition(t) => definition_kind(t.definition)
Statement(s) => statement_kind(s)
Expr(e) => expr_kind(e)
Pattern(p) => pattern_kind(p)
TypeAst(t) => type_kind(t)
Constant(c) => constant_kind(c)
ClauseGuard(g) => guard_kind(g)
BitArraySize(z) => size_kind(z)
ExprOption(o) => option_kind(o)
PatternOption(o) => option_kind(o)
ConstantOption(o) => option_kind(o)
other => other.category()
}
}
///|
fn definition_kind(d : @ast.Definition) -> String {
match d {
Function(_) => "function"
TypeAlias(_) => "type_alias"
CustomType(_) => "custom_type"
Import(_) => "import"
ModuleConstant(_) => "module_constant"
}
}
///|
fn statement_kind(s : @ast.Statement) -> String {
match s {
Expression(_) => "expression"
Assignment(_) => "assignment"
Use(_) => "use"
Assert(_) => "assert"
}
}
///|
fn expr_kind(e : @ast.Expr) -> String {
match e {
Int(..) => "int"
Float(..) => "float"
String(..) => "string"
Block(..) => "block"
Var(..) => "var"
Fn(..) => "fn"
List(..) => "list"
Call(..) => "call"
BinOp(..) => "bin_op"
Pipeline(..) => "pipeline"
Case(..) => "case"
FieldAccess(..) => "field_access"
Tuple(..) => "tuple"
TupleIndex(..) => "tuple_index"
Todo(..) => "todo"
Panic(..) => "panic"
Echo(..) => "echo"
BitArray(..) => "bit_array"
RecordUpdate(..) => "record_update"
NegateBool(..) => "negate_bool"
NegateInt(..) => "negate_int"
}
}
///|
fn pattern_kind(p : @ast.Pattern) -> String {
match p {
Int(..) => "int"
Float(..) => "float"
String(..) => "string"
Variable(..) => "variable"
BitArraySize(_) => "bit_array_size"
Assign(..) => "assign"
Discard(..) => "discard"
List(..) => "list"
Constructor(..) => "constructor"
Tuple(..) => "tuple"
BitArray(..) => "bit_array"
StringPrefix(..) => "string_prefix"
}
}
///|
fn type_kind(t : @ast.TypeAst) -> String {
match t {
Constructor(_) => "constructor"
Fn(_) => "fn"
Var(_) => "var"
Tuple(_) => "tuple"
Hole(_) => "hole"
}
}
///|
fn constant_kind(c : @ast.Constant) -> String {
match c {
Int(..) => "int"
Float(..) => "float"
String(..) => "string"
Tuple(..) => "tuple"
List(..) => "list"
Record(..) => "record"
RecordUpdate(..) => "record_update"
BitArray(..) => "bit_array"
Var(..) => "var"
BinaryOperator(..) => "binary_operator"
Todo(..) => "todo"
}
}
///|
fn guard_kind(g : @ast.ClauseGuard) -> String {
match g {
Block(..) => "block"
BinaryOperator(..) => "binary_operator"
Not(..) => "not"
Var(..) => "var"
TupleIndex(..) => "tuple_index"
FieldAccess(..) => "field_access"
Constant(_) => "constant"
}
}
///|
fn size_kind(z : @ast.BitArraySize) -> String {
match z {
Int(..) => "int"
Variable(..) => "variable"
BinaryOperator(..) => "binary_operator"
Block(..) => "block"
}
}
///|
fn[V] option_kind(o : @ast.BitArrayOption[V]) -> String {
match o {
Bytes(..) => "bytes"
Int(..) => "int"
Float(..) => "float"
Bits(..) => "bits"
Utf8(..) => "utf8"
Utf16(..) => "utf16"
Utf32(..) => "utf32"
Utf8Codepoint(..) => "utf8_codepoint"
Utf16Codepoint(..) => "utf16_codepoint"
Utf32Codepoint(..) => "utf32_codepoint"
Signed(..) => "signed"
Unsigned(..) => "unsigned"
Big(..) => "big"
Little(..) => "little"
Native(..) => "native"
Size(..) => "size"
Unit(..) => "unit"
}
}
///|
/// A guard's span. The compiler's `TupleIndex` guard location covers
/// `.index` only, and a binary operator built on it starts there too, so
/// the span starts at the leftmost node of the guard's left spine (a loop,
/// not a recursion).
fn guard_span(g : @ast.ClauseGuard) -> @span.SrcSpan {
let own = g.location()
let mut start = own.start
let mut cur = g
while true {
let s = cur.location().start
if s < start {
start = s
}
match cur {
BinaryOperator(left~, ..) => cur = left
TupleIndex(tuple~, ..) => cur = tuple
FieldAccess(container~, ..) => cur = container
Block(..) | Not(..) | Var(..) | Constant(_) => break
}
}
@span.SrcSpan::new(start, own.end)
}
///|
fn later(a : Int32, b : Int32) -> Int32 {
if a > b {
a
} else {
b
}
}
///|
fn[V] option_span(o : @ast.BitArrayOption[V]) -> @span.SrcSpan {
match o {
Bytes(location~)
| Int(location~)
| Float(location~)
| Bits(location~)
| Utf8(location~)
| Utf16(location~)
| Utf32(location~)
| Utf8Codepoint(location~)
| Utf16Codepoint(location~)
| Utf32Codepoint(location~)
| Signed(location~)
| Unsigned(location~)
| Big(location~)
| Little(location~)
| Native(location~)
| Size(location~, ..)
| Unit(location~, ..) => location
}
}
///|
/// 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.
///
/// ```mbt check
/// 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 }")
/// }
/// ```
pub fn NodeRef::span(self : NodeRef) -> @span.SrcSpan {
match self {
Module(_, cst) =>
match cst.tokens {
[] => @span.SrcSpan::new(0, 0)
[.., last] => @span.SrcSpan::new(0, last.span.end)
}
Definition(t) =>
match t.definition {
Function(f) => {
// An external function has no body: `end_position` ends the head,
// and the return annotation may lie after it.
let mut end = if f.end_position > f.location.end {
f.end_position
} else {
f.location.end
}
if f.return_annotation is Some(r) {
end = later(end, r.location().end)
}
for a in f.arguments {
end = later(end, a.location.end)
}
@span.SrcSpan::new(f.location.start, end)
}
CustomType(c) => @span.SrcSpan::new(c.location.start, c.end_position)
TypeAlias(a) =>
@span.SrcSpan::new(
a.location.start,
later(a.location.end, a.type_ast.location().end),
)
ModuleConstant(c) => {
// The compiler's location covers `const name` only.
let mut end = later(c.location.end, c.value.location().end)
if c.annotation is Some(t) {
end = later(end, t.location().end)
}
@span.SrcSpan::new(c.location.start, end)
}
Import(i) => i.location
}
Statement(s) => s.location()
Expr(e) => e.location()
Pattern(p) => p.location()
TypeAst(t) => t.location()
Constant(c) => c.location()
ClauseGuard(g) => guard_span(g)
BitArraySize(z) => z.location()
Clause(c) => c.location
Arg(a) => a.location
RecordConstructor(r) => r.location
RecordConstructorArg(a) => a.location
UseAssignment(u) => u.location
TailPattern(t) => t.location
ExprArg(a) => a.location
PatternArg(a) => a.location
ConstantArg(a) => a.location
ExprUpdateArg(a) => a.location
ConstantUpdateArg(a) => a.location
ExprRecord(r) => r.location
ConstantRecord(r) => r.location
ExprSegment(s) => s.location
PatternSegment(s) => s.location
ConstantSegment(s) => s.location
ExprOption(o) => option_span(o)
PatternOption(o) => option_span(o)
ConstantOption(o) => option_span(o)
}
}