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