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