mirror of
https://github.com/KevinMidboe/linguist.git
synced 2025-10-29 09:40:21 +00:00
2197 lines
53 KiB
Plaintext
2197 lines
53 KiB
Plaintext
;; 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;
|
|
.
|