// fold.cm — AST optimization pass
// Constant folding, constant propagation, dead code elimination

var fold = function(ast) {
  var scopes = ast.scopes
  var nr_scopes = length(scopes)

  var type_tag_map = {
    array: "array", record: "record", text: "text",
    number: "number", blob: "blob"
  }

  var binary_ops = {
    "+": true, "-": true, "*": true, "/": true, "%": true,
    "**": true, "==": true, "!=": true, "<": true, ">": true,
    "<=": true, ">=": true, "&": true, "|": true, "^": true,
    "<<": true, ">>": true, ">>>": true, "&&": true, "||": true,
    ",": true, in: true
  }
  var unary_ops = {
    "!": true, "~": true, "-unary": true, "+unary": true, delete: true
  }
  var assign_ops = {
    assign: true, "+=": true, "-=": true, "*=": true,
    "/=": true, "%=": true, "<<=": true, ">>=": true,
    ">>>=": true, "&=": true, "^=": true, "|=": true,
    "**=": true, "&&=": true, "||=": true
  }
  var arith_ops = {
    "+": true, "-": true, "*": true, "/": true, "%": true, "**": true
  }
  var comparison_ops = {
    "==": true, "!=": true, "<": true, ">": true, "<=": true, ">=": true
  }

  // ============================================================
  // Helpers
  // ============================================================

  var is_literal = function(expr) {
    if (expr == null) return false
    var k = expr.kind
    return k == "number" || k == "text" || k == "true" || k == "false" || k == "null"
  }

  // Only intrinsics that can NEVER disrupt regardless of argument types
  var pure_intrinsics = {
    is_array: true, is_text: true, is_number: true, is_integer: true,
    is_function: true, is_logical: true, is_null: true, is_object: true,
    is_stone: true
  }

  var is_pure = function(expr) {
    if (expr == null) return true
    var k = expr.kind
    var i = 0
    var target = null
    if (k == "number" || k == "text" || k == "true" || k == "false" ||
        k == "null" || k == "name" || k == "this") return true
    if (k == "function") return true
    if (k == "!" || k == "~" || k == "-unary" || k == "+unary") {
      return is_pure(expr.expression)
    }
    if (k == "array") {
      i = 0
      while (i < length(expr.list)) {
        if (!is_pure(expr.list[i])) return false
        i = i + 1
      }
      return true
    }
    if (k == "record") {
      i = 0
      while (i < length(expr.list)) {
        if (!is_pure(expr.list[i].right)) return false
        i = i + 1
      }
      return true
    }
    if (k == "then") {
      return is_pure(expr.expression) && is_pure(expr.then) && is_pure(expr.else)
    }
    if (k == "==" || k == "!=" || k == "&&" || k == "||") {
      return is_pure(expr.left) && is_pure(expr.right)
    }
    if (k == "(") {
      target = expr.expression
      if (target != null && target.intrinsic == true && pure_intrinsics[target.name] == true) {
        i = 0
        while (i < length(expr.list)) {
          if (!is_pure(expr.list[i])) return false
          i = i + 1
        }
        return true
      }
    }
    return false
  }

  var copy_loc = function(from, to) {
    to.at = from.at
    to.from_row = from.from_row
    to.from_column = from.from_column
    to.to_row = from.to_row
    to.to_column = from.to_column
    return to
  }

  var make_number = function(val, src) {
    return copy_loc(src, {kind: "number", value: text(val), number: val})
  }

  var make_text = function(val, src) {
    return copy_loc(src, {kind: "text", value: val})
  }

  var make_bool = function(val, src) {
    if (val) return copy_loc(src, {kind: "true"})
    return copy_loc(src, {kind: "false"})
  }

  var make_null = function(src) {
    return copy_loc(src, {kind: "null"})
  }

  var is_truthy_literal = function(expr) {
    if (expr == null) return null
    var k = expr.kind
    var nv = null
    if (k == "true") return true
    if (k == "false" || k == "null") return false
    if (k == "number") {
      nv = expr.number
      if (nv == null) nv = number(expr.value)
      return nv != 0
    }
    if (k == "text") return length(expr.value) > 0
    return null
  }

  // ============================================================
  // Scope helpers
  // ============================================================

  var find_scope = function(fn_nr) {
    var i = 0
    while (i < nr_scopes) {
      if (scopes[i].function_nr == fn_nr) return scopes[i]
      i = i + 1
    }
    return null
  }

  var scope_var = function(fn_nr, name) {
    var sc = find_scope(fn_nr)
    if (sc == null) return null
    return sc[name]
  }

  var remove_scope_var = function(fn_nr, name) {
    var sc = find_scope(fn_nr)
    if (sc == null) return null
    delete sc[name]
  }

  // ============================================================
  // Pass 1: pre-scan for constants and function arities
  // ============================================================

  var const_defs = {}
  var fn_arities = {}

  var register_const = function(fn_nr, name, lit_node) {
    var key = text(fn_nr)
    if (const_defs[key] == null) const_defs[key] = {}
    const_defs[key][name] = lit_node
  }

  var get_const = function(fn_nr, name) {
    var key = text(fn_nr)
    if (const_defs[key] == null) return null
    return const_defs[key][name]
  }

  var register_arity = function(fn_nr, name, count) {
    var key = text(fn_nr)
    if (fn_arities[key] == null) fn_arities[key] = {}
    fn_arities[key][name] = count
  }

  var pre_scan_stmts = null
  var pre_scan_fn = null

  pre_scan_fn = function(node) {
    if (node == null) return null
    if (node.statements != null) pre_scan_stmts(node.statements, node.function_nr)
    if (node.disruption != null) pre_scan_stmts(node.disruption, node.function_nr)
  }

  pre_scan_stmts = function(stmts, fn_nr) {
    var i = 0
    var j = 0
    var stmt = null
    var kind = null
    var name = null
    var sv = null
    var item = null
    var rhs_target = null
    while (i < length(stmts)) {
      stmt = stmts[i]
      kind = stmt.kind
      if (kind == "def") {
        name = stmt.left.name
        if (name != null && is_literal(stmt.right)) {
          sv = scope_var(fn_nr, name)
          if (sv != null && !sv.closure) {
            register_const(fn_nr, name, stmt.right)
          }
        }
        if (name != null && stmt.right != null && stmt.right.kind == "(") {
          rhs_target = stmt.right.expression
          if (rhs_target != null && rhs_target.intrinsic == true) {
            sv = scope_var(fn_nr, name)
            if (sv != null && sv.type_tag == null) {
              if (type_tag_map[rhs_target.name] != null) sv.type_tag = type_tag_map[rhs_target.name]
            }
          }
        }
      } else if (kind == "function") {
        name = stmt.name
        if (name != null && stmt.arity != null) {
          register_arity(fn_nr, name, stmt.arity)
        }
        pre_scan_fn(stmt)
      } else if (kind == "var") {
        if (stmt.right != null && stmt.right.kind == "function" && stmt.right.arity != null) {
          name = stmt.left.name
          if (name != null) {
            sv = scope_var(fn_nr, name)
            if (sv != null && sv.make == "var") {
              register_arity(fn_nr, name, stmt.right.arity)
            }
          }
        }
      } else if (kind == "var_list") {
        j = 0
        while (j < length(stmt.list)) {
          item = stmt.list[j]
          if (item.kind == "var" && item.right != null && item.right.kind == "function" && item.right.arity != null) {
            name = item.left.name
            if (name != null) {
              sv = scope_var(fn_nr, name)
              if (sv != null && sv.make == "var") {
                register_arity(fn_nr, name, item.right.arity)
              }
            }
          }
          j = j + 1
        }
      }
      i = i + 1
    }
  }

  var pre_scan_expr_fns = null
  pre_scan_expr_fns = function(expr) {
    if (expr == null) return null
    var k = expr.kind
    var i = 0
    if (k == "function") {
      pre_scan_fn(expr)
    }
    if (expr.left != null) pre_scan_expr_fns(expr.left)
    if (expr.right != null) pre_scan_expr_fns(expr.right)
    if (expr.expression != null) pre_scan_expr_fns(expr.expression)
    if (expr.then != null) pre_scan_expr_fns(expr.then)
    if (expr.else != null) pre_scan_expr_fns(expr.else)
    if (k == "(" || k == "array") {
      i = 0
      while (i < length(expr.list)) {
        pre_scan_expr_fns(expr.list[i])
        i = i + 1
      }
    }
    if (k == "record") {
      i = 0
      while (i < length(expr.list)) {
        pre_scan_expr_fns(expr.list[i].right)
        i = i + 1
      }
    }
  }

  var pre_scan_stmt_exprs = null
  pre_scan_stmt_exprs = function(stmts, fn_nr) {
    var i = 0
    var j = 0
    var stmt = null
    var kind = null
    while (i < length(stmts)) {
      stmt = stmts[i]
      kind = stmt.kind
      if (kind == "var" || kind == "def") {
        pre_scan_expr_fns(stmt.right)
      } else if (kind == "var_list") {
        j = 0
        while (j < length(stmt.list)) {
          pre_scan_expr_fns(stmt.list[j].right)
          j = j + 1
        }
      } else if (kind == "call") {
        pre_scan_expr_fns(stmt.expression)
      } else if (kind == "if") {
        pre_scan_expr_fns(stmt.expression)
        pre_scan_stmt_exprs(stmt.then, fn_nr)
        pre_scan_stmt_exprs(stmt.list, fn_nr)
        if (stmt.else != null) pre_scan_stmt_exprs(stmt.else, fn_nr)
      } else if (kind == "while" || kind == "do") {
        pre_scan_expr_fns(stmt.expression)
        pre_scan_stmt_exprs(stmt.statements, fn_nr)
      } else if (kind == "for") {
        if (stmt.init != null) {
          if (stmt.init.kind == "var" || stmt.init.kind == "def") {
            pre_scan_expr_fns(stmt.init.right)
          } else {
            pre_scan_expr_fns(stmt.init)
          }
        }
        pre_scan_expr_fns(stmt.test)
        pre_scan_expr_fns(stmt.update)
        pre_scan_stmt_exprs(stmt.statements, fn_nr)
      } else if (kind == "return" || kind == "go") {
        pre_scan_expr_fns(stmt.expression)
      } else if (kind == "block") {
        pre_scan_stmt_exprs(stmt.statements, fn_nr)
      } else if (kind == "label") {
        if (stmt.statement != null) {
          pre_scan_stmt_exprs([stmt.statement], fn_nr)
        }
      } else if (kind == "function") {
        // already handled in pre_scan_stmts
        null
      }
      i = i + 1
    }
  }

  var pre_scan = function() {
    pre_scan_stmts(ast.statements, 0)
    pre_scan_stmts(ast.functions, 0)
    pre_scan_stmt_exprs(ast.statements, 0)
    pre_scan_stmt_exprs(ast.functions, 0)
  }

  // ============================================================
  // Pass 2: fold expressions and statements
  // ============================================================

  var fold_expr = null
  var fold_stmt = null
  var fold_stmts = null

  fold_expr = function(expr, fn_nr) {
    if (expr == null) return null
    var k = expr.kind
    var left = null
    var right = null
    var lv = null
    var rv = null
    var result = null
    var i = 0
    var sv = null
    var lit = null
    var cond_k = null
    var ek = null
    var target = null
    var ar = null
    var akey = null
    var tv = null
    var att = null
    var arg = null

    // Recurse into children first (bottom-up)
    if (binary_ops[k] == true) {
      expr.left = fold_expr(expr.left, fn_nr)
      expr.right = fold_expr(expr.right, fn_nr)
    } else if (k == "." || k == "[") {
      expr.left = fold_expr(expr.left, fn_nr)
      if (k == "[" && expr.right != null) expr.right = fold_expr(expr.right, fn_nr)
    } else if (unary_ops[k] == true) {
      expr.expression = fold_expr(expr.expression, fn_nr)
    } else if (k == "++" || k == "--") {
      return expr
    } else if (k == "then") {
      expr.expression = fold_expr(expr.expression, fn_nr)
      expr.then = fold_expr(expr.then, fn_nr)
      expr.else = fold_expr(expr.else, fn_nr)
    } else if (k == "(") {
      expr.expression = fold_expr(expr.expression, fn_nr)
      i = 0
      while (i < length(expr.list)) {
        expr.list[i] = fold_expr(expr.list[i], fn_nr)
        i = i + 1
      }
    } else if (k == "array" || k == "text literal") {
      i = 0
      while (i < length(expr.list)) {
        expr.list[i] = fold_expr(expr.list[i], fn_nr)
        i = i + 1
      }
    } else if (k == "record") {
      i = 0
      while (i < length(expr.list)) {
        expr.list[i].right = fold_expr(expr.list[i].right, fn_nr)
        i = i + 1
      }
    } else if (k == "function") {
      fold_fn(expr)
      return expr
    } else if (assign_ops[k] == true) {
      expr.right = fold_expr(expr.right, fn_nr)
      return expr
    }

    // Constant propagation: name → literal
    if (k == "name" && expr.level == 0) {
      lit = get_const(fn_nr, expr.name)
      if (lit != null) {
        sv = scope_var(fn_nr, expr.name)
        if (sv != null && !sv.closure) {
          return copy_loc(expr, {kind: lit.kind, value: lit.value, number: lit.number})
        }
      }
      sv = scope_var(fn_nr, expr.name)
      if (sv != null && sv.type_tag != null) {
        expr.type_tag = sv.type_tag
      }
      return expr
    }

    // Binary constant folding
    if (arith_ops[k] == true) {
      left = expr.left
      right = expr.right
      if (left != null && right != null && left.kind == "number" && right.kind == "number") {
        lv = left.number
        rv = right.number
        if (lv == null) lv = number(left.value)
        if (rv == null) rv = number(right.value)
        if (k == "/") {
          if (rv == 0) return make_null(expr)
        }
        if (k == "%") {
          if (rv == 0) return make_null(expr)
        }
        result = null
        if (k == "+") result = lv + rv
        else if (k == "-") result = lv - rv
        else if (k == "*") result = lv * rv
        else if (k == "/") result = lv / rv
        else if (k == "%") result = lv % rv
        else if (k == "**") result = lv ** rv
        if (result == null) return make_null(expr)
        return make_number(result, expr)
      }
      // text + text
      if (k == "+" && left != null && right != null && left.kind == "text" && right.kind == "text") {
        return make_text(left.value + right.value, expr)
      }
      return expr
    }

    // Comparison folding
    if (comparison_ops[k] == true) {
      left = expr.left
      right = expr.right
      if (left != null && right != null) {
        if (left.kind == "number" && right.kind == "number") {
          lv = left.number
          rv = right.number
          if (lv == null) lv = number(left.value)
          if (rv == null) rv = number(right.value)
          if (k == "==") return make_bool(lv == rv, expr)
          if (k == "!=") return make_bool(lv != rv, expr)
          if (k == "<") return make_bool(lv < rv, expr)
          if (k == ">") return make_bool(lv > rv, expr)
          if (k == "<=") return make_bool(lv <= rv, expr)
          if (k == ">=") return make_bool(lv >= rv, expr)
        }
        if (left.kind == "text" && right.kind == "text") {
          if (k == "==") return make_bool(left.value == right.value, expr)
          if (k == "!=") return make_bool(left.value != right.value, expr)
        }
      }
      return expr
    }

    // Bitwise folding
    if (k == "&" || k == "|" || k == "^" || k == "<<" || k == ">>") {
      left = expr.left
      right = expr.right
      if (left != null && right != null && left.kind == "number" && right.kind == "number") {
        lv = left.number
        rv = right.number
        if (lv == null) lv = number(left.value)
        if (rv == null) rv = number(right.value)
        if (k == "&") return make_number(lv & rv, expr)
        if (k == "|") return make_number(lv | rv, expr)
        if (k == "^") return make_number(lv ^ rv, expr)
        if (k == "<<") return make_number(lv << rv, expr)
        if (k == ">>") return make_number(lv >> rv, expr)
      }
      return expr
    }

    // Unary folding
    if (k == "!") {
      if (expr.expression != null) {
        ek = expr.expression.kind
        if (ek == "true") return make_bool(false, expr)
        if (ek == "false") return make_bool(true, expr)
      }
      return expr
    }
    if (k == "~") {
      if (expr.expression != null && expr.expression.kind == "number") {
        lv = expr.expression.number
        if (lv == null) lv = number(expr.expression.value)
        return make_number(~lv, expr)
      }
      return expr
    }
    if (k == "-unary") {
      if (expr.expression != null && expr.expression.kind == "number") {
        lv = expr.expression.number
        if (lv == null) lv = number(expr.expression.value)
        return make_number(0 - lv, expr)
      }
      return expr
    }

    // Ternary with literal condition
    if (k == "then") {
      tv = is_truthy_literal(expr.expression)
      if (tv == true) return expr.then
      if (tv == false) return expr.else
      return expr
    }

    // Call: stamp arity and fold intrinsic type checks
    if (k == "(") {
      target = expr.expression
      if (target != null && target.kind == "name" && target.level == 0) {
        ar = null
        akey = text(fn_nr)
        if (fn_arities[akey] != null) ar = fn_arities[akey][target.name]
        if (ar != null) expr.arity = ar
      }
      if (target != null && target.intrinsic == true && length(expr.list) == 1) {
        arg = expr.list[0]
        att = null
        if (arg.type_tag != null) {
          att = arg.type_tag
        } else if (arg.kind == "name" && arg.level == 0) {
          sv = scope_var(fn_nr, arg.name)
          if (sv != null) att = sv.type_tag
        }
        if (att != null) {
          if (target.name == "is_array") return make_bool(att == "array", expr)
          if (target.name == "is_text") return make_bool(att == "text", expr)
          if (target.name == "is_number") return make_bool(att == "number" || att == "integer", expr)
          if (target.name == "is_integer") return make_bool(att == "integer", expr)
          if (target.name == "is_function") return make_bool(att == "function", expr)
          if (target.name == "is_logical") return make_bool(att == "logical", expr)
          if (target.name == "is_null") return make_bool(att == "null", expr)
          if (target.name == "is_object") return make_bool(att == "record", expr)
          if (target.name == "length") {
            if (att == "array") expr.hint = "array_length"
            else if (att == "text") expr.hint = "text_length"
          }
        }
      }
      return expr
    }

    return expr
  }

  var fold_fn = null

  fold_stmt = function(stmt, fn_nr) {
    if (stmt == null) return null
    var k = stmt.kind
    var i = 0
    var sv = null
    var cond_k = null
    var ik = null
    var tv = null

    if (k == "var" || k == "def") {
      stmt.right = fold_expr(stmt.right, fn_nr)
      if (is_pure(stmt.right)) stmt.pure = true
      return stmt
    }
    if (k == "var_list") {
      i = 0
      while (i < length(stmt.list)) {
        stmt.list[i] = fold_stmt(stmt.list[i], fn_nr)
        i = i + 1
      }
      return stmt
    }
    if (k == "call") {
      stmt.expression = fold_expr(stmt.expression, fn_nr)
      return stmt
    }
    if (k == "if") {
      stmt.expression = fold_expr(stmt.expression, fn_nr)
      tv = is_truthy_literal(stmt.expression)
      if (tv == true) {
        stmt.then = fold_stmts(stmt.then, fn_nr)
        return {kind: "block", statements: stmt.then,
          at: stmt.at, from_row: stmt.from_row, from_column: stmt.from_column,
          to_row: stmt.to_row, to_column: stmt.to_column}
      }
      if (tv == false) {
        if (stmt.else != null && length(stmt.else) > 0) {
          stmt.else = fold_stmts(stmt.else, fn_nr)
          return {kind: "block", statements: stmt.else,
            at: stmt.at, from_row: stmt.from_row, from_column: stmt.from_column,
            to_row: stmt.to_row, to_column: stmt.to_column}
        }
        if (stmt.list != null && length(stmt.list) > 0) {
          return fold_stmt(stmt.list[0], fn_nr)
        }
        return null
      }
      stmt.then = fold_stmts(stmt.then, fn_nr)
      stmt.list = fold_stmts(stmt.list, fn_nr)
      if (stmt.else != null) stmt.else = fold_stmts(stmt.else, fn_nr)
      return stmt
    }
    if (k == "while") {
      stmt.expression = fold_expr(stmt.expression, fn_nr)
      if (stmt.expression.kind == "false" || stmt.expression.kind == "null") return null
      stmt.statements = fold_stmts(stmt.statements, fn_nr)
      return stmt
    }
    if (k == "do") {
      stmt.statements = fold_stmts(stmt.statements, fn_nr)
      stmt.expression = fold_expr(stmt.expression, fn_nr)
      return stmt
    }
    if (k == "for") {
      if (stmt.init != null) {
        ik = stmt.init.kind
        if (ik == "var" || ik == "def") {
          stmt.init = fold_stmt(stmt.init, fn_nr)
        } else {
          stmt.init = fold_expr(stmt.init, fn_nr)
        }
      }
      if (stmt.test != null) stmt.test = fold_expr(stmt.test, fn_nr)
      if (stmt.update != null) stmt.update = fold_expr(stmt.update, fn_nr)
      stmt.statements = fold_stmts(stmt.statements, fn_nr)
      return stmt
    }
    if (k == "return" || k == "go") {
      stmt.expression = fold_expr(stmt.expression, fn_nr)
      return stmt
    }
    if (k == "block") {
      stmt.statements = fold_stmts(stmt.statements, fn_nr)
      return stmt
    }
    if (k == "label") {
      stmt.statement = fold_stmt(stmt.statement, fn_nr)
      return stmt
    }
    if (k == "function") {
      fold_fn(stmt)
      return stmt
    }
    return stmt
  }

  fold_stmts = function(stmts, fn_nr) {
    var i = 0
    var stmt = null
    var out = []
    var sv = null
    var name = null
    while (i < length(stmts)) {
      stmt = fold_stmt(stmts[i], fn_nr)
      if (stmt == null) {
        i = i + 1
        continue
      }
      // Dead code elimination: unused pure var/def
      if (stmt.kind == "var" || stmt.kind == "def") {
        name = stmt.left.name
        if (name != null) {
          sv = scope_var(fn_nr, name)
          if (sv != null && sv.nr_uses == 0 && is_pure(stmt.right)) {
            stmt.dead = true
          }
        }
      }
      // Dead pure call elimination: standalone pure calls with no result
      if (stmt.kind == "call" && is_pure(stmt.expression)) {
        stmt.dead = true
      }
      // Dead function elimination
      if (stmt.kind == "function" && stmt.name != null) {
        sv = scope_var(fn_nr, stmt.name)
        if (sv != null && sv.nr_uses == 0) {
          stmt.dead = true
        }
      }
      if (stmt.dead != true) push(out, stmt)
      i = i + 1
    }
    return out
  }

  fold_fn = function(node) {
    if (node == null) return null
    var fn_nr = node.function_nr
    if (fn_nr == null) return null
    // Fold param defaults
    var i = 0
    while (i < length(node.list)) {
      if (node.list[i].expression != null) {
        node.list[i].expression = fold_expr(node.list[i].expression, fn_nr)
      }
      i = i + 1
    }
    if (node.statements != null) node.statements = fold_stmts(node.statements, fn_nr)
    if (node.disruption != null) node.disruption = fold_stmts(node.disruption, fn_nr)
  }

  // ============================================================
  // Pass 3: cleanup scopes
  // ============================================================

  var cleanup = function() {
    var i = 0
    var sc = null
    var keys = null
    var j = 0
    var key = null
    var entry = null
    var slots = 0
    var close_slots = 0

    // Remove dead vars from scope records and recalculate slot counts
    while (i < nr_scopes) {
      sc = scopes[i]
      keys = array(sc)
      slots = 0
      close_slots = 0
      j = 0
      while (j < length(keys)) {
        key = keys[j]
        if (key != "function_nr") {
          entry = sc[key]
          if (entry != null && entry.nr_uses == 0 && entry.make != "input" && entry.make != "function") {
            delete sc[key]
          } else if (entry != null) {
            slots = slots + 1
            if (entry.closure) close_slots = close_slots + 1
          }
        }
        j = j + 1
      }
      i = i + 1
    }

    // Update nr_slots and nr_close_slots on function nodes
    var update_fn_slots = null
    update_fn_slots = function(node) {
      if (node == null) return null
      var fn_nr = node.function_nr
      if (fn_nr == null) return null
      var sc = find_scope(fn_nr)
      if (sc == null) return null
      var keys = array(sc)
      var s = 0
      var cs = 0
      var ki = 0
      var ent = null
      while (ki < length(keys)) {
        if (keys[ki] != "function_nr") {
          ent = sc[keys[ki]]
          if (ent != null) {
            s = s + 1
            if (ent.closure) cs = cs + 1
          }
        }
        ki = ki + 1
      }
      node.nr_slots = s
      node.nr_close_slots = cs
    }

    var walk_stmts_for_fns = null
    var walk_expr_for_fns = null

    walk_expr_for_fns = function(expr) {
      if (expr == null) return null
      var k = expr.kind
      var i = 0
      if (k == "function") {
        update_fn_slots(expr)
        walk_stmts_for_fns(expr.statements)
        walk_stmts_for_fns(expr.disruption)
        return null
      }
      if (expr.left != null) walk_expr_for_fns(expr.left)
      if (expr.right != null) walk_expr_for_fns(expr.right)
      if (expr.expression != null) walk_expr_for_fns(expr.expression)
      if (expr.then != null) walk_expr_for_fns(expr.then)
      if (expr.else != null) walk_expr_for_fns(expr.else)
      if (k == "(" || k == "array" || k == "text literal") {
        i = 0
        while (i < length(expr.list)) {
          walk_expr_for_fns(expr.list[i])
          i = i + 1
        }
      }
      if (k == "record") {
        i = 0
        while (i < length(expr.list)) {
          walk_expr_for_fns(expr.list[i].right)
          i = i + 1
        }
      }
    }

    walk_stmts_for_fns = function(stmts) {
      if (stmts == null) return null
      var i = 0
      var j = 0
      var stmt = null
      var k = null
      while (i < length(stmts)) {
        stmt = stmts[i]
        k = stmt.kind
        if (k == "function") {
          update_fn_slots(stmt)
          walk_stmts_for_fns(stmt.statements)
          walk_stmts_for_fns(stmt.disruption)
        } else if (k == "var" || k == "def") {
          walk_expr_for_fns(stmt.right)
        } else if (k == "var_list") {
          j = 0
          while (j < length(stmt.list)) {
            walk_expr_for_fns(stmt.list[j].right)
            j = j + 1
          }
        } else if (k == "call") {
          walk_expr_for_fns(stmt.expression)
        } else if (k == "if") {
          walk_expr_for_fns(stmt.expression)
          walk_stmts_for_fns(stmt.then)
          walk_stmts_for_fns(stmt.list)
          if (stmt.else != null) walk_stmts_for_fns(stmt.else)
        } else if (k == "while" || k == "do") {
          walk_expr_for_fns(stmt.expression)
          walk_stmts_for_fns(stmt.statements)
        } else if (k == "for") {
          if (stmt.init != null) {
            if (stmt.init.kind == "var" || stmt.init.kind == "def") {
              walk_expr_for_fns(stmt.init.right)
            } else {
              walk_expr_for_fns(stmt.init)
            }
          }
          walk_expr_for_fns(stmt.test)
          walk_expr_for_fns(stmt.update)
          walk_stmts_for_fns(stmt.statements)
        } else if (k == "return" || k == "go") {
          walk_expr_for_fns(stmt.expression)
        } else if (k == "block") {
          walk_stmts_for_fns(stmt.statements)
        } else if (k == "label") {
          if (stmt.statement != null) walk_stmts_for_fns([stmt.statement])
        }
        i = i + 1
      }
    }

    walk_stmts_for_fns(ast.statements)
    walk_stmts_for_fns(ast.functions)

    // Update intrinsics: collect what's still referenced
    var used_intrinsics = {}
    var collect_intrinsics = null
    var collect_expr_intrinsics = null

    collect_expr_intrinsics = function(expr) {
      if (expr == null) return null
      var k = expr.kind
      var i = 0
      if (k == "name" && expr.level == -1 && expr.name != null && expr.make != "functino") {
        used_intrinsics[expr.name] = true
      }
      if (expr.left != null) collect_expr_intrinsics(expr.left)
      if (expr.right != null) collect_expr_intrinsics(expr.right)
      if (expr.expression != null) collect_expr_intrinsics(expr.expression)
      if (expr.then != null) collect_expr_intrinsics(expr.then)
      if (expr.else != null) collect_expr_intrinsics(expr.else)
      if (k == "(" || k == "array" || k == "text literal") {
        i = 0
        while (i < length(expr.list)) {
          collect_expr_intrinsics(expr.list[i])
          i = i + 1
        }
      }
      if (k == "record") {
        i = 0
        while (i < length(expr.list)) {
          collect_expr_intrinsics(expr.list[i].right)
          i = i + 1
        }
      }
      if (k == "function") {
        collect_intrinsics(expr.statements)
        collect_intrinsics(expr.disruption)
        i = 0
        while (i < length(expr.list)) {
          if (expr.list[i].expression != null) {
            collect_expr_intrinsics(expr.list[i].expression)
          }
          i = i + 1
        }
      }
    }

    collect_intrinsics = function(stmts) {
      if (stmts == null) return null
      var i = 0
      var j = 0
      var pi = 0
      var stmt = null
      var k = null
      while (i < length(stmts)) {
        stmt = stmts[i]
        k = stmt.kind
        if (k == "var" || k == "def") {
          collect_expr_intrinsics(stmt.right)
        } else if (k == "var_list") {
          j = 0
          while (j < length(stmt.list)) {
            collect_expr_intrinsics(stmt.list[j].right)
            j = j + 1
          }
        } else if (k == "call") {
          collect_expr_intrinsics(stmt.expression)
        } else if (k == "if") {
          collect_expr_intrinsics(stmt.expression)
          collect_intrinsics(stmt.then)
          collect_intrinsics(stmt.list)
          if (stmt.else != null) collect_intrinsics(stmt.else)
        } else if (k == "while" || k == "do") {
          collect_expr_intrinsics(stmt.expression)
          collect_intrinsics(stmt.statements)
        } else if (k == "for") {
          if (stmt.init != null) {
            if (stmt.init.kind == "var" || stmt.init.kind == "def") {
              collect_expr_intrinsics(stmt.init.right)
            } else {
              collect_expr_intrinsics(stmt.init)
            }
          }
          collect_expr_intrinsics(stmt.test)
          collect_expr_intrinsics(stmt.update)
          collect_intrinsics(stmt.statements)
        } else if (k == "return" || k == "go") {
          collect_expr_intrinsics(stmt.expression)
        } else if (k == "function") {
          collect_intrinsics(stmt.statements)
          collect_intrinsics(stmt.disruption)
          pi = 0
          while (pi < length(stmt.list)) {
            if (stmt.list[pi].expression != null) {
              collect_expr_intrinsics(stmt.list[pi].expression)
            }
            pi = pi + 1
          }
        } else if (k == "block") {
          collect_intrinsics(stmt.statements)
        } else if (k == "label") {
          if (stmt.statement != null) collect_intrinsics([stmt.statement])
        }
        i = i + 1
      }
    }

    collect_intrinsics(ast.statements)
    collect_intrinsics(ast.functions)

    var new_intrinsics = []
    i = 0
    while (i < length(ast.intrinsics)) {
      if (used_intrinsics[ast.intrinsics[i]] == true) {
        push(new_intrinsics, ast.intrinsics[i])
      }
      i = i + 1
    }
    ast.intrinsics = new_intrinsics
  }

  // ============================================================
  // Main
  // ============================================================

  pre_scan()

  // Pass 2: fold all statements and functions
  ast.statements = fold_stmts(ast.statements, 0)
  var fi = 0
  while (fi < length(ast.functions)) {
    fold_fn(ast.functions[fi])
    fi = fi + 1
  }

  // Remove dead top-level functions
  var live_fns = []
  var fn = null
  fi = 0
  while (fi < length(ast.functions)) {
    fn = ast.functions[fi]
    if (fn.dead != true) {
      push(live_fns, fn)
    }
    fi = fi + 1
  }
  ast.functions = live_fns

  // Pass 3: cleanup
  cleanup()

  return ast
}

return fold
