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