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 return parenthetical; 2718 case "[": 2719 // array literal 2720 auto arr = new ArrayLiteralExpression(); 2721 2722 bool first = true; 2723 moreElements: 2724 if(tokens.empty) 2725 throw new ScriptCompileException("unexpected end of file when reading array literal", token.scriptFilename, token.lineNumber); 2726 2727 auto peek = tokens.front; 2728 if(peek.type == ScriptToken.Type.symbol && peek.str == "]") { 2729 tokens.popFront(); 2730 return arr; 2731 } 2732 2733 if(!first) 2734 tokens.requireNextToken(ScriptToken.Type.symbol, ","); 2735 else 2736 first = false; 2737 2738 arr.elements ~= parseExpression(tokens); 2739 2740 goto moreElements; 2741 case "json!q{": 2742 case "#{": 2743 // json object literal 2744 auto obj = new ObjectLiteralExpression(); 2745 /* 2746 these go 2747 2748 string or ident which is the key 2749 then a colon 2750 then an expression which is the value 2751 2752 then optionally a comma 2753 2754 then either } which finishes it, or another key 2755 */ 2756 2757 if(tokens.empty) 2758 throw new ScriptCompileException("unexpected end of file when reading object literal", token.scriptFilename, token.lineNumber); 2759 2760 moreKeys: 2761 auto key = tokens.front; 2762 tokens.popFront(); 2763 if(key.type == ScriptToken.Type.symbol && key.str == "}") { 2764 // all done! 2765 e = obj; 2766 break; 2767 } 2768 if(key.type != ScriptToken.Type..string && key.type != ScriptToken.Type.identifier) { 2769 throw new ScriptCompileException("unexpected '"~key.str~"' when reading object literal", key.scriptFilename, key.lineNumber); 2770 2771 } 2772 2773 tokens.requireNextToken(ScriptToken.Type.symbol, ":"); 2774 2775 auto value = parseExpression(tokens); 2776 if(tokens.empty) 2777 throw new ScriptCompileException("unclosed object literal", key.scriptFilename, key.lineNumber); 2778 2779 if(tokens.peekNextToken(ScriptToken.Type.symbol, ",")) 2780 tokens.popFront(); 2781 2782 obj.elements[key.str] = value; 2783 2784 goto moreKeys; 2785 case "macro": 2786 case "function": 2787 tokens.requireNextToken(ScriptToken.Type.symbol, "("); 2788 2789 auto exp = new FunctionLiteralExpression(); 2790 if(!tokens.peekNextToken(ScriptToken.Type.symbol, ")")) 2791 exp.arguments = parseVariableDeclaration(tokens, ")"); 2792 2793 tokens.requireNextToken(ScriptToken.Type.symbol, ")"); 2794 2795 exp.functionBody = parseExpression(tokens); 2796 exp.isMacro = token.str == "macro"; 2797 2798 e = exp; 2799 break; 2800 case "null": 2801 e = new NullLiteralExpression(); 2802 break; 2803 case "mixin": 2804 case "eval": 2805 tokens.requireNextToken(ScriptToken.Type.symbol, "("); 2806 e = new MixinExpression(parseExpression(tokens)); 2807 tokens.requireNextToken(ScriptToken.Type.symbol, ")"); 2808 break; 2809 default: 2810 goto unknown; 2811 } 2812 } else { 2813 unknown: 2814 throw new ScriptCompileException("unexpected '"~token.str~"' when reading ident", token.scriptFilename, token.lineNumber); 2815 } 2816 } 2817 2818 funcLoop: while(!tokens.empty) { 2819 auto peek = tokens.front; 2820 if(peek.type == ScriptToken.Type.symbol) { 2821 switch(peek.str) { 2822 case "(": 2823 e = parseFunctionCall(tokens, e); 2824 break; 2825 case "[": 2826 tokens.popFront(); 2827 auto e1 = parseExpression(tokens); 2828 if(tokens.peekNextToken(ScriptToken.Type.symbol, "..")) { 2829 tokens.popFront(); 2830 e = new SliceExpression(e, e1, parseExpression(tokens)); 2831 } else { 2832 e = new IndexExpression(e, e1); 2833 } 2834 tokens.requireNextToken(ScriptToken.Type.symbol, "]"); 2835 break; 2836 case ".": 2837 tokens.popFront(); 2838 e = new DotVarExpression(e, parseVariableName(tokens)); 2839 break; 2840 default: 2841 return e; // we don't know, punt it elsewhere 2842 } 2843 } else return e; // again, we don't know, so just punt it down the line 2844 } 2845 return e; 2846 } 2847 2848 throw new ScriptCompileException("Ran out of tokens when trying to parsePart", null, 0); 2849 } 2850 2851 Expression parseArguments(MyTokenStreamHere)(ref MyTokenStreamHere tokens, Expression exp, ref Expression[] where) { 2852 // arguments. 2853 auto peek = tokens.front; 2854 if(peek.type == ScriptToken.Type.symbol && peek.str == ")") { 2855 tokens.popFront(); 2856 return exp; 2857 } 2858 2859 moreArguments: 2860 2861 if(tokens.peekNextToken(ScriptToken.Type.keyword, "default")) { 2862 tokens.popFront(); 2863 where ~= null; 2864 } else { 2865 where ~= parseExpression(tokens); 2866 } 2867 2868 if(tokens.empty) 2869 throw new ScriptCompileException("unexpected end of file when parsing call expression", peek.scriptFilename, peek.lineNumber); 2870 peek = tokens.front; 2871 if(peek.type == ScriptToken.Type.symbol && peek.str == ",") { 2872 tokens.popFront(); 2873 goto moreArguments; 2874 } else if(peek.type == ScriptToken.Type.symbol && peek.str == ")") { 2875 tokens.popFront(); 2876 return exp; 2877 } else 2878 throw new ScriptCompileException("unexpected '"~peek.str~"' when reading argument list", peek.scriptFilename, peek.lineNumber); 2879 2880 } 2881 2882 Expression parseFunctionCall(MyTokenStreamHere)(ref MyTokenStreamHere tokens, Expression e) { 2883 assert(!tokens.empty); 2884 auto peek = tokens.front; 2885 auto exp = new CallExpression(ScriptLocation(peek.scriptFilename, peek.lineNumber), e); 2886 tokens.popFront(); 2887 if(tokens.empty) 2888 throw new ScriptCompileException("unexpected end of file when parsing call expression", peek.scriptFilename, peek.lineNumber); 2889 return parseArguments(tokens, exp, exp.arguments); 2890 } 2891 2892 Expression parseFactor(MyTokenStreamHere)(ref MyTokenStreamHere tokens) { 2893 auto e1 = parsePart(tokens); 2894 loop: while(!tokens.empty) { 2895 auto peek = tokens.front; 2896 2897 if(peek.type == ScriptToken.Type.symbol) { 2898 switch(peek.str) { 2899 case "<<": 2900 case ">>": 2901 case ">>>": 2902 case "*": 2903 case "/": 2904 case "%": 2905 tokens.popFront(); 2906 e1 = new BinaryExpression(peek.str, e1, parsePart(tokens)); 2907 break; 2908 default: 2909 break loop; 2910 } 2911 } else throw new Exception("Got " ~ peek.str ~ " when expecting symbol"); 2912 } 2913 2914 return e1; 2915 } 2916 2917 Expression parseAddend(MyTokenStreamHere)(ref MyTokenStreamHere tokens) { 2918 auto e1 = parseFactor(tokens); 2919 loop: while(!tokens.empty) { 2920 auto peek = tokens.front; 2921 2922 if(peek.type == ScriptToken.Type.symbol) { 2923 switch(peek.str) { 2924 case "..": // possible FIXME 2925 case ")": // possible FIXME 2926 case "]": // possible FIXME 2927 case "}": // possible FIXME 2928 case ",": // possible FIXME these are passed on to the next thing 2929 case ";": 2930 case ":": // idk 2931 case "?": 2932 return e1; 2933 2934 case "|>": 2935 tokens.popFront(); 2936 e1 = new PipelineExpression(ScriptLocation(peek.scriptFilename, peek.lineNumber), e1, parseFactor(tokens)); 2937 break; 2938 case ".": 2939 tokens.popFront(); 2940 e1 = new DotVarExpression(e1, parseVariableName(tokens)); 2941 break; 2942 case "=": 2943 tokens.popFront(); 2944 return new AssignExpression(e1, parseExpression(tokens)); 2945 case "&&": // thanks to mzfhhhh for fix 2946 case "||": 2947 tokens.popFront(); 2948 e1 = new BinaryExpression(peek.str, e1, parseExpression(tokens)); 2949 break; 2950 case "~": 2951 // FIXME: make sure this has the right associativity 2952 2953 case "&": 2954 case "|": 2955 case "^": 2956 2957 case "&=": 2958 case "|=": 2959 case "^=": 2960 2961 case "+": 2962 case "-": 2963 2964 case "==": 2965 case "!=": 2966 case "<=": 2967 case ">=": 2968 case "<": 2969 case ">": 2970 tokens.popFront(); 2971 e1 = new BinaryExpression(peek.str, e1, parseAddend(tokens)); 2972 break; 2973 case "+=": 2974 case "-=": 2975 case "*=": 2976 case "/=": 2977 case "~=": 2978 case "%=": 2979 tokens.popFront(); 2980 return new OpAssignExpression(peek.str[0..1], e1, parseExpression(tokens)); 2981 default: 2982 throw new ScriptCompileException("Parse error, unexpected " ~ peek.str ~ " when looking for operator", peek.scriptFilename, peek.lineNumber); 2983 } 2984 //} else if(peek.type == ScriptToken.Type.identifier || peek.type == ScriptToken.Type.number) { 2985 //return parseFactor(tokens); 2986 } else 2987 throw new ScriptCompileException("Parse error, unexpected '" ~ peek.str ~ "'", peek.scriptFilename, peek.lineNumber); 2988 } 2989 2990 return e1; 2991 } 2992 2993 Expression parseExpression(MyTokenStreamHere)(ref MyTokenStreamHere tokens, bool consumeEnd = false) { 2994 Expression ret; 2995 ScriptToken first; 2996 string expectedEnd = ";"; 2997 //auto e1 = parseFactor(tokens); 2998 2999 while(tokens.peekNextToken(ScriptToken.Type.symbol, ";")) { 3000 tokens.popFront(); 3001 } 3002 if(!tokens.empty) { 3003 first = tokens.front; 3004 if(tokens.peekNextToken(ScriptToken.Type.symbol, "{")) { 3005 auto start = tokens.front; 3006 tokens.popFront(); 3007 auto e = parseCompoundStatement(tokens, start.lineNumber, "}").array; 3008 ret = new ScopeExpression(e); 3009 expectedEnd = null; // {} don't need ; at the end 3010 } else if(tokens.peekNextToken(ScriptToken.Type.keyword, "scope")) { 3011 auto start = tokens.front; 3012 tokens.popFront(); 3013 tokens.requireNextToken(ScriptToken.Type.symbol, "("); 3014 3015 auto ident = tokens.requireNextToken(ScriptToken.Type.identifier); 3016 switch(ident.str) { 3017 case "success": 3018 case "failure": 3019 case "exit": 3020 break; 3021 default: 3022 throw new ScriptCompileException("unexpected " ~ ident.str ~ ". valid scope(idents) are success, failure, and exit", ident.scriptFilename, ident.lineNumber); 3023 } 3024 3025 tokens.requireNextToken(ScriptToken.Type.symbol, ")"); 3026 3027 string i = "__scope_" ~ ident.str; 3028 auto literal = new FunctionLiteralExpression(); 3029 literal.functionBody = parseExpression(tokens); 3030 3031 auto e = new OpAssignExpression("~", new VariableExpression(i), literal); 3032 ret = e; 3033 } else if(tokens.peekNextToken(ScriptToken.Type.symbol, "(")) { 3034 auto start = tokens.front; 3035 tokens.popFront(); 3036 auto parenthetical = new ParentheticalExpression(parseExpression(tokens)); 3037 tokens.requireNextToken(ScriptToken.Type.symbol, ")"); 3038 if(tokens.peekNextToken(ScriptToken.Type.symbol, "(")) { 3039 // we have a function call, e.g. (test)() 3040 ret = parseFunctionCall(tokens, parenthetical); 3041 } else 3042 ret = parenthetical; 3043 } else if(tokens.peekNextToken(ScriptToken.Type.keyword, "new")) { 3044 auto start = tokens.front; 3045 tokens.popFront(); 3046 3047 auto expr = parseDottedVariableName(tokens); 3048 auto ne = new NewExpression(expr); 3049 if(tokens.peekNextToken(ScriptToken.Type.symbol, "(")) { 3050 tokens.popFront(); 3051 parseArguments(tokens, ne, ne.args); 3052 } 3053 3054 ret = ne; 3055 } else if(tokens.peekNextToken(ScriptToken.Type.keyword, "class")) { 3056 auto start = tokens.front; 3057 tokens.popFront(); 3058 3059 Expression[] expressions; 3060 3061 // the way classes work is they are actually object literals with a different syntax. new foo then just copies it 3062 /* 3063 we create a prototype object 3064 we create an object, with that prototype 3065 3066 set all functions and static stuff to the prototype 3067 the rest goes to the object 3068 3069 the expression returns the object we made 3070 */ 3071 3072 auto vars = new VariableDeclaration(); 3073 vars.identifiers = ["__proto", "__obj"]; 3074 3075 auto staticScopeBacking = new PrototypeObject(); 3076 auto instanceScopeBacking = new PrototypeObject(); 3077 3078 vars.initializers = [new ObjectLiteralExpression(staticScopeBacking), new ObjectLiteralExpression(instanceScopeBacking)]; 3079 expressions ~= vars; 3080 3081 // FIXME: operators need to have their this be bound somehow since it isn't passed 3082 // OR the op rewrite could pass this 3083 3084 expressions ~= new AssignExpression( 3085 new DotVarExpression(new VariableExpression("__obj"), new VariableExpression("prototype")), 3086 new VariableExpression("__proto")); 3087 3088 auto classIdent = tokens.requireNextToken(ScriptToken.Type.identifier); 3089 3090 expressions ~= new AssignExpression( 3091 new DotVarExpression(new VariableExpression("__proto"), new VariableExpression("__classname")), 3092 new StringLiteralExpression(classIdent.str)); 3093 3094 if(tokens.peekNextToken(ScriptToken.Type.symbol, ":")) { 3095 tokens.popFront(); 3096 auto inheritFrom = tokens.requireNextToken(ScriptToken.Type.identifier); 3097 3098 // we set our prototype to the Foo prototype, thereby inheriting any static data that way (includes functions) 3099 // the inheritFrom object itself carries instance data that we need to copy onto our instance 3100 expressions ~= new AssignExpression( 3101 new DotVarExpression(new VariableExpression("__proto"), new VariableExpression("prototype")), 3102 new DotVarExpression(new VariableExpression(inheritFrom.str), new VariableExpression("prototype"))); 3103 3104 expressions ~= new AssignExpression( 3105 new DotVarExpression(new VariableExpression("__proto"), new VariableExpression("super")), 3106 new VariableExpression(inheritFrom.str) 3107 ); 3108 3109 // and copying the instance initializer from the parent 3110 expressions ~= new ShallowCopyExpression(new VariableExpression("__obj"), new VariableExpression(inheritFrom.str)); 3111 } 3112 3113 tokens.requireNextToken(ScriptToken.Type.symbol, "{"); 3114 3115 void addVarDecl(VariableDeclaration decl, string o) { 3116 foreach(i, ident; decl.identifiers) { 3117 // FIXME: make sure this goes on the instance, never the prototype! 3118 expressions ~= new AssignExpression( 3119 new DotVarExpression( 3120 new VariableExpression(o), 3121 new VariableExpression(ident), 3122 false), 3123 decl.initializers[i], 3124 true // no overloading because otherwise an early opIndexAssign can mess up the decls 3125 ); 3126 } 3127 } 3128 3129 // FIXME: we could actually add private vars and just put them in this scope. maybe 3130 3131 while(!tokens.peekNextToken(ScriptToken.Type.symbol, "}")) { 3132 if(tokens.peekNextToken(ScriptToken.Type.symbol, ";")) { 3133 tokens.popFront(); 3134 continue; 3135 } 3136 3137 if(tokens.peekNextToken(ScriptToken.Type.identifier, "this")) { 3138 // ctor 3139 tokens.popFront(); 3140 tokens.requireNextToken(ScriptToken.Type.symbol, "("); 3141 auto args = parseVariableDeclaration(tokens, ")"); 3142 tokens.requireNextToken(ScriptToken.Type.symbol, ")"); 3143 auto bod = parseExpression(tokens); 3144 3145 expressions ~= new AssignExpression( 3146 new DotVarExpression( 3147 new VariableExpression("__proto"), 3148 new VariableExpression("__ctor")), 3149 new FunctionLiteralExpression(args, bod, staticScopeBacking)); 3150 } else if(tokens.peekNextToken(ScriptToken.Type.keyword, "var")) { 3151 // instance variable 3152 auto decl = parseVariableDeclaration(tokens, ";"); 3153 addVarDecl(decl, "__obj"); 3154 } else if(tokens.peekNextToken(ScriptToken.Type.keyword, "static")) { 3155 // prototype var 3156 tokens.popFront(); 3157 auto decl = parseVariableDeclaration(tokens, ";"); 3158 addVarDecl(decl, "__proto"); 3159 } else if(tokens.peekNextToken(ScriptToken.Type.keyword, "function")) { 3160 // prototype function 3161 tokens.popFront(); 3162 auto ident = tokens.requireNextToken(ScriptToken.Type.identifier); 3163 3164 tokens.requireNextToken(ScriptToken.Type.symbol, "("); 3165 VariableDeclaration args; 3166 if(!tokens.peekNextToken(ScriptToken.Type.symbol, ")")) 3167 args = parseVariableDeclaration(tokens, ")"); 3168 tokens.requireNextToken(ScriptToken.Type.symbol, ")"); 3169 auto bod = parseExpression(tokens); 3170 3171 expressions ~= new FunctionDeclaration( 3172 new DotVarExpression( 3173 new VariableExpression("__proto"), 3174 new VariableExpression(ident.str), 3175 false), 3176 ident.str, 3177 new FunctionLiteralExpression(args, bod, staticScopeBacking) 3178 ); 3179 } else throw new ScriptCompileException("Unexpected " ~ tokens.front.str ~ " when reading class decl", tokens.front.scriptFilename, tokens.front.lineNumber); 3180 } 3181 3182 tokens.requireNextToken(ScriptToken.Type.symbol, "}"); 3183 3184 // returning he object from the scope... 3185 expressions ~= new VariableExpression("__obj"); 3186 3187 auto scopeExpr = new ScopeExpression(expressions); 3188 auto classVarExpr = new VariableDeclaration(); 3189 classVarExpr.identifiers = [classIdent.str]; 3190 classVarExpr.initializers = [scopeExpr]; 3191 3192 ret = classVarExpr; 3193 } else if(tokens.peekNextToken(ScriptToken.Type.keyword, "if")) { 3194 tokens.popFront(); 3195 auto e = new IfExpression(); 3196 e.condition = parseExpression(tokens); 3197 e.ifTrue = parseExpression(tokens); 3198 if(tokens.peekNextToken(ScriptToken.Type.symbol, ";")) { 3199 tokens.popFront(); 3200 } 3201 if(tokens.peekNextToken(ScriptToken.Type.keyword, "else")) { 3202 tokens.popFront(); 3203 e.ifFalse = parseExpression(tokens); 3204 } 3205 ret = e; 3206 } else if(tokens.peekNextToken(ScriptToken.Type.keyword, "switch")) { 3207 tokens.popFront(); 3208 auto e = new SwitchExpression(); 3209 tokens.requireNextToken(ScriptToken.Type.symbol, "("); 3210 e.expr = parseExpression(tokens); 3211 tokens.requireNextToken(ScriptToken.Type.symbol, ")"); 3212 3213 tokens.requireNextToken(ScriptToken.Type.symbol, "{"); 3214 3215 while(!tokens.peekNextToken(ScriptToken.Type.symbol, "}")) { 3216 3217 if(tokens.peekNextToken(ScriptToken.Type.keyword, "case")) { 3218 auto start = tokens.front; 3219 tokens.popFront(); 3220 auto c = new CaseExpression(parseExpression(tokens)); 3221 e.cases ~= c; 3222 tokens.requireNextToken(ScriptToken.Type.symbol, ":"); 3223 3224 while(!tokens.peekNextToken(ScriptToken.Type.keyword, "default") && !tokens.peekNextToken(ScriptToken.Type.keyword, "case") && !tokens.peekNextToken(ScriptToken.Type.symbol, "}")) { 3225 c.expressions ~= parseStatement(tokens); 3226 while(tokens.peekNextToken(ScriptToken.Type.symbol, ";")) 3227 tokens.popFront(); 3228 } 3229 } else if(tokens.peekNextToken(ScriptToken.Type.keyword, "default")) { 3230 tokens.popFront(); 3231 tokens.requireNextToken(ScriptToken.Type.symbol, ":"); 3232 3233 auto c = new CaseExpression(null); 3234 3235 while(!tokens.peekNextToken(ScriptToken.Type.keyword, "case") && !tokens.peekNextToken(ScriptToken.Type.symbol, "}")) { 3236 c.expressions ~= parseStatement(tokens); 3237 while(tokens.peekNextToken(ScriptToken.Type.symbol, ";")) 3238 tokens.popFront(); 3239 } 3240 3241 e.cases ~= c; 3242 e.default_ = c; 3243 } else throw new ScriptCompileException("A switch statement must consists of cases and a default, nothing else ", tokens.front.scriptFilename, tokens.front.lineNumber); 3244 } 3245 3246 tokens.requireNextToken(ScriptToken.Type.symbol, "}"); 3247 expectedEnd = ""; 3248 3249 ret = e; 3250 3251 } else if(tokens.peekNextToken(ScriptToken.Type.keyword, "foreach")) { 3252 tokens.popFront(); 3253 auto e = new ForeachExpression(); 3254 tokens.requireNextToken(ScriptToken.Type.symbol, "("); 3255 e.decl = parseVariableDeclaration(tokens, ";"); 3256 tokens.requireNextToken(ScriptToken.Type.symbol, ";"); 3257 e.subject = parseExpression(tokens); 3258 3259 if(tokens.peekNextToken(ScriptToken.Type.symbol, "..")) { 3260 tokens.popFront; 3261 e.subject2 = parseExpression(tokens); 3262 } 3263 3264 tokens.requireNextToken(ScriptToken.Type.symbol, ")"); 3265 e.loopBody = parseExpression(tokens); 3266 ret = e; 3267 3268 expectedEnd = ""; 3269 } else if(tokens.peekNextToken(ScriptToken.Type.keyword, "cast")) { 3270 tokens.popFront(); 3271 auto e = new CastExpression(); 3272 3273 tokens.requireNextToken(ScriptToken.Type.symbol, "("); 3274 e.type = tokens.requireNextToken(ScriptToken.Type.identifier).str; 3275 if(tokens.peekNextToken(ScriptToken.Type.symbol, "[")) { 3276 e.type ~= "[]"; 3277 tokens.popFront(); 3278 tokens.requireNextToken(ScriptToken.Type.symbol, "]"); 3279 } 3280 tokens.requireNextToken(ScriptToken.Type.symbol, ")"); 3281 3282 e.e1 = parseExpression(tokens); 3283 ret = e; 3284 } else if(tokens.peekNextToken(ScriptToken.Type.keyword, "for")) { 3285 tokens.popFront(); 3286 auto e = new ForExpression(); 3287 tokens.requireNextToken(ScriptToken.Type.symbol, "("); 3288 e.initialization = parseStatement(tokens, ";"); 3289 3290 tokens.requireNextToken(ScriptToken.Type.symbol, ";"); 3291 3292 e.condition = parseExpression(tokens); 3293 tokens.requireNextToken(ScriptToken.Type.symbol, ";"); 3294 e.advancement = parseExpression(tokens); 3295 tokens.requireNextToken(ScriptToken.Type.symbol, ")"); 3296 e.loopBody = parseExpression(tokens); 3297 3298 ret = e; 3299 3300 expectedEnd = ""; 3301 } else if(tokens.peekNextToken(ScriptToken.Type.keyword, "while")) { 3302 tokens.popFront(); 3303 auto e = new ForExpression(); 3304 e.condition = parseExpression(tokens); 3305 e.loopBody = parseExpression(tokens); 3306 ret = e; 3307 expectedEnd = ""; 3308 } else if(tokens.peekNextToken(ScriptToken.Type.keyword, "break") || tokens.peekNextToken(ScriptToken.Type.keyword, "continue")) { 3309 auto token = tokens.front; 3310 tokens.popFront(); 3311 ret = new LoopControlExpression(token.str); 3312 } else if(tokens.peekNextToken(ScriptToken.Type.keyword, "return")) { 3313 tokens.popFront(); 3314 Expression retVal; 3315 if(tokens.peekNextToken(ScriptToken.Type.symbol, ";")) 3316 retVal = new NullLiteralExpression(); 3317 else 3318 retVal = parseExpression(tokens); 3319 ret = new ReturnExpression(retVal); 3320 } else if(tokens.peekNextToken(ScriptToken.Type.keyword, "throw")) { 3321 auto token = tokens.front; 3322 tokens.popFront(); 3323 ret = new ThrowExpression(parseExpression(tokens), token); 3324 } else if(tokens.peekNextToken(ScriptToken.Type.keyword, "try")) { 3325 auto tryToken = tokens.front; 3326 auto e = new ExceptionBlockExpression(); 3327 tokens.popFront(); 3328 e.tryExpression = parseExpression(tokens, true); 3329 3330 bool hadFinally = false; 3331 while(tokens.peekNextToken(ScriptToken.Type.keyword, "catch")) { 3332 if(hadFinally) 3333 throw new ScriptCompileException("Catch must come before finally", tokens.front.scriptFilename, tokens.front.lineNumber); 3334 tokens.popFront(); 3335 tokens.requireNextToken(ScriptToken.Type.symbol, "("); 3336 if(tokens.peekNextToken(ScriptToken.Type.keyword, "var")) 3337 tokens.popFront(); 3338 3339 auto ident = tokens.requireNextToken(ScriptToken.Type.identifier); 3340 if(tokens.empty) throw new ScriptCompileException("Catch specifier not closed", ident.scriptFilename, ident.lineNumber); 3341 auto next = tokens.front; 3342 if(next.type == ScriptToken.Type.identifier) { 3343 auto type = ident; 3344 ident = next; 3345 3346 e.catchVarTypeSpecifiers ~= type.str; 3347 e.catchVarDecls ~= ident.str; 3348 3349 tokens.popFront(); 3350 3351 tokens.requireNextToken(ScriptToken.Type.symbol, ")"); 3352 } else { 3353 e.catchVarTypeSpecifiers ~= null; 3354 e.catchVarDecls ~= ident.str; 3355 if(next.type != ScriptToken.Type.symbol || next.str != ")") 3356 throw new ScriptCompileException("ss Unexpected " ~ next.str ~ " when expecting ')'", next.scriptFilename, next.lineNumber); 3357 tokens.popFront(); 3358 } 3359 e.catchExpressions ~= parseExpression(tokens); 3360 } 3361 while(tokens.peekNextToken(ScriptToken.Type.keyword, "finally")) { 3362 hadFinally = true; 3363 tokens.popFront(); 3364 e.finallyExpressions ~= parseExpression(tokens); 3365 } 3366 3367 //if(!hadSomething) 3368 //throw new ScriptCompileException("Parse error, missing finally or catch after try", tryToken.lineNumber); 3369 3370 ret = e; 3371 } else { 3372 ret = parseAddend(tokens); 3373 } 3374 3375 if(!tokens.empty && tokens.peekNextToken(ScriptToken.Type.symbol, "?")) { 3376 auto e = new TernaryExpression(); 3377 e.condition = ret; 3378 tokens.requireNextToken(ScriptToken.Type.symbol, "?"); 3379 e.ifTrue = parseExpression(tokens); 3380 tokens.requireNextToken(ScriptToken.Type.symbol, ":"); 3381 e.ifFalse = parseExpression(tokens); 3382 ret = e; 3383 } 3384 } else { 3385 //assert(0); 3386 // return null; 3387 throw new ScriptCompileException("Parse error, unexpected end of input when reading expression", null, 0);//token.lineNumber); 3388 } 3389 3390 //writeln("parsed expression ", ret.toString()); 3391 3392 if(expectedEnd.length && tokens.empty && consumeEnd) // going loose on final ; at the end of input for repl convenience 3393 throw new ScriptCompileException("Parse error, unexpected end of input when reading expression, expecting " ~ expectedEnd, first.scriptFilename, first.lineNumber); 3394 3395 if(expectedEnd.length && consumeEnd) { 3396 if(tokens.peekNextToken(ScriptToken.Type.symbol, expectedEnd)) 3397 tokens.popFront(); 3398 // FIXME 3399 //if(tokens.front.type != ScriptToken.Type.symbol && tokens.front.str != expectedEnd) 3400 //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); 3401 // tokens = tokens[1 .. $]; 3402 } 3403 3404 return ret; 3405 } 3406 3407 VariableDeclaration parseVariableDeclaration(MyTokenStreamHere)(ref MyTokenStreamHere tokens, string termination) { 3408 VariableDeclaration decl = new VariableDeclaration(); 3409 bool equalOk; 3410 anotherVar: 3411 assert(!tokens.empty); 3412 3413 auto firstToken = tokens.front; 3414 3415 // var a, var b is acceptable 3416 if(tokens.peekNextToken(ScriptToken.Type.keyword, "var")) 3417 tokens.popFront(); 3418 3419 equalOk= true; 3420 if(tokens.empty) 3421 throw new ScriptCompileException("Parse error, dangling var at end of file", firstToken.scriptFilename, firstToken.lineNumber); 3422 3423 string type; 3424 3425 auto next = tokens.front; 3426 tokens.popFront; 3427 if(tokens.empty) 3428 throw new ScriptCompileException("Parse error, incomplete var declaration at end of file", firstToken.scriptFilename, firstToken.lineNumber); 3429 auto next2 = tokens.front; 3430 3431 ScriptToken typeSpecifier; 3432 3433 /* if there's two identifiers back to back, it is a type specifier. otherwise just a name */ 3434 3435 if(next.type == ScriptToken.Type.identifier && next2.type == ScriptToken.Type.identifier) { 3436 // type ident; 3437 typeSpecifier = next; 3438 next = next2; 3439 // get past the type 3440 tokens.popFront(); 3441 } else { 3442 // no type, just carry on with the next thing 3443 } 3444 3445 Expression initializer; 3446 auto identifier = next; 3447 if(identifier.type != ScriptToken.Type.identifier) 3448 throw new ScriptCompileException("Parse error, found '"~identifier.str~"' when expecting var identifier", identifier.scriptFilename, identifier.lineNumber); 3449 3450 //tokens.popFront(); 3451 3452 tryTermination: 3453 if(tokens.empty) 3454 throw new ScriptCompileException("Parse error, missing ; after var declaration at end of file", firstToken.scriptFilename, firstToken.lineNumber); 3455 3456 auto peek = tokens.front; 3457 if(peek.type == ScriptToken.Type.symbol) { 3458 if(peek.str == "=") { 3459 if(!equalOk) 3460 throw new ScriptCompileException("Parse error, unexpected '"~identifier.str~"' after reading var initializer", peek.scriptFilename, peek.lineNumber); 3461 equalOk = false; 3462 tokens.popFront(); 3463 initializer = parseExpression(tokens); 3464 goto tryTermination; 3465 } else if(peek.str == ",") { 3466 tokens.popFront(); 3467 decl.identifiers ~= identifier.str; 3468 decl.initializers ~= initializer; 3469 decl.typeSpecifiers ~= typeSpecifier.str; 3470 goto anotherVar; 3471 } else if(peek.str == termination) { 3472 decl.identifiers ~= identifier.str; 3473 decl.initializers ~= initializer; 3474 decl.typeSpecifiers ~= typeSpecifier.str; 3475 //tokens = tokens[1 .. $]; 3476 // we're done! 3477 } else 3478 throw new ScriptCompileException("Parse error, unexpected '"~peek.str~"' when reading var declaration symbol", peek.scriptFilename, peek.lineNumber); 3479 } else 3480 throw new ScriptCompileException("Parse error, unexpected non-symbol '"~peek.str~"' when reading var declaration", peek.scriptFilename, peek.lineNumber); 3481 3482 return decl; 3483 } 3484 3485 Expression parseStatement(MyTokenStreamHere)(ref MyTokenStreamHere tokens, string terminatingSymbol = null) { 3486 skip: // FIXME 3487 if(tokens.empty) 3488 return null; 3489 3490 if(terminatingSymbol !is null && (tokens.front.type == ScriptToken.Type.symbol && tokens.front.str == terminatingSymbol)) 3491 return null; // we're done 3492 3493 auto token = tokens.front; 3494 3495 // tokens = tokens[1 .. $]; 3496 final switch(token.type) { 3497 case ScriptToken.Type.keyword: 3498 case ScriptToken.Type.symbol: 3499 switch(token.str) { 3500 // assert 3501 case "assert": 3502 tokens.popFront(); 3503 3504 return parseFunctionCall(tokens, new AssertKeyword(token)); 3505 3506 //break; 3507 // declarations 3508 case "var": 3509 return parseVariableDeclaration(tokens, ";"); 3510 case ";": 3511 tokens.popFront(); // FIXME 3512 goto skip; 3513 // literals 3514 case "function": 3515 case "macro": 3516 // function can be a literal, or a declaration. 3517 3518 tokens.popFront(); // we're peeking ahead 3519 3520 if(tokens.peekNextToken(ScriptToken.Type.identifier)) { 3521 // decl style, rewrite it into var ident = function style 3522 // tokens.popFront(); // skipping the function keyword // already done above with the popFront 3523 auto ident = tokens.front; 3524 tokens.popFront(); 3525 3526 tokens.requireNextToken(ScriptToken.Type.symbol, "("); 3527 3528 auto exp = new FunctionLiteralExpression(); 3529 if(!tokens.peekNextToken(ScriptToken.Type.symbol, ")")) 3530 exp.arguments = parseVariableDeclaration(tokens, ")"); 3531 tokens.requireNextToken(ScriptToken.Type.symbol, ")"); 3532 3533 exp.functionBody = parseExpression(tokens); 3534 3535 // a ; should NOT be required here btw 3536 3537 exp.isMacro = token.str == "macro"; 3538 3539 auto e = new FunctionDeclaration(null, ident.str, exp); 3540 3541 return e; 3542 3543 } else { 3544 tokens.pushFront(token); // put it back since everyone expects us to have done that 3545 goto case; // handle it like any other expression 3546 } 3547 3548 case "true": 3549 case "false": 3550 3551 case "json!{": 3552 case "#{": 3553 case "[": 3554 case "(": 3555 case "null": 3556 3557 // scope 3558 case "{": 3559 case "scope": 3560 3561 case "cast": 3562 3563 // classes 3564 case "class": 3565 case "new": 3566 3567 case "super": 3568 3569 // flow control 3570 case "if": 3571 case "while": 3572 case "for": 3573 case "foreach": 3574 case "switch": 3575 3576 // exceptions 3577 case "try": 3578 case "throw": 3579 3580 // evals 3581 case "eval": 3582 case "mixin": 3583 3584 // flow 3585 case "continue": 3586 case "break": 3587 case "return": 3588 return parseExpression(tokens); 3589 // unary prefix operators 3590 case "!": 3591 case "~": 3592 case "-": 3593 return parseExpression(tokens); 3594 3595 // BTW add custom object operator overloading to struct var 3596 // and custom property overloading to PrototypeObject 3597 3598 default: 3599 // whatever else keyword or operator related is actually illegal here 3600 throw new ScriptCompileException("Parse error, unexpected " ~ token.str, token.scriptFilename, token.lineNumber); 3601 } 3602 // break; 3603 case ScriptToken.Type.identifier: 3604 case ScriptToken.Type..string: 3605 case ScriptToken.Type.int_number: 3606 case ScriptToken.Type.float_number: 3607 case ScriptToken.Type.binary_number: 3608 case ScriptToken.Type.hex_number: 3609 case ScriptToken.Type.oct_number: 3610 return parseExpression(tokens); 3611 } 3612 3613 assert(0); 3614 } 3615 3616 // FIXME someday this should work, my parser is so bad 3617 // until then put parens around your == stuff. 3618 version(none) 3619 unittest { 3620 interpret(q{ 3621 var a = 5; 3622 var b = false; 3623 assert(a == 5 || b); 3624 }); 3625 } 3626 version(none) 3627 unittest { 3628 interpret(q{ 3629 var a = 5; 3630 var b = false; 3631 assert(((a == 5) || b)); 3632 }); 3633 } 3634 3635 3636 struct CompoundStatementRange(MyTokenStreamHere) { 3637 // FIXME: if MyTokenStreamHere is not a class, this fails! 3638 MyTokenStreamHere tokens; 3639 int startingLine; 3640 string terminatingSymbol; 3641 bool isEmpty; 3642 3643 this(MyTokenStreamHere t, int startingLine, string terminatingSymbol) { 3644 tokens = t; 3645 this.startingLine = startingLine; 3646 this.terminatingSymbol = terminatingSymbol; 3647 popFront(); 3648 } 3649 3650 bool empty() { 3651 return isEmpty; 3652 } 3653 3654 Expression got; 3655 3656 Expression front() { 3657 return got; 3658 } 3659 3660 void popFront() { 3661 while(!tokens.empty && (terminatingSymbol is null || !(tokens.front.type == ScriptToken.Type.symbol && tokens.front.str == terminatingSymbol))) { 3662 auto n = parseStatement(tokens, terminatingSymbol); 3663 if(n is null) 3664 continue; 3665 got = n; 3666 return; 3667 } 3668 3669 if(tokens.empty && terminatingSymbol !is null) { 3670 throw new ScriptCompileException("Reached end of file while trying to reach matching " ~ terminatingSymbol, null, startingLine); 3671 } 3672 3673 if(terminatingSymbol !is null) { 3674 assert(tokens.front.str == terminatingSymbol); 3675 tokens.skipNext++; 3676 } 3677 3678 isEmpty = true; 3679 } 3680 } 3681 3682 CompoundStatementRange!MyTokenStreamHere 3683 //Expression[] 3684 parseCompoundStatement(MyTokenStreamHere)(ref MyTokenStreamHere tokens, int startingLine = 1, string terminatingSymbol = null) { 3685 return (CompoundStatementRange!MyTokenStreamHere(tokens, startingLine, terminatingSymbol)); 3686 } 3687 3688 auto parseScript(MyTokenStreamHere)(MyTokenStreamHere tokens) { 3689 /* 3690 the language's grammar is simple enough 3691 3692 maybe flow control should be statements though lol. they might not make sense inside. 3693 3694 Expressions: 3695 var identifier; 3696 var identifier = initializer; 3697 var identifier, identifier2 3698 3699 return expression; 3700 return ; 3701 3702 json!{ object literal } 3703 3704 { scope expression } 3705 3706 [ array literal ] 3707 other literal 3708 function (arg list) other expression 3709 3710 ( expression ) // parenthesized expression 3711 operator expression // unary expression 3712 3713 expression operator expression // binary expression 3714 expression (other expression... args) // function call 3715 3716 Binary Operator precedence : 3717 . [] 3718 * / 3719 + - 3720 ~ 3721 < > == != 3722 = 3723 */ 3724 3725 return parseCompoundStatement(tokens); 3726 } 3727 3728 var interpretExpressions(ExpressionStream)(ExpressionStream expressions, PrototypeObject variables) if(is(ElementType!ExpressionStream == Expression)) { 3729 assert(variables !is null); 3730 var ret; 3731 foreach(expression; expressions) { 3732 auto res = expression.interpret(variables); 3733 variables = res.sc; 3734 ret = res.value; 3735 } 3736 return ret; 3737 } 3738 3739 var interpretStream(MyTokenStreamHere)(MyTokenStreamHere tokens, PrototypeObject variables) if(is(ElementType!MyTokenStreamHere == ScriptToken)) { 3740 assert(variables !is null); 3741 // this is an entry point that all others lead to, right before getting to interpretExpressions... 3742 3743 return interpretExpressions(parseScript(tokens), variables); 3744 } 3745 3746 var interpretStream(MyTokenStreamHere)(MyTokenStreamHere tokens, var variables) if(is(ElementType!MyTokenStreamHere == ScriptToken)) { 3747 return interpretStream(tokens, 3748 (variables.payloadType() == var.Type.Object && variables._payload._object !is null) ? variables._payload._object : new PrototypeObject()); 3749 } 3750 3751 var interpret(string code, PrototypeObject variables, string scriptFilename = null) { 3752 assert(variables !is null); 3753 return interpretStream(lexScript(repeat(code, 1), scriptFilename), variables); 3754 } 3755 3756 /++ 3757 This is likely your main entry point to the interpreter. It will interpret the script code 3758 given, with the given global variable object (which will be modified by the script, meaning 3759 you can pass it to subsequent calls to `interpret` to store context), and return the result 3760 of the last expression given. 3761 3762 --- 3763 var globals = var.emptyObject; // the global object must be an object of some type 3764 globals.x = 10; 3765 globals.y = 15; 3766 // you can also set global functions through this same style, etc 3767 3768 var result = interpret(`x + y`, globals); 3769 assert(result == 25); 3770 --- 3771 3772 3773 $(TIP 3774 If you want to just call a script function, interpret the definition of it, 3775 then just call it through the `globals` object you passed to it. 3776 3777 --- 3778 var globals = var.emptyObject; 3779 interpret(`function foo(name) { return "hello, " ~ name ~ "!"; }`, globals); 3780 var result = globals.foo()("world"); 3781 assert(result == "hello, world!"); 3782 --- 3783 ) 3784 3785 Params: 3786 code = the script source code you want to interpret 3787 scriptFilename = the filename of the script, if you want to provide it. Gives nicer error messages if you provide one. 3788 variables = The global object of the script context. It will be modified by the user script. 3789 3790 Returns: 3791 the result of the last expression evaluated by the script engine 3792 +/ 3793 var interpret(string code, var variables = null, string scriptFilename = null, string file = __FILE__, size_t line = __LINE__) { 3794 if(scriptFilename is null) 3795 scriptFilename = file ~ "@" ~ to!string(line); 3796 return interpretStream( 3797 lexScript(repeat(code, 1), scriptFilename), 3798 (variables.payloadType() == var.Type.Object && variables._payload._object !is null) ? variables._payload._object : new PrototypeObject()); 3799 } 3800 3801 /// 3802 var interpretFile(File file, var globals) { 3803 import std.algorithm; 3804 return interpretStream(lexScript(file.byLine.map!((a) => a.idup), file.name), 3805 (globals.payloadType() == var.Type.Object && globals._payload._object !is null) ? globals._payload._object : new PrototypeObject()); 3806 } 3807 3808 /// Enhanced repl uses arsd.terminal for better ux. Added April 26, 2020. Default just uses std.stdio. 3809 void repl(bool enhanced = false)(var globals) { 3810 static if(enhanced) { 3811 import arsd.terminal; 3812 Terminal terminal = Terminal(ConsoleOutputMode.linear); 3813 auto lines() { 3814 struct Range { 3815 string line; 3816 string front() { return line; } 3817 bool empty() { return line is null; } 3818 void popFront() { line = terminal.getline(": "); terminal.writeln(); } 3819 } 3820 Range r; 3821 r.popFront(); 3822 return r; 3823 3824 } 3825 3826 void writeln(T...)(T t) { 3827 terminal.writeln(t); 3828 terminal.flush(); 3829 } 3830 } else { 3831 import std.stdio; 3832 auto lines() { return stdin.byLine; } 3833 } 3834 3835 bool exited; 3836 if(globals == null) 3837 globals = var.emptyObject; 3838 globals.exit = () { exited = true; }; 3839 3840 import std.algorithm; 3841 auto variables = (globals.payloadType() == var.Type.Object && globals._payload._object !is null) ? globals._payload._object : new PrototypeObject(); 3842 3843 // we chain to ensure the priming popFront succeeds so we don't throw here 3844 auto tokens = lexScript( 3845 chain(["var __skipme = 0;"], map!((a) => a.idup)(lines)) 3846 , "stdin"); 3847 auto expressions = parseScript(tokens); 3848 3849 while(!exited && !expressions.empty) { 3850 try { 3851 expressions.popFront; 3852 auto expression = expressions.front; 3853 auto res = expression.interpret(variables); 3854 variables = res.sc; 3855 writeln(">>> ", res.value); 3856 } catch(ScriptCompileException e) { 3857 writeln("*+* ", e.msg); 3858 tokens.popFront(); // skip the one we threw on... 3859 } catch(Exception e) { 3860 writeln("*** ", e.msg); 3861 } 3862 } 3863 } 3864 3865 class ScriptFunctionMetadata : VarMetadata { 3866 FunctionLiteralExpression fle; 3867 this(FunctionLiteralExpression fle) { 3868 this.fle = fle; 3869 } 3870 3871 string convertToString() { 3872 return fle.toString(); 3873 } 3874 }