1 // Based on https://github.com/adamdruppe/arsd/blob/ed61b3ce0b6fb97e83de54ff2e33432ce33d4887/script.d 2 /* 3 Boost Software License - Version 1.0 4 5 Permission is hereby granted, free of charge, to any person or organization 6 obtaining a copy of the software and accompanying documentation covered by 7 this license (the "Software") to use, reproduce, display, distribute, 8 execute, and transmit the Software, and to prepare derivative works of the 9 Software, and to permit third-parties to whom the Software is furnished to 10 do so, all subject to the following: 11 12 The copyright notices in the Software and this entire statement, including 13 the above license grant, this restriction and the following disclaimer, 14 must be included in all copies of the Software, in whole or in part, and 15 all derivative works of the Software, unless such copies or derivative 16 works are solely in the form of machine-executable object code generated by 17 a source language processor. 18 19 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT 22 SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE 23 FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, 24 ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 DEALINGS IN THE SOFTWARE. 26 */ 27 /++ 28 A small script interpreter that builds on [os1.lang.jsvar] to be easily embedded inside and to have has easy 29 two-way interop with the host D program. The script language it implements is based on a hybrid of D and Javascript. 30 The type the language uses is based directly on [var] from [os1.lang.jsvar]. 31 32 The interpreter is slightly buggy and poorly documented, but the basic functionality works well and much of 33 your existing knowledge from Javascript will carry over, making it hopefully easy to use right out of the box. 34 See the [#examples] to quickly get the feel of the script language as well as the interop. 35 36 37 Installation_instructions: 38 This script interpreter is contained entirely in two files: jsvar.d and script.d. Download both of them 39 and add them to your project. Then, `import arsd.script;`, declare and populate a `var globals = var.emptyObject;`, 40 and `interpret("some code", globals);` in D. 41 42 There's nothing else to it, no complicated build, no external dependencies. 43 44 $(CONSOLE 45 $ wget https://raw.githubusercontent.com/adamdruppe/arsd/master/script.d 46 $ wget https://raw.githubusercontent.com/adamdruppe/arsd/master/jsvar.d 47 48 $ dmd yourfile.d script.d jsvar.d 49 ) 50 51 Script_features: 52 53 OVERVIEW 54 $(LIST 55 * easy interop with D thanks to os1.lang.jsvar. When interpreting, pass a var object to use as globals. 56 This object also contains the global state when interpretation is done. 57 * mostly familiar syntax, hybrid of D and Javascript 58 * simple implementation is moderately small and fairly easy to hack on (though it gets messier by the day), but it isn't made for speed. 59 ) 60 61 SPECIFICS 62 $(LIST 63 // * Allows identifiers-with-dashes. To do subtraction, put spaces around the minus sign. 64 * Allows identifiers starting with a dollar sign. 65 * string literals come in "foo" or 'foo', like Javascript, or `raw string` like D. Also come as “nested “double quotes” are an option!” 66 * double quoted string literals can do Ruby-style interpolation: "Hello, #{name}". 67 * mixin aka eval (does it at runtime, so more like eval than mixin, but I want it to look like D) 68 * scope guards, like in D 69 * Built-in assert() which prints its source and its arguments 70 * try/catch/finally/throw 71 You can use try as an expression without any following catch to return the exception: 72 73 var a = try throw "exception";; // the double ; is because one closes the try, the second closes the var 74 // a is now the thrown exception 75 * for/while/foreach 76 * D style operators: +-/* on all numeric types, ~ on strings and arrays, |&^ on integers. 77 Operators can coerce types as needed: 10 ~ "hey" == "10hey". 10 + "3" == 13. 78 Any math, except bitwise math, with a floating point component returns a floating point component, but pure int math is done as ints (unlike Javascript btw). 79 Any bitwise math coerces to int. 80 81 So you can do some type coercion like this: 82 83 a = a|0; // forces to int 84 a = "" ~ a; // forces to string 85 a = a+0.0; // coerces to float 86 87 Though casting is probably better. 88 * Type coercion via cast, similarly to D. 89 var a = "12"; 90 a.typeof == "String"; 91 a = cast(int) a; 92 a.typeof == "Integral"; 93 a == 12; 94 95 Supported types for casting to: int/long (both actually an alias for long, because of how var works), float/double/real, string, char/dchar (these return *integral* types), and arrays, int[], string[], and float[]. 96 97 This forwards directly to the D function var.opCast. 98 99 * some operator overloading on objects, passing opBinary(op, rhs), length, and perhaps others through like they would be in D. 100 opIndex(name) 101 opIndexAssign(value, name) // same order as D, might some day support [n1, n2] => (value, n1, n2) 102 103 obj.__prop("name", value); // bypasses operator overloading, useful for use inside the opIndexAssign especially 104 105 Note: if opIndex is not overloaded, getting a non-existent member will actually add it to the member. This might be a bug but is needed right now in the D impl for nice chaining. Or is it? FIXME 106 * if/else 107 * array slicing, but note that slices are rvalues currently 108 * variables must start with A-Z, a-z, _, or $, then must be [A-Za-z0-9_]*. 109 (The $ can also stand alone, and this is a special thing when slicing, so you probably shouldn't use it at all.). 110 Variable names that start with __ are reserved and you shouldn't use them. 111 * int, float, string, array, bool, and json!q{} literals 112 * var.prototype, var.typeof. prototype works more like Mozilla's __proto__ than standard javascript prototype. 113 * classes: 114 // inheritance works 115 class Foo : bar { 116 // constructors, D style 117 this(var a) { ctor.... } 118 119 // static vars go on the auto created prototype 120 static var b = 10; 121 122 // instance vars go on this instance itself 123 var instancevar = 20; 124 125 // "virtual" functions can be overridden kinda like you expect in D, though there is no override keyword 126 function virt() { 127 b = 30; // lexical scoping is supported for static variables and functions 128 129 // but be sure to use this. as a prefix for any class defined instance variables in here 130 this.instancevar = 10; 131 } 132 } 133 134 var foo = new Foo(12); 135 136 foo.newFunc = function() { this.derived = 0; }; // this is ok too, and scoping, including 'this', works like in Javascript 137 138 You can also use 'new' on another object to get a copy of it. 139 * return, break, continue, but currently cannot do labeled breaks and continues 140 * __FILE__, __LINE__, but currently not as default arguments for D behavior (they always evaluate at the definition point) 141 * most everything are expressions, though note this is pretty buggy! But as a consequence: 142 for(var a = 0, b = 0; a < 10; a+=1, b+=1) {} 143 won't work but this will: 144 for(var a = 0, b = 0; a < 10; {a+=1; b+=1}) {} 145 146 You can encase things in {} anywhere instead of a comma operator, and it works kinda similarly. 147 148 {} creates a new scope inside it and returns the last value evaluated. 149 * functions: 150 var fn = function(args...) expr; 151 or 152 function fn(args....) expr; 153 154 Special function local variables: 155 _arguments = var[] of the arguments passed 156 _thisfunc = reference to the function itself 157 this = reference to the object on which it is being called - note this is like Javascript, not D. 158 159 args can say var if you want, but don't have to 160 default arguments supported in any position 161 when calling, you can use the default keyword to use the default value in any position 162 * macros: 163 A macro is defined just like a function, except with the 164 macro keyword instead of the function keyword. The difference 165 is a macro must interpret its own arguments - it is passed 166 AST objects instead of values. Still a WIP. 167 ) 168 169 170 FIXME: 171 * make sure superclass ctors are called 172 173 FIXME: prettier stack trace when sent to D 174 175 FIXME: interpolated string: "$foo" or "#{expr}" or something. 176 FIXME: support more escape things in strings like \n, \t etc. 177 178 FIXME: add easy to use premade packages for the global object. 179 180 FIXME: maybe simplify the json!q{ } thing a bit. 181 182 FIXME: the debugger statement from javascript might be cool to throw in too. 183 184 FIXME: add continuations or something too 185 186 FIXME: Also ability to get source code for function something so you can mixin. 187 FIXME: add COM support on Windows 188 189 190 Might be nice: 191 varargs 192 lambdas - maybe without function keyword and the x => foo syntax from D. 193 +/ 194 module os1.lang.script; 195 196 /++ 197 This example shows the basics of how to interact with the script. 198 The string enclosed in `q{ .. }` is the script language source. 199 200 The [var] type comes from [os1.lang.jsvar] and provides a dynamic type 201 to D. It is the same type used in the script language and is weakly 202 typed, providing operator overloads to work with many D types seamlessly. 203 204 However, if you do need to convert it to a static type, such as if passing 205 to a function, you can use `get!T` to get a static type out of it. 206 +/ 207 unittest { 208 var globals = var.emptyObject; 209 globals.x = 25; // we can set variables on the global object 210 globals.name = "script.d"; // of various types 211 // and we can make native functions available to the script 212 globals.sum = (int a, int b) { 213 return a + b; 214 }; 215 216 // This is the source code of the script. It is similar 217 // to javascript with pieces borrowed from D, so should 218 // be pretty familiar. 219 string scriptSource = q{ 220 function foo() { 221 return 13; 222 } 223 224 var a = foo() + 12; 225 assert(a == 25); 226 227 // you can also access the D globals from the script 228 assert(x == 25); 229 assert(name == "script.d"); 230 231 // as well as call D functions set via globals: 232 assert(sum(5, 6) == 11); 233 234 // I will also set a function to call from D 235 function bar(str) { 236 // unlike Javascript though, we use the D style 237 // concatenation operator. 238 return str ~ " concatenation"; 239 } 240 }; 241 242 // once you have the globals set up, you call the interpreter 243 // with one simple function. 244 interpret(scriptSource, globals); 245 246 // finally, globals defined from the script are accessible here too: 247 // however, notice the two sets of parenthesis: the first is because 248 // @property is broken in D. The second set calls the function and you 249 // can pass values to it. 250 assert(globals.foo()() == 13); 251 252 assert(globals.bar()("test") == "test concatenation"); 253 254 // this shows how to convert the var back to a D static type. 255 int x = globals.x.get!int; 256 } 257 258 /++ 259 $(H3 Macros) 260 261 Macros are like functions, but instead of evaluating their arguments at 262 the call site and passing value, the AST nodes are passed right in. Calling 263 the node evaluates the argument and yields the result (this is similar to 264 to `lazy` parameters in D), and they also have methods like `toSourceCode`, 265 `type`, and `interpolate`, which forwards to the given string. 266 267 The language also supports macros and custom interpolation functions. This 268 example shows an interpolation string being passed to a macro and used 269 with a custom interpolation string. 270 271 You might use this to encode interpolated things or something like that. 272 +/ 273 unittest { 274 var globals = var.emptyObject; 275 interpret(q{ 276 macro test(x) { 277 return x.interpolate(function(str) { 278 return str ~ "test"; 279 }); 280 } 281 282 var a = "cool"; 283 assert(test("hey #{a}") == "hey cooltest"); 284 }, globals;) 285 } 286 287 public import os1.lang.jsvar; 288 289 import std.stdio; 290 import std.traits; 291 import std.conv; 292 import std.json; 293 294 import std.array; 295 import std.range; 296 297 /* ************************************** 298 script to follow 299 ****************************************/ 300 301 /// Thrown on script syntax errors and the sort. 302 class ScriptCompileException : Exception { 303 this(string msg, int lineNumber, string file = __FILE__, size_t line = __LINE__) { 304 super(to!string(lineNumber) ~ ": " ~ msg, file, line); 305 } 306 } 307 308 /// Thrown on things like interpretation failures. 309 class ScriptRuntimeException : Exception { 310 this(string msg, int lineNumber, string file = __FILE__, size_t line = __LINE__) { 311 super(to!string(lineNumber) ~ ": " ~ msg, file, line); 312 } 313 } 314 315 /// This represents an exception thrown by `throw x;` inside the script as it is interpreted. 316 class ScriptException : Exception { 317 /// 318 var payload; 319 /// 320 int lineNumber; 321 this(var payload, int lineNumber, string file = __FILE__, size_t line = __LINE__) { 322 this.payload = payload; 323 this.lineNumber = lineNumber; 324 super("script@" ~ to!string(lineNumber) ~ ": " ~ to!string(payload), file, line); 325 } 326 327 override string toString() { 328 return "script@" ~ to!string(lineNumber) ~ ": " ~ payload.get!string; 329 } 330 } 331 332 struct ScriptToken { 333 enum Type { identifier, keyword, symbol, string, int_number, float_number } 334 Type type; 335 string str; 336 string scriptFilename; 337 int lineNumber; 338 339 string wasSpecial; 340 } 341 342 // these need to be ordered from longest to shortest 343 // some of these aren't actually used, like struct and goto right now, but I want them reserved for later 344 private enum string[] keywords = [ 345 "function", "continue", 346 "__FILE__", "__LINE__", // these two are special to the lexer 347 "foreach", "json!q{", "default", "finally", 348 "return", "static", "struct", "import", "module", "assert", "switch", 349 "while", "catch", "throw", "scope", "break", "super", "class", "false", "mixin", "super", "macro", 350 "auto", // provided as an alias for var right now, may change later 351 "null", "else", "true", "eval", "goto", "enum", "case", "cast", 352 "var", "for", "try", "new", 353 "if", "do", 354 ]; 355 private enum string[] symbols = [ 356 "//", "/*", "/+", 357 "&&", "||", 358 "+=", "-=", "*=", "/=", "~=", "==", "<=", ">=","!=", 359 "&=", "|=", "^=", 360 "..", 361 ".",",",";",":", 362 "[", "]", "{", "}", "(", ")", 363 "&", "|", "^", 364 "+", "-", "*", "/", "=", "<", ">","~","!", 365 ]; 366 367 // we need reference semantics on this all the time 368 class TokenStream(TextStream) { 369 TextStream textStream; 370 string text; 371 int lineNumber = 1; 372 string scriptFilename; 373 374 void advance(ptrdiff_t size) { 375 foreach(i; 0 .. size) { 376 if(text.empty) 377 break; 378 if(text[0] == '\n') 379 lineNumber ++; 380 text = text[1 .. $]; 381 // text.popFront(); // don't want this because it pops too much trying to do its own UTF-8, which we already handled! 382 } 383 } 384 385 this(TextStream ts, string fn) { 386 textStream = ts; 387 scriptFilename = fn; 388 text = textStream.front; 389 popFront; 390 } 391 392 ScriptToken next; 393 394 // FIXME: might be worth changing this so i can peek far enough ahead to do () => expr lambdas. 395 ScriptToken peek; 396 bool peeked; 397 void pushFront(ScriptToken f) { 398 peek = f; 399 peeked = true; 400 } 401 402 ScriptToken front() { 403 if(peeked) 404 return peek; 405 else 406 return next; 407 } 408 409 bool empty() { 410 advanceSkips(); 411 return text.length == 0 && textStream.empty && !peeked; 412 } 413 414 int skipNext; 415 void advanceSkips() { 416 if(skipNext) { 417 skipNext--; 418 popFront(); 419 } 420 } 421 422 void popFront() { 423 if(peeked) { 424 peeked = false; 425 return; 426 } 427 428 assert(!empty); 429 mainLoop: 430 while(text.length) { 431 ScriptToken token; 432 token.lineNumber = lineNumber; 433 token.scriptFilename = scriptFilename; 434 435 if(text[0] == ' ' || text[0] == '\t' || text[0] == '\n' || text[0] == '\r') { 436 advance(1); 437 continue; 438 } else if(text[0] >= '0' && text[0] <= '9') { 439 int pos; 440 bool sawDot; 441 while(pos < text.length && ((text[pos] >= '0' && text[pos] <= '9') || text[pos] == '.')) { 442 if(text[pos] == '.') { 443 if(sawDot) 444 break; 445 else 446 sawDot = true; 447 } 448 pos++; 449 } 450 451 if(text[pos - 1] == '.') { 452 // This is something like "1.x", which is *not* a floating literal; it is UFCS on an int 453 sawDot = false; 454 pos --; 455 } 456 457 token.type = sawDot ? ScriptToken.Type.float_number : ScriptToken.Type.int_number; 458 token.str = text[0 .. pos]; 459 advance(pos); 460 } else if((text[0] >= 'a' && text[0] <= 'z') || (text[0] == '_') || (text[0] >= 'A' && text[0] <= 'Z') || text[0] == '$') { 461 bool found = false; 462 foreach(keyword; keywords) 463 if(text.length >= keyword.length && text[0 .. keyword.length] == keyword && 464 // making sure this isn't an identifier that starts with a keyword 465 (text.length == keyword.length || !( 466 ( 467 (text[keyword.length] >= '0' && text[keyword.length] <= '9') || 468 (text[keyword.length] >= 'a' && text[keyword.length] <= 'z') || 469 (text[keyword.length] == '_') || 470 (text[keyword.length] >= 'A' && text[keyword.length] <= 'Z') 471 ) 472 ))) 473 { 474 found = true; 475 if(keyword == "__FILE__") { 476 token.type = ScriptToken.Type..string; 477 token.str = to!string(token.scriptFilename); 478 token.wasSpecial = keyword; 479 } else if(keyword == "__LINE__") { 480 token.type = ScriptToken.Type.int_number; 481 token.str = to!string(token.lineNumber); 482 token.wasSpecial = keyword; 483 } else { 484 token.type = ScriptToken.Type.keyword; 485 // auto is done as an alias to var in the lexer just so D habits work there too 486 if(keyword == "auto") { 487 token.str = "var"; 488 token.wasSpecial = keyword; 489 } else 490 token.str = keyword; 491 } 492 advance(keyword.length); 493 break; 494 } 495 496 if(!found) { 497 token.type = ScriptToken.Type.identifier; 498 int pos; 499 if(text[0] == '$') 500 pos++; 501 502 while(pos < text.length 503 && ((text[pos] >= 'a' && text[pos] <= 'z') || 504 (text[pos] == '_') || 505 //(pos != 0 && text[pos] == '-') || // allow mid-identifier dashes for this-kind-of-name. For subtraction, add a space. 506 (text[pos] >= 'A' && text[pos] <= 'Z') || 507 (text[pos] >= '0' && text[pos] <= '9'))) 508 { 509 pos++; 510 } 511 512 token.str = text[0 .. pos]; 513 advance(pos); 514 } 515 } else if(text[0] == '"' || text[0] == '\'' || text[0] == '`' || 516 // Also supporting double curly quoted strings: “foo” which nest. This is the utf 8 coding: 517 (text.length >= 3 && text[0] == 0xe2 && text[1] == 0x80 && text[2] == 0x9c)) 518 { 519 char end = text[0]; // support single quote and double quote strings the same 520 int openCurlyQuoteCount = (end == 0xe2) ? 1 : 0; 521 bool escapingAllowed = end != '`'; // `` strings are raw, they don't support escapes. the others do. 522 token.type = ScriptToken.Type..string; 523 int pos = openCurlyQuoteCount ? 3 : 1; // skip the opening dchar 524 int started = pos; 525 bool escaped = false; 526 bool mustCopy = false; 527 528 bool allowInterpolation = text[0] == '"'; 529 530 bool atEnd() { 531 if(pos == text.length) 532 return false; 533 if(openCurlyQuoteCount) { 534 if(openCurlyQuoteCount == 1) 535 return (pos + 3 <= text.length && text[pos] == 0xe2 && text[pos+1] == 0x80 && text[pos+2] == 0x9d); // ” 536 else // greater than one means we nest 537 return false; 538 } else 539 return text[pos] == end; 540 } 541 542 bool interpolationDetected = false; 543 bool inInterpolate = false; 544 int interpolateCount = 0; 545 546 while(pos < text.length && (escaped || inInterpolate || !atEnd())) { 547 if(inInterpolate) { 548 if(text[pos] == '{') 549 interpolateCount++; 550 else if(text[pos] == '}') { 551 interpolateCount--; 552 if(interpolateCount == 0) 553 inInterpolate = false; 554 } 555 pos++; 556 continue; 557 } 558 559 if(escaped) { 560 mustCopy = true; 561 escaped = false; 562 } else { 563 if(text[pos] == '\\' && escapingAllowed) 564 escaped = true; 565 if(allowInterpolation && text[pos] == '#' && pos + 1 < text.length && text[pos + 1] == '{') { 566 interpolationDetected = true; 567 inInterpolate = true; 568 } 569 if(openCurlyQuoteCount) { 570 // also need to count curly quotes to support nesting 571 if(pos + 3 <= text.length && text[pos+0] == 0xe2 && text[pos+1] == 0x80 && text[pos+2] == 0x9c) // “ 572 openCurlyQuoteCount++; 573 if(pos + 3 <= text.length && text[pos+0] == 0xe2 && text[pos+1] == 0x80 && text[pos+2] == 0x9d) // ” 574 openCurlyQuoteCount--; 575 } 576 } 577 pos++; 578 } 579 580 if(pos == text.length && (escaped || inInterpolate || !atEnd())) 581 throw new ScriptCompileException("Unclosed string literal", token.lineNumber); 582 583 if(mustCopy) { 584 // there must be something escaped in there, so we need 585 // to copy it and properly handle those cases 586 string copy; 587 copy.reserve(pos + 4); 588 589 escaped = false; 590 foreach(idx, dchar ch; text[started .. pos]) { 591 if(escaped) { 592 escaped = false; 593 switch(ch) { 594 case '\\': copy ~= "\\"; break; 595 case 'n': copy ~= "\n"; break; 596 case 'r': copy ~= "\r"; break; 597 case 'a': copy ~= "\a"; break; 598 case 't': copy ~= "\t"; break; 599 case '#': copy ~= "#"; break; 600 case '"': copy ~= "\""; break; 601 case '\'': copy ~= "'"; break; 602 default: 603 throw new ScriptCompileException("Unknown escape char " ~ cast(char) ch, token.lineNumber); 604 } 605 continue; 606 } else if(ch == '\\') { 607 escaped = true; 608 continue; 609 } 610 copy ~= ch; 611 } 612 613 token.str = copy; 614 } else { 615 token.str = text[started .. pos]; 616 } 617 if(interpolationDetected) 618 token.wasSpecial = "\""; 619 advance(pos + ((end == 0xe2) ? 3 : 1)); // skip the closing " too 620 } else { 621 // let's check all symbols 622 bool found = false; 623 foreach(symbol; symbols) 624 if(text.length >= symbol.length && text[0 .. symbol.length] == symbol) { 625 626 if(symbol == "//") { 627 // one line comment 628 int pos = 0; 629 while(pos < text.length && text[pos] != '\n' && text[0] != '\r') 630 pos++; 631 advance(pos); 632 continue mainLoop; 633 } else if(symbol == "/*") { 634 int pos = 0; 635 while(pos + 1 < text.length && text[pos..pos+2] != "*/") 636 pos++; 637 638 if(pos + 1 == text.length) 639 throw new ScriptCompileException("unclosed /* */ comment", lineNumber); 640 641 advance(pos + 2); 642 continue mainLoop; 643 644 } else if(symbol == "/+") { 645 // FIXME: nesting comment 646 } 647 648 found = true; 649 token.type = ScriptToken.Type.symbol; 650 token.str = symbol; 651 advance(symbol.length); 652 break; 653 } 654 655 if(!found) { 656 // FIXME: make sure this gives a valid utf-8 sequence 657 throw new ScriptCompileException("unknown token " ~ text[0], lineNumber); 658 } 659 } 660 661 next = token; 662 return; 663 } 664 665 textStream.popFront(); 666 if(!textStream.empty()) { 667 text = textStream.front; 668 goto mainLoop; 669 } 670 671 return; 672 } 673 674 } 675 676 TokenStream!TextStream lexScript(TextStream)(TextStream textStream, string scriptFilename) if(is(ElementType!TextStream == string)) { 677 return new TokenStream!TextStream(textStream, scriptFilename); 678 } 679 680 class MacroPrototype : PrototypeObject { 681 var func; 682 683 // macros are basically functions that get special treatment for their arguments 684 // they are passed as AST objects instead of interpreted 685 // calling an AST object will interpret it in the script 686 this(var func) { 687 this.func = func; 688 this._properties["opCall"] = (var _this, var[] args) { 689 return func.apply(_this, args); 690 }; 691 } 692 } 693 694 alias helper(alias T) = T; 695 // alternative to virtual function for converting the expression objects to script objects 696 void addChildElementsOfExpressionToScriptExpressionObject(ClassInfo c, Expression _thisin, PrototypeObject sc, ref var obj) { 697 foreach(itemName; __traits(allMembers, mixin(__MODULE__))) 698 static if(__traits(compiles, __traits(getMember, mixin(__MODULE__), itemName))) { 699 alias Class = helper!(__traits(getMember, mixin(__MODULE__), itemName)); 700 static if(is(Class : Expression)) if(c == typeid(Class)) { 701 auto _this = cast(Class) _thisin; 702 foreach(memberName; __traits(allMembers, Class)) { 703 alias member = helper!(__traits(getMember, Class, memberName)); 704 705 static if(is(typeof(member) : Expression)) { 706 auto lol = __traits(getMember, _this, memberName); 707 if(lol is null) 708 obj[memberName] = null; 709 else 710 obj[memberName] = lol.toScriptExpressionObject(sc); 711 } 712 static if(is(typeof(member) : Expression[])) { 713 obj[memberName] = var.emptyArray; 714 foreach(m; __traits(getMember, _this, memberName)) 715 if(m !is null) 716 obj[memberName] ~= m.toScriptExpressionObject(sc); 717 else 718 obj[memberName] ~= null; 719 } 720 static if(is(typeof(member) : string) || is(typeof(member) : long) || is(typeof(member) : real) || is(typeof(member) : bool)) { 721 obj[memberName] = __traits(getMember, _this, memberName); 722 } 723 } 724 } 725 } 726 } 727 728 struct InterpretResult { 729 var value; 730 PrototypeObject sc; 731 enum FlowControl { Normal, Return, Continue, Break, Goto } 732 FlowControl flowControl; 733 string flowControlDetails; // which label 734 } 735 736 class Expression { 737 abstract InterpretResult interpret(PrototypeObject sc); 738 739 // this returns an AST object that can be inspected and possibly altered 740 // by the script. Calling the returned object will interpret the object in 741 // the original scope passed 742 var toScriptExpressionObject(PrototypeObject sc) { 743 var obj = var.emptyObject; 744 745 obj["type"] = typeid(this).name; 746 obj["toSourceCode"] = (var _this, var[] args) { 747 Expression e = this; 748 return var(e.toString()); 749 }; 750 obj["opCall"] = (var _this, var[] args) { 751 Expression e = this; 752 // FIXME: if they changed the properties in the 753 // script, we should update them here too. 754 return e.interpret(sc).value; 755 }; 756 obj["interpolate"] = (var _this, var[] args) { 757 StringLiteralExpression e = cast(StringLiteralExpression) this; 758 if(!e) 759 return var(null); 760 return e.interpolate(args.length ? args[0] : var(null), sc); 761 }; 762 763 764 // adding structure is going to be a little bit magical 765 // I could have done this with a virtual function, but I'm lazy. 766 addChildElementsOfExpressionToScriptExpressionObject(typeid(this), this, sc, obj); 767 768 return obj; 769 } 770 771 string toInterpretedString(PrototypeObject sc) { 772 return toString(); 773 } 774 } 775 776 class MixinExpression : Expression { 777 Expression e1; 778 this(Expression e1) { 779 this.e1 = e1; 780 } 781 782 override string toString() { return "mixin(" ~ e1.toString() ~ ")"; } 783 784 override InterpretResult interpret(PrototypeObject sc) { 785 return InterpretResult(.interpret(e1.interpret(sc).value.get!string ~ ";", sc), sc); 786 } 787 } 788 789 class StringLiteralExpression : Expression { 790 string content; 791 bool allowInterpolation; 792 793 ScriptToken token; 794 795 override string toString() { 796 import std.string : replace; 797 return "\"" ~ content.replace(`\`, `\\`).replace("\"", "\\\"") ~ "\""; 798 } 799 800 this(ScriptToken token) { 801 this.token = token; 802 this(token.str); 803 if(token.wasSpecial == "\"") 804 allowInterpolation = true; 805 806 } 807 808 this(string s) { 809 content = s; 810 } 811 812 var interpolate(var funcObj, PrototypeObject sc) { 813 import std.string : indexOf; 814 if(allowInterpolation) { 815 string r; 816 817 auto c = content; 818 auto idx = c.indexOf("#{"); 819 while(idx != -1) { 820 r ~= c[0 .. idx]; 821 c = c[idx + 2 .. $]; 822 idx = 0; 823 int open = 1; 824 while(idx < c.length) { 825 if(c[idx] == '}') 826 open--; 827 else if(c[idx] == '{') 828 open++; 829 if(open == 0) 830 break; 831 idx++; 832 } 833 if(open != 0) 834 throw new ScriptRuntimeException("Unclosed interpolation thing", token.lineNumber); 835 auto code = c[0 .. idx]; 836 837 var result = .interpret(code, sc); 838 839 if(funcObj == var(null)) 840 r ~= result.get!string; 841 else 842 r ~= funcObj(result).get!string; 843 844 c = c[idx + 1 .. $]; 845 idx = c.indexOf("#{"); 846 } 847 848 r ~= c; 849 return var(r); 850 } else { 851 return var(content); 852 } 853 } 854 855 override InterpretResult interpret(PrototypeObject sc) { 856 return InterpretResult(interpolate(var(null), sc), sc); 857 } 858 } 859 860 class BoolLiteralExpression : Expression { 861 bool literal; 862 this(string l) { 863 literal = to!bool(l); 864 } 865 866 override string toString() { return to!string(literal); } 867 868 override InterpretResult interpret(PrototypeObject sc) { 869 return InterpretResult(var(literal), sc); 870 } 871 } 872 873 class IntLiteralExpression : Expression { 874 long literal; 875 876 this(string s) { 877 literal = to!long(s); 878 } 879 880 override string toString() { return to!string(literal); } 881 882 override InterpretResult interpret(PrototypeObject sc) { 883 return InterpretResult(var(literal), sc); 884 } 885 } 886 class FloatLiteralExpression : Expression { 887 this(string s) { 888 literal = to!real(s); 889 } 890 real literal; 891 override string toString() { return to!string(literal); } 892 override InterpretResult interpret(PrototypeObject sc) { 893 return InterpretResult(var(literal), sc); 894 } 895 } 896 class NullLiteralExpression : Expression { 897 this() {} 898 override string toString() { return "null"; } 899 900 override InterpretResult interpret(PrototypeObject sc) { 901 var n; 902 return InterpretResult(n, sc); 903 } 904 } 905 class NegationExpression : Expression { 906 Expression e; 907 this(Expression e) { this.e = e;} 908 override string toString() { return "-" ~ e.toString(); } 909 910 override InterpretResult interpret(PrototypeObject sc) { 911 var n = e.interpret(sc).value; 912 return InterpretResult(-n, sc); 913 } 914 } 915 class ArrayLiteralExpression : Expression { 916 this() {} 917 918 override string toString() { 919 string s = "["; 920 foreach(i, ele; elements) { 921 if(i) s ~= ", "; 922 s ~= ele.toString(); 923 } 924 s ~= "]"; 925 return s; 926 } 927 928 Expression[] elements; 929 override InterpretResult interpret(PrototypeObject sc) { 930 var n = var.emptyArray; 931 foreach(i, element; elements) 932 n[i] = element.interpret(sc).value; 933 return InterpretResult(n, sc); 934 } 935 } 936 class ObjectLiteralExpression : Expression { 937 Expression[string] elements; 938 939 override string toString() { 940 string s = "json!q{"; 941 bool first = true; 942 foreach(k, e; elements) { 943 if(first) 944 first = false; 945 else 946 s ~= ", "; 947 948 s ~= "\"" ~ k ~ "\":"; // FIXME: escape if needed 949 s ~= e.toString(); 950 } 951 952 s ~= "}"; 953 return s; 954 } 955 956 PrototypeObject backing; 957 this(PrototypeObject backing = null) { 958 this.backing = backing; 959 } 960 961 override InterpretResult interpret(PrototypeObject sc) { 962 var n; 963 if(backing is null) 964 n = var.emptyObject; 965 else 966 n._object = backing; 967 968 foreach(k, v; elements) 969 n[k] = v.interpret(sc).value; 970 971 return InterpretResult(n, sc); 972 } 973 } 974 class FunctionLiteralExpression : Expression { 975 this() { 976 // we want this to not be null at all when we're interpreting since it is used as a comparison for a magic operation 977 if(DefaultArgumentDummyObject is null) 978 DefaultArgumentDummyObject = new PrototypeObject(); 979 } 980 981 this(VariableDeclaration args, Expression bod, PrototypeObject lexicalScope = null) { 982 this(); 983 this.arguments = args; 984 this.functionBody = bod; 985 this.lexicalScope = lexicalScope; 986 } 987 988 override string toString() { 989 string s = (isMacro ? "macro" : "function") ~ " ("; 990 s ~= arguments.toString(); 991 992 s ~= ") "; 993 s ~= functionBody.toString(); 994 return s; 995 } 996 997 /* 998 function identifier (arg list) expression 999 1000 so 1001 var e = function foo() 10; // valid 1002 var e = function foo() { return 10; } // also valid 1003 1004 // the return value is just the last expression's result that was evaluated 1005 // to return void, be sure to do a "return;" at the end of the function 1006 */ 1007 VariableDeclaration arguments; 1008 Expression functionBody; // can be a ScopeExpression btw 1009 1010 PrototypeObject lexicalScope; 1011 1012 bool isMacro; 1013 1014 override InterpretResult interpret(PrototypeObject sc) { 1015 assert(DefaultArgumentDummyObject !is null); 1016 var v; 1017 v._function = (var _this, var[] args) { 1018 auto argumentsScope = new PrototypeObject(); 1019 PrototypeObject scToUse; 1020 if(lexicalScope is null) 1021 scToUse = sc; 1022 else { 1023 scToUse = lexicalScope; 1024 scToUse._secondary = sc; 1025 } 1026 1027 argumentsScope.prototype = scToUse; 1028 1029 argumentsScope._getMember("this", false, false) = _this; 1030 argumentsScope._getMember("_arguments", false, false) = args; 1031 argumentsScope._getMember("_thisfunc", false, false) = v; 1032 1033 if(arguments) 1034 foreach(i, identifier; arguments.identifiers) { 1035 argumentsScope._getMember(identifier, false, false); // create it in this scope... 1036 if(i < args.length && !(args[i].payloadType() == var.Type.Object && args[i]._payload._object is DefaultArgumentDummyObject)) 1037 argumentsScope._getMember(identifier, false, true) = args[i]; 1038 else 1039 if(arguments.initializers[i] !is null) 1040 argumentsScope._getMember(identifier, false, true) = arguments.initializers[i].interpret(sc).value; 1041 } 1042 1043 if(functionBody !is null) 1044 return functionBody.interpret(argumentsScope).value; 1045 else { 1046 assert(0); 1047 } 1048 }; 1049 if(isMacro) { 1050 var n = var.emptyObject; 1051 n._object = new MacroPrototype(v); 1052 v = n; 1053 } 1054 return InterpretResult(v, sc); 1055 } 1056 } 1057 1058 class CastExpression : Expression { 1059 string type; 1060 Expression e1; 1061 1062 override string toString() { 1063 return "cast(" ~ type ~ ") " ~ e1.toString(); 1064 } 1065 1066 override InterpretResult interpret(PrototypeObject sc) { 1067 var n = e1.interpret(sc).value; 1068 foreach(possibleType; CtList!("int", "long", "float", "double", "real", "char", "dchar", "string", "int[]", "string[]", "float[]")) { 1069 if(type == possibleType) 1070 n = mixin("cast(" ~ possibleType ~ ") n"); 1071 } 1072 1073 return InterpretResult(n, sc); 1074 } 1075 } 1076 1077 class VariableDeclaration : Expression { 1078 string[] identifiers; 1079 Expression[] initializers; 1080 1081 this() {} 1082 1083 override string toString() { 1084 string s = ""; 1085 foreach(i, ident; identifiers) { 1086 if(i) 1087 s ~= ", "; 1088 s ~= "var " ~ ident; 1089 if(initializers[i] !is null) 1090 s ~= " = " ~ initializers[i].toString(); 1091 } 1092 return s; 1093 } 1094 1095 1096 override InterpretResult interpret(PrototypeObject sc) { 1097 var n; 1098 1099 foreach(i, identifier; identifiers) { 1100 n = sc._getMember(identifier, false, false); 1101 auto initializer = initializers[i]; 1102 if(initializer) { 1103 n = initializer.interpret(sc).value; 1104 sc._getMember(identifier, false, false) = n; 1105 } 1106 } 1107 return InterpretResult(n, sc); 1108 } 1109 } 1110 1111 template CtList(T...) { alias CtList = T; } 1112 1113 class BinaryExpression : Expression { 1114 string op; 1115 Expression e1; 1116 Expression e2; 1117 1118 override string toString() { 1119 return e1.toString() ~ " " ~ op ~ " " ~ e2.toString(); 1120 } 1121 1122 override string toInterpretedString(PrototypeObject sc) { 1123 return e1.toInterpretedString(sc) ~ " " ~ op ~ " " ~ e2.toInterpretedString(sc); 1124 } 1125 1126 this(string op, Expression e1, Expression e2) { 1127 this.op = op; 1128 this.e1 = e1; 1129 this.e2 = e2; 1130 } 1131 1132 override InterpretResult interpret(PrototypeObject sc) { 1133 var left = e1.interpret(sc).value; 1134 var right = e2.interpret(sc).value; 1135 1136 //writeln(left, " "~op~" ", right); 1137 1138 var n; 1139 foreach(ctOp; CtList!("+", "-", "*", "/", "==", "!=", "<=", ">=", ">", "<", "~", "&&", "||", "&", "|", "^")) 1140 if(ctOp == op) { 1141 n = mixin("left "~ctOp~" right"); 1142 } 1143 1144 return InterpretResult(n, sc); 1145 } 1146 } 1147 1148 class OpAssignExpression : Expression { 1149 string op; 1150 Expression e1; 1151 Expression e2; 1152 1153 this(string op, Expression e1, Expression e2) { 1154 this.op = op; 1155 this.e1 = e1; 1156 this.e2 = e2; 1157 } 1158 1159 override string toString() { 1160 return e1.toString() ~ " " ~ op ~ "= " ~ e2.toString(); 1161 } 1162 1163 override InterpretResult interpret(PrototypeObject sc) { 1164 1165 auto v = cast(VariableExpression) e1; 1166 if(v is null) 1167 throw new ScriptRuntimeException("not an lvalue", 0 /* FIXME */); 1168 1169 var right = e2.interpret(sc).value; 1170 1171 //writeln(left, " "~op~"= ", right); 1172 1173 var n; 1174 foreach(ctOp; CtList!("+=", "-=", "*=", "/=", "~=", "&=", "|=", "^=")) 1175 if(ctOp[0..1] == op) 1176 n = mixin("v.getVar(sc) "~ctOp~" right"); 1177 1178 // FIXME: ensure the variable is updated in scope too 1179 1180 return InterpretResult(n, sc); 1181 1182 } 1183 } 1184 1185 class AssignExpression : Expression { 1186 Expression e1; 1187 Expression e2; 1188 bool suppressOverloading; 1189 1190 this(Expression e1, Expression e2, bool suppressOverloading = false) { 1191 this.e1 = e1; 1192 this.e2 = e2; 1193 this.suppressOverloading = suppressOverloading; 1194 } 1195 1196 override string toString() { return e1.toString() ~ " = " ~ e2.toString(); } 1197 1198 override InterpretResult interpret(PrototypeObject sc) { 1199 auto v = cast(VariableExpression) e1; 1200 if(v is null) 1201 throw new ScriptRuntimeException("not an lvalue", 0 /* FIXME */); 1202 1203 auto ret = v.setVar(sc, e2.interpret(sc).value, false, suppressOverloading); 1204 1205 return InterpretResult(ret, sc); 1206 } 1207 } 1208 1209 1210 class UnaryExpression : Expression { 1211 string op; 1212 Expression e; 1213 // FIXME 1214 1215 override InterpretResult interpret(PrototypeObject sc) { 1216 return InterpretResult(); 1217 } 1218 } 1219 1220 class VariableExpression : Expression { 1221 string identifier; 1222 1223 this(string identifier) { 1224 this.identifier = identifier; 1225 } 1226 1227 override string toString() { 1228 return identifier; 1229 } 1230 1231 override string toInterpretedString(PrototypeObject sc) { 1232 return getVar(sc).get!string; 1233 } 1234 1235 ref var getVar(PrototypeObject sc, bool recurse = true) { 1236 return sc._getMember(identifier, true /* FIXME: recurse?? */, true); 1237 } 1238 1239 ref var setVar(PrototypeObject sc, var t, bool recurse = true, bool suppressOverloading = false) { 1240 return sc._setMember(identifier, t, true /* FIXME: recurse?? */, true, suppressOverloading); 1241 } 1242 1243 ref var getVarFrom(PrototypeObject sc, ref var v) { 1244 return v[identifier]; 1245 } 1246 1247 override InterpretResult interpret(PrototypeObject sc) { 1248 return InterpretResult(getVar(sc), sc); 1249 } 1250 } 1251 1252 class DotVarExpression : VariableExpression { 1253 Expression e1; 1254 VariableExpression e2; 1255 bool recurse = true; 1256 1257 this(Expression e1) { 1258 this.e1 = e1; 1259 super(null); 1260 } 1261 1262 this(Expression e1, VariableExpression e2, bool recurse = true) { 1263 this.e1 = e1; 1264 this.e2 = e2; 1265 this.recurse = recurse; 1266 //assert(typeid(e2) == typeid(VariableExpression)); 1267 super("<do not use>");//e1.identifier ~ "." ~ e2.identifier); 1268 } 1269 1270 override string toString() { 1271 return e1.toString() ~ "." ~ e2.toString(); 1272 } 1273 1274 override ref var getVar(PrototypeObject sc, bool recurse = true) { 1275 if(!this.recurse) { 1276 // this is a special hack... 1277 if(auto ve = cast(VariableExpression) e1) { 1278 return ve.getVar(sc)._getOwnProperty(e2.identifier); 1279 } 1280 assert(0); 1281 } 1282 1283 if(auto ve = cast(VariableExpression) e1) 1284 return this.getVarFrom(sc, ve.getVar(sc, recurse)); 1285 else if(cast(StringLiteralExpression) e1 && e2.identifier == "interpolate") { 1286 auto se = cast(StringLiteralExpression) e1; 1287 var* functor = new var; 1288 //if(!se.allowInterpolation) 1289 //throw new ScriptRuntimeException("Cannot interpolate this string", se.token.lineNumber); 1290 (*functor)._function = (var _this, var[] args) { 1291 return se.interpolate(args.length ? args[0] : var(null), sc); 1292 }; 1293 return *functor; 1294 } else { 1295 // make a temporary for the lhs 1296 auto v = new var(); 1297 *v = e1.interpret(sc).value; 1298 return this.getVarFrom(sc, *v); 1299 } 1300 } 1301 1302 override ref var setVar(PrototypeObject sc, var t, bool recurse = true, bool suppressOverloading = false) { 1303 if(suppressOverloading) 1304 return e1.interpret(sc).value.opIndexAssignNoOverload(t, e2.identifier); 1305 else 1306 return e1.interpret(sc).value.opIndexAssign(t, e2.identifier); 1307 } 1308 1309 1310 override ref var getVarFrom(PrototypeObject sc, ref var v) { 1311 return e2.getVarFrom(sc, v); 1312 } 1313 } 1314 1315 class IndexExpression : VariableExpression { 1316 Expression e1; 1317 Expression e2; 1318 1319 this(Expression e1, Expression e2) { 1320 this.e1 = e1; 1321 this.e2 = e2; 1322 super(null); 1323 } 1324 1325 override string toString() { 1326 return e1.toString() ~ "[" ~ e2.toString() ~ "]"; 1327 } 1328 1329 override ref var getVar(PrototypeObject sc, bool recurse = true) { 1330 if(auto ve = cast(VariableExpression) e1) 1331 return ve.getVar(sc, recurse)[e2.interpret(sc).value]; 1332 else { 1333 auto v = new var(); 1334 *v = e1.interpret(sc).value; 1335 return this.getVarFrom(sc, *v); 1336 } 1337 } 1338 1339 override ref var setVar(PrototypeObject sc, var t, bool recurse = true, bool suppressOverloading = false) { 1340 return getVar(sc,recurse) = t; 1341 } 1342 } 1343 1344 class SliceExpression : Expression { 1345 // e1[e2 .. e3] 1346 Expression e1; 1347 Expression e2; 1348 Expression e3; 1349 1350 this(Expression e1, Expression e2, Expression e3) { 1351 this.e1 = e1; 1352 this.e2 = e2; 1353 this.e3 = e3; 1354 } 1355 1356 override string toString() { 1357 return e1.toString() ~ "[" ~ e2.toString() ~ " .. " ~ e3.toString() ~ "]"; 1358 } 1359 1360 override InterpretResult interpret(PrototypeObject sc) { 1361 var lhs = e1.interpret(sc).value; 1362 1363 auto specialScope = new PrototypeObject(); 1364 specialScope.prototype = sc; 1365 specialScope._getMember("$", false, false) = lhs.length; 1366 1367 return InterpretResult(lhs[e2.interpret(specialScope).value .. e3.interpret(specialScope).value], sc); 1368 } 1369 } 1370 1371 1372 class LoopControlExpression : Expression { 1373 InterpretResult.FlowControl op; 1374 this(string op) { 1375 if(op == "continue") 1376 this.op = InterpretResult.FlowControl.Continue; 1377 else if(op == "break") 1378 this.op = InterpretResult.FlowControl.Break; 1379 else assert(0, op); 1380 } 1381 1382 override string toString() { 1383 import std.string; 1384 return to!string(this.op).toLower(); 1385 } 1386 1387 override InterpretResult interpret(PrototypeObject sc) { 1388 return InterpretResult(var(null), sc, op); 1389 } 1390 } 1391 1392 1393 class ReturnExpression : Expression { 1394 Expression value; 1395 1396 this(Expression v) { 1397 value = v; 1398 } 1399 1400 override string toString() { return "return " ~ value.toString(); } 1401 1402 override InterpretResult interpret(PrototypeObject sc) { 1403 return InterpretResult(value.interpret(sc).value, sc, InterpretResult.FlowControl.Return); 1404 } 1405 } 1406 1407 class ScopeExpression : Expression { 1408 this(Expression[] expressions) { 1409 this.expressions = expressions; 1410 } 1411 1412 Expression[] expressions; 1413 1414 override string toString() { 1415 string s; 1416 s = "{\n"; 1417 foreach(expr; expressions) { 1418 s ~= "\t"; 1419 s ~= expr.toString(); 1420 s ~= ";\n"; 1421 } 1422 s ~= "}"; 1423 return s; 1424 } 1425 1426 override InterpretResult interpret(PrototypeObject sc) { 1427 var ret; 1428 1429 auto innerScope = new PrototypeObject(); 1430 innerScope.prototype = sc; 1431 1432 innerScope._getMember("__scope_exit", false, false) = var.emptyArray; 1433 innerScope._getMember("__scope_success", false, false) = var.emptyArray; 1434 innerScope._getMember("__scope_failure", false, false) = var.emptyArray; 1435 1436 scope(exit) { 1437 foreach(func; innerScope._getMember("__scope_exit", false, true)) 1438 func(); 1439 } 1440 scope(success) { 1441 foreach(func; innerScope._getMember("__scope_success", false, true)) 1442 func(); 1443 } 1444 scope(failure) { 1445 foreach(func; innerScope._getMember("__scope_failure", false, true)) 1446 func(); 1447 } 1448 1449 foreach(expression; expressions) { 1450 auto res = expression.interpret(innerScope); 1451 ret = res.value; 1452 if(res.flowControl != InterpretResult.FlowControl.Normal) 1453 return InterpretResult(ret, sc, res.flowControl); 1454 } 1455 return InterpretResult(ret, sc); 1456 } 1457 } 1458 1459 class ForeachExpression : Expression { 1460 VariableDeclaration decl; 1461 Expression subject; 1462 Expression loopBody; 1463 1464 override InterpretResult interpret(PrototypeObject sc) { 1465 var result; 1466 1467 assert(loopBody !is null); 1468 1469 auto loopScope = new PrototypeObject(); 1470 loopScope.prototype = sc; 1471 1472 InterpretResult.FlowControl flowControl; 1473 1474 static string doLoopBody() { return q{ 1475 if(decl.identifiers.length > 1) { 1476 sc._getMember(decl.identifiers[0], false, false) = i; 1477 sc._getMember(decl.identifiers[1], false, false) = item; 1478 } else { 1479 sc._getMember(decl.identifiers[0], false, false) = item; 1480 } 1481 1482 auto res = loopBody.interpret(loopScope); 1483 result = res.value; 1484 flowControl = res.flowControl; 1485 if(flowControl == InterpretResult.FlowControl.Break) 1486 break; 1487 if(flowControl == InterpretResult.FlowControl.Return) 1488 break; 1489 //if(flowControl == InterpretResult.FlowControl.Continue) 1490 // this is fine, we still want to do the advancement 1491 };} 1492 1493 var what = subject.interpret(sc).value; 1494 foreach(i, item; what) { 1495 mixin(doLoopBody()); 1496 } 1497 1498 if(flowControl != InterpretResult.FlowControl.Return) 1499 flowControl = InterpretResult.FlowControl.Normal; 1500 1501 return InterpretResult(result, sc, flowControl); 1502 } 1503 } 1504 1505 class ForExpression : Expression { 1506 Expression initialization; 1507 Expression condition; 1508 Expression advancement; 1509 Expression loopBody; 1510 1511 this() {} 1512 1513 override InterpretResult interpret(PrototypeObject sc) { 1514 var result; 1515 1516 assert(loopBody !is null); 1517 1518 auto loopScope = new PrototypeObject(); 1519 loopScope.prototype = sc; 1520 if(initialization !is null) 1521 initialization.interpret(loopScope); 1522 1523 InterpretResult.FlowControl flowControl; 1524 1525 static string doLoopBody() { return q{ 1526 auto res = loopBody.interpret(loopScope); 1527 result = res.value; 1528 flowControl = res.flowControl; 1529 if(flowControl == InterpretResult.FlowControl.Break) 1530 break; 1531 if(flowControl == InterpretResult.FlowControl.Return) 1532 break; 1533 //if(flowControl == InterpretResult.FlowControl.Continue) 1534 // this is fine, we still want to do the advancement 1535 if(advancement) 1536 advancement.interpret(loopScope); 1537 };} 1538 1539 if(condition !is null) { 1540 while(condition.interpret(loopScope).value) { 1541 mixin(doLoopBody()); 1542 } 1543 } else 1544 while(true) { 1545 mixin(doLoopBody()); 1546 } 1547 1548 if(flowControl != InterpretResult.FlowControl.Return) 1549 flowControl = InterpretResult.FlowControl.Normal; 1550 1551 return InterpretResult(result, sc, flowControl); 1552 } 1553 1554 override string toString() { 1555 string code = "for("; 1556 if(initialization !is null) 1557 code ~= initialization.toString(); 1558 code ~= "; "; 1559 if(condition !is null) 1560 code ~= condition.toString(); 1561 code ~= "; "; 1562 if(advancement !is null) 1563 code ~= advancement.toString(); 1564 code ~= ") "; 1565 code ~= loopBody.toString(); 1566 1567 return code; 1568 } 1569 } 1570 1571 class IfExpression : Expression { 1572 Expression condition; 1573 Expression ifTrue; 1574 Expression ifFalse; 1575 1576 this() {} 1577 1578 override InterpretResult interpret(PrototypeObject sc) { 1579 InterpretResult result; 1580 assert(condition !is null); 1581 1582 auto ifScope = new PrototypeObject(); 1583 ifScope.prototype = sc; 1584 1585 if(condition.interpret(ifScope).value) { 1586 if(ifTrue !is null) 1587 result = ifTrue.interpret(ifScope); 1588 } else { 1589 if(ifFalse !is null) 1590 result = ifFalse.interpret(ifScope); 1591 } 1592 return InterpretResult(result.value, sc, result.flowControl); 1593 } 1594 1595 override string toString() { 1596 string code = "if("; 1597 code ~= condition.toString(); 1598 code ~= ") "; 1599 if(ifTrue !is null) 1600 code ~= ifTrue.toString(); 1601 else 1602 code ~= " { }"; 1603 if(ifFalse !is null) 1604 code ~= " else " ~ ifFalse.toString(); 1605 return code; 1606 } 1607 } 1608 1609 // this is kinda like a placement new, and currently isn't exposed inside the language, 1610 // but is used for class inheritance 1611 class ShallowCopyExpression : Expression { 1612 Expression e1; 1613 Expression e2; 1614 1615 this(Expression e1, Expression e2) { 1616 this.e1 = e1; 1617 this.e2 = e2; 1618 } 1619 1620 override InterpretResult interpret(PrototypeObject sc) { 1621 auto v = cast(VariableExpression) e1; 1622 if(v is null) 1623 throw new ScriptRuntimeException("not an lvalue", 0 /* FIXME */); 1624 1625 v.getVar(sc, false)._object.copyPropertiesFrom(e2.interpret(sc).value._object); 1626 1627 return InterpretResult(var(null), sc); 1628 } 1629 1630 } 1631 1632 class NewExpression : Expression { 1633 Expression what; 1634 Expression[] args; 1635 this(Expression w) { 1636 what = w; 1637 } 1638 1639 override InterpretResult interpret(PrototypeObject sc) { 1640 assert(what !is null); 1641 1642 var[] args; 1643 foreach(arg; this.args) 1644 args ~= arg.interpret(sc).value; 1645 1646 var original = what.interpret(sc).value; 1647 var n = original._copy; 1648 if(n.payloadType() == var.Type.Object) { 1649 var ctor = original.prototype ? original.prototype._getOwnProperty("__ctor") : var(null); 1650 if(ctor) 1651 ctor.apply(n, args); 1652 } 1653 1654 return InterpretResult(n, sc); 1655 } 1656 } 1657 1658 class ThrowExpression : Expression { 1659 Expression whatToThrow; 1660 ScriptToken where; 1661 1662 this(Expression e, ScriptToken where) { 1663 whatToThrow = e; 1664 this.where = where; 1665 } 1666 1667 override InterpretResult interpret(PrototypeObject sc) { 1668 assert(whatToThrow !is null); 1669 throw new ScriptException(whatToThrow.interpret(sc).value, where.lineNumber); 1670 assert(0); 1671 } 1672 } 1673 1674 class ExceptionBlockExpression : Expression { 1675 Expression tryExpression; 1676 1677 string[] catchVarDecls; 1678 Expression[] catchExpressions; 1679 1680 Expression[] finallyExpressions; 1681 1682 override InterpretResult interpret(PrototypeObject sc) { 1683 InterpretResult result; 1684 result.sc = sc; 1685 assert(tryExpression !is null); 1686 assert(catchVarDecls.length == catchExpressions.length); 1687 1688 if(catchExpressions.length || (catchExpressions.length == 0 && finallyExpressions.length == 0)) 1689 try { 1690 result = tryExpression.interpret(sc); 1691 } catch(Exception e) { 1692 var ex = var.emptyObject; 1693 ex.type = typeid(e).name; 1694 ex.msg = e.msg; 1695 ex.file = e.file; 1696 ex.line = e.line; 1697 1698 // FIXME: this only allows one but it might be nice to actually do different types at some point 1699 if(catchExpressions.length) 1700 foreach(i, ce; catchExpressions) { 1701 auto catchScope = new PrototypeObject(); 1702 catchScope.prototype = sc; 1703 catchScope._getMember(catchVarDecls[i], false, false) = ex; 1704 1705 result = ce.interpret(catchScope); 1706 } else 1707 result = InterpretResult(ex, sc); 1708 } finally { 1709 foreach(fe; finallyExpressions) 1710 result = fe.interpret(sc); 1711 } 1712 else 1713 try { 1714 result = tryExpression.interpret(sc); 1715 } finally { 1716 foreach(fe; finallyExpressions) 1717 result = fe.interpret(sc); 1718 } 1719 1720 return result; 1721 } 1722 } 1723 1724 class ParentheticalExpression : Expression { 1725 Expression inside; 1726 this(Expression inside) { 1727 this.inside = inside; 1728 } 1729 1730 override InterpretResult interpret(PrototypeObject sc) { 1731 return InterpretResult(inside.interpret(sc).value, sc); 1732 } 1733 } 1734 1735 class AssertKeyword : Expression { 1736 ScriptToken token; 1737 this(ScriptToken token) { 1738 this.token = token; 1739 } 1740 override string toString() { 1741 return "assert"; 1742 } 1743 1744 override InterpretResult interpret(PrototypeObject sc) { 1745 if(AssertKeywordObject is null) 1746 AssertKeywordObject = new PrototypeObject(); 1747 var dummy; 1748 dummy._object = AssertKeywordObject; 1749 return InterpretResult(dummy, sc); 1750 } 1751 } 1752 1753 PrototypeObject AssertKeywordObject; 1754 PrototypeObject DefaultArgumentDummyObject; 1755 1756 class CallExpression : Expression { 1757 Expression func; 1758 Expression[] arguments; 1759 1760 override string toString() { 1761 string s = func.toString() ~ "("; 1762 foreach(i, arg; arguments) { 1763 if(i) s ~= ", "; 1764 s ~= arg.toString(); 1765 } 1766 1767 s ~= ")"; 1768 return s; 1769 } 1770 1771 this(Expression func) { 1772 this.func = func; 1773 } 1774 1775 override InterpretResult interpret(PrototypeObject sc) { 1776 if(auto asrt = cast(AssertKeyword) func) { 1777 auto assertExpression = arguments[0]; 1778 Expression assertString; 1779 if(arguments.length > 1) 1780 assertString = arguments[1]; 1781 1782 var v = assertExpression.interpret(sc).value; 1783 1784 if(!v) 1785 throw new ScriptException( 1786 var(this.toString() ~ " failed, got: " ~ assertExpression.toInterpretedString(sc)), 1787 asrt.token.lineNumber); 1788 1789 return InterpretResult(v, sc); 1790 } 1791 1792 auto f = func.interpret(sc).value; 1793 bool isMacro = (f.payloadType == var.Type.Object && ((cast(MacroPrototype) f._payload._object) !is null)); 1794 var[] args; 1795 foreach(argument; arguments) 1796 if(argument !is null) { 1797 if(isMacro) // macro, pass the argument as an expression object 1798 args ~= argument.toScriptExpressionObject(sc); 1799 else // regular function, interpret the arguments 1800 args ~= argument.interpret(sc).value; 1801 } else { 1802 if(DefaultArgumentDummyObject is null) 1803 DefaultArgumentDummyObject = new PrototypeObject(); 1804 1805 var dummy; 1806 dummy._object = DefaultArgumentDummyObject; 1807 1808 args ~= dummy; 1809 } 1810 1811 var _this; 1812 if(auto dve = cast(DotVarExpression) func) { 1813 _this = dve.e1.interpret(sc).value; 1814 } else if(auto ide = cast(IndexExpression) func) { 1815 _this = ide.interpret(sc).value; 1816 } 1817 1818 return InterpretResult(f.apply(_this, args), sc); 1819 } 1820 } 1821 1822 ScriptToken requireNextToken(MyTokenStreamHere)(ref MyTokenStreamHere tokens, ScriptToken.Type type, string str = null, string file = __FILE__, size_t line = __LINE__) { 1823 if(tokens.empty) 1824 throw new ScriptCompileException("script ended prematurely", 0, file, line); 1825 auto next = tokens.front; 1826 if(next.type != type || (str !is null && next.str != str)) 1827 throw new ScriptCompileException("unexpected '"~next.str~"'", next.lineNumber, file, line); 1828 1829 tokens.popFront(); 1830 return next; 1831 } 1832 1833 bool peekNextToken(MyTokenStreamHere)(MyTokenStreamHere tokens, ScriptToken.Type type, string str = null, string file = __FILE__, size_t line = __LINE__) { 1834 if(tokens.empty) 1835 return false; 1836 auto next = tokens.front; 1837 if(next.type != type || (str !is null && next.str != str)) 1838 return false; 1839 return true; 1840 } 1841 1842 VariableExpression parseVariableName(MyTokenStreamHere)(ref MyTokenStreamHere tokens) { 1843 assert(!tokens.empty); 1844 auto token = tokens.front; 1845 if(token.type == ScriptToken.Type.identifier) { 1846 tokens.popFront(); 1847 return new VariableExpression(token.str); 1848 } 1849 throw new ScriptCompileException("Found "~token.str~" when expecting identifier", token.lineNumber); 1850 } 1851 1852 Expression parsePart(MyTokenStreamHere)(ref MyTokenStreamHere tokens) { 1853 if(!tokens.empty) { 1854 auto token = tokens.front; 1855 1856 Expression e; 1857 if(token.type == ScriptToken.Type.identifier) 1858 e = parseVariableName(tokens); 1859 else if(token.type == ScriptToken.Type.symbol && (token.str == "-" || token.str == "+")) { 1860 auto op = token.str; 1861 tokens.popFront(); 1862 1863 e = parsePart(tokens); 1864 if(op == "-") 1865 e = new NegationExpression(e); 1866 } else { 1867 tokens.popFront(); 1868 1869 if(token.type == ScriptToken.Type.int_number) 1870 e = new IntLiteralExpression(token.str); 1871 else if(token.type == ScriptToken.Type.float_number) 1872 e = new FloatLiteralExpression(token.str); 1873 else if(token.type == ScriptToken.Type..string) 1874 e = new StringLiteralExpression(token); 1875 else if(token.type == ScriptToken.Type.symbol || token.type == ScriptToken.Type.keyword) { 1876 switch(token.str) { 1877 case "true": 1878 case "false": 1879 e = new BoolLiteralExpression(token.str); 1880 break; 1881 case "new": 1882 // FIXME: why is this needed here? maybe it should be here instead of parseExpression 1883 tokens.pushFront(token); 1884 return parseExpression(tokens); 1885 case "(": 1886 //tokens.popFront(); 1887 auto parenthetical = new ParentheticalExpression(parseExpression(tokens)); 1888 tokens.requireNextToken(ScriptToken.Type.symbol, ")"); 1889 return parenthetical; 1890 case "[": 1891 // array literal 1892 auto arr = new ArrayLiteralExpression(); 1893 1894 bool first = true; 1895 moreElements: 1896 if(tokens.empty) 1897 throw new ScriptCompileException("unexpected end of file when reading array literal", token.lineNumber); 1898 1899 auto peek = tokens.front; 1900 if(peek.type == ScriptToken.Type.symbol && peek.str == "]") { 1901 tokens.popFront(); 1902 return arr; 1903 } 1904 1905 if(!first) 1906 tokens.requireNextToken(ScriptToken.Type.symbol, ","); 1907 else 1908 first = false; 1909 1910 arr.elements ~= parseExpression(tokens); 1911 1912 goto moreElements; 1913 case "json!q{": 1914 // json object literal 1915 auto obj = new ObjectLiteralExpression(); 1916 /* 1917 these go 1918 1919 string or ident which is the key 1920 then a colon 1921 then an expression which is the value 1922 1923 then optionally a comma 1924 1925 then either } which finishes it, or another key 1926 */ 1927 1928 if(tokens.empty) 1929 throw new ScriptCompileException("unexpected end of file when reading object literal", token.lineNumber); 1930 1931 moreKeys: 1932 auto key = tokens.front; 1933 tokens.popFront(); 1934 if(key.type == ScriptToken.Type.symbol && key.str == "}") { 1935 // all done! 1936 e = obj; 1937 break; 1938 } 1939 if(key.type != ScriptToken.Type..string && key.type != ScriptToken.Type.identifier) { 1940 throw new ScriptCompileException("unexpected '"~key.str~"' when reading object literal", key.lineNumber); 1941 1942 } 1943 1944 tokens.requireNextToken(ScriptToken.Type.symbol, ":"); 1945 1946 auto value = parseExpression(tokens); 1947 if(tokens.empty) 1948 throw new ScriptCompileException("unclosed object literal", key.lineNumber); 1949 1950 if(tokens.peekNextToken(ScriptToken.Type.symbol, ",")) 1951 tokens.popFront(); 1952 1953 obj.elements[key.str] = value; 1954 1955 goto moreKeys; 1956 case "macro": 1957 case "function": 1958 tokens.requireNextToken(ScriptToken.Type.symbol, "("); 1959 1960 auto exp = new FunctionLiteralExpression(); 1961 if(!tokens.peekNextToken(ScriptToken.Type.symbol, ")")) 1962 exp.arguments = parseVariableDeclaration(tokens, ")"); 1963 1964 tokens.requireNextToken(ScriptToken.Type.symbol, ")"); 1965 1966 exp.functionBody = parseExpression(tokens); 1967 exp.isMacro = token.str == "macro"; 1968 1969 e = exp; 1970 break; 1971 case "null": 1972 e = new NullLiteralExpression(); 1973 break; 1974 case "mixin": 1975 case "eval": 1976 tokens.requireNextToken(ScriptToken.Type.symbol, "("); 1977 e = new MixinExpression(parseExpression(tokens)); 1978 tokens.requireNextToken(ScriptToken.Type.symbol, ")"); 1979 break; 1980 default: 1981 goto unknown; 1982 } 1983 } else { 1984 unknown: 1985 throw new ScriptCompileException("unexpected '"~token.str~"' when reading ident", token.lineNumber); 1986 } 1987 } 1988 1989 funcLoop: while(!tokens.empty) { 1990 auto peek = tokens.front; 1991 if(peek.type == ScriptToken.Type.symbol) { 1992 switch(peek.str) { 1993 case "(": 1994 e = parseFunctionCall(tokens, e); 1995 break; 1996 case "[": 1997 tokens.popFront(); 1998 auto e1 = parseExpression(tokens); 1999 if(tokens.peekNextToken(ScriptToken.Type.symbol, "..")) { 2000 tokens.popFront(); 2001 e = new SliceExpression(e, e1, parseExpression(tokens)); 2002 } else { 2003 e = new IndexExpression(e, e1); 2004 } 2005 tokens.requireNextToken(ScriptToken.Type.symbol, "]"); 2006 break; 2007 case ".": 2008 tokens.popFront(); 2009 e = new DotVarExpression(e, parseVariableName(tokens)); 2010 break; 2011 default: 2012 return e; // we don't know, punt it elsewhere 2013 } 2014 } else return e; // again, we don't know, so just punt it down the line 2015 } 2016 return e; 2017 } 2018 assert(0, to!string(tokens)); 2019 } 2020 2021 Expression parseArguments(MyTokenStreamHere)(ref MyTokenStreamHere tokens, Expression exp, ref Expression[] where) { 2022 // arguments. 2023 auto peek = tokens.front; 2024 if(peek.type == ScriptToken.Type.symbol && peek.str == ")") { 2025 tokens.popFront(); 2026 return exp; 2027 } 2028 2029 moreArguments: 2030 2031 if(tokens.peekNextToken(ScriptToken.Type.keyword, "default")) { 2032 tokens.popFront(); 2033 where ~= null; 2034 } else { 2035 where ~= parseExpression(tokens); 2036 } 2037 2038 if(tokens.empty) 2039 throw new ScriptCompileException("unexpected end of file when parsing call expression", peek.lineNumber); 2040 peek = tokens.front; 2041 if(peek.type == ScriptToken.Type.symbol && peek.str == ",") { 2042 tokens.popFront(); 2043 goto moreArguments; 2044 } else if(peek.type == ScriptToken.Type.symbol && peek.str == ")") { 2045 tokens.popFront(); 2046 return exp; 2047 } else 2048 throw new ScriptCompileException("unexpected '"~peek.str~"' when reading argument list", peek.lineNumber); 2049 2050 } 2051 2052 Expression parseFunctionCall(MyTokenStreamHere)(ref MyTokenStreamHere tokens, Expression e) { 2053 assert(!tokens.empty); 2054 auto peek = tokens.front; 2055 auto exp = new CallExpression(e); 2056 tokens.popFront(); 2057 if(tokens.empty) 2058 throw new ScriptCompileException("unexpected end of file when parsing call expression", peek.lineNumber); 2059 return parseArguments(tokens, exp, exp.arguments); 2060 } 2061 2062 Expression parseFactor(MyTokenStreamHere)(ref MyTokenStreamHere tokens) { 2063 auto e1 = parsePart(tokens); 2064 loop: while(!tokens.empty) { 2065 auto peek = tokens.front; 2066 2067 if(peek.type == ScriptToken.Type.symbol) { 2068 switch(peek.str) { 2069 case "*": 2070 case "/": 2071 tokens.popFront(); 2072 e1 = new BinaryExpression(peek.str, e1, parsePart(tokens)); 2073 break; 2074 default: 2075 break loop; 2076 } 2077 } else throw new Exception("Got " ~ peek.str ~ " when expecting symbol"); 2078 } 2079 2080 return e1; 2081 } 2082 2083 Expression parseAddend(MyTokenStreamHere)(ref MyTokenStreamHere tokens) { 2084 auto e1 = parseFactor(tokens); 2085 loop: while(!tokens.empty) { 2086 auto peek = tokens.front; 2087 2088 if(peek.type == ScriptToken.Type.symbol) { 2089 switch(peek.str) { 2090 case "..": // possible FIXME 2091 case ")": // possible FIXME 2092 case "]": // possible FIXME 2093 case "}": // possible FIXME 2094 case ",": // possible FIXME these are passed on to the next thing 2095 case ";": 2096 return e1; 2097 2098 case ".": 2099 tokens.popFront(); 2100 e1 = new DotVarExpression(e1, parseVariableName(tokens)); 2101 break; 2102 case "=": 2103 tokens.popFront(); 2104 return new AssignExpression(e1, parseExpression(tokens)); 2105 case "~": 2106 // FIXME: make sure this has the right associativity 2107 2108 case "&&": // FIXME: precedence? 2109 case "||": 2110 2111 case "&": 2112 case "|": 2113 case "^": 2114 2115 case "&=": 2116 case "|=": 2117 case "^=": 2118 2119 case "+": 2120 case "-": 2121 2122 case "==": 2123 case "!=": 2124 case "<=": 2125 case ">=": 2126 case "<": 2127 case ">": 2128 tokens.popFront(); 2129 e1 = new BinaryExpression(peek.str, e1, parseFactor(tokens)); 2130 break; 2131 case "+=": 2132 case "-=": 2133 case "*=": 2134 case "/=": 2135 case "~=": 2136 tokens.popFront(); 2137 return new OpAssignExpression(peek.str[0..1], e1, parseExpression(tokens)); 2138 default: 2139 throw new ScriptCompileException("Parse error, unexpected " ~ peek.str ~ " when looking for operator", peek.lineNumber); 2140 } 2141 //} else if(peek.type == ScriptToken.Type.identifier || peek.type == ScriptToken.Type.number) { 2142 //return parseFactor(tokens); 2143 } else 2144 throw new ScriptCompileException("Parse error, unexpected '" ~ peek.str ~ "'", peek.lineNumber); 2145 } 2146 2147 return e1; 2148 } 2149 2150 Expression parseExpression(MyTokenStreamHere)(ref MyTokenStreamHere tokens, bool consumeEnd = false) { 2151 Expression ret; 2152 ScriptToken first; 2153 string expectedEnd = ";"; 2154 //auto e1 = parseFactor(tokens); 2155 2156 while(tokens.peekNextToken(ScriptToken.Type.symbol, ";")) { 2157 tokens.popFront(); 2158 } 2159 if(!tokens.empty) { 2160 first = tokens.front; 2161 if(tokens.peekNextToken(ScriptToken.Type.symbol, "{")) { 2162 auto start = tokens.front; 2163 tokens.popFront(); 2164 auto e = parseCompoundStatement(tokens, start.lineNumber, "}").array; 2165 ret = new ScopeExpression(e); 2166 expectedEnd = null; // {} don't need ; at the end 2167 } else if(tokens.peekNextToken(ScriptToken.Type.keyword, "scope")) { 2168 auto start = tokens.front; 2169 tokens.popFront(); 2170 tokens.requireNextToken(ScriptToken.Type.symbol, "("); 2171 2172 auto ident = tokens.requireNextToken(ScriptToken.Type.identifier); 2173 switch(ident.str) { 2174 case "success": 2175 case "failure": 2176 case "exit": 2177 break; 2178 default: 2179 throw new ScriptCompileException("unexpected " ~ ident.str ~ ". valid scope(idents) are success, failure, and exit", ident.lineNumber); 2180 } 2181 2182 tokens.requireNextToken(ScriptToken.Type.symbol, ")"); 2183 2184 string i = "__scope_" ~ ident.str; 2185 auto literal = new FunctionLiteralExpression(); 2186 literal.functionBody = parseExpression(tokens); 2187 2188 auto e = new OpAssignExpression("~", new VariableExpression(i), literal); 2189 ret = e; 2190 } else if(tokens.peekNextToken(ScriptToken.Type.symbol, "(")) { 2191 auto start = tokens.front; 2192 tokens.popFront(); 2193 auto parenthetical = new ParentheticalExpression(parseExpression(tokens)); 2194 tokens.requireNextToken(ScriptToken.Type.symbol, ")"); 2195 if(tokens.peekNextToken(ScriptToken.Type.symbol, "(")) { 2196 // we have a function call, e.g. (test)() 2197 ret = parseFunctionCall(tokens, parenthetical); 2198 } else 2199 ret = parenthetical; 2200 } else if(tokens.peekNextToken(ScriptToken.Type.keyword, "new")) { 2201 auto start = tokens.front; 2202 tokens.popFront(); 2203 2204 auto expr = parseVariableName(tokens); 2205 auto ne = new NewExpression(expr); 2206 if(tokens.peekNextToken(ScriptToken.Type.symbol, "(")) { 2207 tokens.popFront(); 2208 parseArguments(tokens, ne, ne.args); 2209 } 2210 2211 ret = ne; 2212 } else if(tokens.peekNextToken(ScriptToken.Type.keyword, "class")) { 2213 auto start = tokens.front; 2214 tokens.popFront(); 2215 2216 Expression[] expressions; 2217 2218 // the way classes work is they are actually object literals with a different syntax. new foo then just copies it 2219 /* 2220 we create a prototype object 2221 we create an object, with that prototype 2222 2223 set all functions and static stuff to the prototype 2224 the rest goes to the object 2225 2226 the expression returns the object we made 2227 */ 2228 2229 auto vars = new VariableDeclaration(); 2230 vars.identifiers = ["__proto", "__obj"]; 2231 2232 auto staticScopeBacking = new PrototypeObject(); 2233 auto instanceScopeBacking = new PrototypeObject(); 2234 2235 vars.initializers = [new ObjectLiteralExpression(staticScopeBacking), new ObjectLiteralExpression(instanceScopeBacking)]; 2236 expressions ~= vars; 2237 2238 // FIXME: operators need to have their this be bound somehow since it isn't passed 2239 // OR the op rewrite could pass this 2240 2241 expressions ~= new AssignExpression( 2242 new DotVarExpression(new VariableExpression("__obj"), new VariableExpression("prototype")), 2243 new VariableExpression("__proto")); 2244 2245 auto classIdent = tokens.requireNextToken(ScriptToken.Type.identifier); 2246 2247 expressions ~= new AssignExpression( 2248 new DotVarExpression(new VariableExpression("__proto"), new VariableExpression("__classname")), 2249 new StringLiteralExpression(classIdent.str)); 2250 2251 if(tokens.peekNextToken(ScriptToken.Type.symbol, ":")) { 2252 tokens.popFront(); 2253 auto inheritFrom = tokens.requireNextToken(ScriptToken.Type.identifier); 2254 2255 // we set our prototype to the Foo prototype, thereby inheriting any static data that way (includes functions) 2256 // the inheritFrom object itself carries instance data that we need to copy onto our instance 2257 expressions ~= new AssignExpression( 2258 new DotVarExpression(new VariableExpression("__proto"), new VariableExpression("prototype")), 2259 new DotVarExpression(new VariableExpression(inheritFrom.str), new VariableExpression("prototype"))); 2260 2261 // and copying the instance initializer from the parent 2262 expressions ~= new ShallowCopyExpression(new VariableExpression("__obj"), new VariableExpression(inheritFrom.str)); 2263 } 2264 2265 tokens.requireNextToken(ScriptToken.Type.symbol, "{"); 2266 2267 void addVarDecl(VariableDeclaration decl, string o) { 2268 foreach(i, ident; decl.identifiers) { 2269 // FIXME: make sure this goes on the instance, never the prototype! 2270 expressions ~= new AssignExpression( 2271 new DotVarExpression( 2272 new VariableExpression(o), 2273 new VariableExpression(ident), 2274 false), 2275 decl.initializers[i], 2276 true // no overloading because otherwise an early opIndexAssign can mess up the decls 2277 ); 2278 } 2279 } 2280 2281 // FIXME: we could actually add private vars and just put them in this scope. maybe 2282 2283 while(!tokens.peekNextToken(ScriptToken.Type.symbol, "}")) { 2284 if(tokens.peekNextToken(ScriptToken.Type.symbol, ";")) { 2285 tokens.popFront(); 2286 continue; 2287 } 2288 2289 if(tokens.peekNextToken(ScriptToken.Type.identifier, "this")) { 2290 // ctor 2291 tokens.popFront(); 2292 tokens.requireNextToken(ScriptToken.Type.symbol, "("); 2293 auto args = parseVariableDeclaration(tokens, ")"); 2294 tokens.requireNextToken(ScriptToken.Type.symbol, ")"); 2295 auto bod = parseExpression(tokens); 2296 2297 expressions ~= new AssignExpression( 2298 new DotVarExpression( 2299 new VariableExpression("__proto"), 2300 new VariableExpression("__ctor")), 2301 new FunctionLiteralExpression(args, bod, staticScopeBacking)); 2302 } else if(tokens.peekNextToken(ScriptToken.Type.keyword, "var")) { 2303 // instance variable 2304 auto decl = parseVariableDeclaration(tokens, ";"); 2305 addVarDecl(decl, "__obj"); 2306 } else if(tokens.peekNextToken(ScriptToken.Type.keyword, "static")) { 2307 // prototype var 2308 tokens.popFront(); 2309 auto decl = parseVariableDeclaration(tokens, ";"); 2310 addVarDecl(decl, "__proto"); 2311 } else if(tokens.peekNextToken(ScriptToken.Type.keyword, "function")) { 2312 // prototype function 2313 tokens.popFront(); 2314 auto ident = tokens.requireNextToken(ScriptToken.Type.identifier); 2315 2316 tokens.requireNextToken(ScriptToken.Type.symbol, "("); 2317 auto args = parseVariableDeclaration(tokens, ")"); 2318 tokens.requireNextToken(ScriptToken.Type.symbol, ")"); 2319 auto bod = parseExpression(tokens); 2320 2321 expressions ~= new AssignExpression( 2322 new DotVarExpression( 2323 new VariableExpression("__proto"), 2324 new VariableExpression(ident.str), 2325 false), 2326 new FunctionLiteralExpression(args, bod, staticScopeBacking)); 2327 } else throw new ScriptCompileException("Unexpected " ~ tokens.front.str ~ " when reading class decl", tokens.front.lineNumber); 2328 } 2329 2330 tokens.requireNextToken(ScriptToken.Type.symbol, "}"); 2331 2332 // returning he object from the scope... 2333 expressions ~= new VariableExpression("__obj"); 2334 2335 auto scopeExpr = new ScopeExpression(expressions); 2336 auto classVarExpr = new VariableDeclaration(); 2337 classVarExpr.identifiers = [classIdent.str]; 2338 classVarExpr.initializers = [scopeExpr]; 2339 2340 ret = classVarExpr; 2341 } else if(tokens.peekNextToken(ScriptToken.Type.keyword, "if")) { 2342 tokens.popFront(); 2343 auto e = new IfExpression(); 2344 e.condition = parseExpression(tokens); 2345 e.ifTrue = parseExpression(tokens); 2346 if(tokens.peekNextToken(ScriptToken.Type.symbol, ";")) { 2347 tokens.popFront(); 2348 } 2349 if(tokens.peekNextToken(ScriptToken.Type.keyword, "else")) { 2350 tokens.popFront(); 2351 e.ifFalse = parseExpression(tokens); 2352 } 2353 ret = e; 2354 } else if(tokens.peekNextToken(ScriptToken.Type.keyword, "foreach")) { 2355 tokens.popFront(); 2356 auto e = new ForeachExpression(); 2357 tokens.requireNextToken(ScriptToken.Type.symbol, "("); 2358 e.decl = parseVariableDeclaration(tokens, ";"); 2359 tokens.requireNextToken(ScriptToken.Type.symbol, ";"); 2360 e.subject = parseExpression(tokens); 2361 tokens.requireNextToken(ScriptToken.Type.symbol, ")"); 2362 e.loopBody = parseExpression(tokens); 2363 ret = e; 2364 2365 expectedEnd = ""; 2366 } else if(tokens.peekNextToken(ScriptToken.Type.keyword, "cast")) { 2367 tokens.popFront(); 2368 auto e = new CastExpression(); 2369 2370 tokens.requireNextToken(ScriptToken.Type.symbol, "("); 2371 e.type = tokens.requireNextToken(ScriptToken.Type.identifier).str; 2372 if(tokens.peekNextToken(ScriptToken.Type.symbol, "[")) { 2373 e.type ~= "[]"; 2374 tokens.popFront(); 2375 tokens.requireNextToken(ScriptToken.Type.symbol, "]"); 2376 } 2377 tokens.requireNextToken(ScriptToken.Type.symbol, ")"); 2378 2379 e.e1 = parseExpression(tokens); 2380 ret = e; 2381 } else if(tokens.peekNextToken(ScriptToken.Type.keyword, "for")) { 2382 tokens.popFront(); 2383 auto e = new ForExpression(); 2384 tokens.requireNextToken(ScriptToken.Type.symbol, "("); 2385 e.initialization = parseStatement(tokens, ";"); 2386 2387 tokens.requireNextToken(ScriptToken.Type.symbol, ";"); 2388 2389 e.condition = parseExpression(tokens); 2390 tokens.requireNextToken(ScriptToken.Type.symbol, ";"); 2391 e.advancement = parseExpression(tokens); 2392 tokens.requireNextToken(ScriptToken.Type.symbol, ")"); 2393 e.loopBody = parseExpression(tokens); 2394 2395 ret = e; 2396 2397 expectedEnd = ""; 2398 } else if(tokens.peekNextToken(ScriptToken.Type.keyword, "while")) { 2399 tokens.popFront(); 2400 auto e = new ForExpression(); 2401 e.condition = parseExpression(tokens); 2402 e.loopBody = parseExpression(tokens); 2403 ret = e; 2404 expectedEnd = ""; 2405 } else if(tokens.peekNextToken(ScriptToken.Type.keyword, "break") || tokens.peekNextToken(ScriptToken.Type.keyword, "continue")) { 2406 auto token = tokens.front; 2407 tokens.popFront(); 2408 ret = new LoopControlExpression(token.str); 2409 } else if(tokens.peekNextToken(ScriptToken.Type.keyword, "return")) { 2410 tokens.popFront(); 2411 Expression retVal; 2412 if(tokens.peekNextToken(ScriptToken.Type.symbol, ";")) 2413 retVal = new NullLiteralExpression(); 2414 else 2415 retVal = parseExpression(tokens); 2416 ret = new ReturnExpression(retVal); 2417 } else if(tokens.peekNextToken(ScriptToken.Type.keyword, "throw")) { 2418 auto token = tokens.front; 2419 tokens.popFront(); 2420 ret = new ThrowExpression(parseExpression(tokens), token); 2421 } else if(tokens.peekNextToken(ScriptToken.Type.keyword, "try")) { 2422 auto tryToken = tokens.front; 2423 auto e = new ExceptionBlockExpression(); 2424 tokens.popFront(); 2425 e.tryExpression = parseExpression(tokens, true); 2426 2427 bool hadSomething = false; 2428 while(tokens.peekNextToken(ScriptToken.Type.keyword, "catch")) { 2429 if(hadSomething) 2430 throw new ScriptCompileException("Only one catch block is allowed currently ", tokens.front.lineNumber); 2431 hadSomething = true; 2432 tokens.popFront(); 2433 tokens.requireNextToken(ScriptToken.Type.symbol, "("); 2434 if(tokens.peekNextToken(ScriptToken.Type.keyword, "var")) 2435 tokens.popFront(); 2436 auto ident = tokens.requireNextToken(ScriptToken.Type.identifier); 2437 e.catchVarDecls ~= ident.str; 2438 tokens.requireNextToken(ScriptToken.Type.symbol, ")"); 2439 e.catchExpressions ~= parseExpression(tokens); 2440 } 2441 while(tokens.peekNextToken(ScriptToken.Type.keyword, "finally")) { 2442 hadSomething = true; 2443 tokens.popFront(); 2444 e.finallyExpressions ~= parseExpression(tokens); 2445 } 2446 2447 //if(!hadSomething) 2448 //throw new ScriptCompileException("Parse error, missing finally or catch after try", tryToken.lineNumber); 2449 2450 ret = e; 2451 } else 2452 ret = parseAddend(tokens); 2453 } else { 2454 assert(0); 2455 // return null; 2456 // throw new ScriptCompileException("Parse error, unexpected end of input when reading expression", token.lineNumber); 2457 } 2458 2459 //writeln("parsed expression ", ret.toString()); 2460 2461 if(expectedEnd.length && tokens.empty && consumeEnd) // going loose on final ; at the end of input for repl convenience 2462 throw new ScriptCompileException("Parse error, unexpected end of input when reading expression, expecting " ~ expectedEnd, first.lineNumber); 2463 2464 if(expectedEnd.length && consumeEnd) { 2465 if(tokens.peekNextToken(ScriptToken.Type.symbol, expectedEnd)) 2466 tokens.popFront(); 2467 // FIXME 2468 //if(tokens.front.type != ScriptToken.Type.symbol && tokens.front.str != expectedEnd) 2469 //throw new ScriptCompileException("Parse error, missing "~expectedEnd~" at end of expression (starting on "~to!string(first.lineNumber)~"). Saw "~tokens.front.str~" instead", tokens.front.lineNumber); 2470 // tokens = tokens[1 .. $]; 2471 } 2472 2473 return ret; 2474 } 2475 2476 VariableDeclaration parseVariableDeclaration(MyTokenStreamHere)(ref MyTokenStreamHere tokens, string termination) { 2477 VariableDeclaration decl = new VariableDeclaration(); 2478 bool equalOk; 2479 anotherVar: 2480 assert(!tokens.empty); 2481 2482 auto firstToken = tokens.front; 2483 2484 // var a, var b is acceptable 2485 if(tokens.peekNextToken(ScriptToken.Type.keyword, "var")) 2486 tokens.popFront(); 2487 2488 equalOk= true; 2489 if(tokens.empty) 2490 throw new ScriptCompileException("Parse error, dangling var at end of file", firstToken.lineNumber); 2491 2492 Expression initializer; 2493 auto identifier = tokens.front; 2494 if(identifier.type != ScriptToken.Type.identifier) 2495 throw new ScriptCompileException("Parse error, found '"~identifier.str~"' when expecting var identifier", identifier.lineNumber); 2496 2497 tokens.popFront(); 2498 2499 tryTermination: 2500 if(tokens.empty) 2501 throw new ScriptCompileException("Parse error, missing ; after var declaration at end of file", firstToken.lineNumber); 2502 2503 auto peek = tokens.front; 2504 if(peek.type == ScriptToken.Type.symbol) { 2505 if(peek.str == "=") { 2506 if(!equalOk) 2507 throw new ScriptCompileException("Parse error, unexpected '"~identifier.str~"' after reading var initializer", peek.lineNumber); 2508 equalOk = false; 2509 tokens.popFront(); 2510 initializer = parseExpression(tokens); 2511 goto tryTermination; 2512 } else if(peek.str == ",") { 2513 tokens.popFront(); 2514 decl.identifiers ~= identifier.str; 2515 decl.initializers ~= initializer; 2516 goto anotherVar; 2517 } else if(peek.str == termination) { 2518 decl.identifiers ~= identifier.str; 2519 decl.initializers ~= initializer; 2520 //tokens = tokens[1 .. $]; 2521 // we're done! 2522 } else 2523 throw new ScriptCompileException("Parse error, unexpected '"~peek.str~"' when reading var declaration", peek.lineNumber); 2524 } else 2525 throw new ScriptCompileException("Parse error, unexpected '"~peek.str~"' when reading var declaration", peek.lineNumber); 2526 2527 return decl; 2528 } 2529 2530 Expression parseStatement(MyTokenStreamHere)(ref MyTokenStreamHere tokens, string terminatingSymbol = null) { 2531 skip: // FIXME 2532 if(tokens.empty) 2533 return null; 2534 2535 if(terminatingSymbol !is null && (tokens.front.type == ScriptToken.Type.symbol && tokens.front.str == terminatingSymbol)) 2536 return null; // we're done 2537 2538 auto token = tokens.front; 2539 2540 // tokens = tokens[1 .. $]; 2541 final switch(token.type) { 2542 case ScriptToken.Type.keyword: 2543 case ScriptToken.Type.symbol: 2544 switch(token.str) { 2545 // assert 2546 case "assert": 2547 tokens.popFront(); 2548 2549 return parseFunctionCall(tokens, new AssertKeyword(token)); 2550 2551 ////break; 2552 // declarations 2553 case "var": 2554 return parseVariableDeclaration(tokens, ";"); 2555 case ";": 2556 tokens.popFront(); // FIXME 2557 goto skip; 2558 // literals 2559 case "function": 2560 case "macro": 2561 // function can be a literal, or a declaration. 2562 2563 tokens.popFront(); // we're peeking ahead 2564 2565 if(tokens.peekNextToken(ScriptToken.Type.identifier)) { 2566 // decl style, rewrite it into var ident = function style 2567 // tokens.popFront(); // skipping the function keyword // already done above with the popFront 2568 auto ident = tokens.front; 2569 tokens.popFront(); 2570 2571 tokens.requireNextToken(ScriptToken.Type.symbol, "("); 2572 2573 auto exp = new FunctionLiteralExpression(); 2574 if(!tokens.peekNextToken(ScriptToken.Type.symbol, ")")) 2575 exp.arguments = parseVariableDeclaration(tokens, ")"); 2576 tokens.requireNextToken(ScriptToken.Type.symbol, ")"); 2577 2578 exp.functionBody = parseExpression(tokens); 2579 2580 // a ; should NOT be required here btw 2581 2582 auto e = new VariableDeclaration(); 2583 e.identifiers ~= ident.str; 2584 e.initializers ~= exp; 2585 2586 exp.isMacro = token.str == "macro"; 2587 2588 return e; 2589 2590 } else { 2591 tokens.pushFront(token); // put it back since everyone expects us to have done that 2592 goto case; // handle it like any other expression 2593 } 2594 case "json!{": 2595 case "[": 2596 case "(": 2597 case "null": 2598 2599 // scope 2600 case "{": 2601 case "scope": 2602 2603 case "cast": 2604 2605 // classes 2606 case "class": 2607 case "new": 2608 2609 // flow control 2610 case "if": 2611 case "while": 2612 case "for": 2613 case "foreach": 2614 2615 // exceptions 2616 case "try": 2617 case "throw": 2618 2619 // flow 2620 case "continue": 2621 case "break": 2622 case "return": 2623 return parseExpression(tokens); 2624 // unary prefix operators 2625 case "!": 2626 case "~": 2627 case "-": 2628 2629 // BTW add custom object operator overloading to struct var 2630 // and custom property overloading to PrototypeObject 2631 2632 default: 2633 // whatever else keyword or operator related is actually illegal here 2634 throw new ScriptCompileException("Parse error, unexpected " ~ token.str, token.lineNumber); 2635 } 2636 // break; 2637 case ScriptToken.Type.identifier: 2638 case ScriptToken.Type..string: 2639 case ScriptToken.Type.int_number: 2640 case ScriptToken.Type.float_number: 2641 return parseExpression(tokens); 2642 } 2643 2644 assert(0); 2645 } 2646 2647 struct CompoundStatementRange(MyTokenStreamHere) { 2648 // FIXME: if MyTokenStreamHere is not a class, this fails! 2649 MyTokenStreamHere tokens; 2650 int startingLine; 2651 string terminatingSymbol; 2652 bool isEmpty; 2653 2654 this(MyTokenStreamHere t, int startingLine, string terminatingSymbol) { 2655 tokens = t; 2656 this.startingLine = startingLine; 2657 this.terminatingSymbol = terminatingSymbol; 2658 popFront(); 2659 } 2660 2661 bool empty() { 2662 return isEmpty; 2663 } 2664 2665 Expression got; 2666 2667 Expression front() { 2668 return got; 2669 } 2670 2671 void popFront() { 2672 while(!tokens.empty && (terminatingSymbol is null || !(tokens.front.type == ScriptToken.Type.symbol && tokens.front.str == terminatingSymbol))) { 2673 auto n = parseStatement(tokens, terminatingSymbol); 2674 if(n is null) 2675 continue; 2676 got = n; 2677 return; 2678 } 2679 2680 if(tokens.empty && terminatingSymbol !is null) { 2681 throw new ScriptCompileException("Reached end of file while trying to reach matching " ~ terminatingSymbol, startingLine); 2682 } 2683 2684 if(terminatingSymbol !is null) { 2685 assert(tokens.front.str == terminatingSymbol); 2686 tokens.skipNext++; 2687 } 2688 2689 isEmpty = true; 2690 } 2691 } 2692 2693 CompoundStatementRange!MyTokenStreamHere 2694 //Expression[] 2695 parseCompoundStatement(MyTokenStreamHere)(ref MyTokenStreamHere tokens, int startingLine = 1, string terminatingSymbol = null) { 2696 return (CompoundStatementRange!MyTokenStreamHere(tokens, startingLine, terminatingSymbol)); 2697 } 2698 2699 auto parseScript(MyTokenStreamHere)(MyTokenStreamHere tokens) { 2700 /* 2701 the language's grammar is simple enough 2702 2703 maybe flow control should be statements though lol. they might not make sense inside. 2704 2705 Expressions: 2706 var identifier; 2707 var identifier = initializer; 2708 var identifier, identifier2 2709 2710 return expression; 2711 return ; 2712 2713 json!{ object literal } 2714 2715 { scope expression } 2716 2717 [ array literal ] 2718 other literal 2719 function (arg list) other expression 2720 2721 ( expression ) // parenthesized expression 2722 operator expression // unary expression 2723 2724 expression operator expression // binary expression 2725 expression (other expression... args) // function call 2726 2727 Binary Operator precedence : 2728 . [] 2729 * / 2730 + - 2731 ~ 2732 < > == != 2733 = 2734 */ 2735 2736 return parseCompoundStatement(tokens); 2737 } 2738 2739 var interpretExpressions(ExpressionStream)(ExpressionStream expressions, PrototypeObject variables) if(is(ElementType!ExpressionStream == Expression)) { 2740 assert(variables !is null); 2741 var ret; 2742 foreach(expression; expressions) { 2743 auto res = expression.interpret(variables); 2744 variables = res.sc; 2745 ret = res.value; 2746 } 2747 return ret; 2748 } 2749 2750 var interpretStream(MyTokenStreamHere)(MyTokenStreamHere tokens, PrototypeObject variables) if(is(ElementType!MyTokenStreamHere == ScriptToken)) { 2751 assert(variables !is null); 2752 // this is an entry point that all others lead to, right before getting to interpretExpressions... 2753 2754 return interpretExpressions(parseScript(tokens), variables); 2755 } 2756 2757 var interpretStream(MyTokenStreamHere)(MyTokenStreamHere tokens, var variables) if(is(ElementType!MyTokenStreamHere == ScriptToken)) { 2758 return interpretStream(tokens, 2759 (variables.payloadType() == var.Type.Object && variables._payload._object !is null) ? variables._payload._object : new PrototypeObject()); 2760 } 2761 2762 var interpret(string code, PrototypeObject variables, string scriptFilename = null) { 2763 assert(variables !is null); 2764 return interpretStream(lexScript(repeat(code, 1), scriptFilename), variables); 2765 } 2766 2767 /++ 2768 This is likely your main entry point to the interpreter. It will interpret the script code 2769 given, with the given global variable object (which will be modified by the script, meaning 2770 you can pass it to subsequent calls to `interpret` to store context), and return the result 2771 of the last expression given. 2772 2773 --- 2774 var globals = var.emptyObject; // the global object must be an object of some type 2775 globals.x = 10; 2776 globals.y = 15; 2777 // you can also set global functions through this same style, etc 2778 2779 var result = interpret(`x + y`, globals); 2780 assert(result == 25); 2781 --- 2782 2783 2784 $(TIP 2785 If you want to just call a script function, interpret the definition of it, 2786 then just call it through the `globals` object you passed to it. 2787 2788 --- 2789 var globals = var.emptyObject; 2790 interpret(`function foo(name) { return "hello, " ~ name ~ "!"; }`, globals); 2791 var result = globals.foo()("world"); 2792 assert(result == "hello, world!"); 2793 --- 2794 ) 2795 2796 Params: 2797 code = the script source code you want to interpret 2798 scriptFilename = the filename of the script, if you want to provide it. Gives nicer error messages if you provide one. 2799 variables = The global object of the script context. It will be modified by the user script. 2800 2801 Returns: 2802 the result of the last expression evaluated by the script engine 2803 +/ 2804 var interpret(string code, var variables = null, string scriptFilename = null) { 2805 return interpretStream( 2806 lexScript(repeat(code, 1), scriptFilename), 2807 (variables.payloadType() == var.Type.Object && variables._payload._object !is null) ? variables._payload._object : new PrototypeObject()); 2808 } 2809 2810 /// 2811 var interpretFile(File file, var globals) { 2812 import std.algorithm; 2813 return interpretStream(lexScript(file.byLine.map!((a) => a.idup), file.name), 2814 (globals.payloadType() == var.Type.Object && globals._payload._object !is null) ? globals._payload._object : new PrototypeObject()); 2815 } 2816 2817 /// 2818 void repl(var globals) { 2819 import std.stdio; 2820 import std.algorithm; 2821 auto variables = (globals.payloadType() == var.Type.Object && globals._payload._object !is null) ? globals._payload._object : new PrototypeObject(); 2822 2823 // we chain to ensure the priming popFront succeeds so we don't throw here 2824 auto tokens = lexScript( 2825 chain(["var __skipme = 0;"], map!((a) => a.idup)(stdin.byLine)) 2826 , "stdin"); 2827 auto expressions = parseScript(tokens); 2828 2829 while(!expressions.empty) { 2830 try { 2831 expressions.popFront; 2832 auto expression = expressions.front; 2833 auto res = expression.interpret(variables); 2834 variables = res.sc; 2835 writeln(">>> ", res.value); 2836 } catch(ScriptCompileException e) { 2837 writeln("*+* ", e.msg); 2838 tokens.popFront(); // skip the one we threw on... 2839 } catch(Exception e) { 2840 writeln("*** ", e.msg); 2841 } 2842 } 2843 }