1 /** 2 This module implements the Interpreter class, the main class used by host applications to run scripts 3 4 ──────────────────────────────────────────────────────────────────────────────── 5 6 Copyright (C) 2021 pillager86.rf.gd 7 8 This program is free software: you can redistribute it and/or modify it under 9 the terms of the GNU General Public License as published by the Free Software 10 Foundation, either version 3 of the License, or (at your option) any later 11 version. 12 13 This program is distributed in the hope that it will be useful, but WITHOUT ANY 14 WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A 15 PARTICULAR PURPOSE. See the GNU General Public License for more details. 16 17 You should have received a copy of the GNU General Public License along with 18 this program. If not, see <https://www.gnu.org/licenses/>. 19 */ 20 module mildew.interpreter; 21 22 import std.typecons; 23 import std.variant; 24 25 import mildew.compiler; 26 import mildew.environment; 27 import mildew.exceptions: ScriptRuntimeException; 28 import mildew.lexer: Token, Lexer; 29 import mildew.nodes; 30 import mildew.parser; 31 import mildew.types; 32 import mildew.visitors; 33 import mildew.vm; 34 35 /** 36 * This is the main interface for the host application to interact with scripts. It can run scripts in 37 * interpreted mode by walking the syntax tree (deprecated), or if given the useVM option, can run a compiler to 38 * compile scripts into bytecode which is then executed by a VirtualMachine. Note that interpreted mode 39 * is deprecated. 40 */ 41 class Interpreter : INodeVisitor 42 { 43 public: 44 45 /** 46 * Constructs a new Interpreter with a global environment. Note that all calls to evaluate 47 * run in a new environment below the global environment. This allows keywords such as let and const 48 * to not pollute the global namespace. However, scripts can use var to declare variables that 49 * are global. 50 * Params: 51 * useVM = whether or not compilation to bytecode and the VM should be used instead of tree walking. 52 * printVMDebugInfo = if useVM is true, this option prints very verbose data while executing bytecode. 53 */ 54 this(bool useVM = false, bool printVMDebugInfo = true) 55 { 56 _globalEnvironment = new Environment(this); 57 _currentEnvironment = _globalEnvironment; 58 if(useVM) 59 { 60 _compiler = new Compiler(); 61 _printVMDebugInfo = printVMDebugInfo; 62 _vm = new VirtualMachine(_globalEnvironment); 63 } 64 } 65 66 /** 67 * Initializes the Mildew standard library, such as Object, Math, and console namespaces. This 68 * is optional and is not called by the constructor. For a script to use these methods such as 69 * console.log this must be called first. It is also possible to only call specific 70 * initialize*Library functions and/or force set globals from them to UNDEFINED. 71 */ 72 void initializeStdlib() 73 { 74 import mildew.types.bindings: initializeTypesLibrary; 75 import mildew.stdlib.global: initializeGlobalLibrary; 76 import mildew.stdlib.console: initializeConsoleLibrary; 77 import mildew.stdlib.date: initializeDateLibrary; 78 import mildew.stdlib.generator: initializeGeneratorLibrary; 79 import mildew.stdlib.math: initializeMathLibrary; 80 import mildew.stdlib.regexp: initializeRegExpLibrary; 81 import mildew.stdlib.system: initializeSystemLib; 82 initializeTypesLibrary(this); 83 initializeGlobalLibrary(this); 84 initializeConsoleLibrary(this); 85 initializeDateLibrary(this); 86 initializeGeneratorLibrary(this); 87 initializeMathLibrary(this); 88 initializeRegExpLibrary(this); 89 initializeSystemLib(this); 90 } 91 92 /** 93 * Calls a script function. Can throw ScriptRuntimeException. 94 */ 95 deprecated ScriptAny callFunction(ScriptFunction func, ScriptAny thisObj, ScriptAny[] args, bool useVM=false) 96 { 97 auto vr = callFn(func, thisObj, args, false, useVM); 98 if(vr.exception) 99 throw vr.exception; 100 return vr.result; 101 } 102 103 /** 104 * This is the main entry point for evaluating a script program. If the useVM option was set in the 105 * constructor, bytecode compilation and execution will be used, otherwise tree walking. 106 * Params: 107 * code = This is the source code of a script to be executed. 108 * printDisasm = If VM mode is set, print the disassembly of bytecode before running if true. 109 * Returns: 110 * If the script has a return statement with an expression, this value will be the result of that expression 111 * otherwise it will be ScriptAny.UNDEFINED 112 */ 113 ScriptAny evaluate(in string code, bool printDisasm=false) 114 { 115 if(_compiler is null) 116 { 117 auto lexer = Lexer(code); 118 auto tokens = lexer.tokenize(); 119 auto parser = Parser(tokens); 120 auto programBlock = parser.parseProgram(); 121 auto vr = programBlock.accept(this).get!VisitResult; 122 if(vr.exception !is null) 123 throw vr.exception; 124 if(vr.returnFlag) 125 return vr.result; 126 return ScriptAny.UNDEFINED; 127 } 128 else 129 { 130 auto chunk = _compiler.compile(code); 131 if(printDisasm) 132 _vm.printChunk(chunk, true); 133 134 return _vm.run(chunk, _printVMDebugInfo); 135 } 136 } 137 138 /** 139 * Evaluates a file that can be either binary bytecode or textual source code. 140 * Params: 141 * pathName = the location of the code file in the file system. 142 * Returns: 143 * The result of evaluating the file, undefined if no return statement. 144 */ 145 ScriptAny evaluateFile(in string pathName, bool printDisasm=false) 146 { 147 import std.stdio: File, writefln; 148 import mildew.util.encode: decode; 149 150 File inputFile = File(pathName, "rb"); 151 auto raw = new ubyte[inputFile.size]; 152 raw = inputFile.rawRead(raw); 153 if(raw.length > 0 && raw[0] == 0x01) 154 { 155 if(_vm is null) 156 throw new ScriptRuntimeException("This file can only be run in VM mode"); 157 auto chunk = Chunk.deserialize(raw); 158 if(printDisasm) 159 _vm.printChunk(chunk); 160 return _vm.run(chunk, _printVMDebugInfo); 161 } 162 else 163 { 164 auto source = cast(string)raw; 165 return evaluate(source, printDisasm); 166 } 167 } 168 169 // TODO: Read script from file 170 171 // TODO: Create an evaluate function with default exception handling with file name info 172 173 /** 174 * Sets a global variable or constant without checking whether or not the variable or const was already 175 * declared. This is used by host applications to define custom functions or objects. 176 * Params: 177 * name = The name of the variable. 178 * value = The value the variable should be set to. 179 * isConst = Whether or not the script can overwrite the global. 180 */ 181 void forceSetGlobal(T)(in string name, T value, bool isConst=false) 182 { 183 _globalEnvironment.forceSetVarOrConst(name, ScriptAny(value), isConst); 184 } 185 186 /** 187 * Unsets a variable or constant in the global environment. Used by host applications to remove 188 * items that were loaded by the standard library load functions. Specific functions of 189 * script classes can be removed by modifying the "prototype" field of their constructor. 190 */ 191 void forceUnsetGlobal(in string name) 192 { 193 _globalEnvironment.forceRemoveVarOrConst(name); 194 } 195 196 /// whether or not debug info should be printed between each VM instruction 197 bool printVMDebugInfo() const 198 { 199 return _printVMDebugInfo; 200 } 201 202 /// whether or not VM option was set when created 203 bool usingVM() const 204 { 205 return _vm !is null; 206 } 207 208 // The next functions are internal and deprecated and only public due to D language constraints. 209 210 /// extract a VisitResult from a LiteralNode 211 deprecated Variant visitLiteralNode(LiteralNode lnode) 212 { 213 return Variant(VisitResult(lnode.value)); 214 } 215 216 /// handles function literals 217 deprecated Variant visitFunctionLiteralNode(FunctionLiteralNode flnode) 218 { 219 auto func = new ScriptFunction("<anonymous function>", flnode.argList, flnode.statements, _currentEnvironment); 220 return Variant(VisitResult(ScriptAny(func))); 221 } 222 223 /// handle lambda 224 deprecated Variant visitLambdaNode(LambdaNode lnode) 225 { 226 FunctionLiteralNode flnode; 227 if(lnode.returnExpression) 228 { 229 flnode = new FunctionLiteralNode(lnode.argList, [ 230 new ReturnStatementNode(lnode.arrowToken.position.line, lnode.returnExpression) 231 ], "<lambda>", false); 232 } 233 else 234 { 235 flnode = new FunctionLiteralNode(lnode.argList, lnode.statements, "<lambda>", false); 236 } 237 return flnode.accept(this); 238 } 239 240 /// handle template literal nodes 241 deprecated Variant visitTemplateStringNode(TemplateStringNode tsnode) 242 { 243 VisitResult vr; 244 string result = ""; 245 foreach(node ; tsnode.nodes) 246 { 247 vr = node.accept(this).get!VisitResult; 248 if(vr.exception) 249 return Variant(vr); 250 result ~= vr.result.toString(); 251 } 252 return Variant(VisitResult(result)); 253 } 254 255 /// return an array from an array literal node 256 deprecated Variant visitArrayLiteralNode(ArrayLiteralNode alnode) 257 { 258 VisitResult vr; 259 ScriptAny[] values = []; 260 foreach(expression ; alnode.valueNodes) 261 { 262 vr = expression.accept(this).get!VisitResult; 263 if(vr.exception !is null) 264 return Variant(vr); 265 values ~= vr.result; 266 } 267 vr.result = values; 268 return Variant(vr); 269 } 270 271 /// generates object from object literal node 272 deprecated Variant visitObjectLiteralNode(ObjectLiteralNode olnode) 273 { 274 if(olnode.keys.length != olnode.valueNodes.length) 275 throw new Exception("Error with object literal node"); 276 ScriptAny[] vals = []; 277 VisitResult vr; 278 foreach(valueNode ; olnode.valueNodes) 279 { 280 vr = valueNode.accept(this).get!VisitResult; 281 if(vr.exception !is null) 282 return Variant(vr); 283 vals ~= vr.result; 284 } 285 auto obj = new ScriptObject("object", null, null); 286 for(size_t i = 0; i < olnode.keys.length; ++i) 287 { 288 obj.assignField(olnode.keys[i], vals[i]); 289 } 290 vr.result = obj; 291 return Variant(vr); 292 } 293 294 /// handle class literals 295 deprecated Variant visitClassLiteralNode(ClassLiteralNode clnode) 296 { 297 VisitResult vr; 298 299 try 300 { 301 vr.result = clnode.classDefinition.create(_currentEnvironment); 302 } 303 catch(ScriptRuntimeException ex) 304 { 305 vr.exception = ex; 306 } 307 308 return Variant(vr); 309 } 310 311 /// processes binary operations including assignment 312 deprecated Variant visitBinaryOpNode(BinaryOpNode bonode) 313 { 314 import std.conv: to; 315 316 auto lhsResult = bonode.leftNode.accept(this).get!VisitResult; 317 auto rhsResult = bonode.rightNode.accept(this).get!VisitResult; 318 319 if(lhsResult.exception !is null) 320 return Variant(lhsResult); 321 if(rhsResult.exception !is null) 322 return Variant(rhsResult); 323 324 VisitResult finalResult; 325 326 if(bonode.opToken.isAssignmentOperator) 327 { 328 // if an anonymous class or function is being assigned we need to update its name 329 if(rhsResult.result.type == ScriptAny.Type.FUNCTION) 330 { 331 auto func = rhsResult.result.toValue!ScriptFunction; 332 if(func.functionName == "<anonymous function>" || func.functionName == "<anonymous class>") 333 func.functionName = bonode.leftNode.toString; 334 } 335 final switch(lhsResult.accessType) 336 { 337 case VisitResult.AccessType.NO_ACCESS: 338 finalResult.exception = new ScriptRuntimeException("Invalid left hand assignment"); 339 return Variant(finalResult); 340 case VisitResult.AccessType.VAR_ACCESS: 341 return Variant(handleVarReassignment(bonode.opToken, lhsResult.memberOrVarToAccess, rhsResult.result)); 342 case VisitResult.AccessType.ARRAY_ACCESS: 343 return Variant(handleArrayReassignment(bonode.opToken, lhsResult.objectToAccess, 344 lhsResult.indexToAccess, rhsResult.result)); 345 case VisitResult.AccessType.OBJECT_ACCESS: 346 return Variant(handleObjectReassignment(bonode.opToken, lhsResult.objectToAccess, 347 lhsResult.memberOrVarToAccess, rhsResult.result)); 348 } 349 } 350 351 auto lhs = lhsResult.result; 352 auto rhs = rhsResult.result; 353 354 switch(bonode.opToken.type) 355 { 356 case Token.Type.POW: 357 return Variant(VisitResult(lhs ^^ rhs)); 358 case Token.Type.STAR: 359 return Variant(VisitResult(lhs * rhs)); 360 case Token.Type.FSLASH: 361 return Variant(VisitResult(lhs / rhs)); 362 case Token.Type.PERCENT: 363 return Variant(VisitResult(lhs % rhs)); 364 case Token.Type.PLUS: 365 return Variant(VisitResult(lhs + rhs)); 366 case Token.Type.DASH: 367 return Variant(VisitResult(lhs - rhs)); 368 case Token.Type.BIT_LSHIFT: 369 return Variant(VisitResult(lhs << rhs)); 370 case Token.Type.BIT_RSHIFT: 371 return Variant(VisitResult(lhs >> rhs)); 372 case Token.Type.BIT_URSHIFT: 373 return Variant(VisitResult(lhs >>> rhs)); 374 case Token.Type.GT: 375 return Variant(VisitResult(lhs > rhs)); 376 case Token.Type.GE: 377 return Variant(VisitResult(lhs >= rhs)); 378 case Token.Type.LT: 379 return Variant(VisitResult(lhs < rhs)); 380 case Token.Type.LE: 381 return Variant(VisitResult(lhs <= rhs)); 382 case Token.Type.EQUALS: 383 return Variant(VisitResult(lhs == rhs)); 384 case Token.Type.NEQUALS: 385 return Variant(VisitResult(lhs != rhs)); 386 case Token.Type.STRICT_EQUALS: 387 return Variant(VisitResult(lhs.strictEquals(rhs))); 388 case Token.Type.STRICT_NEQUALS: 389 return Variant(VisitResult(!lhs.strictEquals(rhs))); 390 case Token.Type.BIT_AND: 391 return Variant(VisitResult(lhs & rhs)); 392 case Token.Type.BIT_XOR: 393 return Variant(VisitResult(lhs ^ rhs)); 394 case Token.Type.BIT_OR: 395 return Variant(VisitResult(lhs | rhs)); 396 case Token.Type.AND: 397 return Variant(VisitResult(lhs && rhs)); 398 case Token.Type.OR: 399 return Variant(VisitResult(lhs.orOp(rhs))); 400 default: 401 if(bonode.opToken.isKeyword("instanceof")) 402 { 403 if(!lhs.isObject) 404 return Variant(VisitResult(false)); 405 if(rhs.type != ScriptAny.Type.FUNCTION) 406 return Variant(VisitResult(false)); 407 auto lhsObj = lhs.toValue!ScriptObject; // @suppress(dscanner.suspicious.unmodified) 408 auto rhsFunc = rhs.toValue!ScriptFunction; // @suppress(dscanner.suspicious.unmodified) 409 auto proto = lhsObj.prototype; 410 while(proto !is null) 411 { 412 if(proto["constructor"].toValue!ScriptFunction is rhsFunc) 413 return Variant(VisitResult(true)); 414 proto = proto.prototype; 415 } 416 return Variant(VisitResult(false)); 417 } 418 else 419 throw new Exception("Forgot to implement missing binary operator " 420 ~ bonode.opToken.type.to!string ~ " for " ~ this.toString()); 421 } 422 } 423 424 /// returns a value from a unary operation 425 deprecated Variant visitUnaryOpNode(UnaryOpNode uonode) 426 { 427 auto vr = uonode.operandNode.accept(this).get!VisitResult; 428 if(vr.exception !is null) 429 return Variant(vr); 430 auto value = vr.result; 431 int incOrDec = 0; 432 switch(uonode.opToken.type) 433 { 434 case Token.Type.BIT_NOT: 435 return Variant(VisitResult(~value)); 436 case Token.Type.NOT: 437 return Variant(VisitResult(!value)); 438 case Token.Type.PLUS: 439 return Variant(VisitResult(value)); 440 case Token.Type.DASH: 441 return Variant(VisitResult(-value)); 442 case Token.Type.DEC: 443 incOrDec = -1; 444 break; 445 case Token.Type.INC: 446 incOrDec = 1; 447 break; 448 default: 449 if(uonode.opToken.isKeyword("typeof")) 450 return Variant(VisitResult(value.typeToString())); 451 return Variant(VisitResult(ScriptAny.UNDEFINED)); 452 } 453 454 if(incOrDec != 0) 455 { 456 // TODO: fix this to allow constructs such as ++foo++ 457 if(vr.accessType == VisitResult.AccessType.VAR_ACCESS) 458 return Variant(handleVarReassignment(Token.createFakeToken(Token.Type.PLUS_ASSIGN,""), 459 vr.memberOrVarToAccess, ScriptAny(incOrDec))); 460 else if(vr.accessType == VisitResult.AccessType.ARRAY_ACCESS) 461 return Variant(handleArrayReassignment(Token.createFakeToken(Token.Type.PLUS_ASSIGN,""), 462 vr.objectToAccess, vr.indexToAccess, ScriptAny(incOrDec))); 463 else if(vr.accessType == VisitResult.AccessType.OBJECT_ACCESS) 464 return Variant(handleObjectReassignment(Token.createFakeToken(Token.Type.PLUS_ASSIGN,""), 465 vr.objectToAccess, vr.memberOrVarToAccess, ScriptAny(incOrDec))); 466 else 467 vr.exception = new ScriptRuntimeException("Invalid operand for " ~ uonode.opToken.symbol); 468 } 469 return Variant(vr); 470 } 471 472 /// handle constructs such as i++ and i-- 473 deprecated Variant visitPostfixOpNode(PostfixOpNode ponode) 474 { 475 // first get the operand's original value that will be returned 476 VisitResult vr = ponode.operandNode.accept(this).get!VisitResult; 477 if(vr.exception !is null) 478 return Variant(vr); 479 auto incOrDec = 0; 480 if(ponode.opToken.type == Token.Type.INC) 481 incOrDec = 1; 482 else if(ponode.opToken.type == Token.Type.DEC) 483 incOrDec = -1; 484 else 485 throw new Exception("Impossible parse state: invalid postfix operator"); 486 // now perform an increment or decrement assignment based on object access type 487 VisitResult errVR; 488 if(vr.accessType == VisitResult.AccessType.VAR_ACCESS) 489 errVR = handleVarReassignment(Token.createFakeToken(Token.Type.PLUS_ASSIGN,""), 490 vr.memberOrVarToAccess, ScriptAny(incOrDec)); 491 else if(vr.accessType == VisitResult.AccessType.ARRAY_ACCESS) 492 errVR = handleArrayReassignment(Token.createFakeToken(Token.Type.PLUS_ASSIGN,""), 493 vr.objectToAccess, vr.indexToAccess, ScriptAny(incOrDec)); 494 else if(vr.accessType == VisitResult.AccessType.OBJECT_ACCESS) 495 errVR = handleObjectReassignment(Token.createFakeToken(Token.Type.PLUS_ASSIGN,""), 496 vr.objectToAccess, vr.memberOrVarToAccess, ScriptAny(incOrDec)); 497 else 498 vr.exception = new ScriptRuntimeException("Invalid post operand for " ~ ponode.opToken.symbol); 499 if(errVR.exception !is null) 500 return Variant(errVR); 501 return Variant(vr); 502 } 503 504 /// handles : ? operator 505 deprecated Variant visitTerniaryOpNode(TerniaryOpNode tonode) 506 { 507 // first evaluate the condition 508 auto vr = tonode.conditionNode.accept(this).get!VisitResult; 509 if(vr.exception !is null) 510 return Variant(vr); 511 if(vr.result) 512 vr = tonode.onTrueNode.accept(this).get!VisitResult; 513 else 514 vr = tonode.onFalseNode.accept(this).get!VisitResult; 515 return Variant(vr); 516 } 517 518 /// handles variable access 519 deprecated Variant visitVarAccessNode(VarAccessNode vanode) 520 { 521 VisitResult vr; 522 vr.accessType = VisitResult.AccessType.VAR_ACCESS; 523 vr.memberOrVarToAccess = vanode.varToken.text; 524 bool _; // @suppress(dscanner.suspicious.unmodified) 525 immutable ptr = cast(immutable)_currentEnvironment.lookupVariableOrConst(vanode.varToken.text, _); 526 if(ptr == null) 527 vr.exception = new ScriptRuntimeException("Undefined variable lookup " ~ vanode.varToken.text); 528 else 529 vr.result = *ptr; 530 return Variant(vr); 531 } 532 533 /// handles function calls 534 deprecated Variant visitFunctionCallNode(FunctionCallNode fcnode) 535 { 536 ScriptAny thisObj; 537 auto vr = fcnode.functionToCall.accept(this).get!VisitResult; 538 539 if(cast(SuperNode)fcnode.functionToCall) 540 { 541 vr.result = vr.result["constructor"]; 542 } 543 544 if(vr.exception !is null) 545 return Variant(vr); 546 547 // if not a new expression pull this 548 if(!fcnode.returnThis) 549 { 550 if(vr.accessType == VisitResult.AccessType.OBJECT_ACCESS) 551 { 552 auto man = cast(MemberAccessNode)fcnode.functionToCall; 553 if(cast(SuperNode)man.objectNode) 554 thisObj = getLocalThis(); 555 else 556 thisObj = vr.objectToAccess; 557 } 558 else if(vr.accessType == VisitResult.AccessType.ARRAY_ACCESS) 559 { 560 auto ain = cast(ArrayIndexNode)fcnode.functionToCall; 561 if(cast(SuperNode)ain.objectNode) 562 thisObj = getLocalThis(); 563 else 564 thisObj = vr.objectToAccess; 565 } 566 else 567 { 568 thisObj = getLocalThis(); 569 } 570 } 571 572 auto fnToCall = vr.result; 573 if(fnToCall.type == ScriptAny.Type.FUNCTION) 574 { 575 ScriptAny[] args; 576 vr = convertExpressionsToArgs(fcnode.expressionArgs, args); 577 if(vr.exception !is null) 578 return Variant(vr); 579 auto fn = fnToCall.toValue!ScriptFunction; 580 vr = callFn(fn, thisObj, args, fcnode.returnThis); 581 return Variant(vr); 582 } 583 else 584 { 585 vr.result = ScriptAny.UNDEFINED; 586 vr.exception = new ScriptRuntimeException("Unable to call non function " ~ fnToCall.toString); 587 return Variant(vr); 588 } 589 } 590 591 /// handle array index 592 deprecated Variant visitArrayIndexNode(ArrayIndexNode ainode) 593 { 594 import std.utf: UTFException; 595 596 VisitResult vr = ainode.indexValueNode.accept(this).get!VisitResult; 597 if(vr.exception !is null) 598 return Variant(vr); 599 auto index = vr.result; 600 auto objVR = ainode.objectNode.accept(this).get!VisitResult; 601 if(objVR.exception !is null) 602 return Variant(objVR); 603 604 // also need to validate that the object can be accessed 605 if(!objVR.result.isObject) 606 { 607 vr.exception = new ScriptRuntimeException("Cannot index value " ~ objVR.result.toString); 608 return Variant(vr); 609 } 610 611 if(index.type == ScriptAny.Type.STRING) 612 { 613 // we have to be accessing an object or trying to 614 auto indexAsStr = index.toString(); 615 vr.accessType = VisitResult.AccessType.OBJECT_ACCESS; 616 vr.memberOrVarToAccess = index.toString(); 617 vr.objectToAccess = objVR.result; 618 vr.result = vr.objectToAccess.lookupField(indexAsStr); 619 } 620 else if(index.isNumber) 621 { 622 auto indexAsNum = index.toValue!size_t; 623 vr.accessType = VisitResult.AccessType.ARRAY_ACCESS; 624 vr.indexToAccess = indexAsNum; 625 vr.objectToAccess = objVR.result; 626 if(auto asString = objVR.result.toValue!ScriptString) 627 { 628 auto str = asString.toString(); 629 if(indexAsNum >= str.length) 630 vr.result = ScriptAny.UNDEFINED; 631 else 632 { 633 try 634 { 635 vr.result = ScriptAny([ str[indexAsNum] ]); 636 } 637 catch(UTFException) 638 { 639 vr.result = ScriptAny.UNDEFINED; 640 } 641 } 642 } 643 else if(auto asArray = objVR.result.toValue!ScriptArray) 644 { 645 if(indexAsNum >= asArray.array.length) 646 vr.result = ScriptAny.UNDEFINED; 647 else 648 vr.result = asArray.array[indexAsNum]; 649 } 650 else 651 { 652 vr.exception = new ScriptRuntimeException("Attempt to index a non-string or non-array"); 653 } 654 } 655 else 656 { 657 vr.exception = new ScriptRuntimeException("Invalid index type for array or object access"); 658 } 659 return Variant(vr); 660 } 661 662 /// handle dot operator 663 deprecated Variant visitMemberAccessNode(MemberAccessNode manode) 664 { 665 VisitResult vr; 666 string memberName = ""; 667 if(auto van = cast(VarAccessNode)manode.memberNode) 668 { 669 memberName = van.varToken.text; 670 } 671 else 672 { 673 vr.exception = new ScriptRuntimeException("Invalid operand for object member access"); 674 return Variant(vr); 675 } 676 677 auto objVR = manode.objectNode.accept(this).get!VisitResult; 678 if(objVR.exception !is null) 679 return Variant(objVR); 680 // validate that objVR.result is of type object so that it can even be accessed 681 if(!objVR.result.isObject) 682 { 683 vr.exception = new ScriptRuntimeException("Cannot access non-object " 684 ~ objVR.result.toString() ~ ": " ~ this.toString()); 685 return Variant(vr); 686 } 687 688 // set the fields 689 vr.accessType = VisitResult.AccessType.OBJECT_ACCESS; 690 vr.objectToAccess = objVR.result; 691 vr.memberOrVarToAccess = memberName; 692 // if this is a get property we need to use the getter otherwise we lookup field 693 auto obj = vr.objectToAccess.toValue!ScriptObject; 694 if(obj.hasGetter(memberName)) 695 { 696 VisitResult gvr; 697 gvr = getObjectProperty(obj, memberName); 698 if(gvr.exception !is null) 699 return Variant(gvr); 700 vr.result = gvr.result; 701 } 702 else 703 vr.result = objVR.result.lookupField(memberName); 704 return Variant(vr); 705 } 706 707 /// handles new expression 708 deprecated Variant visitNewExpressionNode(NewExpressionNode nenode) 709 { 710 // fce should be a valid function call with its returnThis flag already set by the parser 711 auto vr = nenode.functionCallExpression.accept(this); 712 return vr; // caller will check for any exceptions. 713 } 714 715 /// this should only be directly visited when used by itself 716 deprecated Variant visitSuperNode(SuperNode snode) 717 { 718 auto thisObj = getLocalThis; 719 return Variant(VisitResult(thisObj["__super__"])); 720 } 721 722 deprecated Variant visitYieldNode(YieldNode ynode) 723 { 724 throw new Exception("Yield is not supported in interpreted mode"); 725 } 726 727 /// handles var, let, and const declarations 728 deprecated Variant visitVarDeclarationStatementNode(VarDeclarationStatementNode vdsnode) 729 { 730 VisitResult visitResult; 731 foreach(varNode; vdsnode.varAccessOrAssignmentNodes) 732 { 733 if(auto v = cast(VarAccessNode)varNode) 734 { 735 auto varName = v.varToken.text; 736 visitResult = handleVarDeclaration(vdsnode.qualifier.text, varName, ScriptAny.UNDEFINED); 737 if(visitResult.exception !is null) 738 return Variant(visitResult); 739 } 740 else if(auto binNode = cast(BinaryOpNode)varNode) 741 { 742 // auto binNode = cast(BinaryOpNode)varNode; 743 visitResult = binNode.rightNode.accept(this).get!VisitResult; 744 if(visitResult.exception !is null) 745 return Variant(visitResult); 746 auto valueToAssign = visitResult.result; 747 // we checked this before so should be safe 748 if(auto van = cast(VarAccessNode)(binNode.leftNode)) 749 { 750 auto varName = van.varToken.text; 751 visitResult = handleVarDeclaration(vdsnode.qualifier.text, varName, valueToAssign); 752 if(visitResult.exception !is null) 753 return Variant(visitResult); 754 // success so make sure anon function name matches 755 if(valueToAssign.type == ScriptAny.Type.FUNCTION) 756 { 757 auto func = valueToAssign.toValue!ScriptFunction; 758 if(func.functionName == "<anonymous function>" || func.functionName == "<anonymous class>") 759 func.functionName = varName; 760 } 761 } 762 } 763 else 764 throw new Exception("Invalid declaration got past the parser"); 765 } 766 return Variant(VisitResult(ScriptAny.UNDEFINED)); 767 } 768 769 /// handles {block} statement 770 deprecated Variant visitBlockStatementNode(BlockStatementNode bsnode) 771 { 772 Environment oldEnvironment = _currentEnvironment; // @suppress(dscanner.suspicious.unmodified) 773 _currentEnvironment = new Environment(_currentEnvironment, "<scope>"); 774 auto result = VisitResult(ScriptAny.UNDEFINED); 775 foreach(statement ; bsnode.statementNodes) 776 { 777 result = statement.accept(this).get!VisitResult; 778 if(result.returnFlag || result.breakFlag || result.continueFlag || result.exception !is null) 779 { 780 if(result.exception) 781 result.exception.scriptTraceback ~= tuple(statement.line, statement.toString()); 782 break; 783 } 784 } 785 _currentEnvironment = oldEnvironment; 786 return Variant(result); 787 } 788 789 /// handles if statements 790 deprecated Variant visitIfStatementNode(IfStatementNode isnode) 791 { 792 auto vr = isnode.conditionNode.accept(this).get!VisitResult; 793 if(vr.exception !is null) 794 return Variant(vr); 795 796 if(vr.result) 797 { 798 vr = isnode.onTrueStatement.accept(this).get!VisitResult; 799 } 800 else 801 { 802 if(isnode.onFalseStatement !is null) 803 vr = isnode.onFalseStatement.accept(this).get!VisitResult; 804 } 805 return Variant(vr); 806 } 807 808 /// handles switch case statements 809 deprecated Variant visitSwitchStatementNode(SwitchStatementNode ssnode) 810 { 811 auto vr = ssnode.expressionNode.accept(this).get!VisitResult; 812 if(vr.exception !is null) 813 return Variant(vr); 814 size_t jumpStatement = ssnode.switchBody.defaultStatementID; 815 if(vr.result in ssnode.switchBody.jumpTable) 816 { 817 jumpStatement = ssnode.switchBody.jumpTable[vr.result]; 818 } 819 if(jumpStatement < ssnode.switchBody.statementNodes.length) 820 { 821 for(size_t i = jumpStatement; i < ssnode.switchBody.statementNodes.length; ++i) 822 { 823 vr = ssnode.switchBody.statementNodes[i].accept(this).get!VisitResult; 824 if(vr.returnFlag || vr.continueFlag || vr.breakFlag || vr.exception !is null) 825 return Variant(vr); 826 } 827 } 828 return Variant(vr); 829 } 830 831 /// handles while statements 832 deprecated Variant visitWhileStatementNode(WhileStatementNode wsnode) 833 { 834 if(wsnode.label != "") 835 _currentEnvironment.insertLabel(wsnode.label); 836 auto vr = wsnode.conditionNode.accept(this).get!VisitResult; 837 while(vr.result && vr.exception is null) 838 { 839 vr = wsnode.bodyNode.accept(this).get!VisitResult; 840 if(vr.breakFlag) 841 { 842 if(vr.labelName == "") 843 vr.breakFlag = false; 844 else 845 { 846 if(_currentEnvironment.labelExists(vr.labelName)) 847 { 848 if(wsnode.label == vr.labelName) 849 vr.breakFlag = false; 850 } 851 else 852 vr.exception = new ScriptRuntimeException("Label " ~ vr.labelName ~ " doesn't exist"); 853 } 854 break; 855 } 856 if(vr.continueFlag) 857 { 858 if(vr.labelName == "") 859 vr.continueFlag = false; 860 else 861 { 862 if(_currentEnvironment.labelExists(vr.labelName)) 863 { 864 if(wsnode.label == vr.labelName) 865 vr.continueFlag = false; 866 else 867 break; 868 } 869 else 870 { 871 vr.exception = new ScriptRuntimeException("Label " ~ vr.labelName ~ " doesn't exist"); 872 break; 873 } 874 } 875 } 876 if(vr.exception !is null || vr.returnFlag) 877 break; 878 vr = wsnode.conditionNode.accept(this).get!VisitResult; 879 } 880 if(wsnode.label != "") 881 _currentEnvironment.removeLabelFromCurrent(wsnode.label); 882 return Variant(vr); 883 } 884 885 /// handles do-while statement 886 deprecated Variant visitDoWhileStatementNode(DoWhileStatementNode dwsnode) 887 { 888 auto vr = VisitResult(ScriptAny.UNDEFINED); 889 if(dwsnode.label != "") 890 _currentEnvironment.insertLabel(dwsnode.label); 891 do 892 { 893 vr = dwsnode.bodyNode.accept(this).get!VisitResult; 894 if(vr.breakFlag) 895 { 896 if(vr.labelName == "") 897 vr.breakFlag = false; 898 else 899 { 900 if(_currentEnvironment.labelExists(vr.labelName)) 901 { 902 if(dwsnode.label == vr.labelName) 903 vr.breakFlag = false; 904 } 905 else 906 vr.exception = new ScriptRuntimeException("Label " ~ vr.labelName ~ " doesn't exist"); 907 } 908 break; 909 } 910 if(vr.continueFlag) 911 { 912 if(vr.labelName == "") 913 vr.continueFlag = false; 914 else 915 { 916 if(_currentEnvironment.labelExists(vr.labelName)) 917 { 918 if(dwsnode.label == vr.labelName) 919 vr.continueFlag = false; 920 else 921 break; 922 } 923 else 924 { 925 vr.exception = new ScriptRuntimeException("Label " ~ vr.labelName ~ " doesn't exist"); 926 break; 927 } 928 } 929 } 930 if(vr.exception !is null || vr.returnFlag) 931 break; 932 vr = dwsnode.conditionNode.accept(this).get!VisitResult; 933 } 934 while(vr.result && vr.exception is null); 935 if(dwsnode.label != "") 936 _currentEnvironment.removeLabelFromCurrent(dwsnode.label); 937 return Variant(vr); 938 } 939 940 /// handles for(;;) statements 941 deprecated Variant visitForStatementNode(ForStatementNode fsnode) 942 { 943 Environment oldEnvironment = _currentEnvironment; // @suppress(dscanner.suspicious.unmodified) 944 _currentEnvironment = new Environment(_currentEnvironment, "<outer_for_loop>"); 945 if(fsnode.label != "") 946 _currentEnvironment.insertLabel(fsnode.label); 947 auto vr = VisitResult(ScriptAny.UNDEFINED); 948 if(fsnode.varDeclarationStatement !is null) 949 vr = fsnode.varDeclarationStatement.accept(this).get!VisitResult; 950 if(vr.exception is null) 951 { 952 vr = fsnode.conditionNode.accept(this).get!VisitResult; 953 while(vr.result && vr.exception is null) 954 { 955 vr = fsnode.bodyNode.accept(this).get!VisitResult; 956 if(vr.breakFlag) 957 { 958 if(vr.labelName == "") 959 vr.breakFlag = false; 960 else 961 { 962 if(_currentEnvironment.labelExists(vr.labelName)) 963 { 964 if(fsnode.label == vr.labelName) 965 vr.breakFlag = false; 966 } 967 else 968 vr.exception = new ScriptRuntimeException("Label " ~ vr.labelName ~ " doesn't exist"); 969 } 970 break; 971 } 972 if(vr.continueFlag) 973 { 974 if(vr.labelName == "") 975 vr.continueFlag = false; 976 else 977 { 978 if(_currentEnvironment.labelExists(vr.labelName)) 979 { 980 if(fsnode.label == vr.labelName) 981 vr.continueFlag = false; 982 else 983 break; 984 } 985 else 986 { 987 vr.exception = new ScriptRuntimeException("Label " ~ vr.labelName ~ " doesn't exist"); 988 break; 989 } 990 } 991 } 992 if(vr.exception !is null || vr.returnFlag) 993 break; 994 vr = fsnode.incrementNode.accept(this).get!VisitResult; 995 if(vr.exception !is null) 996 break; 997 vr = fsnode.conditionNode.accept(this).get!VisitResult; 998 } 999 } 1000 if(fsnode.label != "") 1001 _currentEnvironment.removeLabelFromCurrent(fsnode.label); 1002 _currentEnvironment = oldEnvironment; 1003 return Variant(vr); 1004 } 1005 1006 /// handles for-of (and for-in) loops. TODO rewrite with iterators and implement string 1007 deprecated Variant visitForOfStatementNode(ForOfStatementNode fosnode) 1008 { 1009 auto vr = fosnode.objectToIterateNode.accept(this).get!VisitResult; 1010 // make sure this is iterable 1011 if(vr.exception !is null) 1012 return Variant(vr); 1013 1014 if(fosnode.label != "") 1015 _currentEnvironment.insertLabel(fosnode.label); 1016 1017 // FOR NOW no distinguish "of" and "in" 1018 if(vr.result.type == ScriptAny.Type.ARRAY) 1019 { 1020 auto arr = vr.result.toValue!(ScriptAny[]); 1021 for(size_t i = 0; i < arr.length; ++i) 1022 { 1023 // TODO optimize this to reassign variables instead of creating new environments each iteration 1024 auto oldEnvironment = _currentEnvironment; // @suppress(dscanner.suspicious.unmodified) 1025 _currentEnvironment = new Environment(_currentEnvironment, "<for_of_loop>"); 1026 // if one var access node, then value, otherwise index then value 1027 if(fosnode.varAccessNodes.length == 1) 1028 { 1029 _currentEnvironment.declareVariableOrConst(fosnode.varAccessNodes[0].varToken.text, 1030 arr[i], fosnode.qualifierToken.text == "const"? true: false); 1031 } 1032 else 1033 { 1034 _currentEnvironment.declareVariableOrConst(fosnode.varAccessNodes[0].varToken.text, 1035 ScriptAny(i), fosnode.qualifierToken.text == "const"? true: false); 1036 _currentEnvironment.declareVariableOrConst(fosnode.varAccessNodes[1].varToken.text, 1037 arr[i], fosnode.qualifierToken.text == "const"? true: false); 1038 } 1039 vr = fosnode.bodyNode.accept(this).get!VisitResult; 1040 _currentEnvironment = oldEnvironment; 1041 if(vr.breakFlag) 1042 { 1043 if(vr.labelName == "") 1044 vr.breakFlag = false; 1045 else 1046 { 1047 if(_currentEnvironment.labelExists(vr.labelName)) 1048 { 1049 if(fosnode.label == vr.labelName) 1050 vr.breakFlag = false; 1051 } 1052 else 1053 vr.exception = new ScriptRuntimeException("Label " ~ vr.labelName ~ " doesn't exist"); 1054 } 1055 break; 1056 } 1057 if(vr.continueFlag) 1058 { 1059 if(vr.labelName == "") 1060 vr.continueFlag = false; 1061 else 1062 { 1063 if(_currentEnvironment.labelExists(vr.labelName)) 1064 { 1065 if(fosnode.label == vr.labelName) 1066 vr.continueFlag = false; 1067 else 1068 break; 1069 } 1070 else 1071 { 1072 vr.exception = new ScriptRuntimeException("Label " ~ vr.labelName ~ " doesn't exist"); 1073 break; 1074 } 1075 } 1076 } 1077 if(vr.exception !is null || vr.returnFlag) 1078 break; 1079 } 1080 } 1081 else if(vr.result.isObject) 1082 { 1083 auto obj = vr.result.toValue!ScriptObject; 1084 // first value is key, second value is value if there 1085 foreach(key, val; obj.dictionary) 1086 { 1087 // TODO optimize this to reassign variables instead of creating new ones each iteration 1088 auto oldEnvironment = _currentEnvironment; // @suppress(dscanner.suspicious.unmodified) 1089 _currentEnvironment = new Environment(_currentEnvironment, "<for_of_loop>"); 1090 _currentEnvironment.declareVariableOrConst(fosnode.varAccessNodes[0].varToken.text, 1091 ScriptAny(key), fosnode.qualifierToken.text == "const" ? true: false); 1092 if(fosnode.varAccessNodes.length > 1) 1093 _currentEnvironment.declareVariableOrConst(fosnode.varAccessNodes[1].varToken.text, 1094 ScriptAny(val), fosnode.qualifierToken.text == "const" ? true: false); 1095 vr = fosnode.bodyNode.accept(this).get!VisitResult; 1096 _currentEnvironment = oldEnvironment; 1097 if(vr.breakFlag) 1098 { 1099 if(vr.labelName == "") 1100 vr.breakFlag = false; 1101 else 1102 { 1103 if(_currentEnvironment.labelExists(vr.labelName)) 1104 { 1105 if(fosnode.label == vr.labelName) 1106 vr.breakFlag = false; 1107 } 1108 else 1109 vr.exception = new ScriptRuntimeException("Label " ~ vr.labelName ~ " doesn't exist"); 1110 } 1111 break; 1112 } 1113 if(vr.continueFlag) 1114 { 1115 if(vr.labelName == "") 1116 vr.continueFlag = false; 1117 else 1118 { 1119 if(_currentEnvironment.labelExists(vr.labelName)) 1120 { 1121 if(fosnode.label == vr.labelName) 1122 vr.continueFlag = false; 1123 else 1124 break; 1125 } 1126 else 1127 { 1128 vr.exception = new ScriptRuntimeException("Label " ~ vr.labelName ~ " doesn't exist"); 1129 break; 1130 } 1131 } 1132 } 1133 if(vr.exception !is null || vr.returnFlag) 1134 break; 1135 if(vr.exception !is null) 1136 break; 1137 } 1138 } 1139 1140 else 1141 { 1142 vr.exception = new ScriptRuntimeException("Cannot iterate over " ~ fosnode.objectToIterateNode.toString); 1143 } 1144 1145 if(fosnode.label != "") 1146 _currentEnvironment.removeLabelFromCurrent(fosnode.label); 1147 1148 return Variant(vr); 1149 } 1150 1151 /// handle break statements 1152 deprecated Variant visitBreakStatementNode(BreakStatementNode bsnode) 1153 { 1154 auto vr = VisitResult(ScriptAny.UNDEFINED); 1155 vr.breakFlag = true; 1156 vr.labelName = bsnode.label; 1157 return Variant(vr); 1158 } 1159 1160 /// handle continue statements 1161 deprecated Variant visitContinueStatementNode(ContinueStatementNode csnode) 1162 { 1163 auto vr = VisitResult(ScriptAny.UNDEFINED); 1164 vr.continueFlag = true; 1165 vr.labelName = csnode.label; 1166 return Variant(vr); 1167 } 1168 1169 /// handles return statements 1170 deprecated Variant visitReturnStatementNode(ReturnStatementNode rsnode) 1171 { 1172 VisitResult vr = VisitResult(ScriptAny.UNDEFINED); 1173 if(rsnode.expressionNode !is null) 1174 { 1175 vr = rsnode.expressionNode.accept(this).get!VisitResult; 1176 if(vr.exception !is null) 1177 { 1178 return Variant(vr); 1179 } 1180 } 1181 vr.returnFlag = true; 1182 return Variant(vr); 1183 } 1184 1185 /// handle function declarations 1186 deprecated Variant visitFunctionDeclarationStatementNode(FunctionDeclarationStatementNode fdsnode) 1187 { 1188 auto func = new ScriptFunction(fdsnode.name, fdsnode.argNames, fdsnode.statementNodes, _currentEnvironment); 1189 immutable okToDeclare = _currentEnvironment.declareVariableOrConst(fdsnode.name, ScriptAny(func), false); 1190 VisitResult vr = VisitResult(ScriptAny.UNDEFINED); 1191 if(!okToDeclare) 1192 { 1193 vr.exception = new ScriptRuntimeException("Cannot redeclare variable or const " ~ fdsnode.name 1194 ~ " with a function declaration"); 1195 } 1196 return Variant(vr); 1197 } 1198 1199 /// handles throw statements 1200 deprecated Variant visitThrowStatementNode(ThrowStatementNode tsnode) 1201 { 1202 auto vr = tsnode.expressionNode.accept(this).get!VisitResult; 1203 if(vr.exception !is null) 1204 { 1205 return Variant(vr); 1206 } 1207 vr.exception = new ScriptRuntimeException("Uncaught script exception"); 1208 vr.exception.thrownValue = vr.result; 1209 vr.result = ScriptAny.UNDEFINED; 1210 return Variant(vr); 1211 } 1212 1213 /// handle try catch block statements 1214 deprecated Variant visitTryCatchBlockStatementNode(TryCatchBlockStatementNode tcbsnode) 1215 { 1216 auto vr = tcbsnode.tryBlockNode.accept(this).get!VisitResult; 1217 // if there was an exception we need to start a new environment and set it as a local variable 1218 if(vr.exception !is null && tcbsnode.catchBlockNode !is null) 1219 { 1220 auto oldEnvironment = _currentEnvironment; // @suppress(dscanner.suspicious.unmodified) 1221 _currentEnvironment = new Environment(_currentEnvironment, "<catch>"); 1222 if(vr.exception.thrownValue != ScriptAny.UNDEFINED) 1223 _currentEnvironment.forceSetVarOrConst(tcbsnode.exceptionName, vr.exception.thrownValue, false); 1224 else 1225 _currentEnvironment.forceSetVarOrConst(tcbsnode.exceptionName, ScriptAny(vr.exception.message), false); 1226 vr.exception = null; 1227 // if another exception is thrown in the catch block, it will propagate through this return value 1228 vr = tcbsnode.catchBlockNode.accept(this).get!VisitResult; 1229 _currentEnvironment = oldEnvironment; 1230 } 1231 if(tcbsnode.finallyBlockNode) 1232 { 1233 auto finVR = tcbsnode.finallyBlockNode.accept(this).get!VisitResult; 1234 if(finVR.exception) 1235 return Variant(finVR); 1236 } 1237 return Variant(vr); 1238 } 1239 1240 /// handle delete statement 1241 deprecated Variant visitDeleteStatementNode(DeleteStatementNode dsnode) 1242 { 1243 auto vr = dsnode.memberAccessOrArrayIndexNode.accept(this).get!VisitResult; 1244 // TODO handle array 1245 if(vr.accessType != VisitResult.AccessType.OBJECT_ACCESS) 1246 { 1247 vr.exception = new ScriptRuntimeException("Invalid operand for delete operator"); 1248 return Variant(vr); 1249 } 1250 if(vr.objectToAccess.isObject) 1251 { 1252 auto obj = vr.objectToAccess.toValue!ScriptObject; 1253 obj.dictionary.remove(vr.memberOrVarToAccess); 1254 } 1255 vr.result = ScriptAny.UNDEFINED; 1256 return Variant(vr); 1257 } 1258 1259 /// handle class declaration 1260 deprecated Variant visitClassDeclarationStatementNode(ClassDeclarationStatementNode cdsnode) 1261 { 1262 VisitResult vr; 1263 // generate class 1264 try 1265 { 1266 vr.result = cdsnode.classDefinition.create(_currentEnvironment); 1267 } 1268 catch (ScriptRuntimeException ex) 1269 { 1270 vr.exception = ex; 1271 return Variant(vr); 1272 } 1273 auto ctor = vr.result; 1274 // first try to assign the constructor as a local function 1275 immutable ok = _currentEnvironment.declareVariableOrConst(cdsnode.classDefinition.className, 1276 ctor, false); 1277 if(!ok) 1278 { 1279 vr.exception = new ScriptRuntimeException("Class declaration " ~ cdsnode.classDefinition.className 1280 ~ " may not overwrite local variable or const"); 1281 return Variant(vr); 1282 } 1283 1284 return Variant(VisitResult(ScriptAny.UNDEFINED)); // everything was ok 1285 } 1286 1287 /// handle expression statements 1288 deprecated Variant visitExpressionStatementNode(ExpressionStatementNode esnode) 1289 { 1290 VisitResult vr; 1291 if(esnode.expressionNode !is null) 1292 vr = esnode.expressionNode.accept(this).get!VisitResult; 1293 vr.result = ScriptAny.UNDEFINED; // they should never return a result 1294 return Variant(vr); // caller will handle any exception 1295 } 1296 1297 /// Virtual machine property, may be null 1298 VirtualMachine vm() { return _vm; } 1299 1300 package: 1301 /// holds information from visiting nodes TODO redesign this as a union 1302 deprecated struct VisitResult 1303 { 1304 enum AccessType { NO_ACCESS=0, VAR_ACCESS, ARRAY_ACCESS, OBJECT_ACCESS } 1305 1306 this(T)(T val) 1307 { 1308 result = ScriptAny(val); 1309 } 1310 1311 this(T : ScriptAny)(T val) 1312 { 1313 result = val; 1314 } 1315 1316 ScriptAny result; 1317 1318 AccessType accessType; 1319 ScriptAny objectToAccess; 1320 string memberOrVarToAccess; 1321 size_t indexToAccess; 1322 1323 bool returnFlag, breakFlag, continueFlag; 1324 string labelName; 1325 ScriptRuntimeException exception; 1326 } 1327 1328 private: 1329 1330 deprecated VisitResult callFn(ScriptFunction func, ScriptAny thisObj, ScriptAny[] args, 1331 bool returnThis = false, bool useVM = false) 1332 { 1333 VisitResult vr; 1334 if(returnThis) 1335 { 1336 if(!thisObj.isObject) 1337 thisObj = new ScriptObject(func.functionName, func["prototype"].toValue!ScriptObject, null); 1338 } 1339 // handle script functions 1340 if(func.type == ScriptFunction.Type.SCRIPT_FUNCTION) 1341 { 1342 auto prevEnvironment = _currentEnvironment; // @suppress(dscanner.suspicious.unmodified) 1343 _currentEnvironment = new Environment(func.closure, func.functionName); 1344 // set args as locals 1345 for(size_t i = 0; i < func.argNames.length; ++i) 1346 { 1347 if(i < args.length) 1348 _currentEnvironment.forceSetVarOrConst(func.argNames[i], args[i], false); 1349 else 1350 _currentEnvironment.forceSetVarOrConst(func.argNames[i], ScriptAny.UNDEFINED, false); 1351 } 1352 // put all arguments inside "arguments" local 1353 _currentEnvironment.forceSetVarOrConst("arguments", ScriptAny(args), false); 1354 // set up "this" local 1355 _currentEnvironment.forceSetVarOrConst("this", thisObj, true); 1356 foreach(statement ; func.statementNodes) 1357 { 1358 vr = statement.accept(this).get!VisitResult; 1359 if(vr.breakFlag) // TODO add enum stack to parser to prevent validation of breaks inside functions without loop 1360 vr.breakFlag = false; 1361 if(vr.continueFlag) // likewise 1362 vr.continueFlag = false; 1363 if(vr.returnFlag || vr.exception !is null) 1364 { 1365 if(vr.exception !is null) 1366 vr.exception.scriptTraceback ~= tuple(statement.line, statement.toString()); 1367 vr.returnFlag = false; 1368 break; 1369 } 1370 } 1371 if(returnThis) 1372 { 1373 bool _; // @suppress(dscanner.suspicious.unmodified) 1374 immutable thisPtr = cast(immutable)_currentEnvironment.lookupVariableOrConst("this", _); 1375 if(thisPtr != null) 1376 vr.result = *thisPtr; 1377 } 1378 _currentEnvironment = prevEnvironment; 1379 return vr; 1380 } 1381 else 1382 { 1383 ScriptAny returnValue; 1384 auto nfe = NativeFunctionError.NO_ERROR; 1385 if(func.type == ScriptFunction.Type.NATIVE_FUNCTION) 1386 { 1387 auto nativefn = func.nativeFunction; 1388 returnValue = nativefn(_currentEnvironment, &thisObj, args, nfe); 1389 } 1390 else 1391 { 1392 auto nativedg = func.nativeDelegate; 1393 returnValue = nativedg(_currentEnvironment, &thisObj, args, nfe); 1394 } 1395 if(returnThis) 1396 vr.result = thisObj; 1397 else 1398 vr.result = returnValue; 1399 // check NFE 1400 final switch(nfe) 1401 { 1402 case NativeFunctionError.NO_ERROR: 1403 break; 1404 case NativeFunctionError.WRONG_NUMBER_OF_ARGS: 1405 vr.exception = new ScriptRuntimeException("Incorrect number of args to native method or function"); 1406 break; 1407 case NativeFunctionError.WRONG_TYPE_OF_ARG: 1408 vr.exception = new ScriptRuntimeException("Wrong argument type to native method or function"); 1409 break; 1410 case NativeFunctionError.RETURN_VALUE_IS_EXCEPTION: 1411 vr.exception = new ScriptRuntimeException(returnValue.toString()); 1412 break; 1413 } 1414 1415 return vr; 1416 } 1417 } 1418 1419 deprecated VisitResult convertExpressionsToArgs(ExpressionNode[] exprs, out ScriptAny[] args) 1420 { 1421 args = []; 1422 VisitResult vr; 1423 foreach(expr ; exprs) 1424 { 1425 vr = expr.accept(this).get!VisitResult; 1426 if(vr.exception !is null) 1427 { 1428 args = []; 1429 return vr; 1430 } 1431 args ~= vr.result; 1432 } 1433 return vr; 1434 } 1435 1436 deprecated ScriptAny getLocalThis() 1437 { 1438 bool _; // @suppress(dscanner.suspicious.unmodified) 1439 if(!_currentEnvironment.variableOrConstExists("this")) 1440 return ScriptAny.UNDEFINED; 1441 auto thisObj = *(_currentEnvironment.lookupVariableOrConst("this", _)); 1442 return thisObj; 1443 } 1444 1445 deprecated VisitResult getObjectProperty(ScriptObject obj, in string propName) 1446 { 1447 VisitResult vr; 1448 ScriptObject objToSearch = obj; 1449 while(objToSearch !is null) 1450 { 1451 if(propName in objToSearch.getters) 1452 { 1453 vr = callFn(objToSearch.getters[propName], ScriptAny(obj), [], false); 1454 return vr; 1455 } 1456 objToSearch = objToSearch.prototype; 1457 } 1458 vr.exception = new ScriptRuntimeException("Object " ~ obj.toString() ~ " has no get property `" ~ propName ~ "`"); 1459 return vr; 1460 } 1461 1462 deprecated VisitResult handleArrayReassignment(Token opToken, ScriptAny arr, size_t index, ScriptAny value) 1463 { 1464 VisitResult vr; 1465 if(arr.type != ScriptAny.Type.ARRAY) 1466 { 1467 vr.exception = new ScriptRuntimeException("Cannot assign to index of non-array"); 1468 return vr; 1469 } 1470 auto scriptArray = arr.toValue!ScriptArray; 1471 if(index >= scriptArray.length) 1472 { 1473 vr.exception = new ScriptRuntimeException("Out of bounds array assignment"); 1474 return vr; 1475 } 1476 1477 switch(opToken.type) 1478 { 1479 case Token.Type.ASSIGN: 1480 scriptArray.array[index] = value; 1481 break; 1482 case Token.Type.PLUS_ASSIGN: 1483 scriptArray.array[index] = scriptArray.array[index] + value; 1484 break; 1485 case Token.Type.DASH_ASSIGN: 1486 scriptArray.array[index] = scriptArray.array[index] - value; 1487 break; 1488 default: 1489 throw new Exception("Unhandled assignment operator"); 1490 } 1491 vr.result = scriptArray.array[index]; 1492 return vr; 1493 } 1494 1495 deprecated VisitResult handleObjectReassignment(Token opToken, ScriptAny objToAccess, in string index, ScriptAny value) 1496 { 1497 VisitResult vr; 1498 if(!objToAccess.isObject) 1499 { 1500 vr.exception = new ScriptRuntimeException("Cannot index non-object"); 1501 return vr; 1502 } 1503 auto obj = objToAccess.toValue!ScriptObject; 1504 // we may need the original value 1505 ScriptAny originalValue, newValue; 1506 if(obj.hasGetter(index)) 1507 { 1508 // if getter with no setter this is an error 1509 if(!obj.hasSetter(index)) 1510 { 1511 vr.exception = new ScriptRuntimeException("Object " ~ obj.toString() ~ " has no set property `" ~ index ~ "`"); 1512 return vr; 1513 } 1514 vr = getObjectProperty(obj, index); 1515 if(vr.exception !is null) 1516 return vr; 1517 originalValue = vr.result; 1518 } 1519 else 1520 { 1521 originalValue = obj[index]; 1522 } 1523 1524 switch(opToken.type) 1525 { 1526 case Token.Type.ASSIGN: 1527 newValue = value; 1528 break; 1529 case Token.Type.PLUS_ASSIGN: 1530 newValue = originalValue + value; 1531 break; 1532 case Token.Type.DASH_ASSIGN: 1533 newValue = originalValue - value; 1534 break; 1535 default: 1536 throw new Exception("Unhandled assignment operator"); 1537 } 1538 if(obj.hasSetter(index)) 1539 { 1540 setObjectProperty(obj, index, newValue); 1541 if(obj.hasGetter(index)) 1542 vr.result = newValue; 1543 } 1544 else 1545 { 1546 obj.assignField(index, newValue); 1547 vr.result = newValue; 1548 } 1549 return vr; 1550 } 1551 1552 deprecated VisitResult handleVarDeclaration(in string qual, in string varName, ScriptAny value) 1553 { 1554 VisitResult vr; 1555 bool ok = false; 1556 string msg = ""; 1557 if(qual == "var") 1558 { 1559 ok = _globalEnvironment.declareVariableOrConst(varName, value, false); 1560 if(!ok) 1561 msg = "Unable to redeclare global " ~ varName; 1562 } 1563 else if(qual == "let") 1564 { 1565 ok = _currentEnvironment.declareVariableOrConst(varName, value, false); 1566 if(!ok) 1567 msg = "Unable to redeclare local variable " ~ varName; 1568 } 1569 else if(qual == "const") 1570 { 1571 ok = _currentEnvironment.declareVariableOrConst(varName, value, true); 1572 if(!ok) 1573 msg = "Unable to redeclare local const " ~ varName; 1574 } 1575 if(!ok) 1576 vr.exception = new ScriptRuntimeException(msg); 1577 return vr; 1578 } 1579 1580 deprecated VisitResult handleVarReassignment(Token opToken, in string varName, ScriptAny value) 1581 { 1582 bool isConst; // @suppress(dscanner.suspicious.unmodified) 1583 auto ptr = _currentEnvironment.lookupVariableOrConst(varName, isConst); 1584 VisitResult vr; 1585 if(isConst) 1586 vr.exception = new ScriptRuntimeException("Unable to reassign const " ~ varName); 1587 else if(ptr == null) 1588 vr.exception = new ScriptRuntimeException("Unable to reassign undefined variable " ~ varName); 1589 1590 if(vr.exception) 1591 return vr; 1592 1593 switch(opToken.type) 1594 { 1595 case Token.Type.ASSIGN: 1596 *ptr = value; 1597 break; 1598 case Token.Type.PLUS_ASSIGN: 1599 *ptr = *ptr + value; 1600 break; 1601 case Token.Type.DASH_ASSIGN: 1602 *ptr = *ptr - value; 1603 break; 1604 default: 1605 throw new Exception("Unhandled reassignment operator"); 1606 } 1607 vr.result = *ptr; 1608 return vr; 1609 } 1610 1611 deprecated VisitResult setObjectProperty(ScriptObject obj, string propName, ScriptAny value) 1612 { 1613 VisitResult vr; 1614 auto objectToSearch = obj; 1615 while(objectToSearch !is null) 1616 { 1617 if(propName in objectToSearch.setters) 1618 { 1619 vr = callFn(objectToSearch.setters[propName], ScriptAny(obj), [value], false); 1620 return vr; 1621 } 1622 objectToSearch = objectToSearch.prototype; 1623 } 1624 vr.exception = new ScriptRuntimeException("Object " ~ obj.toString() ~ " has no set property `" ~ propName ~ "`"); 1625 return vr; 1626 } 1627 1628 Compiler _compiler; 1629 bool _printVMDebugInfo; 1630 VirtualMachine _vm; 1631 Environment _globalEnvironment; 1632 Environment _currentEnvironment; 1633 } 1634