mirror of
				https://github.com/KevinMidboe/linguist.git
				synced 2025-10-29 17:50:22 +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;
 | |
| .
 |