///|
fn binding_scope(
bindings : Array[ExprBinding],
names : Array[String],
loc : Location,
) -> Array[String] raise Error {
let result = names.copy()
let seen : Map[String, Bool] = Map([])
for { name: n, .. } in bindings {
if seen.contains(n) {
raise problem(Static, loc, "duplicate binding '\{n}'")
}
seen[n] = true
result.push(n)
}
result
}
///|
fn validate(
e : Expr,
names : Array[String],
in_object : Bool,
) -> Unit raise Error {
match e.kind {
Literal(_)
| NumberLiteral(_)
| ImportFile(_, _)
| ImportBinary(_)
| StandardFunction(_)
| StandardValue(_) => ()
Var(n) => {
if n == "super" {
raise problem(
Static,
e.loc,
"super requires field access or the in operator",
)
}
if n == "self" && in_object {
return
}
if !names.contains(n) {
raise problem(Static, e.loc, "Unknown variable: \{n}")
}
}
Local(bs, body) => {
let scope = binding_scope(bs, names, e.loc)
for { value: v, .. } in bs {
validate(v, scope, in_object)
}
validate(body, scope, in_object)
}
Func(ps, body) => {
let scope = names.copy()
for { name: n, .. } in ps {
scope.push(n)
}
for { default_value: d, .. } in ps {
if d is Some(v) {
validate(v, scope, in_object)
}
}
validate(body, scope, in_object)
}
Call(f, args, _) => {
validate(f, names, in_object)
let seen : Map[String, Bool] = Map([])
for (n, a) in args {
if n is Some(name) {
if seen.contains(name) {
raise problem(Static, a.loc, "duplicate argument '\{name}'")
}
seen[name] = true
}
validate(a, names, in_object)
}
}
If(a, b, c) => {
validate(a, names, in_object)
validate(b, names, in_object)
validate(c, names, in_object)
}
Arr(xs) =>
for x in xs {
validate(x, names, in_object)
}
Obj(fs, bs, assertions, _) => {
let outer = names.copy()
outer.push("$")
let scope = binding_scope(bs, outer, e.loc)
for { value: v, .. } in bs {
validate(v, scope, true)
}
let keys : Map[String, Bool] = Map([])
for f in fs {
validate(f.key, names, in_object)
if f.key.kind is Literal(String(k)) {
if keys.contains(k) {
raise problem(Static, f.key.loc, "duplicate field '\{k}'")
}
keys[k] = true
}
validate(f.value, scope, true)
}
for (a, b) in assertions {
validate(a, scope, true)
if b is Some(v) {
validate(v, scope, true)
}
}
}
ArrayComp(body, cs) => {
let scope = validate_clauses(cs, names, in_object)
validate(body, scope, in_object)
}
ObjectComp(key, body, cs, bs) => {
let scope = validate_clauses(cs, names, in_object)
validate(key, scope, in_object)
scope.push("$")
let inner = binding_scope(bs, scope, e.loc)
for { value: v, .. } in bs {
validate(v, inner, true)
}
validate(body, inner, true)
}
Index({ kind: Var("super"), .. }, b)
| Binary("in", b, { kind: Var("super"), .. }) => {
if !in_object {
raise problem(Static, e.loc, "super outside object")
}
validate(b, names, in_object)
}
Index(a, b) | Binary(_, a, b) => {
validate(a, names, in_object)
validate(b, names, in_object)
}
Slice(a, start, end, step) => {
validate(a, names, in_object)
for x in [start, end, step] {
if x is Some(v) {
validate(v, names, in_object)
}
}
}
Unary(_, a) | Fail(a) => validate(a, names, in_object)
Assert(a, b, c) => {
validate(a, names, in_object)
if b is Some(v) {
validate(v, names, in_object)
}
validate(c, names, in_object)
}
}
}
///|
fn validate_clauses(
cs : Array[Clause],
names : Array[String],
in_object : Bool,
) -> Array[String] raise Error {
let scope = names.copy()
for c in cs {
match c {
For(n, e) => {
validate(e, scope, in_object)
scope.push(n)
}
When(e) => validate(e, scope, in_object)
}
}
scope
}