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