;; while (read(player) != ".") endwhile I M P O R T A N T ================= The following code cannot be used as is. You will need to rewrite functionality that is not present in your server/core. The most straight-forward target (other than Stunt/Improvise) is a server/core that provides a map datatype and anonymous objects. Installation in my server uses the following object numbers: #36819 -> MOOcode Experimental Language Package #36820 -> Changelog #36821 -> Dictionary #36822 -> MOOcode Compiler #38128 -> Syntax Tree Pretty Printer #37644 -> Tokenizer Prototype #37645 -> Parser Prototype #37648 -> Symbol Prototype #37649 -> Literal Prototype #37650 -> Statement Prototype #37651 -> Operator Prototype #37652 -> Control Flow Statement Prototype #37653 -> Assignment Operator Prototype #38140 -> Compound Assignment Operator Prototype #38123 -> Prefix Operator Prototype #37654 -> Infix Operator Prototype #37655 -> Name Prototype #37656 -> Bracket Operator Prototype #37657 -> Brace Operator Prototype #37658 -> If Statement Prototype #38119 -> For Statement Prototype #38120 -> Loop Statement Prototype #38126 -> Fork Statement Prototype #38127 -> Try Statement Prototype #37659 -> Invocation Operator Prototype #37660 -> Verb Selector Operator Prototype #37661 -> Property Selector Operator Prototype #38124 -> Error Catching Operator Prototype #38122 -> Positional Symbol Prototype #38141 -> From Statement Prototype #37662 -> Utilities #36823 -> MOOcode Experimental Language Package Tests #36824 -> MOOcode Compiler Tests #37646 -> Tokenizer Tests #37647 -> Parser Tests . ; /* BASE */ ; parent($plastic.tokenizer_proto) @program _:_ensure_prototype as application/x-moocode (typeof(this) == OBJ) || raise(E_INVARG, "Callable on prototypes only"); . @program _:_ensure_instance as application/x-moocode (typeof(this) == ANON) || raise(E_INVARG, "Callable on instances only"); . ; /* COMPILER */ @program $plastic.compiler:_lookup as application/x-moocode $private(); this:_ensure_instance(); {name} = args; if (`value = this.variable_map[name] ! E_RANGE') return value; elseif (name in this.variable_map || name in this.reserved_names) value = name; while (value in this.variable_map || value in this.reserved_names) value = tostr("_", value); endwhile this.variable_map[name] = value; return value; else value = name; this.variable_map[name] = value; return value; endif . @program $plastic.compiler:_generate as application/x-moocode $private(); this:_ensure_instance(); {name} = args; if (`value = this.variable_map[name] ! E_RANGE') return value; else value = tostr("_", random()); while (value in this.variable_map || value in this.reserved_names) value = tostr("_", random()); endwhile this.variable_map[name] = value; return value; endif . @program $plastic.compiler:compile as application/x-moocode this:_ensure_prototype(); {source, ?options = []} = args; tokenizer = this.plastic.tokenizer_proto:create(source); parser = this.plastic.parser_proto:create(tokenizer); compiler = create(this, 1); try statements = parser:statements(); except ex (ANY) return {0, {tostr("Line ", ex[3].tokenizer.row, ": ", ex[2])}}; endtry source = {}; for statement in (statements) if (statement.type != "statement") source = {@source, tostr(compiler:p(statement), ";")}; else source = {@source, @compiler:p(statement)}; endif endfor return {1, source}; . @program $plastic.compiler:p as application/x-moocode this:_ensure_instance(); {statement} = args; ticks_left() < 10000 || seconds_left() < 2 && suspend(0); if (statement.type == "variable") return this:_lookup(statement.value); elseif (statement.type == "unique") return this:_generate(statement.value); elseif (isa(statement, this.plastic.sign_operator_proto)) if (statement.type == "unary") return tostr(statement.value == "-" ? "-" | "", this:p(statement.first)); else return tostr("(", this:p(statement.first), " ", statement.value, " ", this:p(statement.second), ")"); endif elseif (isa(statement, this.plastic.control_flow_statement_proto)) if ((first = statement.first) != 0) return {tostr(statement.id, " " , this:p(first), ";")}; else return {tostr(statement.id, ";")}; endif elseif (isa(statement, this.plastic.if_statement_proto)) value = statement.value; code = {tostr("if (", this:p(value[1]), ")")}; for s in (value[2]) if (respond_to(s, "std")) code = {@code, @this:p(s)}; else code = {@code, this:p(s) + ";"}; endif endfor i = 3; while (length(value) >= i && typeof(value[i]) != LIST) code = {@code, tostr("elseif (", this:p(value[i]), ")")}; i = i + 1; for s in (value[i]) if (respond_to(s, "std")) code = {@code, @this:p(s)}; else code = {@code, this:p(s) + ";"}; endif endfor i = i + 1; endwhile if (length(value) == i) code = {@code, "else"}; for s in (value[i]) if (respond_to(s, "std")) code = {@code, @this:p(s)}; else code = {@code, this:p(s) + ";"}; endif endfor endif code = {@code, "endif"}; return code; elseif (isa(statement, this.plastic.for_statement_proto)) value = statement.value; if (statement.subtype == "range") code = {tostr("for ", this:p(value[1]), " in [", this:p(value[2]), "..", this:p(value[3]), "]")}; statements = value[4]; elseif (length(value) == 4) code = {tostr("for ", this:p(value[1]), ", ", this:p(value[2]), " in (", this:p(value[3]), ")")}; statements = value[4]; else code = {tostr("for ", this:p(value[1]), " in (", this:p(value[2]), ")")}; statements = value[3]; endif for s in (statements) if (respond_to(s, "std")) code = {@code, @this:p(s)}; else code = {@code, this:p(s) + ";"}; endif endfor code = {@code, "endfor"}; return code; elseif (isa(statement, this.plastic.loop_statement_proto)) value = statement.value; i = 0; if (length(value) > 2) prefix = tostr("while ", this:p(value[i = i + 1])); else prefix = tostr("while"); endif if (statement.id == "while") code = {tostr(prefix, " (", this:p(value[i = i + 1]), ")")}; else code = {tostr(prefix, " (!(", this:p(value[i = i + 1]), "))")}; endif for s in (value[i = i + 1]) if (respond_to(s, "std")) code = {@code, @this:p(s)}; else code = {@code, this:p(s) + ";"}; endif endfor code = {@code, "endwhile"}; return code; elseif (isa(statement, this.plastic.fork_statement_proto)) value = statement.value; i = 0; if (length(value) > 2) code = {tostr("fork ", this:p(value[i = i + 1]), " (", this:p(value[i = i + 1]), ")")}; else code = {tostr("fork", " (", this:p(value[i = i + 1]), ")")}; endif for s in (value[i = i + 1]) if (respond_to(s, "std")) code = {@code, @this:p(s)}; else code = {@code, this:p(s) + ";"}; endif endfor code = {@code, "endfork"}; return code; elseif (isa(statement, this.plastic.try_statement_proto)) value = statement.value; code = {"try"}; for s in (value[1]) if (respond_to(s, "std")) code = {@code, @this:p(s)}; else code = {@code, this:p(s) + ";"}; endif endfor if (statement.subtype == "finally") code = {@code, "finally"}; for s in (value[2]) if (respond_to(s, "std")) code = {@code, @this:p(s)}; else code = {@code, this:p(s) + ";"}; endif endfor else for value in (value[2..$]) if (length(value) == 3) x = {}; for s in (value[2]) x = {@x, this:p(s)}; endfor code = {@code, tostr("except ", this:p(value[1]), " (", x:join(", "), ")")}; statements = value[3]; else x = {}; for s in (value[1]) x = {@x, this:p(s)}; endfor code = {@code, tostr("except (", x:join(", "), ")")}; statements = value[2]; endif for s in (statements) if (respond_to(s, "std")) code = {@code, @this:p(s)}; else code = {@code, this:p(s) + ";"}; endif endfor endfor endif code = {@code, "endtry"}; return code; elseif (isa(statement, this.plastic.assignment_operator_proto)) if (statement.first.type == "pattern") res = "{"; rest = 0; for v in (statement.first.value) if (v.type == "unary") v = tostr("@", this:p(v.first)); elseif (v.type == "binary") v = tostr("?", this:p(v.first), " = ", this:p(v.second)); else v = this:p(v); endif res = tostr(res, (rest ? ", " | ""), v); rest = 1; endfor res = tostr(res, "}"); return tostr("(", res, " ", statement.value, " ", this:p(statement.second), ")"); else return tostr("(", this:p(statement.first), " ", statement.value, " ", this:p(statement.second), ")"); endif elseif (isa(statement, this.plastic.bracket_operator_proto)) if (statement.type == "ternary") return tostr("(", this:p(statement.first), "[", this:p(statement.second), "..", this:p(statement.third), "])"); elseif (statement.type == "binary") return tostr("(", this:p(statement.first), "[", this:p(statement.second), "])"); else res = "["; first = 1; for v in (statement.value) ticks_left() < 10000 || seconds_left() < 2 && suspend(0); res = tostr(res, (first ? "" | ", "), this:p(v[1]), " -> ", this:p(v[2])); first = 0; endfor res = tostr(res, "]"); return res; return {res}; endif elseif (isa(statement, this.plastic.brace_operator_proto)) res = "{"; first = 1; for v in (statement.value) ticks_left() < 10000 || seconds_left() < 2 && suspend(0); res = tostr(res, (first ? "" | ", "), this:p(v)); first = 0; endfor res = tostr(res, "}"); return res; elseif (isa(statement, this.plastic.invocation_operator_proto)) if (statement.type == "ternary") a = {}; for v in (statement.third) a = {@a, this:p(v)}; endfor if (statement.second.type == "identifier") return tostr(this:p(statement.first), ":", this:p(statement.second), "(", a:join(", "), ")"); else return tostr(this:p(statement.first), ":(", this:p(statement.second), ")(", a:join(", "), ")"); endif elseif (statement.type == "binary") a = {}; for v in (statement.second) a = {@a, this:p(v)}; endfor return tostr(this:p(statement.first), "(", a:join(", "), ")"); else return tostr(this:p(statement.first)); endif elseif (isa(statement, this.plastic.property_selector_operator_proto)) if (statement.second.type == "identifier") return tostr(this:p(statement.first), ".", this:p(statement.second)); else return tostr(this:p(statement.first), ".(", this:p(statement.second) + ")"); endif elseif (isa(statement, this.plastic.error_catching_operator_proto)) if (statement.type == "unary") return tostr(statement.value, this:p(statement.first)); endif x = {}; for s in (statement.second) x = {@x, this:p(s)}; endfor second = x:join(", "); if (statement.type == "ternary") return tostr("`", this:p(statement.first), " ! ", second, " => ", this:p(statement.third), "'"); else return tostr("`", this:p(statement.first), " ! ", second, "'"); endif elseif (isa(statement, this.plastic.literal_proto)) return toliteral(statement.value); elseif (isa(statement, this.plastic.positional_symbol_proto)) return statement.value; elseif (isa(statement, this.plastic.prefix_operator_proto)) return tostr(statement.value, this:p(statement.first)); elseif (isa(statement, this.plastic.infix_operator_proto)) value = statement.value; value = (value != "**") ? value | "^"; return tostr("(", this:p(statement.first), " ", value, " ", this:p(statement.second), ")"); elseif (isa(statement, this.plastic.traditional_ternary_operator_proto)) return tostr("(", this:p(statement.first), " ? ", this:p(statement.second), " | ", this:p(statement.third), ")"); elseif (isa(statement, this.plastic.name_proto)) return statement.value; else raise(E_INVARG); endif . ; /* PRINTER */ @program $plastic.printer:_print as application/x-moocode {statement, ?indent = ""} = args; if (typeof(statement) == LIST) result = {tostr(indent, "-")}; for item in (statement) result = {@result, @this:_print(item, indent + " ")}; endfor return result; endif if (`typeof(statement.value) == LIST ! ANY') result = {tostr(indent, statement.id, " : ", statement.type)}; for value in (statement.value) result = {@result, @this:_print(value, indent + " ")}; endfor else result = {tostr(indent, typeof(statement.value) == ERR ? toliteral(statement.value) | statement.value, " : ", statement.type)}; for prop in ({"first", "second", "third"}) if (`value = statement.(prop) ! E_PROPNF' != E_PROPNF && value != 0) result = {@result, @this:_print(value, indent + " ")}; endif endfor endif return result; . @program $plastic.printer:print as application/x-moocode {source, ?options = []} = args; tokenizer = this.plastic.tokenizer_proto:create(source); parser = this.plastic.parser_proto:create(tokenizer); statements = parser:statements(); source = {}; for statement in (statements) source = {@source, @this:_print(statement)}; endfor return source; . ; /* TOKENIZER */ @program $plastic.tokenizer_proto:create as application/x-moocode this:_ensure_prototype(); instance = create(this, 1); instance.row = 1; instance.column = 1; instance.source = (length(args) == 1 && typeof(args[1]) == LIST) ? args[1] | args; return instance; . @program $plastic.tokenizer_proto:advance as application/x-moocode this:_ensure_instance(); this.token = 0; if (!this.source) return this; endif row = this.row; column = this.column; source = this.source; eol = 0; block_comment = 0; inline_comment = 0; while loop (length(source) >= row) if (column > (len = length(source[row]))) eol = 1; inline_comment = 0; row = row + 1; column = 1; continue loop; endif next_two = len > column ? source[row][column..column + 1] | ""; if (block_comment && next_two == "*/") block_comment = 0; column = column + 2; continue loop; elseif (next_two == "/*") block_comment = 1; column = column + 2; continue loop; elseif (next_two == "//") inline_comment = 1; column = column + 2; continue loop; endif if (block_comment || inline_comment) column = column + 1; continue loop; endif if (len >= column && ((c = source[row][column]) == " " || c == " ")) column = column + 1; continue loop; endif if (this.token) this.token["eol"] = eol; eol = 0; break loop; endif if (`c = source[row][column] ! E_RANGE') /* name and error */ /* MOO error literals look like names but they're not. Worse, a * valid error like E_PERM is treated like a literal, while an * invalid error like E_FOO is treated like a variable. Any name * that starts with the characters "E_" is *now* an error literal, * but invalid errors are errors. */ if ((c >= "a" && c <= "z") || c == "_" || c == "$") col1 = column; /* mark the start */ column = column + 1; while (`c = source[row][column] ! E_RANGE') if ((c >= "a" && c <= "z") || (c >= "0" && c <= "9") || c == "_") column = column + 1; else break; endif endwhile col2 = column - 1; chars = source[row][col1..col2]; if (index(chars, "E_") == 1) try this.token = ["type" -> "error", "value" -> this.errors[chars]]; except ex (E_RANGE) this.token = ["type" -> "error", "value" -> chars, "error" -> tostr("Invalid error: ", chars)]; endtry else this.token = ["type" -> "name", "value" -> chars]; endif continue loop; /* object number */ elseif (c == "#") col1 = column; /* mark the start */ column = column + 1; if (`c = source[row][column] ! E_RANGE') if (c == "+" || c == "-") column = column + 1; endif endif while (`c = source[row][column] ! E_RANGE') if (c >= "0" && c <= "9") column = column + 1; else break; endif endwhile col2 = column - 1; chars = source[row][col1..col2]; if (chars[$] < "0" || chars[$] > "9") this.token = ["type" -> "object", "value" -> chars, "error" -> "Bad object number"]; elseif (c >= "a" && c <= "z") this.token = ["type" -> "object", "value" -> chars + c, "error" -> "Bad object number"]; else this.token = ["type" -> "object", "value" -> toobj(chars)]; endif continue loop; /* number */ elseif (c >= "0" && c <= "9") float = 0; col1 = column; /* mark the start */ column = column + 1; while (`c = source[row][column] ! E_RANGE') if (c >= "0" && c <= "9") column = column + 1; else break; endif endwhile if (c == "." && ((cc = `source[row][column + 1] ! E_RANGE') != ".")) /* not `..' */ float = 1; column = column + 1; while (`c = source[row][column] ! E_RANGE') if (c >= "0" && c <= "9") column = column + 1; else break; endif endwhile endif if (c == "e") float = 1; column = column + 1; if (`c = source[row][column] ! E_RANGE' && c in {"-", "+"}) column = column + 1; endif while (`c = source[row][column] ! E_RANGE') if (c >= "0" && c <= "9") column = column + 1; else break; endif endwhile endif col2 = column - 1; chars = source[row][col1..col2]; if ((chars[$] < "0" || chars[$] > "9") && chars[$] != ".") this.token = ["type" -> "number", "value" -> chars, "error" -> "Bad number"]; elseif (c >= "a" && c <= "z") this.token = ["type" -> "number", "value" -> chars + c, "error" -> "Bad number"]; else this.token = ["type" -> "number", "value" -> float ? tofloat(chars) | toint(chars)]; endif continue loop; /* string */ elseif (c == "\"" || c == "'") esc = 0; chars = ""; q = c; col1 = column; /* mark the start */ column = column + 1; while (`c = source[row][column] ! E_RANGE' && (c != q || esc)) column = column + 1; if (c != "\\" || esc) chars = tostr(chars, c); esc = 0; else esc = 1; endif endwhile column = column + 1; col2 = column - 1; if (c != q) this.token = ["type" -> "string", "value" -> source[row][col1..col2 - 1], "error" -> "Unterminated string"]; continue loop; else this.token = ["type" -> "string", "value" -> chars]; continue loop; endif /* possible multi-character operator */ elseif (index("-+<>=*/%!|&.", c)) col1 = column; /* mark the start */ column = column + 1; if (`c = source[row][column] ! E_RANGE' && index(">=*!|&.", c)) column = column + 1; this.token = ["type" -> "operator", "value" -> source[row][col1..column - 1]]; continue loop; else this.token = ["type" -> "operator", "value" -> source[row][col1]]; continue loop; endif /* operator */ else column = column + 1; this.token = ["type" -> "operator", "value" -> c]; continue loop; endif column = column + 1; endif endwhile this.row = row; this.column = column; this.source = source; /* check for unterminated comment */ if (block_comment) this.token = ["type" -> "comment", "value" -> "", "error" -> "Unterminated comment"]; endif /* dollar sign by itself is not a name */ if (this.token && this.token["type"] == "name" && this.token["value"] == "$") this.token["type"] = "operator"; endif /* catch the last token */ if (row > length(source) && this.token) this.token["eol"] = 1; endif return this; . @program $plastic.tokenizer_proto:token as application/x-moocode this:_ensure_instance(); return this.token; . ; /* PARSER */ @program $plastic.parser_proto:create as application/x-moocode this:_ensure_prototype(); {tokenizer, @options} = args; instance = create(this, 1); instance.tokenizer = tokenizer; instance.symbols = []; plastic = this.plastic; /* `(end)' is required */ instance:symbol("(end)"); instance:symbol("(name)", 0, plastic.name_proto); instance:symbol("(literal)", 0, plastic.literal_proto); instance:symbol(";", 0, plastic.operator_proto); instance:symbol(",", 0, plastic.operator_proto); instance:symbol("]", 0, plastic.operator_proto); instance:symbol("}", 0, plastic.operator_proto); instance:symbol("->", 0, plastic.operator_proto); instance:symbol("=>", 0, plastic.operator_proto); instance:symbol("..", 0, plastic.operator_proto); instance:symbol("|", 0, plastic.operator_proto); instance:symbol("`", 0, plastic.operator_proto); instance:symbol("!", 0, plastic.prefix_operator_proto); instance:symbol("!!", 50, plastic.error_catching_operator_proto); instance:symbol("=", 100, plastic.assignment_operator_proto); instance:symbol("+=", 100, plastic.compound_assignment_operator_proto); instance:symbol("-=", 100, plastic.compound_assignment_operator_proto); instance:symbol("*=", 100, plastic.compound_assignment_operator_proto); instance:symbol("/=", 100, plastic.compound_assignment_operator_proto); instance:symbol("%=", 100, plastic.compound_assignment_operator_proto); instance:symbol("?", 200, plastic.traditional_ternary_operator_proto); instance:symbol("&&", 300, plastic.infix_operator_proto, ["right" -> 1]); instance:symbol("||", 300, plastic.infix_operator_proto, ["right" -> 1]); instance:symbol("!=", 400, plastic.infix_operator_proto); instance:symbol("==", 400, plastic.infix_operator_proto); instance:symbol("<", 400, plastic.infix_operator_proto); instance:symbol("<=", 400, plastic.infix_operator_proto); instance:symbol(">", 400, plastic.infix_operator_proto); instance:symbol(">=", 400, plastic.infix_operator_proto); instance:symbol("in", 400, plastic.infix_operator_proto); instance:symbol("+", 500, plastic.sign_operator_proto); instance:symbol("-", 500, plastic.sign_operator_proto); instance:symbol("*", 600, plastic.infix_operator_proto); instance:symbol("/", 600, plastic.infix_operator_proto); instance:symbol("%", 600, plastic.infix_operator_proto); instance:symbol("**", 650, plastic.infix_operator_proto); instance:symbol("[", 800, plastic.bracket_operator_proto); instance:symbol("{", 0, plastic.brace_operator_proto); /* never bind left */ instance:symbol("return", 0, plastic.control_flow_statement_proto); instance:symbol("break", 0, plastic.control_flow_statement_proto); instance:symbol("continue", 0, plastic.control_flow_statement_proto); instance:symbol("if", 0, plastic.if_statement_proto); instance:symbol("for", 0, plastic.for_statement_proto); instance:symbol("while", 0, plastic.loop_statement_proto); instance:symbol("until", 0, plastic.loop_statement_proto); instance:symbol("fork", 0, plastic.fork_statement_proto); instance:symbol("try", 0, plastic.try_statement_proto); instance:symbol("from", 0, plastic.from_statement_proto); instance:symbol(":", 800, plastic.verb_selector_operator_proto); instance:symbol(".", 800, plastic.property_selector_operator_proto); /* the infix form is function/verb invocation */ instance:symbol("(", 800, plastic.invocation_operator_proto); instance:symbol(")", 0, plastic.operator_proto); return instance; . @program $plastic.parser_proto:symbol as application/x-moocode this:_ensure_instance(); {id, ?bp = 0, ?proto = $nothing, ?options = []} = args; proto = valid(proto) ? proto | this.plastic.symbol_proto; if ((symbol = `this.symbols[id] ! E_RANGE') == E_RANGE) symbol = proto:create(id, bp, options); endif this.symbols[id] = symbol; return symbol; . @program $plastic.parser_proto:reserve_statement as application/x-moocode this:_ensure_instance(); {symbol} = args; id = symbol.id; /* raise error if this symbol is not a name, statement or keyword */ if ((type = this.symbols[id].type) != "name" && type != "statement" && type != "keyword") an_or_a = index("aeiou", type[1]) ? "an" | "a"; raise("Syntax error", tostr("`", id, "' is ", an_or_a, " ", type), this); endif symbol.reserved = 1; symbol.type = verb[9..$]; this.symbols[id] = symbol; . @program $plastic.parser_proto:make_identifier as application/x-moocode this:_ensure_instance(); {symbol} = args; id = symbol.id; /* raise error if this symbol is reserved */ if (this.symbols[id].reserved) raise("Syntax error", tostr("`", id, "' is reserved"), this); endif symbol.reserved = 0; symbol.type = verb[6..$]; this.symbols[id] = symbol; . @program $plastic.parser_proto:token as application/x-moocode this:_ensure_instance(); {?ttid = 0} = args; if (this.token == 0) this.tokenizer:advance(); token = this.tokenizer.token; if (token) type = token["type"]; value = token["value"]; eol = token["eol"]; if (`token["error"] ! E_RANGE') raise("Syntax error", token["error"], this); elseif (type == "number" || type == "string" || type == "object" || type == "error") symbol = this:symbol("(literal)"); this.token = symbol:clone(); this.token.type = type; this.token.value = value; this.token.eol = eol; elseif (type == "operator") /* Update the symbol table itself and give the operator the * initial type "operator" (the type will change to "unary", * "binary" or "ternary" when we learn how this symbol is used in * the program). */ /* check the symbol table */ if ((symbol = `this.symbols[value] ! E_RANGE') == E_RANGE) raise("Syntax error", tostr("Unknown operator: `", value, "'"), this); endif this.token = symbol:clone(); this.token.type = "operator"; this.token.value = value; this.token.eol = eol; elseif (type == "name") /* Update the symbol table itself and give the name the initial * type "name" (the type will change to "variable", "identifier", * "statement" or "keyword" when we learn how this symbol is used * in the program). */ id = value; /* peek into the symbol table */ if ((symbol = `this.symbols[id] ! E_RANGE') != E_RANGE) this.token = symbol:clone(); this.symbols[id] = this.token; this.token.type = symbol.type || "name"; this.token.id = id; this.token.value = value; this.token.eol = eol; else symbol = this:symbol("(name)"); this.token = symbol:clone(); this.symbols[id] = this.token; this.token.type = "name"; this.token.id = id; this.token.value = value; this.token.eol = eol; endif else raise("Syntax error", "Unexpected token", this); endif else symbol = this:symbol("(end)"); this.token = symbol:clone(); endif endif if (ttid) this:advance(ttid); endif return this.token; . @program $plastic.parser_proto:advance as application/x-moocode this:_ensure_instance(); {?id = 0} = args; /* raise error if token doesn't match expectation */ if (id && this.token != 0 && this.token.id != id) raise("Syntax error", tostr("Expected `", id, "'"), this); endif this.token = 0; return this; . @program $plastic.parser_proto:expression as application/x-moocode this:_ensure_instance(); {?bp = 0} = args; token = this:token(); this:advance(); /* don't call `nud()' and/or `led()' on `(end)' */ if (token.id == "(end)") return token; endif left = token:nud(this); while (bp < this:token().bp) this.plastic.utilities:suspend_if_necessary(); token = this:token(); this:advance(); left = token:led(this, left); endwhile return left; . @program $plastic.parser_proto:statement as application/x-moocode this:_ensure_instance(); token = this:token(); /* disregarded naked semicolons */ while (token.id == ";") this:advance(); token = this:token(); endwhile /* either the beginning of a statement */ /* or an expression with an optional semicolon */ if (respond_to(token, "std")) this:advance(); return token:std(this); else expression = this:expression(); if (this:token().id == ";") this:advance(); endif return expression; endif . @program $plastic.parser_proto:statements as application/x-moocode this:_ensure_instance(); terminals = args; statements = {}; while (1) this.plastic.utilities:suspend_if_necessary(); token = this:token(); if (token.id == "(end)" || ((token.type in {"name", "statement", "keyword"}) && token.value in terminals)) break; endif statement = this:statement(); if (statement.id == "(end)") break; endif statements = {@statements, statement}; endwhile return statements; . @program $plastic.parser_proto:parse_all as application/x-moocode this:_ensure_instance(); /* This is the API entry-point for clients that want to turn a * stream of tokens into a syntax tree and to return it for further * modification, changing ownership to the client in the process. * Assumes "change owner" permission has been granted by the * caller. */ stack = statements = this:statements(); while (stack) this.plastic.utilities:suspend_if_necessary(); {top, @stack} = stack; stack = {@stack, @this.plastic.utilities:children(top)}; if (typeof(top) == ANON && isa(top, this.plastic.symbol_proto)) this.object_utilities:change_owner(top, caller_perms()); endif endwhile return statements; . @program $plastic.parser_proto:push as application/x-moocode this:_ensure_instance(); definition = args; {id, @rest} = definition; new = 0; if (`this.symbols[id] ! E_RANGE' == E_RANGE) this:symbol(@definition); new = 1; endif return {new, id}; . @program $plastic.parser_proto:pop as application/x-moocode this:_ensure_instance(); {args} = args; {new, id} = args; if (new) this.symbols = this.symbols:delete(id); endif . ; /* UTILITIES */ @program $plastic.utilities:suspend_if_necessary as application/x-moocode (ticks_left() < 10000 || seconds_left() < 2) && suspend(0); . @program $plastic.utilities:parse_map_sequence as application/x-moocode {parser, separator, infix, terminator, ?symbols = {}} = args; ids = {}; for symbol in (symbols) ids = {@ids, parser:push(@symbol)}; endfor if (terminator && parser:token().id == terminator) return {}; endif key = parser:expression(0); parser:advance(infix); value = parser:expression(0); map = {{key, value}}; while (parser:token().id == separator) this:suspend_if_necessary(); map && parser:advance(separator); key = parser:expression(0); parser:advance(infix); value = parser:expression(0); map = {@map, {key, value}}; endwhile for id in (ids) parser:pop(id); endfor return map; . @program $plastic.utilities:parse_list_sequence as application/x-moocode {parser, separator, terminator, ?symbols = "defaults"} = args; /* enable defaults */ if (symbols && symbols == "defaults") symbols = {{"@", 0, this.plastic.prefix_operator_proto}}; endif ids = {}; for symbol in (symbols) ids = {@ids, parser:push(@symbol)}; endfor if (terminator && parser:token().id == terminator) return {}; endif expression = parser:expression(0); list = {expression}; while (parser:token().id == separator) this:suspend_if_necessary(); parser:advance(separator); expression = parser:expression(0); list = {@list, expression}; endwhile for id in (ids) parser:pop(id); endfor return list; . @program $plastic.utilities:validate_scattering_pattern as application/x-moocode {parser, pattern} = args; state = 1; for element in (pattern) if (state == 1 && element.type == "variable") continue; elseif ((state == 1 || state == 2) && element.type == "binary" && element.id == "=") if (element.first.type == "variable") state = 2; continue; endif elseif ((state == 1 || state == 2) && element.type == "unary" && element.id == "@") if (element.first.type == "variable") state = 3; continue; endif endif raise("Syntax error", "Illegal scattering pattern", parser); endfor . @program $plastic.utilities:children as application/x-moocode {node} = args; /* Intelligently gather children from various places. */ if (typeof(node) == LIST) return node; elseif (`typeof(value = node.value) == LIST ! ANY') return value; else children = {}; for prop in ({"first", "second", "third"}) if (`value = node.(prop) ! E_PROPNF => 0' != 0) children = {@children, value}; endif endfor return children; endif . @program $plastic.utilities:match as application/x-moocode {root, pattern} = args; /* Pattern is a map. The keys specify the properties (`id', `type', * etc.) to match on. The values specify the property values for the * match comparison. Conducts a depth first search for pattern. */ keys = pattern:keys(); matches = {}; stack = {root}; while next (stack) this:suspend_if_necessary(); {top, @stack} = stack; stack = {@stack, @this:children(top)}; if (typeof(top) == ANON && isa(top, this.plastic.symbol_proto)) for key in (keys) if (top.(key) != pattern[key]) continue next; endif endfor matches = {@matches, top}; endif endwhile return matches; . @program $plastic.symbol_proto:create as application/x-moocode {id, ?bp = 0, ?opts = []} = args; (typeof(this) == OBJ) || raise(E_PERM, "Call not allowed on anonymous object"); instance = create(this, 1); instance.id = id; instance.value = id; instance.bp = bp; for v, k in (opts) if (k in {"id", "type", "bp", "right"}) instance.(k) = v; endif endfor return instance; . @program $plastic.symbol_proto:clone as application/x-moocode (typeof(this) == OBJ) && raise(E_PERM, "Call not allowed on permanent object"); parents = parents(this); instance = create(parents, 1); for ancestor in (ancestors(this)) for property in (`properties(ancestor) ! E_PERM => {}') instance.(property) = this.(property); endfor endfor return instance; . @program $plastic.symbol_proto:nud as application/x-moocode {parser} = args; raise("Syntax error", tostr("Undefined: ", this.id), parser); . @program $plastic.symbol_proto:led as application/x-moocode {parser, _} = args; raise("Syntax error", tostr("Missing operator: ", this.id), parser); . @program $plastic.literal_proto:nud as application/x-moocode return this; . @program $plastic.positional_symbol_proto:nud as application/x-moocode return this; . @program $plastic.prefix_operator_proto:nud as application/x-moocode {parser} = args; first = parser:expression(700); if (first.id == "(end)") raise("Syntax error", "Expected an expression", parser); endif this.type = "unary"; this.first = first; return this; . @program $plastic.infix_operator_proto:led as application/x-moocode {parser, first} = args; right = this.right && 1; /* does this operator associate to the right? */ second = parser:expression(this.bp - right); if (second.id == "(end)") raise("Syntax error", "Expected an expression", parser); endif this.type = "binary"; this.first = first; this.second = second; return this; . @program $plastic.sign_operator_proto:nud as application/x-moocode {parser} = args; first = parser:expression(700); if (first.id == "(end)") raise("Syntax error", "Expected an expression", parser); endif this.type = "unary"; this.first = first; return this; . @program $plastic.sign_operator_proto:led as application/x-moocode {parser, first} = args; second = parser:expression(this.bp); if (second.id == "(end)") raise("Syntax error", "Expected an expression", parser); endif this.type = "binary"; this.first = first; this.second = second; return this; . @program $plastic.statement_proto:std as application/x-moocode {parser} = args; raise("Syntax error", "Undefined", parser); . @program $plastic.control_flow_statement_proto:std as application/x-moocode {parser} = args; if (this.id != "return" && parser.loop_depth < 1) raise("Syntax error", tostr("No enclosing loop for ", this.id), parser); endif /* update the symbol table */ parser:reserve_statement(this); if (!this.eol && parser:token().id != ";") expression = parser:expression(0); this.first = expression; endif if (this.id != "return" && this.first != 0) if (this.first.type != "variable") raise("Syntax error", "Loop name must be a name", parser); endif if (!(this.first.value in parser.loop_variables)) raise("Syntax error", tostr("Invalid loop name for ", this.id), parser); endif endif if (parser:token().id == ";") parser:advance(";"); endif return this; . @program $plastic.if_statement_proto:std as application/x-moocode {parser} = args; /* update the symbol table */ parser:reserve_statement(this); a = {}; /* the predicate */ parser:token("("); expression = parser:expression(0); a = {@a, expression}; parser:token(")"); /* the consequent */ statements = parser:statements("elseif", "else", "endif", "end"); a = {@a, statements}; /* the alternatives */ while (parser:token().id == "elseif") parser:advance("elseif"); /* predicate */ parser:token("("); expression = parser:expression(0); a = {@a, expression}; parser:token(")"); /* consequent */ statements = parser:statements("elseif", "else", "endif", "end"); a = {@a, statements}; /* update the symbol table */ symbol = parser.symbols["elseif"]; parser:reserve_keyword(symbol); endwhile /* the final alternative */ if (parser:token().id == "else") parser:advance("else"); statements = parser:statements("endif", "end"); a = {@a, statements}; /* update the symbol table */ symbol = parser.symbols["else"]; parser:reserve_keyword(symbol); endif /* the last token must be "endif" or "end" */ if ((id = parser:token().id) == "endif") parser:advance("endif"); else parser:advance("end"); endif /* update the symbol table */ symbol = parser.symbols[id]; parser:reserve_keyword(symbol); /* store the parts in this token's `value' */ this.value = a; return this; . @program $plastic.for_statement_proto:std as application/x-moocode {parser} = args; /* update the symbol table */ parser:reserve_statement(this); a = {}; /* the index(s) */ variables = {}; variable = parser:token(); parser:make_variable(variable); parser:advance(); variables = {@variables, variable}; if (parser:token().id == ",") while (parser:token().id == ",") parser:advance(","); variable = parser:token(); parser:make_variable(variable); parser:advance(); variables = {@variables, variable}; endwhile elseif (parser:token().id == "->") while (parser:token().id == "->") parser:advance("->"); variable = parser:token(); parser:make_variable(variable); parser:advance(); variables = {variable, @variables}; endwhile endif a = {@a, @variables}; for _, i in (variables) variables[i] = variables[i].id; endfor parser:token("in"); /* update the symbol table */ symbol = parser.symbols["in"]; parser:reserve_keyword(symbol); /* could be a range or a collection */ if (parser:token().id == "[") /* range */ this.subtype = "range"; length(a) < 2 || raise("Syntax error", "Too many loop variables", parser); parser:token("["); first = parser:expression(0); a = {@a, first}; parser:token(".."); second = parser:expression(0); a = {@a, second}; parser:token("]"); else /* collection */ this.subtype = "collection"; length(a) < 3 || raise("Syntax error", "Too many loop variables", parser); parser:token("("); expression = parser:expression(0); a = {@a, expression}; parser:token(")"); endif /* the body */ parser.loop_variables = {@parser.loop_variables, @variables}; parser.loop_depth = parser.loop_depth + 1; statements = parser:statements("endfor", "end"); a = {@a, statements}; l = length(variables); parser.loop_variables = parser.loop_variables[1..$ - l]; parser.loop_depth = parser.loop_depth - 1; /* the last token must be "endfor" or "end" */ if ((id = parser:token().id) == "endfor") parser:advance("endfor"); else parser:advance("end"); endif /* update the symbol table */ symbol = parser.symbols[id]; parser:reserve_keyword(symbol); /* store the parts in this token's `value' */ this.value = a; return this; . @program $plastic.loop_statement_proto:std as application/x-moocode {parser} = args; /* update the symbol table */ parser:reserve_statement(this); end = tostr("end", this.id); a = {}; /* possible, optional loop name */ variables = {}; if ((type = parser:token().type) == "variable" || type == "name") variable = parser:token(); parser:make_variable(variable); parser:advance(); variables = {@variables, variable.id}; a = {@a, variable}; endif /* the condition */ parser:token("("); expression = parser:expression(0); a = {@a, expression}; parser:token(")"); /* the body */ parser.loop_variables = {@parser.loop_variables, @variables}; parser.loop_depth = parser.loop_depth + 1; statements = parser:statements(end, "end"); a = {@a, statements}; l = length(variables); parser.loop_variables = parser.loop_variables[1..$ - l]; parser.loop_depth = parser.loop_depth - 1; /* the last token must be "endwhile/enduntil" or "end" */ if ((id = parser:token().id) == end) parser:advance(end); else parser:advance("end"); endif /* update the symbol table */ symbol = parser.symbols[id]; parser:reserve_keyword(symbol); /* store the parts in this token's `value' */ this.value = a; return this; . @program $plastic.fork_statement_proto:std as application/x-moocode {parser} = args; /* update the symbol table */ parser:reserve_statement(this); a = {}; /* possible, optional task name */ if ((type = parser:token().type) == "variable" || type == "name") variable = parser:token(); parser:make_variable(variable); parser:advance(); a = {@a, variable}; endif /* the expression */ parser:token("("); expression = parser:expression(0); a = {@a, expression}; parser:token(")"); /* the body */ statements = parser:statements("endfork", "end"); a = {@a, statements}; /* the last token must be "endfork" or "end" */ if ((id = parser:token().id) == "endfork") parser:advance("endfork"); else parser:advance("end"); endif /* update the symbol table */ symbol = parser.symbols[id]; parser:reserve_keyword(symbol); /* store the parts in this token's `value' */ this.value = a; return this; . @program $plastic.try_statement_proto:std as application/x-moocode {parser} = args; /* update the symbol table */ parser:reserve_statement(this); a = {}; /* the body */ body = parser:statements("except", "finally", "endtry", "end"); a = {@a, body}; if (parser:token().id == "finally") parser:advance("finally"); b = parser:statements("endtry", "end"); /* update the symbol table */ symbol = parser.symbols["finally"]; parser:reserve_keyword(symbol); this.subtype = "finally"; a = {@a, b}; else b = {}; id = parser:push("@", 0, this.plastic.prefix_operator_proto); /* the exceptions */ while (parser:token().id == "except") parser:advance("except"); /* variable and codes */ if ((variable = parser:token()).id != "(") parser:advance(); if (variable.type != "name" && variable.type != "variable") raise("Syntax error", "Variable must be an identifier", parser); endif parser:make_variable(variable); parser:token("("); if ((token = parser:token()).id == "ANY") parser:advance("ANY"); symbol = parser.symbols["ANY"]; parser:reserve_keyword(symbol); codes = {token}; else codes = this.plastic.utilities:parse_list_sequence(parser, ",", ")"); endif parser:token(")"); !codes && raise("Syntax error", "Codes may not be empty", parser); b = {variable, codes}; /* just codes */ else parser:token("("); if ((token = parser:token()).id == "ANY") parser:advance("ANY"); symbol = parser.symbols["ANY"]; parser:reserve_keyword(symbol); codes = {token}; else codes = this.plastic.utilities:parse_list_sequence(parser, ",", ")"); endif parser:token(")"); !codes && raise("Syntax error", "Codes may not be empty", parser); b = {codes}; endif /* handler */ handler = parser:statements("except", "finally", "endtry", "end"); b = {@b, handler}; /* update the symbol table */ symbol = parser.symbols["except"]; parser:reserve_keyword(symbol); a = {@a, b}; endwhile parser:pop(id); if (!b) raise("Syntax error", "Missing except", parser); endif this.subtype = "except"; endif /* the last token must be "endtry" or "end" */ if ((id = parser:token().id) == "endtry") parser:advance("endtry"); else parser:advance("end"); endif /* update the symbol table */ symbol = parser.symbols[id]; parser:reserve_keyword(symbol); /* store the parts in this token's `value' */ this.value = a; return this; . @program $plastic.assignment_operator_proto:led as application/x-moocode {parser, first} = args; if (first.type == "unary" && first.id == "{") /* scattering syntax */ this.plastic.utilities:validate_scattering_pattern(parser, first.value); first.type = "pattern"; endif if (first.type != "variable" && first.type != "pattern" && first.id != "." && first.id != "[") raise("Syntax error", "Illegal expression on left side of assignment", parser); endif second = parser:expression(this.bp - 1); if (second.id == "(end)") raise("Syntax error", "Expected an expression", parser); endif this.type = "binary"; this.first = first; this.second = second; return this; . @program $plastic.compound_assignment_operator_proto:led as application/x-moocode {parser, first} = args; if (first.type != "variable" && first.id != "." && first.id != "[") raise("Syntax error", "Illegal expression on left side of assignment", parser); endif second = parser:expression(this.bp - 1); if (second.id == "(end)") raise("Syntax error", "Expected an expression", parser); endif op = this.id[1]; bp = parser.symbols[op].bp; inner = parser.plastic.infix_operator_proto:create(op, bp); inner.type = "binary"; inner.first = first; inner.second = second; outer = parser.plastic.assignment_operator_proto:create("=", this.bp); outer.type = "binary"; outer.first = first; outer.second = inner; return outer; . @program $plastic.name_proto:nud as application/x-moocode {parser} = args; /* Assume the name is a variable. Subsequent usage may modify this * assumption. */ parser:make_variable(this); return this; . @program $plastic.bracket_operator_proto:nud as application/x-moocode {parser} = args; sequence = this.plastic.utilities:parse_map_sequence(parser, ",", "->", "]"); parser:token("]"); this.type = "unary"; this.value = sequence; this.first = this; return this; . @program $plastic.bracket_operator_proto:led as application/x-moocode {parser, first} = args; caret = parser:push("^", 0, this.plastic.positional_symbol_proto); dollar = parser:push("$", 0, this.plastic.positional_symbol_proto); second = parser:expression(0); if (parser:token().id == "..") parser:advance(".."); third = parser:expression(0); parser:advance("]"); this.type = "ternary"; this.first = first; this.second = second; this.third = third; else parser:advance("]"); this.type = "binary"; this.first = first; this.second = second; endif parser:pop(caret); parser:pop(dollar); return this; . @program $plastic.brace_operator_proto:nud as application/x-moocode {parser} = args; sequence = this.plastic.utilities:parse_list_sequence(parser, ",", "}"); parser:token("}"); this.type = "unary"; this.value = sequence; this.first = this; return this; . @program $plastic.invocation_operator_proto:nud as application/x-moocode {parser} = args; expression = parser:expression(0); parser:token(")"); this.type = "unary"; this.first = expression; return this; . @program $plastic.invocation_operator_proto:led as application/x-moocode {parser, left} = args; sequence = this.plastic.utilities:parse_list_sequence(parser, ",", ")"); parser:token(")"); /* The invocation operator handles function and verb invocation, and * guards against use on properties. */ if (left.id == ".") raise("Syntax error", "Invalid application of invocation", parser); elseif (left.id == ":") first = left.first; second = left.second; /* the verb selector operator has our back */ this.type = "ternary"; this.first = first; this.second = second; this.third = sequence; else if (left.type != "variable") raise("Syntax error", "Expected an identifier", parser); endif parser:make_identifier(left); if (`import = parser.imports[left.value] ! E_RANGE' != E_RANGE) this.type = "ternary"; this.first = import; this.second = left; this.third = sequence; else this.type = "binary"; this.first = left; this.second = sequence; endif endif return this; . @program $plastic.verb_selector_operator_proto:led as application/x-moocode {parser, first} = args; if (parser:token().id == "(") parser:advance("("); second = parser:expression(0); parser:advance(")"); else second = parser:expression(this.bp); if (second.type != "variable") raise("Syntax error", "Expected an identifier", parser); endif parser:make_identifier(second); endif if (parser:token().id != "(") raise("Syntax error", "Expected `('", parser); endif this.type = "binary"; this.first = first; this.second = second; return this; . @program $plastic.property_selector_operator_proto:led as application/x-moocode {parser, first} = args; if (parser:token().id == "(") parser:advance("("); second = parser:expression(0); parser:advance(")"); else second = parser:expression(this.bp); if (second.type != "variable") raise("Syntax error", "Expected an identifier", parser); endif parser:make_identifier(second); endif this.type = "binary"; this.first = first; this.second = second; return this; . @program $plastic.error_catching_operator_proto:nud as application/x-moocode {parser} = args; first = parser:expression(700); if (first.id == "(end)") raise("Syntax error", "Expected an expression", parser); endif this.type = "unary"; this.first = first; return this; . @program $plastic.error_catching_operator_proto:led as application/x-moocode {parser, first} = args; /* Error codes are either the keyword `ANY' or a list of expressions * (see 4.1.12 Catching Errors in Expressions). */ id = parser:push("@", 0, this.plastic.prefix_operator_proto); if ((token = parser:token()).id == "ANY") parser:advance("ANY"); symbol = parser.symbols["ANY"]; parser:reserve_keyword(symbol); second = {token}; else second = this.plastic.utilities:parse_list_sequence(parser, ",", ""); if (second[$].id == "(end)") raise("Syntax error", "Expected `ANY' or a list of expressions", parser); endif endif parser:pop(id); if ((token = parser:token()).id == "=>") parser:advance("=>"); third = parser:expression(0); if (third.id == "(end)") raise("Syntax error", "Expected an expression", parser); endif this.type = "ternary"; this.first = first; this.second = second; this.third = third; else this.type = "binary"; this.first = first; this.second = second; endif return this; . @program $plastic.traditional_ternary_operator_proto:led as application/x-moocode {parser, first} = args; second = parser:expression(0); parser:token("|"); third = parser:expression(0); if (second.id == "(end)" || third.id == "(end)") raise("Syntax error", "Expected an expression", parser); endif this.type = "ternary"; this.first = first; this.second = second; this.third = third; return this; . @program $plastic.from_statement_proto:std as application/x-moocode {parser} = args; /* update the symbol table */ parser:reserve_statement(this); types = {"name", "variable", "identifier"}; /* the reference */ if ((target = parser:token()).type == "string") parser:advance(); elseif (target.type in types && target.value == "this") parser:advance(); elseif (target.type in types && target.value[1] == "$") target = parser:expression(); if (target.id == ".") temp = target; while (temp.id == ".") if ((temp.first.id != "." && !(temp.first.type in types)) && !(temp.second.type in types)) raise("Syntax error", tostr("Invalid reference: ", target.id), parser); endif temp = temp.first; endwhile elseif (target.value[1] == "$") /* ok */ else raise("Syntax error", tostr("Invalid reference: ", target.id), parser); endif else raise("Syntax error", tostr("Invalid reference: ", target.id), parser); endif parser:token("use"); /* update the symbol table */ symbol = parser.symbols["use"]; parser:reserve_keyword(symbol); /* the import(s) */ imports = {}; if ((import = parser:token()).id == "(end)") raise("Syntax error", "Expected an identifier", parser); elseif (!(import.type in types)) raise("Syntax error", "Import must be an identifier", parser); endif parser:make_identifier(import); parser:advance(); imports = {@imports, import}; while (parser:token().id == ",") parser:advance(","); if ((import = parser:token()).id == "(end)") raise("Syntax error", "Expected an identifier", parser); elseif (!(import.type in types)) raise("Syntax error", "Import must be an identifier", parser); endif parser:make_identifier(import); parser:advance(); imports = {@imports, import}; endwhile /* generate code */ if (target.type == "string") temp = parser.plastic.invocation_operator_proto:create("("); temp.type = "binary"; temp.first = parser.plastic.name_proto:create("$lookup"); temp.first.type = "identifier"; temp.first.value = "$lookup"; temp.second = {target}; target = temp; endif first = parser.plastic.name_proto:create(tostr(random())); first.type = "unique"; if (!length(imports)) raise("Syntax error", "Missing imports", parser); endif for import in (imports) if (`parser.imports[import.id] ! E_RANGE' != E_RANGE) raise("Syntax error", "Duplicate imports", parser); endif parser.imports[import.id] = first; endfor result = parser.plastic.assignment_operator_proto:create("="); result.type = "binary"; result.first = first; result.second = target; return result; .