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