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