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, true, true) "~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, bool returnRawProperty = false) { 1583 try { 1584 return sc._getMember(identifier, true /* FIXME: recurse?? */, true, returnRawProperty); 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, bool returnRawProperty) { 1596 if(returnRawProperty) { 1597 if(v.payloadType == var.Type.Object) 1598 return v._payload._object._getMember(identifier, true, false, returnRawProperty); 1599 } 1600 1601 return v[identifier]; 1602 } 1603 1604 override InterpretResult interpret(PrototypeObject sc) { 1605 return InterpretResult(getVar(sc), sc); 1606 } 1607 } 1608 1609 class SuperExpression : Expression { 1610 VariableExpression dot; 1611 string origDot; 1612 this(VariableExpression dot) { 1613 if(dot !is null) { 1614 origDot = dot.identifier; 1615 //dot.identifier = "__super_" ~ dot.identifier; // omg this is so bad 1616 } 1617 this.dot = dot; 1618 } 1619 1620 override string toString() { 1621 if(dot is null) 1622 return "super"; 1623 else 1624 return "super." ~ origDot; 1625 } 1626 1627 override InterpretResult interpret(PrototypeObject sc) { 1628 var a = sc._getMember("super", true, true); 1629 if(a._object is null) 1630 throw new Exception("null proto for super"); 1631 PrototypeObject proto = a._object.prototype; 1632 if(proto is null) 1633 throw new Exception("no super"); 1634 //proto = proto.prototype; 1635 1636 if(dot !is null) 1637 a = proto._getMember(dot.identifier, true, true); 1638 else 1639 a = proto._getMember("__ctor", true, true); 1640 return InterpretResult(a, sc); 1641 } 1642 } 1643 1644 class DotVarExpression : VariableExpression { 1645 Expression e1; 1646 VariableExpression e2; 1647 bool recurse = true; 1648 1649 this(Expression e1) { 1650 this.e1 = e1; 1651 super(null); 1652 } 1653 1654 this(Expression e1, VariableExpression e2, bool recurse = true) { 1655 this.e1 = e1; 1656 this.e2 = e2; 1657 this.recurse = recurse; 1658 //assert(typeid(e2) == typeid(VariableExpression)); 1659 super("<do not use>");//e1.identifier ~ "." ~ e2.identifier); 1660 } 1661 1662 override string toString() { 1663 return e1.toString() ~ "." ~ e2.toString(); 1664 } 1665 1666 override ref var getVar(PrototypeObject sc, bool recurse = true, bool returnRawProperty = false) { 1667 if(!this.recurse) { 1668 // this is a special hack... 1669 if(auto ve = cast(VariableExpression) e1) { 1670 return ve.getVar(sc)._getOwnProperty(e2.identifier); 1671 } 1672 assert(0); 1673 } 1674 1675 if(e2.identifier == "__source") { 1676 auto val = e1.interpret(sc).value; 1677 if(auto meta = cast(ScriptFunctionMetadata) val._metadata) 1678 return *(new var(meta.convertToString())); 1679 else 1680 return *(new var(val.toJson())); 1681 } 1682 1683 if(auto ve = cast(VariableExpression) e1) { 1684 return this.getVarFrom(sc, ve.getVar(sc, recurse), returnRawProperty); 1685 } else if(cast(StringLiteralExpression) e1 && e2.identifier == "interpolate") { 1686 auto se = cast(StringLiteralExpression) e1; 1687 var* functor = new var; 1688 //if(!se.allowInterpolation) 1689 //throw new ScriptRuntimeException("Cannot interpolate this string", se.token.lineNumber); 1690 (*functor)._function = (var _this, var[] args) { 1691 return se.interpolate(args.length ? args[0] : var(null), sc); 1692 }; 1693 return *functor; 1694 } else { 1695 // make a temporary for the lhs 1696 auto v = new var(); 1697 *v = e1.interpret(sc).value; 1698 return this.getVarFrom(sc, *v, returnRawProperty); 1699 } 1700 } 1701 1702 override ref var setVar(PrototypeObject sc, var t, bool recurse = true, bool suppressOverloading = false) { 1703 if(suppressOverloading) 1704 return e1.interpret(sc).value.opIndexAssignNoOverload(t, e2.identifier); 1705 else 1706 return e1.interpret(sc).value.opIndexAssign(t, e2.identifier); 1707 } 1708 1709 1710 override ref var getVarFrom(PrototypeObject sc, ref var v, bool returnRawProperty) { 1711 return e2.getVarFrom(sc, v, returnRawProperty); 1712 } 1713 1714 override string toInterpretedString(PrototypeObject sc) { 1715 return getVar(sc).get!string; 1716 } 1717 } 1718 1719 class IndexExpression : VariableExpression { 1720 Expression e1; 1721 Expression e2; 1722 1723 this(Expression e1, Expression e2) { 1724 this.e1 = e1; 1725 this.e2 = e2; 1726 super(null); 1727 } 1728 1729 override string toString() { 1730 return e1.toString() ~ "[" ~ e2.toString() ~ "]"; 1731 } 1732 1733 override ref var getVar(PrototypeObject sc, bool recurse = true, bool returnRawProperty = false) { 1734 if(auto ve = cast(VariableExpression) e1) 1735 return ve.getVar(sc, recurse, returnRawProperty)[e2.interpret(sc).value]; 1736 else { 1737 auto v = new var(); 1738 *v = e1.interpret(sc).value; 1739 return this.getVarFrom(sc, *v, returnRawProperty); 1740 } 1741 } 1742 1743 override ref var setVar(PrototypeObject sc, var t, bool recurse = true, bool suppressOverloading = false) { 1744 return getVar(sc,recurse) = t; 1745 } 1746 } 1747 1748 class SliceExpression : Expression { 1749 // e1[e2 .. e3] 1750 Expression e1; 1751 Expression e2; 1752 Expression e3; 1753 1754 this(Expression e1, Expression e2, Expression e3) { 1755 this.e1 = e1; 1756 this.e2 = e2; 1757 this.e3 = e3; 1758 } 1759 1760 override string toString() { 1761 return e1.toString() ~ "[" ~ e2.toString() ~ " .. " ~ e3.toString() ~ "]"; 1762 } 1763 1764 override InterpretResult interpret(PrototypeObject sc) { 1765 var lhs = e1.interpret(sc).value; 1766 1767 auto specialScope = new PrototypeObject(); 1768 specialScope.prototype = sc; 1769 specialScope._getMember("$", false, false) = lhs.length; 1770 1771 return InterpretResult(lhs[e2.interpret(specialScope).value .. e3.interpret(specialScope).value], sc); 1772 } 1773 } 1774 1775 1776 class LoopControlExpression : Expression { 1777 InterpretResult.FlowControl op; 1778 this(string op) { 1779 if(op == "continue") 1780 this.op = InterpretResult.FlowControl.Continue; 1781 else if(op == "break") 1782 this.op = InterpretResult.FlowControl.Break; 1783 else assert(0, op); 1784 } 1785 1786 override string toString() { 1787 import std.string; 1788 return to!string(this.op).toLower(); 1789 } 1790 1791 override InterpretResult interpret(PrototypeObject sc) { 1792 return InterpretResult(var(null), sc, op); 1793 } 1794 } 1795 1796 1797 class ReturnExpression : Expression { 1798 Expression value; 1799 1800 this(Expression v) { 1801 value = v; 1802 } 1803 1804 override string toString() { return "return " ~ value.toString(); } 1805 1806 override InterpretResult interpret(PrototypeObject sc) { 1807 return InterpretResult(value.interpret(sc).value, sc, InterpretResult.FlowControl.Return); 1808 } 1809 } 1810 1811 class ScopeExpression : Expression { 1812 this(Expression[] expressions) { 1813 this.expressions = expressions; 1814 } 1815 1816 Expression[] expressions; 1817 1818 override string toString() { 1819 string s; 1820 s = "{\n"; 1821 foreach(expr; expressions) { 1822 s ~= "\t"; 1823 s ~= expr.toString(); 1824 s ~= ";\n"; 1825 } 1826 s ~= "}"; 1827 return s; 1828 } 1829 1830 override InterpretResult interpret(PrototypeObject sc) { 1831 var ret; 1832 1833 auto innerScope = new PrototypeObject(); 1834 innerScope.prototype = sc; 1835 1836 innerScope._getMember("__scope_exit", false, false) = var.emptyArray; 1837 innerScope._getMember("__scope_success", false, false) = var.emptyArray; 1838 innerScope._getMember("__scope_failure", false, false) = var.emptyArray; 1839 1840 scope(exit) { 1841 foreach(func; innerScope._getMember("__scope_exit", false, true)) 1842 func(); 1843 } 1844 scope(success) { 1845 foreach(func; innerScope._getMember("__scope_success", false, true)) 1846 func(); 1847 } 1848 scope(failure) { 1849 foreach(func; innerScope._getMember("__scope_failure", false, true)) 1850 func(); 1851 } 1852 1853 foreach(expression; expressions) { 1854 auto res = expression.interpret(innerScope); 1855 ret = res.value; 1856 if(res.flowControl != InterpretResult.FlowControl.Normal) 1857 return InterpretResult(ret, sc, res.flowControl); 1858 } 1859 return InterpretResult(ret, sc); 1860 } 1861 } 1862 1863 class SwitchExpression : Expression { 1864 Expression expr; 1865 CaseExpression[] cases; 1866 CaseExpression default_; 1867 1868 override InterpretResult interpret(PrototypeObject sc) { 1869 auto e = expr.interpret(sc); 1870 1871 bool hitAny; 1872 bool fallingThrough; 1873 bool secondRun; 1874 1875 var last; 1876 1877 again: 1878 foreach(c; cases) { 1879 if(!secondRun && !fallingThrough && c is default_) continue; 1880 if(fallingThrough || (secondRun && c is default_) || c.condition.interpret(sc) == e) { 1881 fallingThrough = false; 1882 if(!secondRun) 1883 hitAny = true; 1884 InterpretResult ret; 1885 expr_loop: foreach(exp; c.expressions) { 1886 ret = exp.interpret(sc); 1887 with(InterpretResult.FlowControl) 1888 final switch(ret.flowControl) { 1889 case Normal: 1890 last = ret.value; 1891 break; 1892 case Return: 1893 case Goto: 1894 return ret; 1895 case Continue: 1896 fallingThrough = true; 1897 break expr_loop; 1898 case Break: 1899 return InterpretResult(last, sc); 1900 } 1901 } 1902 1903 if(!fallingThrough) 1904 break; 1905 } 1906 } 1907 1908 if(!hitAny && !secondRun) { 1909 secondRun = true; 1910 goto again; 1911 } 1912 1913 return InterpretResult(last, sc); 1914 } 1915 } 1916 1917 class CaseExpression : Expression { 1918 this(Expression condition) { 1919 this.condition = condition; 1920 } 1921 Expression condition; 1922 Expression[] expressions; 1923 1924 override string toString() { 1925 string code; 1926 if(condition is null) 1927 code = "default:"; 1928 else 1929 code = "case " ~ condition.toString() ~ ":"; 1930 1931 foreach(expr; expressions) 1932 code ~= "\n" ~ expr.toString() ~ ";"; 1933 1934 return code; 1935 } 1936 1937 override InterpretResult interpret(PrototypeObject sc) { 1938 // I did this inline up in the SwitchExpression above. maybe insane?! 1939 assert(0); 1940 } 1941 } 1942 1943 unittest { 1944 interpret(q{ 1945 var a = 10; 1946 // case and break should work 1947 var brk; 1948 1949 // var brk = switch doesn't parse, but this will..... 1950 // (I kinda went everything is an expression but not all the way. this code SUX.) 1951 brk = switch(a) { 1952 case 10: 1953 a = 30; 1954 break; 1955 case 30: 1956 a = 40; 1957 break; 1958 default: 1959 a = 0; 1960 } 1961 1962 assert(a == 30); 1963 assert(brk == 30); // value of switch set to last expression evaled inside 1964 1965 // so should default 1966 switch(a) { 1967 case 20: 1968 a = 40; 1969 break; 1970 default: 1971 a = 40; 1972 } 1973 1974 assert(a == 40); 1975 1976 switch(a) { 1977 case 40: 1978 a = 50; 1979 case 60: // no implicit fallthrough in this lang... 1980 a = 60; 1981 } 1982 1983 assert(a == 50); 1984 1985 var ret; 1986 1987 ret = switch(a) { 1988 case 50: 1989 a = 60; 1990 continue; // request fallthrough. D uses "goto case", but I haven't implemented any goto yet so continue is best fit 1991 case 90: 1992 a = 70; 1993 } 1994 1995 assert(a == 70); // the explicit `continue` requests fallthrough behavior 1996 assert(ret == 70); 1997 }); 1998 } 1999 2000 unittest { 2001 // overloads 2002 interpret(q{ 2003 function foo(int a) { return 10 + a; } 2004 function foo(float a) { return 100 + a; } 2005 function foo(string a) { return "string " ~ a; } 2006 2007 assert(foo(4) == 14); 2008 assert(foo(4.5) == 104.5); 2009 assert(foo("test") == "string test"); 2010 2011 // can redefine specific override 2012 function foo(int a) { return a; } 2013 assert(foo(4) == 4); 2014 // leaving others in place 2015 assert(foo(4.5) == 104.5); 2016 assert(foo("test") == "string test"); 2017 }); 2018 } 2019 2020 unittest { 2021 // catching objects 2022 interpret(q{ 2023 class Foo {} 2024 class Bar : Foo {} 2025 2026 var res = try throw new Bar(); catch(Bar b) { 2 } catch(e) { 1 }; 2027 assert(res == 2); 2028 2029 var res = try throw new Foo(); catch(Bar b) { 2 } catch(e) { 1 }; 2030 assert(res == 1); 2031 2032 var res = try throw Foo; catch(Foo b) { 2 } catch(e) { 1 }; 2033 assert(res == 2); 2034 }); 2035 } 2036 2037 unittest { 2038 // ternary precedence 2039 interpret(q{ 2040 assert(0 == 0 ? true : false == true); 2041 assert((0 == 0) ? true : false == true); 2042 // lol FIXME 2043 //assert(((0 == 0) ? true : false) == true); 2044 }); 2045 } 2046 2047 unittest { 2048 // new nested class 2049 interpret(q{ 2050 class A {} 2051 A.b = class B { var c; this(a) { this.c = a; } } 2052 var c = new A.b(5); 2053 assert(A.b.c == null); 2054 assert(c.c == 5); 2055 }); 2056 } 2057 2058 class ForeachExpression : Expression { 2059 VariableDeclaration decl; 2060 Expression subject; 2061 Expression subject2; 2062 Expression loopBody; 2063 2064 override string toString() { 2065 return "foreach(" ~ decl.toString() ~ "; " ~ subject.toString() ~ ((subject2 is null) ? "" : (".." ~ subject2.toString)) ~ ") " ~ loopBody.toString(); 2066 } 2067 2068 override InterpretResult interpret(PrototypeObject sc) { 2069 var result; 2070 2071 assert(loopBody !is null); 2072 2073 auto loopScope = new PrototypeObject(); 2074 loopScope.prototype = sc; 2075 2076 InterpretResult.FlowControl flowControl; 2077 2078 static string doLoopBody() { return q{ 2079 if(decl.identifiers.length > 1) { 2080 sc._getMember(decl.identifiers[0], false, false) = i; 2081 sc._getMember(decl.identifiers[1], false, false) = item; 2082 } else { 2083 sc._getMember(decl.identifiers[0], false, false) = item; 2084 } 2085 2086 auto res = loopBody.interpret(loopScope); 2087 result = res.value; 2088 flowControl = res.flowControl; 2089 if(flowControl == InterpretResult.FlowControl.Break) 2090 break; 2091 if(flowControl == InterpretResult.FlowControl.Return) 2092 break; 2093 //if(flowControl == InterpretResult.FlowControl.Continue) 2094 // this is fine, we still want to do the advancement 2095 };} 2096 2097 var what = subject.interpret(sc).value; 2098 var termination = subject2 is null ? var(null) : subject2.interpret(sc).value; 2099 if(what.payloadType == var.Type.Integral && subject2 is null) { 2100 // loop from 0 to what 2101 int end = what.get!int; 2102 foreach(item; 0 .. end) { 2103 auto i = item; 2104 mixin(doLoopBody()); 2105 } 2106 } else if(what.payloadType == var.Type.Integral && termination.payloadType == var.Type.Integral) { 2107 // loop what .. termination 2108 int start = what.get!int; 2109 int end = termination.get!int; 2110 int stride; 2111 if(end < start) { 2112 stride = -1; 2113 } else { 2114 stride = 1; 2115 } 2116 int i = -1; 2117 for(int item = start; item != end; item += stride) { 2118 i++; 2119 mixin(doLoopBody()); 2120 } 2121 } else { 2122 if(subject2 !is null) 2123 throw new ScriptRuntimeException("foreach( a .. b ) invalid unless a is an integer", null, 0); // FIXME 2124 foreach(i, item; what) { 2125 mixin(doLoopBody()); 2126 } 2127 } 2128 2129 if(flowControl != InterpretResult.FlowControl.Return) 2130 flowControl = InterpretResult.FlowControl.Normal; 2131 2132 return InterpretResult(result, sc, flowControl); 2133 } 2134 } 2135 2136 class ForExpression : Expression { 2137 Expression initialization; 2138 Expression condition; 2139 Expression advancement; 2140 Expression loopBody; 2141 2142 this() {} 2143 2144 override InterpretResult interpret(PrototypeObject sc) { 2145 var result; 2146 2147 assert(loopBody !is null); 2148 2149 auto loopScope = new PrototypeObject(); 2150 loopScope.prototype = sc; 2151 if(initialization !is null) 2152 initialization.interpret(loopScope); 2153 2154 InterpretResult.FlowControl flowControl; 2155 2156 static string doLoopBody() { return q{ 2157 auto res = loopBody.interpret(loopScope); 2158 result = res.value; 2159 flowControl = res.flowControl; 2160 if(flowControl == InterpretResult.FlowControl.Break) 2161 break; 2162 if(flowControl == InterpretResult.FlowControl.Return) 2163 break; 2164 //if(flowControl == InterpretResult.FlowControl.Continue) 2165 // this is fine, we still want to do the advancement 2166 if(advancement) 2167 advancement.interpret(loopScope); 2168 };} 2169 2170 if(condition !is null) { 2171 while(condition.interpret(loopScope).value) { 2172 mixin(doLoopBody()); 2173 } 2174 } else 2175 while(true) { 2176 mixin(doLoopBody()); 2177 } 2178 2179 if(flowControl != InterpretResult.FlowControl.Return) 2180 flowControl = InterpretResult.FlowControl.Normal; 2181 2182 return InterpretResult(result, sc, flowControl); 2183 } 2184 2185 override string toString() { 2186 string code = "for("; 2187 if(initialization !is null) 2188 code ~= initialization.toString(); 2189 code ~= "; "; 2190 if(condition !is null) 2191 code ~= condition.toString(); 2192 code ~= "; "; 2193 if(advancement !is null) 2194 code ~= advancement.toString(); 2195 code ~= ") "; 2196 code ~= loopBody.toString(); 2197 2198 return code; 2199 } 2200 } 2201 2202 class IfExpression : Expression { 2203 Expression condition; 2204 Expression ifTrue; 2205 Expression ifFalse; 2206 2207 this() {} 2208 2209 override InterpretResult interpret(PrototypeObject sc) { 2210 InterpretResult result; 2211 assert(condition !is null); 2212 2213 auto ifScope = new PrototypeObject(); 2214 ifScope.prototype = sc; 2215 2216 if(condition.interpret(ifScope).value) { 2217 if(ifTrue !is null) 2218 result = ifTrue.interpret(ifScope); 2219 } else { 2220 if(ifFalse !is null) 2221 result = ifFalse.interpret(ifScope); 2222 } 2223 return InterpretResult(result.value, sc, result.flowControl); 2224 } 2225 2226 override string toString() { 2227 string code = "if "; 2228 code ~= condition.toString(); 2229 code ~= " "; 2230 if(ifTrue !is null) 2231 code ~= ifTrue.toString(); 2232 else 2233 code ~= " { }"; 2234 if(ifFalse !is null) 2235 code ~= " else " ~ ifFalse.toString(); 2236 return code; 2237 } 2238 } 2239 2240 class TernaryExpression : Expression { 2241 Expression condition; 2242 Expression ifTrue; 2243 Expression ifFalse; 2244 2245 this() {} 2246 2247 override InterpretResult interpret(PrototypeObject sc) { 2248 InterpretResult result; 2249 assert(condition !is null); 2250 2251 auto ifScope = new PrototypeObject(); 2252 ifScope.prototype = sc; 2253 2254 if(condition.interpret(ifScope).value) { 2255 result = ifTrue.interpret(ifScope); 2256 } else { 2257 result = ifFalse.interpret(ifScope); 2258 } 2259 return InterpretResult(result.value, sc, result.flowControl); 2260 } 2261 2262 override string toString() { 2263 string code = ""; 2264 code ~= condition.toString(); 2265 code ~= " ? "; 2266 code ~= ifTrue.toString(); 2267 code ~= " : "; 2268 code ~= ifFalse.toString(); 2269 return code; 2270 } 2271 } 2272 2273 // this is kinda like a placement new, and currently isn't exposed inside the language, 2274 // but is used for class inheritance 2275 class ShallowCopyExpression : Expression { 2276 Expression e1; 2277 Expression e2; 2278 2279 this(Expression e1, Expression e2) { 2280 this.e1 = e1; 2281 this.e2 = e2; 2282 } 2283 2284 override InterpretResult interpret(PrototypeObject sc) { 2285 auto v = cast(VariableExpression) e1; 2286 if(v is null) 2287 throw new ScriptRuntimeException("not an lvalue", null, 0 /* FIXME */); 2288 2289 v.getVar(sc, false)._object.copyPropertiesFrom(e2.interpret(sc).value._object); 2290 2291 return InterpretResult(var(null), sc); 2292 } 2293 2294 } 2295 2296 class NewExpression : Expression { 2297 Expression what; 2298 Expression[] args; 2299 this(Expression w) { 2300 what = w; 2301 } 2302 2303 override InterpretResult interpret(PrototypeObject sc) { 2304 assert(what !is null); 2305 2306 var[] args; 2307 foreach(arg; this.args) 2308 args ~= arg.interpret(sc).value; 2309 2310 var original = what.interpret(sc).value; 2311 var n = original._copy_new; 2312 if(n.payloadType() == var.Type.Object) { 2313 var ctor = original.prototype ? original.prototype._getOwnProperty("__ctor") : var(null); 2314 if(ctor) 2315 ctor.apply(n, args); 2316 } 2317 2318 return InterpretResult(n, sc); 2319 } 2320 } 2321 2322 class ThrowExpression : Expression { 2323 Expression whatToThrow; 2324 ScriptToken where; 2325 2326 this(Expression e, ScriptToken where) { 2327 whatToThrow = e; 2328 this.where = where; 2329 } 2330 2331 override InterpretResult interpret(PrototypeObject sc) { 2332 assert(whatToThrow !is null); 2333 throw new ScriptException(whatToThrow.interpret(sc).value, ScriptLocation(where.scriptFilename, where.lineNumber)); 2334 assert(0); 2335 } 2336 } 2337 2338 bool isCompatibleType(var v, string specifier, PrototypeObject sc) { 2339 var t = toType(specifier, sc); 2340 auto score = typeCompatibilityScore(v, t); 2341 return score > 0; 2342 } 2343 2344 var toType(string specifier, PrototypeObject sc) { 2345 switch(specifier) { 2346 case "int", "long": return var(0); 2347 case "float", "double": return var(0.0); 2348 case "string": return var(""); 2349 default: 2350 auto got = sc._peekMember(specifier, true); 2351 if(got) 2352 return *got; 2353 else 2354 return var.init; 2355 } 2356 } 2357 2358 var[] toTypes(string[] specifiers, PrototypeObject sc) { 2359 var[] arr; 2360 foreach(s; specifiers) 2361 arr ~= toType(s, sc); 2362 return arr; 2363 } 2364 2365 2366 class ExceptionBlockExpression : Expression { 2367 Expression tryExpression; 2368 2369 string[] catchVarDecls; 2370 string[] catchVarTypeSpecifiers; 2371 Expression[] catchExpressions; 2372 2373 Expression[] finallyExpressions; 2374 2375 override InterpretResult interpret(PrototypeObject sc) { 2376 InterpretResult result; 2377 result.sc = sc; 2378 assert(tryExpression !is null); 2379 assert(catchVarDecls.length == catchExpressions.length); 2380 2381 void caught(var ex) { 2382 if(catchExpressions.length) 2383 foreach(i, ce; catchExpressions) { 2384 if(catchVarTypeSpecifiers[i].length == 0 || isCompatibleType(ex, catchVarTypeSpecifiers[i], sc)) { 2385 auto catchScope = new PrototypeObject(); 2386 catchScope.prototype = sc; 2387 catchScope._getMember(catchVarDecls[i], false, false) = ex; 2388 2389 result = ce.interpret(catchScope); 2390 break; 2391 } 2392 } else 2393 result = InterpretResult(ex, sc); 2394 } 2395 2396 if(catchExpressions.length || (catchExpressions.length == 0 && finallyExpressions.length == 0)) 2397 try { 2398 result = tryExpression.interpret(sc); 2399 } catch(NonScriptCatchableException e) { 2400 // the script cannot catch these so it continues up regardless 2401 throw e; 2402 } catch(ScriptException e) { 2403 // FIXME: what about the other information here? idk. 2404 caught(e.payload); 2405 } catch(Exception e) { 2406 var ex = var.emptyObject; 2407 ex.type = typeid(e).name; 2408 ex.msg = e.msg; 2409 ex.file = e.file; 2410 ex.line = e.line; 2411 2412 caught(ex); 2413 } finally { 2414 foreach(fe; finallyExpressions) 2415 result = fe.interpret(sc); 2416 } 2417 else 2418 try { 2419 result = tryExpression.interpret(sc); 2420 } finally { 2421 foreach(fe; finallyExpressions) 2422 result = fe.interpret(sc); 2423 } 2424 2425 return result; 2426 } 2427 } 2428 2429 class ParentheticalExpression : Expression { 2430 Expression inside; 2431 this(Expression inside) { 2432 this.inside = inside; 2433 } 2434 2435 override string toString() { 2436 return "(" ~ inside.toString() ~ ")"; 2437 } 2438 2439 override InterpretResult interpret(PrototypeObject sc) { 2440 return InterpretResult(inside.interpret(sc).value, sc); 2441 } 2442 } 2443 2444 class AssertKeyword : Expression { 2445 ScriptToken token; 2446 this(ScriptToken token) { 2447 this.token = token; 2448 } 2449 override string toString() { 2450 return "assert"; 2451 } 2452 2453 override InterpretResult interpret(PrototypeObject sc) { 2454 if(AssertKeywordObject is null) 2455 AssertKeywordObject = new PrototypeObject(); 2456 var dummy; 2457 dummy._object = AssertKeywordObject; 2458 return InterpretResult(dummy, sc); 2459 } 2460 } 2461 2462 PrototypeObject AssertKeywordObject; 2463 PrototypeObject DefaultArgumentDummyObject; 2464 2465 class CallExpression : Expression { 2466 Expression func; 2467 Expression[] arguments; 2468 ScriptLocation loc; 2469 2470 override string toString() { 2471 string s = func.toString() ~ "("; 2472 foreach(i, arg; arguments) { 2473 if(i) s ~= ", "; 2474 s ~= arg.toString(); 2475 } 2476 2477 s ~= ")"; 2478 return s; 2479 } 2480 2481 this(ScriptLocation loc, Expression func) { 2482 this.loc = loc; 2483 this.func = func; 2484 } 2485 2486 override string toInterpretedString(PrototypeObject sc) { 2487 return interpret(sc).value.get!string; 2488 } 2489 2490 override InterpretResult interpret(PrototypeObject sc) { 2491 if(auto asrt = cast(AssertKeyword) func) { 2492 auto assertExpression = arguments[0]; 2493 Expression assertString; 2494 if(arguments.length > 1) 2495 assertString = arguments[1]; 2496 2497 var v = assertExpression.interpret(sc).value; 2498 2499 if(!v) 2500 throw new ScriptException( 2501 var(this.toString() ~ " failed, got: " ~ assertExpression.toInterpretedString(sc)), 2502 ScriptLocation(asrt.token.scriptFilename, asrt.token.lineNumber)); 2503 2504 return InterpretResult(v, sc); 2505 } 2506 2507 auto f = func.interpret(sc).value; 2508 bool isMacro = (f.payloadType == var.Type.Object && ((cast(MacroPrototype) f._payload._object) !is null)); 2509 var[] args; 2510 foreach(argument; arguments) 2511 if(argument !is null) { 2512 if(isMacro) // macro, pass the argument as an expression object 2513 args ~= argument.toScriptExpressionObject(sc); 2514 else // regular function, interpret the arguments 2515 args ~= argument.interpret(sc).value; 2516 } else { 2517 if(DefaultArgumentDummyObject is null) 2518 DefaultArgumentDummyObject = new PrototypeObject(); 2519 2520 var dummy; 2521 dummy._object = DefaultArgumentDummyObject; 2522 2523 args ~= dummy; 2524 } 2525 2526 var _this; 2527 if(auto dve = cast(DotVarExpression) func) { 2528 _this = dve.e1.interpret(sc).value; 2529 } else if(auto ide = cast(IndexExpression) func) { 2530 _this = ide.interpret(sc).value; 2531 } else if(auto se = cast(SuperExpression) func) { 2532 // super things are passed this object despite looking things up on the prototype 2533 // so it calls the correct instance 2534 _this = sc._getMember("this", true, true); 2535 } 2536 2537 try { 2538 return InterpretResult(f.apply(_this, args), sc); 2539 } catch(DynamicTypeException dte) { 2540 dte.callStack ~= loc; 2541 throw dte; 2542 } catch(ScriptException se) { 2543 se.callStack ~= loc; 2544 throw se; 2545 } 2546 } 2547 } 2548 2549 ScriptToken requireNextToken(MyTokenStreamHere)(ref MyTokenStreamHere tokens, ScriptToken.Type type, string str = null, string file = __FILE__, size_t line = __LINE__) { 2550 if(tokens.empty) 2551 throw new ScriptCompileException("script ended prematurely", null, 0, file, line); 2552 auto next = tokens.front; 2553 if(next.type != type || (str !is null && next.str != str)) 2554 throw new ScriptCompileException("unexpected '"~next.str~"' while expecting " ~ to!string(type) ~ " " ~ str, next.scriptFilename, next.lineNumber, file, line); 2555 2556 tokens.popFront(); 2557 return next; 2558 } 2559 2560 bool peekNextToken(MyTokenStreamHere)(MyTokenStreamHere tokens, ScriptToken.Type type, string str = null, string file = __FILE__, size_t line = __LINE__) { 2561 if(tokens.empty) 2562 return false; 2563 auto next = tokens.front; 2564 if(next.type != type || (str !is null && next.str != str)) 2565 return false; 2566 return true; 2567 } 2568 2569 VariableExpression parseVariableName(MyTokenStreamHere)(ref MyTokenStreamHere tokens) { 2570 assert(!tokens.empty); 2571 auto token = tokens.front; 2572 if(token.type == ScriptToken.Type.identifier) { 2573 tokens.popFront(); 2574 return new VariableExpression(token.str, ScriptLocation(token.scriptFilename, token.lineNumber)); 2575 } 2576 throw new ScriptCompileException("Found "~token.str~" when expecting identifier", token.scriptFilename, token.lineNumber); 2577 } 2578 2579 Expression parseDottedVariableName(MyTokenStreamHere)(ref MyTokenStreamHere tokens) { 2580 assert(!tokens.empty); 2581 2582 auto ve = parseVariableName(tokens); 2583 2584 auto token = tokens.front; 2585 if(token.type == ScriptToken.Type.symbol && token.str == ".") { 2586 tokens.popFront(); 2587 return new DotVarExpression(ve, parseVariableName(tokens)); 2588 } 2589 return ve; 2590 } 2591 2592 2593 Expression parsePart(MyTokenStreamHere)(ref MyTokenStreamHere tokens) { 2594 if(!tokens.empty) { 2595 auto token = tokens.front; 2596 2597 Expression e; 2598 2599 if(token.str == "super") { 2600 tokens.popFront(); 2601 VariableExpression dot; 2602 if(!tokens.empty && tokens.front.str == ".") { 2603 tokens.popFront(); 2604 dot = parseVariableName(tokens); 2605 } 2606 e = new SuperExpression(dot); 2607 } 2608 else if(token.type == ScriptToken.Type.identifier) 2609 e = parseVariableName(tokens); 2610 else if(token.type == ScriptToken.Type.symbol && (token.str == "-" || token.str == "+" || token.str == "!" || token.str == "~")) { 2611 auto op = token.str; 2612 tokens.popFront(); 2613 2614 e = parsePart(tokens); 2615 if(op == "-") 2616 e = new NegationExpression(e); 2617 else if(op == "!") 2618 e = new NotExpression(e); 2619 else if(op == "~") 2620 e = new BitFlipExpression(e); 2621 } else { 2622 tokens.popFront(); 2623 2624 if(token.type == ScriptToken.Type.int_number) 2625 e = new IntLiteralExpression(token.str); 2626 else if(token.type == ScriptToken.Type.float_number) 2627 e = new FloatLiteralExpression(token.str); 2628 else if(token.type == ScriptToken.Type..string) 2629 e = new StringLiteralExpression(token); 2630 else if(token.type == ScriptToken.Type.symbol || token.type == ScriptToken.Type.keyword) { 2631 switch(token.str) { 2632 case "true": 2633 case "false": 2634 e = new BoolLiteralExpression(token.str); 2635 break; 2636 case "new": 2637 // FIXME: why is this needed here? maybe it should be here instead of parseExpression 2638 tokens.pushFront(token); 2639 return parseExpression(tokens); 2640 case "(": 2641 //tokens.popFront(); 2642 auto parenthetical = new ParentheticalExpression(parseExpression(tokens)); 2643 tokens.requireNextToken(ScriptToken.Type.symbol, ")"); 2644 2645 return parenthetical; 2646 case "[": 2647 // array literal 2648 auto arr = new ArrayLiteralExpression(); 2649 2650 bool first = true; 2651 moreElements: 2652 if(tokens.empty) 2653 throw new ScriptCompileException("unexpected end of file when reading array literal", token.scriptFilename, token.lineNumber); 2654 2655 auto peek = tokens.front; 2656 if(peek.type == ScriptToken.Type.symbol && peek.str == "]") { 2657 tokens.popFront(); 2658 return arr; 2659 } 2660 2661 if(!first) 2662 tokens.requireNextToken(ScriptToken.Type.symbol, ","); 2663 else 2664 first = false; 2665 2666 arr.elements ~= parseExpression(tokens); 2667 2668 goto moreElements; 2669 case "json!q{": 2670 case "#{": 2671 // json object literal 2672 auto obj = new ObjectLiteralExpression(); 2673 /* 2674 these go 2675 2676 string or ident which is the key 2677 then a colon 2678 then an expression which is the value 2679 2680 then optionally a comma 2681 2682 then either } which finishes it, or another key 2683 */ 2684 2685 if(tokens.empty) 2686 throw new ScriptCompileException("unexpected end of file when reading object literal", token.scriptFilename, token.lineNumber); 2687 2688 moreKeys: 2689 auto key = tokens.front; 2690 tokens.popFront(); 2691 if(key.type == ScriptToken.Type.symbol && key.str == "}") { 2692 // all done! 2693 e = obj; 2694 break; 2695 } 2696 if(key.type != ScriptToken.Type..string && key.type != ScriptToken.Type.identifier) { 2697 throw new ScriptCompileException("unexpected '"~key.str~"' when reading object literal", key.scriptFilename, key.lineNumber); 2698 2699 } 2700 2701 tokens.requireNextToken(ScriptToken.Type.symbol, ":"); 2702 2703 auto value = parseExpression(tokens); 2704 if(tokens.empty) 2705 throw new ScriptCompileException("unclosed object literal", key.scriptFilename, key.lineNumber); 2706 2707 if(tokens.peekNextToken(ScriptToken.Type.symbol, ",")) 2708 tokens.popFront(); 2709 2710 obj.elements[key.str] = value; 2711 2712 goto moreKeys; 2713 case "macro": 2714 case "function": 2715 tokens.requireNextToken(ScriptToken.Type.symbol, "("); 2716 2717 auto exp = new FunctionLiteralExpression(); 2718 if(!tokens.peekNextToken(ScriptToken.Type.symbol, ")")) 2719 exp.arguments = parseVariableDeclaration(tokens, ")"); 2720 2721 tokens.requireNextToken(ScriptToken.Type.symbol, ")"); 2722 2723 exp.functionBody = parseExpression(tokens); 2724 exp.isMacro = token.str == "macro"; 2725 2726 e = exp; 2727 break; 2728 case "null": 2729 e = new NullLiteralExpression(); 2730 break; 2731 case "mixin": 2732 case "eval": 2733 tokens.requireNextToken(ScriptToken.Type.symbol, "("); 2734 e = new MixinExpression(parseExpression(tokens)); 2735 tokens.requireNextToken(ScriptToken.Type.symbol, ")"); 2736 break; 2737 default: 2738 goto unknown; 2739 } 2740 } else { 2741 unknown: 2742 throw new ScriptCompileException("unexpected '"~token.str~"' when reading ident", token.scriptFilename, token.lineNumber); 2743 } 2744 } 2745 2746 funcLoop: while(!tokens.empty) { 2747 auto peek = tokens.front; 2748 if(peek.type == ScriptToken.Type.symbol) { 2749 switch(peek.str) { 2750 case "(": 2751 e = parseFunctionCall(tokens, e); 2752 break; 2753 case "[": 2754 tokens.popFront(); 2755 auto e1 = parseExpression(tokens); 2756 if(tokens.peekNextToken(ScriptToken.Type.symbol, "..")) { 2757 tokens.popFront(); 2758 e = new SliceExpression(e, e1, parseExpression(tokens)); 2759 } else { 2760 e = new IndexExpression(e, e1); 2761 } 2762 tokens.requireNextToken(ScriptToken.Type.symbol, "]"); 2763 break; 2764 case ".": 2765 tokens.popFront(); 2766 e = new DotVarExpression(e, parseVariableName(tokens)); 2767 break; 2768 default: 2769 return e; // we don't know, punt it elsewhere 2770 } 2771 } else return e; // again, we don't know, so just punt it down the line 2772 } 2773 return e; 2774 } 2775 2776 throw new ScriptCompileException("Ran out of tokens when trying to parsePart", null, 0); 2777 } 2778 2779 Expression parseArguments(MyTokenStreamHere)(ref MyTokenStreamHere tokens, Expression exp, ref Expression[] where) { 2780 // arguments. 2781 auto peek = tokens.front; 2782 if(peek.type == ScriptToken.Type.symbol && peek.str == ")") { 2783 tokens.popFront(); 2784 return exp; 2785 } 2786 2787 moreArguments: 2788 2789 if(tokens.peekNextToken(ScriptToken.Type.keyword, "default")) { 2790 tokens.popFront(); 2791 where ~= null; 2792 } else { 2793 where ~= parseExpression(tokens); 2794 } 2795 2796 if(tokens.empty) 2797 throw new ScriptCompileException("unexpected end of file when parsing call expression", peek.scriptFilename, peek.lineNumber); 2798 peek = tokens.front; 2799 if(peek.type == ScriptToken.Type.symbol && peek.str == ",") { 2800 tokens.popFront(); 2801 goto moreArguments; 2802 } else if(peek.type == ScriptToken.Type.symbol && peek.str == ")") { 2803 tokens.popFront(); 2804 return exp; 2805 } else 2806 throw new ScriptCompileException("unexpected '"~peek.str~"' when reading argument list", peek.scriptFilename, peek.lineNumber); 2807 2808 } 2809 2810 Expression parseFunctionCall(MyTokenStreamHere)(ref MyTokenStreamHere tokens, Expression e) { 2811 assert(!tokens.empty); 2812 auto peek = tokens.front; 2813 auto exp = new CallExpression(ScriptLocation(peek.scriptFilename, peek.lineNumber), e); 2814 tokens.popFront(); 2815 if(tokens.empty) 2816 throw new ScriptCompileException("unexpected end of file when parsing call expression", peek.scriptFilename, peek.lineNumber); 2817 return parseArguments(tokens, exp, exp.arguments); 2818 } 2819 2820 Expression parseFactor(MyTokenStreamHere)(ref MyTokenStreamHere tokens) { 2821 auto e1 = parsePart(tokens); 2822 loop: while(!tokens.empty) { 2823 auto peek = tokens.front; 2824 2825 if(peek.type == ScriptToken.Type.symbol) { 2826 switch(peek.str) { 2827 case "<<": 2828 case ">>": 2829 case ">>>": 2830 case "*": 2831 case "/": 2832 case "%": 2833 tokens.popFront(); 2834 e1 = new BinaryExpression(peek.str, e1, parsePart(tokens)); 2835 break; 2836 default: 2837 break loop; 2838 } 2839 } else throw new Exception("Got " ~ peek.str ~ " when expecting symbol"); 2840 } 2841 2842 return e1; 2843 } 2844 2845 Expression parseAddend(MyTokenStreamHere)(ref MyTokenStreamHere tokens) { 2846 auto e1 = parseFactor(tokens); 2847 loop: while(!tokens.empty) { 2848 auto peek = tokens.front; 2849 2850 if(peek.type == ScriptToken.Type.symbol) { 2851 switch(peek.str) { 2852 case "..": // possible FIXME 2853 case ")": // possible FIXME 2854 case "]": // possible FIXME 2855 case "}": // possible FIXME 2856 case ",": // possible FIXME these are passed on to the next thing 2857 case ";": 2858 case ":": // idk 2859 case "?": 2860 return e1; 2861 2862 case "|>": 2863 tokens.popFront(); 2864 e1 = new PipelineExpression(ScriptLocation(peek.scriptFilename, peek.lineNumber), e1, parseFactor(tokens)); 2865 break; 2866 case ".": 2867 tokens.popFront(); 2868 e1 = new DotVarExpression(e1, parseVariableName(tokens)); 2869 break; 2870 case "=": 2871 tokens.popFront(); 2872 return new AssignExpression(e1, parseExpression(tokens)); 2873 case "&&": // thanks to mzfhhhh for fix 2874 case "||": 2875 tokens.popFront(); 2876 e1 = new BinaryExpression(peek.str, e1, parseExpression(tokens)); 2877 break; 2878 case "~": 2879 // FIXME: make sure this has the right associativity 2880 2881 case "&": 2882 case "|": 2883 case "^": 2884 2885 case "&=": 2886 case "|=": 2887 case "^=": 2888 2889 case "+": 2890 case "-": 2891 2892 case "==": 2893 case "!=": 2894 case "<=": 2895 case ">=": 2896 case "<": 2897 case ">": 2898 tokens.popFront(); 2899 e1 = new BinaryExpression(peek.str, e1, parseAddend(tokens)); 2900 break; 2901 case "+=": 2902 case "-=": 2903 case "*=": 2904 case "/=": 2905 case "~=": 2906 case "%=": 2907 tokens.popFront(); 2908 return new OpAssignExpression(peek.str[0..1], e1, parseExpression(tokens)); 2909 default: 2910 throw new ScriptCompileException("Parse error, unexpected " ~ peek.str ~ " when looking for operator", peek.scriptFilename, peek.lineNumber); 2911 } 2912 //} else if(peek.type == ScriptToken.Type.identifier || peek.type == ScriptToken.Type.number) { 2913 //return parseFactor(tokens); 2914 } else 2915 throw new ScriptCompileException("Parse error, unexpected '" ~ peek.str ~ "'", peek.scriptFilename, peek.lineNumber); 2916 } 2917 2918 return e1; 2919 } 2920 2921 Expression parseExpression(MyTokenStreamHere)(ref MyTokenStreamHere tokens, bool consumeEnd = false) { 2922 Expression ret; 2923 ScriptToken first; 2924 string expectedEnd = ";"; 2925 //auto e1 = parseFactor(tokens); 2926 2927 while(tokens.peekNextToken(ScriptToken.Type.symbol, ";")) { 2928 tokens.popFront(); 2929 } 2930 if(!tokens.empty) { 2931 first = tokens.front; 2932 if(tokens.peekNextToken(ScriptToken.Type.symbol, "{")) { 2933 auto start = tokens.front; 2934 tokens.popFront(); 2935 auto e = parseCompoundStatement(tokens, start.lineNumber, "}").array; 2936 ret = new ScopeExpression(e); 2937 expectedEnd = null; // {} don't need ; at the end 2938 } else if(tokens.peekNextToken(ScriptToken.Type.keyword, "scope")) { 2939 auto start = tokens.front; 2940 tokens.popFront(); 2941 tokens.requireNextToken(ScriptToken.Type.symbol, "("); 2942 2943 auto ident = tokens.requireNextToken(ScriptToken.Type.identifier); 2944 switch(ident.str) { 2945 case "success": 2946 case "failure": 2947 case "exit": 2948 break; 2949 default: 2950 throw new ScriptCompileException("unexpected " ~ ident.str ~ ". valid scope(idents) are success, failure, and exit", ident.scriptFilename, ident.lineNumber); 2951 } 2952 2953 tokens.requireNextToken(ScriptToken.Type.symbol, ")"); 2954 2955 string i = "__scope_" ~ ident.str; 2956 auto literal = new FunctionLiteralExpression(); 2957 literal.functionBody = parseExpression(tokens); 2958 2959 auto e = new OpAssignExpression("~", new VariableExpression(i), literal); 2960 ret = e; 2961 } else if(tokens.peekNextToken(ScriptToken.Type.symbol, "(")) { 2962 auto start = tokens.front; 2963 tokens.popFront(); 2964 auto parenthetical = new ParentheticalExpression(parseExpression(tokens)); 2965 tokens.requireNextToken(ScriptToken.Type.symbol, ")"); 2966 if(tokens.peekNextToken(ScriptToken.Type.symbol, "(")) { 2967 // we have a function call, e.g. (test)() 2968 ret = parseFunctionCall(tokens, parenthetical); 2969 } else 2970 ret = parenthetical; 2971 } else if(tokens.peekNextToken(ScriptToken.Type.keyword, "new")) { 2972 auto start = tokens.front; 2973 tokens.popFront(); 2974 2975 auto expr = parseDottedVariableName(tokens); 2976 auto ne = new NewExpression(expr); 2977 if(tokens.peekNextToken(ScriptToken.Type.symbol, "(")) { 2978 tokens.popFront(); 2979 parseArguments(tokens, ne, ne.args); 2980 } 2981 2982 ret = ne; 2983 } else if(tokens.peekNextToken(ScriptToken.Type.keyword, "class")) { 2984 auto start = tokens.front; 2985 tokens.popFront(); 2986 2987 Expression[] expressions; 2988 2989 // the way classes work is they are actually object literals with a different syntax. new foo then just copies it 2990 /* 2991 we create a prototype object 2992 we create an object, with that prototype 2993 2994 set all functions and static stuff to the prototype 2995 the rest goes to the object 2996 2997 the expression returns the object we made 2998 */ 2999 3000 auto vars = new VariableDeclaration(); 3001 vars.identifiers = ["__proto", "__obj"]; 3002 3003 auto staticScopeBacking = new PrototypeObject(); 3004 auto instanceScopeBacking = new PrototypeObject(); 3005 3006 vars.initializers = [new ObjectLiteralExpression(staticScopeBacking), new ObjectLiteralExpression(instanceScopeBacking)]; 3007 expressions ~= vars; 3008 3009 // FIXME: operators need to have their this be bound somehow since it isn't passed 3010 // OR the op rewrite could pass this 3011 3012 expressions ~= new AssignExpression( 3013 new DotVarExpression(new VariableExpression("__obj"), new VariableExpression("prototype")), 3014 new VariableExpression("__proto")); 3015 3016 auto classIdent = tokens.requireNextToken(ScriptToken.Type.identifier); 3017 3018 expressions ~= new AssignExpression( 3019 new DotVarExpression(new VariableExpression("__proto"), new VariableExpression("__classname")), 3020 new StringLiteralExpression(classIdent.str)); 3021 3022 if(tokens.peekNextToken(ScriptToken.Type.symbol, ":")) { 3023 tokens.popFront(); 3024 auto inheritFrom = tokens.requireNextToken(ScriptToken.Type.identifier); 3025 3026 // we set our prototype to the Foo prototype, thereby inheriting any static data that way (includes functions) 3027 // the inheritFrom object itself carries instance data that we need to copy onto our instance 3028 expressions ~= new AssignExpression( 3029 new DotVarExpression(new VariableExpression("__proto"), new VariableExpression("prototype")), 3030 new DotVarExpression(new VariableExpression(inheritFrom.str), new VariableExpression("prototype"))); 3031 3032 expressions ~= new AssignExpression( 3033 new DotVarExpression(new VariableExpression("__proto"), new VariableExpression("super")), 3034 new VariableExpression(inheritFrom.str) 3035 ); 3036 3037 // and copying the instance initializer from the parent 3038 expressions ~= new ShallowCopyExpression(new VariableExpression("__obj"), new VariableExpression(inheritFrom.str)); 3039 } 3040 3041 tokens.requireNextToken(ScriptToken.Type.symbol, "{"); 3042 3043 void addVarDecl(VariableDeclaration decl, string o) { 3044 foreach(i, ident; decl.identifiers) { 3045 // FIXME: make sure this goes on the instance, never the prototype! 3046 expressions ~= new AssignExpression( 3047 new DotVarExpression( 3048 new VariableExpression(o), 3049 new VariableExpression(ident), 3050 false), 3051 decl.initializers[i], 3052 true // no overloading because otherwise an early opIndexAssign can mess up the decls 3053 ); 3054 } 3055 } 3056 3057 // FIXME: we could actually add private vars and just put them in this scope. maybe 3058 3059 while(!tokens.peekNextToken(ScriptToken.Type.symbol, "}")) { 3060 if(tokens.peekNextToken(ScriptToken.Type.symbol, ";")) { 3061 tokens.popFront(); 3062 continue; 3063 } 3064 3065 if(tokens.peekNextToken(ScriptToken.Type.identifier, "this")) { 3066 // ctor 3067 tokens.popFront(); 3068 tokens.requireNextToken(ScriptToken.Type.symbol, "("); 3069 auto args = parseVariableDeclaration(tokens, ")"); 3070 tokens.requireNextToken(ScriptToken.Type.symbol, ")"); 3071 auto bod = parseExpression(tokens); 3072 3073 expressions ~= new AssignExpression( 3074 new DotVarExpression( 3075 new VariableExpression("__proto"), 3076 new VariableExpression("__ctor")), 3077 new FunctionLiteralExpression(args, bod, staticScopeBacking)); 3078 } else if(tokens.peekNextToken(ScriptToken.Type.keyword, "var")) { 3079 // instance variable 3080 auto decl = parseVariableDeclaration(tokens, ";"); 3081 addVarDecl(decl, "__obj"); 3082 } else if(tokens.peekNextToken(ScriptToken.Type.keyword, "static")) { 3083 // prototype var 3084 tokens.popFront(); 3085 auto decl = parseVariableDeclaration(tokens, ";"); 3086 addVarDecl(decl, "__proto"); 3087 } else if(tokens.peekNextToken(ScriptToken.Type.keyword, "function")) { 3088 // prototype function 3089 tokens.popFront(); 3090 auto ident = tokens.requireNextToken(ScriptToken.Type.identifier); 3091 3092 tokens.requireNextToken(ScriptToken.Type.symbol, "("); 3093 VariableDeclaration args; 3094 if(!tokens.peekNextToken(ScriptToken.Type.symbol, ")")) 3095 args = parseVariableDeclaration(tokens, ")"); 3096 tokens.requireNextToken(ScriptToken.Type.symbol, ")"); 3097 auto bod = parseExpression(tokens); 3098 3099 expressions ~= new FunctionDeclaration( 3100 new DotVarExpression( 3101 new VariableExpression("__proto"), 3102 new VariableExpression(ident.str), 3103 false), 3104 ident.str, 3105 new FunctionLiteralExpression(args, bod, staticScopeBacking) 3106 ); 3107 } else throw new ScriptCompileException("Unexpected " ~ tokens.front.str ~ " when reading class decl", tokens.front.scriptFilename, tokens.front.lineNumber); 3108 } 3109 3110 tokens.requireNextToken(ScriptToken.Type.symbol, "}"); 3111 3112 // returning he object from the scope... 3113 expressions ~= new VariableExpression("__obj"); 3114 3115 auto scopeExpr = new ScopeExpression(expressions); 3116 auto classVarExpr = new VariableDeclaration(); 3117 classVarExpr.identifiers = [classIdent.str]; 3118 classVarExpr.initializers = [scopeExpr]; 3119 3120 ret = classVarExpr; 3121 } else if(tokens.peekNextToken(ScriptToken.Type.keyword, "if")) { 3122 tokens.popFront(); 3123 auto e = new IfExpression(); 3124 e.condition = parseExpression(tokens); 3125 e.ifTrue = parseExpression(tokens); 3126 if(tokens.peekNextToken(ScriptToken.Type.symbol, ";")) { 3127 tokens.popFront(); 3128 } 3129 if(tokens.peekNextToken(ScriptToken.Type.keyword, "else")) { 3130 tokens.popFront(); 3131 e.ifFalse = parseExpression(tokens); 3132 } 3133 ret = e; 3134 } else if(tokens.peekNextToken(ScriptToken.Type.keyword, "switch")) { 3135 tokens.popFront(); 3136 auto e = new SwitchExpression(); 3137 tokens.requireNextToken(ScriptToken.Type.symbol, "("); 3138 e.expr = parseExpression(tokens); 3139 tokens.requireNextToken(ScriptToken.Type.symbol, ")"); 3140 3141 tokens.requireNextToken(ScriptToken.Type.symbol, "{"); 3142 3143 while(!tokens.peekNextToken(ScriptToken.Type.symbol, "}")) { 3144 3145 if(tokens.peekNextToken(ScriptToken.Type.keyword, "case")) { 3146 auto start = tokens.front; 3147 tokens.popFront(); 3148 auto c = new CaseExpression(parseExpression(tokens)); 3149 e.cases ~= c; 3150 tokens.requireNextToken(ScriptToken.Type.symbol, ":"); 3151 3152 while(!tokens.peekNextToken(ScriptToken.Type.keyword, "default") && !tokens.peekNextToken(ScriptToken.Type.keyword, "case") && !tokens.peekNextToken(ScriptToken.Type.symbol, "}")) { 3153 c.expressions ~= parseStatement(tokens); 3154 while(tokens.peekNextToken(ScriptToken.Type.symbol, ";")) 3155 tokens.popFront(); 3156 } 3157 } else if(tokens.peekNextToken(ScriptToken.Type.keyword, "default")) { 3158 tokens.popFront(); 3159 tokens.requireNextToken(ScriptToken.Type.symbol, ":"); 3160 3161 auto c = new CaseExpression(null); 3162 3163 while(!tokens.peekNextToken(ScriptToken.Type.keyword, "case") && !tokens.peekNextToken(ScriptToken.Type.symbol, "}")) { 3164 c.expressions ~= parseStatement(tokens); 3165 while(tokens.peekNextToken(ScriptToken.Type.symbol, ";")) 3166 tokens.popFront(); 3167 } 3168 3169 e.cases ~= c; 3170 e.default_ = c; 3171 } else throw new ScriptCompileException("A switch statement must consists of cases and a default, nothing else ", tokens.front.scriptFilename, tokens.front.lineNumber); 3172 } 3173 3174 tokens.requireNextToken(ScriptToken.Type.symbol, "}"); 3175 expectedEnd = ""; 3176 3177 ret = e; 3178 3179 } else if(tokens.peekNextToken(ScriptToken.Type.keyword, "foreach")) { 3180 tokens.popFront(); 3181 auto e = new ForeachExpression(); 3182 tokens.requireNextToken(ScriptToken.Type.symbol, "("); 3183 e.decl = parseVariableDeclaration(tokens, ";"); 3184 tokens.requireNextToken(ScriptToken.Type.symbol, ";"); 3185 e.subject = parseExpression(tokens); 3186 3187 if(tokens.peekNextToken(ScriptToken.Type.symbol, "..")) { 3188 tokens.popFront; 3189 e.subject2 = parseExpression(tokens); 3190 } 3191 3192 tokens.requireNextToken(ScriptToken.Type.symbol, ")"); 3193 e.loopBody = parseExpression(tokens); 3194 ret = e; 3195 3196 expectedEnd = ""; 3197 } else if(tokens.peekNextToken(ScriptToken.Type.keyword, "cast")) { 3198 tokens.popFront(); 3199 auto e = new CastExpression(); 3200 3201 tokens.requireNextToken(ScriptToken.Type.symbol, "("); 3202 e.type = tokens.requireNextToken(ScriptToken.Type.identifier).str; 3203 if(tokens.peekNextToken(ScriptToken.Type.symbol, "[")) { 3204 e.type ~= "[]"; 3205 tokens.popFront(); 3206 tokens.requireNextToken(ScriptToken.Type.symbol, "]"); 3207 } 3208 tokens.requireNextToken(ScriptToken.Type.symbol, ")"); 3209 3210 e.e1 = parseExpression(tokens); 3211 ret = e; 3212 } else if(tokens.peekNextToken(ScriptToken.Type.keyword, "for")) { 3213 tokens.popFront(); 3214 auto e = new ForExpression(); 3215 tokens.requireNextToken(ScriptToken.Type.symbol, "("); 3216 e.initialization = parseStatement(tokens, ";"); 3217 3218 tokens.requireNextToken(ScriptToken.Type.symbol, ";"); 3219 3220 e.condition = parseExpression(tokens); 3221 tokens.requireNextToken(ScriptToken.Type.symbol, ";"); 3222 e.advancement = parseExpression(tokens); 3223 tokens.requireNextToken(ScriptToken.Type.symbol, ")"); 3224 e.loopBody = parseExpression(tokens); 3225 3226 ret = e; 3227 3228 expectedEnd = ""; 3229 } else if(tokens.peekNextToken(ScriptToken.Type.keyword, "while")) { 3230 tokens.popFront(); 3231 auto e = new ForExpression(); 3232 e.condition = parseExpression(tokens); 3233 e.loopBody = parseExpression(tokens); 3234 ret = e; 3235 expectedEnd = ""; 3236 } else if(tokens.peekNextToken(ScriptToken.Type.keyword, "break") || tokens.peekNextToken(ScriptToken.Type.keyword, "continue")) { 3237 auto token = tokens.front; 3238 tokens.popFront(); 3239 ret = new LoopControlExpression(token.str); 3240 } else if(tokens.peekNextToken(ScriptToken.Type.keyword, "return")) { 3241 tokens.popFront(); 3242 Expression retVal; 3243 if(tokens.peekNextToken(ScriptToken.Type.symbol, ";")) 3244 retVal = new NullLiteralExpression(); 3245 else 3246 retVal = parseExpression(tokens); 3247 ret = new ReturnExpression(retVal); 3248 } else if(tokens.peekNextToken(ScriptToken.Type.keyword, "throw")) { 3249 auto token = tokens.front; 3250 tokens.popFront(); 3251 ret = new ThrowExpression(parseExpression(tokens), token); 3252 } else if(tokens.peekNextToken(ScriptToken.Type.keyword, "try")) { 3253 auto tryToken = tokens.front; 3254 auto e = new ExceptionBlockExpression(); 3255 tokens.popFront(); 3256 e.tryExpression = parseExpression(tokens, true); 3257 3258 bool hadFinally = false; 3259 while(tokens.peekNextToken(ScriptToken.Type.keyword, "catch")) { 3260 if(hadFinally) 3261 throw new ScriptCompileException("Catch must come before finally", tokens.front.scriptFilename, tokens.front.lineNumber); 3262 tokens.popFront(); 3263 tokens.requireNextToken(ScriptToken.Type.symbol, "("); 3264 if(tokens.peekNextToken(ScriptToken.Type.keyword, "var")) 3265 tokens.popFront(); 3266 3267 auto ident = tokens.requireNextToken(ScriptToken.Type.identifier); 3268 if(tokens.empty) throw new ScriptCompileException("Catch specifier not closed", ident.scriptFilename, ident.lineNumber); 3269 auto next = tokens.front; 3270 if(next.type == ScriptToken.Type.identifier) { 3271 auto type = ident; 3272 ident = next; 3273 3274 e.catchVarTypeSpecifiers ~= type.str; 3275 e.catchVarDecls ~= ident.str; 3276 3277 tokens.popFront(); 3278 3279 tokens.requireNextToken(ScriptToken.Type.symbol, ")"); 3280 } else { 3281 e.catchVarTypeSpecifiers ~= null; 3282 e.catchVarDecls ~= ident.str; 3283 if(next.type != ScriptToken.Type.symbol || next.str != ")") 3284 throw new ScriptCompileException("ss Unexpected " ~ next.str ~ " when expecting ')'", next.scriptFilename, next.lineNumber); 3285 tokens.popFront(); 3286 } 3287 e.catchExpressions ~= parseExpression(tokens); 3288 } 3289 while(tokens.peekNextToken(ScriptToken.Type.keyword, "finally")) { 3290 hadFinally = true; 3291 tokens.popFront(); 3292 e.finallyExpressions ~= parseExpression(tokens); 3293 } 3294 3295 //if(!hadSomething) 3296 //throw new ScriptCompileException("Parse error, missing finally or catch after try", tryToken.lineNumber); 3297 3298 ret = e; 3299 } else { 3300 ret = parseAddend(tokens); 3301 } 3302 3303 if(!tokens.empty && tokens.peekNextToken(ScriptToken.Type.symbol, "?")) { 3304 auto e = new TernaryExpression(); 3305 e.condition = ret; 3306 tokens.requireNextToken(ScriptToken.Type.symbol, "?"); 3307 e.ifTrue = parseExpression(tokens); 3308 tokens.requireNextToken(ScriptToken.Type.symbol, ":"); 3309 e.ifFalse = parseExpression(tokens); 3310 ret = e; 3311 } 3312 } else { 3313 //assert(0); 3314 // return null; 3315 throw new ScriptCompileException("Parse error, unexpected end of input when reading expression", null, 0);//token.lineNumber); 3316 } 3317 3318 //writeln("parsed expression ", ret.toString()); 3319 3320 if(expectedEnd.length && tokens.empty && consumeEnd) // going loose on final ; at the end of input for repl convenience 3321 throw new ScriptCompileException("Parse error, unexpected end of input when reading expression, expecting " ~ expectedEnd, first.scriptFilename, first.lineNumber); 3322 3323 if(expectedEnd.length && consumeEnd) { 3324 if(tokens.peekNextToken(ScriptToken.Type.symbol, expectedEnd)) 3325 tokens.popFront(); 3326 // FIXME 3327 //if(tokens.front.type != ScriptToken.Type.symbol && tokens.front.str != expectedEnd) 3328 //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); 3329 // tokens = tokens[1 .. $]; 3330 } 3331 3332 return ret; 3333 } 3334 3335 VariableDeclaration parseVariableDeclaration(MyTokenStreamHere)(ref MyTokenStreamHere tokens, string termination) { 3336 VariableDeclaration decl = new VariableDeclaration(); 3337 bool equalOk; 3338 anotherVar: 3339 assert(!tokens.empty); 3340 3341 auto firstToken = tokens.front; 3342 3343 // var a, var b is acceptable 3344 if(tokens.peekNextToken(ScriptToken.Type.keyword, "var")) 3345 tokens.popFront(); 3346 3347 equalOk= true; 3348 if(tokens.empty) 3349 throw new ScriptCompileException("Parse error, dangling var at end of file", firstToken.scriptFilename, firstToken.lineNumber); 3350 3351 string type; 3352 3353 auto next = tokens.front; 3354 tokens.popFront; 3355 if(tokens.empty) 3356 throw new ScriptCompileException("Parse error, incomplete var declaration at end of file", firstToken.scriptFilename, firstToken.lineNumber); 3357 auto next2 = tokens.front; 3358 3359 ScriptToken typeSpecifier; 3360 3361 /* if there's two identifiers back to back, it is a type specifier. otherwise just a name */ 3362 3363 if(next.type == ScriptToken.Type.identifier && next2.type == ScriptToken.Type.identifier) { 3364 // type ident; 3365 typeSpecifier = next; 3366 next = next2; 3367 // get past the type 3368 tokens.popFront(); 3369 } else { 3370 // no type, just carry on with the next thing 3371 } 3372 3373 Expression initializer; 3374 auto identifier = next; 3375 if(identifier.type != ScriptToken.Type.identifier) 3376 throw new ScriptCompileException("Parse error, found '"~identifier.str~"' when expecting var identifier", identifier.scriptFilename, identifier.lineNumber); 3377 3378 //tokens.popFront(); 3379 3380 tryTermination: 3381 if(tokens.empty) 3382 throw new ScriptCompileException("Parse error, missing ; after var declaration at end of file", firstToken.scriptFilename, firstToken.lineNumber); 3383 3384 auto peek = tokens.front; 3385 if(peek.type == ScriptToken.Type.symbol) { 3386 if(peek.str == "=") { 3387 if(!equalOk) 3388 throw new ScriptCompileException("Parse error, unexpected '"~identifier.str~"' after reading var initializer", peek.scriptFilename, peek.lineNumber); 3389 equalOk = false; 3390 tokens.popFront(); 3391 initializer = parseExpression(tokens); 3392 goto tryTermination; 3393 } else if(peek.str == ",") { 3394 tokens.popFront(); 3395 decl.identifiers ~= identifier.str; 3396 decl.initializers ~= initializer; 3397 decl.typeSpecifiers ~= typeSpecifier.str; 3398 goto anotherVar; 3399 } else if(peek.str == termination) { 3400 decl.identifiers ~= identifier.str; 3401 decl.initializers ~= initializer; 3402 decl.typeSpecifiers ~= typeSpecifier.str; 3403 //tokens = tokens[1 .. $]; 3404 // we're done! 3405 } else 3406 throw new ScriptCompileException("Parse error, unexpected '"~peek.str~"' when reading var declaration symbol", peek.scriptFilename, peek.lineNumber); 3407 } else 3408 throw new ScriptCompileException("Parse error, unexpected non-symbol '"~peek.str~"' when reading var declaration", peek.scriptFilename, peek.lineNumber); 3409 3410 return decl; 3411 } 3412 3413 Expression parseStatement(MyTokenStreamHere)(ref MyTokenStreamHere tokens, string terminatingSymbol = null) { 3414 skip: // FIXME 3415 if(tokens.empty) 3416 return null; 3417 3418 if(terminatingSymbol !is null && (tokens.front.type == ScriptToken.Type.symbol && tokens.front.str == terminatingSymbol)) 3419 return null; // we're done 3420 3421 auto token = tokens.front; 3422 3423 // tokens = tokens[1 .. $]; 3424 final switch(token.type) { 3425 case ScriptToken.Type.keyword: 3426 case ScriptToken.Type.symbol: 3427 switch(token.str) { 3428 // assert 3429 case "assert": 3430 tokens.popFront(); 3431 3432 return parseFunctionCall(tokens, new AssertKeyword(token)); 3433 3434 //break; 3435 // declarations 3436 case "var": 3437 return parseVariableDeclaration(tokens, ";"); 3438 case ";": 3439 tokens.popFront(); // FIXME 3440 goto skip; 3441 // literals 3442 case "function": 3443 case "macro": 3444 // function can be a literal, or a declaration. 3445 3446 tokens.popFront(); // we're peeking ahead 3447 3448 if(tokens.peekNextToken(ScriptToken.Type.identifier)) { 3449 // decl style, rewrite it into var ident = function style 3450 // tokens.popFront(); // skipping the function keyword // already done above with the popFront 3451 auto ident = tokens.front; 3452 tokens.popFront(); 3453 3454 tokens.requireNextToken(ScriptToken.Type.symbol, "("); 3455 3456 auto exp = new FunctionLiteralExpression(); 3457 if(!tokens.peekNextToken(ScriptToken.Type.symbol, ")")) 3458 exp.arguments = parseVariableDeclaration(tokens, ")"); 3459 tokens.requireNextToken(ScriptToken.Type.symbol, ")"); 3460 3461 exp.functionBody = parseExpression(tokens); 3462 3463 // a ; should NOT be required here btw 3464 3465 exp.isMacro = token.str == "macro"; 3466 3467 auto e = new FunctionDeclaration(null, ident.str, exp); 3468 3469 return e; 3470 3471 } else { 3472 tokens.pushFront(token); // put it back since everyone expects us to have done that 3473 goto case; // handle it like any other expression 3474 } 3475 3476 case "true": 3477 case "false": 3478 3479 case "json!{": 3480 case "#{": 3481 case "[": 3482 case "(": 3483 case "null": 3484 3485 // scope 3486 case "{": 3487 case "scope": 3488 3489 case "cast": 3490 3491 // classes 3492 case "class": 3493 case "new": 3494 3495 case "super": 3496 3497 // flow control 3498 case "if": 3499 case "while": 3500 case "for": 3501 case "foreach": 3502 case "switch": 3503 3504 // exceptions 3505 case "try": 3506 case "throw": 3507 3508 // evals 3509 case "eval": 3510 case "mixin": 3511 3512 // flow 3513 case "continue": 3514 case "break": 3515 case "return": 3516 return parseExpression(tokens); 3517 // unary prefix operators 3518 case "!": 3519 case "~": 3520 case "-": 3521 return parseExpression(tokens); 3522 3523 // BTW add custom object operator overloading to struct var 3524 // and custom property overloading to PrototypeObject 3525 3526 default: 3527 // whatever else keyword or operator related is actually illegal here 3528 throw new ScriptCompileException("Parse error, unexpected " ~ token.str, token.scriptFilename, token.lineNumber); 3529 } 3530 // break; 3531 case ScriptToken.Type.identifier: 3532 case ScriptToken.Type..string: 3533 case ScriptToken.Type.int_number: 3534 case ScriptToken.Type.float_number: 3535 return parseExpression(tokens); 3536 } 3537 3538 assert(0); 3539 } 3540 3541 // FIXME someday this should work, my parser is so bad 3542 // until then put parens around your == stuff. 3543 version(none) 3544 unittest { 3545 interpret(q{ 3546 var a = 5; 3547 var b = false; 3548 assert(a == 5 || b); 3549 }); 3550 } 3551 version(none) 3552 unittest { 3553 interpret(q{ 3554 var a = 5; 3555 var b = false; 3556 assert(((a == 5) || b)); 3557 }); 3558 } 3559 3560 3561 struct CompoundStatementRange(MyTokenStreamHere) { 3562 // FIXME: if MyTokenStreamHere is not a class, this fails! 3563 MyTokenStreamHere tokens; 3564 int startingLine; 3565 string terminatingSymbol; 3566 bool isEmpty; 3567 3568 this(MyTokenStreamHere t, int startingLine, string terminatingSymbol) { 3569 tokens = t; 3570 this.startingLine = startingLine; 3571 this.terminatingSymbol = terminatingSymbol; 3572 popFront(); 3573 } 3574 3575 bool empty() { 3576 return isEmpty; 3577 } 3578 3579 Expression got; 3580 3581 Expression front() { 3582 return got; 3583 } 3584 3585 void popFront() { 3586 while(!tokens.empty && (terminatingSymbol is null || !(tokens.front.type == ScriptToken.Type.symbol && tokens.front.str == terminatingSymbol))) { 3587 auto n = parseStatement(tokens, terminatingSymbol); 3588 if(n is null) 3589 continue; 3590 got = n; 3591 return; 3592 } 3593 3594 if(tokens.empty && terminatingSymbol !is null) { 3595 throw new ScriptCompileException("Reached end of file while trying to reach matching " ~ terminatingSymbol, null, startingLine); 3596 } 3597 3598 if(terminatingSymbol !is null) { 3599 assert(tokens.front.str == terminatingSymbol); 3600 tokens.skipNext++; 3601 } 3602 3603 isEmpty = true; 3604 } 3605 } 3606 3607 CompoundStatementRange!MyTokenStreamHere 3608 //Expression[] 3609 parseCompoundStatement(MyTokenStreamHere)(ref MyTokenStreamHere tokens, int startingLine = 1, string terminatingSymbol = null) { 3610 return (CompoundStatementRange!MyTokenStreamHere(tokens, startingLine, terminatingSymbol)); 3611 } 3612 3613 auto parseScript(MyTokenStreamHere)(MyTokenStreamHere tokens) { 3614 /* 3615 the language's grammar is simple enough 3616 3617 maybe flow control should be statements though lol. they might not make sense inside. 3618 3619 Expressions: 3620 var identifier; 3621 var identifier = initializer; 3622 var identifier, identifier2 3623 3624 return expression; 3625 return ; 3626 3627 json!{ object literal } 3628 3629 { scope expression } 3630 3631 [ array literal ] 3632 other literal 3633 function (arg list) other expression 3634 3635 ( expression ) // parenthesized expression 3636 operator expression // unary expression 3637 3638 expression operator expression // binary expression 3639 expression (other expression... args) // function call 3640 3641 Binary Operator precedence : 3642 . [] 3643 * / 3644 + - 3645 ~ 3646 < > == != 3647 = 3648 */ 3649 3650 return parseCompoundStatement(tokens); 3651 } 3652 3653 var interpretExpressions(ExpressionStream)(ExpressionStream expressions, PrototypeObject variables) if(is(ElementType!ExpressionStream == Expression)) { 3654 assert(variables !is null); 3655 var ret; 3656 foreach(expression; expressions) { 3657 auto res = expression.interpret(variables); 3658 variables = res.sc; 3659 ret = res.value; 3660 } 3661 return ret; 3662 } 3663 3664 var interpretStream(MyTokenStreamHere)(MyTokenStreamHere tokens, PrototypeObject variables) if(is(ElementType!MyTokenStreamHere == ScriptToken)) { 3665 assert(variables !is null); 3666 // this is an entry point that all others lead to, right before getting to interpretExpressions... 3667 3668 return interpretExpressions(parseScript(tokens), variables); 3669 } 3670 3671 var interpretStream(MyTokenStreamHere)(MyTokenStreamHere tokens, var variables) if(is(ElementType!MyTokenStreamHere == ScriptToken)) { 3672 return interpretStream(tokens, 3673 (variables.payloadType() == var.Type.Object && variables._payload._object !is null) ? variables._payload._object : new PrototypeObject()); 3674 } 3675 3676 var interpret(string code, PrototypeObject variables, string scriptFilename = null) { 3677 assert(variables !is null); 3678 return interpretStream(lexScript(repeat(code, 1), scriptFilename), variables); 3679 } 3680 3681 /++ 3682 This is likely your main entry point to the interpreter. It will interpret the script code 3683 given, with the given global variable object (which will be modified by the script, meaning 3684 you can pass it to subsequent calls to `interpret` to store context), and return the result 3685 of the last expression given. 3686 3687 --- 3688 var globals = var.emptyObject; // the global object must be an object of some type 3689 globals.x = 10; 3690 globals.y = 15; 3691 // you can also set global functions through this same style, etc 3692 3693 var result = interpret(`x + y`, globals); 3694 assert(result == 25); 3695 --- 3696 3697 3698 $(TIP 3699 If you want to just call a script function, interpret the definition of it, 3700 then just call it through the `globals` object you passed to it. 3701 3702 --- 3703 var globals = var.emptyObject; 3704 interpret(`function foo(name) { return "hello, " ~ name ~ "!"; }`, globals); 3705 var result = globals.foo()("world"); 3706 assert(result == "hello, world!"); 3707 --- 3708 ) 3709 3710 Params: 3711 code = the script source code you want to interpret 3712 scriptFilename = the filename of the script, if you want to provide it. Gives nicer error messages if you provide one. 3713 variables = The global object of the script context. It will be modified by the user script. 3714 3715 Returns: 3716 the result of the last expression evaluated by the script engine 3717 +/ 3718 var interpret(string code, var variables = null, string scriptFilename = null, string file = __FILE__, size_t line = __LINE__) { 3719 if(scriptFilename is null) 3720 scriptFilename = file ~ "@" ~ to!string(line); 3721 return interpretStream( 3722 lexScript(repeat(code, 1), scriptFilename), 3723 (variables.payloadType() == var.Type.Object && variables._payload._object !is null) ? variables._payload._object : new PrototypeObject()); 3724 } 3725 3726 /// 3727 var interpretFile(File file, var globals) { 3728 import std.algorithm; 3729 return interpretStream(lexScript(file.byLine.map!((a) => a.idup), file.name), 3730 (globals.payloadType() == var.Type.Object && globals._payload._object !is null) ? globals._payload._object : new PrototypeObject()); 3731 } 3732 3733 /// Enhanced repl uses arsd.terminal for better ux. Added April 26, 2020. Default just uses std.stdio. 3734 void repl(bool enhanced = false)(var globals) { 3735 static if(enhanced) { 3736 import arsd.terminal; 3737 Terminal terminal = Terminal(ConsoleOutputMode.linear); 3738 auto lines() { 3739 struct Range { 3740 string line; 3741 string front() { return line; } 3742 bool empty() { return line is null; } 3743 void popFront() { line = terminal.getline(": "); terminal.writeln(); } 3744 } 3745 Range r; 3746 r.popFront(); 3747 return r; 3748 3749 } 3750 3751 void writeln(T...)(T t) { 3752 terminal.writeln(t); 3753 terminal.flush(); 3754 } 3755 } else { 3756 import std.stdio; 3757 auto lines() { return stdin.byLine; } 3758 } 3759 3760 bool exited; 3761 if(globals == null) 3762 globals = var.emptyObject; 3763 globals.exit = () { exited = true; }; 3764 3765 import std.algorithm; 3766 auto variables = (globals.payloadType() == var.Type.Object && globals._payload._object !is null) ? globals._payload._object : new PrototypeObject(); 3767 3768 // we chain to ensure the priming popFront succeeds so we don't throw here 3769 auto tokens = lexScript( 3770 chain(["var __skipme = 0;"], map!((a) => a.idup)(lines)) 3771 , "stdin"); 3772 auto expressions = parseScript(tokens); 3773 3774 while(!exited && !expressions.empty) { 3775 try { 3776 expressions.popFront; 3777 auto expression = expressions.front; 3778 auto res = expression.interpret(variables); 3779 variables = res.sc; 3780 writeln(">>> ", res.value); 3781 } catch(ScriptCompileException e) { 3782 writeln("*+* ", e.msg); 3783 tokens.popFront(); // skip the one we threw on... 3784 } catch(Exception e) { 3785 writeln("*** ", e.msg); 3786 } 3787 } 3788 } 3789 3790 class ScriptFunctionMetadata : VarMetadata { 3791 FunctionLiteralExpression fle; 3792 this(FunctionLiteralExpression fle) { 3793 this.fle = fle; 3794 } 3795 3796 string convertToString() { 3797 return fle.toString(); 3798 } 3799 }