///|
priv struct AstReader {
limits : Limits
mut nodes : Int
assertion_ranges : Map[String, SourceRange]
}
///|
fn ast_name(name : String, loc : Location) -> Unit raise JsonnetError {
if !is_name(name) ||
!name.to_array().all(c => identifier_start(c) || digit(c)) {
raise problem(Static, loc, "invalid identifier '\{name}'")
}
}
///|
fn check_ast_range(begin : Location, end : Location) -> Unit raise JsonnetError {
if end.file != begin.file ||
begin.line < 1 ||
begin.column < 1 ||
end.line < begin.line ||
end.column < 1 ||
(end.line == begin.line && end.column < begin.column) {
raise problem(Static, begin, "invalid AST source range")
}
}
///|
// Parser checks which must also hold for manually constructed syntax trees.
fn check_ast_shape(e : Ast) -> Unit raise JsonnetError {
if e.end is Some(end) {
check_ast_range(e.loc, end)
}
let name = fn(n) raise JsonnetError { ast_name(n, e.loc) }
let bindings = fn(bs : Array[AstBinding]) raise JsonnetError {
for b in bs {
name(b.name)
if b.range is Some(r) {
check_ast_range(r.begin, r.end)
}
}
}
let clauses = fn(cs : Array[AstClause]) raise JsonnetError {
guard cs.get(0) is Some(For(_, _)) else {
raise problem(Static, e.loc, "comprehension must begin with for")
}
for c in cs {
if c is For(n, _) {
name(n)
}
}
}
match e.kind {
NumberLiteral(raw) => {
let chars = raw.to_array()
let valid = chars.get(0) is Some(c) &&
digit(c) &&
(try number_end(chars, 0, e.loc) == chars.length() catch {
_ => false
})
if !valid {
raise problem(Static, e.loc, "invalid AST numeric literal")
}
}
Literal(Array(_) | Object(_)) =>
raise problem(Static, e.loc, "AST literal must be scalar")
Var(n) => if n != "self" && n != "super" && n != "$" { name(n) }
Local(bs, _) => bindings(bs)
Func(ps, _) =>
for p in ps {
name(p.name)
if p.range is Some(r) {
check_ast_range(r.begin, r.end)
}
}
Call(_, args, _) => {
let mut named = false
for (n, _) in args {
if n is Some(n) {
name(n)
named = true
} else if named {
raise problem(
Static,
e.loc,
"positional argument after named argument",
)
}
}
}
Obj(fs, bs, _) => {
bindings(bs)
for f in fs {
if f.visibility < 1 || f.visibility > 3 {
raise problem(Static, f.key.loc, "invalid field visibility")
}
}
}
ArrayComp(_, cs) => clauses(cs)
ObjectComp(_, _, cs, bs) => {
clauses(cs)
bindings(bs)
}
Unary(op, _) =>
if op != "+" && op != "-" && op != "!" && op != "~" {
raise problem(Static, e.loc, "invalid unary operator '\{op}'")
}
Binary(op, _, _) =>
if precedence(op) == 0 {
raise problem(Static, e.loc, "invalid binary operator '\{op}'")
}
_ => ()
}
}