diff --git a/fold.ce b/fold.ce new file mode 100644 index 00000000..0881f8b8 --- /dev/null +++ b/fold.ce @@ -0,0 +1,13 @@ +var fd = use("fd") +var json = use("json") + +var filename = args[0] +var src = text(fd.slurp(filename)) +var tokenize = use("tokenize") +var parse = use("parse") +var fold = use("fold") + +var tok_result = tokenize(src, filename) +var ast = parse(tok_result.tokens, src, filename, tokenize) +var folded = fold(ast) +print(json.encode(folded)) diff --git a/fold.cm b/fold.cm new file mode 100644 index 00000000..bc3d114e --- /dev/null +++ b/fold.cm @@ -0,0 +1,968 @@ +// 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) + + // ============================================================ + // 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" + } + + var is_pure = function(expr) { + if (expr == null) return true + var k = expr.kind + var i = 0 + 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) + } + 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 + 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) + } + } + } 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 + + // Recurse into children first (bottom-up) + if (k == "+" || k == "-" || k == "*" || k == "/" || k == "%" || + k == "**" || k == "==" || k == "!=" || k == "<" || k == ">" || + k == "<=" || k == ">=" || k == "&" || k == "|" || k == "^" || + k == "<<" || k == ">>" || k == ">>>" || k == "&&" || k == "||" || + k == "," || k == "in") { + 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 (k == "!" || k == "~" || k == "-unary" || k == "+unary" || k == "delete") { + 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") { + 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 == "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 == "function") { + fold_fn(expr) + return expr + } else if (k == "assign" || k == "+=" || k == "-=" || k == "*=" || + k == "/=" || k == "%=" || k == "<<=" || k == ">>=" || + k == ">>>=" || k == "&=" || k == "^=" || k == "|=" || + k == "**=" || k == "&&=" || k == "||=") { + 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}) + } + } + return expr + } + + // Binary constant folding + if (k == "+" || 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 == "/") { + 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 (k == "==" || k == "!=" || k == "<" || k == ">" || k == "<=" || k == ">=") { + 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 + 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 + } + 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) + 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 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") { + 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 diff --git a/internal/bootstrap.cm b/internal/bootstrap.cm index 6118967a..3fe1aa97 100644 --- a/internal/bootstrap.cm +++ b/internal/bootstrap.cm @@ -24,8 +24,10 @@ function use_basic(path) { var tok_path = core_path + "/tokenize.cm" var par_path = core_path + "/parse.cm" +var fold_path = core_path + "/fold.cm" var tokenize_mod = mach_eval("tokenize", text(fd.slurp(tok_path)), {use: use_basic}) var parse_mod = mach_eval("parse", text(fd.slurp(par_path)), {use: use_basic}) +var fold_mod = mach_eval("fold", text(fd.slurp(fold_path)), {use: use_basic}) // Optionally load mcode compiler module var mcode_mod = null @@ -66,6 +68,7 @@ function analyze(src, filename) { } disrupt } + ast = fold_mod(ast) return ast } diff --git a/parse.cm b/parse.cm index 36a6f107..1f9dd688 100644 --- a/parse.cm +++ b/parse.cm @@ -403,6 +403,7 @@ var parse = function(tokens, src, filename, tokenizer) { if (tok.kind == ")") advance() else if (tok.kind == "eof") parse_error(tok, "unterminated method parameter list") if (length(params) > 4) parse_error(tok, "functions cannot have more than 4 parameters") + fn.arity = length(params) if (tok.kind == "{") { advance() fn.statements = parse_block_statements() @@ -843,6 +844,7 @@ var parse = function(tokens, src, filename, tokenizer) { } if (length(params) > 4) parse_error(tok, "functions cannot have more than 4 parameters") + node.arity = length(params) if (tok.kind == "{") { advance() @@ -935,6 +937,7 @@ var parse = function(tokens, src, filename, tokenizer) { } if (length(params) > 4) parse_error(tok, "functions cannot have more than 4 parameters") + node.arity = length(params) if (tok.kind != "=>") { parse_error(tok, "expected '=>' in arrow function") @@ -1274,7 +1277,6 @@ var parse = function(tokens, src, filename, tokenizer) { var sem_errors = [] var scopes_array = [] var intrinsics = [] - var block_var_counter = 0 var sem_error = function(node, msg) { var err = {message: msg} @@ -1289,15 +1291,13 @@ var parse = function(tokens, src, filename, tokenizer) { vars: [], in_loop: opts.in_loop == true, function_nr: fn_nr, - is_function_scope: opts.is_func == true, - block_depth: opts.bdepth != null ? opts.bdepth : 0 + is_function_scope: opts.is_func == true } } var sem_add_var = function(scope, name, make_opts) { push(scope.vars, { name: name, - scope_name: null, is_const: make_opts.is_const == true, make: make_opts.make, function_nr: make_opts.fn_nr, @@ -1364,23 +1364,10 @@ var parse = function(tokens, src, filename, tokenizer) { return functino_names[name] == true } - var sem_propagate_block_vars = function(parent, block) { + var sem_propagate_vars = function(parent, child) { var i = 0 - var v = null - var sn = null - while (i < length(block.vars)) { - v = block.vars[i] - sn = v.scope_name - if (sn == null) sn = v.name - push(parent.vars, { - name: sn, - scope_name: null, - is_const: v.is_const, - make: v.make, - function_nr: v.function_nr, - nr_uses: v.nr_uses, - closure: v.closure - }) + while (i < length(child.vars)) { + push(parent.vars, child.vars[i]) i = i + 1 } } @@ -1471,7 +1458,6 @@ var parse = function(tokens, src, filename, tokenizer) { if (r.v != null) { left_node.level = r.level left_node.function_nr = r.def_function_nr - if (r.v.scope_name != null) left_node.scope_name = r.v.scope_name } else { left_node.level = -1 } @@ -1525,7 +1511,6 @@ var parse = function(tokens, src, filename, tokenizer) { if (r.v != null) { operand.level = r.level operand.function_nr = r.def_function_nr - if (r.v.scope_name != null) operand.scope_name = r.v.scope_name } else { operand.level = -1 } @@ -1647,7 +1632,6 @@ var parse = function(tokens, src, filename, tokenizer) { expr.function_nr = r.def_function_nr r.v.nr_uses = r.v.nr_uses + 1 if (r.level > 0) r.v.closure = 1 - if (r.v.scope_name != null) expr.scope_name = r.v.scope_name } else { expr.level = -1 sem_add_intrinsic(name) @@ -1664,15 +1648,10 @@ var parse = function(tokens, src, filename, tokenizer) { var name = null var existing = null var i = 0 - var sn = null - var then_scope = null - var list_scope = null - var else_scope = null var loop_scope = null var do_scope = null var for_scope = null var init_kind = null - var blk_scope = null var fn_nr_val = null var fn_scope = null var pname = null @@ -1695,15 +1674,9 @@ var parse = function(tokens, src, filename, tokenizer) { if (existing != null && existing.is_const) { sem_error(stmt.left, "cannot redeclare constant '" + name + "'") } - if (existing == null || existing.function_nr != scope.function_nr || scope.block_depth > 0) { + if (existing == null || existing.function_nr != scope.function_nr) { sem_add_var(scope, name, {make: "var", fn_nr: scope.function_nr}) } - if (scope.block_depth > 0) { - sn = "_" + name + "_" + text(block_var_counter) - block_var_counter = block_var_counter + 1 - scope.vars[length(scope.vars) - 1].scope_name = sn - stmt.left.scope_name = sn - } } sem_check_expr(scope, stmt.right) return null @@ -1720,12 +1693,6 @@ var parse = function(tokens, src, filename, tokenizer) { existing.make = "def" } else { sem_add_var(scope, name, {is_const: true, make: "def", fn_nr: scope.function_nr}) - if (scope.block_depth > 0) { - sn = "_" + name + "_" + text(block_var_counter) - block_var_counter = block_var_counter + 1 - scope.vars[length(scope.vars) - 1].scope_name = sn - stmt.left.scope_name = sn - } } } sem_check_expr(scope, stmt.right) @@ -1739,58 +1706,52 @@ var parse = function(tokens, src, filename, tokenizer) { if (kind == "if") { sem_check_expr(scope, stmt.expression) - then_scope = make_scope(scope, scope.function_nr, {bdepth: scope.block_depth + 1}) i = 0 while (i < length(stmt.then)) { - sem_check_stmt(then_scope, stmt.then[i]) + sem_check_stmt(scope, stmt.then[i]) i = i + 1 } - sem_propagate_block_vars(scope, then_scope) - list_scope = make_scope(scope, scope.function_nr, {bdepth: scope.block_depth + 1}) i = 0 while (i < length(stmt.list)) { - sem_check_stmt(list_scope, stmt.list[i]) + sem_check_stmt(scope, stmt.list[i]) i = i + 1 } - sem_propagate_block_vars(scope, list_scope) if (stmt.else != null) { - else_scope = make_scope(scope, scope.function_nr, {bdepth: scope.block_depth + 1}) i = 0 while (i < length(stmt.else)) { - sem_check_stmt(else_scope, stmt.else[i]) + sem_check_stmt(scope, stmt.else[i]) i = i + 1 } - sem_propagate_block_vars(scope, else_scope) } return null } if (kind == "while") { sem_check_expr(scope, stmt.expression) - loop_scope = make_scope(scope, scope.function_nr, {in_loop: true, bdepth: scope.block_depth + 1}) + loop_scope = make_scope(scope, scope.function_nr, {in_loop: true}) i = 0 while (i < length(stmt.statements)) { sem_check_stmt(loop_scope, stmt.statements[i]) i = i + 1 } - sem_propagate_block_vars(scope, loop_scope) + sem_propagate_vars(scope, loop_scope) return null } if (kind == "do") { - do_scope = make_scope(scope, scope.function_nr, {in_loop: true, bdepth: scope.block_depth + 1}) + do_scope = make_scope(scope, scope.function_nr, {in_loop: true}) i = 0 while (i < length(stmt.statements)) { sem_check_stmt(do_scope, stmt.statements[i]) i = i + 1 } - sem_propagate_block_vars(scope, do_scope) + sem_propagate_vars(scope, do_scope) sem_check_expr(scope, stmt.expression) return null } if (kind == "for") { - for_scope = make_scope(scope, scope.function_nr, {in_loop: true, bdepth: scope.block_depth + 1}) + for_scope = make_scope(scope, scope.function_nr, {in_loop: true}) if (stmt.init != null) { init_kind = stmt.init.kind if (init_kind == "var" || init_kind == "def") { @@ -1806,7 +1767,7 @@ var parse = function(tokens, src, filename, tokenizer) { sem_check_stmt(for_scope, stmt.statements[i]) i = i + 1 } - sem_propagate_block_vars(scope, for_scope) + sem_propagate_vars(scope, for_scope) return null } @@ -1834,13 +1795,11 @@ var parse = function(tokens, src, filename, tokenizer) { } if (kind == "block") { - blk_scope = make_scope(scope, scope.function_nr, {bdepth: scope.block_depth + 1}) i = 0 while (i < length(stmt.statements)) { - sem_check_stmt(blk_scope, stmt.statements[i]) + sem_check_stmt(scope, stmt.statements[i]) i = i + 1 } - sem_propagate_block_vars(scope, blk_scope) return null }