def CP_SLASH = 47 def CP_BSLASH = 92 var is_alpha = function(c) { return (c >= 65 && c <= 90) || (c >= 97 && c <= 122) } var parse = function(tokens, src, filename) { var _src_len = length(src) var cp = [] var _i = 0 while (_i < _src_len) { push(cp, codepoint(src[_i])) _i = _i + 1 } // ============================================================ // Parser Cursor // ============================================================ var cursor = 0 var tok = null var got_lf = false var prev_tok = null 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 function_nr = 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 is_keyword = function(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" } // ============================================================ // 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_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") { 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_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 == "/") { node = ast_node("regexp", start) 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 // 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 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 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 } 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_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 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 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 dup = false var j = 0 var old_dis = 0 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 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") 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") } } 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") { 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") if (tok.kind != "=>") { parse_error(tok, "expected '=>' in arrow function") } else { advance() } 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 } 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") { 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 block_var_counter = 0 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, block_depth: opts.bdepth != null ? opts.bdepth : 0 } } var sem_add_var = function(scope, name, make_opts) { push(scope.vars, { name: name, scope_name: null, is_const: make_opts.is_const == true, make: make_opts.make, function_nr: make_opts.fn_nr, 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_add_intrinsic = function(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 } var is_functino_name = function(name) { return functino_names[name] == true } var sem_propagate_block_vars = function(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 } } 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 } 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 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 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 } 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 } 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 } 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") { 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 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 if (r.v.scope_name != null) expr.scope_name = r.v.scope_name } else { expr.level = -1 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 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_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) 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) 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 } } 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