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 foreach(idx, dchar ch; text[started .. pos]) { 823 if(escaped) { 824 escaped = false; 825 switch(ch) { 826 case '\\': copy ~= "\\"; break; 827 case 'n': copy ~= "\n"; break; 828 case 'r': copy ~= "\r"; break; 829 case 'a': copy ~= "\a"; break; 830 case 't': copy ~= "\t"; break; 831 case '#': copy ~= "#"; break; 832 case '"': copy ~= "\""; break; 833 case '\'': copy ~= "'"; break; 834 default: 835 throw new ScriptCompileException("Unknown escape char " ~ cast(char) ch, token.scriptFilename, token.lineNumber); 836 } 837 continue; 838 } else if(ch == '\\') { 839 escaped = true; 840 continue; 841 } 842 copy ~= ch; 843 } 844 845 token.str = copy; 846 } else { 847 token.str = text[started .. pos]; 848 } 849 if(interpolationDetected) 850 token.wasSpecial = "\""; 851 advance(pos + ((end == 0xe2) ? 3 : 1)); // skip the closing " too 852 } else { 853 // let's check all symbols 854 bool found = false; 855 foreach(symbol; symbols) 856 if(text.length >= symbol.length && text[0 .. symbol.length] == symbol) { 857 858 if(symbol == "//") { 859 // one line comment 860 int pos = 0; 861 while(pos < text.length && text[pos] != '\n' && text[0] != '\r') 862 pos++; 863 advance(pos); 864 continue mainLoop; 865 } else if(symbol == "/*") { 866 int pos = 0; 867 while(pos + 1 < text.length && text[pos..pos+2] != "*/") 868 pos++; 869 870 if(pos + 1 == text.length) 871 throw new ScriptCompileException("unclosed /* */ comment", token.scriptFilename, lineNumber); 872 873 advance(pos + 2); 874 continue mainLoop; 875 876 } else if(symbol == "/+") { 877 int open = 0; 878 int pos = 0; 879 while(pos + 1 < text.length) { 880 if(text[pos..pos+2] == "/+") { 881 open++; 882 pos++; 883 } else if(text[pos..pos+2] == "+/") { 884 open--; 885 pos++; 886 if(open == 0) 887 break; 888 } 889 pos++; 890 } 891 892 if(pos + 1 == text.length) 893 throw new ScriptCompileException("unclosed /+ +/ comment", token.scriptFilename, lineNumber); 894 895 advance(pos + 1); 896 continue mainLoop; 897 } 898 // FIXME: documentation comments 899 900 found = true; 901 token.type = ScriptToken.Type.symbol; 902 token.str = symbol; 903 advance(symbol.length); 904 break; 905 } 906 907 if(!found) { 908 // FIXME: make sure this gives a valid utf-8 sequence 909 throw new ScriptCompileException("unknown token " ~ text[0], token.scriptFilename, lineNumber); 910 } 911 } 912 913 next = token; 914 return; 915 } 916 917 textStream.popFront(); 918 if(!textStream.empty()) { 919 text = textStream.front; 920 goto mainLoop; 921 } 922 923 return; 924 } 925 926 } 927 928 TokenStream!TextStream lexScript(TextStream)(TextStream textStream, string scriptFilename) if(is(ElementType!TextStream == string)) { 929 return new TokenStream!TextStream(textStream, scriptFilename); 930 } 931 932 class MacroPrototype : PrototypeObject { 933 var func; 934 935 // macros are basically functions that get special treatment for their arguments 936 // they are passed as AST objects instead of interpreted 937 // calling an AST object will interpret it in the script 938 this(var func) { 939 this.func = func; 940 this._properties["opCall"] = (var _this, var[] args) { 941 return func.apply(_this, args); 942 }; 943 } 944 } 945 946 alias helper(alias T) = T; 947 // alternative to virtual function for converting the expression objects to script objects 948 void addChildElementsOfExpressionToScriptExpressionObject(ClassInfo c, Expression _thisin, PrototypeObject sc, ref var obj) { 949 foreach(itemName; __traits(allMembers, mixin(__MODULE__))) 950 static if(__traits(compiles, __traits(getMember, mixin(__MODULE__), itemName))) { 951 alias Class = helper!(__traits(getMember, mixin(__MODULE__), itemName)); 952 static if(is(Class : Expression)) if(c == typeid(Class)) { 953 auto _this = cast(Class) _thisin; 954 foreach(memberName; __traits(allMembers, Class)) { 955 alias member = helper!(__traits(getMember, Class, memberName)); 956 957 static if(is(typeof(member) : Expression)) { 958 auto lol = __traits(getMember, _this, memberName); 959 if(lol is null) 960 obj[memberName] = null; 961 else 962 obj[memberName] = lol.toScriptExpressionObject(sc); 963 } 964 static if(is(typeof(member) : Expression[])) { 965 obj[memberName] = var.emptyArray; 966 foreach(m; __traits(getMember, _this, memberName)) 967 if(m !is null) 968 obj[memberName] ~= m.toScriptExpressionObject(sc); 969 else 970 obj[memberName] ~= null; 971 } 972 static if(is(typeof(member) : string) || is(typeof(member) : long) || is(typeof(member) : real) || is(typeof(member) : bool)) { 973 obj[memberName] = __traits(getMember, _this, memberName); 974 } 975 } 976 } 977 } 978 } 979 980 struct InterpretResult { 981 var value; 982 PrototypeObject sc; 983 enum FlowControl { Normal, Return, Continue, Break, Goto } 984 FlowControl flowControl; 985 string flowControlDetails; // which label 986 } 987 988 class Expression { 989 abstract InterpretResult interpret(PrototypeObject sc); 990 991 // this returns an AST object that can be inspected and possibly altered 992 // by the script. Calling the returned object will interpret the object in 993 // the original scope passed 994 var toScriptExpressionObject(PrototypeObject sc) { 995 var obj = var.emptyObject; 996 997 obj["type"] = typeid(this).name; 998 obj["toSourceCode"] = (var _this, var[] args) { 999 Expression e = this; 1000 return var(e.toString()); 1001 }; 1002 obj["opCall"] = (var _this, var[] args) { 1003 Expression e = this; 1004 // FIXME: if they changed the properties in the 1005 // script, we should update them here too. 1006 return e.interpret(sc).value; 1007 }; 1008 obj["interpolate"] = (var _this, var[] args) { 1009 StringLiteralExpression e = cast(StringLiteralExpression) this; 1010 if(!e) 1011 return var(null); 1012 return e.interpolate(args.length ? args[0] : var(null), sc); 1013 }; 1014 1015 1016 // adding structure is going to be a little bit magical 1017 // I could have done this with a virtual function, but I'm lazy. 1018 addChildElementsOfExpressionToScriptExpressionObject(typeid(this), this, sc, obj); 1019 1020 return obj; 1021 } 1022 1023 string toInterpretedString(PrototypeObject sc) { 1024 return toString(); 1025 } 1026 } 1027 1028 class MixinExpression : Expression { 1029 Expression e1; 1030 this(Expression e1) { 1031 this.e1 = e1; 1032 } 1033 1034 override string toString() { return "mixin(" ~ e1.toString() ~ ")"; } 1035 1036 override InterpretResult interpret(PrototypeObject sc) { 1037 return InterpretResult(.interpret(e1.interpret(sc).value.get!string ~ ";", sc), sc); 1038 } 1039 } 1040 1041 class StringLiteralExpression : Expression { 1042 string content; 1043 bool allowInterpolation; 1044 1045 ScriptToken token; 1046 1047 override string toString() { 1048 import std.string : replace; 1049 return "\"" ~ content.replace(`\`, `\\`).replace("\"", "\\\"") ~ "\""; 1050 } 1051 1052 this(ScriptToken token) { 1053 this.token = token; 1054 this(token.str); 1055 if(token.wasSpecial == "\"") 1056 allowInterpolation = true; 1057 1058 } 1059 1060 this(string s) { 1061 content = s; 1062 } 1063 1064 var interpolate(var funcObj, PrototypeObject sc) { 1065 import std.string : indexOf; 1066 if(allowInterpolation) { 1067 string r; 1068 1069 auto c = content; 1070 auto idx = c.indexOf("#{"); 1071 while(idx != -1) { 1072 r ~= c[0 .. idx]; 1073 c = c[idx + 2 .. $]; 1074 idx = 0; 1075 int open = 1; 1076 while(idx < c.length) { 1077 if(c[idx] == '}') 1078 open--; 1079 else if(c[idx] == '{') 1080 open++; 1081 if(open == 0) 1082 break; 1083 idx++; 1084 } 1085 if(open != 0) 1086 throw new ScriptRuntimeException("Unclosed interpolation thing", token.scriptFilename, token.lineNumber); 1087 auto code = c[0 .. idx]; 1088 1089 var result = .interpret(code, sc); 1090 1091 if(funcObj == var(null)) 1092 r ~= result.get!string; 1093 else 1094 r ~= funcObj(result).get!string; 1095 1096 c = c[idx + 1 .. $]; 1097 idx = c.indexOf("#{"); 1098 } 1099 1100 r ~= c; 1101 return var(r); 1102 } else { 1103 return var(content); 1104 } 1105 } 1106 1107 override InterpretResult interpret(PrototypeObject sc) { 1108 return InterpretResult(interpolate(var(null), sc), sc); 1109 } 1110 } 1111 1112 class BoolLiteralExpression : Expression { 1113 bool literal; 1114 this(string l) { 1115 literal = to!bool(l); 1116 } 1117 1118 override string toString() { return to!string(literal); } 1119 1120 override InterpretResult interpret(PrototypeObject sc) { 1121 return InterpretResult(var(literal), sc); 1122 } 1123 } 1124 1125 class IntLiteralExpression : Expression { 1126 long literal; 1127 1128 this(string s, int radix) { 1129 literal = to!long(s.replace("_", ""), radix); 1130 } 1131 1132 override string toString() { return to!string(literal); } 1133 1134 override InterpretResult interpret(PrototypeObject sc) { 1135 return InterpretResult(var(literal), sc); 1136 } 1137 } 1138 class FloatLiteralExpression : Expression { 1139 this(string s) { 1140 literal = to!real(s.replace("_", "")); 1141 } 1142 real literal; 1143 override string toString() { return to!string(literal); } 1144 override InterpretResult interpret(PrototypeObject sc) { 1145 return InterpretResult(var(literal), sc); 1146 } 1147 } 1148 class NullLiteralExpression : Expression { 1149 this() {} 1150 override string toString() { return "null"; } 1151 1152 override InterpretResult interpret(PrototypeObject sc) { 1153 var n; 1154 return InterpretResult(n, sc); 1155 } 1156 } 1157 class NegationExpression : Expression { 1158 Expression e; 1159 this(Expression e) { this.e = e;} 1160 override string toString() { return "-" ~ e.toString(); } 1161 1162 override InterpretResult interpret(PrototypeObject sc) { 1163 var n = e.interpret(sc).value; 1164 return InterpretResult(-n, sc); 1165 } 1166 } 1167 class NotExpression : Expression { 1168 Expression e; 1169 this(Expression e) { this.e = e;} 1170 override string toString() { return "!" ~ e.toString(); } 1171 1172 override InterpretResult interpret(PrototypeObject sc) { 1173 var n = e.interpret(sc).value; 1174 return InterpretResult(var(!n), sc); 1175 } 1176 } 1177 class BitFlipExpression : Expression { 1178 Expression e; 1179 this(Expression e) { this.e = e;} 1180 override string toString() { return "~" ~ e.toString(); } 1181 1182 override InterpretResult interpret(PrototypeObject sc) { 1183 var n = e.interpret(sc).value; 1184 // possible FIXME given the size. but it is fuzzy when dynamic.. 1185 return InterpretResult(var(~(n.get!long)), sc); 1186 } 1187 } 1188 1189 class ArrayLiteralExpression : Expression { 1190 this() {} 1191 1192 override string toString() { 1193 string s = "["; 1194 foreach(i, ele; elements) { 1195 if(i) s ~= ", "; 1196 s ~= ele.toString(); 1197 } 1198 s ~= "]"; 1199 return s; 1200 } 1201 1202 Expression[] elements; 1203 override InterpretResult interpret(PrototypeObject sc) { 1204 var n = var.emptyArray; 1205 foreach(i, element; elements) 1206 n[i] = element.interpret(sc).value; 1207 return InterpretResult(n, sc); 1208 } 1209 } 1210 class ObjectLiteralExpression : Expression { 1211 Expression[string] elements; 1212 1213 override string toString() { 1214 string s = "#{"; 1215 bool first = true; 1216 foreach(k, e; elements) { 1217 if(first) 1218 first = false; 1219 else 1220 s ~= ", "; 1221 1222 s ~= "\"" ~ k ~ "\":"; // FIXME: escape if needed 1223 s ~= e.toString(); 1224 } 1225 1226 s ~= "}"; 1227 return s; 1228 } 1229 1230 PrototypeObject backing; 1231 this(PrototypeObject backing = null) { 1232 this.backing = backing; 1233 } 1234 1235 override InterpretResult interpret(PrototypeObject sc) { 1236 var n; 1237 if(backing is null) 1238 n = var.emptyObject; 1239 else 1240 n._object = backing; 1241 1242 foreach(k, v; elements) 1243 n[k] = v.interpret(sc).value; 1244 1245 return InterpretResult(n, sc); 1246 } 1247 } 1248 class FunctionLiteralExpression : Expression { 1249 this() { 1250 // we want this to not be null at all when we're interpreting since it is used as a comparison for a magic operation 1251 if(DefaultArgumentDummyObject is null) 1252 DefaultArgumentDummyObject = new PrototypeObject(); 1253 } 1254 1255 this(VariableDeclaration args, Expression bod, PrototypeObject lexicalScope = null) { 1256 this(); 1257 this.arguments = args; 1258 this.functionBody = bod; 1259 this.lexicalScope = lexicalScope; 1260 } 1261 1262 override string toString() { 1263 string s = (isMacro ? "macro" : "function") ~ " ("; 1264 if(arguments !is null) 1265 s ~= arguments.toString(); 1266 1267 s ~= ") "; 1268 s ~= functionBody.toString(); 1269 return s; 1270 } 1271 1272 /* 1273 function identifier (arg list) expression 1274 1275 so 1276 var e = function foo() 10; // valid 1277 var e = function foo() { return 10; } // also valid 1278 1279 // the return value is just the last expression's result that was evaluated 1280 // to return void, be sure to do a "return;" at the end of the function 1281 */ 1282 VariableDeclaration arguments; 1283 Expression functionBody; // can be a ScopeExpression btw 1284 1285 PrototypeObject lexicalScope; 1286 1287 bool isMacro; 1288 1289 override InterpretResult interpret(PrototypeObject sc) { 1290 assert(DefaultArgumentDummyObject !is null); 1291 var v; 1292 v._metadata = new ScriptFunctionMetadata(this); 1293 v._function = (var _this, var[] args) { 1294 auto argumentsScope = new PrototypeObject(); 1295 PrototypeObject scToUse; 1296 if(lexicalScope is null) 1297 scToUse = sc; 1298 else { 1299 scToUse = lexicalScope; 1300 scToUse._secondary = sc; 1301 } 1302 1303 argumentsScope.prototype = scToUse; 1304 1305 argumentsScope._getMember("this", false, false) = _this; 1306 argumentsScope._getMember("_arguments", false, false) = args; 1307 argumentsScope._getMember("_thisfunc", false, false) = v; 1308 1309 if(arguments) 1310 foreach(i, identifier; arguments.identifiers) { 1311 argumentsScope._getMember(identifier, false, false); // create it in this scope... 1312 if(i < args.length && !(args[i].payloadType() == var.Type.Object && args[i]._payload._object is DefaultArgumentDummyObject)) 1313 argumentsScope._getMember(identifier, false, true) = args[i]; 1314 else 1315 if(arguments.initializers[i] !is null) 1316 argumentsScope._getMember(identifier, false, true) = arguments.initializers[i].interpret(sc).value; 1317 } 1318 1319 if(functionBody !is null) 1320 return functionBody.interpret(argumentsScope).value; 1321 else { 1322 assert(0); 1323 } 1324 }; 1325 if(isMacro) { 1326 var n = var.emptyObject; 1327 n._object = new MacroPrototype(v); 1328 v = n; 1329 } 1330 return InterpretResult(v, sc); 1331 } 1332 } 1333 1334 class CastExpression : Expression { 1335 string type; 1336 Expression e1; 1337 1338 override string toString() { 1339 return "cast(" ~ type ~ ") " ~ e1.toString(); 1340 } 1341 1342 override InterpretResult interpret(PrototypeObject sc) { 1343 var n = e1.interpret(sc).value; 1344 switch(type) { 1345 foreach(possibleType; CtList!("int", "long", "float", "double", "real", "char", "dchar", "string", "int[]", "string[]", "float[]")) { 1346 case possibleType: 1347 n = mixin("cast(" ~ possibleType ~ ") n"); 1348 break; 1349 } 1350 default: 1351 // FIXME, we can probably cast other types like classes here. 1352 } 1353 1354 return InterpretResult(n, sc); 1355 } 1356 } 1357 1358 class VariableDeclaration : Expression { 1359 string[] identifiers; 1360 Expression[] initializers; 1361 string[] typeSpecifiers; 1362 1363 this() {} 1364 1365 override string toString() { 1366 string s = ""; 1367 foreach(i, ident; identifiers) { 1368 if(i) 1369 s ~= ", "; 1370 s ~= "var "; 1371 if(typeSpecifiers[i].length) { 1372 s ~= typeSpecifiers[i]; 1373 s ~= " "; 1374 } 1375 s ~= ident; 1376 if(initializers[i] !is null) 1377 s ~= " = " ~ initializers[i].toString(); 1378 } 1379 return s; 1380 } 1381 1382 1383 override InterpretResult interpret(PrototypeObject sc) { 1384 var n; 1385 1386 foreach(i, identifier; identifiers) { 1387 n = sc._getMember(identifier, false, false); 1388 auto initializer = initializers[i]; 1389 if(initializer) { 1390 n = initializer.interpret(sc).value; 1391 sc._getMember(identifier, false, false) = n; 1392 } 1393 } 1394 return InterpretResult(n, sc); 1395 } 1396 } 1397 1398 class FunctionDeclaration : Expression { 1399 DotVarExpression where; 1400 string ident; 1401 FunctionLiteralExpression expr; 1402 1403 this(DotVarExpression where, string ident, FunctionLiteralExpression expr) { 1404 this.where = where; 1405 this.ident = ident; 1406 this.expr = expr; 1407 } 1408 1409 override InterpretResult interpret(PrototypeObject sc) { 1410 var n = expr.interpret(sc).value; 1411 1412 var replacement; 1413 1414 if(expr.isMacro) { 1415 // can't overload macros 1416 replacement = n; 1417 } else { 1418 var got; 1419 1420 if(where is null) { 1421 got = sc._getMember(ident, false, false); 1422 } else { 1423 got = where.interpret(sc).value; 1424 } 1425 1426 OverloadSet os = got.get!OverloadSet; 1427 if(os is null) { 1428 os = new OverloadSet; 1429 } 1430 1431 os.addOverload(OverloadSet.Overload(expr.arguments ? toTypes(expr.arguments.typeSpecifiers, sc) : null, n)); 1432 1433 replacement = var(os); 1434 } 1435 1436 if(where is null) { 1437 sc._getMember(ident, false, false) = replacement; 1438 } else { 1439 where.setVar(sc, replacement, false, true); 1440 } 1441 1442 return InterpretResult(n, sc); 1443 } 1444 1445 override string toString() { 1446 string s = (expr.isMacro ? "macro" : "function") ~ " "; 1447 s ~= ident; 1448 s ~= "("; 1449 if(expr.arguments !is null) 1450 s ~= expr.arguments.toString(); 1451 1452 s ~= ") "; 1453 s ~= expr.functionBody.toString(); 1454 1455 return s; 1456 } 1457 } 1458 1459 template CtList(T...) { alias CtList = T; } 1460 1461 class BinaryExpression : Expression { 1462 string op; 1463 Expression e1; 1464 Expression e2; 1465 1466 override string toString() { 1467 return e1.toString() ~ " " ~ op ~ " " ~ e2.toString(); 1468 } 1469 1470 override string toInterpretedString(PrototypeObject sc) { 1471 return e1.toInterpretedString(sc) ~ " " ~ op ~ " " ~ e2.toInterpretedString(sc); 1472 } 1473 1474 this(string op, Expression e1, Expression e2) { 1475 this.op = op; 1476 this.e1 = e1; 1477 this.e2 = e2; 1478 } 1479 1480 override InterpretResult interpret(PrototypeObject sc) { 1481 var left = e1.interpret(sc).value; 1482 var right = e2.interpret(sc).value; 1483 1484 //writeln(left, " "~op~" ", right); 1485 1486 var n; 1487 sw: switch(op) { 1488 // I would actually kinda prefer this to be static foreach, but normal 1489 // tuple foreach here has broaded compiler compatibility. 1490 foreach(ctOp; CtList!("+", "-", "*", "/", "==", "!=", "<=", ">=", ">", "<", "~", "&&", "||", "&", "|", "^", "%", ">>", "<<", ">>>")) // FIXME 1491 case ctOp: { 1492 n = mixin("left "~ctOp~" right"); 1493 break sw; 1494 } 1495 default: 1496 assert(0, op); 1497 } 1498 1499 return InterpretResult(n, sc); 1500 } 1501 } 1502 1503 class OpAssignExpression : Expression { 1504 string op; 1505 Expression e1; 1506 Expression e2; 1507 1508 this(string op, Expression e1, Expression e2) { 1509 this.op = op; 1510 this.e1 = e1; 1511 this.e2 = e2; 1512 } 1513 1514 override string toString() { 1515 return e1.toString() ~ " " ~ op ~ "= " ~ e2.toString(); 1516 } 1517 1518 override InterpretResult interpret(PrototypeObject sc) { 1519 1520 auto v = cast(VariableExpression) e1; 1521 if(v is null) 1522 throw new ScriptRuntimeException("not an lvalue", null, 0 /* FIXME */); 1523 1524 var right = e2.interpret(sc).value; 1525 1526 //writeln(left, " "~op~"= ", right); 1527 1528 var n; 1529 foreach(ctOp; CtList!("+=", "-=", "*=", "/=", "~=", "&=", "|=", "^=", "%=")) 1530 if(ctOp[0..1] == op) 1531 n = mixin("v.getVar(sc, true, true) "~ctOp~" right"); 1532 1533 // FIXME: ensure the variable is updated in scope too 1534 1535 return InterpretResult(n, sc); 1536 1537 } 1538 } 1539 1540 class PipelineExpression : Expression { 1541 Expression e1; 1542 Expression e2; 1543 CallExpression ce; 1544 ScriptLocation loc; 1545 1546 this(ScriptLocation loc, Expression e1, Expression e2) { 1547 this.loc = loc; 1548 this.e1 = e1; 1549 this.e2 = e2; 1550 1551 if(auto ce = cast(CallExpression) e2) { 1552 this.ce = new CallExpression(loc, ce.func); 1553 this.ce.arguments = [e1] ~ ce.arguments; 1554 } else { 1555 this.ce = new CallExpression(loc, e2); 1556 this.ce.arguments ~= e1; 1557 } 1558 } 1559 1560 override string toString() { return e1.toString() ~ " |> " ~ e2.toString(); } 1561 1562 override InterpretResult interpret(PrototypeObject sc) { 1563 return ce.interpret(sc); 1564 } 1565 } 1566 1567 class AssignExpression : Expression { 1568 Expression e1; 1569 Expression e2; 1570 bool suppressOverloading; 1571 1572 this(Expression e1, Expression e2, bool suppressOverloading = false) { 1573 this.e1 = e1; 1574 this.e2 = e2; 1575 this.suppressOverloading = suppressOverloading; 1576 } 1577 1578 override string toString() { return e1.toString() ~ " = " ~ e2.toString(); } 1579 1580 override InterpretResult interpret(PrototypeObject sc) { 1581 auto v = cast(VariableExpression) e1; 1582 if(v is null) 1583 throw new ScriptRuntimeException("not an lvalue", null, 0 /* FIXME */); 1584 1585 auto ret = v.setVar(sc, e2 is null ? var(null) : e2.interpret(sc).value, false, suppressOverloading); 1586 1587 return InterpretResult(ret, sc); 1588 } 1589 } 1590 class VariableExpression : Expression { 1591 string identifier; 1592 ScriptLocation loc; 1593 1594 this(string identifier, ScriptLocation loc = ScriptLocation.init) { 1595 this.identifier = identifier; 1596 this.loc = loc; 1597 } 1598 1599 override string toString() { 1600 return identifier; 1601 } 1602 1603 override string toInterpretedString(PrototypeObject sc) { 1604 return getVar(sc).get!string; 1605 } 1606 1607 ref var getVar(PrototypeObject sc, bool recurse = true, bool returnRawProperty = false) { 1608 try { 1609 return sc._getMember(identifier, true /* FIXME: recurse?? */, true, returnRawProperty); 1610 } catch(DynamicTypeException dte) { 1611 dte.callStack ~= loc; 1612 throw dte; 1613 } 1614 } 1615 1616 ref var setVar(PrototypeObject sc, var t, bool recurse = true, bool suppressOverloading = false) { 1617 return sc._setMember(identifier, t, true /* FIXME: recurse?? */, true, suppressOverloading); 1618 } 1619 1620 ref var getVarFrom(PrototypeObject sc, ref var v, bool returnRawProperty) { 1621 if(returnRawProperty) { 1622 if(v.payloadType == var.Type.Object) 1623 return v._payload._object._getMember(identifier, true, false, returnRawProperty); 1624 } 1625 1626 return v[identifier]; 1627 } 1628 1629 override InterpretResult interpret(PrototypeObject sc) { 1630 return InterpretResult(getVar(sc), sc); 1631 } 1632 } 1633 1634 class SuperExpression : Expression { 1635 VariableExpression dot; 1636 string origDot; 1637 this(VariableExpression dot) { 1638 if(dot !is null) { 1639 origDot = dot.identifier; 1640 //dot.identifier = "__super_" ~ dot.identifier; // omg this is so bad 1641 } 1642 this.dot = dot; 1643 } 1644 1645 override string toString() { 1646 if(dot is null) 1647 return "super"; 1648 else 1649 return "super." ~ origDot; 1650 } 1651 1652 override InterpretResult interpret(PrototypeObject sc) { 1653 var a = sc._getMember("super", true, true); 1654 if(a._object is null) 1655 throw new Exception("null proto for super"); 1656 PrototypeObject proto = a._object.prototype; 1657 if(proto is null) 1658 throw new Exception("no super"); 1659 //proto = proto.prototype; 1660 1661 if(dot !is null) 1662 a = proto._getMember(dot.identifier, true, true); 1663 else 1664 a = proto._getMember("__ctor", true, true); 1665 return InterpretResult(a, sc); 1666 } 1667 } 1668 1669 class DotVarExpression : VariableExpression { 1670 Expression e1; 1671 VariableExpression e2; 1672 bool recurse = true; 1673 1674 this(Expression e1) { 1675 this.e1 = e1; 1676 super(null); 1677 } 1678 1679 this(Expression e1, VariableExpression e2, bool recurse = true) { 1680 this.e1 = e1; 1681 this.e2 = e2; 1682 this.recurse = recurse; 1683 //assert(typeid(e2) == typeid(VariableExpression)); 1684 super("<do not use>");//e1.identifier ~ "." ~ e2.identifier); 1685 } 1686 1687 override string toString() { 1688 return e1.toString() ~ "." ~ e2.toString(); 1689 } 1690 1691 override ref var getVar(PrototypeObject sc, bool recurse = true, bool returnRawProperty = false) { 1692 if(!this.recurse) { 1693 // this is a special hack... 1694 if(auto ve = cast(VariableExpression) e1) { 1695 return ve.getVar(sc)._getOwnProperty(e2.identifier); 1696 } 1697 assert(0); 1698 } 1699 1700 if(e2.identifier == "__source") { 1701 auto val = e1.interpret(sc).value; 1702 if(auto meta = cast(ScriptFunctionMetadata) val._metadata) 1703 return *(new var(meta.convertToString())); 1704 else 1705 return *(new var(val.toJson())); 1706 } 1707 1708 if(auto ve = cast(VariableExpression) e1) { 1709 return this.getVarFrom(sc, ve.getVar(sc, recurse), returnRawProperty); 1710 } else if(cast(StringLiteralExpression) e1 && e2.identifier == "interpolate") { 1711 auto se = cast(StringLiteralExpression) e1; 1712 var* functor = new var; 1713 //if(!se.allowInterpolation) 1714 //throw new ScriptRuntimeException("Cannot interpolate this string", se.token.lineNumber); 1715 (*functor)._function = (var _this, var[] args) { 1716 return se.interpolate(args.length ? args[0] : var(null), sc); 1717 }; 1718 return *functor; 1719 } else { 1720 // make a temporary for the lhs 1721 auto v = new var(); 1722 *v = e1.interpret(sc).value; 1723 return this.getVarFrom(sc, *v, returnRawProperty); 1724 } 1725 } 1726 1727 override ref var setVar(PrototypeObject sc, var t, bool recurse = true, bool suppressOverloading = false) { 1728 if(suppressOverloading) 1729 return e1.interpret(sc).value.opIndexAssignNoOverload(t, e2.identifier); 1730 else 1731 return e1.interpret(sc).value.opIndexAssign(t, e2.identifier); 1732 } 1733 1734 1735 override ref var getVarFrom(PrototypeObject sc, ref var v, bool returnRawProperty) { 1736 return e2.getVarFrom(sc, v, returnRawProperty); 1737 } 1738 1739 override string toInterpretedString(PrototypeObject sc) { 1740 return getVar(sc).get!string; 1741 } 1742 } 1743 1744 class IndexExpression : VariableExpression { 1745 Expression e1; 1746 Expression e2; 1747 1748 this(Expression e1, Expression e2) { 1749 this.e1 = e1; 1750 this.e2 = e2; 1751 super(null); 1752 } 1753 1754 override string toString() { 1755 return e1.toString() ~ "[" ~ e2.toString() ~ "]"; 1756 } 1757 1758 override ref var getVar(PrototypeObject sc, bool recurse = true, bool returnRawProperty = false) { 1759 if(auto ve = cast(VariableExpression) e1) 1760 return ve.getVar(sc, recurse, returnRawProperty)[e2.interpret(sc).value]; 1761 else { 1762 auto v = new var(); 1763 *v = e1.interpret(sc).value; 1764 return this.getVarFrom(sc, *v, returnRawProperty); 1765 } 1766 } 1767 1768 override ref var setVar(PrototypeObject sc, var t, bool recurse = true, bool suppressOverloading = false) { 1769 return getVar(sc,recurse) = t; 1770 } 1771 } 1772 1773 class SliceExpression : Expression { 1774 // e1[e2 .. e3] 1775 Expression e1; 1776 Expression e2; 1777 Expression e3; 1778 1779 this(Expression e1, Expression e2, Expression e3) { 1780 this.e1 = e1; 1781 this.e2 = e2; 1782 this.e3 = e3; 1783 } 1784 1785 override string toString() { 1786 return e1.toString() ~ "[" ~ e2.toString() ~ " .. " ~ e3.toString() ~ "]"; 1787 } 1788 1789 override InterpretResult interpret(PrototypeObject sc) { 1790 var lhs = e1.interpret(sc).value; 1791 1792 auto specialScope = new PrototypeObject(); 1793 specialScope.prototype = sc; 1794 specialScope._getMember("$", false, false) = lhs.length; 1795 1796 return InterpretResult(lhs[e2.interpret(specialScope).value .. e3.interpret(specialScope).value], sc); 1797 } 1798 } 1799 1800 1801 class LoopControlExpression : Expression { 1802 InterpretResult.FlowControl op; 1803 this(string op) { 1804 if(op == "continue") 1805 this.op = InterpretResult.FlowControl.Continue; 1806 else if(op == "break") 1807 this.op = InterpretResult.FlowControl.Break; 1808 else assert(0, op); 1809 } 1810 1811 override string toString() { 1812 import std.string; 1813 return to!string(this.op).toLower(); 1814 } 1815 1816 override InterpretResult interpret(PrototypeObject sc) { 1817 return InterpretResult(var(null), sc, op); 1818 } 1819 } 1820 1821 1822 class ReturnExpression : Expression { 1823 Expression value; 1824 1825 this(Expression v) { 1826 value = v; 1827 } 1828 1829 override string toString() { return "return " ~ value.toString(); } 1830 1831 override InterpretResult interpret(PrototypeObject sc) { 1832 return InterpretResult(value.interpret(sc).value, sc, InterpretResult.FlowControl.Return); 1833 } 1834 } 1835 1836 class ScopeExpression : Expression { 1837 this(Expression[] expressions) { 1838 this.expressions = expressions; 1839 } 1840 1841 Expression[] expressions; 1842 1843 override string toString() { 1844 string s; 1845 s = "{\n"; 1846 foreach(expr; expressions) { 1847 s ~= "\t"; 1848 s ~= expr.toString(); 1849 s ~= ";\n"; 1850 } 1851 s ~= "}"; 1852 return s; 1853 } 1854 1855 override InterpretResult interpret(PrototypeObject sc) { 1856 var ret; 1857 1858 auto innerScope = new PrototypeObject(); 1859 innerScope.prototype = sc; 1860 1861 innerScope._getMember("__scope_exit", false, false) = var.emptyArray; 1862 innerScope._getMember("__scope_success", false, false) = var.emptyArray; 1863 innerScope._getMember("__scope_failure", false, false) = var.emptyArray; 1864 1865 scope(exit) { 1866 foreach(func; innerScope._getMember("__scope_exit", false, true)) 1867 func(); 1868 } 1869 scope(success) { 1870 foreach(func; innerScope._getMember("__scope_success", false, true)) 1871 func(); 1872 } 1873 scope(failure) { 1874 foreach(func; innerScope._getMember("__scope_failure", false, true)) 1875 func(); 1876 } 1877 1878 foreach(expression; expressions) { 1879 auto res = expression.interpret(innerScope); 1880 ret = res.value; 1881 if(res.flowControl != InterpretResult.FlowControl.Normal) 1882 return InterpretResult(ret, sc, res.flowControl); 1883 } 1884 return InterpretResult(ret, sc); 1885 } 1886 } 1887 1888 class SwitchExpression : Expression { 1889 Expression expr; 1890 CaseExpression[] cases; 1891 CaseExpression default_; 1892 1893 override InterpretResult interpret(PrototypeObject sc) { 1894 auto e = expr.interpret(sc); 1895 1896 bool hitAny; 1897 bool fallingThrough; 1898 bool secondRun; 1899 1900 var last; 1901 1902 again: 1903 foreach(c; cases) { 1904 if(!secondRun && !fallingThrough && c is default_) continue; 1905 if(fallingThrough || (secondRun && c is default_) || c.condition.interpret(sc) == e) { 1906 fallingThrough = false; 1907 if(!secondRun) 1908 hitAny = true; 1909 InterpretResult ret; 1910 expr_loop: foreach(exp; c.expressions) { 1911 ret = exp.interpret(sc); 1912 with(InterpretResult.FlowControl) 1913 final switch(ret.flowControl) { 1914 case Normal: 1915 last = ret.value; 1916 break; 1917 case Return: 1918 case Goto: 1919 return ret; 1920 case Continue: 1921 fallingThrough = true; 1922 break expr_loop; 1923 case Break: 1924 return InterpretResult(last, sc); 1925 } 1926 } 1927 1928 if(!fallingThrough) 1929 break; 1930 } 1931 } 1932 1933 if(!hitAny && !secondRun) { 1934 secondRun = true; 1935 goto again; 1936 } 1937 1938 return InterpretResult(last, sc); 1939 } 1940 } 1941 1942 class CaseExpression : Expression { 1943 this(Expression condition) { 1944 this.condition = condition; 1945 } 1946 Expression condition; 1947 Expression[] expressions; 1948 1949 override string toString() { 1950 string code; 1951 if(condition is null) 1952 code = "default:"; 1953 else 1954 code = "case " ~ condition.toString() ~ ":"; 1955 1956 foreach(expr; expressions) 1957 code ~= "\n" ~ expr.toString() ~ ";"; 1958 1959 return code; 1960 } 1961 1962 override InterpretResult interpret(PrototypeObject sc) { 1963 // I did this inline up in the SwitchExpression above. maybe insane?! 1964 assert(0); 1965 } 1966 } 1967 1968 unittest { 1969 interpret(q{ 1970 var a = 10; 1971 // case and break should work 1972 var brk; 1973 1974 // var brk = switch doesn't parse, but this will..... 1975 // (I kinda went everything is an expression but not all the way. this code SUX.) 1976 brk = switch(a) { 1977 case 10: 1978 a = 30; 1979 break; 1980 case 30: 1981 a = 40; 1982 break; 1983 default: 1984 a = 0; 1985 } 1986 1987 assert(a == 30); 1988 assert(brk == 30); // value of switch set to last expression evaled inside 1989 1990 // so should default 1991 switch(a) { 1992 case 20: 1993 a = 40; 1994 break; 1995 default: 1996 a = 40; 1997 } 1998 1999 assert(a == 40); 2000 2001 switch(a) { 2002 case 40: 2003 a = 50; 2004 case 60: // no implicit fallthrough in this lang... 2005 a = 60; 2006 } 2007 2008 assert(a == 50); 2009 2010 var ret; 2011 2012 ret = switch(a) { 2013 case 50: 2014 a = 60; 2015 continue; // request fallthrough. D uses "goto case", but I haven't implemented any goto yet so continue is best fit 2016 case 90: 2017 a = 70; 2018 } 2019 2020 assert(a == 70); // the explicit `continue` requests fallthrough behavior 2021 assert(ret == 70); 2022 }); 2023 } 2024 2025 unittest { 2026 // overloads 2027 interpret(q{ 2028 function foo(int a) { return 10 + a; } 2029 function foo(float a) { return 100 + a; } 2030 function foo(string a) { return "string " ~ a; } 2031 2032 assert(foo(4) == 14); 2033 assert(foo(4.5) == 104.5); 2034 assert(foo("test") == "string test"); 2035 2036 // can redefine specific override 2037 function foo(int a) { return a; } 2038 assert(foo(4) == 4); 2039 // leaving others in place 2040 assert(foo(4.5) == 104.5); 2041 assert(foo("test") == "string test"); 2042 }); 2043 } 2044 2045 unittest { 2046 // catching objects 2047 interpret(q{ 2048 class Foo {} 2049 class Bar : Foo {} 2050 2051 var res = try throw new Bar(); catch(Bar b) { 2 } catch(e) { 1 }; 2052 assert(res == 2); 2053 2054 var res = try throw new Foo(); catch(Bar b) { 2 } catch(e) { 1 }; 2055 assert(res == 1); 2056 2057 var res = try throw Foo; catch(Foo b) { 2 } catch(e) { 1 }; 2058 assert(res == 2); 2059 }); 2060 } 2061 2062 unittest { 2063 // ternary precedence 2064 interpret(q{ 2065 assert(0 == 0 ? true : false == true); 2066 assert((0 == 0) ? true : false == true); 2067 // lol FIXME 2068 //assert(((0 == 0) ? true : false) == true); 2069 }); 2070 } 2071 2072 unittest { 2073 // new nested class 2074 interpret(q{ 2075 class A {} 2076 A.b = class B { var c; this(a) { this.c = a; } } 2077 var c = new A.b(5); 2078 assert(A.b.c == null); 2079 assert(c.c == 5); 2080 }); 2081 } 2082 2083 unittest { 2084 interpret(q{ 2085 assert(0x10 == 16); 2086 assert(0o10 == 8); 2087 assert(0b10 == 2); 2088 assert(10 == 10); 2089 assert(10_10 == 1010); 2090 }); 2091 } 2092 2093 class ForeachExpression : Expression { 2094 VariableDeclaration decl; 2095 Expression subject; 2096 Expression subject2; 2097 Expression loopBody; 2098 2099 override string toString() { 2100 return "foreach(" ~ decl.toString() ~ "; " ~ subject.toString() ~ ((subject2 is null) ? "" : (".." ~ subject2.toString)) ~ ") " ~ loopBody.toString(); 2101 } 2102 2103 override InterpretResult interpret(PrototypeObject sc) { 2104 var result; 2105 2106 assert(loopBody !is null); 2107 2108 auto loopScope = new PrototypeObject(); 2109 loopScope.prototype = sc; 2110 2111 InterpretResult.FlowControl flowControl; 2112 2113 static string doLoopBody() { return q{ 2114 if(decl.identifiers.length > 1) { 2115 sc._getMember(decl.identifiers[0], false, false) = i; 2116 sc._getMember(decl.identifiers[1], false, false) = item; 2117 } else { 2118 sc._getMember(decl.identifiers[0], false, false) = item; 2119 } 2120 2121 auto res = loopBody.interpret(loopScope); 2122 result = res.value; 2123 flowControl = res.flowControl; 2124 if(flowControl == InterpretResult.FlowControl.Break) 2125 break; 2126 if(flowControl == InterpretResult.FlowControl.Return) 2127 break; 2128 //if(flowControl == InterpretResult.FlowControl.Continue) 2129 // this is fine, we still want to do the advancement 2130 };} 2131 2132 var what = subject.interpret(sc).value; 2133 var termination = subject2 is null ? var(null) : subject2.interpret(sc).value; 2134 if(what.payloadType == var.Type.Integral && subject2 is null) { 2135 // loop from 0 to what 2136 int end = what.get!int; 2137 foreach(item; 0 .. end) { 2138 auto i = item; 2139 mixin(doLoopBody()); 2140 } 2141 } else if(what.payloadType == var.Type.Integral && termination.payloadType == var.Type.Integral) { 2142 // loop what .. termination 2143 int start = what.get!int; 2144 int end = termination.get!int; 2145 int stride; 2146 if(end < start) { 2147 stride = -1; 2148 } else { 2149 stride = 1; 2150 } 2151 int i = -1; 2152 for(int item = start; item != end; item += stride) { 2153 i++; 2154 mixin(doLoopBody()); 2155 } 2156 } else { 2157 if(subject2 !is null) 2158 throw new ScriptRuntimeException("foreach( a .. b ) invalid unless a is an integer", null, 0); // FIXME 2159 foreach(i, item; what) { 2160 mixin(doLoopBody()); 2161 } 2162 } 2163 2164 if(flowControl != InterpretResult.FlowControl.Return) 2165 flowControl = InterpretResult.FlowControl.Normal; 2166 2167 return InterpretResult(result, sc, flowControl); 2168 } 2169 } 2170 2171 class ForExpression : Expression { 2172 Expression initialization; 2173 Expression condition; 2174 Expression advancement; 2175 Expression loopBody; 2176 2177 this() {} 2178 2179 override InterpretResult interpret(PrototypeObject sc) { 2180 var result; 2181 2182 assert(loopBody !is null); 2183 2184 auto loopScope = new PrototypeObject(); 2185 loopScope.prototype = sc; 2186 if(initialization !is null) 2187 initialization.interpret(loopScope); 2188 2189 InterpretResult.FlowControl flowControl; 2190 2191 static string doLoopBody() { return q{ 2192 auto res = loopBody.interpret(loopScope); 2193 result = res.value; 2194 flowControl = res.flowControl; 2195 if(flowControl == InterpretResult.FlowControl.Break) 2196 break; 2197 if(flowControl == InterpretResult.FlowControl.Return) 2198 break; 2199 //if(flowControl == InterpretResult.FlowControl.Continue) 2200 // this is fine, we still want to do the advancement 2201 if(advancement) 2202 advancement.interpret(loopScope); 2203 };} 2204 2205 if(condition !is null) { 2206 while(condition.interpret(loopScope).value) { 2207 mixin(doLoopBody()); 2208 } 2209 } else 2210 while(true) { 2211 mixin(doLoopBody()); 2212 } 2213 2214 if(flowControl != InterpretResult.FlowControl.Return) 2215 flowControl = InterpretResult.FlowControl.Normal; 2216 2217 return InterpretResult(result, sc, flowControl); 2218 } 2219 2220 override string toString() { 2221 string code = "for("; 2222 if(initialization !is null) 2223 code ~= initialization.toString(); 2224 code ~= "; "; 2225 if(condition !is null) 2226 code ~= condition.toString(); 2227 code ~= "; "; 2228 if(advancement !is null) 2229 code ~= advancement.toString(); 2230 code ~= ") "; 2231 code ~= loopBody.toString(); 2232 2233 return code; 2234 } 2235 } 2236 2237 class IfExpression : Expression { 2238 Expression condition; 2239 Expression ifTrue; 2240 Expression ifFalse; 2241 2242 this() {} 2243 2244 override InterpretResult interpret(PrototypeObject sc) { 2245 InterpretResult result; 2246 assert(condition !is null); 2247 2248 auto ifScope = new PrototypeObject(); 2249 ifScope.prototype = sc; 2250 2251 if(condition.interpret(ifScope).value) { 2252 if(ifTrue !is null) 2253 result = ifTrue.interpret(ifScope); 2254 } else { 2255 if(ifFalse !is null) 2256 result = ifFalse.interpret(ifScope); 2257 } 2258 return InterpretResult(result.value, sc, result.flowControl); 2259 } 2260 2261 override string toString() { 2262 string code = "if "; 2263 code ~= condition.toString(); 2264 code ~= " "; 2265 if(ifTrue !is null) 2266 code ~= ifTrue.toString(); 2267 else 2268 code ~= " { }"; 2269 if(ifFalse !is null) 2270 code ~= " else " ~ ifFalse.toString(); 2271 return code; 2272 } 2273 } 2274 2275 class TernaryExpression : Expression { 2276 Expression condition; 2277 Expression ifTrue; 2278 Expression ifFalse; 2279 2280 this() {} 2281 2282 override InterpretResult interpret(PrototypeObject sc) { 2283 InterpretResult result; 2284 assert(condition !is null); 2285 2286 auto ifScope = new PrototypeObject(); 2287 ifScope.prototype = sc; 2288 2289 if(condition.interpret(ifScope).value) { 2290 result = ifTrue.interpret(ifScope); 2291 } else { 2292 result = ifFalse.interpret(ifScope); 2293 } 2294 return InterpretResult(result.value, sc, result.flowControl); 2295 } 2296 2297 override string toString() { 2298 string code = ""; 2299 code ~= condition.toString(); 2300 code ~= " ? "; 2301 code ~= ifTrue.toString(); 2302 code ~= " : "; 2303 code ~= ifFalse.toString(); 2304 return code; 2305 } 2306 } 2307 2308 // this is kinda like a placement new, and currently isn't exposed inside the language, 2309 // but is used for class inheritance 2310 class ShallowCopyExpression : Expression { 2311 Expression e1; 2312 Expression e2; 2313 2314 this(Expression e1, Expression e2) { 2315 this.e1 = e1; 2316 this.e2 = e2; 2317 } 2318 2319 override InterpretResult interpret(PrototypeObject sc) { 2320 auto v = cast(VariableExpression) e1; 2321 if(v is null) 2322 throw new ScriptRuntimeException("not an lvalue", null, 0 /* FIXME */); 2323 2324 v.getVar(sc, false)._object.copyPropertiesFrom(e2.interpret(sc).value._object); 2325 2326 return InterpretResult(var(null), sc); 2327 } 2328 2329 } 2330 2331 class NewExpression : Expression { 2332 Expression what; 2333 Expression[] args; 2334 this(Expression w) { 2335 what = w; 2336 } 2337 2338 override InterpretResult interpret(PrototypeObject sc) { 2339 assert(what !is null); 2340 2341 var[] args; 2342 foreach(arg; this.args) 2343 args ~= arg.interpret(sc).value; 2344 2345 var original = what.interpret(sc).value; 2346 var n = original._copy_new; 2347 if(n.payloadType() == var.Type.Object) { 2348 var ctor = original.prototype ? original.prototype._getOwnProperty("__ctor") : var(null); 2349 if(ctor) 2350 ctor.apply(n, args); 2351 } 2352 2353 return InterpretResult(n, sc); 2354 } 2355 } 2356 2357 class ThrowExpression : Expression { 2358 Expression whatToThrow; 2359 ScriptToken where; 2360 2361 this(Expression e, ScriptToken where) { 2362 whatToThrow = e; 2363 this.where = where; 2364 } 2365 2366 override InterpretResult interpret(PrototypeObject sc) { 2367 assert(whatToThrow !is null); 2368 throw new ScriptException(whatToThrow.interpret(sc).value, ScriptLocation(where.scriptFilename, where.lineNumber)); 2369 assert(0); 2370 } 2371 } 2372 2373 bool isCompatibleType(var v, string specifier, PrototypeObject sc) { 2374 var t = toType(specifier, sc); 2375 auto score = typeCompatibilityScore(v, t); 2376 return score > 0; 2377 } 2378 2379 var toType(string specifier, PrototypeObject sc) { 2380 switch(specifier) { 2381 case "int", "long": return var(0); 2382 case "float", "double": return var(0.0); 2383 case "string": return var(""); 2384 default: 2385 auto got = sc._peekMember(specifier, true); 2386 if(got) 2387 return *got; 2388 else 2389 return var.init; 2390 } 2391 } 2392 2393 var[] toTypes(string[] specifiers, PrototypeObject sc) { 2394 var[] arr; 2395 foreach(s; specifiers) 2396 arr ~= toType(s, sc); 2397 return arr; 2398 } 2399 2400 2401 class ExceptionBlockExpression : Expression { 2402 Expression tryExpression; 2403 2404 string[] catchVarDecls; 2405 string[] catchVarTypeSpecifiers; 2406 Expression[] catchExpressions; 2407 2408 Expression[] finallyExpressions; 2409 2410 override InterpretResult interpret(PrototypeObject sc) { 2411 InterpretResult result; 2412 result.sc = sc; 2413 assert(tryExpression !is null); 2414 assert(catchVarDecls.length == catchExpressions.length); 2415 2416 void caught(var ex) { 2417 if(catchExpressions.length) 2418 foreach(i, ce; catchExpressions) { 2419 if(catchVarTypeSpecifiers[i].length == 0 || isCompatibleType(ex, catchVarTypeSpecifiers[i], sc)) { 2420 auto catchScope = new PrototypeObject(); 2421 catchScope.prototype = sc; 2422 catchScope._getMember(catchVarDecls[i], false, false) = ex; 2423 2424 result = ce.interpret(catchScope); 2425 break; 2426 } 2427 } else 2428 result = InterpretResult(ex, sc); 2429 } 2430 2431 if(catchExpressions.length || (catchExpressions.length == 0 && finallyExpressions.length == 0)) 2432 try { 2433 result = tryExpression.interpret(sc); 2434 } catch(NonScriptCatchableException e) { 2435 // the script cannot catch these so it continues up regardless 2436 throw e; 2437 } catch(ScriptException e) { 2438 // FIXME: what about the other information here? idk. 2439 caught(e.payload); 2440 } catch(Exception e) { 2441 var ex = var.emptyObject; 2442 ex.type = typeid(e).name; 2443 ex.msg = e.msg; 2444 ex.file = e.file; 2445 ex.line = e.line; 2446 2447 caught(ex); 2448 } finally { 2449 foreach(fe; finallyExpressions) 2450 result = fe.interpret(sc); 2451 } 2452 else 2453 try { 2454 result = tryExpression.interpret(sc); 2455 } finally { 2456 foreach(fe; finallyExpressions) 2457 result = fe.interpret(sc); 2458 } 2459 2460 return result; 2461 } 2462 } 2463 2464 class ParentheticalExpression : Expression { 2465 Expression inside; 2466 this(Expression inside) { 2467 this.inside = inside; 2468 } 2469 2470 override string toString() { 2471 return "(" ~ inside.toString() ~ ")"; 2472 } 2473 2474 override InterpretResult interpret(PrototypeObject sc) { 2475 return InterpretResult(inside.interpret(sc).value, sc); 2476 } 2477 } 2478 2479 class AssertKeyword : Expression { 2480 ScriptToken token; 2481 this(ScriptToken token) { 2482 this.token = token; 2483 } 2484 override string toString() { 2485 return "assert"; 2486 } 2487 2488 override InterpretResult interpret(PrototypeObject sc) { 2489 if(AssertKeywordObject is null) 2490 AssertKeywordObject = new PrototypeObject(); 2491 var dummy; 2492 dummy._object = AssertKeywordObject; 2493 return InterpretResult(dummy, sc); 2494 } 2495 } 2496 2497 PrototypeObject AssertKeywordObject; 2498 PrototypeObject DefaultArgumentDummyObject; 2499 2500 class CallExpression : Expression { 2501 Expression func; 2502 Expression[] arguments; 2503 ScriptLocation loc; 2504 2505 override string toString() { 2506 string s = func.toString() ~ "("; 2507 foreach(i, arg; arguments) { 2508 if(i) s ~= ", "; 2509 s ~= arg.toString(); 2510 } 2511 2512 s ~= ")"; 2513 return s; 2514 } 2515 2516 this(ScriptLocation loc, Expression func) { 2517 this.loc = loc; 2518 this.func = func; 2519 } 2520 2521 override string toInterpretedString(PrototypeObject sc) { 2522 return interpret(sc).value.get!string; 2523 } 2524 2525 override InterpretResult interpret(PrototypeObject sc) { 2526 if(auto asrt = cast(AssertKeyword) func) { 2527 auto assertExpression = arguments[0]; 2528 Expression assertString; 2529 if(arguments.length > 1) 2530 assertString = arguments[1]; 2531 2532 var v = assertExpression.interpret(sc).value; 2533 2534 if(!v) 2535 throw new ScriptException( 2536 var(this.toString() ~ " failed, got: " ~ assertExpression.toInterpretedString(sc)), 2537 ScriptLocation(asrt.token.scriptFilename, asrt.token.lineNumber)); 2538 2539 return InterpretResult(v, sc); 2540 } 2541 2542 auto f = func.interpret(sc).value; 2543 bool isMacro = (f.payloadType == var.Type.Object && ((cast(MacroPrototype) f._payload._object) !is null)); 2544 var[] args; 2545 foreach(argument; arguments) 2546 if(argument !is null) { 2547 if(isMacro) // macro, pass the argument as an expression object 2548 args ~= argument.toScriptExpressionObject(sc); 2549 else // regular function, interpret the arguments 2550 args ~= argument.interpret(sc).value; 2551 } else { 2552 if(DefaultArgumentDummyObject is null) 2553 DefaultArgumentDummyObject = new PrototypeObject(); 2554 2555 var dummy; 2556 dummy._object = DefaultArgumentDummyObject; 2557 2558 args ~= dummy; 2559 } 2560 2561 var _this; 2562 if(auto dve = cast(DotVarExpression) func) { 2563 _this = dve.e1.interpret(sc).value; 2564 } else if(auto ide = cast(IndexExpression) func) { 2565 _this = ide.interpret(sc).value; 2566 } else if(auto se = cast(SuperExpression) func) { 2567 // super things are passed this object despite looking things up on the prototype 2568 // so it calls the correct instance 2569 _this = sc._getMember("this", true, true); 2570 } 2571 2572 try { 2573 return InterpretResult(f.apply(_this, args), sc); 2574 } catch(DynamicTypeException dte) { 2575 dte.callStack ~= loc; 2576 throw dte; 2577 } catch(ScriptException se) { 2578 se.callStack ~= loc; 2579 throw se; 2580 } 2581 } 2582 } 2583 2584 ScriptToken requireNextToken(MyTokenStreamHere)(ref MyTokenStreamHere tokens, ScriptToken.Type type, string str = null, string file = __FILE__, size_t line = __LINE__) { 2585 if(tokens.empty) 2586 throw new ScriptCompileException("script ended prematurely", null, 0, file, line); 2587 auto next = tokens.front; 2588 if(next.type != type || (str !is null && next.str != str)) 2589 throw new ScriptCompileException("unexpected '"~next.str~"' while expecting " ~ to!string(type) ~ " " ~ str, next.scriptFilename, next.lineNumber, file, line); 2590 2591 tokens.popFront(); 2592 return next; 2593 } 2594 2595 bool peekNextToken(MyTokenStreamHere)(MyTokenStreamHere tokens, ScriptToken.Type type, string str = null, string file = __FILE__, size_t line = __LINE__) { 2596 if(tokens.empty) 2597 return false; 2598 auto next = tokens.front; 2599 if(next.type != type || (str !is null && next.str != str)) 2600 return false; 2601 return true; 2602 } 2603 2604 VariableExpression parseVariableName(MyTokenStreamHere)(ref MyTokenStreamHere tokens) { 2605 assert(!tokens.empty); 2606 auto token = tokens.front; 2607 if(token.type == ScriptToken.Type.identifier) { 2608 tokens.popFront(); 2609 return new VariableExpression(token.str, ScriptLocation(token.scriptFilename, token.lineNumber)); 2610 } 2611 throw new ScriptCompileException("Found "~token.str~" when expecting identifier", token.scriptFilename, token.lineNumber); 2612 } 2613 2614 Expression parseDottedVariableName(MyTokenStreamHere)(ref MyTokenStreamHere tokens) { 2615 assert(!tokens.empty); 2616 2617 auto ve = parseVariableName(tokens); 2618 2619 auto token = tokens.front; 2620 if(token.type == ScriptToken.Type.symbol && token.str == ".") { 2621 tokens.popFront(); 2622 return new DotVarExpression(ve, parseVariableName(tokens)); 2623 } 2624 return ve; 2625 } 2626 2627 2628 Expression parsePart(MyTokenStreamHere)(ref MyTokenStreamHere tokens) { 2629 if(!tokens.empty) { 2630 auto token = tokens.front; 2631 2632 Expression e; 2633 2634 if(token.str == "super") { 2635 tokens.popFront(); 2636 VariableExpression dot; 2637 if(!tokens.empty && tokens.front.str == ".") { 2638 tokens.popFront(); 2639 dot = parseVariableName(tokens); 2640 } 2641 e = new SuperExpression(dot); 2642 } 2643 else if(token.type == ScriptToken.Type.identifier) 2644 e = parseVariableName(tokens); 2645 else if(token.type == ScriptToken.Type.symbol && (token.str == "-" || token.str == "+" || token.str == "!" || token.str == "~")) { 2646 auto op = token.str; 2647 tokens.popFront(); 2648 2649 e = parsePart(tokens); 2650 if(op == "-") 2651 e = new NegationExpression(e); 2652 else if(op == "!") 2653 e = new NotExpression(e); 2654 else if(op == "~") 2655 e = new BitFlipExpression(e); 2656 } else { 2657 tokens.popFront(); 2658 2659 if(token.type == ScriptToken.Type.int_number) 2660 e = new IntLiteralExpression(token.str, 10); 2661 else if(token.type == ScriptToken.Type.oct_number) 2662 e = new IntLiteralExpression(token.str, 8); 2663 else if(token.type == ScriptToken.Type.hex_number) 2664 e = new IntLiteralExpression(token.str, 16); 2665 else if(token.type == ScriptToken.Type.binary_number) 2666 e = new IntLiteralExpression(token.str, 2); 2667 else if(token.type == ScriptToken.Type.float_number) 2668 e = new FloatLiteralExpression(token.str); 2669 else if(token.type == ScriptToken.Type..string) 2670 e = new StringLiteralExpression(token); 2671 else if(token.type == ScriptToken.Type.symbol || token.type == ScriptToken.Type.keyword) { 2672 switch(token.str) { 2673 case "true": 2674 case "false": 2675 e = new BoolLiteralExpression(token.str); 2676 break; 2677 case "new": 2678 // FIXME: why is this needed here? maybe it should be here instead of parseExpression 2679 tokens.pushFront(token); 2680 return parseExpression(tokens); 2681 case "(": 2682 //tokens.popFront(); 2683 auto parenthetical = new ParentheticalExpression(parseExpression(tokens)); 2684 tokens.requireNextToken(ScriptToken.Type.symbol, ")"); 2685 2686 return parenthetical; 2687 case "[": 2688 // array literal 2689 auto arr = new ArrayLiteralExpression(); 2690 2691 bool first = true; 2692 moreElements: 2693 if(tokens.empty) 2694 throw new ScriptCompileException("unexpected end of file when reading array literal", token.scriptFilename, token.lineNumber); 2695 2696 auto peek = tokens.front; 2697 if(peek.type == ScriptToken.Type.symbol && peek.str == "]") { 2698 tokens.popFront(); 2699 return arr; 2700 } 2701 2702 if(!first) 2703 tokens.requireNextToken(ScriptToken.Type.symbol, ","); 2704 else 2705 first = false; 2706 2707 arr.elements ~= parseExpression(tokens); 2708 2709 goto moreElements; 2710 case "json!q{": 2711 case "#{": 2712 // json object literal 2713 auto obj = new ObjectLiteralExpression(); 2714 /* 2715 these go 2716 2717 string or ident which is the key 2718 then a colon 2719 then an expression which is the value 2720 2721 then optionally a comma 2722 2723 then either } which finishes it, or another key 2724 */ 2725 2726 if(tokens.empty) 2727 throw new ScriptCompileException("unexpected end of file when reading object literal", token.scriptFilename, token.lineNumber); 2728 2729 moreKeys: 2730 auto key = tokens.front; 2731 tokens.popFront(); 2732 if(key.type == ScriptToken.Type.symbol && key.str == "}") { 2733 // all done! 2734 e = obj; 2735 break; 2736 } 2737 if(key.type != ScriptToken.Type..string && key.type != ScriptToken.Type.identifier) { 2738 throw new ScriptCompileException("unexpected '"~key.str~"' when reading object literal", key.scriptFilename, key.lineNumber); 2739 2740 } 2741 2742 tokens.requireNextToken(ScriptToken.Type.symbol, ":"); 2743 2744 auto value = parseExpression(tokens); 2745 if(tokens.empty) 2746 throw new ScriptCompileException("unclosed object literal", key.scriptFilename, key.lineNumber); 2747 2748 if(tokens.peekNextToken(ScriptToken.Type.symbol, ",")) 2749 tokens.popFront(); 2750 2751 obj.elements[key.str] = value; 2752 2753 goto moreKeys; 2754 case "macro": 2755 case "function": 2756 tokens.requireNextToken(ScriptToken.Type.symbol, "("); 2757 2758 auto exp = new FunctionLiteralExpression(); 2759 if(!tokens.peekNextToken(ScriptToken.Type.symbol, ")")) 2760 exp.arguments = parseVariableDeclaration(tokens, ")"); 2761 2762 tokens.requireNextToken(ScriptToken.Type.symbol, ")"); 2763 2764 exp.functionBody = parseExpression(tokens); 2765 exp.isMacro = token.str == "macro"; 2766 2767 e = exp; 2768 break; 2769 case "null": 2770 e = new NullLiteralExpression(); 2771 break; 2772 case "mixin": 2773 case "eval": 2774 tokens.requireNextToken(ScriptToken.Type.symbol, "("); 2775 e = new MixinExpression(parseExpression(tokens)); 2776 tokens.requireNextToken(ScriptToken.Type.symbol, ")"); 2777 break; 2778 default: 2779 goto unknown; 2780 } 2781 } else { 2782 unknown: 2783 throw new ScriptCompileException("unexpected '"~token.str~"' when reading ident", token.scriptFilename, token.lineNumber); 2784 } 2785 } 2786 2787 funcLoop: while(!tokens.empty) { 2788 auto peek = tokens.front; 2789 if(peek.type == ScriptToken.Type.symbol) { 2790 switch(peek.str) { 2791 case "(": 2792 e = parseFunctionCall(tokens, e); 2793 break; 2794 case "[": 2795 tokens.popFront(); 2796 auto e1 = parseExpression(tokens); 2797 if(tokens.peekNextToken(ScriptToken.Type.symbol, "..")) { 2798 tokens.popFront(); 2799 e = new SliceExpression(e, e1, parseExpression(tokens)); 2800 } else { 2801 e = new IndexExpression(e, e1); 2802 } 2803 tokens.requireNextToken(ScriptToken.Type.symbol, "]"); 2804 break; 2805 case ".": 2806 tokens.popFront(); 2807 e = new DotVarExpression(e, parseVariableName(tokens)); 2808 break; 2809 default: 2810 return e; // we don't know, punt it elsewhere 2811 } 2812 } else return e; // again, we don't know, so just punt it down the line 2813 } 2814 return e; 2815 } 2816 2817 throw new ScriptCompileException("Ran out of tokens when trying to parsePart", null, 0); 2818 } 2819 2820 Expression parseArguments(MyTokenStreamHere)(ref MyTokenStreamHere tokens, Expression exp, ref Expression[] where) { 2821 // arguments. 2822 auto peek = tokens.front; 2823 if(peek.type == ScriptToken.Type.symbol && peek.str == ")") { 2824 tokens.popFront(); 2825 return exp; 2826 } 2827 2828 moreArguments: 2829 2830 if(tokens.peekNextToken(ScriptToken.Type.keyword, "default")) { 2831 tokens.popFront(); 2832 where ~= null; 2833 } else { 2834 where ~= parseExpression(tokens); 2835 } 2836 2837 if(tokens.empty) 2838 throw new ScriptCompileException("unexpected end of file when parsing call expression", peek.scriptFilename, peek.lineNumber); 2839 peek = tokens.front; 2840 if(peek.type == ScriptToken.Type.symbol && peek.str == ",") { 2841 tokens.popFront(); 2842 goto moreArguments; 2843 } else if(peek.type == ScriptToken.Type.symbol && peek.str == ")") { 2844 tokens.popFront(); 2845 return exp; 2846 } else 2847 throw new ScriptCompileException("unexpected '"~peek.str~"' when reading argument list", peek.scriptFilename, peek.lineNumber); 2848 2849 } 2850 2851 Expression parseFunctionCall(MyTokenStreamHere)(ref MyTokenStreamHere tokens, Expression e) { 2852 assert(!tokens.empty); 2853 auto peek = tokens.front; 2854 auto exp = new CallExpression(ScriptLocation(peek.scriptFilename, peek.lineNumber), e); 2855 tokens.popFront(); 2856 if(tokens.empty) 2857 throw new ScriptCompileException("unexpected end of file when parsing call expression", peek.scriptFilename, peek.lineNumber); 2858 return parseArguments(tokens, exp, exp.arguments); 2859 } 2860 2861 Expression parseFactor(MyTokenStreamHere)(ref MyTokenStreamHere tokens) { 2862 auto e1 = parsePart(tokens); 2863 loop: while(!tokens.empty) { 2864 auto peek = tokens.front; 2865 2866 if(peek.type == ScriptToken.Type.symbol) { 2867 switch(peek.str) { 2868 case "<<": 2869 case ">>": 2870 case ">>>": 2871 case "*": 2872 case "/": 2873 case "%": 2874 tokens.popFront(); 2875 e1 = new BinaryExpression(peek.str, e1, parsePart(tokens)); 2876 break; 2877 default: 2878 break loop; 2879 } 2880 } else throw new Exception("Got " ~ peek.str ~ " when expecting symbol"); 2881 } 2882 2883 return e1; 2884 } 2885 2886 Expression parseAddend(MyTokenStreamHere)(ref MyTokenStreamHere tokens) { 2887 auto e1 = parseFactor(tokens); 2888 loop: while(!tokens.empty) { 2889 auto peek = tokens.front; 2890 2891 if(peek.type == ScriptToken.Type.symbol) { 2892 switch(peek.str) { 2893 case "..": // possible FIXME 2894 case ")": // possible FIXME 2895 case "]": // possible FIXME 2896 case "}": // possible FIXME 2897 case ",": // possible FIXME these are passed on to the next thing 2898 case ";": 2899 case ":": // idk 2900 case "?": 2901 return e1; 2902 2903 case "|>": 2904 tokens.popFront(); 2905 e1 = new PipelineExpression(ScriptLocation(peek.scriptFilename, peek.lineNumber), e1, parseFactor(tokens)); 2906 break; 2907 case ".": 2908 tokens.popFront(); 2909 e1 = new DotVarExpression(e1, parseVariableName(tokens)); 2910 break; 2911 case "=": 2912 tokens.popFront(); 2913 return new AssignExpression(e1, parseExpression(tokens)); 2914 case "&&": // thanks to mzfhhhh for fix 2915 case "||": 2916 tokens.popFront(); 2917 e1 = new BinaryExpression(peek.str, e1, parseExpression(tokens)); 2918 break; 2919 case "~": 2920 // FIXME: make sure this has the right associativity 2921 2922 case "&": 2923 case "|": 2924 case "^": 2925 2926 case "&=": 2927 case "|=": 2928 case "^=": 2929 2930 case "+": 2931 case "-": 2932 2933 case "==": 2934 case "!=": 2935 case "<=": 2936 case ">=": 2937 case "<": 2938 case ">": 2939 tokens.popFront(); 2940 e1 = new BinaryExpression(peek.str, e1, parseAddend(tokens)); 2941 break; 2942 case "+=": 2943 case "-=": 2944 case "*=": 2945 case "/=": 2946 case "~=": 2947 case "%=": 2948 tokens.popFront(); 2949 return new OpAssignExpression(peek.str[0..1], e1, parseExpression(tokens)); 2950 default: 2951 throw new ScriptCompileException("Parse error, unexpected " ~ peek.str ~ " when looking for operator", peek.scriptFilename, peek.lineNumber); 2952 } 2953 //} else if(peek.type == ScriptToken.Type.identifier || peek.type == ScriptToken.Type.number) { 2954 //return parseFactor(tokens); 2955 } else 2956 throw new ScriptCompileException("Parse error, unexpected '" ~ peek.str ~ "'", peek.scriptFilename, peek.lineNumber); 2957 } 2958 2959 return e1; 2960 } 2961 2962 Expression parseExpression(MyTokenStreamHere)(ref MyTokenStreamHere tokens, bool consumeEnd = false) { 2963 Expression ret; 2964 ScriptToken first; 2965 string expectedEnd = ";"; 2966 //auto e1 = parseFactor(tokens); 2967 2968 while(tokens.peekNextToken(ScriptToken.Type.symbol, ";")) { 2969 tokens.popFront(); 2970 } 2971 if(!tokens.empty) { 2972 first = tokens.front; 2973 if(tokens.peekNextToken(ScriptToken.Type.symbol, "{")) { 2974 auto start = tokens.front; 2975 tokens.popFront(); 2976 auto e = parseCompoundStatement(tokens, start.lineNumber, "}").array; 2977 ret = new ScopeExpression(e); 2978 expectedEnd = null; // {} don't need ; at the end 2979 } else if(tokens.peekNextToken(ScriptToken.Type.keyword, "scope")) { 2980 auto start = tokens.front; 2981 tokens.popFront(); 2982 tokens.requireNextToken(ScriptToken.Type.symbol, "("); 2983 2984 auto ident = tokens.requireNextToken(ScriptToken.Type.identifier); 2985 switch(ident.str) { 2986 case "success": 2987 case "failure": 2988 case "exit": 2989 break; 2990 default: 2991 throw new ScriptCompileException("unexpected " ~ ident.str ~ ". valid scope(idents) are success, failure, and exit", ident.scriptFilename, ident.lineNumber); 2992 } 2993 2994 tokens.requireNextToken(ScriptToken.Type.symbol, ")"); 2995 2996 string i = "__scope_" ~ ident.str; 2997 auto literal = new FunctionLiteralExpression(); 2998 literal.functionBody = parseExpression(tokens); 2999 3000 auto e = new OpAssignExpression("~", new VariableExpression(i), literal); 3001 ret = e; 3002 } else if(tokens.peekNextToken(ScriptToken.Type.symbol, "(")) { 3003 auto start = tokens.front; 3004 tokens.popFront(); 3005 auto parenthetical = new ParentheticalExpression(parseExpression(tokens)); 3006 tokens.requireNextToken(ScriptToken.Type.symbol, ")"); 3007 if(tokens.peekNextToken(ScriptToken.Type.symbol, "(")) { 3008 // we have a function call, e.g. (test)() 3009 ret = parseFunctionCall(tokens, parenthetical); 3010 } else 3011 ret = parenthetical; 3012 } else if(tokens.peekNextToken(ScriptToken.Type.keyword, "new")) { 3013 auto start = tokens.front; 3014 tokens.popFront(); 3015 3016 auto expr = parseDottedVariableName(tokens); 3017 auto ne = new NewExpression(expr); 3018 if(tokens.peekNextToken(ScriptToken.Type.symbol, "(")) { 3019 tokens.popFront(); 3020 parseArguments(tokens, ne, ne.args); 3021 } 3022 3023 ret = ne; 3024 } else if(tokens.peekNextToken(ScriptToken.Type.keyword, "class")) { 3025 auto start = tokens.front; 3026 tokens.popFront(); 3027 3028 Expression[] expressions; 3029 3030 // the way classes work is they are actually object literals with a different syntax. new foo then just copies it 3031 /* 3032 we create a prototype object 3033 we create an object, with that prototype 3034 3035 set all functions and static stuff to the prototype 3036 the rest goes to the object 3037 3038 the expression returns the object we made 3039 */ 3040 3041 auto vars = new VariableDeclaration(); 3042 vars.identifiers = ["__proto", "__obj"]; 3043 3044 auto staticScopeBacking = new PrototypeObject(); 3045 auto instanceScopeBacking = new PrototypeObject(); 3046 3047 vars.initializers = [new ObjectLiteralExpression(staticScopeBacking), new ObjectLiteralExpression(instanceScopeBacking)]; 3048 expressions ~= vars; 3049 3050 // FIXME: operators need to have their this be bound somehow since it isn't passed 3051 // OR the op rewrite could pass this 3052 3053 expressions ~= new AssignExpression( 3054 new DotVarExpression(new VariableExpression("__obj"), new VariableExpression("prototype")), 3055 new VariableExpression("__proto")); 3056 3057 auto classIdent = tokens.requireNextToken(ScriptToken.Type.identifier); 3058 3059 expressions ~= new AssignExpression( 3060 new DotVarExpression(new VariableExpression("__proto"), new VariableExpression("__classname")), 3061 new StringLiteralExpression(classIdent.str)); 3062 3063 if(tokens.peekNextToken(ScriptToken.Type.symbol, ":")) { 3064 tokens.popFront(); 3065 auto inheritFrom = tokens.requireNextToken(ScriptToken.Type.identifier); 3066 3067 // we set our prototype to the Foo prototype, thereby inheriting any static data that way (includes functions) 3068 // the inheritFrom object itself carries instance data that we need to copy onto our instance 3069 expressions ~= new AssignExpression( 3070 new DotVarExpression(new VariableExpression("__proto"), new VariableExpression("prototype")), 3071 new DotVarExpression(new VariableExpression(inheritFrom.str), new VariableExpression("prototype"))); 3072 3073 expressions ~= new AssignExpression( 3074 new DotVarExpression(new VariableExpression("__proto"), new VariableExpression("super")), 3075 new VariableExpression(inheritFrom.str) 3076 ); 3077 3078 // and copying the instance initializer from the parent 3079 expressions ~= new ShallowCopyExpression(new VariableExpression("__obj"), new VariableExpression(inheritFrom.str)); 3080 } 3081 3082 tokens.requireNextToken(ScriptToken.Type.symbol, "{"); 3083 3084 void addVarDecl(VariableDeclaration decl, string o) { 3085 foreach(i, ident; decl.identifiers) { 3086 // FIXME: make sure this goes on the instance, never the prototype! 3087 expressions ~= new AssignExpression( 3088 new DotVarExpression( 3089 new VariableExpression(o), 3090 new VariableExpression(ident), 3091 false), 3092 decl.initializers[i], 3093 true // no overloading because otherwise an early opIndexAssign can mess up the decls 3094 ); 3095 } 3096 } 3097 3098 // FIXME: we could actually add private vars and just put them in this scope. maybe 3099 3100 while(!tokens.peekNextToken(ScriptToken.Type.symbol, "}")) { 3101 if(tokens.peekNextToken(ScriptToken.Type.symbol, ";")) { 3102 tokens.popFront(); 3103 continue; 3104 } 3105 3106 if(tokens.peekNextToken(ScriptToken.Type.identifier, "this")) { 3107 // ctor 3108 tokens.popFront(); 3109 tokens.requireNextToken(ScriptToken.Type.symbol, "("); 3110 auto args = parseVariableDeclaration(tokens, ")"); 3111 tokens.requireNextToken(ScriptToken.Type.symbol, ")"); 3112 auto bod = parseExpression(tokens); 3113 3114 expressions ~= new AssignExpression( 3115 new DotVarExpression( 3116 new VariableExpression("__proto"), 3117 new VariableExpression("__ctor")), 3118 new FunctionLiteralExpression(args, bod, staticScopeBacking)); 3119 } else if(tokens.peekNextToken(ScriptToken.Type.keyword, "var")) { 3120 // instance variable 3121 auto decl = parseVariableDeclaration(tokens, ";"); 3122 addVarDecl(decl, "__obj"); 3123 } else if(tokens.peekNextToken(ScriptToken.Type.keyword, "static")) { 3124 // prototype var 3125 tokens.popFront(); 3126 auto decl = parseVariableDeclaration(tokens, ";"); 3127 addVarDecl(decl, "__proto"); 3128 } else if(tokens.peekNextToken(ScriptToken.Type.keyword, "function")) { 3129 // prototype function 3130 tokens.popFront(); 3131 auto ident = tokens.requireNextToken(ScriptToken.Type.identifier); 3132 3133 tokens.requireNextToken(ScriptToken.Type.symbol, "("); 3134 VariableDeclaration args; 3135 if(!tokens.peekNextToken(ScriptToken.Type.symbol, ")")) 3136 args = parseVariableDeclaration(tokens, ")"); 3137 tokens.requireNextToken(ScriptToken.Type.symbol, ")"); 3138 auto bod = parseExpression(tokens); 3139 3140 expressions ~= new FunctionDeclaration( 3141 new DotVarExpression( 3142 new VariableExpression("__proto"), 3143 new VariableExpression(ident.str), 3144 false), 3145 ident.str, 3146 new FunctionLiteralExpression(args, bod, staticScopeBacking) 3147 ); 3148 } else throw new ScriptCompileException("Unexpected " ~ tokens.front.str ~ " when reading class decl", tokens.front.scriptFilename, tokens.front.lineNumber); 3149 } 3150 3151 tokens.requireNextToken(ScriptToken.Type.symbol, "}"); 3152 3153 // returning he object from the scope... 3154 expressions ~= new VariableExpression("__obj"); 3155 3156 auto scopeExpr = new ScopeExpression(expressions); 3157 auto classVarExpr = new VariableDeclaration(); 3158 classVarExpr.identifiers = [classIdent.str]; 3159 classVarExpr.initializers = [scopeExpr]; 3160 3161 ret = classVarExpr; 3162 } else if(tokens.peekNextToken(ScriptToken.Type.keyword, "if")) { 3163 tokens.popFront(); 3164 auto e = new IfExpression(); 3165 e.condition = parseExpression(tokens); 3166 e.ifTrue = parseExpression(tokens); 3167 if(tokens.peekNextToken(ScriptToken.Type.symbol, ";")) { 3168 tokens.popFront(); 3169 } 3170 if(tokens.peekNextToken(ScriptToken.Type.keyword, "else")) { 3171 tokens.popFront(); 3172 e.ifFalse = parseExpression(tokens); 3173 } 3174 ret = e; 3175 } else if(tokens.peekNextToken(ScriptToken.Type.keyword, "switch")) { 3176 tokens.popFront(); 3177 auto e = new SwitchExpression(); 3178 tokens.requireNextToken(ScriptToken.Type.symbol, "("); 3179 e.expr = parseExpression(tokens); 3180 tokens.requireNextToken(ScriptToken.Type.symbol, ")"); 3181 3182 tokens.requireNextToken(ScriptToken.Type.symbol, "{"); 3183 3184 while(!tokens.peekNextToken(ScriptToken.Type.symbol, "}")) { 3185 3186 if(tokens.peekNextToken(ScriptToken.Type.keyword, "case")) { 3187 auto start = tokens.front; 3188 tokens.popFront(); 3189 auto c = new CaseExpression(parseExpression(tokens)); 3190 e.cases ~= c; 3191 tokens.requireNextToken(ScriptToken.Type.symbol, ":"); 3192 3193 while(!tokens.peekNextToken(ScriptToken.Type.keyword, "default") && !tokens.peekNextToken(ScriptToken.Type.keyword, "case") && !tokens.peekNextToken(ScriptToken.Type.symbol, "}")) { 3194 c.expressions ~= parseStatement(tokens); 3195 while(tokens.peekNextToken(ScriptToken.Type.symbol, ";")) 3196 tokens.popFront(); 3197 } 3198 } else if(tokens.peekNextToken(ScriptToken.Type.keyword, "default")) { 3199 tokens.popFront(); 3200 tokens.requireNextToken(ScriptToken.Type.symbol, ":"); 3201 3202 auto c = new CaseExpression(null); 3203 3204 while(!tokens.peekNextToken(ScriptToken.Type.keyword, "case") && !tokens.peekNextToken(ScriptToken.Type.symbol, "}")) { 3205 c.expressions ~= parseStatement(tokens); 3206 while(tokens.peekNextToken(ScriptToken.Type.symbol, ";")) 3207 tokens.popFront(); 3208 } 3209 3210 e.cases ~= c; 3211 e.default_ = c; 3212 } else throw new ScriptCompileException("A switch statement must consists of cases and a default, nothing else ", tokens.front.scriptFilename, tokens.front.lineNumber); 3213 } 3214 3215 tokens.requireNextToken(ScriptToken.Type.symbol, "}"); 3216 expectedEnd = ""; 3217 3218 ret = e; 3219 3220 } else if(tokens.peekNextToken(ScriptToken.Type.keyword, "foreach")) { 3221 tokens.popFront(); 3222 auto e = new ForeachExpression(); 3223 tokens.requireNextToken(ScriptToken.Type.symbol, "("); 3224 e.decl = parseVariableDeclaration(tokens, ";"); 3225 tokens.requireNextToken(ScriptToken.Type.symbol, ";"); 3226 e.subject = parseExpression(tokens); 3227 3228 if(tokens.peekNextToken(ScriptToken.Type.symbol, "..")) { 3229 tokens.popFront; 3230 e.subject2 = parseExpression(tokens); 3231 } 3232 3233 tokens.requireNextToken(ScriptToken.Type.symbol, ")"); 3234 e.loopBody = parseExpression(tokens); 3235 ret = e; 3236 3237 expectedEnd = ""; 3238 } else if(tokens.peekNextToken(ScriptToken.Type.keyword, "cast")) { 3239 tokens.popFront(); 3240 auto e = new CastExpression(); 3241 3242 tokens.requireNextToken(ScriptToken.Type.symbol, "("); 3243 e.type = tokens.requireNextToken(ScriptToken.Type.identifier).str; 3244 if(tokens.peekNextToken(ScriptToken.Type.symbol, "[")) { 3245 e.type ~= "[]"; 3246 tokens.popFront(); 3247 tokens.requireNextToken(ScriptToken.Type.symbol, "]"); 3248 } 3249 tokens.requireNextToken(ScriptToken.Type.symbol, ")"); 3250 3251 e.e1 = parseExpression(tokens); 3252 ret = e; 3253 } else if(tokens.peekNextToken(ScriptToken.Type.keyword, "for")) { 3254 tokens.popFront(); 3255 auto e = new ForExpression(); 3256 tokens.requireNextToken(ScriptToken.Type.symbol, "("); 3257 e.initialization = parseStatement(tokens, ";"); 3258 3259 tokens.requireNextToken(ScriptToken.Type.symbol, ";"); 3260 3261 e.condition = parseExpression(tokens); 3262 tokens.requireNextToken(ScriptToken.Type.symbol, ";"); 3263 e.advancement = parseExpression(tokens); 3264 tokens.requireNextToken(ScriptToken.Type.symbol, ")"); 3265 e.loopBody = parseExpression(tokens); 3266 3267 ret = e; 3268 3269 expectedEnd = ""; 3270 } else if(tokens.peekNextToken(ScriptToken.Type.keyword, "while")) { 3271 tokens.popFront(); 3272 auto e = new ForExpression(); 3273 e.condition = parseExpression(tokens); 3274 e.loopBody = parseExpression(tokens); 3275 ret = e; 3276 expectedEnd = ""; 3277 } else if(tokens.peekNextToken(ScriptToken.Type.keyword, "break") || tokens.peekNextToken(ScriptToken.Type.keyword, "continue")) { 3278 auto token = tokens.front; 3279 tokens.popFront(); 3280 ret = new LoopControlExpression(token.str); 3281 } else if(tokens.peekNextToken(ScriptToken.Type.keyword, "return")) { 3282 tokens.popFront(); 3283 Expression retVal; 3284 if(tokens.peekNextToken(ScriptToken.Type.symbol, ";")) 3285 retVal = new NullLiteralExpression(); 3286 else 3287 retVal = parseExpression(tokens); 3288 ret = new ReturnExpression(retVal); 3289 } else if(tokens.peekNextToken(ScriptToken.Type.keyword, "throw")) { 3290 auto token = tokens.front; 3291 tokens.popFront(); 3292 ret = new ThrowExpression(parseExpression(tokens), token); 3293 } else if(tokens.peekNextToken(ScriptToken.Type.keyword, "try")) { 3294 auto tryToken = tokens.front; 3295 auto e = new ExceptionBlockExpression(); 3296 tokens.popFront(); 3297 e.tryExpression = parseExpression(tokens, true); 3298 3299 bool hadFinally = false; 3300 while(tokens.peekNextToken(ScriptToken.Type.keyword, "catch")) { 3301 if(hadFinally) 3302 throw new ScriptCompileException("Catch must come before finally", tokens.front.scriptFilename, tokens.front.lineNumber); 3303 tokens.popFront(); 3304 tokens.requireNextToken(ScriptToken.Type.symbol, "("); 3305 if(tokens.peekNextToken(ScriptToken.Type.keyword, "var")) 3306 tokens.popFront(); 3307 3308 auto ident = tokens.requireNextToken(ScriptToken.Type.identifier); 3309 if(tokens.empty) throw new ScriptCompileException("Catch specifier not closed", ident.scriptFilename, ident.lineNumber); 3310 auto next = tokens.front; 3311 if(next.type == ScriptToken.Type.identifier) { 3312 auto type = ident; 3313 ident = next; 3314 3315 e.catchVarTypeSpecifiers ~= type.str; 3316 e.catchVarDecls ~= ident.str; 3317 3318 tokens.popFront(); 3319 3320 tokens.requireNextToken(ScriptToken.Type.symbol, ")"); 3321 } else { 3322 e.catchVarTypeSpecifiers ~= null; 3323 e.catchVarDecls ~= ident.str; 3324 if(next.type != ScriptToken.Type.symbol || next.str != ")") 3325 throw new ScriptCompileException("ss Unexpected " ~ next.str ~ " when expecting ')'", next.scriptFilename, next.lineNumber); 3326 tokens.popFront(); 3327 } 3328 e.catchExpressions ~= parseExpression(tokens); 3329 } 3330 while(tokens.peekNextToken(ScriptToken.Type.keyword, "finally")) { 3331 hadFinally = true; 3332 tokens.popFront(); 3333 e.finallyExpressions ~= parseExpression(tokens); 3334 } 3335 3336 //if(!hadSomething) 3337 //throw new ScriptCompileException("Parse error, missing finally or catch after try", tryToken.lineNumber); 3338 3339 ret = e; 3340 } else { 3341 ret = parseAddend(tokens); 3342 } 3343 3344 if(!tokens.empty && tokens.peekNextToken(ScriptToken.Type.symbol, "?")) { 3345 auto e = new TernaryExpression(); 3346 e.condition = ret; 3347 tokens.requireNextToken(ScriptToken.Type.symbol, "?"); 3348 e.ifTrue = parseExpression(tokens); 3349 tokens.requireNextToken(ScriptToken.Type.symbol, ":"); 3350 e.ifFalse = parseExpression(tokens); 3351 ret = e; 3352 } 3353 } else { 3354 //assert(0); 3355 // return null; 3356 throw new ScriptCompileException("Parse error, unexpected end of input when reading expression", null, 0);//token.lineNumber); 3357 } 3358 3359 //writeln("parsed expression ", ret.toString()); 3360 3361 if(expectedEnd.length && tokens.empty && consumeEnd) // going loose on final ; at the end of input for repl convenience 3362 throw new ScriptCompileException("Parse error, unexpected end of input when reading expression, expecting " ~ expectedEnd, first.scriptFilename, first.lineNumber); 3363 3364 if(expectedEnd.length && consumeEnd) { 3365 if(tokens.peekNextToken(ScriptToken.Type.symbol, expectedEnd)) 3366 tokens.popFront(); 3367 // FIXME 3368 //if(tokens.front.type != ScriptToken.Type.symbol && tokens.front.str != expectedEnd) 3369 //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); 3370 // tokens = tokens[1 .. $]; 3371 } 3372 3373 return ret; 3374 } 3375 3376 VariableDeclaration parseVariableDeclaration(MyTokenStreamHere)(ref MyTokenStreamHere tokens, string termination) { 3377 VariableDeclaration decl = new VariableDeclaration(); 3378 bool equalOk; 3379 anotherVar: 3380 assert(!tokens.empty); 3381 3382 auto firstToken = tokens.front; 3383 3384 // var a, var b is acceptable 3385 if(tokens.peekNextToken(ScriptToken.Type.keyword, "var")) 3386 tokens.popFront(); 3387 3388 equalOk= true; 3389 if(tokens.empty) 3390 throw new ScriptCompileException("Parse error, dangling var at end of file", firstToken.scriptFilename, firstToken.lineNumber); 3391 3392 string type; 3393 3394 auto next = tokens.front; 3395 tokens.popFront; 3396 if(tokens.empty) 3397 throw new ScriptCompileException("Parse error, incomplete var declaration at end of file", firstToken.scriptFilename, firstToken.lineNumber); 3398 auto next2 = tokens.front; 3399 3400 ScriptToken typeSpecifier; 3401 3402 /* if there's two identifiers back to back, it is a type specifier. otherwise just a name */ 3403 3404 if(next.type == ScriptToken.Type.identifier && next2.type == ScriptToken.Type.identifier) { 3405 // type ident; 3406 typeSpecifier = next; 3407 next = next2; 3408 // get past the type 3409 tokens.popFront(); 3410 } else { 3411 // no type, just carry on with the next thing 3412 } 3413 3414 Expression initializer; 3415 auto identifier = next; 3416 if(identifier.type != ScriptToken.Type.identifier) 3417 throw new ScriptCompileException("Parse error, found '"~identifier.str~"' when expecting var identifier", identifier.scriptFilename, identifier.lineNumber); 3418 3419 //tokens.popFront(); 3420 3421 tryTermination: 3422 if(tokens.empty) 3423 throw new ScriptCompileException("Parse error, missing ; after var declaration at end of file", firstToken.scriptFilename, firstToken.lineNumber); 3424 3425 auto peek = tokens.front; 3426 if(peek.type == ScriptToken.Type.symbol) { 3427 if(peek.str == "=") { 3428 if(!equalOk) 3429 throw new ScriptCompileException("Parse error, unexpected '"~identifier.str~"' after reading var initializer", peek.scriptFilename, peek.lineNumber); 3430 equalOk = false; 3431 tokens.popFront(); 3432 initializer = parseExpression(tokens); 3433 goto tryTermination; 3434 } else if(peek.str == ",") { 3435 tokens.popFront(); 3436 decl.identifiers ~= identifier.str; 3437 decl.initializers ~= initializer; 3438 decl.typeSpecifiers ~= typeSpecifier.str; 3439 goto anotherVar; 3440 } else if(peek.str == termination) { 3441 decl.identifiers ~= identifier.str; 3442 decl.initializers ~= initializer; 3443 decl.typeSpecifiers ~= typeSpecifier.str; 3444 //tokens = tokens[1 .. $]; 3445 // we're done! 3446 } else 3447 throw new ScriptCompileException("Parse error, unexpected '"~peek.str~"' when reading var declaration symbol", peek.scriptFilename, peek.lineNumber); 3448 } else 3449 throw new ScriptCompileException("Parse error, unexpected non-symbol '"~peek.str~"' when reading var declaration", peek.scriptFilename, peek.lineNumber); 3450 3451 return decl; 3452 } 3453 3454 Expression parseStatement(MyTokenStreamHere)(ref MyTokenStreamHere tokens, string terminatingSymbol = null) { 3455 skip: // FIXME 3456 if(tokens.empty) 3457 return null; 3458 3459 if(terminatingSymbol !is null && (tokens.front.type == ScriptToken.Type.symbol && tokens.front.str == terminatingSymbol)) 3460 return null; // we're done 3461 3462 auto token = tokens.front; 3463 3464 // tokens = tokens[1 .. $]; 3465 final switch(token.type) { 3466 case ScriptToken.Type.keyword: 3467 case ScriptToken.Type.symbol: 3468 switch(token.str) { 3469 // assert 3470 case "assert": 3471 tokens.popFront(); 3472 3473 return parseFunctionCall(tokens, new AssertKeyword(token)); 3474 3475 //break; 3476 // declarations 3477 case "var": 3478 return parseVariableDeclaration(tokens, ";"); 3479 case ";": 3480 tokens.popFront(); // FIXME 3481 goto skip; 3482 // literals 3483 case "function": 3484 case "macro": 3485 // function can be a literal, or a declaration. 3486 3487 tokens.popFront(); // we're peeking ahead 3488 3489 if(tokens.peekNextToken(ScriptToken.Type.identifier)) { 3490 // decl style, rewrite it into var ident = function style 3491 // tokens.popFront(); // skipping the function keyword // already done above with the popFront 3492 auto ident = tokens.front; 3493 tokens.popFront(); 3494 3495 tokens.requireNextToken(ScriptToken.Type.symbol, "("); 3496 3497 auto exp = new FunctionLiteralExpression(); 3498 if(!tokens.peekNextToken(ScriptToken.Type.symbol, ")")) 3499 exp.arguments = parseVariableDeclaration(tokens, ")"); 3500 tokens.requireNextToken(ScriptToken.Type.symbol, ")"); 3501 3502 exp.functionBody = parseExpression(tokens); 3503 3504 // a ; should NOT be required here btw 3505 3506 exp.isMacro = token.str == "macro"; 3507 3508 auto e = new FunctionDeclaration(null, ident.str, exp); 3509 3510 return e; 3511 3512 } else { 3513 tokens.pushFront(token); // put it back since everyone expects us to have done that 3514 goto case; // handle it like any other expression 3515 } 3516 3517 case "true": 3518 case "false": 3519 3520 case "json!{": 3521 case "#{": 3522 case "[": 3523 case "(": 3524 case "null": 3525 3526 // scope 3527 case "{": 3528 case "scope": 3529 3530 case "cast": 3531 3532 // classes 3533 case "class": 3534 case "new": 3535 3536 case "super": 3537 3538 // flow control 3539 case "if": 3540 case "while": 3541 case "for": 3542 case "foreach": 3543 case "switch": 3544 3545 // exceptions 3546 case "try": 3547 case "throw": 3548 3549 // evals 3550 case "eval": 3551 case "mixin": 3552 3553 // flow 3554 case "continue": 3555 case "break": 3556 case "return": 3557 return parseExpression(tokens); 3558 // unary prefix operators 3559 case "!": 3560 case "~": 3561 case "-": 3562 return parseExpression(tokens); 3563 3564 // BTW add custom object operator overloading to struct var 3565 // and custom property overloading to PrototypeObject 3566 3567 default: 3568 // whatever else keyword or operator related is actually illegal here 3569 throw new ScriptCompileException("Parse error, unexpected " ~ token.str, token.scriptFilename, token.lineNumber); 3570 } 3571 // break; 3572 case ScriptToken.Type.identifier: 3573 case ScriptToken.Type..string: 3574 case ScriptToken.Type.int_number: 3575 case ScriptToken.Type.float_number: 3576 case ScriptToken.Type.binary_number: 3577 case ScriptToken.Type.hex_number: 3578 case ScriptToken.Type.oct_number: 3579 return parseExpression(tokens); 3580 } 3581 3582 assert(0); 3583 } 3584 3585 // FIXME someday this should work, my parser is so bad 3586 // until then put parens around your == stuff. 3587 version(none) 3588 unittest { 3589 interpret(q{ 3590 var a = 5; 3591 var b = false; 3592 assert(a == 5 || b); 3593 }); 3594 } 3595 version(none) 3596 unittest { 3597 interpret(q{ 3598 var a = 5; 3599 var b = false; 3600 assert(((a == 5) || b)); 3601 }); 3602 } 3603 3604 3605 struct CompoundStatementRange(MyTokenStreamHere) { 3606 // FIXME: if MyTokenStreamHere is not a class, this fails! 3607 MyTokenStreamHere tokens; 3608 int startingLine; 3609 string terminatingSymbol; 3610 bool isEmpty; 3611 3612 this(MyTokenStreamHere t, int startingLine, string terminatingSymbol) { 3613 tokens = t; 3614 this.startingLine = startingLine; 3615 this.terminatingSymbol = terminatingSymbol; 3616 popFront(); 3617 } 3618 3619 bool empty() { 3620 return isEmpty; 3621 } 3622 3623 Expression got; 3624 3625 Expression front() { 3626 return got; 3627 } 3628 3629 void popFront() { 3630 while(!tokens.empty && (terminatingSymbol is null || !(tokens.front.type == ScriptToken.Type.symbol && tokens.front.str == terminatingSymbol))) { 3631 auto n = parseStatement(tokens, terminatingSymbol); 3632 if(n is null) 3633 continue; 3634 got = n; 3635 return; 3636 } 3637 3638 if(tokens.empty && terminatingSymbol !is null) { 3639 throw new ScriptCompileException("Reached end of file while trying to reach matching " ~ terminatingSymbol, null, startingLine); 3640 } 3641 3642 if(terminatingSymbol !is null) { 3643 assert(tokens.front.str == terminatingSymbol); 3644 tokens.skipNext++; 3645 } 3646 3647 isEmpty = true; 3648 } 3649 } 3650 3651 CompoundStatementRange!MyTokenStreamHere 3652 //Expression[] 3653 parseCompoundStatement(MyTokenStreamHere)(ref MyTokenStreamHere tokens, int startingLine = 1, string terminatingSymbol = null) { 3654 return (CompoundStatementRange!MyTokenStreamHere(tokens, startingLine, terminatingSymbol)); 3655 } 3656 3657 auto parseScript(MyTokenStreamHere)(MyTokenStreamHere tokens) { 3658 /* 3659 the language's grammar is simple enough 3660 3661 maybe flow control should be statements though lol. they might not make sense inside. 3662 3663 Expressions: 3664 var identifier; 3665 var identifier = initializer; 3666 var identifier, identifier2 3667 3668 return expression; 3669 return ; 3670 3671 json!{ object literal } 3672 3673 { scope expression } 3674 3675 [ array literal ] 3676 other literal 3677 function (arg list) other expression 3678 3679 ( expression ) // parenthesized expression 3680 operator expression // unary expression 3681 3682 expression operator expression // binary expression 3683 expression (other expression... args) // function call 3684 3685 Binary Operator precedence : 3686 . [] 3687 * / 3688 + - 3689 ~ 3690 < > == != 3691 = 3692 */ 3693 3694 return parseCompoundStatement(tokens); 3695 } 3696 3697 var interpretExpressions(ExpressionStream)(ExpressionStream expressions, PrototypeObject variables) if(is(ElementType!ExpressionStream == Expression)) { 3698 assert(variables !is null); 3699 var ret; 3700 foreach(expression; expressions) { 3701 auto res = expression.interpret(variables); 3702 variables = res.sc; 3703 ret = res.value; 3704 } 3705 return ret; 3706 } 3707 3708 var interpretStream(MyTokenStreamHere)(MyTokenStreamHere tokens, PrototypeObject variables) if(is(ElementType!MyTokenStreamHere == ScriptToken)) { 3709 assert(variables !is null); 3710 // this is an entry point that all others lead to, right before getting to interpretExpressions... 3711 3712 return interpretExpressions(parseScript(tokens), variables); 3713 } 3714 3715 var interpretStream(MyTokenStreamHere)(MyTokenStreamHere tokens, var variables) if(is(ElementType!MyTokenStreamHere == ScriptToken)) { 3716 return interpretStream(tokens, 3717 (variables.payloadType() == var.Type.Object && variables._payload._object !is null) ? variables._payload._object : new PrototypeObject()); 3718 } 3719 3720 var interpret(string code, PrototypeObject variables, string scriptFilename = null) { 3721 assert(variables !is null); 3722 return interpretStream(lexScript(repeat(code, 1), scriptFilename), variables); 3723 } 3724 3725 /++ 3726 This is likely your main entry point to the interpreter. It will interpret the script code 3727 given, with the given global variable object (which will be modified by the script, meaning 3728 you can pass it to subsequent calls to `interpret` to store context), and return the result 3729 of the last expression given. 3730 3731 --- 3732 var globals = var.emptyObject; // the global object must be an object of some type 3733 globals.x = 10; 3734 globals.y = 15; 3735 // you can also set global functions through this same style, etc 3736 3737 var result = interpret(`x + y`, globals); 3738 assert(result == 25); 3739 --- 3740 3741 3742 $(TIP 3743 If you want to just call a script function, interpret the definition of it, 3744 then just call it through the `globals` object you passed to it. 3745 3746 --- 3747 var globals = var.emptyObject; 3748 interpret(`function foo(name) { return "hello, " ~ name ~ "!"; }`, globals); 3749 var result = globals.foo()("world"); 3750 assert(result == "hello, world!"); 3751 --- 3752 ) 3753 3754 Params: 3755 code = the script source code you want to interpret 3756 scriptFilename = the filename of the script, if you want to provide it. Gives nicer error messages if you provide one. 3757 variables = The global object of the script context. It will be modified by the user script. 3758 3759 Returns: 3760 the result of the last expression evaluated by the script engine 3761 +/ 3762 var interpret(string code, var variables = null, string scriptFilename = null, string file = __FILE__, size_t line = __LINE__) { 3763 if(scriptFilename is null) 3764 scriptFilename = file ~ "@" ~ to!string(line); 3765 return interpretStream( 3766 lexScript(repeat(code, 1), scriptFilename), 3767 (variables.payloadType() == var.Type.Object && variables._payload._object !is null) ? variables._payload._object : new PrototypeObject()); 3768 } 3769 3770 /// 3771 var interpretFile(File file, var globals) { 3772 import std.algorithm; 3773 return interpretStream(lexScript(file.byLine.map!((a) => a.idup), file.name), 3774 (globals.payloadType() == var.Type.Object && globals._payload._object !is null) ? globals._payload._object : new PrototypeObject()); 3775 } 3776 3777 /// Enhanced repl uses arsd.terminal for better ux. Added April 26, 2020. Default just uses std.stdio. 3778 void repl(bool enhanced = false)(var globals) { 3779 static if(enhanced) { 3780 import arsd.terminal; 3781 Terminal terminal = Terminal(ConsoleOutputMode.linear); 3782 auto lines() { 3783 struct Range { 3784 string line; 3785 string front() { return line; } 3786 bool empty() { return line is null; } 3787 void popFront() { line = terminal.getline(": "); terminal.writeln(); } 3788 } 3789 Range r; 3790 r.popFront(); 3791 return r; 3792 3793 } 3794 3795 void writeln(T...)(T t) { 3796 terminal.writeln(t); 3797 terminal.flush(); 3798 } 3799 } else { 3800 import std.stdio; 3801 auto lines() { return stdin.byLine; } 3802 } 3803 3804 bool exited; 3805 if(globals == null) 3806 globals = var.emptyObject; 3807 globals.exit = () { exited = true; }; 3808 3809 import std.algorithm; 3810 auto variables = (globals.payloadType() == var.Type.Object && globals._payload._object !is null) ? globals._payload._object : new PrototypeObject(); 3811 3812 // we chain to ensure the priming popFront succeeds so we don't throw here 3813 auto tokens = lexScript( 3814 chain(["var __skipme = 0;"], map!((a) => a.idup)(lines)) 3815 , "stdin"); 3816 auto expressions = parseScript(tokens); 3817 3818 while(!exited && !expressions.empty) { 3819 try { 3820 expressions.popFront; 3821 auto expression = expressions.front; 3822 auto res = expression.interpret(variables); 3823 variables = res.sc; 3824 writeln(">>> ", res.value); 3825 } catch(ScriptCompileException e) { 3826 writeln("*+* ", e.msg); 3827 tokens.popFront(); // skip the one we threw on... 3828 } catch(Exception e) { 3829 writeln("*** ", e.msg); 3830 } 3831 } 3832 } 3833 3834 class ScriptFunctionMetadata : VarMetadata { 3835 FunctionLiteralExpression fle; 3836 this(FunctionLiteralExpression fle) { 3837 this.fle = fle; 3838 } 3839 3840 string convertToString() { 3841 return fle.toString(); 3842 } 3843 }