///|
// Pratt parsing can create arbitrarily deep left-associated trees without
// increasing parser recursion. Walk iteratively before recursive validation.
fn check_ast_depth(root : Expr, limit : Int) -> Unit raise {
  let stack = [(root, 0)]
  while stack.pop() is Some((e, depth)) {
    if depth >= limit {
      raise problem(Limit, e.loc, "syntax tree depth limit exceeded")
    }
    let push = fn(child) { stack.push((child, depth + 1)) }
    let clauses = fn(cs : Array[Clause]) {
      for c in cs {
        match c {
          For(_, x) | When(x) => push(x)
        }
      }
    }
    match e.kind {
      Literal(_)
      | NumberLiteral(_)
      | Var(_)
      | ImportFile(_, _)
      | ImportBinary(_)
      | StandardFunction(_)
      | StandardValue(_) => ()
      Local(bs, body) => {
        for { value: v, .. } in bs {
          push(v)
        }
        push(body)
      }
      Func(ps, body) => {
        for { default_value: v, .. } in ps {
          if v is Some(x) {
            push(x)
          }
        }
        push(body)
      }
      If(a, b, c) => {
        push(a)
        push(b)
        push(c)
      }
      Call(f, args, _) => {
        push(f)
        for (_, v) in args {
          push(v)
        }
      }
      Arr(a) =>
        for x in a {
          push(x)
        }
      Obj(fs, bs, assertions, _) => {
        for f in fs {
          push(f.key)
          push(f.value)
        }
        for { value: v, .. } in bs {
          push(v)
        }
        for (c, m) in assertions {
          push(c)
          if m is Some(v) {
            push(v)
          }
        }
      }
      ArrayComp(body, cs) => {
        push(body)
        clauses(cs)
      }
      ObjectComp(key, body, cs, bs) => {
        for { value: v, .. } in bs {
          push(v)
        }
        push(key)
        push(body)
        clauses(cs)
      }
      Index(a, b) | Binary(_, a, b) => {
        push(a)
        push(b)
      }
      Slice(a, start, end, step) => {
        push(a)
        for x in [start, end, step] {
          if x is Some(v) {
            push(v)
          }
        }
      }
      Unary(_, a) | Fail(a) => push(a)
      Assert(a, m, b) => {
        push(a)
        push(b)
        if m is Some(v) {
          push(v)
        }
      }
    }
  }
}

///|
fn check_ast_nodes(root : Expr, limit : Int) -> Unit raise {
  let stack = [root]
  let mut nodes = 0
  while stack.pop() is Some(e) {
    if nodes >= limit {
      raise problem(Limit, e.loc, "syntax tree node limit exceeded")
    }
    nodes += 1
    stack.append(ast_export_children(e))
  }
}