// ============================================================ // Section 1: Inline Tokenizer (from tokenize.ce) // ============================================================ var src = args[0] var filename = length(args) > 1 ? args[1] : "" // Convert to codepoint array var _src_len = length(src) var cp = [] var _i = 0 while (_i < _src_len) { push(cp, codepoint(src[_i])) _i = _i + 1 } var pos = 0 var row = 0 var col = 0 var tokens = [] // Codepoint constants def CP_LF = 10 def CP_CR = 13 def CP_TAB = 9 def CP_SPACE = 32 def CP_BANG = 33 def CP_DQUOTE = 34 def CP_HASH = 35 def CP_DOLLAR = 36 def CP_PERCENT = 37 def CP_AMP = 38 def CP_SQUOTE = 39 def CP_LPAREN = 40 def CP_RPAREN = 41 def CP_STAR = 42 def CP_PLUS = 43 def CP_COMMA = 44 def CP_MINUS = 45 def CP_DOT = 46 def CP_SLASH = 47 def CP_0 = 48 def CP_1 = 49 def CP_7 = 55 def CP_9 = 57 def CP_COLON = 58 def CP_SEMI = 59 def CP_LT = 60 def CP_EQ = 61 def CP_GT = 62 def CP_QMARK = 63 def CP_AT = 64 def CP_A = 65 def CP_B = 66 def CP_E = 69 def CP_F = 70 def CP_O = 79 def CP_X = 88 def CP_Z = 90 def CP_LBRACKET = 91 def CP_BSLASH = 92 def CP_RBRACKET = 93 def CP_CARET = 94 def CP_UNDERSCORE = 95 def CP_BACKTICK = 96 def CP_a = 97 def CP_b = 98 def CP_e = 101 def CP_f = 102 def CP_n = 110 def CP_o = 111 def CP_r = 114 def CP_t = 116 def CP_x = 120 def CP_z = 122 def CP_LBRACE = 123 def CP_PIPE = 124 def CP_RBRACE = 125 def CP_TILDE = 126 var keywords = { if: "if", in: "in", do: "do", go: "go", var: "var", def: "def", for: "for", else: "else", this: "this", null: "null", true: "true", false: "false", while: "while", break: "break", return: "return", delete: "delete", disrupt: "disrupt", function: "function", continue: "continue", disruption: "disruption" } function pk() { if (pos >= _src_len) return -1 return cp[pos] } function pk_at(n) { var idx = pos + n if (idx >= _src_len) return -1 return cp[idx] } function adv() { var c = cp[pos] pos = pos + 1 if (c == CP_LF) { row = row + 1 col = 0 } else { col = col + 1 } return c } function is_digit(c) { return c >= CP_0 && c <= CP_9 } function is_hex(c) { return (c >= CP_0 && c <= CP_9) || (c >= CP_a && c <= CP_f) || (c >= CP_A && c <= CP_F) } function is_alpha(c) { return (c >= CP_a && c <= CP_z) || (c >= CP_A && c <= CP_Z) } function is_alnum(c) { return is_alpha(c) || is_digit(c) } function is_ident_start(c) { return is_alpha(c) || c == CP_UNDERSCORE || c == CP_DOLLAR } function is_ident_char(c) { return is_alnum(c) || c == CP_UNDERSCORE || c == CP_DOLLAR || c == CP_QMARK || c == CP_BANG } function substr(start, end) { var s = "" var i = start while (i < end) { s = s + character(cp[i]) i = i + 1 } return s } function read_string(quote_cp) { var start = pos var start_row = row var start_col = col var value = "" var esc = 0 adv() while (pos < _src_len && pk() != quote_cp) { if (pk() == CP_BSLASH) { adv() esc = adv() if (esc == CP_n) { value = value + "\n" } else if (esc == CP_t) { value = value + "\t" } else if (esc == CP_r) { value = value + "\r" } else if (esc == CP_BSLASH) { value = value + "\\" } else if (esc == CP_SQUOTE) { value = value + "'" } else if (esc == CP_DQUOTE) { value = value + "\"" } else if (esc == CP_0) { value = value + character(0) } else if (esc == CP_BACKTICK) { value = value + "`" } else { value = value + character(esc) } } else { value = value + character(adv()) } } if (pos < _src_len) adv() push(tokens, { kind: "text", at: start, from_row: start_row, from_column: start_col, to_row: row, to_column: col, value: value }) } function read_template() { var start = pos var start_row = row var start_col = col var value = "" var esc = 0 var depth = 0 var tc = 0 var q = 0 adv() while (pos < _src_len && pk() != CP_BACKTICK) { if (pk() == CP_BSLASH && pos + 1 < _src_len) { adv() esc = adv() if (esc == CP_n) { value = value + "\n" } else if (esc == CP_t) { value = value + "\t" } else if (esc == CP_r) { value = value + "\r" } else if (esc == CP_BSLASH) { value = value + "\\" } else if (esc == CP_BACKTICK) { value = value + "`" } else if (esc == CP_DOLLAR) { value = value + "$" } else if (esc == CP_0) { value = value + character(0) } else { value = value + character(esc) } } else if (pk() == CP_DOLLAR && pos + 1 < _src_len && pk_at(1) == CP_LBRACE) { adv() adv() depth = 1 while (pos < _src_len && depth > 0) { tc = pk() if (tc == CP_LBRACE) { depth = depth + 1; adv() } else if (tc == CP_RBRACE) { depth = depth - 1; adv() } else if (tc == CP_SQUOTE || tc == CP_DQUOTE || tc == CP_BACKTICK) { q = adv() while (pos < _src_len && pk() != q) { if (pk() == CP_BSLASH && pos + 1 < _src_len) adv() adv() } if (pos < _src_len) adv() } else { adv() } } } else { value = value + character(adv()) } } if (pos < _src_len) adv() push(tokens, { kind: "text", at: start, from_row: start_row, from_column: start_col, to_row: row, to_column: col, value: value }) } function read_number() { var start = pos var start_row = row var start_col = col var raw = "" if (pk() == CP_0 && (pk_at(1) == CP_x || pk_at(1) == CP_X)) { adv(); adv() while (pos < _src_len && (is_hex(pk()) || pk() == CP_UNDERSCORE)) adv() } else if (pk() == CP_0 && (pk_at(1) == CP_b || pk_at(1) == CP_B)) { adv(); adv() while (pos < _src_len && (pk() == CP_0 || pk() == CP_1 || pk() == CP_UNDERSCORE)) adv() } else if (pk() == CP_0 && (pk_at(1) == CP_o || pk_at(1) == CP_O)) { adv(); adv() while (pos < _src_len && pk() >= CP_0 && pk() <= CP_7) adv() } else { while (pos < _src_len && (is_digit(pk()) || pk() == CP_UNDERSCORE)) adv() if (pos < _src_len && pk() == CP_DOT) { adv() while (pos < _src_len && (is_digit(pk()) || pk() == CP_UNDERSCORE)) adv() } if (pos < _src_len && (pk() == CP_e || pk() == CP_E)) { adv() if (pos < _src_len && (pk() == CP_PLUS || pk() == CP_MINUS)) adv() while (pos < _src_len && is_digit(pk())) adv() } } raw = substr(start, pos) push(tokens, { kind: "number", at: start, from_row: start_row, from_column: start_col, to_row: row, to_column: col, value: raw, number: number(raw) }) } function read_name() { var start = pos var start_row = row var start_col = col var name = "" var kw = null while (pos < _src_len && is_ident_char(pk())) adv() name = substr(start, pos) kw = keywords[name] if (kw != null) { push(tokens, { kind: kw, at: start, from_row: start_row, from_column: start_col, to_row: row, to_column: col }) } else { push(tokens, { kind: "name", at: start, from_row: start_row, from_column: start_col, to_row: row, to_column: col, value: name }) } } function read_comment() { var start = pos var start_row = row var start_col = col var raw = "" if (pk_at(1) == CP_SLASH) { while (pos < _src_len && pk() != CP_LF && pk() != CP_CR) adv() } else { adv(); adv() while (pos < _src_len) { if (pk() == CP_STAR && pk_at(1) == CP_SLASH) { adv(); adv() break } adv() } } raw = substr(start, pos) push(tokens, { kind: "comment", at: start, from_row: start_row, from_column: start_col, to_row: row, to_column: col, value: raw }) } function emit_op(kind, count) { var start = pos var start_row = row var start_col = col var i = 0 while (i < count) { adv(); i = i + 1 } push(tokens, { kind: kind, at: start, from_row: start_row, from_column: start_col, to_row: row, to_column: col }) } function emit_ident(count) { var start = pos var start_row = row var start_col = col var val = "" var i = 0 while (i < count) { val = val + character(adv()); i = i + 1 } push(tokens, { kind: "name", at: start, from_row: start_row, from_column: start_col, to_row: row, to_column: col, value: val }) } function tokenize_one() { var c = pk() var start = 0 var start_row = 0 var start_col = 0 var raw = "" if (c == -1) return false if (c == CP_LF) { start = pos; start_row = row; start_col = col adv() push(tokens, { kind: "newline", at: start, from_row: start_row, from_column: start_col, to_row: row, to_column: col, value: "\n" }) return true } if (c == CP_CR) { start = pos; start_row = row; start_col = col adv() if (pos < _src_len && pk() == CP_LF) adv() push(tokens, { kind: "newline", at: start, from_row: start_row, from_column: start_col, to_row: row, to_column: col, value: "\n" }) return true } if (c == CP_SPACE || c == CP_TAB) { start = pos; start_row = row; start_col = col while (pos < _src_len && (pk() == CP_SPACE || pk() == CP_TAB)) adv() raw = substr(start, pos) push(tokens, { kind: "space", at: start, from_row: start_row, from_column: start_col, to_row: row, to_column: col, value: raw }) return true } if (c == CP_SQUOTE || c == CP_DQUOTE) { read_string(c); return true } if (c == CP_BACKTICK) { read_template(); return true } if (is_digit(c)) { read_number(); return true } if (c == CP_DOT && is_digit(pk_at(1))) { read_number(); return true } if (is_ident_start(c)) { read_name(); return true } if (c == CP_SLASH) { if (pk_at(1) == CP_SLASH || pk_at(1) == CP_STAR) { read_comment(); return true } if (pk_at(1) == CP_EQ) { emit_op("/=", 2); return true } if (pk_at(1) == CP_BANG) { emit_ident(2); return true } emit_op("/", 1); return true } if (c == CP_STAR) { if (pk_at(1) == CP_STAR) { if (pk_at(2) == CP_BANG) { emit_ident(3); return true } if (pk_at(2) == CP_EQ) { emit_op("**=", 3); return true } emit_op("**", 2); return true } if (pk_at(1) == CP_EQ) { emit_op("*=", 2); return true } if (pk_at(1) == CP_BANG) { emit_ident(2); return true } emit_op("*", 1); return true } if (c == CP_PERCENT) { if (pk_at(1) == CP_EQ) { emit_op("%=", 2); return true } if (pk_at(1) == CP_BANG) { emit_ident(2); return true } emit_op("%", 1); return true } if (c == CP_PLUS) { if (pk_at(1) == CP_EQ) { emit_op("+=", 2); return true } if (pk_at(1) == CP_PLUS) { emit_op("++", 2); return true } if (pk_at(1) == CP_BANG) { emit_ident(2); return true } emit_op("+", 1); return true } if (c == CP_MINUS) { if (pk_at(1) == CP_EQ) { emit_op("-=", 2); return true } if (pk_at(1) == CP_MINUS) { emit_op("--", 2); return true } if (pk_at(1) == CP_BANG) { emit_ident(2); return true } emit_op("-", 1); return true } if (c == CP_LT) { if (pk_at(1) == CP_EQ && pk_at(2) == CP_BANG) { emit_ident(3); return true } if (pk_at(1) == CP_EQ) { emit_op("<=", 2); return true } if (pk_at(1) == CP_LT) { if (pk_at(2) == CP_BANG) { emit_ident(3); return true } if (pk_at(2) == CP_EQ) { emit_op("<<=", 3); return true } emit_op("<<", 2); return true } if (pk_at(1) == CP_BANG) { emit_ident(2); return true } emit_op("<", 1); return true } if (c == CP_GT) { if (pk_at(1) == CP_EQ && pk_at(2) == CP_BANG) { emit_ident(3); return true } if (pk_at(1) == CP_EQ) { emit_op(">=", 2); return true } if (pk_at(1) == CP_GT) { if (pk_at(2) == CP_GT) { if (pk_at(3) == CP_BANG) { emit_ident(4); return true } if (pk_at(3) == CP_EQ) { emit_op(">>>=", 4); return true } emit_op(">>>", 3); return true } if (pk_at(2) == CP_BANG) { emit_ident(3); return true } if (pk_at(2) == CP_EQ) { emit_op(">>=", 3); return true } emit_op(">>", 2); return true } if (pk_at(1) == CP_BANG) { emit_ident(2); return true } emit_op(">", 1); return true } if (c == CP_EQ) { if (pk_at(1) == CP_EQ) { if (pk_at(2) == CP_EQ) { emit_op("===", 3); return true } emit_op("==", 2); return true } if (pk_at(1) == CP_GT) { emit_op("=>", 2); return true } if (pk_at(1) == CP_BANG) { emit_ident(2); return true } emit_op("=", 1); return true } if (c == CP_BANG) { if (pk_at(1) == CP_EQ) { if (pk_at(2) == CP_BANG) { emit_ident(3); return true } if (pk_at(2) == CP_EQ) { emit_op("!==", 3); return true } emit_op("!=", 2); return true } emit_op("!", 1); return true } if (c == CP_AMP) { if (pk_at(1) == CP_AMP) { if (pk_at(2) == CP_BANG) { emit_ident(3); return true } if (pk_at(2) == CP_EQ) { emit_op("&&=", 3); return true } emit_op("&&", 2); return true } if (pk_at(1) == CP_EQ) { emit_op("&=", 2); return true } if (pk_at(1) == CP_BANG) { emit_ident(2); return true } emit_op("&", 1); return true } if (c == CP_PIPE) { if (pk_at(1) == CP_PIPE) { if (pk_at(2) == CP_BANG) { emit_ident(3); return true } if (pk_at(2) == CP_EQ) { emit_op("||=", 3); return true } emit_op("||", 2); return true } if (pk_at(1) == CP_EQ) { emit_op("|=", 2); return true } if (pk_at(1) == CP_BANG) { emit_ident(2); return true } emit_op("|", 1); return true } if (c == CP_CARET) { if (pk_at(1) == CP_EQ) { emit_op("^=", 2); return true } if (pk_at(1) == CP_BANG) { emit_ident(2); return true } emit_op("^", 1); return true } if (c == CP_LBRACKET) { if (pk_at(1) == CP_RBRACKET && pk_at(2) == CP_BANG) { emit_ident(3); return true } emit_op("[", 1); return true } if (c == CP_TILDE) { if (pk_at(1) == CP_BANG) { emit_ident(2); return true } emit_op("~", 1); return true } emit_op(character(c), 1) return true } // Tokenize while (pos < _src_len) { tokenize_one() } push(tokens, { kind: "eof", at: pos, from_row: row, from_column: col, to_row: row, to_column: col }) // ============================================================ // Section 2: Parser Cursor // ============================================================ var cursor = 0 var tok = null var got_lf = false var prev_tok = null function advance() { 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] } function peek_ahead(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] } function init_cursor() { cursor = -1 advance() } // ============================================================ // Section 3: AST Helpers // ============================================================ var errors = [] var error_count = 0 var function_nr = 1 function ast_node(kind, token) { return { kind: kind, at: token.at, from_row: token.from_row, from_column: token.from_column } } function ast_node_end(node) { node.to_row = prev_tok.to_row node.to_column = prev_tok.to_column return node } function parse_error(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 }) } function is_keyword(kind) { return kind == "if" || kind == "in" || kind == "do" || kind == "go" || kind == "var" || kind == "def" || kind == "for" || kind == "else" || kind == "this" || kind == "null" || kind == "true" || kind == "false" || kind == "while" || kind == "break" || kind == "return" || kind == "delete" || kind == "disrupt" || kind == "function" || kind == "continue" || kind == "disruption" } // ============================================================ // Section 4: 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 function is_arrow_function() { // Check if ( ... ) => pattern 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 } // Skip whitespace/newline/comment tokens 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 == "=>" } function parse_primary() { 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_str = "" var flags = "" 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") { node = ast_node("text", start) node.value = tok.value advance() ast_node_end(node) return node } if (k == "name") { // Check for single-param arrow: 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 == "(") { // Method shorthand 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_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") 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") } fn.function_nr = function_nr function_nr = function_nr + 1 ast_node_end(fn) pair.right = fn } else if (!(is_ident && (tok.kind == "," || tok.kind == "}"))) { parse_error(tok, "expected ':' after property name") } push(list, pair) if (tok.kind == ",") advance() 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 == "/") { // Regex literal node = ast_node("regexp", start) // Re-scan from token position to parse regex rpos = tok.at + 1 pattern_str = "" flags = "" while (rpos < _src_len && cp[rpos] != CP_SLASH) { if (cp[rpos] == CP_BSLASH && rpos + 1 < _src_len) { pattern_str = pattern_str + character(cp[rpos]) + character(cp[rpos + 1]) rpos = rpos + 2 } else { pattern_str = pattern_str + character(cp[rpos]) rpos = rpos + 1 } } if (rpos < _src_len) rpos = rpos + 1 while (rpos < _src_len && is_alpha(cp[rpos])) { flags = flags + character(cp[rpos]) rpos = rpos + 1 } node.pattern = pattern_str if (length(flags) > 0) node.flags = flags advance() ast_node_end(node) return node } // Error if (k == "eof") { parse_error(start, "unexpected end of input") } else { parse_error(start, "unexpected token where expression expected") } advance() return null } function parse_postfix() { var node = parse_primary() var start = null var new_node = null var index = null var arg = null var args_list = 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 } function parse_unary() { var start = tok var node = null var expr = null var k = tok.kind 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 == "++") { advance() node = ast_node("++", start) node.expression = parse_unary() node.postfix = false ast_node_end(node) return node } if (k == "--") { advance() node = ast_node("--", start) node.expression = parse_unary() node.postfix = false 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 } function parse_binary(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 } function parse_ternary() { 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_expr() if (tok.kind == ":") advance() else parse_error(tok, "expected ':' in ternary expression") else_expr = parse_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", "+=": "+=", "-=": "-=", "*=": "*=", "/=": "/=", "%=": "%=", "<<=": "<<=", ">>=": ">>=", ">>>=": ">>>=", "&=": "&=", "^=": "^=", "|=": "|=", "**=": "**=", "&&=": "&&=", "||=": "||=" } 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 if (left_node == null) return null start = tok kind = assign_ops[tok.kind] if (kind == null) return left_node // Validate assignment target 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() node = ast_node(kind, start) node.left = left_node node.right = right_node // Check push/pop bracket syntax 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 } // ============================================================ // Section 5: Statement Parsing // ============================================================ var in_disruption = 0 function expect_semi() { if (tok.kind == ";") { advance(); return null } if (tok.kind == "eof" || tok.kind == "}" || got_lf || tok.kind == "else") return null parse_error(tok, "expecting ';'") } function sync_to_statement() { 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 dup = false var j = 0 var old_dis = 0 if (in_disruption) { parse_error(tok, "cannot define function inside disruption clause") } advance() // skip 'function' // Optional name if (tok.kind == "name") { node.name = tok.value advance() } // Parameters 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 // Check duplicate pname = tok.value dup = false j = 0 while (j < length(prev_names)) { if (prev_names[j] == pname) { dup = true; break } j = j + 1 } if (dup) 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") // Body 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") } // Disruption clause 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") } } node.function_nr = function_nr function_nr = function_nr + 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 dup = false var j = 0 node.arrow = true if (in_disruption) { parse_error(tok, "cannot define function inside disruption clause") } node.list = params if (tok.kind == "name") { // Single param without parens 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 dup = false j = 0 while (j < length(prev_names)) { if (prev_names[j] == pname) { dup = true; break } j = j + 1 } if (dup) 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") // Arrow token if (tok.kind != "=>") { parse_error(tok, "expected '=>' in arrow function") } else { advance() } // Body if (tok.kind == "{") { advance() stmts = parse_block_statements() node.statements = stmts if (tok.kind == "}") advance() } else { // Expression body stmts = [] ret = ast_node("return", tok) expr = parse_assign_expr() ret.expression = expr ast_node_end(ret) push(stmts, ret) node.statements = stmts } node.function_nr = function_nr function_nr = function_nr + 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 if (k == "{") { 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") { 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 + "'") } 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 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") { elif = parse_statement() if (elif != null) push(else_ifs, elif) } else { else_stmts = [] node.else = else_stmts body = parse_statement() if (body != null) push(else_stmts, body) } } 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 body = parse_statement() if (body != null) push(stmts, body) ast_node_end(node) return node } if (k == "do") { node = ast_node("do", start) advance() stmts = [] node.statements = stmts body = parse_statement() if (body != null) push(stmts, body) 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") { 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 body = parse_statement() if (body != null) push(stmts, body) 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") { // Check for labeled statement 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 } } // Expression statement 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 } // ============================================================ // Section 6: Program // ============================================================ function parse_program() { 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 } // ============================================================ // Section 7: Semantic Analysis // ============================================================ var sem_errors = [] var scopes_array = [] var intrinsics = [] var block_var_counter = 0 function sem_error(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) } function make_scope(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, block_depth: opts.bdepth != null ? opts.bdepth : 0 } } function sem_add_var(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, nr_uses: 0, closure: 0 }) } function sem_lookup_var(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 } function sem_find_var(scope, name) { var r = sem_lookup_var(scope, name) return r.v } function sem_in_loop(scope) { var s = scope while (s != null) { if (s.in_loop) return true s = s.parent } return false } function sem_add_intrinsic(name) { var i = 0 while (i < length(intrinsics)) { if (intrinsics[i] == name) return null i = i + 1 } 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 } function is_functino_name(name) { return functino_names[name] == true } function sem_propagate_block_vars(parent, block) { 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 }) i = i + 1 } } function sem_build_scope_record(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 } 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 function sem_predeclare_vars(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 } } function sem_check_assign_target(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 if (r.v.scope_name != null) left_node.scope_name = r.v.scope_name } 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) } } } 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 // Assignment operators if (kind == "assign" || kind == "+=" || kind == "-=" || kind == "*=" || kind == "/=" || kind == "%=" || kind == "<<=" || kind == ">>=" || kind == ">>>=" || kind == "&=" || kind == "^=" || kind == "|=" || kind == "**=" || kind == "&&=" || kind == "||=") { sem_check_assign_target(scope, expr.left) sem_check_expr(scope, expr.right) return null } // Increment/decrement 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 if (r.v.scope_name != null) operand.scope_name = r.v.scope_name } else { operand.level = -1 } } } return null } // Binary ops 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 == "." || kind == "[") { sem_check_expr(scope, expr.left) sem_check_expr(scope, expr.right) return null } // Ternary if (kind == "then") { sem_check_expr(scope, expr.expression) sem_check_expr(scope, expr.then) sem_check_expr(scope, expr.else) return null } // Call 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 } // Unary ops if (kind == "!" || kind == "~" || kind == "delete" || kind == "-unary" || kind == "+unary") { sem_check_expr(scope, expr.expression) return null } // Array literal if (kind == "array") { i = 0 while (i < length(expr.list)) { sem_check_expr(scope, expr.list[i]) i = i + 1 } return null } // Record literal 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 } // Function expression if (kind == "function") { 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}) expr.outer = scope.function_nr // Add params 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 } // Pre-register declarations 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 } } // Disruption if (expr.disruption != null) { i = 0 while (i < length(expr.disruption)) { sem_check_stmt(fn_scope, expr.disruption[i]) i = i + 1 } } // Build scope record 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 } // Template literal if (kind == "text literal") { i = 0 while (i < length(expr.list)) { sem_check_expr(scope, expr.list[i]) i = i + 1 } return null } // Name 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 if (r.v.scope_name != null) expr.scope_name = r.v.scope_name } else { expr.level = -1 sem_add_intrinsic(name) } } return null } // Leaf nodes: number, text, regexp, null, true, false, this } 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 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 var def_val = null var sr = 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 || scope.block_depth > 0) { 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 } 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}) 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 } if (kind == "call") { sem_check_expr(scope, stmt.expression) return null } if (kind == "if") { sem_check_expr(scope, stmt.expression) // then 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]) i = i + 1 } sem_propagate_block_vars(scope, then_scope) // else-if list 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]) i = i + 1 } sem_propagate_block_vars(scope, list_scope) // else 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]) 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}) 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) return null } if (kind == "do") { do_scope = make_scope(scope, scope.function_nr, {in_loop: true, bdepth: scope.block_depth + 1}) 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_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}) 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_block_vars(scope, for_scope) return null } if (kind == "return" || kind == "go") { sem_check_expr(scope, stmt.expression) 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") { 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]) i = i + 1 } sem_propagate_block_vars(scope, blk_scope) return null } if (kind == "label") { sem_check_stmt(scope, stmt.statement) return null } if (kind == "function") { name = stmt.name if (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}) 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 } } function semantic_check(ast) { var global_scope = make_scope(null, 0, {is_func: true}) var i = 0 var stmt = null var name = null // Pre-register top-level function names 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 } // Check all statements i = 0 while (i < length(ast.statements)) { sem_check_stmt(global_scope, ast.statements[i]) i = i + 1 } // Check function bodies i = 0 while (i < length(ast.functions)) { sem_check_stmt(global_scope, ast.functions[i]) i = i + 1 } // Build program scope record and prepend var sr = sem_build_scope_record(global_scope) var new_scopes = [sr.rec] i = 0 while (i < length(scopes_array)) { push(new_scopes, scopes_array[i]) i = i + 1 } scopes_array = new_scopes // Attach to AST ast.scopes = scopes_array ast.intrinsics = intrinsics if (length(sem_errors) > 0) { ast.errors = sem_errors } } // ============================================================ // Section 8: 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 } } print(json.encode(ast))