Files
cell/parse.cm

2186 lines
60 KiB
Plaintext

var parse = function(tokens, src, filename, tokenizer) {
var _src_len = length(src)
var template_escape_map = {
n: "\n", t: "\t", r: "\r", "\\": "\\",
"`": "`", "$": "$", "0": character(0)
}
// ============================================================
// Parser Cursor
// ============================================================
var cursor = 0
var tok = null
var got_lf = false
var prev_tok = null
var _control_depth = 0
var _control_type = null
var _expecting_body = false
var advance = function() {
var t = null
var k = null
prev_tok = tok
cursor = cursor + 1
got_lf = false
while (cursor < length(tokens)) {
t = tokens[cursor]
k = t.kind
if (k == "space" || k == "comment") {
cursor = cursor + 1
continue
}
if (k == "newline") {
got_lf = true
cursor = cursor + 1
continue
}
tok = t
return null
}
tok = tokens[length(tokens) - 1]
}
var peek_ahead = function(n) {
var c = cursor + 1
var count = 0
var t = null
var k = null
while (c < length(tokens)) {
t = tokens[c]
k = t.kind
if (k != "space" && k != "comment" && k != "newline") {
count = count + 1
if (count == n) return t
}
c = c + 1
}
return tokens[length(tokens) - 1]
}
var init_cursor = function() {
cursor = -1
advance()
}
// ============================================================
// AST Helpers
// ============================================================
var errors = []
var error_count = 0
var fn_counter = 1
var ast_node = function(kind, token) {
return {
kind: kind,
at: token.at,
from_row: token.from_row,
from_column: token.from_column
}
}
var ast_node_end = function(node) {
node.to_row = prev_tok.to_row
node.to_column = prev_tok.to_column
return node
}
var parse_error = function(token, msg) {
if (error_count >= 5) return null
error_count = error_count + 1
push(errors, {
message: msg,
line: token.from_row + 1,
column: token.from_column + 1,
offset: token.at
})
}
var _keywords = {
"if": true, in: true, "do": true, go: true,
"var": true, def: true, "for": true,
"else": true, "this": true, "null": true, "true": true,
"false": true, "while": true, "break": true,
"return": true, "delete": true,
disrupt: true, "function": true, "continue": true,
disruption: true
}
var is_keyword = function(kind) {
return _keywords[kind] == true
}
// ============================================================
// Expression Parsing
// ============================================================
// Forward declarations via var
var parse_expr = null
var parse_assign_expr = null
var parse_assign = null
var parse_statement = null
var parse_block_statements = null
var parse_function_inner = null
var parse_arrow_function = null
var is_arrow_function = function() {
if (tok.kind != "(") return false
var c = cursor + 1
var depth = 1
var k = null
while (c < length(tokens) && depth > 0) {
k = tokens[c].kind
if (k == "(") { depth = depth + 1 }
else if (k == ")") { depth = depth - 1 }
else if (k == "text" || k == "number") { null }
c = c + 1
}
while (c < length(tokens)) {
k = tokens[c].kind
if (k != "space" && k != "newline" && k != "comment") break
c = c + 1
}
if (c >= length(tokens)) return false
return tokens[c].kind == "=>"
}
var parse_primary = function() {
var start = tok
var node = null
var k = tok.kind
var list = null
var pair = null
var left = null
var right = null
var is_ident = false
var is_kw = false
var p1 = null
var elem = null
var fn_start = null
var fn = null
var name_item = null
var params = null
var param = null
var rpos = 0
var pattern_parts = null
var flags_parts = null
var tv = null
var has_interp = false
var ti = 0
var tpl_list = null
var fmt_parts = null
var idx = 0
var tvi = 0
var tvlen = 0
var depth = 0
var expr_parts = null
var expr_str = null
var tc = null
var tq = null
var esc_ch = null
var esc_val = null
var expr_tokens = null
var sub_ast = null
var sub_stmt = null
var sub_expr = null
var meth_old_cd = 0
var meth_old_ct = null
var meth_old_eb = false
if (k == "number") {
node = ast_node("number", start)
node.value = tok.value
node.number = tok.number
advance()
ast_node_end(node)
return node
}
if (k == "text") {
// Check for template interpolation: ${...}
tv = tok.value
has_interp = false
ti = 0
while (ti < length(tv) - 1) {
if (tv[ti] == "$" && tv[ti + 1] == "{") {
if (ti == 0 || tv[ti - 1] != "\\") {
has_interp = true
break
}
}
ti = ti + 1
}
if (!has_interp || tokenizer == null) {
node = ast_node("text", start)
node.value = tok.value
advance()
ast_node_end(node)
return node
}
// Template literal with interpolation
node = ast_node("text literal", start)
tpl_list = []
node.list = tpl_list
fmt_parts = []
idx = 0
tvi = 0
tvlen = length(tv)
while (tvi < tvlen) {
if (tv[tvi] == "\\" && tvi + 1 < tvlen) {
esc_ch = tv[tvi + 1]
esc_val = template_escape_map[esc_ch]
if (esc_val != null) { push(fmt_parts, esc_val) }
else { push(fmt_parts, esc_ch) }
tvi = tvi + 2
} else if (tv[tvi] == "$" && tvi + 1 < tvlen && tv[tvi + 1] == "{") {
tvi = tvi + 2
depth = 1
expr_parts = []
while (tvi < tvlen && depth > 0) {
tc = tv[tvi]
if (tc == "{") { depth = depth + 1; push(expr_parts, tc); tvi = tvi + 1 }
else if (tc == "}") {
depth = depth - 1
if (depth > 0) { push(expr_parts, tc) }
tvi = tvi + 1
}
else if (tc == "'" || tc == "\"" || tc == "`") {
tq = tc
push(expr_parts, tc)
tvi = tvi + 1
while (tvi < tvlen && tv[tvi] != tq) {
if (tv[tvi] == "\\" && tvi + 1 < tvlen) {
push(expr_parts, tv[tvi])
tvi = tvi + 1
}
push(expr_parts, tv[tvi])
tvi = tvi + 1
}
if (tvi < tvlen) { push(expr_parts, tv[tvi]); tvi = tvi + 1 }
} else {
push(expr_parts, tc)
tvi = tvi + 1
}
}
expr_str = text(expr_parts)
expr_tokens = tokenizer(expr_str, "<template>").tokens
sub_ast = parse(expr_tokens, expr_str, "<template>", tokenizer)
if (sub_ast != null && sub_ast.statements != null && length(sub_ast.statements) > 0) {
sub_stmt = sub_ast.statements[0]
sub_expr = null
if (sub_stmt.kind == "call") {
sub_expr = sub_stmt.expression
} else {
sub_expr = sub_stmt
}
push(tpl_list, sub_expr)
}
push(fmt_parts, "{")
push(fmt_parts, text(idx))
push(fmt_parts, "}")
idx = idx + 1
} else {
push(fmt_parts, tv[tvi])
tvi = tvi + 1
}
}
node.value = text(fmt_parts)
advance()
ast_node_end(node)
return node
}
if (k == "name") {
p1 = peek_ahead(1)
if (p1.kind == "=>") {
return parse_arrow_function()
}
node = ast_node("name", start)
node.name = tok.value
advance()
ast_node_end(node)
return node
}
if (k == "null") {
node = ast_node("null", start)
advance()
ast_node_end(node)
return node
}
if (k == "true") {
node = ast_node("true", start)
advance()
ast_node_end(node)
return node
}
if (k == "false") {
node = ast_node("false", start)
advance()
ast_node_end(node)
return node
}
if (k == "this") {
node = ast_node("this", start)
advance()
ast_node_end(node)
return node
}
if (k == "[") {
node = ast_node("array", start)
list = []
node.list = list
advance()
while (tok.kind != "]" && tok.kind != "eof") {
elem = parse_assign_expr()
if (elem != null) push(list, elem)
if (tok.kind == ",") advance()
else break
}
ast_node_end(node)
if (tok.kind == "]") advance()
else if (tok.kind == "eof") parse_error(tok, "unterminated array literal, expected ']'")
return node
}
if (k == "{") {
node = ast_node("record", start)
list = []
node.list = list
advance()
while (tok.kind != "}" && tok.kind != "eof") {
pair = {}
is_ident = (tok.kind == "name")
is_kw = is_keyword(tok.kind)
if (is_ident || is_kw || tok.kind == "text" || tok.kind == "number") {
if (is_kw) {
left = ast_node("name", tok)
left.name = tok.kind
advance()
ast_node_end(left)
} else {
left = parse_primary()
}
pair.left = left
} else if (tok.kind == "[") {
advance()
left = parse_assign_expr()
pair.left = left
if (tok.kind == "]") advance()
else parse_error(tok, "expected ']' after computed property")
} else {
parse_error(tok, "expected property name in object literal")
break
}
if (tok.kind == ":") {
advance()
right = parse_assign_expr()
pair.right = right
} else if (tok.kind == "(") {
fn_start = tok
fn = ast_node("function", fn_start)
name_item = pair.left
if (name_item != null && name_item.name != null) {
fn.name = name_item.name
}
params = []
fn.list = params
advance()
while (tok.kind != ")" && tok.kind != "eof") {
if (tok.kind == "name") {
param = ast_node("name", tok)
param.name = tok.value
advance()
ast_node_end(param)
if (tok.kind == "=" || tok.kind == "|") {
advance()
param.expression = parse_assign_expr()
}
push(params, param)
} else {
parse_error(tok, "expected parameter name")
break
}
if (tok.kind == ",") advance()
else break
}
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)
meth_old_cd = _control_depth
meth_old_ct = _control_type
meth_old_eb = _expecting_body
_control_depth = 0
_control_type = null
_expecting_body = false
if (tok.kind == "{") {
advance()
fn.statements = parse_block_statements()
if (tok.kind == "}") advance()
else if (tok.kind == "eof") parse_error(tok, "unterminated method body")
} else {
parse_error(tok, "expected '{' for method body")
}
_control_depth = meth_old_cd
_control_type = meth_old_ct
_expecting_body = meth_old_eb
fn.function_nr = fn_counter
fn_counter = fn_counter + 1
ast_node_end(fn)
pair.right = fn
} else if (is_ident && (tok.kind == "," || tok.kind == "}")) {
right = ast_node("name", pair.left)
right.name = pair.left.name
ast_node_end(right)
pair.right = right
} else {
parse_error(tok, "expected ':' after property name")
}
push(list, pair)
if (tok.kind == ",") advance()
else if (tok.kind == "{") {
if (right && right.kind == "(") {
parse_error(tok, "unexpected '{' after property value; use method shorthand `name(args) { ... }` or `name: function(args) { ... }`")
} else {
parse_error(tok, "expected ',' or '}' in object literal")
}
break
} else break
}
ast_node_end(node)
if (tok.kind == "}") advance()
else if (tok.kind == "eof") parse_error(tok, "unterminated object literal, expected '}'")
return node
}
if (k == "(") {
if (is_arrow_function()) {
return parse_arrow_function()
}
advance()
node = parse_expr()
if (tok.kind == ")") advance()
else if (tok.kind == "eof") parse_error(tok, "unterminated parenthesized expression, expected ')'")
else parse_error(tok, "expected ')' after expression")
return node
}
if (k == "function") {
return parse_function_inner()
}
if (k == "/") {
node = ast_node("regexp", start)
rpos = tok.at + 1
pattern_parts = []
flags_parts = []
while (rpos < _src_len && src[rpos] != "/") {
if (src[rpos] == "\\" && rpos + 1 < _src_len) {
push(pattern_parts, src[rpos])
push(pattern_parts, src[rpos + 1])
rpos = rpos + 2
} else {
push(pattern_parts, src[rpos])
rpos = rpos + 1
}
}
if (rpos < _src_len) rpos = rpos + 1
while (rpos < _src_len && is_letter(src[rpos])) {
push(flags_parts, src[rpos])
rpos = rpos + 1
}
node.pattern = text(pattern_parts)
if (length(flags_parts) > 0) node.flags = text(flags_parts)
// Skip all tokens consumed by the regex re-scan
while (true) {
advance()
if (tok.kind == "eof" || tok.at >= rpos) break
}
ast_node_end(node)
return node
}
if (k == "eof") {
parse_error(start, "unexpected end of input")
} else {
parse_error(start, "unexpected token where expression expected")
}
advance()
return null
}
var parse_postfix = function() {
var node = parse_primary()
var start = null
var new_node = null
var index = null
var arg = null
var args_list = null
var one_node = null
var binop_node = null
var op = null
if (node == null) return null
while (true) {
start = tok
if (tok.kind == ".") {
advance()
new_node = ast_node(".", start)
new_node.left = node
if (tok.kind == "name" || is_keyword(tok.kind)) {
if (tok.kind == "name") {
new_node.right = tok.value
} else {
new_node.right = tok.kind
}
advance()
} else {
parse_error(tok, "expected property name after '.'")
}
ast_node_end(new_node)
node = new_node
} else if (tok.kind == "[") {
advance()
new_node = ast_node("[", start)
new_node.left = node
if (tok.kind == "]") {
advance()
} else {
index = parse_assign_expr()
new_node.right = index
if (tok.kind == "]") advance()
else parse_error(tok, "expected ']'")
}
ast_node_end(new_node)
node = new_node
} else if (tok.kind == "(") {
advance()
new_node = ast_node("(", start)
new_node.expression = node
args_list = []
new_node.list = args_list
while (tok.kind != ")" && tok.kind != "eof") {
arg = parse_assign_expr()
if (arg != null) push(args_list, arg)
if (tok.kind == ",") advance()
else break
}
if (tok.kind == ")") advance()
else parse_error(tok, "unterminated argument list, expected ')'")
ast_node_end(new_node)
node = new_node
} else if (tok.kind == "++") {
new_node = ast_node("++", start)
new_node.expression = node
new_node.postfix = true
advance()
ast_node_end(new_node)
node = new_node
} else if (tok.kind == "--") {
new_node = ast_node("--", start)
new_node.expression = node
new_node.postfix = true
advance()
ast_node_end(new_node)
node = new_node
} else {
break
}
}
return node
}
var parse_unary = function() {
var start = tok
var node = null
var expr = null
var k = tok.kind
var operand = null
var one_node = null
var binop_node = null
var op = null
if (k == "!") {
advance()
node = ast_node("!", start)
node.expression = parse_unary()
ast_node_end(node)
return node
}
if (k == "~") {
advance()
node = ast_node("~", start)
node.expression = parse_unary()
ast_node_end(node)
return node
}
if (k == "+") {
advance()
node = ast_node("+unary", start)
node.expression = parse_unary()
ast_node_end(node)
return node
}
if (k == "-") {
advance()
node = ast_node("-unary", start)
node.expression = parse_unary()
ast_node_end(node)
return node
}
if (k == "++" || k == "--") {
advance()
operand = parse_unary()
one_node = ast_node("number", start)
one_node.number = 1
one_node.value = "1"
ast_node_end(one_node)
op = "+"
if (k == "--") op = "-"
binop_node = ast_node(op, start)
binop_node.left = operand
binop_node.right = one_node
ast_node_end(binop_node)
node = ast_node("assign", start)
node.left = operand
node.right = binop_node
ast_node_end(node)
return node
}
if (k == "delete") {
advance()
node = ast_node("delete", start)
node.expression = parse_unary()
ast_node_end(node)
return node
}
return parse_postfix()
}
// Binary operator precedence
var binop_prec = {
"**": 14,
"*": 13, "/": 13, "%": 13,
"+": 12, "-": 12,
"<<": 11, ">>": 11, ">>>": 11,
"<": 10, ">": 10, "<=": 10, ">=": 10, in: 10,
"==": 9, "!=": 9, "===": 9, "!==": 9,
"&": 8, "^": 7, "|": 6,
"&&": 5, "||": 4
}
var parse_binary = function(min_prec) {
var left_node = parse_unary()
var start = null
var op = null
var prec = null
var next_prec = 0
var right_node = null
var node = null
if (left_node == null) return null
while (true) {
start = tok
op = tok.kind
prec = binop_prec[op]
if (prec == null || prec < min_prec) break
advance()
next_prec = prec + 1
if (prec == 14) next_prec = prec // right-assoc for **
right_node = parse_binary(next_prec)
node = ast_node(op, start)
node.left = left_node
node.right = right_node
ast_node_end(node)
left_node = node
}
return left_node
}
var parse_ternary = function() {
var cond = parse_binary(1)
var start = null
var then_expr = null
var else_expr = null
var node = null
if (cond == null) return null
if (tok.kind == "?") {
start = tok
advance()
then_expr = parse_assign_expr()
if (tok.kind == ":") advance()
else parse_error(tok, "expected ':' in ternary expression")
else_expr = parse_assign_expr()
node = ast_node("then", start)
node.expression = cond
node.then = then_expr
node.else = else_expr
ast_node_end(node)
return node
}
return cond
}
// Assign operators
var assign_ops = {
"=": "assign", "+=": "+=", "-=": "-=", "*=": "*=", "/=": "/=", "%=": "%=",
"<<=": "<<=", ">>=": ">>=", ">>>=": ">>>=",
"&=": "&=", "^=": "^=", "|=": "|=", "**=": "**=",
"&&=": "&&=", "||=": "||="
}
var compound_binop = {
"+=": "+", "-=": "-", "*=": "*", "/=": "/", "%=": "%",
"<<=": "<<", ">>=": ">>", ">>>=": ">>>",
"&=": "&", "^=": "^", "|=": "|", "**=": "**",
"&&=": "&&", "||=": "||"
}
parse_assign = function(unused) {
var left_node = parse_ternary()
var start = null
var kind = null
var right_node = null
var node = null
var left_kind = null
var right_kind = null
var binop = null
var binop_node = null
if (left_node == null) return null
start = tok
kind = assign_ops[tok.kind]
if (kind == null) return left_node
left_kind = left_node.kind
if (left_kind != "name" && left_kind != "." && left_kind != "[") {
parse_error(start, "invalid assignment left-hand side")
}
advance()
right_node = parse_assign()
binop = compound_binop[kind]
if (binop != null) {
binop_node = ast_node(binop, start)
binop_node.left = left_node
binop_node.right = right_node
ast_node_end(binop_node)
node = ast_node("assign", start)
node.left = left_node
node.right = binop_node
} else {
node = ast_node(kind, start)
node.left = left_node
node.right = right_node
if (left_node.kind == "[" && left_node.right == null) node.push = true
if (right_node != null && right_node.kind == "[" && right_node.right == null) node.pop = true
}
ast_node_end(node)
return node
}
parse_assign_expr = function(unused) {
return parse_assign()
}
parse_expr = function(unused) {
var left_node = parse_assign()
var start = null
var right_node = null
var node = null
if (left_node == null) return null
while (tok.kind == ",") {
start = tok
advance()
right_node = parse_assign()
node = ast_node(",", start)
node.left = left_node
node.right = right_node
ast_node_end(node)
left_node = node
}
return left_node
}
// ============================================================
// Statement Parsing
// ============================================================
var in_disruption = 0
var expect_semi = function() {
if (tok.kind == ";") { advance(); return null }
if (tok.kind == "eof" || tok.kind == "}" || got_lf || tok.kind == "else") return null
parse_error(tok, "expecting ';'")
}
var sync_to_statement = function() {
var k = null
while (tok.kind != "eof") {
k = tok.kind
if (k == ";") { advance(); return null }
if (k == "}") return null
if (k == "var" || k == "def" || k == "if" || k == "while" ||
k == "for" || k == "return" || k == "disrupt" ||
k == "function" || k == "break" || k == "continue" || k == "do") return null
advance()
}
}
parse_block_statements = function(unused) {
var stmts = []
var before = null
var stmt = null
while (tok.kind != "}" && tok.kind != "eof") {
before = cursor
stmt = parse_statement()
if (stmt != null) {
push(stmts, stmt)
} else if (cursor == before) {
sync_to_statement()
}
}
return stmts
}
parse_function_inner = function(unused) {
var start = tok
var node = ast_node("function", start)
var params = []
var stmts = null
var param = null
var prev_names = null
var pname = null
var old_dis = 0
var old_cd = _control_depth
var old_ct = _control_type
var old_eb = _expecting_body
if (in_disruption) {
parse_error(tok, "cannot define function inside disruption clause")
}
advance() // skip 'function'
if (tok.kind == "name") {
node.name = tok.value
advance()
}
node.list = params
if (tok.kind == "(") {
advance()
prev_names = []
while (tok.kind != ")" && tok.kind != "eof") {
if (tok.kind == "name") {
param = ast_node("name", tok)
param.name = tok.value
pname = tok.value
if (find(prev_names, pname) != null) parse_error(tok, "duplicate parameter name '" + pname + "'")
push(prev_names, pname)
advance()
ast_node_end(param)
if (tok.kind == "=" || tok.kind == "|") {
advance()
param.expression = parse_assign_expr()
}
push(params, param)
} else {
parse_error(tok, "expected parameter name")
break
}
if (tok.kind == ",") advance()
else break
}
if (tok.kind == ")") advance()
else if (tok.kind == "eof") parse_error(tok, "unterminated function parameter list, expected ')'")
} else {
parse_error(tok, "expected '(' after function name")
}
if (length(params) > 4) parse_error(tok, "functions cannot have more than 4 parameters")
node.arity = length(params)
_control_depth = 0
_control_type = null
_expecting_body = false
if (tok.kind == "{") {
advance()
stmts = parse_block_statements()
node.statements = stmts
if (tok.kind == "}") advance()
else if (tok.kind == "eof") parse_error(tok, "unterminated function body, expected '}'")
} else {
parse_error(tok, "expected '{' for function body")
}
if (tok.kind == "disruption") {
advance()
if (tok.kind == "{") {
advance()
old_dis = in_disruption
in_disruption = 1
node.disruption = parse_block_statements()
in_disruption = old_dis
if (tok.kind == "}") advance()
else if (tok.kind == "eof") parse_error(tok, "unterminated disruption clause, expected '}'")
} else {
parse_error(tok, "expected '{' after disruption")
}
}
_control_depth = old_cd
_control_type = old_ct
_expecting_body = old_eb
node.function_nr = fn_counter
fn_counter = fn_counter + 1
ast_node_end(node)
return node
}
parse_arrow_function = function(unused) {
var start = tok
var node = ast_node("function", start)
var params = []
var param = null
var stmts = null
var ret = null
var expr = null
var prev_names = null
var pname = null
var old_cd = _control_depth
var old_ct = _control_type
var old_eb = _expecting_body
node.arrow = true
if (in_disruption) {
parse_error(tok, "cannot define function inside disruption clause")
}
node.list = params
if (tok.kind == "name") {
param = ast_node("name", tok)
param.name = tok.value
advance()
ast_node_end(param)
push(params, param)
} else if (tok.kind == "(") {
advance()
prev_names = []
while (tok.kind != ")" && tok.kind != "eof") {
if (tok.kind == "name") {
param = ast_node("name", tok)
param.name = tok.value
pname = tok.value
if (find(prev_names, pname) != null) parse_error(tok, "duplicate parameter name '" + pname + "'")
push(prev_names, pname)
advance()
ast_node_end(param)
if (tok.kind == "=" || tok.kind == "|") {
advance()
param.expression = parse_assign_expr()
}
push(params, param)
} else {
parse_error(tok, "expected parameter name")
break
}
if (tok.kind == ",") advance()
else break
}
if (tok.kind == ")") advance()
}
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")
} else {
advance()
}
_control_depth = 0
_control_type = null
_expecting_body = false
if (tok.kind == "{") {
advance()
stmts = parse_block_statements()
node.statements = stmts
if (tok.kind == "}") advance()
} else {
stmts = []
ret = ast_node("return", tok)
expr = parse_assign_expr()
ret.expression = expr
ast_node_end(ret)
push(stmts, ret)
node.statements = stmts
}
_control_depth = old_cd
_control_type = old_ct
_expecting_body = old_eb
node.function_nr = fn_counter
fn_counter = fn_counter + 1
ast_node_end(node)
return node
}
parse_statement = function(unused) {
var start = tok
var node = null
var k = tok.kind
var stmts = null
var cond = null
var then_stmts = null
var else_stmts = null
var else_ifs = null
var body = null
var expr = null
var init = null
var test = null
var update = null
var left_node = null
var right_node = null
var kind_name = null
var is_def = false
var decls = null
var decl_count = 0
var var_name = null
var right_kind = null
var elif = null
var p1_tok = null
var labeled_stmt = null
var depth = 0
var saved_ct = null
var saved_cd = 0
var saved_eb = false
if (k == "{") {
if (!_expecting_body) {
parse_error(start, "bare block '{ ... }' is not a valid statement; use a function, if, while, or for instead")
advance()
depth = 1
while (tok.kind != "eof" && depth > 0) {
if (tok.kind == "{") depth = depth + 1
else if (tok.kind == "}") depth = depth - 1
if (depth > 0) advance()
}
if (tok.kind == "}") advance()
return null
}
_expecting_body = false
node = ast_node("block", start)
advance()
stmts = parse_block_statements()
node.statements = stmts
if (tok.kind == "}") advance()
ast_node_end(node)
return node
}
if (k == "var" || k == "def") {
if (_control_depth > 0) {
parse_error(start, "'" + k + "' declarations must appear at function body level, not inside '" + _control_type + "'; move this declaration before the '" + _control_type + "' statement")
}
kind_name = k
is_def = (k == "def")
advance()
if (tok.kind != "name") {
parse_error(tok, "expected identifier after '" + kind_name + "'")
return null
}
decls = []
decl_count = 0
while (tok.kind == "name") {
node = ast_node(kind_name, start)
left_node = ast_node("name", tok)
left_node.name = tok.value
var_name = tok.value
advance()
ast_node_end(left_node)
node.left = left_node
if (tok.kind == "=") {
advance()
right_node = parse_assign_expr()
node.right = right_node
if (right_node != null && right_node.kind == "[" && right_node.right == null) {
node.pop = true
}
} else if (is_def) {
parse_error(start, "missing initializer for constant '" + var_name + "'")
} else {
parse_error(start, "'var' declarations must be initialized; use 'var " + var_name + " = null' if no value is needed")
}
ast_node_end(node)
push(decls, node)
decl_count = decl_count + 1
if (tok.kind == ",") advance()
else break
}
expect_semi()
if (decl_count == 1) {
return decls[0]
}
node = ast_node("var_list", start)
node.list = decls
ast_node_end(node)
return node
}
if (k == "if") {
node = ast_node("if", start)
advance()
if (tok.kind == "(") advance()
else parse_error(tok, "expected '(' before condition")
cond = parse_expr()
node.expression = cond
if (tok.kind == ")") advance()
else parse_error(tok, "expected ')' after if condition")
then_stmts = []
node.then = then_stmts
saved_ct = _control_type
saved_cd = _control_depth
_control_type = "if"
_control_depth = _control_depth + 1
_expecting_body = true
body = parse_statement()
if (body != null) push(then_stmts, body)
else_ifs = []
node.list = else_ifs
if (tok.kind == "else") {
advance()
if (tok.kind == "if") {
_control_depth = saved_cd
_control_type = saved_ct
elif = parse_statement()
if (elif != null) push(else_ifs, elif)
ast_node_end(node)
return node
} else {
else_stmts = []
node.else = else_stmts
_expecting_body = true
body = parse_statement()
if (body != null) push(else_stmts, body)
}
}
_control_depth = saved_cd
_control_type = saved_ct
ast_node_end(node)
return node
}
if (k == "while") {
node = ast_node("while", start)
advance()
if (tok.kind == "(") advance()
else parse_error(tok, "expected '(' before condition")
cond = parse_expr()
node.expression = cond
if (tok.kind == ")") advance()
else parse_error(tok, "expected ')' after while condition")
stmts = []
node.statements = stmts
saved_ct = _control_type
saved_cd = _control_depth
_control_type = "while"
_control_depth = _control_depth + 1
_expecting_body = true
body = parse_statement()
if (body != null) push(stmts, body)
_control_depth = saved_cd
_control_type = saved_ct
ast_node_end(node)
return node
}
if (k == "do") {
node = ast_node("do", start)
advance()
stmts = []
node.statements = stmts
saved_ct = _control_type
saved_cd = _control_depth
_control_type = "do"
_control_depth = _control_depth + 1
_expecting_body = true
body = parse_statement()
if (body != null) push(stmts, body)
_control_depth = saved_cd
_control_type = saved_ct
if (tok.kind == "while") advance()
else parse_error(tok, "expected 'while' after do body")
if (tok.kind == "(") advance()
else parse_error(tok, "expected '(' before condition")
cond = parse_expr()
node.expression = cond
if (tok.kind == ")") advance()
else parse_error(tok, "expected ')' after do-while condition")
expect_semi()
ast_node_end(node)
return node
}
if (k == "for") {
node = ast_node("for", start)
advance()
if (tok.kind == "(") advance()
else parse_error(tok, "expected '(' after for")
if (tok.kind != ";") {
if (tok.kind == "var" || tok.kind == "def") {
parse_error(tok, "'" + tok.kind + "' declarations cannot appear in the for initializer; declare variables before the for loop")
init = parse_statement()
node.init = init
} else {
init = parse_expr()
node.init = init
if (tok.kind == ";") advance()
}
} else {
advance()
}
if (tok.kind != ";") {
test = parse_expr()
node.test = test
}
if (tok.kind == ";") advance()
if (tok.kind != ")") {
update = parse_expr()
node.update = update
}
if (tok.kind == ")") advance()
else parse_error(tok, "expected ')' after for clauses")
stmts = []
node.statements = stmts
saved_ct = _control_type
saved_cd = _control_depth
_control_type = "for"
_control_depth = _control_depth + 1
_expecting_body = true
body = parse_statement()
if (body != null) push(stmts, body)
_control_depth = saved_cd
_control_type = saved_ct
ast_node_end(node)
return node
}
if (k == "return") {
node = ast_node("return", start)
advance()
if (tok.kind != ";" && tok.kind != "}" && !got_lf) {
expr = parse_expr()
node.expression = expr
}
expect_semi()
ast_node_end(node)
return node
}
if (k == "go") {
node = ast_node("go", start)
advance()
if (tok.kind != ";" && tok.kind != "}" && !got_lf) {
expr = parse_expr()
node.expression = expr
}
expect_semi()
ast_node_end(node)
return node
}
if (k == "disrupt") {
node = ast_node("disrupt", start)
advance()
expect_semi()
ast_node_end(node)
return node
}
if (k == "break") {
node = ast_node("break", start)
advance()
if (tok.kind == "name" && !got_lf) {
node.name = tok.value
advance()
}
expect_semi()
ast_node_end(node)
return node
}
if (k == "continue") {
node = ast_node("continue", start)
advance()
if (tok.kind == "name" && !got_lf) {
node.name = tok.value
advance()
}
expect_semi()
ast_node_end(node)
return node
}
if (k == "function") {
return parse_function_inner()
}
if (k == ";") {
advance()
return null
}
if (k == "name") {
if (tok.value == "try" || tok.value == "catch" || tok.value == "finally") {
parse_error(start, "'" + tok.value + "' is not supported; use disrupt/disruption instead")
sync_to_statement()
return null
}
if (tok.value == "throw") {
parse_error(start, "'throw' is not supported; use disrupt instead")
sync_to_statement()
return null
}
if (tok.value == "class") {
parse_error(start, "'class' is not supported; use meme()/proto() instead")
sync_to_statement()
return null
}
if (tok.value == "new") {
parse_error(start, "'new' is not supported; use meme()/proto() instead")
sync_to_statement()
return null
}
if (tok.value == "switch" || tok.value == "case") {
parse_error(start, "'" + tok.value + "' is not supported; use if/else instead")
sync_to_statement()
return null
}
if (tok.value == "let" || tok.value == "const") {
parse_error(start, "'" + tok.value + "' is not supported; use var/def instead")
sync_to_statement()
return null
}
p1_tok = peek_ahead(1)
if (p1_tok.kind == ":") {
node = ast_node("label", start)
node.name = tok.value
advance() // skip identifier
advance() // skip colon
labeled_stmt = parse_statement()
node.statement = labeled_stmt
ast_node_end(node)
return node
}
}
expr = parse_expr()
if (expr != null) {
node = ast_node("call", start)
node.expression = expr
ast_node_end(node)
expect_semi()
return node
}
parse_error(start, "unexpected token at start of statement")
return null
}
// ============================================================
// Program
// ============================================================
var parse_program = function() {
var root = {kind: "program", filename: filename}
var functions = []
var statements = []
var before = 0
var stmt = null
root.functions = functions
root.statements = statements
while (tok.kind != "eof") {
before = cursor
stmt = parse_statement()
if (stmt != null) {
if (stmt.kind == "function") {
push(functions, stmt)
} else {
push(statements, stmt)
}
} else if (cursor == before) {
sync_to_statement()
}
}
return root
}
// ============================================================
// Semantic Analysis
// ============================================================
var sem_errors = []
var scopes_array = []
var intrinsics = []
var sem_error = function(node, msg) {
var err = {message: msg}
if (node.from_row != null) err.line = node.from_row + 1
if (node.from_column != null) err.column = node.from_column + 1
push(sem_errors, err)
}
var make_scope = function(parent, fn_nr, opts) {
return {
parent: parent,
vars: [],
in_loop: opts.in_loop == true,
function_nr: fn_nr,
is_function_scope: opts.is_func == true,
func_node: null,
has_inner_func: false
}
}
var sem_add_var = function(scope, name, make_opts) {
push(scope.vars, {
name: name,
is_const: make_opts.is_const == true,
make: make_opts.make,
function_nr: make_opts.fn_nr,
nr_uses: 0,
closure: 0
})
}
var sem_lookup_var = function(scope, name) {
var result = {v: null, level: 0, def_function_nr: -1}
var cur_fn = scope.function_nr
var s = scope
var i = 0
while (s != null) {
i = 0
while (i < length(s.vars)) {
if (s.vars[i].name == name) {
result.v = s.vars[i]
result.def_function_nr = s.vars[i].function_nr
return result
}
i = i + 1
}
if (s.parent != null && s.parent.function_nr != cur_fn) {
result.level = result.level + 1
cur_fn = s.parent.function_nr
}
s = s.parent
}
return result
}
var sem_find_var = function(scope, name) {
var r = sem_lookup_var(scope, name)
return r.v
}
var sem_in_loop = function(scope) {
var s = scope
while (s != null) {
if (s.in_loop) return true
s = s.parent
}
return false
}
var sem_find_func_scope = function(scope) {
var s = scope
while (s != null) {
if (s.is_function_scope) return s
s = s.parent
}
return null
}
var sem_add_intrinsic = function(name) {
if (find(intrinsics, name) == null) push(intrinsics, name)
}
var functino_names = {
"+!": true, "-!": true, "*!": true, "/!": true, "%!": true, "**!": true,
"<!": true, ">!": true, "<=!": true, ">=!": true, "=!": true, "!=!": true,
"&!": true, "|!": true, "^!": true, "<<!": true, ">>!": true, ">>>!": true,
"&&!": true, "||!": true, "~!": true, "[]!": true
}
var is_functino_name = function(name) {
return functino_names[name] == true
}
var derive_type_tag = function(expr) {
if (expr == null) return null
var k = expr.kind
if (k == "array") return "array"
if (k == "record") return "record"
if (k == "function") return "function"
if (k == "text" || k == "text literal") return "text"
if (k == "number") {
if (is_integer(expr.number)) return "integer"
return "number"
}
if (k == "true" || k == "false") return "logical"
if (k == "null") return "null"
return null
}
var _assign_kinds = {
assign: true, "+=": true, "-=": true, "*=": true, "/=": true, "%=": true,
"<<=": true, ">>=": true, ">>>=": true,
"&=": true, "^=": true, "|=": true, "**=": true,
"&&=": true, "||=": true
}
var sem_propagate_vars = function(parent, child) {
parent.vars = array(parent.vars, child.vars)
}
var sem_build_scope_record = function(scope) {
var rec = {function_nr: scope.function_nr}
var slots = 0
var close_slots = 0
var i = 0
var v = null
while (i < length(scope.vars)) {
v = scope.vars[i]
rec[v.name] = {
make: v.make,
function_nr: v.function_nr,
nr_uses: v.nr_uses,
closure: v.closure == 1,
level: 0,
type_tag: v.type_tag
}
slots = slots + 1
if (v.closure) close_slots = close_slots + 1
i = i + 1
}
return {rec: rec, nr_slots: slots, nr_close: close_slots}
}
// Forward declarations
var sem_check_expr = null
var sem_check_stmt = null
var sem_predeclare_vars = function(scope, stmts) {
var i = 0
var stmt = null
var kind = null
var name = null
var item = null
var ik = null
var j = 0
while (i < length(stmts)) {
stmt = stmts[i]
kind = stmt.kind
if (kind == "function") {
name = stmt.name
if (name != null && sem_find_var(scope, name) == null) {
sem_add_var(scope, name, {make: "function", fn_nr: scope.function_nr})
}
} else if (kind == "var") {
name = stmt.left.name
if (name != null && sem_find_var(scope, name) == null) {
sem_add_var(scope, name, {make: "var", fn_nr: scope.function_nr})
}
} else if (kind == "var_list") {
j = 0
while (j < length(stmt.list)) {
item = stmt.list[j]
ik = item.kind
if (ik == "var") {
name = item.left.name
if (name != null && sem_find_var(scope, name) == null) {
sem_add_var(scope, name, {make: "var", fn_nr: scope.function_nr})
}
}
j = j + 1
}
}
i = i + 1
}
}
var sem_check_assign_target = function(scope, left_node) {
if (left_node == null) return null
var kind = left_node.kind
var name = null
var v = null
var r = null
var obj_expr = null
if (kind == "name") {
name = left_node.name
if (name == null) return null
v = sem_find_var(scope, name)
if (v == null) {
sem_error(left_node, "cannot assign to unbound variable '" + name + "'")
} else if (v.is_const) {
sem_error(left_node, "cannot assign to constant '" + name + "'")
}
r = sem_lookup_var(scope, name)
if (r.v != null) {
left_node.level = r.level
left_node.function_nr = r.def_function_nr
} else {
left_node.level = -1
}
} else if (kind == "." || kind == "[") {
obj_expr = left_node.left
sem_check_expr(scope, obj_expr)
if (kind == "[" && left_node.right != null) {
sem_check_expr(scope, left_node.right)
}
// Type error detection for known-type constant objects
if (obj_expr != null && obj_expr.kind == "name" && obj_expr.name != null) {
v = sem_find_var(scope, obj_expr.name)
if (v != null && v.is_const && v.type_tag != null) {
if (kind == ".") {
if (v.type_tag == "array") {
sem_error(left_node, "cannot set property on array '" + obj_expr.name + "'")
}
} else if (kind == "[") {
if (left_node.right == null) {
// Push: a[] = val
if (v.type_tag != "array") {
sem_error(left_node, "push only works on arrays, not " + v.type_tag + " '" + obj_expr.name + "'")
}
} else if (v.type_tag == "array") {
if (left_node.right.kind == "text") {
sem_error(left_node, "cannot use text key on array '" + obj_expr.name + "'")
}
} else if (v.type_tag == "record") {
if (left_node.right.kind == "number" && is_integer(left_node.right.number)) {
sem_error(left_node, "cannot use integer key on record '" + obj_expr.name + "'; use text key")
}
}
}
} else if (v != null && v.is_const && v.type_tag == null) {
// Infer type_tag from usage pattern (def only)
if (kind == ".") {
v.type_tag = "record"
} else if (kind == "[") {
if (left_node.right == null) {
// Push: a[] = val → array
v.type_tag = "array"
} else if (left_node.right.kind == "number" && is_integer(left_node.right.number)) {
v.type_tag = "array"
} else if (left_node.right.kind == "text") {
v.type_tag = "record"
}
}
}
}
}
}
sem_check_expr = function(scope, expr) {
if (expr == null) return null
var kind = expr.kind
if (kind == null) return null
var name = null
var r = null
var i = 0
var operand = null
var v = null
var prop = null
var val = null
var fn_nr_val = null
var fn_scope = null
var pname = null
var def_val = null
var sr = null
var enclosing = null
if (_assign_kinds[kind] == true) {
sem_check_assign_target(scope, expr.left)
sem_check_expr(scope, expr.right)
return null
}
if (kind == "++" || kind == "--") {
operand = expr.expression
if (operand != null && operand.kind == "name") {
name = operand.name
if (name != null) {
v = sem_find_var(scope, name)
if (v == null) {
sem_error(expr, "cannot assign to unbound variable '" + name + "'")
} else if (v.is_const) {
sem_error(expr, "cannot assign to constant '" + name + "'")
}
r = sem_lookup_var(scope, name)
if (r.v != null) {
operand.level = r.level
operand.function_nr = r.def_function_nr
} else {
operand.level = -1
}
}
} else if (operand != null) {
sem_check_assign_target(scope, operand)
}
return null
}
if (kind == "this") {
if (scope.function_nr == 0) {
sem_error(expr, "'this' cannot be used at the top level of a program")
}
return null
}
if (kind == "[") {
sem_check_expr(scope, expr.left)
sem_check_expr(scope, expr.right)
if (expr.right != null) {
if (expr.right.kind == "number" && is_integer(expr.right.number)) {
expr.access_kind = "index"
} else if (expr.right.kind == "text") {
expr.access_kind = "field"
}
}
return null
}
if (kind == "," || kind == "+" || kind == "-" || kind == "*" ||
kind == "/" || kind == "%" || kind == "==" || kind == "!=" ||
kind == "<" || kind == ">" || kind == "<=" || kind == ">=" ||
kind == "&&" || kind == "||" || kind == "&" ||
kind == "|" || kind == "^" || kind == "<<" || kind == ">>" ||
kind == ">>>" || kind == "**" || kind == "in" ||
kind == ".") {
sem_check_expr(scope, expr.left)
sem_check_expr(scope, expr.right)
return null
}
if (kind == "then") {
sem_check_expr(scope, expr.expression)
sem_check_expr(scope, expr.then)
sem_check_expr(scope, expr.else)
return null
}
if (kind == "(") {
sem_check_expr(scope, expr.expression)
i = 0
while (i < length(expr.list)) {
sem_check_expr(scope, expr.list[i])
i = i + 1
}
return null
}
if (kind == "!" || kind == "~" || kind == "delete" ||
kind == "-unary" || kind == "+unary") {
sem_check_expr(scope, expr.expression)
return null
}
if (kind == "array") {
i = 0
while (i < length(expr.list)) {
sem_check_expr(scope, expr.list[i])
i = i + 1
}
return null
}
if (kind == "record") {
i = 0
while (i < length(expr.list)) {
prop = expr.list[i]
val = prop.right
sem_check_expr(scope, val)
i = i + 1
}
return null
}
if (kind == "function") {
enclosing = sem_find_func_scope(scope)
if (enclosing != null) enclosing.has_inner_func = true
fn_nr_val = expr.function_nr
if (fn_nr_val == null) fn_nr_val = scope.function_nr
fn_scope = make_scope(scope, fn_nr_val, {is_func: true})
fn_scope.func_node = expr
expr.outer = scope.function_nr
i = 0
while (i < length(expr.list)) {
pname = expr.list[i].name
if (pname != null) sem_add_var(fn_scope, pname, {is_const: true, make: "input", fn_nr: fn_nr_val})
def_val = expr.list[i].expression
if (def_val != null) sem_check_expr(fn_scope, def_val)
i = i + 1
}
if (expr.statements != null) {
sem_predeclare_vars(fn_scope, expr.statements)
i = 0
while (i < length(expr.statements)) {
sem_check_stmt(fn_scope, expr.statements[i])
i = i + 1
}
}
if (expr.disruption != null) {
i = 0
while (i < length(expr.disruption)) {
sem_check_stmt(fn_scope, expr.disruption[i])
i = i + 1
}
}
sr = sem_build_scope_record(fn_scope)
push(scopes_array, sr.rec)
expr.nr_slots = sr.nr_slots
expr.nr_close_slots = sr.nr_close
return null
}
if (kind == "text literal") {
i = 0
while (i < length(expr.list)) {
sem_check_expr(scope, expr.list[i])
i = i + 1
}
return null
}
if (kind == "name") {
name = expr.name
if (name != null) {
if (is_functino_name(name)) {
expr.make = "functino"
expr.level = -1
return null
}
r = sem_lookup_var(scope, name)
if (r.v != null) {
expr.level = r.level
expr.function_nr = r.def_function_nr
r.v.nr_uses = r.v.nr_uses + 1
if (r.level > 0) r.v.closure = 1
} else {
expr.level = -1
expr.intrinsic = true
sem_add_intrinsic(name)
}
}
return null
}
}
sem_check_stmt = function(scope, stmt) {
if (stmt == null) return null
var kind = stmt.kind
if (kind == null) return null
var name = null
var existing = null
var i = 0
var loop_scope = null
var do_scope = null
var for_scope = null
var init_kind = null
var fn_nr_val = null
var fn_scope = null
var pname = null
var def_val = null
var sr = null
var enclosing = null
var func_scope = null
var tt = null
if (kind == "var_list") {
i = 0
while (i < length(stmt.list)) {
sem_check_stmt(scope, stmt.list[i])
i = i + 1
}
return null
}
if (kind == "var") {
name = stmt.left.name
if (name != null) {
existing = sem_find_var(scope, name)
if (existing != null && existing.is_const) {
sem_error(stmt.left, "cannot redeclare constant '" + name + "'")
}
if (existing == null || existing.function_nr != scope.function_nr) {
sem_add_var(scope, name, {make: "var", fn_nr: scope.function_nr})
}
}
sem_check_expr(scope, stmt.right)
return null
}
if (kind == "def") {
name = stmt.left.name
if (name != null) {
existing = sem_find_var(scope, name)
if (existing != null && existing.is_const) {
sem_error(stmt.left, "cannot redeclare constant '" + name + "'")
} else if (existing != null && !existing.is_const && existing.function_nr == scope.function_nr) {
existing.is_const = 1
existing.make = "def"
} else {
sem_add_var(scope, name, {is_const: true, make: "def", fn_nr: scope.function_nr})
}
}
sem_check_expr(scope, stmt.right)
if (name != null) {
tt = derive_type_tag(stmt.right)
if (tt != null && tt != "null") {
existing = sem_find_var(scope, name)
if (existing != null) existing.type_tag = tt
}
}
return null
}
if (kind == "call") {
sem_check_expr(scope, stmt.expression)
return null
}
if (kind == "if") {
sem_check_expr(scope, stmt.expression)
i = 0
while (i < length(stmt.then)) {
sem_check_stmt(scope, stmt.then[i])
i = i + 1
}
i = 0
while (i < length(stmt.list)) {
sem_check_stmt(scope, stmt.list[i])
i = i + 1
}
if (stmt.else != null) {
i = 0
while (i < length(stmt.else)) {
sem_check_stmt(scope, stmt.else[i])
i = i + 1
}
}
return null
}
if (kind == "while") {
sem_check_expr(scope, stmt.expression)
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_vars(scope, loop_scope)
return null
}
if (kind == "do") {
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_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})
if (stmt.init != null) {
init_kind = stmt.init.kind
if (init_kind == "var" || init_kind == "def") {
sem_check_stmt(for_scope, stmt.init)
} else {
sem_check_expr(for_scope, stmt.init)
}
}
sem_check_expr(for_scope, stmt.test)
sem_check_expr(for_scope, stmt.update)
i = 0
while (i < length(stmt.statements)) {
sem_check_stmt(for_scope, stmt.statements[i])
i = i + 1
}
sem_propagate_vars(scope, for_scope)
return null
}
if (kind == "go") {
sem_check_expr(scope, stmt.expression)
if (stmt.expression == null || stmt.expression.kind != "(") {
sem_error(stmt, "'go' must be followed by a function call")
} else {
func_scope = sem_find_func_scope(scope)
if (func_scope != null && func_scope.func_node != null) {
if (func_scope.func_node.disruption != null) {
sem_error(stmt, "cannot use 'go' in a function with a disruption clause")
}
if (func_scope.has_inner_func) {
sem_error(stmt, "cannot use 'go' in a function that defines inner functions")
}
}
stmt.tail = true
}
return null
}
if (kind == "return") {
sem_check_expr(scope, stmt.expression)
if (stmt.expression != null && stmt.expression.kind == "(") {
stmt.tail = true
}
return null
}
if (kind == "disrupt") {
return null
}
if (kind == "break") {
if (!sem_in_loop(scope)) {
sem_error(stmt, "'break' used outside of loop")
}
return null
}
if (kind == "continue") {
if (!sem_in_loop(scope)) {
sem_error(stmt, "'continue' used outside of loop")
}
return null
}
if (kind == "block") {
i = 0
while (i < length(stmt.statements)) {
sem_check_stmt(scope, stmt.statements[i])
i = i + 1
}
return null
}
if (kind == "label") {
sem_check_stmt(scope, stmt.statement)
return null
}
if (kind == "function") {
enclosing = sem_find_func_scope(scope)
if (enclosing != null) enclosing.has_inner_func = true
name = stmt.name
if (name != null && sem_find_var(scope, name) == null) sem_add_var(scope, name, {make: "function", fn_nr: scope.function_nr})
fn_nr_val = stmt.function_nr
if (fn_nr_val == null) fn_nr_val = scope.function_nr
fn_scope = make_scope(scope, fn_nr_val, {is_func: true})
fn_scope.func_node = stmt
stmt.outer = scope.function_nr
i = 0
while (i < length(stmt.list)) {
pname = stmt.list[i].name
if (pname != null) sem_add_var(fn_scope, pname, {is_const: true, make: "input", fn_nr: fn_nr_val})
def_val = stmt.list[i].expression
if (def_val != null) sem_check_expr(fn_scope, def_val)
i = i + 1
}
sem_predeclare_vars(fn_scope, stmt.statements)
i = 0
while (i < length(stmt.statements)) {
sem_check_stmt(fn_scope, stmt.statements[i])
i = i + 1
}
if (stmt.disruption != null) {
i = 0
while (i < length(stmt.disruption)) {
sem_check_stmt(fn_scope, stmt.disruption[i])
i = i + 1
}
}
sr = sem_build_scope_record(fn_scope)
push(scopes_array, sr.rec)
stmt.nr_slots = sr.nr_slots
stmt.nr_close_slots = sr.nr_close
return null
}
}
var semantic_check = function(ast) {
var global_scope = make_scope(null, 0, {is_func: true})
var i = 0
var stmt = null
var name = null
var sr = null
var new_scopes = null
i = 0
while (i < length(ast.functions)) {
name = ast.functions[i].name
if (name != null) sem_add_var(global_scope, name, {make: "function", fn_nr: 0})
i = i + 1
}
i = 0
while (i < length(ast.statements)) {
sem_check_stmt(global_scope, ast.statements[i])
i = i + 1
}
i = 0
while (i < length(ast.functions)) {
sem_check_stmt(global_scope, ast.functions[i])
i = i + 1
}
sr = sem_build_scope_record(global_scope)
new_scopes = [sr.rec]
i = 0
while (i < length(scopes_array)) {
push(new_scopes, scopes_array[i])
i = i + 1
}
scopes_array = new_scopes
ast.scopes = scopes_array
ast.intrinsics = intrinsics
if (length(sem_errors) > 0) {
ast.errors = sem_errors
}
}
// ============================================================
// Main
// ============================================================
init_cursor()
var ast = parse_program()
if (error_count == 0) {
semantic_check(ast)
}
// Merge parse errors
var _mi = 0
if (length(errors) > 0) {
if (ast.errors != null) {
_mi = 0
while (_mi < length(errors)) {
push(ast.errors, errors[_mi])
_mi = _mi + 1
}
} else {
ast.errors = errors
}
}
return ast
}
return parse