1 // dmd -g -ofscripttest -unittest -main script.d jsvar.d && ./scripttest 2 /* 3 4 FIXME: fix `(new A()).b` 5 6 7 FIXME: i kinda do want a catch type filter e.g. catch(Exception f) 8 and perhaps overloads 9 10 11 12 For type annotations, maybe it can statically match later, but right now 13 it just forbids any assignment to that variable that isn't that type. 14 15 I'll have to define int, float, etc though as basic types. 16 17 18 19 FIXME: I also kinda want implicit construction of structs at times. 20 21 REPL plan: 22 easy movement to/from a real editor 23 can edit a specific function 24 repl is a different set of globals 25 maybe ctrl+enter to execute vs insert another line 26 27 28 write state to file 29 read state from file 30 state consists of all variables and source to functions. 31 maybe need @retained for a variable that is meant to keep 32 its value between loads? 33 34 ddoc???? 35 udas?!?!?! 36 37 Steal Ruby's [regex, capture] maybe 38 39 and the => operator too 40 41 I kinda like the javascript foo`blargh` template literals too. 42 43 ++ and -- are not implemented. 44 45 */ 46 47 /++ 48 A small script interpreter that builds on [arsd.jsvar] to be easily embedded inside and to have has easy 49 two-way interop with the host D program. The script language it implements is based on a hybrid of D and Javascript. 50 The type the language uses is based directly on [var] from [arsd.jsvar]. 51 52 The interpreter is slightly buggy and poorly documented, but the basic functionality works well and much of 53 your existing knowledge from Javascript will carry over, making it hopefully easy to use right out of the box. 54 See the [#examples] to quickly get the feel of the script language as well as the interop. 55 56 I haven't benchmarked it, but I expect it is pretty slow. My goal is to see what is possible for easy interoperability 57 with dynamic functionality and D rather than speed. 58 59 60 $(TIP 61 A goal of this language is to blur the line between D and script, but 62 in the examples below, which are generated from D unit tests, 63 the non-italics code is D, and the italics is the script. Notice 64 how it is a string passed to the [interpret] function. 65 66 In some smaller, stand-alone code samples, there will be a tag "adrscript" 67 in the upper right of the box to indicate it is script. Otherwise, it 68 is D. 69 ) 70 71 Installation_instructions: 72 This script interpreter is contained entirely in two files: jsvar.d and script.d. Download both of them 73 and add them to your project. Then, `import arsd.script;`, declare and populate a `var globals = var.emptyObject;`, 74 and `interpret("some code", globals);` in D. 75 76 There's nothing else to it, no complicated build, no external dependencies. 77 78 $(CONSOLE 79 $ wget https://raw.githubusercontent.com/adamdruppe/arsd/master/script.d 80 $ wget https://raw.githubusercontent.com/adamdruppe/arsd/master/jsvar.d 81 82 $ dmd yourfile.d script.d jsvar.d 83 ) 84 85 Script_features: 86 87 OVERVIEW 88 $(LIST 89 * Can subclass D objects in script. See [http://dpldocs.info/this-week-in-d/Blog.Posted_2020_04_27.html#subclasses-in-script 90 * easy interop with D thanks to arsd.jsvar. When interpreting, pass a var object to use as globals. 91 This object also contains the global state when interpretation is done. 92 * mostly familiar syntax, hybrid of D and Javascript 93 * 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. 94 ) 95 96 SPECIFICS 97 $(LIST 98 // * Allows identifiers-with-dashes. To do subtraction, put spaces around the minus sign. 99 * Allows identifiers starting with a dollar sign. 100 * string literals come in "foo" or 'foo', like Javascript, or `raw string` like D. Also come as “nested “double quotes” are an option!” 101 * double quoted string literals can do Ruby-style interpolation: "Hello, #{name}". 102 * mixin aka eval (does it at runtime, so more like eval than mixin, but I want it to look like D) 103 * scope guards, like in D 104 * Built-in assert() which prints its source and its arguments 105 * try/catch/finally/throw 106 You can use try as an expression without any following catch to return the exception: 107 108 ```adrscript 109 var a = try throw "exception";; // the double ; is because one closes the try, the second closes the var 110 // a is now the thrown exception 111 ``` 112 * for/while/foreach 113 * D style operators: +-/* on all numeric types, ~ on strings and arrays, |&^ on integers. 114 Operators can coerce types as needed: 10 ~ "hey" == "10hey". 10 + "3" == 13. 115 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). 116 Any bitwise math coerces to int. 117 118 So you can do some type coercion like this: 119 120 ```adrscript 121 a = a|0; // forces to int 122 a = "" ~ a; // forces to string 123 a = a+0.0; // coerces to float 124 ``` 125 126 Though casting is probably better. 127 * Type coercion via cast, similarly to D. 128 ```adrscript 129 var a = "12"; 130 a.typeof == "String"; 131 a = cast(int) a; 132 a.typeof == "Integral"; 133 a == 12; 134 ``` 135 136 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[]. 137 138 This forwards directly to the D function var.opCast. 139 140 * some operator overloading on objects, passing opBinary(op, rhs), length, and perhaps others through like they would be in D. 141 opIndex(name) 142 opIndexAssign(value, name) // same order as D, might some day support [n1, n2] => (value, n1, n2) 143 144 obj.__prop("name", value); // bypasses operator overloading, useful for use inside the opIndexAssign especially 145 146 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 147 148 FIXME: it doesn't do opIndex with multiple args. 149 * if/else 150 * array slicing, but note that slices are rvalues currently 151 * variables must start with A-Z, a-z, _, or $, then must be [A-Za-z0-9_]*. 152 (The $ can also stand alone, and this is a special thing when slicing, so you probably shouldn't use it at all.). 153 Variable names that start with __ are reserved and you shouldn't use them. 154 * int, float, string, array, bool, and `#{}` (previously known as `json!q{}` aka object) literals 155 * var.prototype, var.typeof. prototype works more like Mozilla's __proto__ than standard javascript prototype. 156 * the `|>` pipeline operator 157 * classes: 158 ```adrscript 159 // inheritance works 160 class Foo : bar { 161 // constructors, D style 162 this(var a) { ctor.... } 163 164 // static vars go on the auto created prototype 165 static var b = 10; 166 167 // instance vars go on this instance itself 168 var instancevar = 20; 169 170 // "virtual" functions can be overridden kinda like you expect in D, though there is no override keyword 171 function virt() { 172 b = 30; // lexical scoping is supported for static variables and functions 173 174 // but be sure to use this. as a prefix for any class defined instance variables in here 175 this.instancevar = 10; 176 } 177 } 178 179 var foo = new Foo(12); 180 181 foo.newFunc = function() { this.derived = 0; }; // this is ok too, and scoping, including 'this', works like in Javascript 182 ``` 183 184 You can also use 'new' on another object to get a copy of it. 185 * return, break, continue, but currently cannot do labeled breaks and continues 186 * __FILE__, __LINE__, but currently not as default arguments for D behavior (they always evaluate at the definition point) 187 * most everything are expressions, though note this is pretty buggy! But as a consequence: 188 for(var a = 0, b = 0; a < 10; a+=1, b+=1) {} 189 won't work but this will: 190 for(var a = 0, b = 0; a < 10; {a+=1; b+=1}) {} 191 192 You can encase things in {} anywhere instead of a comma operator, and it works kinda similarly. 193 194 {} creates a new scope inside it and returns the last value evaluated. 195 * functions: 196 var fn = function(args...) expr; 197 or 198 function fn(args....) expr; 199 200 Special function local variables: 201 _arguments = var[] of the arguments passed 202 _thisfunc = reference to the function itself 203 this = reference to the object on which it is being called - note this is like Javascript, not D. 204 205 args can say var if you want, but don't have to 206 default arguments supported in any position 207 when calling, you can use the default keyword to use the default value in any position 208 * macros: 209 A macro is defined just like a function, except with the 210 macro keyword instead of the function keyword. The difference 211 is a macro must interpret its own arguments - it is passed 212 AST objects instead of values. Still a WIP. 213 ) 214 215 216 Todo_list: 217 218 I also have a wishlist here that I may do in the future, but don't expect them any time soon. 219 220 FIXME: maybe some kind of splat operator too. choose([1,2,3]...) expands to choose(1,2,3) 221 222 make sure superclass ctors are called 223 224 FIXME: prettier stack trace when sent to D 225 226 FIXME: support more escape things in strings like \n, \t etc. 227 228 FIXME: add easy to use premade packages for the global object. 229 230 FIXME: the debugger statement from javascript might be cool to throw in too. 231 232 FIXME: add continuations or something too - actually doing it with fibers works pretty well 233 234 FIXME: Also ability to get source code for function something so you can mixin. 235 236 FIXME: add COM support on Windows ???? 237 238 239 Might be nice: 240 varargs 241 lambdas - maybe without function keyword and the x => foo syntax from D. 242 243 Author: Adam D Ruppe 244 245 History: 246 September 1, 2020: added overloading for functions and type matching in `catch` blocks among other bug fixes 247 248 April 28, 2020: added `#{}` as an alternative to the `json!q{}` syntax for object literals. Also fixed unary `!` operator. 249 250 April 26, 2020: added `switch`, fixed precedence bug, fixed doc issues and added some unittests 251 252 Started writing it in July 2013. Yes, a basic precedence issue was there for almost SEVEN YEARS. You can use this as a toy but please don't use it for anything too serious, it really is very poorly written and not intelligently designed at all. 253 +/ 254 module arsd.script; 255 256 /++ 257 This example shows the basics of how to interact with the script. 258 The string enclosed in `q{ .. }` is the script language source. 259 260 The [var] type comes from [arsd.jsvar] and provides a dynamic type 261 to D. It is the same type used in the script language and is weakly 262 typed, providing operator overloads to work with many D types seamlessly. 263 264 However, if you do need to convert it to a static type, such as if passing 265 to a function, you can use `get!T` to get a static type out of it. 266 +/ 267 unittest { 268 var globals = var.emptyObject; 269 globals.x = 25; // we can set variables on the global object 270 globals.name = "script.d"; // of various types 271 // and we can make native functions available to the script 272 globals.sum = (int a, int b) { 273 return a + b; 274 }; 275 276 // This is the source code of the script. It is similar 277 // to javascript with pieces borrowed from D, so should 278 // be pretty familiar. 279 string scriptSource = q{ 280 function foo() { 281 return 13; 282 } 283 284 var a = foo() + 12; 285 assert(a == 25); 286 287 // you can also access the D globals from the script 288 assert(x == 25); 289 assert(name == "script.d"); 290 291 // as well as call D functions set via globals: 292 assert(sum(5, 6) == 11); 293 294 // I will also set a function to call from D 295 function bar(str) { 296 // unlike Javascript though, we use the D style 297 // concatenation operator. 298 return str ~ " concatenation"; 299 } 300 }; 301 302 // once you have the globals set up, you call the interpreter 303 // with one simple function. 304 interpret(scriptSource, globals); 305 306 // finally, globals defined from the script are accessible here too: 307 // however, notice the two sets of parenthesis: the first is because 308 // @property is broken in D. The second set calls the function and you 309 // can pass values to it. 310 assert(globals.foo()() == 13); 311 312 assert(globals.bar()("test") == "test concatenation"); 313 314 // this shows how to convert the var back to a D static type. 315 int x = globals.x.get!int; 316 } 317 318 /++ 319 $(H3 Macros) 320 321 Macros are like functions, but instead of evaluating their arguments at 322 the call site and passing value, the AST nodes are passed right in. Calling 323 the node evaluates the argument and yields the result (this is similar to 324 to `lazy` parameters in D), and they also have methods like `toSourceCode`, 325 `type`, and `interpolate`, which forwards to the given string. 326 327 The language also supports macros and custom interpolation functions. This 328 example shows an interpolation string being passed to a macro and used 329 with a custom interpolation string. 330 331 You might use this to encode interpolated things or something like that. 332 +/ 333 unittest { 334 var globals = var.emptyObject; 335 interpret(q{ 336 macro test(x) { 337 return x.interpolate(function(str) { 338 return str ~ "test"; 339 }); 340 } 341 342 var a = "cool"; 343 assert(test("hey #{a}") == "hey cooltest"); 344 }, globals); 345 } 346 347 /++ 348 $(H3 Classes demo) 349 350 See also: [arsd.jsvar.subclassable] for more interop with D classes. 351 +/ 352 unittest { 353 var globals = var.emptyObject; 354 interpret(q{ 355 class Base { 356 function foo() { return "Base"; } 357 function set() { this.a = 10; } 358 function get() { return this.a; } // this MUST be used for instance variables though as they do not exist in static lookup 359 function test() { return foo(); } // I did NOT use `this` here which means it does STATIC lookup! 360 // kinda like mixin templates in D lol. 361 var a = 5; 362 static var b = 10; // static vars are attached to the class specifically 363 } 364 class Child : Base { 365 function foo() { 366 assert(super.foo() == "Base"); 367 return "Child"; 368 }; 369 function set() { this.a = 7; } 370 function get2() { return this.a; } 371 var a = 9; 372 } 373 374 var c = new Child(); 375 assert(c.foo() == "Child"); 376 377 assert(c.test() == "Base"); // static lookup of methods if you don't use `this` 378 379 /* 380 // these would pass in D, but do NOT pass here because of dynamic variable lookup in script. 381 assert(c.get() == 5); 382 assert(c.get2() == 9); 383 c.set(); 384 assert(c.get() == 5); // parent instance is separate 385 assert(c.get2() == 7); 386 */ 387 388 // showing the shared vars now.... I personally prefer the D way but meh, this lang 389 // is an unholy cross of D and Javascript so that means it sucks sometimes. 390 assert(c.get() == c.get2()); 391 c.set(); 392 assert(c.get2() == 7); 393 assert(c.get() == c.get2()); 394 395 // super, on the other hand, must always be looked up statically, or else this 396 // next example with infinite recurse and smash the stack. 397 class Third : Child { } 398 var t = new Third(); 399 assert(t.foo() == "Child"); 400 }, globals); 401 } 402 403 /++ 404 $(H3 Properties from D) 405 406 Note that it is not possible yet to define a property function from the script language. 407 +/ 408 unittest { 409 static class Test { 410 // the @scriptable is required to make it accessible 411 @scriptable int a; 412 413 @scriptable @property int ro() { return 30; } 414 415 int _b = 20; 416 @scriptable @property int b() { return _b; } 417 @scriptable @property int b(int val) { return _b = val; } 418 } 419 420 Test test = new Test; 421 422 test.a = 15; 423 424 var globals = var.emptyObject; 425 globals.test = test; 426 // but once it is @scriptable, both read and write works from here: 427 interpret(q{ 428 assert(test.a == 15); 429 test.a = 10; 430 assert(test.a == 10); 431 432 assert(test.ro == 30); // @property functions from D wrapped too 433 test.ro = 40; 434 assert(test.ro == 30); // setting it does nothing though 435 436 assert(test.b == 20); // reader still works if read/write available too 437 test.b = 25; 438 assert(test.b == 25); // writer action reflected 439 440 // however other opAssign operators are not implemented correctly on properties at this time so this fails! 441 //test.b *= 2; 442 //assert(test.b == 50); 443 }, globals); 444 445 // and update seen back in D 446 assert(test.a == 10); // on the original native object 447 assert(test.b == 25); 448 449 assert(globals.test.a == 10); // and via the var accessor for member var 450 assert(globals.test.b == 25); // as well as @property func 451 } 452 453 454 public import arsd.jsvar; 455 456 import std.stdio; 457 import std.traits; 458 import std.conv; 459 import std.json; 460 461 import std.array; 462 import std.range; 463 464 /* ************************************** 465 script to follow 466 ****************************************/ 467 468 /++ 469 A base class for exceptions that can never be caught by scripts; 470 throwing it from a function called from a script is guaranteed to 471 bubble all the way up to your [interpret] call.. 472 (scripts can also never catch Error btw) 473 474 History: 475 Added on April 24, 2020 (v7.3.0) 476 +/ 477 class NonScriptCatchableException : Exception { 478 import std.exception; 479 /// 480 mixin basicExceptionCtors; 481 } 482 483 //class TEST : Throwable {this() { super("lol"); }} 484 485 /// Thrown on script syntax errors and the sort. 486 class ScriptCompileException : Exception { 487 string s; 488 int lineNumber; 489 this(string msg, string s, int lineNumber, string file = __FILE__, size_t line = __LINE__) { 490 this.s = s; 491 this.lineNumber = lineNumber; 492 super(to!string(lineNumber) ~ ": " ~ msg, file, line); 493 } 494 } 495 496 /// Thrown on things like interpretation failures. 497 class ScriptRuntimeException : Exception { 498 string s; 499 int lineNumber; 500 this(string msg, string s, int lineNumber, string file = __FILE__, size_t line = __LINE__) { 501 this.s = s; 502 this.lineNumber = lineNumber; 503 super(to!string(lineNumber) ~ ": " ~ msg, file, line); 504 } 505 } 506 507 /// This represents an exception thrown by `throw x;` inside the script as it is interpreted. 508 class ScriptException : Exception { 509 /// 510 var payload; 511 /// 512 ScriptLocation loc; 513 /// 514 ScriptLocation[] callStack; 515 this(var payload, ScriptLocation loc, string file = __FILE__, size_t line = __LINE__) { 516 this.payload = payload; 517 if(loc.scriptFilename.length == 0) 518 loc.scriptFilename = "user_script"; 519 this.loc = loc; 520 super(loc.scriptFilename ~ "@" ~ to!string(loc.lineNumber) ~ ": " ~ to!string(payload), file, line); 521 } 522 523 /* 524 override string toString() { 525 return loc.scriptFilename ~ "@" ~ to!string(loc.lineNumber) ~ ": " ~ payload.get!string ~ to!string(callStack); 526 } 527 */ 528 529 // might be nice to take a D exception and put a script stack trace in there too...... 530 // also need toString to show the callStack 531 } 532 533 struct ScriptToken { 534 enum Type { identifier, keyword, symbol, string, int_number, float_number } 535 Type type; 536 string str; 537 string scriptFilename; 538 int lineNumber; 539 540 string wasSpecial; 541 } 542 543 // these need to be ordered from longest to shortest 544 // some of these aren't actually used, like struct and goto right now, but I want them reserved for later 545 private enum string[] keywords = [ 546 "function", "continue", 547 "__FILE__", "__LINE__", // these two are special to the lexer 548 "foreach", "json!q{", "default", "finally", 549 "return", "static", "struct", "import", "module", "assert", "switch", 550 "while", "catch", "throw", "scope", "break", "class", "false", "mixin", "macro", "super", 551 // "this" is just treated as just a magic identifier..... 552 "auto", // provided as an alias for var right now, may change later 553 "null", "else", "true", "eval", "goto", "enum", "case", "cast", 554 "var", "for", "try", "new", 555 "if", "do", 556 ]; 557 private enum string[] symbols = [ 558 ">>>", // FIXME 559 "//", "/*", "/+", 560 "&&", "||", 561 "+=", "-=", "*=", "/=", "~=", "==", "<=", ">=","!=", "%=", 562 "&=", "|=", "^=", 563 "#{", 564 "..", 565 "<<", ">>", // FIXME 566 "|>", 567 "=>", // FIXME 568 "?", ".",",",";",":", 569 "[", "]", "{", "}", "(", ")", 570 "&", "|", "^", 571 "+", "-", "*", "/", "=", "<", ">","~","!","%" 572 ]; 573 574 // we need reference semantics on this all the time 575 class TokenStream(TextStream) { 576 TextStream textStream; 577 string text; 578 int lineNumber = 1; 579 string scriptFilename; 580 581 void advance(ptrdiff_t size) { 582 foreach(i; 0 .. size) { 583 if(text.empty) 584 break; 585 if(text[0] == '\n') 586 lineNumber ++; 587 text = text[1 .. $]; 588 // text.popFront(); // don't want this because it pops too much trying to do its own UTF-8, which we already handled! 589 } 590 } 591 592 this(TextStream ts, string fn) { 593 textStream = ts; 594 scriptFilename = fn; 595 text = textStream.front; 596 popFront; 597 } 598 599 ScriptToken next; 600 601 // FIXME: might be worth changing this so i can peek far enough ahead to do () => expr lambdas. 602 ScriptToken peek; 603 bool peeked; 604 void pushFront(ScriptToken f) { 605 peek = f; 606 peeked = true; 607 } 608 609 ScriptToken front() { 610 if(peeked) 611 return peek; 612 else 613 return next; 614 } 615 616 bool empty() { 617 advanceSkips(); 618 return text.length == 0 && textStream.empty && !peeked; 619 } 620 621 int skipNext; 622 void advanceSkips() { 623 if(skipNext) { 624 skipNext--; 625 popFront(); 626 } 627 } 628 629 void popFront() { 630 if(peeked) { 631 peeked = false; 632 return; 633 } 634 635 assert(!empty); 636 mainLoop: 637 while(text.length) { 638 ScriptToken token; 639 token.lineNumber = lineNumber; 640 token.scriptFilename = scriptFilename; 641 642 if(text[0] == ' ' || text[0] == '\t' || text[0] == '\n' || text[0] == '\r') { 643 advance(1); 644 continue; 645 } else if(text[0] >= '0' && text[0] <= '9') { 646 int pos; 647 bool sawDot; 648 while(pos < text.length && ((text[pos] >= '0' && text[pos] <= '9') || text[pos] == '.')) { 649 if(text[pos] == '.') { 650 if(sawDot) 651 break; 652 else 653 sawDot = true; 654 } 655 pos++; 656 } 657 658 if(text[pos - 1] == '.') { 659 // This is something like "1.x", which is *not* a floating literal; it is UFCS on an int 660 sawDot = false; 661 pos --; 662 } 663 664 token.type = sawDot ? ScriptToken.Type.float_number : ScriptToken.Type.int_number; 665 token.str = text[0 .. pos]; 666 advance(pos); 667 } else if((text[0] >= 'a' && text[0] <= 'z') || (text[0] == '_') || (text[0] >= 'A' && text[0] <= 'Z') || text[0] == '$') { 668 bool found = false; 669 foreach(keyword; keywords) 670 if(text.length >= keyword.length && text[0 .. keyword.length] == keyword && 671 // making sure this isn't an identifier that starts with a keyword 672 (text.length == keyword.length || !( 673 ( 674 (text[keyword.length] >= '0' && text[keyword.length] <= '9') || 675 (text[keyword.length] >= 'a' && text[keyword.length] <= 'z') || 676 (text[keyword.length] == '_') || 677 (text[keyword.length] >= 'A' && text[keyword.length] <= 'Z') 678 ) 679 ))) 680 { 681 found = true; 682 if(keyword == "__FILE__") { 683 token.type = ScriptToken.Type..string; 684 token.str = to!string(token.scriptFilename); 685 token.wasSpecial = keyword; 686 } else if(keyword == "__LINE__") { 687 token.type = ScriptToken.Type.int_number; 688 token.str = to!string(token.lineNumber); 689 token.wasSpecial = keyword; 690 } else { 691 token.type = ScriptToken.Type.keyword; 692 // auto is done as an alias to var in the lexer just so D habits work there too 693 if(keyword == "auto") { 694 token.str = "var"; 695 token.wasSpecial = keyword; 696 } else 697 token.str = keyword; 698 } 699 advance(keyword.length); 700 break; 701 } 702 703 if(!found) { 704 token.type = ScriptToken.Type.identifier; 705 int pos; 706 if(text[0] == '$') 707 pos++; 708 709 while(pos < text.length 710 && ((text[pos] >= 'a' && text[pos] <= 'z') || 711 (text[pos] == '_') || 712 //(pos != 0 && text[pos] == '-') || // allow mid-identifier dashes for this-kind-of-name. For subtraction, add a space. 713 (text[pos] >= 'A' && text[pos] <= 'Z') || 714 (text[pos] >= '0' && text[pos] <= '9'))) 715 { 716 pos++; 717 } 718 719 token.str = text[0 .. pos]; 720 advance(pos); 721 } 722 } else if(text[0] == '"' || text[0] == '\'' || text[0] == '`' || 723 // Also supporting double curly quoted strings: “foo” which nest. This is the utf 8 coding: 724 (text.length >= 3 && text[0] == 0xe2 && text[1] == 0x80 && text[2] == 0x9c)) 725 { 726 char end = text[0]; // support single quote and double quote strings the same 727 int openCurlyQuoteCount = (end == 0xe2) ? 1 : 0; 728 bool escapingAllowed = end != '`'; // `` strings are raw, they don't support escapes. the others do. 729 token.type = ScriptToken.Type..string; 730 int pos = openCurlyQuoteCount ? 3 : 1; // skip the opening dchar 731 int started = pos; 732 bool escaped = false; 733 bool mustCopy = false; 734 735 bool allowInterpolation = text[0] == '"'; 736 737 bool atEnd() { 738 if(pos == text.length) 739 return false; 740 if(openCurlyQuoteCount) { 741 if(openCurlyQuoteCount == 1) 742 return (pos + 3 <= text.length && text[pos] == 0xe2 && text[pos+1] == 0x80 && text[pos+2] == 0x9d); // ” 743 else // greater than one means we nest 744 return false; 745 } else 746 return text[pos] == end; 747 } 748 749 bool interpolationDetected = false; 750 bool inInterpolate = false; 751 int interpolateCount = 0; 752 753 while(pos < text.length && (escaped || inInterpolate || !atEnd())) { 754 if(inInterpolate) { 755 if(text[pos] == '{') 756 interpolateCount++; 757 else if(text[pos] == '}') { 758 interpolateCount--; 759 if(interpolateCount == 0) 760 inInterpolate = false; 761 } 762 pos++; 763 continue; 764 } 765 766 if(escaped) { 767 mustCopy = true; 768 escaped = false; 769 } else { 770 if(text[pos] == '\\' && escapingAllowed) 771 escaped = true; 772 if(allowInterpolation && text[pos] == '#' && pos + 1 < text.length && text[pos + 1] == '{') { 773 interpolationDetected = true; 774 inInterpolate = true; 775 } 776 if(openCurlyQuoteCount) { 777 // also need to count curly quotes to support nesting 778 if(pos + 3 <= text.length && text[pos+0] == 0xe2 && text[pos+1] == 0x80 && text[pos+2] == 0x9c) // “ 779 openCurlyQuoteCount++; 780 if(pos + 3 <= text.length && text[pos+0] == 0xe2 && text[pos+1] == 0x80 && text[pos+2] == 0x9d) // ” 781 openCurlyQuoteCount--; 782 } 783 } 784 pos++; 785 } 786 787 if(pos == text.length && (escaped || inInterpolate || !atEnd())) 788 throw new ScriptCompileException("Unclosed string literal", token.scriptFilename, token.lineNumber); 789 790 if(mustCopy) { 791 // there must be something escaped in there, so we need 792 // to copy it and properly handle those cases 793 string copy; 794 copy.reserve(pos + 4); 795 796 escaped = false; 797 foreach(idx, dchar ch; text[started .. pos]) { 798 if(escaped) { 799 escaped = false; 800 switch(ch) { 801 case '\\': copy ~= "\\"; break; 802 case 'n': copy ~= "\n"; break; 803 case 'r': copy ~= "\r"; break; 804 case 'a': copy ~= "\a"; break; 805 case 't': copy ~= "\t"; break; 806 case '#': copy ~= "#"; break; 807 case '"': copy ~= "\""; break; 808 case '\'': copy ~= "'"; break; 809 default: 810 throw new ScriptCompileException("Unknown escape char " ~ cast(char) ch, token.scriptFilename, token.lineNumber); 811 } 812 continue; 813 } else if(ch == '\\') { 814 escaped = true; 815 continue; 816 } 817 copy ~= ch; 818 } 819 820 token.str = copy; 821 } else { 822 token.str = text[started .. pos]; 823 } 824 if(interpolationDetected) 825 token.wasSpecial = "\""; 826 advance(pos + ((end == 0xe2) ? 3 : 1)); // skip the closing " too 827 } else { 828 // let's check all symbols 829 bool found = false; 830 foreach(symbol; symbols) 831 if(text.length >= symbol.length && text[0 .. symbol.length] == symbol) { 832 833 if(symbol == "//") { 834 // one line comment 835 int pos = 0; 836 while(pos < text.length && text[pos] != '\n' && text[0] != '\r') 837 pos++; 838 advance(pos); 839 continue mainLoop; 840 } else if(symbol == "/*") { 841 int pos = 0; 842 while(pos + 1 < text.length && text[pos..pos+2] != "*/") 843 pos++; 844 845 if(pos + 1 == text.length) 846 throw new ScriptCompileException("unclosed /* */ comment", token.scriptFilename, lineNumber); 847 848 advance(pos + 2); 849 continue mainLoop; 850 851 } else if(symbol == "/+") { 852 int open = 0; 853 int pos = 0; 854 while(pos + 1 < text.length) { 855 if(text[pos..pos+2] == "/+") { 856 open++; 857 pos++; 858 } else if(text[pos..pos+2] == "+/") { 859 open--; 860 pos++; 861 if(open == 0) 862 break; 863 } 864 pos++; 865 } 866 867 if(pos + 1 == text.length) 868 throw new ScriptCompileException("unclosed /+ +/ comment", token.scriptFilename, lineNumber); 869 870 advance(pos + 1); 871 continue mainLoop; 872 } 873 // FIXME: documentation comments 874 875 found = true; 876 token.type = ScriptToken.Type.symbol; 877 token.str = symbol; 878 advance(symbol.length); 879 break; 880 } 881 882 if(!found) { 883 // FIXME: make sure this gives a valid utf-8 sequence 884 throw new ScriptCompileException("unknown token " ~ text[0], token.scriptFilename, lineNumber); 885 } 886 } 887 888 next = token; 889 return; 890 } 891 892 textStream.popFront(); 893 if(!textStream.empty()) { 894 text = textStream.front; 895 goto mainLoop; 896 } 897 898 return; 899 } 900 901 } 902 903 TokenStream!TextStream lexScript(TextStream)(TextStream textStream, string scriptFilename) if(is(ElementType!TextStream == string)) { 904 return new TokenStream!TextStream(textStream, scriptFilename); 905 } 906 907 class MacroPrototype : PrototypeObject { 908 var func; 909 910 // macros are basically functions that get special treatment for their arguments 911 // they are passed as AST objects instead of interpreted 912 // calling an AST object will interpret it in the script 913 this(var func) { 914 this.func = func; 915 this._properties["opCall"] = (var _this, var[] args) { 916 return func.apply(_this, args); 917 }; 918 } 919 } 920 921 alias helper(alias T) = T; 922 // alternative to virtual function for converting the expression objects to script objects 923 void addChildElementsOfExpressionToScriptExpressionObject(ClassInfo c, Expression _thisin, PrototypeObject sc, ref var obj) { 924 foreach(itemName; __traits(allMembers, mixin(__MODULE__))) 925 static if(__traits(compiles, __traits(getMember, mixin(__MODULE__), itemName))) { 926 alias Class = helper!(__traits(getMember, mixin(__MODULE__), itemName)); 927 static if(is(Class : Expression)) if(c == typeid(Class)) { 928 auto _this = cast(Class) _thisin; 929 foreach(memberName; __traits(allMembers, Class)) { 930 alias member = helper!(__traits(getMember, Class, memberName)); 931 932 static if(is(typeof(member) : Expression)) { 933 auto lol = __traits(getMember, _this, memberName); 934 if(lol is null) 935 obj[memberName] = null; 936 else 937 obj[memberName] = lol.toScriptExpressionObject(sc); 938 } 939 static if(is(typeof(member) : Expression[])) { 940 obj[memberName] = var.emptyArray; 941 foreach(m; __traits(getMember, _this, memberName)) 942 if(m !is null) 943 obj[memberName] ~= m.toScriptExpressionObject(sc); 944 else 945 obj[memberName] ~= null; 946 } 947 static if(is(typeof(member) : string) || is(typeof(member) : long) || is(typeof(member) : real) || is(typeof(member) : bool)) { 948 obj[memberName] = __traits(getMember, _this, memberName); 949 } 950 } 951 } 952 } 953 } 954 955 struct InterpretResult { 956 var value; 957 PrototypeObject sc; 958 enum FlowControl { Normal, Return, Continue, Break, Goto } 959 FlowControl flowControl; 960 string flowControlDetails; // which label 961 } 962 963 class Expression { 964 abstract InterpretResult interpret(PrototypeObject sc); 965 966 // this returns an AST object that can be inspected and possibly altered 967 // by the script. Calling the returned object will interpret the object in 968 // the original scope passed 969 var toScriptExpressionObject(PrototypeObject sc) { 970 var obj = var.emptyObject; 971 972 obj["type"] = typeid(this).name; 973 obj["toSourceCode"] = (var _this, var[] args) { 974 Expression e = this; 975 return var(e.toString()); 976 }; 977 obj["opCall"] = (var _this, var[] args) { 978 Expression e = this; 979 // FIXME: if they changed the properties in the 980 // script, we should update them here too. 981 return e.interpret(sc).value; 982 }; 983 obj["interpolate"] = (var _this, var[] args) { 984 StringLiteralExpression e = cast(StringLiteralExpression) this; 985 if(!e) 986 return var(null); 987 return e.interpolate(args.length ? args[0] : var(null), sc); 988 }; 989 990 991 // adding structure is going to be a little bit magical 992 // I could have done this with a virtual function, but I'm lazy. 993 addChildElementsOfExpressionToScriptExpressionObject(typeid(this), this, sc, obj); 994 995 return obj; 996 } 997 998 string toInterpretedString(PrototypeObject sc) { 999 return toString(); 1000 } 1001 } 1002 1003 class MixinExpression : Expression { 1004 Expression e1; 1005 this(Expression e1) { 1006 this.e1 = e1; 1007 } 1008 1009 override string toString() { return "mixin(" ~ e1.toString() ~ ")"; } 1010 1011 override InterpretResult interpret(PrototypeObject sc) { 1012 return InterpretResult(.interpret(e1.interpret(sc).value.get!string ~ ";", sc), sc); 1013 } 1014 } 1015 1016 class StringLiteralExpression : Expression { 1017 string content; 1018 bool allowInterpolation; 1019 1020 ScriptToken token; 1021 1022 override string toString() { 1023 import std.string : replace; 1024 return "\"" ~ content.replace(`\`, `\\`).replace("\"", "\\\"") ~ "\""; 1025 } 1026 1027 this(ScriptToken token) { 1028 this.token = token; 1029 this(token.str); 1030 if(token.wasSpecial == "\"") 1031 allowInterpolation = true; 1032 1033 } 1034 1035 this(string s) { 1036 content = s; 1037 } 1038 1039 var interpolate(var funcObj, PrototypeObject sc) { 1040 import std.string : indexOf; 1041 if(allowInterpolation) { 1042 string r; 1043 1044 auto c = content; 1045 auto idx = c.indexOf("#{"); 1046 while(idx != -1) { 1047 r ~= c[0 .. idx]; 1048 c = c[idx + 2 .. $]; 1049 idx = 0; 1050 int open = 1; 1051 while(idx < c.length) { 1052 if(c[idx] == '}') 1053 open--; 1054 else if(c[idx] == '{') 1055 open++; 1056 if(open == 0) 1057 break; 1058 idx++; 1059 } 1060 if(open != 0) 1061 throw new ScriptRuntimeException("Unclosed interpolation thing", token.scriptFilename, token.lineNumber); 1062 auto code = c[0 .. idx]; 1063 1064 var result = .interpret(code, sc); 1065 1066 if(funcObj == var(null)) 1067 r ~= result.get!string; 1068 else 1069 r ~= funcObj(result).get!string; 1070 1071 c = c[idx + 1 .. $]; 1072 idx = c.indexOf("#{"); 1073 } 1074 1075 r ~= c; 1076 return var(r); 1077 } else { 1078 return var(content); 1079 } 1080 } 1081 1082 override InterpretResult interpret(PrototypeObject sc) { 1083 return InterpretResult(interpolate(var(null), sc), sc); 1084 } 1085 } 1086 1087 class BoolLiteralExpression : Expression { 1088 bool literal; 1089 this(string l) { 1090 literal = to!bool(l); 1091 } 1092 1093 override string toString() { return to!string(literal); } 1094 1095 override InterpretResult interpret(PrototypeObject sc) { 1096 return InterpretResult(var(literal), sc); 1097 } 1098 } 1099 1100 class IntLiteralExpression : Expression { 1101 long literal; 1102 1103 this(string s) { 1104 literal = to!long(s); 1105 } 1106 1107 override string toString() { return to!string(literal); } 1108 1109 override InterpretResult interpret(PrototypeObject sc) { 1110 return InterpretResult(var(literal), sc); 1111 } 1112 } 1113 class FloatLiteralExpression : Expression { 1114 this(string s) { 1115 literal = to!real(s); 1116 } 1117 real literal; 1118 override string toString() { return to!string(literal); } 1119 override InterpretResult interpret(PrototypeObject sc) { 1120 return InterpretResult(var(literal), sc); 1121 } 1122 } 1123 class NullLiteralExpression : Expression { 1124 this() {} 1125 override string toString() { return "null"; } 1126 1127 override InterpretResult interpret(PrototypeObject sc) { 1128 var n; 1129 return InterpretResult(n, sc); 1130 } 1131 } 1132 class NegationExpression : Expression { 1133 Expression e; 1134 this(Expression e) { this.e = e;} 1135 override string toString() { return "-" ~ e.toString(); } 1136 1137 override InterpretResult interpret(PrototypeObject sc) { 1138 var n = e.interpret(sc).value; 1139 return InterpretResult(-n, sc); 1140 } 1141 } 1142 class NotExpression : Expression { 1143 Expression e; 1144 this(Expression e) { this.e = e;} 1145 override string toString() { return "!" ~ e.toString(); } 1146 1147 override InterpretResult interpret(PrototypeObject sc) { 1148 var n = e.interpret(sc).value; 1149 return InterpretResult(var(!n), sc); 1150 } 1151 } 1152 class BitFlipExpression : Expression { 1153 Expression e; 1154 this(Expression e) { this.e = e;} 1155 override string toString() { return "~" ~ e.toString(); } 1156 1157 override InterpretResult interpret(PrototypeObject sc) { 1158 var n = e.interpret(sc).value; 1159 // possible FIXME given the size. but it is fuzzy when dynamic.. 1160 return InterpretResult(var(~(n.get!long)), sc); 1161 } 1162 } 1163 1164 class ArrayLiteralExpression : Expression { 1165 this() {} 1166 1167 override string toString() { 1168 string s = "["; 1169 foreach(i, ele; elements) { 1170 if(i) s ~= ", "; 1171 s ~= ele.toString(); 1172 } 1173 s ~= "]"; 1174 return s; 1175 } 1176 1177 Expression[] elements; 1178 override InterpretResult interpret(PrototypeObject sc) { 1179 var n = var.emptyArray; 1180 foreach(i, element; elements) 1181 n[i] = element.interpret(sc).value; 1182 return InterpretResult(n, sc); 1183 } 1184 } 1185 class ObjectLiteralExpression : Expression { 1186 Expression[string] elements; 1187 1188 override string toString() { 1189 string s = "#{"; 1190 bool first = true; 1191 foreach(k, e; elements) { 1192 if(first) 1193 first = false; 1194 else 1195 s ~= ", "; 1196 1197 s ~= "\"" ~ k ~ "\":"; // FIXME: escape if needed 1198 s ~= e.toString(); 1199 } 1200 1201 s ~= "}"; 1202 return s; 1203 } 1204 1205 PrototypeObject backing; 1206 this(PrototypeObject backing = null) { 1207 this.backing = backing; 1208 } 1209 1210 override InterpretResult interpret(PrototypeObject sc) { 1211 var n; 1212 if(backing is null) 1213 n = var.emptyObject; 1214 else 1215 n._object = backing; 1216 1217 foreach(k, v; elements) 1218 n[k] = v.interpret(sc).value; 1219 1220 return InterpretResult(n, sc); 1221 } 1222 } 1223 class FunctionLiteralExpression : Expression { 1224 this() { 1225 // we want this to not be null at all when we're interpreting since it is used as a comparison for a magic operation 1226 if(DefaultArgumentDummyObject is null) 1227 DefaultArgumentDummyObject = new PrototypeObject(); 1228 } 1229 1230 this(VariableDeclaration args, Expression bod, PrototypeObject lexicalScope = null) { 1231 this(); 1232 this.arguments = args; 1233 this.functionBody = bod; 1234 this.lexicalScope = lexicalScope; 1235 } 1236 1237 override string toString() { 1238 string s = (isMacro ? "macro" : "function") ~ " ("; 1239 if(arguments !is null) 1240 s ~= arguments.toString(); 1241 1242 s ~= ") "; 1243 s ~= functionBody.toString(); 1244 return s; 1245 } 1246 1247 /* 1248 function identifier (arg list) expression 1249 1250 so 1251 var e = function foo() 10; // valid 1252 var e = function foo() { return 10; } // also valid 1253 1254 // the return value is just the last expression's result that was evaluated 1255 // to return void, be sure to do a "return;" at the end of the function 1256 */ 1257 VariableDeclaration arguments; 1258 Expression functionBody; // can be a ScopeExpression btw 1259 1260 PrototypeObject lexicalScope; 1261 1262 bool isMacro; 1263 1264 override InterpretResult interpret(PrototypeObject sc) { 1265 assert(DefaultArgumentDummyObject !is null); 1266 var v; 1267 v._metadata = new ScriptFunctionMetadata(this); 1268 v._function = (var _this, var[] args) { 1269 auto argumentsScope = new PrototypeObject(); 1270 PrototypeObject scToUse; 1271 if(lexicalScope is null) 1272 scToUse = sc; 1273 else { 1274 scToUse = lexicalScope; 1275 scToUse._secondary = sc; 1276 } 1277 1278 argumentsScope.prototype = scToUse; 1279 1280 argumentsScope._getMember("this", false, false) = _this; 1281 argumentsScope._getMember("_arguments", false, false) = args; 1282 argumentsScope._getMember("_thisfunc", false, false) = v; 1283 1284 if(arguments) 1285 foreach(i, identifier; arguments.identifiers) { 1286 argumentsScope._getMember(identifier, false, false); // create it in this scope... 1287 if(i < args.length && !(args[i].payloadType() == var.Type.Object && args[i]._payload._object is DefaultArgumentDummyObject)) 1288 argumentsScope._getMember(identifier, false, true) = args[i]; 1289 else 1290 if(arguments.initializers[i] !is null) 1291 argumentsScope._getMember(identifier, false, true) = arguments.initializers[i].interpret(sc).value; 1292 } 1293 1294 if(functionBody !is null) 1295 return functionBody.interpret(argumentsScope).value; 1296 else { 1297 assert(0); 1298 } 1299 }; 1300 if(isMacro) { 1301 var n = var.emptyObject; 1302 n._object = new MacroPrototype(v); 1303 v = n; 1304 } 1305 return InterpretResult(v, sc); 1306 } 1307 } 1308 1309 class CastExpression : Expression { 1310 string type; 1311 Expression e1; 1312 1313 override string toString() { 1314 return "cast(" ~ type ~ ") " ~ e1.toString(); 1315 } 1316 1317 override InterpretResult interpret(PrototypeObject sc) { 1318 var n = e1.interpret(sc).value; 1319 switch(type) { 1320 foreach(possibleType; CtList!("int", "long", "float", "double", "real", "char", "dchar", "string", "int[]", "string[]", "float[]")) { 1321 case possibleType: 1322 n = mixin("cast(" ~ possibleType ~ ") n"); 1323 break; 1324 } 1325 default: 1326 // FIXME, we can probably cast other types like classes here. 1327 } 1328 1329 return InterpretResult(n, sc); 1330 } 1331 } 1332 1333 class VariableDeclaration : Expression { 1334 string[] identifiers; 1335 Expression[] initializers; 1336 string[] typeSpecifiers; 1337 1338 this() {} 1339 1340 override string toString() { 1341 string s = ""; 1342 foreach(i, ident; identifiers) { 1343 if(i) 1344 s ~= ", "; 1345 s ~= "var "; 1346 if(typeSpecifiers[i].length) { 1347 s ~= typeSpecifiers[i]; 1348 s ~= " "; 1349 } 1350 s ~= ident; 1351 if(initializers[i] !is null) 1352 s ~= " = " ~ initializers[i].toString(); 1353 } 1354 return s; 1355 } 1356 1357 1358 override InterpretResult interpret(PrototypeObject sc) { 1359 var n; 1360 1361 foreach(i, identifier; identifiers) { 1362 n = sc._getMember(identifier, false, false); 1363 auto initializer = initializers[i]; 1364 if(initializer) { 1365 n = initializer.interpret(sc).value; 1366 sc._getMember(identifier, false, false) = n; 1367 } 1368 } 1369 return InterpretResult(n, sc); 1370 } 1371 } 1372 1373 class FunctionDeclaration : Expression { 1374 DotVarExpression where; 1375 string ident; 1376 FunctionLiteralExpression expr; 1377 1378 this(DotVarExpression where, string ident, FunctionLiteralExpression expr) { 1379 this.where = where; 1380 this.ident = ident; 1381 this.expr = expr; 1382 } 1383 1384 override InterpretResult interpret(PrototypeObject sc) { 1385 var n = expr.interpret(sc).value; 1386 1387 var replacement; 1388 1389 if(expr.isMacro) { 1390 // can't overload macros 1391 replacement = n; 1392 } else { 1393 var got; 1394 1395 if(where is null) { 1396 got = sc._getMember(ident, false, false); 1397 } else { 1398 got = where.interpret(sc).value; 1399 } 1400 1401 OverloadSet os = got.get!OverloadSet; 1402 if(os is null) { 1403 os = new OverloadSet; 1404 } 1405 1406 os.addOverload(OverloadSet.Overload(expr.arguments ? toTypes(expr.arguments.typeSpecifiers, sc) : null, n)); 1407 1408 replacement = var(os); 1409 } 1410 1411 if(where is null) { 1412 sc._getMember(ident, false, false) = replacement; 1413 } else { 1414 where.setVar(sc, replacement, false, true); 1415 } 1416 1417 return InterpretResult(n, sc); 1418 } 1419 1420 override string toString() { 1421 string s = (expr.isMacro ? "macro" : "function") ~ " "; 1422 s ~= ident; 1423 s ~= "("; 1424 if(expr.arguments !is null) 1425 s ~= expr.arguments.toString(); 1426 1427 s ~= ") "; 1428 s ~= expr.functionBody.toString(); 1429 1430 return s; 1431 } 1432 } 1433 1434 template CtList(T...) { alias CtList = T; } 1435 1436 class BinaryExpression : Expression { 1437 string op; 1438 Expression e1; 1439 Expression e2; 1440 1441 override string toString() { 1442 return e1.toString() ~ " " ~ op ~ " " ~ e2.toString(); 1443 } 1444 1445 override string toInterpretedString(PrototypeObject sc) { 1446 return e1.toInterpretedString(sc) ~ " " ~ op ~ " " ~ e2.toInterpretedString(sc); 1447 } 1448 1449 this(string op, Expression e1, Expression e2) { 1450 this.op = op; 1451 this.e1 = e1; 1452 this.e2 = e2; 1453 } 1454 1455 override InterpretResult interpret(PrototypeObject sc) { 1456 var left = e1.interpret(sc).value; 1457 var right = e2.interpret(sc).value; 1458 1459 //writeln(left, " "~op~" ", right); 1460 1461 var n; 1462 sw: switch(op) { 1463 // I would actually kinda prefer this to be static foreach, but normal 1464 // tuple foreach here has broaded compiler compatibility. 1465 foreach(ctOp; CtList!("+", "-", "*", "/", "==", "!=", "<=", ">=", ">", "<", "~", "&&", "||", "&", "|", "^", "%")) //, ">>", "<<", ">>>")) // FIXME 1466 case ctOp: { 1467 n = mixin("left "~ctOp~" right"); 1468 break sw; 1469 } 1470 default: 1471 assert(0, op); 1472 } 1473 1474 return InterpretResult(n, sc); 1475 } 1476 } 1477 1478 class OpAssignExpression : Expression { 1479 string op; 1480 Expression e1; 1481 Expression e2; 1482 1483 this(string op, Expression e1, Expression e2) { 1484 this.op = op; 1485 this.e1 = e1; 1486 this.e2 = e2; 1487 } 1488 1489 override string toString() { 1490 return e1.toString() ~ " " ~ op ~ "= " ~ e2.toString(); 1491 } 1492 1493 override InterpretResult interpret(PrototypeObject sc) { 1494 1495 auto v = cast(VariableExpression) e1; 1496 if(v is null) 1497 throw new ScriptRuntimeException("not an lvalue", null, 0 /* FIXME */); 1498 1499 var right = e2.interpret(sc).value; 1500 1501 //writeln(left, " "~op~"= ", right); 1502 1503 var n; 1504 foreach(ctOp; CtList!("+=", "-=", "*=", "/=", "~=", "&=", "|=", "^=", "%=")) 1505 if(ctOp[0..1] == op) 1506 n = mixin("v.getVar(sc) "~ctOp~" right"); 1507 1508 // FIXME: ensure the variable is updated in scope too 1509 1510 return InterpretResult(n, sc); 1511 1512 } 1513 } 1514 1515 class PipelineExpression : Expression { 1516 Expression e1; 1517 Expression e2; 1518 CallExpression ce; 1519 ScriptLocation loc; 1520 1521 this(ScriptLocation loc, Expression e1, Expression e2) { 1522 this.loc = loc; 1523 this.e1 = e1; 1524 this.e2 = e2; 1525 1526 if(auto ce = cast(CallExpression) e2) { 1527 this.ce = new CallExpression(loc, ce.func); 1528 this.ce.arguments = [e1] ~ ce.arguments; 1529 } else { 1530 this.ce = new CallExpression(loc, e2); 1531 this.ce.arguments ~= e1; 1532 } 1533 } 1534 1535 override string toString() { return e1.toString() ~ " |> " ~ e2.toString(); } 1536 1537 override InterpretResult interpret(PrototypeObject sc) { 1538 return ce.interpret(sc); 1539 } 1540 } 1541 1542 class AssignExpression : Expression { 1543 Expression e1; 1544 Expression e2; 1545 bool suppressOverloading; 1546 1547 this(Expression e1, Expression e2, bool suppressOverloading = false) { 1548 this.e1 = e1; 1549 this.e2 = e2; 1550 this.suppressOverloading = suppressOverloading; 1551 } 1552 1553 override string toString() { return e1.toString() ~ " = " ~ e2.toString(); } 1554 1555 override InterpretResult interpret(PrototypeObject sc) { 1556 auto v = cast(VariableExpression) e1; 1557 if(v is null) 1558 throw new ScriptRuntimeException("not an lvalue", null, 0 /* FIXME */); 1559 1560 auto ret = v.setVar(sc, e2 is null ? var(null) : e2.interpret(sc).value, false, suppressOverloading); 1561 1562 return InterpretResult(ret, sc); 1563 } 1564 } 1565 class VariableExpression : Expression { 1566 string identifier; 1567 ScriptLocation loc; 1568 1569 this(string identifier, ScriptLocation loc = ScriptLocation.init) { 1570 this.identifier = identifier; 1571 this.loc = loc; 1572 } 1573 1574 override string toString() { 1575 return identifier; 1576 } 1577 1578 override string toInterpretedString(PrototypeObject sc) { 1579 return getVar(sc).get!string; 1580 } 1581 1582 ref var getVar(PrototypeObject sc, bool recurse = true) { 1583 try { 1584 return sc._getMember(identifier, true /* FIXME: recurse?? */, true); 1585 } catch(DynamicTypeException dte) { 1586 dte.callStack ~= loc; 1587 throw dte; 1588 } 1589 } 1590 1591 ref var setVar(PrototypeObject sc, var t, bool recurse = true, bool suppressOverloading = false) { 1592 return sc._setMember(identifier, t, true /* FIXME: recurse?? */, true, suppressOverloading); 1593 } 1594 1595 ref var getVarFrom(PrototypeObject sc, ref var v) { 1596 return v[identifier]; 1597 } 1598 1599 override InterpretResult interpret(PrototypeObject sc) { 1600 return InterpretResult(getVar(sc), sc); 1601 } 1602 } 1603 1604 class SuperExpression : Expression { 1605 VariableExpression dot; 1606 string origDot; 1607 this(VariableExpression dot) { 1608 if(dot !is null) { 1609 origDot = dot.identifier; 1610 //dot.identifier = "__super_" ~ dot.identifier; // omg this is so bad 1611 } 1612 this.dot = dot; 1613 } 1614 1615 override string toString() { 1616 if(dot is null) 1617 return "super"; 1618 else 1619 return "super." ~ origDot; 1620 } 1621 1622 override InterpretResult interpret(PrototypeObject sc) { 1623 var a = sc._getMember("super", true, true); 1624 if(a._object is null) 1625 throw new Exception("null proto for super"); 1626 PrototypeObject proto = a._object.prototype; 1627 if(proto is null) 1628 throw new Exception("no super"); 1629 //proto = proto.prototype; 1630 1631 if(dot !is null) 1632 a = proto._getMember(dot.identifier, true, true); 1633 else 1634 a = proto._getMember("__ctor", true, true); 1635 return InterpretResult(a, sc); 1636 } 1637 } 1638 1639 class DotVarExpression : VariableExpression { 1640 Expression e1; 1641 VariableExpression e2; 1642 bool recurse = true; 1643 1644 this(Expression e1) { 1645 this.e1 = e1; 1646 super(null); 1647 } 1648 1649 this(Expression e1, VariableExpression e2, bool recurse = true) { 1650 this.e1 = e1; 1651 this.e2 = e2; 1652 this.recurse = recurse; 1653 //assert(typeid(e2) == typeid(VariableExpression)); 1654 super("<do not use>");//e1.identifier ~ "." ~ e2.identifier); 1655 } 1656 1657 override string toString() { 1658 return e1.toString() ~ "." ~ e2.toString(); 1659 } 1660 1661 override ref var getVar(PrototypeObject sc, bool recurse = true) { 1662 if(!this.recurse) { 1663 // this is a special hack... 1664 if(auto ve = cast(VariableExpression) e1) { 1665 return ve.getVar(sc)._getOwnProperty(e2.identifier); 1666 } 1667 assert(0); 1668 } 1669 1670 if(e2.identifier == "__source") { 1671 auto val = e1.interpret(sc).value; 1672 if(auto meta = cast(ScriptFunctionMetadata) val._metadata) 1673 return *(new var(meta.convertToString())); 1674 else 1675 return *(new var(val.toJson())); 1676 } 1677 1678 if(auto ve = cast(VariableExpression) e1) { 1679 return this.getVarFrom(sc, ve.getVar(sc, recurse)); 1680 } else if(cast(StringLiteralExpression) e1 && e2.identifier == "interpolate") { 1681 auto se = cast(StringLiteralExpression) e1; 1682 var* functor = new var; 1683 //if(!se.allowInterpolation) 1684 //throw new ScriptRuntimeException("Cannot interpolate this string", se.token.lineNumber); 1685 (*functor)._function = (var _this, var[] args) { 1686 return se.interpolate(args.length ? args[0] : var(null), sc); 1687 }; 1688 return *functor; 1689 } else { 1690 // make a temporary for the lhs 1691 auto v = new var(); 1692 *v = e1.interpret(sc).value; 1693 return this.getVarFrom(sc, *v); 1694 } 1695 } 1696 1697 override ref var setVar(PrototypeObject sc, var t, bool recurse = true, bool suppressOverloading = false) { 1698 if(suppressOverloading) 1699 return e1.interpret(sc).value.opIndexAssignNoOverload(t, e2.identifier); 1700 else 1701 return e1.interpret(sc).value.opIndexAssign(t, e2.identifier); 1702 } 1703 1704 1705 override ref var getVarFrom(PrototypeObject sc, ref var v) { 1706 return e2.getVarFrom(sc, v); 1707 } 1708 1709 override string toInterpretedString(PrototypeObject sc) { 1710 return getVar(sc).get!string; 1711 } 1712 } 1713 1714 class IndexExpression : VariableExpression { 1715 Expression e1; 1716 Expression e2; 1717 1718 this(Expression e1, Expression e2) { 1719 this.e1 = e1; 1720 this.e2 = e2; 1721 super(null); 1722 } 1723 1724 override string toString() { 1725 return e1.toString() ~ "[" ~ e2.toString() ~ "]"; 1726 } 1727 1728 override ref var getVar(PrototypeObject sc, bool recurse = true) { 1729 if(auto ve = cast(VariableExpression) e1) 1730 return ve.getVar(sc, recurse)[e2.interpret(sc).value]; 1731 else { 1732 auto v = new var(); 1733 *v = e1.interpret(sc).value; 1734 return this.getVarFrom(sc, *v); 1735 } 1736 } 1737 1738 override ref var setVar(PrototypeObject sc, var t, bool recurse = true, bool suppressOverloading = false) { 1739 return getVar(sc,recurse) = t; 1740 } 1741 } 1742 1743 class SliceExpression : Expression { 1744 // e1[e2 .. e3] 1745 Expression e1; 1746 Expression e2; 1747 Expression e3; 1748 1749 this(Expression e1, Expression e2, Expression e3) { 1750 this.e1 = e1; 1751 this.e2 = e2; 1752 this.e3 = e3; 1753 } 1754 1755 override string toString() { 1756 return e1.toString() ~ "[" ~ e2.toString() ~ " .. " ~ e3.toString() ~ "]"; 1757 } 1758 1759 override InterpretResult interpret(PrototypeObject sc) { 1760 var lhs = e1.interpret(sc).value; 1761 1762 auto specialScope = new PrototypeObject(); 1763 specialScope.prototype = sc; 1764 specialScope._getMember("$", false, false) = lhs.length; 1765 1766 return InterpretResult(lhs[e2.interpret(specialScope).value .. e3.interpret(specialScope).value], sc); 1767 } 1768 } 1769 1770 1771 class LoopControlExpression : Expression { 1772 InterpretResult.FlowControl op; 1773 this(string op) { 1774 if(op == "continue") 1775 this.op = InterpretResult.FlowControl.Continue; 1776 else if(op == "break") 1777 this.op = InterpretResult.FlowControl.Break; 1778 else assert(0, op); 1779 } 1780 1781 override string toString() { 1782 import std.string; 1783 return to!string(this.op).toLower(); 1784 } 1785 1786 override InterpretResult interpret(PrototypeObject sc) { 1787 return InterpretResult(var(null), sc, op); 1788 } 1789 } 1790 1791 1792 class ReturnExpression : Expression { 1793 Expression value; 1794 1795 this(Expression v) { 1796 value = v; 1797 } 1798 1799 override string toString() { return "return " ~ value.toString(); } 1800 1801 override InterpretResult interpret(PrototypeObject sc) { 1802 return InterpretResult(value.interpret(sc).value, sc, InterpretResult.FlowControl.Return); 1803 } 1804 } 1805 1806 class ScopeExpression : Expression { 1807 this(Expression[] expressions) { 1808 this.expressions = expressions; 1809 } 1810 1811 Expression[] expressions; 1812 1813 override string toString() { 1814 string s; 1815 s = "{\n"; 1816 foreach(expr; expressions) { 1817 s ~= "\t"; 1818 s ~= expr.toString(); 1819 s ~= ";\n"; 1820 } 1821 s ~= "}"; 1822 return s; 1823 } 1824 1825 override InterpretResult interpret(PrototypeObject sc) { 1826 var ret; 1827 1828 auto innerScope = new PrototypeObject(); 1829 innerScope.prototype = sc; 1830 1831 innerScope._getMember("__scope_exit", false, false) = var.emptyArray; 1832 innerScope._getMember("__scope_success", false, false) = var.emptyArray; 1833 innerScope._getMember("__scope_failure", false, false) = var.emptyArray; 1834 1835 scope(exit) { 1836 foreach(func; innerScope._getMember("__scope_exit", false, true)) 1837 func(); 1838 } 1839 scope(success) { 1840 foreach(func; innerScope._getMember("__scope_success", false, true)) 1841 func(); 1842 } 1843 scope(failure) { 1844 foreach(func; innerScope._getMember("__scope_failure", false, true)) 1845 func(); 1846 } 1847 1848 foreach(expression; expressions) { 1849 auto res = expression.interpret(innerScope); 1850 ret = res.value; 1851 if(res.flowControl != InterpretResult.FlowControl.Normal) 1852 return InterpretResult(ret, sc, res.flowControl); 1853 } 1854 return InterpretResult(ret, sc); 1855 } 1856 } 1857 1858 class SwitchExpression : Expression { 1859 Expression expr; 1860 CaseExpression[] cases; 1861 CaseExpression default_; 1862 1863 override InterpretResult interpret(PrototypeObject sc) { 1864 auto e = expr.interpret(sc); 1865 1866 bool hitAny; 1867 bool fallingThrough; 1868 bool secondRun; 1869 1870 var last; 1871 1872 again: 1873 foreach(c; cases) { 1874 if(!secondRun && !fallingThrough && c is default_) continue; 1875 if(fallingThrough || (secondRun && c is default_) || c.condition.interpret(sc) == e) { 1876 fallingThrough = false; 1877 if(!secondRun) 1878 hitAny = true; 1879 InterpretResult ret; 1880 expr_loop: foreach(exp; c.expressions) { 1881 ret = exp.interpret(sc); 1882 with(InterpretResult.FlowControl) 1883 final switch(ret.flowControl) { 1884 case Normal: 1885 last = ret.value; 1886 break; 1887 case Return: 1888 case Goto: 1889 return ret; 1890 case Continue: 1891 fallingThrough = true; 1892 break expr_loop; 1893 case Break: 1894 return InterpretResult(last, sc); 1895 } 1896 } 1897 1898 if(!fallingThrough) 1899 break; 1900 } 1901 } 1902 1903 if(!hitAny && !secondRun) { 1904 secondRun = true; 1905 goto again; 1906 } 1907 1908 return InterpretResult(last, sc); 1909 } 1910 } 1911 1912 class CaseExpression : Expression { 1913 this(Expression condition) { 1914 this.condition = condition; 1915 } 1916 Expression condition; 1917 Expression[] expressions; 1918 1919 override string toString() { 1920 string code; 1921 if(condition is null) 1922 code = "default:"; 1923 else 1924 code = "case " ~ condition.toString() ~ ":"; 1925 1926 foreach(expr; expressions) 1927 code ~= "\n" ~ expr.toString() ~ ";"; 1928 1929 return code; 1930 } 1931 1932 override InterpretResult interpret(PrototypeObject sc) { 1933 // I did this inline up in the SwitchExpression above. maybe insane?! 1934 assert(0); 1935 } 1936 } 1937 1938 unittest { 1939 interpret(q{ 1940 var a = 10; 1941 // case and break should work 1942 var brk; 1943 1944 // var brk = switch doesn't parse, but this will..... 1945 // (I kinda went everything is an expression but not all the way. this code SUX.) 1946 brk = switch(a) { 1947 case 10: 1948 a = 30; 1949 break; 1950 case 30: 1951 a = 40; 1952 break; 1953 default: 1954 a = 0; 1955 } 1956 1957 assert(a == 30); 1958 assert(brk == 30); // value of switch set to last expression evaled inside 1959 1960 // so should default 1961 switch(a) { 1962 case 20: 1963 a = 40; 1964 break; 1965 default: 1966 a = 40; 1967 } 1968 1969 assert(a == 40); 1970 1971 switch(a) { 1972 case 40: 1973 a = 50; 1974 case 60: // no implicit fallthrough in this lang... 1975 a = 60; 1976 } 1977 1978 assert(a == 50); 1979 1980 var ret; 1981 1982 ret = switch(a) { 1983 case 50: 1984 a = 60; 1985 continue; // request fallthrough. D uses "goto case", but I haven't implemented any goto yet so continue is best fit 1986 case 90: 1987 a = 70; 1988 } 1989 1990 assert(a == 70); // the explicit `continue` requests fallthrough behavior 1991 assert(ret == 70); 1992 }); 1993 } 1994 1995 unittest { 1996 // overloads 1997 interpret(q{ 1998 function foo(int a) { return 10 + a; } 1999 function foo(float a) { return 100 + a; } 2000 function foo(string a) { return "string " ~ a; } 2001 2002 assert(foo(4) == 14); 2003 assert(foo(4.5) == 104.5); 2004 assert(foo("test") == "string test"); 2005 2006 // can redefine specific override 2007 function foo(int a) { return a; } 2008 assert(foo(4) == 4); 2009 // leaving others in place 2010 assert(foo(4.5) == 104.5); 2011 assert(foo("test") == "string test"); 2012 }); 2013 } 2014 2015 unittest { 2016 // catching objects 2017 interpret(q{ 2018 class Foo {} 2019 class Bar : Foo {} 2020 2021 var res = try throw new Bar(); catch(Bar b) { 2 } catch(e) { 1 }; 2022 assert(res == 2); 2023 2024 var res = try throw new Foo(); catch(Bar b) { 2 } catch(e) { 1 }; 2025 assert(res == 1); 2026 2027 var res = try throw Foo; catch(Foo b) { 2 } catch(e) { 1 }; 2028 assert(res == 2); 2029 }); 2030 } 2031 2032 unittest { 2033 // ternary precedence 2034 interpret(q{ 2035 assert(0 == 0 ? true : false == true); 2036 assert((0 == 0) ? true : false == true); 2037 // lol FIXME 2038 //assert(((0 == 0) ? true : false) == true); 2039 }); 2040 } 2041 2042 unittest { 2043 // new nested class 2044 interpret(q{ 2045 class A {} 2046 A.b = class B { var c; this(a) { this.c = a; } } 2047 var c = new A.b(5); 2048 assert(A.b.c == null); 2049 assert(c.c == 5); 2050 }); 2051 } 2052 2053 class ForeachExpression : Expression { 2054 VariableDeclaration decl; 2055 Expression subject; 2056 Expression subject2; 2057 Expression loopBody; 2058 2059 override string toString() { 2060 return "foreach(" ~ decl.toString() ~ "; " ~ subject.toString() ~ ((subject2 is null) ? "" : (".." ~ subject2.toString)) ~ ") " ~ loopBody.toString(); 2061 } 2062 2063 override InterpretResult interpret(PrototypeObject sc) { 2064 var result; 2065 2066 assert(loopBody !is null); 2067 2068 auto loopScope = new PrototypeObject(); 2069 loopScope.prototype = sc; 2070 2071 InterpretResult.FlowControl flowControl; 2072 2073 static string doLoopBody() { return q{ 2074 if(decl.identifiers.length > 1) { 2075 sc._getMember(decl.identifiers[0], false, false) = i; 2076 sc._getMember(decl.identifiers[1], false, false) = item; 2077 } else { 2078 sc._getMember(decl.identifiers[0], false, false) = item; 2079 } 2080 2081 auto res = loopBody.interpret(loopScope); 2082 result = res.value; 2083 flowControl = res.flowControl; 2084 if(flowControl == InterpretResult.FlowControl.Break) 2085 break; 2086 if(flowControl == InterpretResult.FlowControl.Return) 2087 break; 2088 //if(flowControl == InterpretResult.FlowControl.Continue) 2089 // this is fine, we still want to do the advancement 2090 };} 2091 2092 var what = subject.interpret(sc).value; 2093 var termination = subject2 is null ? var(null) : subject2.interpret(sc).value; 2094 if(what.payloadType == var.Type.Integral && subject2 is null) { 2095 // loop from 0 to what 2096 int end = what.get!int; 2097 foreach(item; 0 .. end) { 2098 auto i = item; 2099 mixin(doLoopBody()); 2100 } 2101 } else if(what.payloadType == var.Type.Integral && termination.payloadType == var.Type.Integral) { 2102 // loop what .. termination 2103 int start = what.get!int; 2104 int end = termination.get!int; 2105 int stride; 2106 if(end < start) { 2107 stride = -1; 2108 } else { 2109 stride = 1; 2110 } 2111 int i = -1; 2112 for(int item = start; item != end; item += stride) { 2113 i++; 2114 mixin(doLoopBody()); 2115 } 2116 } else { 2117 if(subject2 !is null) 2118 throw new ScriptRuntimeException("foreach( a .. b ) invalid unless a is an integer", null, 0); // FIXME 2119 foreach(i, item; what) { 2120 mixin(doLoopBody()); 2121 } 2122 } 2123 2124 if(flowControl != InterpretResult.FlowControl.Return) 2125 flowControl = InterpretResult.FlowControl.Normal; 2126 2127 return InterpretResult(result, sc, flowControl); 2128 } 2129 } 2130 2131 class ForExpression : Expression { 2132 Expression initialization; 2133 Expression condition; 2134 Expression advancement; 2135 Expression loopBody; 2136 2137 this() {} 2138 2139 override InterpretResult interpret(PrototypeObject sc) { 2140 var result; 2141 2142 assert(loopBody !is null); 2143 2144 auto loopScope = new PrototypeObject(); 2145 loopScope.prototype = sc; 2146 if(initialization !is null) 2147 initialization.interpret(loopScope); 2148 2149 InterpretResult.FlowControl flowControl; 2150 2151 static string doLoopBody() { return q{ 2152 auto res = loopBody.interpret(loopScope); 2153 result = res.value; 2154 flowControl = res.flowControl; 2155 if(flowControl == InterpretResult.FlowControl.Break) 2156 break; 2157 if(flowControl == InterpretResult.FlowControl.Return) 2158 break; 2159 //if(flowControl == InterpretResult.FlowControl.Continue) 2160 // this is fine, we still want to do the advancement 2161 if(advancement) 2162 advancement.interpret(loopScope); 2163 };} 2164 2165 if(condition !is null) { 2166 while(condition.interpret(loopScope).value) { 2167 mixin(doLoopBody()); 2168 } 2169 } else 2170 while(true) { 2171 mixin(doLoopBody()); 2172 } 2173 2174 if(flowControl != InterpretResult.FlowControl.Return) 2175 flowControl = InterpretResult.FlowControl.Normal; 2176 2177 return InterpretResult(result, sc, flowControl); 2178 } 2179 2180 override string toString() { 2181 string code = "for("; 2182 if(initialization !is null) 2183 code ~= initialization.toString(); 2184 code ~= "; "; 2185 if(condition !is null) 2186 code ~= condition.toString(); 2187 code ~= "; "; 2188 if(advancement !is null) 2189 code ~= advancement.toString(); 2190 code ~= ") "; 2191 code ~= loopBody.toString(); 2192 2193 return code; 2194 } 2195 } 2196 2197 class IfExpression : Expression { 2198 Expression condition; 2199 Expression ifTrue; 2200 Expression ifFalse; 2201 2202 this() {} 2203 2204 override InterpretResult interpret(PrototypeObject sc) { 2205 InterpretResult result; 2206 assert(condition !is null); 2207 2208 auto ifScope = new PrototypeObject(); 2209 ifScope.prototype = sc; 2210 2211 if(condition.interpret(ifScope).value) { 2212 if(ifTrue !is null) 2213 result = ifTrue.interpret(ifScope); 2214 } else { 2215 if(ifFalse !is null) 2216 result = ifFalse.interpret(ifScope); 2217 } 2218 return InterpretResult(result.value, sc, result.flowControl); 2219 } 2220 2221 override string toString() { 2222 string code = "if "; 2223 code ~= condition.toString(); 2224 code ~= " "; 2225 if(ifTrue !is null) 2226 code ~= ifTrue.toString(); 2227 else 2228 code ~= " { }"; 2229 if(ifFalse !is null) 2230 code ~= " else " ~ ifFalse.toString(); 2231 return code; 2232 } 2233 } 2234 2235 class TernaryExpression : Expression { 2236 Expression condition; 2237 Expression ifTrue; 2238 Expression ifFalse; 2239 2240 this() {} 2241 2242 override InterpretResult interpret(PrototypeObject sc) { 2243 InterpretResult result; 2244 assert(condition !is null); 2245 2246 auto ifScope = new PrototypeObject(); 2247 ifScope.prototype = sc; 2248 2249 if(condition.interpret(ifScope).value) { 2250 result = ifTrue.interpret(ifScope); 2251 } else { 2252 result = ifFalse.interpret(ifScope); 2253 } 2254 return InterpretResult(result.value, sc, result.flowControl); 2255 } 2256 2257 override string toString() { 2258 string code = ""; 2259 code ~= condition.toString(); 2260 code ~= " ? "; 2261 code ~= ifTrue.toString(); 2262 code ~= " : "; 2263 code ~= ifFalse.toString(); 2264 return code; 2265 } 2266 } 2267 2268 // this is kinda like a placement new, and currently isn't exposed inside the language, 2269 // but is used for class inheritance 2270 class ShallowCopyExpression : Expression { 2271 Expression e1; 2272 Expression e2; 2273 2274 this(Expression e1, Expression e2) { 2275 this.e1 = e1; 2276 this.e2 = e2; 2277 } 2278 2279 override InterpretResult interpret(PrototypeObject sc) { 2280 auto v = cast(VariableExpression) e1; 2281 if(v is null) 2282 throw new ScriptRuntimeException("not an lvalue", null, 0 /* FIXME */); 2283 2284 v.getVar(sc, false)._object.copyPropertiesFrom(e2.interpret(sc).value._object); 2285 2286 return InterpretResult(var(null), sc); 2287 } 2288 2289 } 2290 2291 class NewExpression : Expression { 2292 Expression what; 2293 Expression[] args; 2294 this(Expression w) { 2295 what = w; 2296 } 2297 2298 override InterpretResult interpret(PrototypeObject sc) { 2299 assert(what !is null); 2300 2301 var[] args; 2302 foreach(arg; this.args) 2303 args ~= arg.interpret(sc).value; 2304 2305 var original = what.interpret(sc).value; 2306 var n = original._copy_new; 2307 if(n.payloadType() == var.Type.Object) { 2308 var ctor = original.prototype ? original.prototype._getOwnProperty("__ctor") : var(null); 2309 if(ctor) 2310 ctor.apply(n, args); 2311 } 2312 2313 return InterpretResult(n, sc); 2314 } 2315 } 2316 2317 class ThrowExpression : Expression { 2318 Expression whatToThrow; 2319 ScriptToken where; 2320 2321 this(Expression e, ScriptToken where) { 2322 whatToThrow = e; 2323 this.where = where; 2324 } 2325 2326 override InterpretResult interpret(PrototypeObject sc) { 2327 assert(whatToThrow !is null); 2328 throw new ScriptException(whatToThrow.interpret(sc).value, ScriptLocation(where.scriptFilename, where.lineNumber)); 2329 assert(0); 2330 } 2331 } 2332 2333 bool isCompatibleType(var v, string specifier, PrototypeObject sc) { 2334 var t = toType(specifier, sc); 2335 auto score = typeCompatibilityScore(v, t); 2336 return score > 0; 2337 } 2338 2339 var toType(string specifier, PrototypeObject sc) { 2340 switch(specifier) { 2341 case "int", "long": return var(0); 2342 case "float", "double": return var(0.0); 2343 case "string": return var(""); 2344 default: 2345 auto got = sc._peekMember(specifier, true); 2346 if(got) 2347 return *got; 2348 else 2349 return var.init; 2350 } 2351 } 2352 2353 var[] toTypes(string[] specifiers, PrototypeObject sc) { 2354 var[] arr; 2355 foreach(s; specifiers) 2356 arr ~= toType(s, sc); 2357 return arr; 2358 } 2359 2360 2361 class ExceptionBlockExpression : Expression { 2362 Expression tryExpression; 2363 2364 string[] catchVarDecls; 2365 string[] catchVarTypeSpecifiers; 2366 Expression[] catchExpressions; 2367 2368 Expression[] finallyExpressions; 2369 2370 override InterpretResult interpret(PrototypeObject sc) { 2371 InterpretResult result; 2372 result.sc = sc; 2373 assert(tryExpression !is null); 2374 assert(catchVarDecls.length == catchExpressions.length); 2375 2376 void caught(var ex) { 2377 if(catchExpressions.length) 2378 foreach(i, ce; catchExpressions) { 2379 if(catchVarTypeSpecifiers[i].length == 0 || isCompatibleType(ex, catchVarTypeSpecifiers[i], sc)) { 2380 auto catchScope = new PrototypeObject(); 2381 catchScope.prototype = sc; 2382 catchScope._getMember(catchVarDecls[i], false, false) = ex; 2383 2384 result = ce.interpret(catchScope); 2385 break; 2386 } 2387 } else 2388 result = InterpretResult(ex, sc); 2389 } 2390 2391 if(catchExpressions.length || (catchExpressions.length == 0 && finallyExpressions.length == 0)) 2392 try { 2393 result = tryExpression.interpret(sc); 2394 } catch(NonScriptCatchableException e) { 2395 // the script cannot catch these so it continues up regardless 2396 throw e; 2397 } catch(ScriptException e) { 2398 // FIXME: what about the other information here? idk. 2399 caught(e.payload); 2400 } catch(Exception e) { 2401 var ex = var.emptyObject; 2402 ex.type = typeid(e).name; 2403 ex.msg = e.msg; 2404 ex.file = e.file; 2405 ex.line = e.line; 2406 2407 caught(ex); 2408 } finally { 2409 foreach(fe; finallyExpressions) 2410 result = fe.interpret(sc); 2411 } 2412 else 2413 try { 2414 result = tryExpression.interpret(sc); 2415 } finally { 2416 foreach(fe; finallyExpressions) 2417 result = fe.interpret(sc); 2418 } 2419 2420 return result; 2421 } 2422 } 2423 2424 class ParentheticalExpression : Expression { 2425 Expression inside; 2426 this(Expression inside) { 2427 this.inside = inside; 2428 } 2429 2430 override string toString() { 2431 return "(" ~ inside.toString() ~ ")"; 2432 } 2433 2434 override InterpretResult interpret(PrototypeObject sc) { 2435 return InterpretResult(inside.interpret(sc).value, sc); 2436 } 2437 } 2438 2439 class AssertKeyword : Expression { 2440 ScriptToken token; 2441 this(ScriptToken token) { 2442 this.token = token; 2443 } 2444 override string toString() { 2445 return "assert"; 2446 } 2447 2448 override InterpretResult interpret(PrototypeObject sc) { 2449 if(AssertKeywordObject is null) 2450 AssertKeywordObject = new PrototypeObject(); 2451 var dummy; 2452 dummy._object = AssertKeywordObject; 2453 return InterpretResult(dummy, sc); 2454 } 2455 } 2456 2457 PrototypeObject AssertKeywordObject; 2458 PrototypeObject DefaultArgumentDummyObject; 2459 2460 class CallExpression : Expression { 2461 Expression func; 2462 Expression[] arguments; 2463 ScriptLocation loc; 2464 2465 override string toString() { 2466 string s = func.toString() ~ "("; 2467 foreach(i, arg; arguments) { 2468 if(i) s ~= ", "; 2469 s ~= arg.toString(); 2470 } 2471 2472 s ~= ")"; 2473 return s; 2474 } 2475 2476 this(ScriptLocation loc, Expression func) { 2477 this.loc = loc; 2478 this.func = func; 2479 } 2480 2481 override string toInterpretedString(PrototypeObject sc) { 2482 return interpret(sc).value.get!string; 2483 } 2484 2485 override InterpretResult interpret(PrototypeObject sc) { 2486 if(auto asrt = cast(AssertKeyword) func) { 2487 auto assertExpression = arguments[0]; 2488 Expression assertString; 2489 if(arguments.length > 1) 2490 assertString = arguments[1]; 2491 2492 var v = assertExpression.interpret(sc).value; 2493 2494 if(!v) 2495 throw new ScriptException( 2496 var(this.toString() ~ " failed, got: " ~ assertExpression.toInterpretedString(sc)), 2497 ScriptLocation(asrt.token.scriptFilename, asrt.token.lineNumber)); 2498 2499 return InterpretResult(v, sc); 2500 } 2501 2502 auto f = func.interpret(sc).value; 2503 bool isMacro = (f.payloadType == var.Type.Object && ((cast(MacroPrototype) f._payload._object) !is null)); 2504 var[] args; 2505 foreach(argument; arguments) 2506 if(argument !is null) { 2507 if(isMacro) // macro, pass the argument as an expression object 2508 args ~= argument.toScriptExpressionObject(sc); 2509 else // regular function, interpret the arguments 2510 args ~= argument.interpret(sc).value; 2511 } else { 2512 if(DefaultArgumentDummyObject is null) 2513 DefaultArgumentDummyObject = new PrototypeObject(); 2514 2515 var dummy; 2516 dummy._object = DefaultArgumentDummyObject; 2517 2518 args ~= dummy; 2519 } 2520 2521 var _this; 2522 if(auto dve = cast(DotVarExpression) func) { 2523 _this = dve.e1.interpret(sc).value; 2524 } else if(auto ide = cast(IndexExpression) func) { 2525 _this = ide.interpret(sc).value; 2526 } else if(auto se = cast(SuperExpression) func) { 2527 // super things are passed this object despite looking things up on the prototype 2528 // so it calls the correct instance 2529 _this = sc._getMember("this", true, true); 2530 } 2531 2532 try { 2533 return InterpretResult(f.apply(_this, args), sc); 2534 } catch(DynamicTypeException dte) { 2535 dte.callStack ~= loc; 2536 throw dte; 2537 } catch(ScriptException se) { 2538 se.callStack ~= loc; 2539 throw se; 2540 } 2541 } 2542 } 2543 2544 ScriptToken requireNextToken(MyTokenStreamHere)(ref MyTokenStreamHere tokens, ScriptToken.Type type, string str = null, string file = __FILE__, size_t line = __LINE__) { 2545 if(tokens.empty) 2546 throw new ScriptCompileException("script ended prematurely", null, 0, file, line); 2547 auto next = tokens.front; 2548 if(next.type != type || (str !is null && next.str != str)) 2549 throw new ScriptCompileException("unexpected '"~next.str~"' while expecting " ~ to!string(type) ~ " " ~ str, next.scriptFilename, next.lineNumber, file, line); 2550 2551 tokens.popFront(); 2552 return next; 2553 } 2554 2555 bool peekNextToken(MyTokenStreamHere)(MyTokenStreamHere tokens, ScriptToken.Type type, string str = null, string file = __FILE__, size_t line = __LINE__) { 2556 if(tokens.empty) 2557 return false; 2558 auto next = tokens.front; 2559 if(next.type != type || (str !is null && next.str != str)) 2560 return false; 2561 return true; 2562 } 2563 2564 VariableExpression parseVariableName(MyTokenStreamHere)(ref MyTokenStreamHere tokens) { 2565 assert(!tokens.empty); 2566 auto token = tokens.front; 2567 if(token.type == ScriptToken.Type.identifier) { 2568 tokens.popFront(); 2569 return new VariableExpression(token.str, ScriptLocation(token.scriptFilename, token.lineNumber)); 2570 } 2571 throw new ScriptCompileException("Found "~token.str~" when expecting identifier", token.scriptFilename, token.lineNumber); 2572 } 2573 2574 Expression parseDottedVariableName(MyTokenStreamHere)(ref MyTokenStreamHere tokens) { 2575 assert(!tokens.empty); 2576 2577 auto ve = parseVariableName(tokens); 2578 2579 auto token = tokens.front; 2580 if(token.type == ScriptToken.Type.symbol && token.str == ".") { 2581 tokens.popFront(); 2582 return new DotVarExpression(ve, parseVariableName(tokens)); 2583 } 2584 return ve; 2585 } 2586 2587 2588 Expression parsePart(MyTokenStreamHere)(ref MyTokenStreamHere tokens) { 2589 if(!tokens.empty) { 2590 auto token = tokens.front; 2591 2592 Expression e; 2593 2594 if(token.str == "super") { 2595 tokens.popFront(); 2596 VariableExpression dot; 2597 if(!tokens.empty && tokens.front.str == ".") { 2598 tokens.popFront(); 2599 dot = parseVariableName(tokens); 2600 } 2601 e = new SuperExpression(dot); 2602 } 2603 else if(token.type == ScriptToken.Type.identifier) 2604 e = parseVariableName(tokens); 2605 else if(token.type == ScriptToken.Type.symbol && (token.str == "-" || token.str == "+" || token.str == "!" || token.str == "~")) { 2606 auto op = token.str; 2607 tokens.popFront(); 2608 2609 e = parsePart(tokens); 2610 if(op == "-") 2611 e = new NegationExpression(e); 2612 else if(op == "!") 2613 e = new NotExpression(e); 2614 else if(op == "~") 2615 e = new BitFlipExpression(e); 2616 } else { 2617 tokens.popFront(); 2618 2619 if(token.type == ScriptToken.Type.int_number) 2620 e = new IntLiteralExpression(token.str); 2621 else if(token.type == ScriptToken.Type.float_number) 2622 e = new FloatLiteralExpression(token.str); 2623 else if(token.type == ScriptToken.Type..string) 2624 e = new StringLiteralExpression(token); 2625 else if(token.type == ScriptToken.Type.symbol || token.type == ScriptToken.Type.keyword) { 2626 switch(token.str) { 2627 case "true": 2628 case "false": 2629 e = new BoolLiteralExpression(token.str); 2630 break; 2631 case "new": 2632 // FIXME: why is this needed here? maybe it should be here instead of parseExpression 2633 tokens.pushFront(token); 2634 return parseExpression(tokens); 2635 case "(": 2636 //tokens.popFront(); 2637 auto parenthetical = new ParentheticalExpression(parseExpression(tokens)); 2638 tokens.requireNextToken(ScriptToken.Type.symbol, ")"); 2639 2640 return parenthetical; 2641 case "[": 2642 // array literal 2643 auto arr = new ArrayLiteralExpression(); 2644 2645 bool first = true; 2646 moreElements: 2647 if(tokens.empty) 2648 throw new ScriptCompileException("unexpected end of file when reading array literal", token.scriptFilename, token.lineNumber); 2649 2650 auto peek = tokens.front; 2651 if(peek.type == ScriptToken.Type.symbol && peek.str == "]") { 2652 tokens.popFront(); 2653 return arr; 2654 } 2655 2656 if(!first) 2657 tokens.requireNextToken(ScriptToken.Type.symbol, ","); 2658 else 2659 first = false; 2660 2661 arr.elements ~= parseExpression(tokens); 2662 2663 goto moreElements; 2664 case "json!q{": 2665 case "#{": 2666 // json object literal 2667 auto obj = new ObjectLiteralExpression(); 2668 /* 2669 these go 2670 2671 string or ident which is the key 2672 then a colon 2673 then an expression which is the value 2674 2675 then optionally a comma 2676 2677 then either } which finishes it, or another key 2678 */ 2679 2680 if(tokens.empty) 2681 throw new ScriptCompileException("unexpected end of file when reading object literal", token.scriptFilename, token.lineNumber); 2682 2683 moreKeys: 2684 auto key = tokens.front; 2685 tokens.popFront(); 2686 if(key.type == ScriptToken.Type.symbol && key.str == "}") { 2687 // all done! 2688 e = obj; 2689 break; 2690 } 2691 if(key.type != ScriptToken.Type..string && key.type != ScriptToken.Type.identifier) { 2692 throw new ScriptCompileException("unexpected '"~key.str~"' when reading object literal", key.scriptFilename, key.lineNumber); 2693 2694 } 2695 2696 tokens.requireNextToken(ScriptToken.Type.symbol, ":"); 2697 2698 auto value = parseExpression(tokens); 2699 if(tokens.empty) 2700 throw new ScriptCompileException("unclosed object literal", key.scriptFilename, key.lineNumber); 2701 2702 if(tokens.peekNextToken(ScriptToken.Type.symbol, ",")) 2703 tokens.popFront(); 2704 2705 obj.elements[key.str] = value; 2706 2707 goto moreKeys; 2708 case "macro": 2709 case "function": 2710 tokens.requireNextToken(ScriptToken.Type.symbol, "("); 2711 2712 auto exp = new FunctionLiteralExpression(); 2713 if(!tokens.peekNextToken(ScriptToken.Type.symbol, ")")) 2714 exp.arguments = parseVariableDeclaration(tokens, ")"); 2715 2716 tokens.requireNextToken(ScriptToken.Type.symbol, ")"); 2717 2718 exp.functionBody = parseExpression(tokens); 2719 exp.isMacro = token.str == "macro"; 2720 2721 e = exp; 2722 break; 2723 case "null": 2724 e = new NullLiteralExpression(); 2725 break; 2726 case "mixin": 2727 case "eval": 2728 tokens.requireNextToken(ScriptToken.Type.symbol, "("); 2729 e = new MixinExpression(parseExpression(tokens)); 2730 tokens.requireNextToken(ScriptToken.Type.symbol, ")"); 2731 break; 2732 default: 2733 goto unknown; 2734 } 2735 } else { 2736 unknown: 2737 throw new ScriptCompileException("unexpected '"~token.str~"' when reading ident", token.scriptFilename, token.lineNumber); 2738 } 2739 } 2740 2741 funcLoop: while(!tokens.empty) { 2742 auto peek = tokens.front; 2743 if(peek.type == ScriptToken.Type.symbol) { 2744 switch(peek.str) { 2745 case "(": 2746 e = parseFunctionCall(tokens, e); 2747 break; 2748 case "[": 2749 tokens.popFront(); 2750 auto e1 = parseExpression(tokens); 2751 if(tokens.peekNextToken(ScriptToken.Type.symbol, "..")) { 2752 tokens.popFront(); 2753 e = new SliceExpression(e, e1, parseExpression(tokens)); 2754 } else { 2755 e = new IndexExpression(e, e1); 2756 } 2757 tokens.requireNextToken(ScriptToken.Type.symbol, "]"); 2758 break; 2759 case ".": 2760 tokens.popFront(); 2761 e = new DotVarExpression(e, parseVariableName(tokens)); 2762 break; 2763 default: 2764 return e; // we don't know, punt it elsewhere 2765 } 2766 } else return e; // again, we don't know, so just punt it down the line 2767 } 2768 return e; 2769 } 2770 2771 throw new ScriptCompileException("Ran out of tokens when trying to parsePart", null, 0); 2772 } 2773 2774 Expression parseArguments(MyTokenStreamHere)(ref MyTokenStreamHere tokens, Expression exp, ref Expression[] where) { 2775 // arguments. 2776 auto peek = tokens.front; 2777 if(peek.type == ScriptToken.Type.symbol && peek.str == ")") { 2778 tokens.popFront(); 2779 return exp; 2780 } 2781 2782 moreArguments: 2783 2784 if(tokens.peekNextToken(ScriptToken.Type.keyword, "default")) { 2785 tokens.popFront(); 2786 where ~= null; 2787 } else { 2788 where ~= parseExpression(tokens); 2789 } 2790 2791 if(tokens.empty) 2792 throw new ScriptCompileException("unexpected end of file when parsing call expression", peek.scriptFilename, peek.lineNumber); 2793 peek = tokens.front; 2794 if(peek.type == ScriptToken.Type.symbol && peek.str == ",") { 2795 tokens.popFront(); 2796 goto moreArguments; 2797 } else if(peek.type == ScriptToken.Type.symbol && peek.str == ")") { 2798 tokens.popFront(); 2799 return exp; 2800 } else 2801 throw new ScriptCompileException("unexpected '"~peek.str~"' when reading argument list", peek.scriptFilename, peek.lineNumber); 2802 2803 } 2804 2805 Expression parseFunctionCall(MyTokenStreamHere)(ref MyTokenStreamHere tokens, Expression e) { 2806 assert(!tokens.empty); 2807 auto peek = tokens.front; 2808 auto exp = new CallExpression(ScriptLocation(peek.scriptFilename, peek.lineNumber), e); 2809 tokens.popFront(); 2810 if(tokens.empty) 2811 throw new ScriptCompileException("unexpected end of file when parsing call expression", peek.scriptFilename, peek.lineNumber); 2812 return parseArguments(tokens, exp, exp.arguments); 2813 } 2814 2815 Expression parseFactor(MyTokenStreamHere)(ref MyTokenStreamHere tokens) { 2816 auto e1 = parsePart(tokens); 2817 loop: while(!tokens.empty) { 2818 auto peek = tokens.front; 2819 2820 if(peek.type == ScriptToken.Type.symbol) { 2821 switch(peek.str) { 2822 case "*": 2823 case "/": 2824 case "%": 2825 tokens.popFront(); 2826 e1 = new BinaryExpression(peek.str, e1, parsePart(tokens)); 2827 break; 2828 default: 2829 break loop; 2830 } 2831 } else throw new Exception("Got " ~ peek.str ~ " when expecting symbol"); 2832 } 2833 2834 return e1; 2835 } 2836 2837 Expression parseAddend(MyTokenStreamHere)(ref MyTokenStreamHere tokens) { 2838 auto e1 = parseFactor(tokens); 2839 loop: while(!tokens.empty) { 2840 auto peek = tokens.front; 2841 2842 if(peek.type == ScriptToken.Type.symbol) { 2843 switch(peek.str) { 2844 case "..": // possible FIXME 2845 case ")": // possible FIXME 2846 case "]": // possible FIXME 2847 case "}": // possible FIXME 2848 case ",": // possible FIXME these are passed on to the next thing 2849 case ";": 2850 case ":": // idk 2851 case "?": 2852 return e1; 2853 2854 case "|>": 2855 tokens.popFront(); 2856 e1 = new PipelineExpression(ScriptLocation(peek.scriptFilename, peek.lineNumber), e1, parseFactor(tokens)); 2857 break; 2858 case ".": 2859 tokens.popFront(); 2860 e1 = new DotVarExpression(e1, parseVariableName(tokens)); 2861 break; 2862 case "=": 2863 tokens.popFront(); 2864 return new AssignExpression(e1, parseExpression(tokens)); 2865 case "&&": // thanks to mzfhhhh for fix 2866 case "||": 2867 tokens.popFront(); 2868 e1 = new BinaryExpression(peek.str, e1, parseExpression(tokens)); 2869 break; 2870 case "~": 2871 // FIXME: make sure this has the right associativity 2872 2873 case "&": 2874 case "|": 2875 case "^": 2876 2877 case "&=": 2878 case "|=": 2879 case "^=": 2880 2881 case "+": 2882 case "-": 2883 2884 case "==": 2885 case "!=": 2886 case "<=": 2887 case ">=": 2888 case "<": 2889 case ">": 2890 tokens.popFront(); 2891 e1 = new BinaryExpression(peek.str, e1, parseAddend(tokens)); 2892 break; 2893 case "+=": 2894 case "-=": 2895 case "*=": 2896 case "/=": 2897 case "~=": 2898 case "%=": 2899 tokens.popFront(); 2900 return new OpAssignExpression(peek.str[0..1], e1, parseExpression(tokens)); 2901 default: 2902 throw new ScriptCompileException("Parse error, unexpected " ~ peek.str ~ " when looking for operator", peek.scriptFilename, peek.lineNumber); 2903 } 2904 //} else if(peek.type == ScriptToken.Type.identifier || peek.type == ScriptToken.Type.number) { 2905 //return parseFactor(tokens); 2906 } else 2907 throw new ScriptCompileException("Parse error, unexpected '" ~ peek.str ~ "'", peek.scriptFilename, peek.lineNumber); 2908 } 2909 2910 return e1; 2911 } 2912 2913 Expression parseExpression(MyTokenStreamHere)(ref MyTokenStreamHere tokens, bool consumeEnd = false) { 2914 Expression ret; 2915 ScriptToken first; 2916 string expectedEnd = ";"; 2917 //auto e1 = parseFactor(tokens); 2918 2919 while(tokens.peekNextToken(ScriptToken.Type.symbol, ";")) { 2920 tokens.popFront(); 2921 } 2922 if(!tokens.empty) { 2923 first = tokens.front; 2924 if(tokens.peekNextToken(ScriptToken.Type.symbol, "{")) { 2925 auto start = tokens.front; 2926 tokens.popFront(); 2927 auto e = parseCompoundStatement(tokens, start.lineNumber, "}").array; 2928 ret = new ScopeExpression(e); 2929 expectedEnd = null; // {} don't need ; at the end 2930 } else if(tokens.peekNextToken(ScriptToken.Type.keyword, "scope")) { 2931 auto start = tokens.front; 2932 tokens.popFront(); 2933 tokens.requireNextToken(ScriptToken.Type.symbol, "("); 2934 2935 auto ident = tokens.requireNextToken(ScriptToken.Type.identifier); 2936 switch(ident.str) { 2937 case "success": 2938 case "failure": 2939 case "exit": 2940 break; 2941 default: 2942 throw new ScriptCompileException("unexpected " ~ ident.str ~ ". valid scope(idents) are success, failure, and exit", ident.scriptFilename, ident.lineNumber); 2943 } 2944 2945 tokens.requireNextToken(ScriptToken.Type.symbol, ")"); 2946 2947 string i = "__scope_" ~ ident.str; 2948 auto literal = new FunctionLiteralExpression(); 2949 literal.functionBody = parseExpression(tokens); 2950 2951 auto e = new OpAssignExpression("~", new VariableExpression(i), literal); 2952 ret = e; 2953 } else if(tokens.peekNextToken(ScriptToken.Type.symbol, "(")) { 2954 auto start = tokens.front; 2955 tokens.popFront(); 2956 auto parenthetical = new ParentheticalExpression(parseExpression(tokens)); 2957 tokens.requireNextToken(ScriptToken.Type.symbol, ")"); 2958 if(tokens.peekNextToken(ScriptToken.Type.symbol, "(")) { 2959 // we have a function call, e.g. (test)() 2960 ret = parseFunctionCall(tokens, parenthetical); 2961 } else 2962 ret = parenthetical; 2963 } else if(tokens.peekNextToken(ScriptToken.Type.keyword, "new")) { 2964 auto start = tokens.front; 2965 tokens.popFront(); 2966 2967 auto expr = parseDottedVariableName(tokens); 2968 auto ne = new NewExpression(expr); 2969 if(tokens.peekNextToken(ScriptToken.Type.symbol, "(")) { 2970 tokens.popFront(); 2971 parseArguments(tokens, ne, ne.args); 2972 } 2973 2974 ret = ne; 2975 } else if(tokens.peekNextToken(ScriptToken.Type.keyword, "class")) { 2976 auto start = tokens.front; 2977 tokens.popFront(); 2978 2979 Expression[] expressions; 2980 2981 // the way classes work is they are actually object literals with a different syntax. new foo then just copies it 2982 /* 2983 we create a prototype object 2984 we create an object, with that prototype 2985 2986 set all functions and static stuff to the prototype 2987 the rest goes to the object 2988 2989 the expression returns the object we made 2990 */ 2991 2992 auto vars = new VariableDeclaration(); 2993 vars.identifiers = ["__proto", "__obj"]; 2994 2995 auto staticScopeBacking = new PrototypeObject(); 2996 auto instanceScopeBacking = new PrototypeObject(); 2997 2998 vars.initializers = [new ObjectLiteralExpression(staticScopeBacking), new ObjectLiteralExpression(instanceScopeBacking)]; 2999 expressions ~= vars; 3000 3001 // FIXME: operators need to have their this be bound somehow since it isn't passed 3002 // OR the op rewrite could pass this 3003 3004 expressions ~= new AssignExpression( 3005 new DotVarExpression(new VariableExpression("__obj"), new VariableExpression("prototype")), 3006 new VariableExpression("__proto")); 3007 3008 auto classIdent = tokens.requireNextToken(ScriptToken.Type.identifier); 3009 3010 expressions ~= new AssignExpression( 3011 new DotVarExpression(new VariableExpression("__proto"), new VariableExpression("__classname")), 3012 new StringLiteralExpression(classIdent.str)); 3013 3014 if(tokens.peekNextToken(ScriptToken.Type.symbol, ":")) { 3015 tokens.popFront(); 3016 auto inheritFrom = tokens.requireNextToken(ScriptToken.Type.identifier); 3017 3018 // we set our prototype to the Foo prototype, thereby inheriting any static data that way (includes functions) 3019 // the inheritFrom object itself carries instance data that we need to copy onto our instance 3020 expressions ~= new AssignExpression( 3021 new DotVarExpression(new VariableExpression("__proto"), new VariableExpression("prototype")), 3022 new DotVarExpression(new VariableExpression(inheritFrom.str), new VariableExpression("prototype"))); 3023 3024 expressions ~= new AssignExpression( 3025 new DotVarExpression(new VariableExpression("__proto"), new VariableExpression("super")), 3026 new VariableExpression(inheritFrom.str) 3027 ); 3028 3029 // and copying the instance initializer from the parent 3030 expressions ~= new ShallowCopyExpression(new VariableExpression("__obj"), new VariableExpression(inheritFrom.str)); 3031 } 3032 3033 tokens.requireNextToken(ScriptToken.Type.symbol, "{"); 3034 3035 void addVarDecl(VariableDeclaration decl, string o) { 3036 foreach(i, ident; decl.identifiers) { 3037 // FIXME: make sure this goes on the instance, never the prototype! 3038 expressions ~= new AssignExpression( 3039 new DotVarExpression( 3040 new VariableExpression(o), 3041 new VariableExpression(ident), 3042 false), 3043 decl.initializers[i], 3044 true // no overloading because otherwise an early opIndexAssign can mess up the decls 3045 ); 3046 } 3047 } 3048 3049 // FIXME: we could actually add private vars and just put them in this scope. maybe 3050 3051 while(!tokens.peekNextToken(ScriptToken.Type.symbol, "}")) { 3052 if(tokens.peekNextToken(ScriptToken.Type.symbol, ";")) { 3053 tokens.popFront(); 3054 continue; 3055 } 3056 3057 if(tokens.peekNextToken(ScriptToken.Type.identifier, "this")) { 3058 // ctor 3059 tokens.popFront(); 3060 tokens.requireNextToken(ScriptToken.Type.symbol, "("); 3061 auto args = parseVariableDeclaration(tokens, ")"); 3062 tokens.requireNextToken(ScriptToken.Type.symbol, ")"); 3063 auto bod = parseExpression(tokens); 3064 3065 expressions ~= new AssignExpression( 3066 new DotVarExpression( 3067 new VariableExpression("__proto"), 3068 new VariableExpression("__ctor")), 3069 new FunctionLiteralExpression(args, bod, staticScopeBacking)); 3070 } else if(tokens.peekNextToken(ScriptToken.Type.keyword, "var")) { 3071 // instance variable 3072 auto decl = parseVariableDeclaration(tokens, ";"); 3073 addVarDecl(decl, "__obj"); 3074 } else if(tokens.peekNextToken(ScriptToken.Type.keyword, "static")) { 3075 // prototype var 3076 tokens.popFront(); 3077 auto decl = parseVariableDeclaration(tokens, ";"); 3078 addVarDecl(decl, "__proto"); 3079 } else if(tokens.peekNextToken(ScriptToken.Type.keyword, "function")) { 3080 // prototype function 3081 tokens.popFront(); 3082 auto ident = tokens.requireNextToken(ScriptToken.Type.identifier); 3083 3084 tokens.requireNextToken(ScriptToken.Type.symbol, "("); 3085 VariableDeclaration args; 3086 if(!tokens.peekNextToken(ScriptToken.Type.symbol, ")")) 3087 args = parseVariableDeclaration(tokens, ")"); 3088 tokens.requireNextToken(ScriptToken.Type.symbol, ")"); 3089 auto bod = parseExpression(tokens); 3090 3091 expressions ~= new FunctionDeclaration( 3092 new DotVarExpression( 3093 new VariableExpression("__proto"), 3094 new VariableExpression(ident.str), 3095 false), 3096 ident.str, 3097 new FunctionLiteralExpression(args, bod, staticScopeBacking) 3098 ); 3099 } else throw new ScriptCompileException("Unexpected " ~ tokens.front.str ~ " when reading class decl", tokens.front.scriptFilename, tokens.front.lineNumber); 3100 } 3101 3102 tokens.requireNextToken(ScriptToken.Type.symbol, "}"); 3103 3104 // returning he object from the scope... 3105 expressions ~= new VariableExpression("__obj"); 3106 3107 auto scopeExpr = new ScopeExpression(expressions); 3108 auto classVarExpr = new VariableDeclaration(); 3109 classVarExpr.identifiers = [classIdent.str]; 3110 classVarExpr.initializers = [scopeExpr]; 3111 3112 ret = classVarExpr; 3113 } else if(tokens.peekNextToken(ScriptToken.Type.keyword, "if")) { 3114 tokens.popFront(); 3115 auto e = new IfExpression(); 3116 e.condition = parseExpression(tokens); 3117 e.ifTrue = parseExpression(tokens); 3118 if(tokens.peekNextToken(ScriptToken.Type.symbol, ";")) { 3119 tokens.popFront(); 3120 } 3121 if(tokens.peekNextToken(ScriptToken.Type.keyword, "else")) { 3122 tokens.popFront(); 3123 e.ifFalse = parseExpression(tokens); 3124 } 3125 ret = e; 3126 } else if(tokens.peekNextToken(ScriptToken.Type.keyword, "switch")) { 3127 tokens.popFront(); 3128 auto e = new SwitchExpression(); 3129 tokens.requireNextToken(ScriptToken.Type.symbol, "("); 3130 e.expr = parseExpression(tokens); 3131 tokens.requireNextToken(ScriptToken.Type.symbol, ")"); 3132 3133 tokens.requireNextToken(ScriptToken.Type.symbol, "{"); 3134 3135 while(!tokens.peekNextToken(ScriptToken.Type.symbol, "}")) { 3136 3137 if(tokens.peekNextToken(ScriptToken.Type.keyword, "case")) { 3138 auto start = tokens.front; 3139 tokens.popFront(); 3140 auto c = new CaseExpression(parseExpression(tokens)); 3141 e.cases ~= c; 3142 tokens.requireNextToken(ScriptToken.Type.symbol, ":"); 3143 3144 while(!tokens.peekNextToken(ScriptToken.Type.keyword, "default") && !tokens.peekNextToken(ScriptToken.Type.keyword, "case") && !tokens.peekNextToken(ScriptToken.Type.symbol, "}")) { 3145 c.expressions ~= parseStatement(tokens); 3146 while(tokens.peekNextToken(ScriptToken.Type.symbol, ";")) 3147 tokens.popFront(); 3148 } 3149 } else if(tokens.peekNextToken(ScriptToken.Type.keyword, "default")) { 3150 tokens.popFront(); 3151 tokens.requireNextToken(ScriptToken.Type.symbol, ":"); 3152 3153 auto c = new CaseExpression(null); 3154 3155 while(!tokens.peekNextToken(ScriptToken.Type.keyword, "case") && !tokens.peekNextToken(ScriptToken.Type.symbol, "}")) { 3156 c.expressions ~= parseStatement(tokens); 3157 while(tokens.peekNextToken(ScriptToken.Type.symbol, ";")) 3158 tokens.popFront(); 3159 } 3160 3161 e.cases ~= c; 3162 e.default_ = c; 3163 } else throw new ScriptCompileException("A switch statement must consists of cases and a default, nothing else ", tokens.front.scriptFilename, tokens.front.lineNumber); 3164 } 3165 3166 tokens.requireNextToken(ScriptToken.Type.symbol, "}"); 3167 expectedEnd = ""; 3168 3169 ret = e; 3170 3171 } else if(tokens.peekNextToken(ScriptToken.Type.keyword, "foreach")) { 3172 tokens.popFront(); 3173 auto e = new ForeachExpression(); 3174 tokens.requireNextToken(ScriptToken.Type.symbol, "("); 3175 e.decl = parseVariableDeclaration(tokens, ";"); 3176 tokens.requireNextToken(ScriptToken.Type.symbol, ";"); 3177 e.subject = parseExpression(tokens); 3178 3179 if(tokens.peekNextToken(ScriptToken.Type.symbol, "..")) { 3180 tokens.popFront; 3181 e.subject2 = parseExpression(tokens); 3182 } 3183 3184 tokens.requireNextToken(ScriptToken.Type.symbol, ")"); 3185 e.loopBody = parseExpression(tokens); 3186 ret = e; 3187 3188 expectedEnd = ""; 3189 } else if(tokens.peekNextToken(ScriptToken.Type.keyword, "cast")) { 3190 tokens.popFront(); 3191 auto e = new CastExpression(); 3192 3193 tokens.requireNextToken(ScriptToken.Type.symbol, "("); 3194 e.type = tokens.requireNextToken(ScriptToken.Type.identifier).str; 3195 if(tokens.peekNextToken(ScriptToken.Type.symbol, "[")) { 3196 e.type ~= "[]"; 3197 tokens.popFront(); 3198 tokens.requireNextToken(ScriptToken.Type.symbol, "]"); 3199 } 3200 tokens.requireNextToken(ScriptToken.Type.symbol, ")"); 3201 3202 e.e1 = parseExpression(tokens); 3203 ret = e; 3204 } else if(tokens.peekNextToken(ScriptToken.Type.keyword, "for")) { 3205 tokens.popFront(); 3206 auto e = new ForExpression(); 3207 tokens.requireNextToken(ScriptToken.Type.symbol, "("); 3208 e.initialization = parseStatement(tokens, ";"); 3209 3210 tokens.requireNextToken(ScriptToken.Type.symbol, ";"); 3211 3212 e.condition = parseExpression(tokens); 3213 tokens.requireNextToken(ScriptToken.Type.symbol, ";"); 3214 e.advancement = parseExpression(tokens); 3215 tokens.requireNextToken(ScriptToken.Type.symbol, ")"); 3216 e.loopBody = parseExpression(tokens); 3217 3218 ret = e; 3219 3220 expectedEnd = ""; 3221 } else if(tokens.peekNextToken(ScriptToken.Type.keyword, "while")) { 3222 tokens.popFront(); 3223 auto e = new ForExpression(); 3224 e.condition = parseExpression(tokens); 3225 e.loopBody = parseExpression(tokens); 3226 ret = e; 3227 expectedEnd = ""; 3228 } else if(tokens.peekNextToken(ScriptToken.Type.keyword, "break") || tokens.peekNextToken(ScriptToken.Type.keyword, "continue")) { 3229 auto token = tokens.front; 3230 tokens.popFront(); 3231 ret = new LoopControlExpression(token.str); 3232 } else if(tokens.peekNextToken(ScriptToken.Type.keyword, "return")) { 3233 tokens.popFront(); 3234 Expression retVal; 3235 if(tokens.peekNextToken(ScriptToken.Type.symbol, ";")) 3236 retVal = new NullLiteralExpression(); 3237 else 3238 retVal = parseExpression(tokens); 3239 ret = new ReturnExpression(retVal); 3240 } else if(tokens.peekNextToken(ScriptToken.Type.keyword, "throw")) { 3241 auto token = tokens.front; 3242 tokens.popFront(); 3243 ret = new ThrowExpression(parseExpression(tokens), token); 3244 } else if(tokens.peekNextToken(ScriptToken.Type.keyword, "try")) { 3245 auto tryToken = tokens.front; 3246 auto e = new ExceptionBlockExpression(); 3247 tokens.popFront(); 3248 e.tryExpression = parseExpression(tokens, true); 3249 3250 bool hadFinally = false; 3251 while(tokens.peekNextToken(ScriptToken.Type.keyword, "catch")) { 3252 if(hadFinally) 3253 throw new ScriptCompileException("Catch must come before finally", tokens.front.scriptFilename, tokens.front.lineNumber); 3254 tokens.popFront(); 3255 tokens.requireNextToken(ScriptToken.Type.symbol, "("); 3256 if(tokens.peekNextToken(ScriptToken.Type.keyword, "var")) 3257 tokens.popFront(); 3258 3259 auto ident = tokens.requireNextToken(ScriptToken.Type.identifier); 3260 if(tokens.empty) throw new ScriptCompileException("Catch specifier not closed", ident.scriptFilename, ident.lineNumber); 3261 auto next = tokens.front; 3262 if(next.type == ScriptToken.Type.identifier) { 3263 auto type = ident; 3264 ident = next; 3265 3266 e.catchVarTypeSpecifiers ~= type.str; 3267 e.catchVarDecls ~= ident.str; 3268 3269 tokens.popFront(); 3270 3271 tokens.requireNextToken(ScriptToken.Type.symbol, ")"); 3272 } else { 3273 e.catchVarTypeSpecifiers ~= null; 3274 e.catchVarDecls ~= ident.str; 3275 if(next.type != ScriptToken.Type.symbol || next.str != ")") 3276 throw new ScriptCompileException("ss Unexpected " ~ next.str ~ " when expecting ')'", next.scriptFilename, next.lineNumber); 3277 tokens.popFront(); 3278 } 3279 e.catchExpressions ~= parseExpression(tokens); 3280 } 3281 while(tokens.peekNextToken(ScriptToken.Type.keyword, "finally")) { 3282 hadFinally = true; 3283 tokens.popFront(); 3284 e.finallyExpressions ~= parseExpression(tokens); 3285 } 3286 3287 //if(!hadSomething) 3288 //throw new ScriptCompileException("Parse error, missing finally or catch after try", tryToken.lineNumber); 3289 3290 ret = e; 3291 } else { 3292 ret = parseAddend(tokens); 3293 } 3294 3295 if(!tokens.empty && tokens.peekNextToken(ScriptToken.Type.symbol, "?")) { 3296 auto e = new TernaryExpression(); 3297 e.condition = ret; 3298 tokens.requireNextToken(ScriptToken.Type.symbol, "?"); 3299 e.ifTrue = parseExpression(tokens); 3300 tokens.requireNextToken(ScriptToken.Type.symbol, ":"); 3301 e.ifFalse = parseExpression(tokens); 3302 ret = e; 3303 } 3304 } else { 3305 //assert(0); 3306 // return null; 3307 throw new ScriptCompileException("Parse error, unexpected end of input when reading expression", null, 0);//token.lineNumber); 3308 } 3309 3310 //writeln("parsed expression ", ret.toString()); 3311 3312 if(expectedEnd.length && tokens.empty && consumeEnd) // going loose on final ; at the end of input for repl convenience 3313 throw new ScriptCompileException("Parse error, unexpected end of input when reading expression, expecting " ~ expectedEnd, first.scriptFilename, first.lineNumber); 3314 3315 if(expectedEnd.length && consumeEnd) { 3316 if(tokens.peekNextToken(ScriptToken.Type.symbol, expectedEnd)) 3317 tokens.popFront(); 3318 // FIXME 3319 //if(tokens.front.type != ScriptToken.Type.symbol && tokens.front.str != expectedEnd) 3320 //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); 3321 // tokens = tokens[1 .. $]; 3322 } 3323 3324 return ret; 3325 } 3326 3327 VariableDeclaration parseVariableDeclaration(MyTokenStreamHere)(ref MyTokenStreamHere tokens, string termination) { 3328 VariableDeclaration decl = new VariableDeclaration(); 3329 bool equalOk; 3330 anotherVar: 3331 assert(!tokens.empty); 3332 3333 auto firstToken = tokens.front; 3334 3335 // var a, var b is acceptable 3336 if(tokens.peekNextToken(ScriptToken.Type.keyword, "var")) 3337 tokens.popFront(); 3338 3339 equalOk= true; 3340 if(tokens.empty) 3341 throw new ScriptCompileException("Parse error, dangling var at end of file", firstToken.scriptFilename, firstToken.lineNumber); 3342 3343 string type; 3344 3345 auto next = tokens.front; 3346 tokens.popFront; 3347 if(tokens.empty) 3348 throw new ScriptCompileException("Parse error, incomplete var declaration at end of file", firstToken.scriptFilename, firstToken.lineNumber); 3349 auto next2 = tokens.front; 3350 3351 ScriptToken typeSpecifier; 3352 3353 /* if there's two identifiers back to back, it is a type specifier. otherwise just a name */ 3354 3355 if(next.type == ScriptToken.Type.identifier && next2.type == ScriptToken.Type.identifier) { 3356 // type ident; 3357 typeSpecifier = next; 3358 next = next2; 3359 // get past the type 3360 tokens.popFront(); 3361 } else { 3362 // no type, just carry on with the next thing 3363 } 3364 3365 Expression initializer; 3366 auto identifier = next; 3367 if(identifier.type != ScriptToken.Type.identifier) 3368 throw new ScriptCompileException("Parse error, found '"~identifier.str~"' when expecting var identifier", identifier.scriptFilename, identifier.lineNumber); 3369 3370 //tokens.popFront(); 3371 3372 tryTermination: 3373 if(tokens.empty) 3374 throw new ScriptCompileException("Parse error, missing ; after var declaration at end of file", firstToken.scriptFilename, firstToken.lineNumber); 3375 3376 auto peek = tokens.front; 3377 if(peek.type == ScriptToken.Type.symbol) { 3378 if(peek.str == "=") { 3379 if(!equalOk) 3380 throw new ScriptCompileException("Parse error, unexpected '"~identifier.str~"' after reading var initializer", peek.scriptFilename, peek.lineNumber); 3381 equalOk = false; 3382 tokens.popFront(); 3383 initializer = parseExpression(tokens); 3384 goto tryTermination; 3385 } else if(peek.str == ",") { 3386 tokens.popFront(); 3387 decl.identifiers ~= identifier.str; 3388 decl.initializers ~= initializer; 3389 decl.typeSpecifiers ~= typeSpecifier.str; 3390 goto anotherVar; 3391 } else if(peek.str == termination) { 3392 decl.identifiers ~= identifier.str; 3393 decl.initializers ~= initializer; 3394 decl.typeSpecifiers ~= typeSpecifier.str; 3395 //tokens = tokens[1 .. $]; 3396 // we're done! 3397 } else 3398 throw new ScriptCompileException("Parse error, unexpected '"~peek.str~"' when reading var declaration symbol", peek.scriptFilename, peek.lineNumber); 3399 } else 3400 throw new ScriptCompileException("Parse error, unexpected non-symbol '"~peek.str~"' when reading var declaration", peek.scriptFilename, peek.lineNumber); 3401 3402 return decl; 3403 } 3404 3405 Expression parseStatement(MyTokenStreamHere)(ref MyTokenStreamHere tokens, string terminatingSymbol = null) { 3406 skip: // FIXME 3407 if(tokens.empty) 3408 return null; 3409 3410 if(terminatingSymbol !is null && (tokens.front.type == ScriptToken.Type.symbol && tokens.front.str == terminatingSymbol)) 3411 return null; // we're done 3412 3413 auto token = tokens.front; 3414 3415 // tokens = tokens[1 .. $]; 3416 final switch(token.type) { 3417 case ScriptToken.Type.keyword: 3418 case ScriptToken.Type.symbol: 3419 switch(token.str) { 3420 // assert 3421 case "assert": 3422 tokens.popFront(); 3423 3424 return parseFunctionCall(tokens, new AssertKeyword(token)); 3425 3426 //break; 3427 // declarations 3428 case "var": 3429 return parseVariableDeclaration(tokens, ";"); 3430 case ";": 3431 tokens.popFront(); // FIXME 3432 goto skip; 3433 // literals 3434 case "function": 3435 case "macro": 3436 // function can be a literal, or a declaration. 3437 3438 tokens.popFront(); // we're peeking ahead 3439 3440 if(tokens.peekNextToken(ScriptToken.Type.identifier)) { 3441 // decl style, rewrite it into var ident = function style 3442 // tokens.popFront(); // skipping the function keyword // already done above with the popFront 3443 auto ident = tokens.front; 3444 tokens.popFront(); 3445 3446 tokens.requireNextToken(ScriptToken.Type.symbol, "("); 3447 3448 auto exp = new FunctionLiteralExpression(); 3449 if(!tokens.peekNextToken(ScriptToken.Type.symbol, ")")) 3450 exp.arguments = parseVariableDeclaration(tokens, ")"); 3451 tokens.requireNextToken(ScriptToken.Type.symbol, ")"); 3452 3453 exp.functionBody = parseExpression(tokens); 3454 3455 // a ; should NOT be required here btw 3456 3457 exp.isMacro = token.str == "macro"; 3458 3459 auto e = new FunctionDeclaration(null, ident.str, exp); 3460 3461 return e; 3462 3463 } else { 3464 tokens.pushFront(token); // put it back since everyone expects us to have done that 3465 goto case; // handle it like any other expression 3466 } 3467 3468 case "true": 3469 case "false": 3470 3471 case "json!{": 3472 case "#{": 3473 case "[": 3474 case "(": 3475 case "null": 3476 3477 // scope 3478 case "{": 3479 case "scope": 3480 3481 case "cast": 3482 3483 // classes 3484 case "class": 3485 case "new": 3486 3487 case "super": 3488 3489 // flow control 3490 case "if": 3491 case "while": 3492 case "for": 3493 case "foreach": 3494 case "switch": 3495 3496 // exceptions 3497 case "try": 3498 case "throw": 3499 3500 // evals 3501 case "eval": 3502 case "mixin": 3503 3504 // flow 3505 case "continue": 3506 case "break": 3507 case "return": 3508 3509 return parseExpression(tokens); 3510 // unary prefix operators 3511 case "!": 3512 case "~": 3513 case "-": 3514 return parseExpression(tokens); 3515 3516 // BTW add custom object operator overloading to struct var 3517 // and custom property overloading to PrototypeObject 3518 3519 default: 3520 // whatever else keyword or operator related is actually illegal here 3521 throw new ScriptCompileException("Parse error, unexpected " ~ token.str, token.scriptFilename, token.lineNumber); 3522 } 3523 // break; 3524 case ScriptToken.Type.identifier: 3525 case ScriptToken.Type..string: 3526 case ScriptToken.Type.int_number: 3527 case ScriptToken.Type.float_number: 3528 return parseExpression(tokens); 3529 } 3530 3531 assert(0); 3532 } 3533 3534 struct CompoundStatementRange(MyTokenStreamHere) { 3535 // FIXME: if MyTokenStreamHere is not a class, this fails! 3536 MyTokenStreamHere tokens; 3537 int startingLine; 3538 string terminatingSymbol; 3539 bool isEmpty; 3540 3541 this(MyTokenStreamHere t, int startingLine, string terminatingSymbol) { 3542 tokens = t; 3543 this.startingLine = startingLine; 3544 this.terminatingSymbol = terminatingSymbol; 3545 popFront(); 3546 } 3547 3548 bool empty() { 3549 return isEmpty; 3550 } 3551 3552 Expression got; 3553 3554 Expression front() { 3555 return got; 3556 } 3557 3558 void popFront() { 3559 while(!tokens.empty && (terminatingSymbol is null || !(tokens.front.type == ScriptToken.Type.symbol && tokens.front.str == terminatingSymbol))) { 3560 auto n = parseStatement(tokens, terminatingSymbol); 3561 if(n is null) 3562 continue; 3563 got = n; 3564 return; 3565 } 3566 3567 if(tokens.empty && terminatingSymbol !is null) { 3568 throw new ScriptCompileException("Reached end of file while trying to reach matching " ~ terminatingSymbol, null, startingLine); 3569 } 3570 3571 if(terminatingSymbol !is null) { 3572 assert(tokens.front.str == terminatingSymbol); 3573 tokens.skipNext++; 3574 } 3575 3576 isEmpty = true; 3577 } 3578 } 3579 3580 CompoundStatementRange!MyTokenStreamHere 3581 //Expression[] 3582 parseCompoundStatement(MyTokenStreamHere)(ref MyTokenStreamHere tokens, int startingLine = 1, string terminatingSymbol = null) { 3583 return (CompoundStatementRange!MyTokenStreamHere(tokens, startingLine, terminatingSymbol)); 3584 } 3585 3586 auto parseScript(MyTokenStreamHere)(MyTokenStreamHere tokens) { 3587 /* 3588 the language's grammar is simple enough 3589 3590 maybe flow control should be statements though lol. they might not make sense inside. 3591 3592 Expressions: 3593 var identifier; 3594 var identifier = initializer; 3595 var identifier, identifier2 3596 3597 return expression; 3598 return ; 3599 3600 json!{ object literal } 3601 3602 { scope expression } 3603 3604 [ array literal ] 3605 other literal 3606 function (arg list) other expression 3607 3608 ( expression ) // parenthesized expression 3609 operator expression // unary expression 3610 3611 expression operator expression // binary expression 3612 expression (other expression... args) // function call 3613 3614 Binary Operator precedence : 3615 . [] 3616 * / 3617 + - 3618 ~ 3619 < > == != 3620 = 3621 */ 3622 3623 return parseCompoundStatement(tokens); 3624 } 3625 3626 var interpretExpressions(ExpressionStream)(ExpressionStream expressions, PrototypeObject variables) if(is(ElementType!ExpressionStream == Expression)) { 3627 assert(variables !is null); 3628 var ret; 3629 foreach(expression; expressions) { 3630 auto res = expression.interpret(variables); 3631 variables = res.sc; 3632 ret = res.value; 3633 } 3634 return ret; 3635 } 3636 3637 var interpretStream(MyTokenStreamHere)(MyTokenStreamHere tokens, PrototypeObject variables) if(is(ElementType!MyTokenStreamHere == ScriptToken)) { 3638 assert(variables !is null); 3639 // this is an entry point that all others lead to, right before getting to interpretExpressions... 3640 3641 return interpretExpressions(parseScript(tokens), variables); 3642 } 3643 3644 var interpretStream(MyTokenStreamHere)(MyTokenStreamHere tokens, var variables) if(is(ElementType!MyTokenStreamHere == ScriptToken)) { 3645 return interpretStream(tokens, 3646 (variables.payloadType() == var.Type.Object && variables._payload._object !is null) ? variables._payload._object : new PrototypeObject()); 3647 } 3648 3649 var interpret(string code, PrototypeObject variables, string scriptFilename = null) { 3650 assert(variables !is null); 3651 return interpretStream(lexScript(repeat(code, 1), scriptFilename), variables); 3652 } 3653 3654 /++ 3655 This is likely your main entry point to the interpreter. It will interpret the script code 3656 given, with the given global variable object (which will be modified by the script, meaning 3657 you can pass it to subsequent calls to `interpret` to store context), and return the result 3658 of the last expression given. 3659 3660 --- 3661 var globals = var.emptyObject; // the global object must be an object of some type 3662 globals.x = 10; 3663 globals.y = 15; 3664 // you can also set global functions through this same style, etc 3665 3666 var result = interpret(`x + y`, globals); 3667 assert(result == 25); 3668 --- 3669 3670 3671 $(TIP 3672 If you want to just call a script function, interpret the definition of it, 3673 then just call it through the `globals` object you passed to it. 3674 3675 --- 3676 var globals = var.emptyObject; 3677 interpret(`function foo(name) { return "hello, " ~ name ~ "!"; }`, globals); 3678 var result = globals.foo()("world"); 3679 assert(result == "hello, world!"); 3680 --- 3681 ) 3682 3683 Params: 3684 code = the script source code you want to interpret 3685 scriptFilename = the filename of the script, if you want to provide it. Gives nicer error messages if you provide one. 3686 variables = The global object of the script context. It will be modified by the user script. 3687 3688 Returns: 3689 the result of the last expression evaluated by the script engine 3690 +/ 3691 var interpret(string code, var variables = null, string scriptFilename = null, string file = __FILE__, size_t line = __LINE__) { 3692 if(scriptFilename is null) 3693 scriptFilename = file ~ "@" ~ to!string(line); 3694 return interpretStream( 3695 lexScript(repeat(code, 1), scriptFilename), 3696 (variables.payloadType() == var.Type.Object && variables._payload._object !is null) ? variables._payload._object : new PrototypeObject()); 3697 } 3698 3699 /// 3700 var interpretFile(File file, var globals) { 3701 import std.algorithm; 3702 return interpretStream(lexScript(file.byLine.map!((a) => a.idup), file.name), 3703 (globals.payloadType() == var.Type.Object && globals._payload._object !is null) ? globals._payload._object : new PrototypeObject()); 3704 } 3705 3706 /// Enhanced repl uses arsd.terminal for better ux. Added April 26, 2020. Default just uses std.stdio. 3707 void repl(bool enhanced = false)(var globals) { 3708 static if(enhanced) { 3709 import arsd.terminal; 3710 Terminal terminal = Terminal(ConsoleOutputMode.linear); 3711 auto lines() { 3712 struct Range { 3713 string line; 3714 string front() { return line; } 3715 bool empty() { return line is null; } 3716 void popFront() { line = terminal.getline(": "); terminal.writeln(); } 3717 } 3718 Range r; 3719 r.popFront(); 3720 return r; 3721 3722 } 3723 3724 void writeln(T...)(T t) { 3725 terminal.writeln(t); 3726 terminal.flush(); 3727 } 3728 } else { 3729 import std.stdio; 3730 auto lines() { return stdin.byLine; } 3731 } 3732 3733 bool exited; 3734 if(globals == null) 3735 globals = var.emptyObject; 3736 globals.exit = () { exited = true; }; 3737 3738 import std.algorithm; 3739 auto variables = (globals.payloadType() == var.Type.Object && globals._payload._object !is null) ? globals._payload._object : new PrototypeObject(); 3740 3741 // we chain to ensure the priming popFront succeeds so we don't throw here 3742 auto tokens = lexScript( 3743 chain(["var __skipme = 0;"], map!((a) => a.idup)(lines)) 3744 , "stdin"); 3745 auto expressions = parseScript(tokens); 3746 3747 while(!exited && !expressions.empty) { 3748 try { 3749 expressions.popFront; 3750 auto expression = expressions.front; 3751 auto res = expression.interpret(variables); 3752 variables = res.sc; 3753 writeln(">>> ", res.value); 3754 } catch(ScriptCompileException e) { 3755 writeln("*+* ", e.msg); 3756 tokens.popFront(); // skip the one we threw on... 3757 } catch(Exception e) { 3758 writeln("*** ", e.msg); 3759 } 3760 } 3761 } 3762 3763 class ScriptFunctionMetadata : VarMetadata { 3764 FunctionLiteralExpression fle; 3765 this(FunctionLiteralExpression fle) { 3766 this.fle = fle; 3767 } 3768 3769 string convertToString() { 3770 return fle.toString(); 3771 } 3772 }