Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Moocode Toolkit
;; 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;
.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment