1 /** 2 This module implements the VirtualMachine that executes compiled bytecode. 3 ──────────────────────────────────────────────────────────────────────────────── 4 Copyright (C) 2021 pillager86.rf.gd 5 6 This program is free software: you can redistribute it and/or modify it under 7 the terms of the GNU General Public License as published by the Free Software 8 Foundation, either version 3 of the License, or (at your option) any later 9 version. 10 11 This program is distributed in the hope that it will be useful, but WITHOUT ANY 12 WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A 13 PARTICULAR PURPOSE. See the GNU General Public License for more details. 14 15 You should have received a copy of the GNU General Public License along with 16 this program. If not, see <https://www.gnu.org/licenses/>. 17 */ 18 module mildew.vm.virtualmachine; 19 20 import std.concurrency; 21 import std.conv: to; 22 import std.stdio; 23 import std..string; 24 import std.typecons; 25 26 import mildew.environment; 27 import mildew.exceptions; 28 import mildew.stdlib.generator; 29 import mildew.stdlib.regexp; 30 import mildew.types; 31 import mildew.util.encode; 32 import mildew.util.regex: extract; 33 import mildew.util.stack; 34 import mildew.vm.chunk; 35 import mildew.vm.consttable; 36 37 /// 8-bit opcodes 38 enum OpCode : ubyte 39 { 40 NOP, // nop() -> ip += 1 41 CONST, // const(uint) : load a const by index from the const table 42 CONST_0, // const0() : load long(0) on to stack 43 CONST_1, // const1() : push 1 to the stack 44 CONST_N1, // constN1() : push -1 to the stack 45 PUSH, // push(int) : push a stack value, can start at -1 46 POP, // pop() : remove exactly one value from stack 47 POPN, // pop(uint) : remove n values from stack 48 SET, // set(uint) : set index of stack to value at top without popping stack 49 STACK, // stack(uint) : push n number of undefines to stack 50 STACK_1, // stack1() : push one undefined to stack 51 ARRAY, // array(uint) : pops n items to create array and pushes to top 52 OBJECT, // array(uint) : create an object from key-value pairs starting with stack[-n] so n must be even 53 CLASS, // class(ubyte,ubyte,ubyte,ubyte) : first arg is how many normal method pairs, second arg is how many getters, 54 // third arg is how many setter pairs, 4th arg is static method pairs, and at stack[-1] base class. 55 // the finalized class constructor is pushed after all those values are popped. String group before method group 56 // stack[-2] is constructor 57 REGEX, // creates a regex from a string at stack[-1] such as /foo/g 58 ITER, // pushes a function that returns {value:..., done:bool} performed on pop() 59 DEL, // delete member stack[-1]:string from object stack[2]. pops 2 60 NEW, // similar to call(uint) except only func, arg1, arg2, etc. 61 THIS, // pushes local "this" or undefined if not found 62 OPENSCOPE, // openscope() : open an environment scope 63 CLOSESCOPE, // closescope() : close an environment scope 64 DECLVAR, // declvar(uint) : declare pop() to a global described by a const string 65 DECLLET, // decllet(uint) : declare pop() to a local described by a const string 66 DECLCONST, // declconst(uint) : declare pop() as stack[-2] to a local const described by a const 67 GETVAR, // getvar(uint) : push variable described by const table string on to stack 68 SETVAR, // setvar(uint) : store top() in variable described by const table index string leave value on top 69 OBJGET, // objget() : retrieves stack[-2][stack[-1]], popping 2 and pushing 1 70 OBJSET, // objset() : sets stack[-3][stack[-2]] to stack[-1], pops 3 and pushes the value that was set 71 CALL, // call(uint) : stack should be this, func, arg1, arg2, arg3 and arg would be 3 72 JMPFALSE, // jmpfalse(int) : relative jump 73 JMP, // jmp(int) -> : relative jump 74 SWITCH, // switch(uint) -> arg=abs jmp, stack[-2] jmp table stack[-1] value to test 75 GOTO, // goto(uint, ubyte) : absolute ip. second param is number of scopes to subtract 76 THROW, // throw() : throws pop() as a script runtime exception 77 RETHROW, // rethrow() : rethrow the exception flag, should only be generated with try-finally 78 TRY, // try(uint) : parameter is ip to goto for catch (or sometimes finally if finally only) 79 ENDTRY, // pop unconsumed try-entry from try-entry list 80 LOADEXC, // loads the current exception on to stack, either message or thrown value 81 82 // special ops 83 CONCAT, // concat(uint) : concat N elements on stack and push resulting string 84 85 // binary and unary and terniary ops 86 BITNOT, // bitwise not 87 NOT, // not top() 88 NEGATE, // negate top() 89 TYPEOF, // typeof operator 90 INSTANCEOF, // instanceof operator 91 POW, // exponent operation 92 MUL, // multiplication 93 DIV, // division 94 MOD, // modulo 95 ADD, // add() : adds stack[-2,-1], pops 2, push 1 96 SUB, // minus 97 BITLSH, // bit shift left top 98 BITRSH, // bit shift right top 99 BITURSH, // bit shift right unsigned 100 LT, // less than 101 LE, // less than or equal 102 GT, // greater than 103 GE, // greater than or equal 104 EQUALS, // equals 105 NEQUALS, // !equals 106 STREQUALS, // strict equals (===) 107 BITAND, // bitwise and 108 BITOR, // bitwise or 109 BITXOR, // bitwise xor 110 AND, // and 111 OR, // or 112 TERN, // : ? looks at stack[-3..-1] 113 114 RETURN, // return from a function, should leave exactly one value on stack 115 HALT, // completely stop the vm 116 } 117 118 alias OpCodeFunction = int function(VirtualMachine, Chunk chunk); 119 120 private ScriptAny getLocalThis(Environment env) 121 { 122 bool _; // @suppress(dscanner.suspicious.unmodified) 123 auto thisPtr = env.lookupVariableOrConst("this", _); 124 if(thisPtr == null) 125 return ScriptAny.UNDEFINED; 126 else 127 return *thisPtr; 128 } 129 130 /// helper function. TODO: rework as flag system that implements try-catch-finally mechanics 131 private int throwRuntimeError(in string message, VirtualMachine vm, Chunk chunk, 132 ScriptAny thrownValue = ScriptAny.UNDEFINED, 133 ScriptRuntimeException rethrow = null) 134 { 135 vm._exc = new ScriptRuntimeException(message); 136 if(thrownValue != ScriptAny.UNDEFINED) 137 vm._exc.thrownValue = thrownValue; 138 if(rethrow) 139 vm._exc = rethrow; 140 // unwind stack starting with current 141 if(chunk.bytecode in chunk.debugMap) 142 { 143 immutable lineNum = chunk.debugMap[chunk.bytecode].getLineNumber(vm._ip); 144 vm._exc.scriptTraceback ~= tuple(lineNum, chunk.debugMap[chunk.bytecode].getSourceLine(lineNum)); 145 } 146 // consume latest try-data entry if available 147 if(vm._tryData.length > 0) 148 { 149 immutable tryData = vm._tryData[$-1]; 150 vm._tryData = vm._tryData[0..$-1]; 151 immutable depthToReduce = vm._environment.depth - tryData.depth; 152 for(int i = 0; i < depthToReduce; ++i) 153 vm._environment = vm._environment.parent; 154 vm._stack.size = tryData.stackSize; 155 vm._ip = tryData.catchGoto; 156 return 1; 157 } 158 while(vm._callStack.size > 0) 159 { 160 auto csData = vm._callStack.pop(); // @suppress(dscanner.suspicious.unmodified) 161 vm._ip = csData.ip; 162 chunk.bytecode = csData.bc; 163 vm._tryData = csData.tryData; 164 vm._environment = csData.env; 165 if(chunk.bytecode in chunk.debugMap) 166 { 167 immutable lineNum = chunk.debugMap[chunk.bytecode].getLineNumber(vm._ip); 168 vm._exc.scriptTraceback ~= tuple(lineNum, chunk.debugMap[chunk.bytecode].getSourceLine(lineNum)); 169 } 170 // look for a try-data entry from the popped call stack 171 if(vm._tryData.length > 0) 172 { 173 immutable tryData = vm._tryData[$-1]; 174 vm._tryData = vm._tryData[0..$-1]; 175 immutable depthToReduce = vm._environment.depth - tryData.depth; 176 for(int i = 0; i < depthToReduce; ++i) 177 vm._environment = vm._environment.parent; 178 vm._stack.size = tryData.stackSize; 179 vm._ip = tryData.catchGoto; 180 return 1; 181 } 182 } 183 if(vm._parent) 184 { 185 vm._parent._exc = vm._exc; 186 } 187 // there is no available script exception handler found so clear stack and throw the exception 188 vm._stack.size = 0; 189 throw vm._exc; 190 } 191 192 private string opCodeToString(const OpCode op) 193 { 194 return op.to!string().toLower(); 195 } 196 197 pragma(inline, true) 198 private int opNop(VirtualMachine vm, Chunk chunk) 199 { 200 ++vm._ip; 201 return 0; 202 } 203 204 pragma(inline, true) 205 private int opConst(VirtualMachine vm, Chunk chunk) 206 { 207 immutable constID = decode!uint(chunk.bytecode[vm._ip + 1..$]); 208 auto value = chunk.constTable.get(constID); 209 if(value.type == ScriptAny.Type.FUNCTION) 210 value = value.toValue!ScriptFunction().copyCompiled(vm._environment); 211 vm._stack.push(value); 212 vm._ip += 1 + uint.sizeof; 213 return 0; 214 } 215 216 pragma(inline, true) 217 private int opConst0(VirtualMachine vm, Chunk chunk) 218 { 219 vm._stack.push(ScriptAny(0)); 220 ++vm._ip; 221 return 0; 222 } 223 224 pragma(inline, true) 225 private int opConst1(VirtualMachine vm, Chunk chunk) 226 { 227 vm._stack.push(ScriptAny(1)); 228 ++vm._ip; 229 return 0; 230 } 231 232 pragma(inline, true) 233 private int opPush(VirtualMachine vm, Chunk chunk) 234 { 235 immutable index = decode!int(chunk.bytecode[vm._ip + 1..$]); 236 if(index < 0) 237 vm._stack.push(vm._stack.array[$ + index]); 238 else 239 vm._stack.push(vm._stack.array[index]); 240 vm._ip += 1 + int.sizeof; 241 return 0; 242 } 243 244 pragma(inline, true) 245 private int opPop(VirtualMachine vm, Chunk chunk) 246 { 247 vm._stack.pop(); 248 ++vm._ip; 249 return 0; 250 } 251 252 pragma(inline, true) 253 private int opPopN(VirtualMachine vm, Chunk chunk) 254 { 255 immutable amount = decode!uint(chunk.bytecode[vm._ip + 1..$]); 256 vm._stack.pop(amount); 257 vm._ip += 1 + uint.sizeof; 258 return 0; 259 } 260 261 pragma(inline, true) 262 private int opSet(VirtualMachine vm, Chunk chunk) 263 { 264 immutable index = decode!uint(chunk.bytecode[vm._ip + 1..$]); 265 vm._stack.array[index] = vm._stack.array[$-1]; 266 vm._ip += 1 + uint.sizeof; 267 return 0; 268 } 269 270 pragma(inline, true) 271 private int opStack(VirtualMachine vm, Chunk chunk) 272 { 273 immutable n = decode!uint(chunk.bytecode[vm._ip + 1..$]); 274 ScriptAny[] undefineds = new ScriptAny[n]; 275 vm._stack.push(undefineds); 276 vm._ip += 1 + uint.sizeof; 277 return 0; 278 } 279 280 pragma(inline, true) 281 private int opStack1(VirtualMachine vm, Chunk chunk) 282 { 283 vm._stack.push(ScriptAny.UNDEFINED); 284 ++vm._ip; 285 return 0; 286 } 287 288 pragma(inline, true) 289 private int opArray(VirtualMachine vm, Chunk chunk) 290 { 291 immutable n = decode!uint(chunk.bytecode[vm._ip + 1..$]); 292 auto arr = vm._stack.pop(n); 293 vm._stack.push(ScriptAny(arr)); 294 vm._ip += 1 + uint.sizeof; 295 return 0; 296 } 297 298 pragma(inline, true) 299 private int opObject(VirtualMachine vm, Chunk chunk) 300 { 301 immutable n = decode!uint(chunk.bytecode[vm._ip + 1..$]) * 2; 302 auto pairList = vm._stack.pop(n); 303 auto obj = new ScriptObject("object", null, null); 304 for(uint i = 0; i < n; i += 2) 305 obj[pairList[i].toString()] = pairList[i+1]; 306 vm._stack.push(ScriptAny(obj)); 307 vm._ip += 1 + uint.sizeof; 308 return 0; 309 } 310 311 pragma(inline, true) 312 private int opClass(VirtualMachine vm, Chunk chunk) 313 { 314 immutable numMethods = decode!ubyte(chunk.bytecode[vm._ip + 1..$]); 315 immutable numGetters = decode!ubyte(chunk.bytecode[vm._ip + 2..$]); 316 immutable numSetters = decode!ubyte(chunk.bytecode[vm._ip + 3..$]); 317 immutable numStatics = decode!ubyte(chunk.bytecode[vm._ip + 4..$]); 318 auto baseClass = vm._stack.pop(); 319 auto ctor = vm._stack.pop(); // @suppress(dscanner.suspicious.unmodified) 320 auto statics = vm._stack.pop(numStatics); 321 auto staticNames = vm._stack.pop(numStatics); 322 auto setters = vm._stack.pop(numSetters); 323 auto setterNames = vm._stack.pop(numSetters); 324 auto getters = vm._stack.pop(numGetters); 325 auto getterNames = vm._stack.pop(numGetters); 326 auto methods = vm._stack.pop(numMethods); 327 auto methodNames = vm._stack.pop(numMethods); 328 329 auto constructor = ctor.toValue!ScriptFunction; 330 if(constructor is null) 331 throw new VMException("Malformed class instruction: invalid constructor", vm._ip, OpCode.CLASS); 332 333 for(auto i = 0; i < numMethods; ++i) 334 { 335 auto method = methods[i].toValue!ScriptFunction; // @suppress(dscanner.suspicious.unmodified) 336 if(method is null) 337 throw new VMException("Malformed class instruction: invalid method", vm._ip, OpCode.CLASS); 338 constructor["prototype"][methodNames[i].toString] = method; 339 } 340 341 for(auto i = 0; i < numGetters; ++i) 342 { 343 auto getter = getters[i].toValue!ScriptFunction; 344 if(getter is null) 345 throw new VMException("Malformed class instruction: invalid get property", vm._ip, OpCode.CLASS); 346 constructor["prototype"].addGetterProperty(getterNames[i].toString(), getter); 347 } 348 349 for(auto i = 0; i < numSetters; ++i) 350 { 351 auto setter = setters[i].toValue!ScriptFunction; 352 if(setter is null) 353 throw new VMException("Malformed class instruction: invalid set property", vm._ip, OpCode.CLASS); 354 constructor["prototype"].addSetterProperty(setterNames[i].toString(), setter); 355 } 356 357 for(auto i = 0; i < numStatics; ++i) 358 { 359 constructor[staticNames[i].toString()] = statics[i]; 360 } 361 362 if(baseClass) 363 { 364 auto baseClassCtor = baseClass.toValue!ScriptFunction; 365 if(baseClassCtor is null) 366 return throwRuntimeError("Invalid base class " ~ baseClass.toString(), vm, chunk); 367 auto ctorPrototype = constructor["prototype"].toValue!ScriptObject; 368 ctorPrototype.prototype = baseClassCtor["prototype"].toValue!ScriptObject; 369 constructor.prototype = baseClassCtor; 370 } 371 372 // push the resulting modified constructor 373 vm._stack.push(ScriptAny(constructor)); 374 375 vm._ip += 1 + 4 * ubyte.sizeof; 376 return 0; 377 } 378 379 pragma(inline, true) 380 private int opRegex(VirtualMachine vm, Chunk chunk) 381 { 382 auto regexString = vm._stack.pop().toString(); 383 auto parts = extract(regexString); 384 auto regexResult = ScriptAny(new ScriptObject("RegExp", getRegExpProto, new ScriptRegExp(parts[0], parts[1]))); 385 vm._stack.push(regexResult); 386 ++vm._ip; 387 return 0; 388 } 389 390 pragma(inline, true) 391 private int opIter(VirtualMachine vm, Chunk chunk) 392 { 393 auto objToIterate = vm._stack.pop(); 394 // can be a string, array, or object 395 // FUTURE: a Generator returned by a Generator function 396 if(!(objToIterate.isObject)) 397 return throwRuntimeError("Cannot iterate over non-object " ~ objToIterate.toString, vm, chunk); 398 if(objToIterate.type == ScriptAny.Type.STRING) 399 { 400 immutable elements = objToIterate.toValue!string; 401 auto generator = new Generator!(Tuple!(size_t,dstring))({ 402 size_t indexCounter = 0; 403 foreach(dchar ele ; elements) 404 { 405 ++indexCounter; 406 yield(tuple(indexCounter-1,ele.to!dstring)); 407 } 408 }); 409 vm._stack.push(ScriptAny(new ScriptFunction("next", 410 delegate ScriptAny(Environment env, ScriptAny* thisObj, ScriptAny[] args, ref NativeFunctionError){ 411 auto retVal = new ScriptObject("iteration", null, null); 412 if(generator.empty) 413 { 414 retVal.assignField("done", ScriptAny(true)); 415 } 416 else 417 { 418 auto result = generator.front(); 419 retVal.assignField("key", ScriptAny(result[0])); 420 retVal.assignField("value", ScriptAny(result[1])); 421 generator.popFront(); 422 } 423 return ScriptAny(retVal); 424 }, 425 false))); 426 } 427 else if(objToIterate.type == ScriptAny.Type.ARRAY) 428 { 429 auto elements = objToIterate.toValue!(ScriptAny[]); // @suppress(dscanner.suspicious.unmodified) 430 auto generator = new Generator!(Tuple!(size_t, ScriptAny))({ 431 size_t indexCounter = 0; 432 foreach(item ; elements) 433 { 434 ++indexCounter; 435 yield(tuple(indexCounter-1, item)); 436 } 437 }); 438 vm._stack.push(ScriptAny(new ScriptFunction("next", 439 delegate ScriptAny(Environment env, ScriptAny* thisObj, ScriptAny[] args, ref NativeFunctionError) { 440 auto retVal = new ScriptObject("iteration", null, null); 441 if(generator.empty) 442 { 443 retVal.assignField("done", ScriptAny(true)); 444 } 445 else 446 { 447 auto result = generator.front(); 448 retVal.assignField("key", ScriptAny(result[0])); 449 retVal.assignField("value", ScriptAny(result[1])); 450 generator.popFront(); 451 } 452 return ScriptAny(retVal); 453 }))); 454 } 455 else if(objToIterate.isObject) 456 { 457 if(objToIterate.isNativeObjectType!ScriptGenerator) 458 { 459 auto func = new ScriptFunction("next", &native_Generator_next, false); 460 func.bind(objToIterate); 461 vm._stack.push(ScriptAny(func)); 462 } 463 else 464 { 465 auto obj = objToIterate.toValue!ScriptObject; 466 auto generator = new Generator!(Tuple!(string, ScriptAny))({ 467 foreach(k, v ; obj.dictionary) 468 yield(tuple(k,v)); 469 }); 470 vm._stack.push(ScriptAny(new ScriptFunction("next", 471 delegate ScriptAny(Environment env, ScriptAny* thisObj, ScriptAny[] args, ref NativeFunctionError){ 472 auto retVal = new ScriptObject("iteration", null, null); 473 if(generator.empty) 474 { 475 retVal.assignField("done", ScriptAny(true)); 476 } 477 else 478 { 479 auto result = generator.front(); 480 retVal.assignField("key", ScriptAny(result[0])); 481 retVal.assignField("value", result[1]); 482 generator.popFront(); 483 } 484 return ScriptAny(retVal); 485 }))); 486 } 487 } 488 ++vm._ip; 489 return 0; 490 } 491 492 pragma(inline, true) 493 private int opDel(VirtualMachine vm, Chunk chunk) 494 { 495 auto memberToDelete = vm._stack.pop().toString(); 496 auto objToDelete = vm._stack.pop(); 497 auto obj = objToDelete.toValue!ScriptObject; 498 if(obj is null) 499 return throwRuntimeError("Cannot delete member of non-object " ~ objToDelete.toString, 500 vm, chunk); 501 obj.dictionary.remove(memberToDelete); 502 ++vm._ip; 503 return 0; 504 } 505 506 pragma(inline, true) 507 private int opNew(VirtualMachine vm, Chunk chunk) 508 { 509 immutable n = decode!uint(chunk.bytecode[vm._ip + 1..$]) + 1; 510 auto callInfo = vm._stack.pop(n); 511 auto funcAny = callInfo[0]; 512 auto args = callInfo[1..$]; 513 514 NativeFunctionError nfe = NativeFunctionError.NO_ERROR; 515 if(funcAny.type != ScriptAny.Type.FUNCTION) 516 return throwRuntimeError("Unable to instantiate new object from non-function " ~ funcAny.toString(), vm, chunk); 517 auto func = funcAny.toValue!ScriptFunction; // @suppress(dscanner.suspicious.unmodified) 518 519 ScriptAny thisObj = new ScriptObject(func.functionName, func["prototype"].toValue!ScriptObject, null); 520 521 if(func.type == ScriptFunction.Type.SCRIPT_FUNCTION) 522 { 523 if(func.compiled.length == 0) 524 throw new VMException("Empty script function cannot be called", vm._ip, OpCode.CALL); 525 if(func.isGenerator) 526 return throwRuntimeError("Cannot use new with a Generator Function", vm, chunk); 527 vm._callStack.push(VirtualMachine.CallData(VirtualMachine.FuncCallType.NEW, chunk.bytecode, 528 vm._ip, vm._environment, vm._tryData)); 529 vm._environment = new Environment(func.closure, func.functionName); 530 vm._ip = 0; 531 chunk.bytecode = func.compiled; 532 vm._tryData = []; 533 // set this 534 vm._environment.forceSetVarOrConst("this", thisObj, true); 535 // set args 536 for(size_t i = 0; i < func.argNames.length; ++i) 537 { 538 if(i >= args.length) 539 vm._environment.forceSetVarOrConst(func.argNames[i], ScriptAny.UNDEFINED, false); 540 else 541 vm._environment.forceSetVarOrConst(func.argNames[i], args[i], false); 542 } 543 vm._environment.forceSetVarOrConst("arguments", ScriptAny(args), false); 544 return 0; 545 } 546 else if(func.type == ScriptFunction.Type.NATIVE_FUNCTION) 547 { 548 auto nativeFunc = func.nativeFunction; 549 auto possibleError = nativeFunc(vm._environment, &thisObj, args, nfe); 550 if(nfe == NativeFunctionError.RETURN_VALUE_IS_EXCEPTION) 551 vm._stack.push(possibleError); 552 else 553 vm._stack.push(thisObj); 554 } 555 else if(func.type == ScriptFunction.Type.NATIVE_DELEGATE) 556 { 557 auto nativeDelegate = func.nativeDelegate; 558 auto possibleError = nativeDelegate(vm._environment, &thisObj, args, nfe); 559 if(nfe == NativeFunctionError.RETURN_VALUE_IS_EXCEPTION) 560 vm._stack.push(possibleError); 561 else 562 vm._stack.push(thisObj); 563 } 564 final switch(nfe) 565 { 566 case NativeFunctionError.NO_ERROR: 567 break; 568 case NativeFunctionError.RETURN_VALUE_IS_EXCEPTION: 569 return throwRuntimeError(vm._stack.pop().toString(), vm, chunk); 570 case NativeFunctionError.WRONG_NUMBER_OF_ARGS: 571 return throwRuntimeError("Wrong number of arguments to native function", vm, chunk); 572 case NativeFunctionError.WRONG_TYPE_OF_ARG: 573 return throwRuntimeError("Wrong type of argument to native function", vm, chunk); 574 } 575 if(vm._exc) 576 return throwRuntimeError(null, vm, chunk, ScriptAny.UNDEFINED, vm._exc); 577 vm._ip += 1 + uint.sizeof; 578 return 0; 579 } 580 581 pragma(inline, true) 582 private int opThis(VirtualMachine vm, Chunk chunk) 583 { 584 bool _; // @suppress(dscanner.suspicious.unmodified) 585 auto thisPtr = vm._environment.lookupVariableOrConst("this", _); 586 if(thisPtr == null) 587 vm._stack.push(ScriptAny.UNDEFINED); 588 else 589 vm._stack.push(*thisPtr); 590 ++vm._ip; 591 return 0; 592 } 593 594 pragma(inline, true) 595 private int opOpenScope(VirtualMachine vm, Chunk chunk) 596 { 597 vm._environment = new Environment(vm._environment); 598 // debug writefln("VM{ environment depth=%s", vm._environment.depth); 599 ++vm._ip; 600 return 0; 601 } 602 603 pragma(inline, true) 604 private int opCloseScope(VirtualMachine vm, Chunk chunk) 605 { 606 vm._environment = vm._environment.parent; 607 // debug writefln("VM} environment depth=%s", vm._environment.depth); 608 ++vm._ip; 609 return 0; 610 } 611 612 pragma(inline, true) 613 private int opDeclVar(VirtualMachine vm, Chunk chunk) 614 { 615 auto constID = decode!uint(chunk.bytecode[vm._ip + 1..$]); 616 auto varName = chunk.constTable.get(constID).toString(); 617 auto value = vm._stack.pop(); 618 immutable ok = vm._globals.declareVariableOrConst(varName, value, false); 619 if(!ok) 620 return throwRuntimeError("Cannot redeclare global " ~ varName, vm, chunk); 621 vm._ip += 1 + uint.sizeof; 622 return 0; 623 } 624 625 pragma(inline, true) 626 private int opDeclLet(VirtualMachine vm, Chunk chunk) 627 { 628 auto constID = decode!uint(chunk.bytecode[vm._ip + 1..$]); 629 auto varName = chunk.constTable.get(constID).toString(); 630 auto value = vm._stack.pop(); 631 immutable ok = vm._environment.declareVariableOrConst(varName, value, false); 632 if(!ok) 633 return throwRuntimeError("Cannot redeclare local " ~ varName, vm, chunk); 634 vm._ip += 1 + uint.sizeof; 635 return 0; 636 } 637 638 pragma(inline, true) 639 private int opDeclConst(VirtualMachine vm, Chunk chunk) 640 { 641 auto constID = decode!uint(chunk.bytecode[vm._ip + 1..$]); 642 auto varName = chunk.constTable.get(constID).toString(); 643 auto value = vm._stack.pop(); 644 immutable ok = vm._environment.declareVariableOrConst(varName, value, true); 645 if(!ok) 646 return throwRuntimeError("Cannot redeclare const " ~ varName, vm, chunk); 647 vm._ip += 1 + uint.sizeof; 648 return 0; 649 } 650 651 pragma(inline, true) 652 private int opGetVar(VirtualMachine vm, Chunk chunk) 653 { 654 auto constID = decode!uint(chunk.bytecode[vm._ip + 1..$]); 655 auto varName = chunk.constTable.get(constID).toString(); 656 bool isConst; // @suppress(dscanner.suspicious.unmodified) 657 auto valuePtr = vm._environment.lookupVariableOrConst(varName, isConst); 658 if(valuePtr == null) 659 return throwRuntimeError("Variable lookup failed: " ~ varName, vm, chunk); 660 vm._stack.push(*valuePtr); 661 vm._ip += 1 + uint.sizeof; 662 return 0; 663 } 664 665 pragma(inline, true) 666 private int opSetVar(VirtualMachine vm, Chunk chunk) 667 { 668 auto constID = decode!uint(chunk.bytecode[vm._ip + 1..$]); 669 auto varName = chunk.constTable.get(constID).toString(); 670 bool isConst; // @suppress(dscanner.suspicious.unmodified) 671 auto varPtr = vm._environment.lookupVariableOrConst(varName, isConst); 672 if(varPtr == null) 673 { 674 return throwRuntimeError("Cannot assign to undefined variable: " ~ varName, vm, chunk); 675 } 676 auto value = vm._stack.peek(); // @suppress(dscanner.suspicious.unmodified) 677 if(value == ScriptAny.UNDEFINED) 678 vm._environment.unsetVariable(varName); 679 else 680 *varPtr = value; 681 vm._ip += 1 + uint.sizeof; 682 return 0; 683 } 684 685 pragma(inline, true) 686 private int opObjGet(VirtualMachine vm, Chunk chunk) 687 { 688 import std.utf: UTFException; 689 690 auto objToAccess = vm._stack.array[$-2]; 691 auto field = vm._stack.array[$-1]; // @suppress(dscanner.suspicious.unmodified) 692 vm._stack.pop(2); 693 // TODO handle getters 694 // if field is integer it is array access 695 if(field.isNumber 696 && (objToAccess.type == ScriptAny.Type.ARRAY || objToAccess.type == ScriptAny.Type.STRING)) 697 { 698 auto index = field.toValue!long; 699 if(objToAccess.type == ScriptAny.Type.ARRAY) 700 { 701 auto arr = objToAccess.toValue!(ScriptAny[]); 702 if(index < 0) 703 index = arr.length + index; 704 if(index < 0 || index >= arr.length) 705 return throwRuntimeError("Out of bounds array access", vm, chunk); 706 vm._stack.push(arr[index]); 707 } 708 else if(objToAccess.type == ScriptAny.Type.STRING) 709 { 710 auto str = objToAccess.toValue!(ScriptString)().toString(); 711 if(index < 0) 712 index = str.length + index; 713 if(index < 0 || index >= str.length) 714 return throwRuntimeError("Out of bounds string access", vm, chunk); 715 try 716 { 717 vm._stack.push(ScriptAny([str[index]])); 718 } 719 catch(UTFException) 720 { 721 vm._stack.push(ScriptAny.UNDEFINED); 722 } 723 } 724 } 725 else // else object field or property access 726 { 727 auto index = field.toString(); 728 if(objToAccess.isObject) 729 { 730 auto obj = objToAccess.toValue!ScriptObject; // @suppress(dscanner.suspicious.unmodified) 731 auto getter = obj.findGetter(index); 732 if(getter) 733 { 734 // this might be a super property call 735 auto thisObj = getLocalThis(vm._environment); 736 ScriptAny retVal; 737 if(ScriptFunction.isInstanceOf(thisObj.toValue!ScriptObject, 738 objToAccess["constructor"].toValue!ScriptFunction)) 739 { 740 retVal = vm.runFunction(getter, thisObj, []); 741 } 742 else 743 { 744 retVal = vm.runFunction(getter, objToAccess, []); 745 } 746 vm._stack.push(retVal); 747 if(vm._exc) 748 throwRuntimeError(null, vm, chunk, ScriptAny.UNDEFINED, vm._exc); 749 } 750 else 751 vm._stack.push(objToAccess[index]); 752 } 753 else 754 throwRuntimeError("Unable to access members of non-object " ~ objToAccess.toString(), 755 vm, chunk); 756 } 757 ++vm._ip; 758 return 0; 759 } 760 761 pragma(inline, true) 762 private int opObjSet(VirtualMachine vm, Chunk chunk) 763 { 764 auto objToAccess = vm._stack.array[$-3]; 765 auto fieldToAssign = vm._stack.array[$-2]; // @suppress(dscanner.suspicious.unmodified) 766 auto value = vm._stack.array[$-1]; 767 vm._stack.pop(3); 768 if(fieldToAssign.isNumber 769 && (objToAccess.type == ScriptAny.Type.ARRAY || objToAccess.type == ScriptAny.Type.STRING)) 770 { 771 auto index = fieldToAssign.toValue!long; 772 if(objToAccess.type == ScriptAny.Type.ARRAY) 773 { 774 auto arr = objToAccess.toValue!(ScriptAny[]); 775 if(index < 0) 776 index = arr.length + index; 777 if(index < 0 || index >= arr.length) 778 return throwRuntimeError("Out of bounds array assignment", vm, chunk); 779 arr[index] = value; 780 vm._stack.push(value); 781 } 782 } 783 else 784 { 785 auto index = fieldToAssign.toValue!string; 786 if(!objToAccess.isObject) 787 return throwRuntimeError("Unable to assign member of non-object " ~ objToAccess.toString(), vm, chunk); 788 auto obj = objToAccess.toValue!ScriptObject; // @suppress(dscanner.suspicious.unmodified) 789 auto setter = obj.findSetter(index); 790 if(setter) 791 { 792 auto thisObj = getLocalThis(vm._environment); 793 immutable isSuperProp = ScriptFunction.isInstanceOf(thisObj.toValue!ScriptObject, 794 objToAccess["constructor"].toValue!ScriptFunction); 795 if(isSuperProp) 796 vm.runFunction(setter, thisObj, [value]); 797 else 798 vm.runFunction(setter, objToAccess, [value]); 799 if(vm._exc) 800 throwRuntimeError(null, vm, chunk, ScriptAny.UNDEFINED, vm._exc); 801 // if getter push that or else undefined 802 auto getter = obj.findGetter(index); 803 if(getter) 804 { 805 if(isSuperProp) 806 vm._stack.push(vm.runFunction(getter, thisObj, [])); 807 else 808 vm._stack.push(vm.runFunction(getter, objToAccess, [])); 809 } 810 else 811 { 812 vm._stack.push(ScriptAny.UNDEFINED); 813 } 814 815 if(vm._exc) 816 return throwRuntimeError(null, vm, chunk, ScriptAny.UNDEFINED, vm._exc); 817 } 818 else 819 { 820 if(obj.hasGetter(index)) 821 return throwRuntimeError("Object " ~ obj.toString() ~ " has getter for property " 822 ~ index ~ " but no setter.", vm, chunk); 823 objToAccess[index] = value; 824 vm._stack.push(value); 825 } 826 } 827 ++vm._ip; 828 return 0; 829 } 830 831 pragma(inline, true) 832 private int opCall(VirtualMachine vm, Chunk chunk) 833 { 834 immutable n = decode!uint(chunk.bytecode[vm._ip + 1..$]) + 2; 835 if(vm._stack.size < n) 836 { 837 vm.printStack(); 838 throw new VMException("opCall failure, stack < " ~ n.to!string, vm._ip, OpCode.CALL); 839 } 840 auto callInfo = vm._stack.pop(n); 841 auto thisObj = callInfo[0]; // @suppress(dscanner.suspicious.unmodified) 842 auto funcAny = callInfo[1]; 843 auto args = callInfo[2..$]; 844 NativeFunctionError nfe = NativeFunctionError.NO_ERROR; 845 if(funcAny.type != ScriptAny.Type.FUNCTION) 846 return throwRuntimeError("Unable to call non-function " ~ funcAny.toString(), vm, chunk); 847 auto func = funcAny.toValue!ScriptFunction; // @suppress(dscanner.suspicious.unmodified) 848 if(func.boundThis != ScriptAny.UNDEFINED) 849 thisObj = func.boundThis; 850 if(func.type == ScriptFunction.Type.SCRIPT_FUNCTION) 851 { 852 if(func.compiled.length == 0) 853 throw new VMException("Empty script function cannot be called", vm._ip, OpCode.CALL); 854 855 if(!func.isGenerator) // only script functions can be marked as generators 856 { 857 vm._callStack.push(VirtualMachine.CallData(VirtualMachine.FuncCallType.NORMAL, chunk.bytecode, 858 vm._ip, vm._environment, vm._tryData)); 859 vm._environment = new Environment(func.closure, func.functionName); 860 vm._ip = 0; 861 chunk.bytecode = func.compiled; 862 vm._tryData = []; 863 // set this 864 vm._environment.forceSetVarOrConst("this", thisObj, true); 865 // set args 866 for(size_t i = 0; i < func.argNames.length; ++i) 867 { 868 if(i >= args.length) 869 vm._environment.forceSetVarOrConst(func.argNames[i], ScriptAny.UNDEFINED, false); 870 else 871 vm._environment.forceSetVarOrConst(func.argNames[i], args[i], false); 872 } 873 // set arguments variable 874 vm._environment.forceSetVarOrConst("arguments", ScriptAny(args), false); 875 // check exc in case exception was thrown during call or apply 876 if(vm._exc) 877 return throwRuntimeError(null, vm, chunk, ScriptAny.UNDEFINED, vm._exc); 878 return 0; 879 } 880 else 881 { 882 ScriptObject newGen = new ScriptObject("Generator", getGeneratorPrototype, 883 new ScriptGenerator(vm._environment, func, args, thisObj)); 884 vm._stack.push(ScriptAny(newGen)); 885 vm._ip += 1 + uint.sizeof; 886 return 0; 887 } 888 } 889 else if(func.type == ScriptFunction.Type.NATIVE_FUNCTION) 890 { 891 auto nativeFunc = func.nativeFunction; 892 vm._stack.push(nativeFunc(vm._environment, &thisObj, args, nfe)); 893 } 894 else if(func.type == ScriptFunction.Type.NATIVE_DELEGATE) 895 { 896 auto nativeDelegate = func.nativeDelegate; 897 vm._stack.push(nativeDelegate(vm._environment, &thisObj, args, nfe)); 898 } 899 final switch(nfe) 900 { 901 case NativeFunctionError.NO_ERROR: 902 break; 903 case NativeFunctionError.RETURN_VALUE_IS_EXCEPTION: 904 return throwRuntimeError(vm._stack.peek().toString(), vm, chunk); 905 case NativeFunctionError.WRONG_NUMBER_OF_ARGS: 906 return throwRuntimeError("Wrong number of arguments to native function", vm, chunk); 907 case NativeFunctionError.WRONG_TYPE_OF_ARG: 908 return throwRuntimeError("Wrong type of argument to native function", vm, chunk); 909 } 910 if(vm._exc) 911 return throwRuntimeError(null, vm, chunk, ScriptAny.UNDEFINED, vm._exc); 912 vm._ip += 1 + uint.sizeof; 913 return 0; 914 } 915 916 pragma(inline, true) 917 private int opJmpFalse(VirtualMachine vm, Chunk chunk) 918 { 919 immutable jmpAmount = decode!int(chunk.bytecode[vm._ip + 1..$]); 920 immutable shouldJump = vm._stack.pop(); 921 if(!shouldJump) 922 vm._ip += jmpAmount; 923 else 924 vm._ip += 1 + int.sizeof; 925 return 0; 926 } 927 928 pragma(inline, true) 929 private int opJmp(VirtualMachine vm, Chunk chunk) 930 { 931 immutable jmpAmount = decode!int(chunk.bytecode[vm._ip + 1..$]); 932 vm._ip += jmpAmount; 933 return 0; 934 } 935 936 pragma(inline, true) 937 private int opSwitch(VirtualMachine vm, Chunk chunk) 938 { 939 immutable relAbsJmp = decode!uint(chunk.bytecode[vm._ip + 1..$]); 940 auto valueToTest = vm._stack.pop(); 941 auto jumpTableArray = vm._stack.pop(); 942 // build the jump table out of the entries 943 if(jumpTableArray.type != ScriptAny.Type.ARRAY) 944 throw new VMException("Invalid jump table", vm._ip, OpCode.SWITCH); 945 int[ScriptAny] jmpTable; 946 foreach(entry ; jumpTableArray.toValue!(ScriptAny[])) 947 { 948 if(entry.type != ScriptAny.Type.ARRAY) 949 throw new VMException("Invalid jump table entry", vm._ip, OpCode.SWITCH); 950 auto entryArray = entry.toValue!(ScriptAny[]); 951 if(entryArray.length < 2) 952 throw new VMException("Invalid jump table entry size", vm._ip, OpCode.SWITCH); 953 jmpTable[entryArray[0]] = entryArray[1].toValue!int; 954 } 955 if(valueToTest in jmpTable) 956 vm._ip = jmpTable[valueToTest]; 957 else 958 vm._ip = relAbsJmp; 959 return 0; 960 } 961 962 pragma(inline, true) 963 private int opGoto(VirtualMachine vm, Chunk chunk) 964 { 965 immutable address = decode!uint(chunk.bytecode[vm._ip + 1..$]); 966 immutable depth = decode!ubyte(chunk.bytecode[vm._ip+1+uint.sizeof..$]); 967 for(ubyte i = 0; i < depth; ++i) 968 { 969 vm._environment = vm._environment.parent; 970 } 971 vm._ip = address; 972 return 0; 973 } 974 975 pragma(inline, true) 976 private int opThrow(VirtualMachine vm, Chunk chunk) 977 { 978 auto valToThrow = vm._stack.pop(); 979 return throwRuntimeError("Uncaught script exception", vm, chunk, valToThrow); 980 } 981 982 pragma(inline, true) 983 private int opRethrow(VirtualMachine vm, Chunk chunk) 984 { 985 if(vm._exc) 986 return throwRuntimeError(vm._exc.msg, vm, chunk, vm._exc.thrownValue, vm._exc); 987 ++vm._ip; 988 return 0; 989 } 990 991 pragma(inline, true) 992 private int opTry(VirtualMachine vm, Chunk chunk) 993 { 994 immutable catchGoto = decode!uint(chunk.bytecode[vm._ip + 1..$]); 995 immutable depth = cast(int)vm._environment.depth(); 996 vm._tryData ~= VirtualMachine.TryData(depth, vm._stack.size, catchGoto); 997 vm._ip += 1 + uint.sizeof; 998 return 0; 999 } 1000 1001 pragma(inline, true) 1002 private int opEndTry(VirtualMachine vm, Chunk chunk) 1003 { 1004 vm._tryData = vm._tryData[0..$-1]; 1005 ++vm._ip; 1006 return 0; 1007 } 1008 1009 pragma(inline, true) 1010 private int opLoadExc(VirtualMachine vm, Chunk chunk) 1011 { 1012 if(vm._exc is null) 1013 throw new VMException("An exception was never thrown", vm._ip, OpCode.LOADEXC); 1014 if(vm._exc.thrownValue != ScriptAny.UNDEFINED) 1015 vm._stack.push(vm._exc.thrownValue); 1016 else 1017 vm._stack.push(ScriptAny(vm._exc.msg)); 1018 vm._exc = null; // once loaded by a catch block it should be cleared 1019 ++vm._ip; 1020 return 0; 1021 } 1022 1023 pragma(inline, true) 1024 private int opConcat(VirtualMachine vm, Chunk chunk) 1025 { 1026 immutable n = decode!uint(chunk.bytecode[vm._ip + 1..$]); 1027 string result = ""; 1028 auto values = vm._stack.pop(n); 1029 foreach(value ; values) 1030 result ~= value.toString(); 1031 vm._stack.push(ScriptAny(result)); 1032 vm._ip += 1 + uint.sizeof; 1033 return 0; 1034 } 1035 1036 pragma(inline, true) 1037 private int opBitNot(VirtualMachine vm, Chunk chunk) 1038 { 1039 vm._stack.array[$-1] = ~vm._stack.array[$-1]; 1040 ++vm._ip; 1041 return 0; 1042 } 1043 1044 pragma(inline, true) 1045 private int opNot(VirtualMachine vm, Chunk chunk) 1046 { 1047 vm._stack.array[$-1] = ScriptAny(!vm._stack.array[$-1]); 1048 ++vm._ip; 1049 return 0; 1050 } 1051 1052 pragma(inline, true) 1053 private int opNegate(VirtualMachine vm, Chunk chunk) 1054 { 1055 vm._stack.array[$-1] = -vm._stack.array[$-1]; 1056 ++vm._ip; 1057 return 0; 1058 } 1059 1060 pragma(inline, true) 1061 private int opTypeof(VirtualMachine vm, Chunk chunk) 1062 { 1063 vm._stack.array[$-1] = ScriptAny(vm._stack.array[$-1].typeToString()); 1064 ++vm._ip; 1065 return 0; 1066 } 1067 1068 pragma(inline, true) 1069 private int opInstanceOf(VirtualMachine vm, Chunk chunk) 1070 { 1071 auto operands = vm._stack.pop(2); 1072 if(!operands[0].isObject) 1073 vm._stack.push(ScriptAny(false)); 1074 else if(operands[1].type != ScriptAny.Type.FUNCTION) 1075 vm._stack.push(ScriptAny(false)); 1076 else 1077 { 1078 auto lhsObj = operands[0].toValue!ScriptObject; // @suppress(dscanner.suspicious.unmodified) 1079 auto rhsFunc = operands[1].toValue!ScriptFunction; // @suppress(dscanner.suspicious.unmodified) 1080 auto proto = lhsObj.prototype; 1081 while(proto !is null) 1082 { 1083 if(proto["constructor"].toValue!ScriptFunction is rhsFunc) 1084 { 1085 vm._stack.push(ScriptAny(true)); 1086 ++vm._ip; 1087 return 0; 1088 } 1089 proto = proto.prototype; 1090 } 1091 } 1092 vm._stack.push(ScriptAny(false)); 1093 ++vm._ip; 1094 return 0; 1095 } 1096 1097 private string DEFINE_BIN_OP(string name, string op)() 1098 { 1099 import std.format: format; 1100 return format(q{ 1101 pragma(inline, true) 1102 private int %1$s(VirtualMachine vm, Chunk chunk) 1103 { 1104 auto operands = vm._stack.pop(2); 1105 vm._stack.push(operands[0] %2$s operands[1]); 1106 ++vm._ip; 1107 return 0; 1108 } 1109 }, name, op); 1110 } 1111 1112 mixin(DEFINE_BIN_OP!("opPow", "^^")); 1113 mixin(DEFINE_BIN_OP!("opMul", "*")); 1114 mixin(DEFINE_BIN_OP!("opDiv", "/")); 1115 mixin(DEFINE_BIN_OP!("opMod", "%")); 1116 mixin(DEFINE_BIN_OP!("opAdd", "+")); 1117 mixin(DEFINE_BIN_OP!("opSub", "-")); 1118 mixin(DEFINE_BIN_OP!("opBitRSh", ">>")); 1119 mixin(DEFINE_BIN_OP!("opBitURSh", ">>>")); 1120 mixin(DEFINE_BIN_OP!("opBitLSh", "<<")); 1121 1122 private string DEFINE_BIN_BOOL_OP(string name, string op)() 1123 { 1124 import std.format: format; 1125 return format(q{ 1126 pragma(inline, true) 1127 private int %1$s(VirtualMachine vm, Chunk chunk) 1128 { 1129 auto operands = vm._stack.pop(2); 1130 vm._stack.push(ScriptAny(operands[0] %2$s operands[1])); 1131 ++vm._ip; 1132 return 0; 1133 } 1134 }, name, op); 1135 } 1136 1137 mixin(DEFINE_BIN_BOOL_OP!("opLT", "<")); 1138 mixin(DEFINE_BIN_BOOL_OP!("opLE", "<=")); 1139 mixin(DEFINE_BIN_BOOL_OP!("opGT", ">")); 1140 mixin(DEFINE_BIN_BOOL_OP!("opGE", ">=")); 1141 mixin(DEFINE_BIN_BOOL_OP!("opEQ", "==")); 1142 mixin(DEFINE_BIN_BOOL_OP!("opNEQ", "!=")); 1143 1144 pragma(inline, true) 1145 private int opStrictEquals(VirtualMachine vm, Chunk chunk) 1146 { 1147 auto operands = vm._stack.pop(2); 1148 vm._stack.push(ScriptAny(operands[0].strictEquals(operands[1]))); 1149 ++vm._ip; 1150 return 0; 1151 } 1152 1153 mixin(DEFINE_BIN_OP!("opBitAnd", "&")); 1154 mixin(DEFINE_BIN_OP!("opBitOr", "|")); 1155 mixin(DEFINE_BIN_OP!("opBitXor", "^")); 1156 1157 mixin(DEFINE_BIN_BOOL_OP!("opAnd", "&&")); 1158 1159 pragma(inline, true) 1160 private int opOr(VirtualMachine vm, Chunk chunk) 1161 { 1162 auto operands = vm._stack.pop(2); 1163 vm._stack.push(operands[0].orOp(operands[1])); 1164 ++vm._ip; 1165 return 0; 1166 } 1167 1168 pragma(inline, true) 1169 private int opTern(VirtualMachine vm, Chunk chunk) 1170 { 1171 auto operands = vm._stack.pop(3); 1172 if(operands[0]) 1173 vm._stack.push(operands[1]); 1174 else 1175 vm._stack.push(operands[2]); 1176 ++vm._ip; 1177 return 0; 1178 } 1179 1180 pragma(inline, true) 1181 private int opReturn(VirtualMachine vm, Chunk chunk) 1182 { 1183 if(vm._stack.size < 1) 1184 throw new VMException("Return value missing from return", vm._ip, OpCode.RETURN); 1185 1186 if(vm._callStack.size > 0) 1187 { 1188 auto fcdata = vm._callStack.pop(); // @suppress(dscanner.suspicious.unmodified) 1189 chunk.bytecode = fcdata.bc; 1190 if(fcdata.fct == VirtualMachine.FuncCallType.NEW) 1191 { 1192 // pop whatever was returned and push the "this" 1193 vm._stack.pop(); 1194 bool _; 1195 vm._stack.push(*vm._environment.lookupVariableOrConst("this", _)); 1196 } 1197 vm._ip = fcdata.ip; 1198 vm._environment = fcdata.env; 1199 vm._tryData = fcdata.tryData; 1200 } 1201 else 1202 { 1203 vm._ip = chunk.bytecode.length; 1204 } 1205 vm._ip += 1 + uint.sizeof; // the size of call() 1206 return 0; 1207 } 1208 1209 pragma(inline, true) 1210 private int opHalt(VirtualMachine vm, Chunk chunk) 1211 { 1212 vm._stopped = true; 1213 ++vm._ip; 1214 return 0; 1215 } 1216 1217 /*pragma(inline, true) 1218 private int startFunctionCall(VirtualMachine vm, Chunk chunk, ScriptFunction func, ScriptAny thisObj, ScriptAny[] args) 1219 { 1220 NativeFunctionError nfe = NativeFunctionError.NO_ERROR; 1221 if(func.type == ScriptFunction.Type.SCRIPT_FUNCTION) 1222 { 1223 if(func.compiled.length == 0) 1224 throw new VMException("Empty script function cannot be called", vm._ip, OpCode.CALL); 1225 vm._callStack.push(VirtualMachine.CallData(VirtualMachine.FuncCallType.NORMAL, chunk.bytecode, 1226 vm._ip, vm._environment, vm._tryData)); 1227 vm._environment = new Environment(func.closure, func.functionName); 1228 vm._ip = 0; 1229 chunk.bytecode = func.compiled; 1230 vm._tryData = []; 1231 // set this 1232 vm._environment.forceSetVarOrConst("this", thisObj, true); 1233 // set args 1234 for(size_t i = 0; i < func.argNames.length; ++i) 1235 { 1236 if(i >= args.length) 1237 vm._environment.forceSetVarOrConst(func.argNames[i], ScriptAny.UNDEFINED, false); 1238 else 1239 vm._environment.forceSetVarOrConst(func.argNames[i], args[i], false); 1240 } 1241 // set arguments variable 1242 vm._environment.forceSetVarOrConst("arguments", ScriptAny(args), false); 1243 // check exc in case exception was thrown during call or apply 1244 if(vm._exc) 1245 throwRuntimeError(null, vm, chunk, ScriptAny.UNDEFINED, vm._exc); 1246 return 0; 1247 } 1248 else if(func.type == ScriptFunction.Type.NATIVE_FUNCTION) 1249 { 1250 auto nativeFunc = func.nativeFunction; 1251 vm._stack.push(nativeFunc(vm._environment, &thisObj, args, nfe)); 1252 } 1253 else if(func.type == ScriptFunction.Type.NATIVE_DELEGATE) 1254 { 1255 auto nativeDelegate = func.nativeDelegate; 1256 vm._stack.push(nativeDelegate(vm._environment, &thisObj, args, nfe)); 1257 } 1258 final switch(nfe) 1259 { 1260 case NativeFunctionError.NO_ERROR: 1261 break; 1262 case NativeFunctionError.RETURN_VALUE_IS_EXCEPTION: 1263 return throwRuntimeError(vm._stack.peek().toString(), vm, chunk); 1264 case NativeFunctionError.WRONG_NUMBER_OF_ARGS: 1265 return throwRuntimeError("Wrong number of arguments to native function", vm, chunk); 1266 case NativeFunctionError.WRONG_TYPE_OF_ARG: 1267 return throwRuntimeError("Wrong type of argument to native function", vm, chunk); 1268 } 1269 vm._ip += 1 + uint.sizeof; 1270 return 0; 1271 }*/ 1272 1273 /// implements virtual machine 1274 class VirtualMachine 1275 { 1276 /// ctor 1277 this(Environment globalEnv) 1278 { 1279 _environment = globalEnv; 1280 _globals = globalEnv; 1281 _ops[] = &opNop; 1282 _ops[OpCode.CONST] = &opConst; 1283 _ops[OpCode.CONST_0] = &opConst0; 1284 _ops[OpCode.CONST_1] = &opConst1; 1285 _ops[OpCode.PUSH] = &opPush; 1286 _ops[OpCode.POP] = &opPop; 1287 _ops[OpCode.POPN] = &opPopN; 1288 _ops[OpCode.SET] = &opSet; 1289 _ops[OpCode.STACK] = &opStack; 1290 _ops[OpCode.STACK_1] = &opStack1; 1291 _ops[OpCode.ARRAY] = &opArray; 1292 _ops[OpCode.OBJECT] = &opObject; 1293 _ops[OpCode.CLASS] = &opClass; 1294 _ops[OpCode.REGEX] = &opRegex; 1295 _ops[OpCode.ITER] = &opIter; 1296 _ops[OpCode.DEL] = &opDel; 1297 _ops[OpCode.NEW] = &opNew; 1298 _ops[OpCode.THIS] = &opThis; 1299 _ops[OpCode.OPENSCOPE] = &opOpenScope; 1300 _ops[OpCode.CLOSESCOPE] = &opCloseScope; 1301 _ops[OpCode.DECLVAR] = &opDeclVar; 1302 _ops[OpCode.DECLLET] = &opDeclLet; 1303 _ops[OpCode.DECLCONST] = &opDeclConst; 1304 _ops[OpCode.GETVAR] = &opGetVar; 1305 _ops[OpCode.SETVAR] = &opSetVar; 1306 _ops[OpCode.OBJGET] = &opObjGet; 1307 _ops[OpCode.OBJSET] = &opObjSet; 1308 _ops[OpCode.CALL] = &opCall; 1309 _ops[OpCode.JMPFALSE] = &opJmpFalse; 1310 _ops[OpCode.JMP] = &opJmp; 1311 _ops[OpCode.SWITCH] = &opSwitch; 1312 _ops[OpCode.GOTO] = &opGoto; 1313 _ops[OpCode.THROW] = &opThrow; 1314 _ops[OpCode.RETHROW] = &opRethrow; 1315 _ops[OpCode.TRY] = &opTry; 1316 _ops[OpCode.ENDTRY] = &opEndTry; 1317 _ops[OpCode.LOADEXC] = &opLoadExc; 1318 _ops[OpCode.CONCAT] = &opConcat; 1319 _ops[OpCode.BITNOT] = &opBitNot; 1320 _ops[OpCode.NOT] = &opNot; 1321 _ops[OpCode.NEGATE] = &opNegate; 1322 _ops[OpCode.TYPEOF] = &opTypeof; 1323 _ops[OpCode.INSTANCEOF] = &opInstanceOf; 1324 _ops[OpCode.POW] = &opPow; 1325 _ops[OpCode.MUL] = &opMul; 1326 _ops[OpCode.DIV] = &opDiv; 1327 _ops[OpCode.MOD] = &opMod; 1328 _ops[OpCode.ADD] = &opAdd; 1329 _ops[OpCode.SUB] = &opSub; 1330 _ops[OpCode.BITRSH] = &opBitRSh; 1331 _ops[OpCode.BITURSH] = &opBitURSh; 1332 _ops[OpCode.BITLSH] = &opBitLSh; 1333 _ops[OpCode.LT] = &opLT; 1334 _ops[OpCode.LE] = &opLE; 1335 _ops[OpCode.GT] = &opGT; 1336 _ops[OpCode.GE] = &opGE; 1337 _ops[OpCode.EQUALS] = &opEQ; 1338 _ops[OpCode.NEQUALS] = &opNEQ; 1339 _ops[OpCode.STREQUALS] = &opStrictEquals; 1340 _ops[OpCode.BITAND] = &opBitAnd; 1341 _ops[OpCode.BITOR] = &opBitOr; 1342 _ops[OpCode.BITXOR] = &opBitXor; 1343 _ops[OpCode.AND] = &opAnd; 1344 _ops[OpCode.OR] = &opOr; 1345 _ops[OpCode.TERN] = &opTern; 1346 _ops[OpCode.RETURN] = &opReturn; 1347 _ops[OpCode.HALT] = &opHalt; 1348 _stack.reserve(256); 1349 } 1350 1351 /// print a chunk instruction by instruction, using the const table to indicate values 1352 void printChunk(Chunk chunk, bool printConstTable=false) 1353 { 1354 if(printConstTable) 1355 { 1356 writeln("===== CONST TABLE ====="); 1357 foreach(index, value ; chunk.constTable) 1358 { 1359 writef("#%s: ", index); 1360 if(value.type == ScriptAny.Type.FUNCTION) 1361 { 1362 auto fn = value.toValue!ScriptFunction; 1363 writeln("<function> " ~ fn.functionName); 1364 auto funcChunk = new Chunk(); 1365 funcChunk.constTable = chunk.constTable; 1366 funcChunk.bytecode = fn.compiled; 1367 printChunk(funcChunk, false); 1368 } 1369 else 1370 { 1371 write("<" ~ value.typeToString() ~ "> "); 1372 if(value.toString().length < 100) 1373 writeln(value.toString()); 1374 else 1375 writeln(); 1376 } 1377 } 1378 } 1379 if(printConstTable) 1380 writeln("===== DISASSEMBLY ====="); 1381 size_t ip = 0; 1382 while(ip < chunk.bytecode.length) 1383 { 1384 auto op = cast(OpCode)chunk.bytecode[ip]; 1385 printInstruction(ip, chunk); 1386 switch(op) 1387 { 1388 case OpCode.NOP: 1389 ++ip; 1390 break; 1391 case OpCode.CONST: 1392 ip += 1 + uint.sizeof; 1393 break; 1394 case OpCode.CONST_0: 1395 case OpCode.CONST_1: 1396 ++ip; 1397 break; 1398 case OpCode.PUSH: 1399 ip += 1 + int.sizeof; 1400 break; 1401 case OpCode.POP: 1402 ++ip; 1403 break; 1404 case OpCode.POPN: 1405 ip += 1 + uint.sizeof; 1406 break; 1407 case OpCode.SET: 1408 ip += 1 + uint.sizeof; 1409 break; 1410 case OpCode.STACK: 1411 ip += 1 + uint.sizeof; 1412 break; 1413 case OpCode.STACK_1: 1414 ++ip; 1415 break; 1416 case OpCode.ARRAY: 1417 ip += 1 + uint.sizeof; 1418 break; 1419 case OpCode.OBJECT: 1420 ip += 1 + uint.sizeof; 1421 break; 1422 case OpCode.CLASS: 1423 ip += 1 + 4 * ubyte.sizeof; 1424 break; 1425 case OpCode.ITER: 1426 case OpCode.DEL: 1427 ++ip; 1428 break; 1429 case OpCode.NEW: 1430 ip += 1 + uint.sizeof; 1431 break; 1432 case OpCode.THIS: 1433 ++ip; 1434 break; 1435 case OpCode.OPENSCOPE: 1436 ++ip; 1437 break; 1438 case OpCode.CLOSESCOPE: 1439 ++ip; 1440 break; 1441 case OpCode.DECLVAR: 1442 case OpCode.DECLLET: 1443 case OpCode.DECLCONST: 1444 ip += 1 + uint.sizeof; 1445 break; 1446 case OpCode.GETVAR: 1447 case OpCode.SETVAR: 1448 ip += 1 + uint.sizeof; 1449 break; 1450 case OpCode.OBJGET: 1451 case OpCode.OBJSET: 1452 ++ip; 1453 break; 1454 case OpCode.CALL: 1455 ip += 1 + uint.sizeof; 1456 break; 1457 case OpCode.JMPFALSE: 1458 case OpCode.JMP: 1459 ip += 1 + int.sizeof; 1460 break; 1461 case OpCode.SWITCH: 1462 ip += 1 + uint.sizeof; 1463 break; 1464 case OpCode.GOTO: 1465 ip += 1 + uint.sizeof + ubyte.sizeof; 1466 break; 1467 case OpCode.THROW: 1468 case OpCode.RETHROW: 1469 ++ip; 1470 break; 1471 case OpCode.TRY: 1472 ip += 1 + uint.sizeof; 1473 break; 1474 case OpCode.ENDTRY: 1475 case OpCode.LOADEXC: 1476 ++ip; 1477 break; 1478 case OpCode.CONCAT: 1479 ip += 1 + uint.sizeof; 1480 break; 1481 case OpCode.BITNOT: 1482 case OpCode.NOT: 1483 case OpCode.NEGATE: 1484 case OpCode.TYPEOF: 1485 case OpCode.INSTANCEOF: 1486 case OpCode.POW: 1487 case OpCode.MUL: 1488 case OpCode.DIV: 1489 case OpCode.MOD: 1490 case OpCode.ADD: 1491 case OpCode.SUB: 1492 case OpCode.LT: 1493 case OpCode.LE: 1494 case OpCode.GT: 1495 case OpCode.GE: 1496 case OpCode.EQUALS: 1497 case OpCode.NEQUALS: 1498 case OpCode.STREQUALS: 1499 case OpCode.BITAND: 1500 case OpCode.BITOR: 1501 case OpCode.BITXOR: 1502 case OpCode.AND: 1503 case OpCode.OR: 1504 case OpCode.TERN: 1505 ++ip; 1506 break; 1507 case OpCode.RETURN: 1508 case OpCode.HALT: 1509 ++ip; 1510 break; 1511 default: 1512 ++ip; 1513 } 1514 } 1515 writeln("======================="); 1516 } 1517 1518 /// prints an individual instruction without moving the ip 1519 void printInstruction(in size_t ip, Chunk chunk) 1520 { 1521 auto op = cast(OpCode)chunk.bytecode[ip]; 1522 switch(op) 1523 { 1524 case OpCode.NOP: 1525 writefln("%05d: %s", ip, op.opCodeToString); 1526 break; 1527 case OpCode.CONST: { 1528 immutable constID = decode!uint(chunk.bytecode[ip + 1..$]); 1529 printInstructionWithConstID(ip, op, constID, chunk); 1530 break; 1531 } 1532 case OpCode.CONST_0: 1533 case OpCode.CONST_1: 1534 writefln("%05d: %s", ip, op.opCodeToString); 1535 break; 1536 case OpCode.PUSH: { 1537 immutable index = decode!int(chunk.bytecode[ip + 1..$]); 1538 writefln("%05d: %s index=%s", ip, op.opCodeToString, index); 1539 break; 1540 } 1541 case OpCode.POP: 1542 writefln("%05d: %s", ip, op.opCodeToString); 1543 break; 1544 case OpCode.POPN: { 1545 immutable amount = decode!uint(chunk.bytecode[ip + 1..$]); 1546 writefln("%05d: %s amount=%s", ip, op.opCodeToString, amount); 1547 break; 1548 } 1549 case OpCode.SET: { 1550 immutable index = decode!uint(chunk.bytecode[ip + 1..$]); 1551 writefln("%05d: %s index=%s", ip, op.opCodeToString, index); 1552 break; 1553 } 1554 case OpCode.STACK: { 1555 immutable n = decode!uint(chunk.bytecode[ip + 1..$]); 1556 writefln("%05d: %s n=%s", ip, op.opCodeToString, n); 1557 break; 1558 } 1559 case OpCode.STACK_1: 1560 writefln("%05d: %s", ip, op.opCodeToString); 1561 break; 1562 case OpCode.ARRAY: { 1563 immutable n = decode!uint(chunk.bytecode[ip + 1..$]); 1564 writefln("%05d: %s n=%s", ip, op.opCodeToString, n); 1565 break; 1566 } 1567 case OpCode.OBJECT: { 1568 immutable n = decode!uint(chunk.bytecode[ip + 1..$]); 1569 writefln("%05d: %s n=%s", ip, op.opCodeToString, n); 1570 break; 1571 } 1572 case OpCode.CLASS: { 1573 immutable numMethods = decode!ubyte(chunk.bytecode[ip + 1..$]); 1574 immutable numGetters = decode!ubyte(chunk.bytecode[ip + 2..$]); 1575 immutable numSetters = decode!ubyte(chunk.bytecode[ip + 3..$]); 1576 immutable numStatics = decode!ubyte(chunk.bytecode[ip + 4..$]); 1577 writefln("%05d: %s %s,%s,%s,%s", ip, op.opCodeToString, numMethods, numGetters, numSetters, numStatics); 1578 break; 1579 } 1580 case OpCode.ITER: 1581 case OpCode.DEL: 1582 writefln("%05d: %s", ip, op.opCodeToString); 1583 break; 1584 case OpCode.NEW: { 1585 immutable args = decode!uint(chunk.bytecode[ip + 1..$]); 1586 writefln("%05d: %s args=%s", ip, op.opCodeToString, args); 1587 break; 1588 } 1589 case OpCode.THIS: 1590 writefln("%05d: %s", ip, op.opCodeToString); 1591 break; 1592 case OpCode.OPENSCOPE: 1593 case OpCode.CLOSESCOPE: 1594 writefln("%05d: %s", ip, op.opCodeToString); 1595 break; 1596 case OpCode.DECLVAR: 1597 case OpCode.DECLLET: 1598 case OpCode.DECLCONST: { 1599 immutable constID = decode!uint(chunk.bytecode[ip + 1..$]); 1600 printInstructionWithConstID(ip, op, constID, chunk); 1601 break; 1602 } 1603 case OpCode.GETVAR: 1604 case OpCode.SETVAR: { 1605 immutable constID = decode!uint(chunk.bytecode[ip + 1..$]); 1606 printInstructionWithConstID(ip, op, constID, chunk); 1607 break; 1608 } 1609 case OpCode.OBJSET: 1610 case OpCode.OBJGET: 1611 writefln("%05d: %s", ip, op.opCodeToString); 1612 break; 1613 case OpCode.CALL: { 1614 immutable args = decode!uint(chunk.bytecode[ip + 1..$]); 1615 writefln("%05d: %s args=%s", ip, op.opCodeToString, args); 1616 break; 1617 } 1618 case OpCode.JMPFALSE: 1619 case OpCode.JMP: { 1620 immutable jump = decode!int(chunk.bytecode[ip + 1..$]); 1621 writefln("%05d: %s jump=%s", ip, op.opCodeToString, jump); 1622 break; 1623 } 1624 case OpCode.SWITCH: { 1625 immutable def = decode!uint(chunk.bytecode[ip + 1..$]); 1626 writefln("%05d: %s default=%s", ip, op.opCodeToString, def); 1627 break; 1628 } 1629 case OpCode.GOTO: { 1630 immutable instruction = decode!uint(chunk.bytecode[ip + 1..$]); 1631 immutable depth = decode!ubyte(chunk.bytecode[ip + 1 + uint.sizeof..$]); 1632 writefln("%05d: %s instruction=%s, depth=%s", ip, op.opCodeToString, instruction, depth); 1633 break; 1634 } 1635 case OpCode.THROW: 1636 case OpCode.RETHROW: 1637 writefln("%05d: %s", ip, op.opCodeToString); 1638 break; 1639 case OpCode.TRY: { 1640 immutable catchGoto = decode!uint(chunk.bytecode[ip + 1..$]); 1641 writefln("%05d: %s catch=%s", ip, op.opCodeToString, catchGoto); 1642 break; 1643 } 1644 case OpCode.ENDTRY: 1645 case OpCode.LOADEXC: 1646 writefln("%05d: %s", ip, op.opCodeToString); 1647 break; 1648 case OpCode.CONCAT: { 1649 immutable n = decode!uint(chunk.bytecode[ip + 1..$]); 1650 writefln("%05d: %s n=%s", ip, op.opCodeToString, n); 1651 break; 1652 } 1653 case OpCode.BITNOT: 1654 case OpCode.NOT: 1655 case OpCode.NEGATE: 1656 case OpCode.TYPEOF: 1657 case OpCode.INSTANCEOF: 1658 case OpCode.POW: 1659 case OpCode.MUL: 1660 case OpCode.DIV: 1661 case OpCode.MOD: 1662 case OpCode.ADD: 1663 case OpCode.SUB: 1664 case OpCode.LT: 1665 case OpCode.LE: 1666 case OpCode.GT: 1667 case OpCode.GE: 1668 case OpCode.EQUALS: 1669 case OpCode.NEQUALS: 1670 case OpCode.STREQUALS: 1671 case OpCode.BITAND: 1672 case OpCode.BITOR: 1673 case OpCode.BITXOR: 1674 case OpCode.AND: 1675 case OpCode.OR: 1676 case OpCode.TERN: 1677 writefln("%05d: %s", ip, op.opCodeToString); 1678 break; 1679 case OpCode.RETURN: 1680 case OpCode.HALT: 1681 writefln("%05d: %s", ip, op.opCodeToString); 1682 break; 1683 default: 1684 writefln("%05d: ??? (%s)", ip, cast(ubyte)op); 1685 } 1686 } 1687 1688 /// print the current contents of the stack 1689 void printStack() 1690 { 1691 write("Stack: ["); 1692 for(size_t i = 0; i < _stack.size; ++i) 1693 { 1694 if(_stack.array[i].type == ScriptAny.Type.STRING) 1695 { 1696 auto str = _stack.array[i].toString(); 1697 if(str.length < 100) 1698 write("\"" ~ str ~ "\""); 1699 else 1700 write("[string too long to display]"); 1701 } 1702 else if(_stack.array[i].type == ScriptAny.Type.ARRAY) 1703 { 1704 immutable arrLen = _stack.array[i].toValue!(ScriptAny[]).length; 1705 if(arrLen < 100) 1706 write(_stack.array[i].toString()); 1707 else 1708 write("[array too long to display]"); 1709 } 1710 else 1711 { 1712 write(_stack.array[i].toString()); 1713 } 1714 if(i < _stack.size - 1) 1715 write(", "); 1716 } 1717 writeln("]"); 1718 } 1719 1720 /// run a chunk of bytecode with a given const table 1721 ScriptAny run(Chunk chunk, bool printDebugInfo=false, bool retCS=false) 1722 { 1723 _ip = 0; 1724 ubyte op; 1725 _stopped = false; 1726 _exc = null; 1727 _currentConstTable = chunk.constTable; 1728 int retCSCallDepth = 1; 1729 while(_ip < chunk.bytecode.length && !_stopped) 1730 { 1731 op = chunk.bytecode[_ip]; 1732 // handle runFunction 1733 if(retCS) 1734 { 1735 if(op == OpCode.RETURN) 1736 { 1737 --retCSCallDepth; 1738 if(retCSCallDepth <= 0) 1739 return _stack.pop(); 1740 } 1741 else if(op == OpCode.CALL || op == OpCode.NEW) 1742 ++retCSCallDepth; 1743 } 1744 if(printDebugInfo) 1745 printInstruction(_ip, chunk); 1746 _ops[op](this, chunk); 1747 if(printDebugInfo) 1748 printStack(); 1749 } 1750 // if something is on the stack, that's the return value 1751 if(_stack.size > 0) 1752 return _stack.pop(); 1753 _currentConstTable = null; 1754 return ScriptAny.UNDEFINED; 1755 } 1756 1757 /// For calling script functions with call or apply. Ugly hackish function that needs 1758 /// to be reworked. 1759 package(mildew) ScriptAny runFunction(ScriptFunction func, ScriptAny thisObj, ScriptAny[] args, 1760 ScriptAny yieldFunc=ScriptAny.UNDEFINED) // this parameter is necessary 1761 { 1762 ScriptAny result; 1763 NativeFunctionError nfe; 1764 if(func.type == ScriptFunction.Type.SCRIPT_FUNCTION) 1765 { 1766 auto chunk = new Chunk(); 1767 chunk.constTable = _currentConstTable; 1768 chunk.bytecode = func.compiled; 1769 auto oldTryData = _tryData; // @suppress(dscanner.suspicious.unmodified) 1770 _tryData = []; 1771 immutable oldIP = _ip; 1772 auto oldEnv = _environment; // @suppress(dscanner.suspicious.unmodified) 1773 _environment = new Environment(func.closure); 1774 // THIS MAY NOT BE TEMPORARY 1775 if(yieldFunc != ScriptAny.UNDEFINED) 1776 { 1777 _environment.forceSetVarOrConst("yield", yieldFunc, true); 1778 // allow for __yield__ syntax inside of manually created Generators 1779 _environment.forceSetVarOrConst("__yield__", yieldFunc, true); 1780 } 1781 // ^ 1782 _environment.forceSetVarOrConst("this", thisObj, false); 1783 _environment.forceSetVarOrConst("arguments", ScriptAny(args), false); 1784 for(size_t i = 0; i < func.argNames.length; ++i) 1785 { 1786 if(i >= args.length) 1787 _environment.forceSetVarOrConst(func.argNames[i], ScriptAny.UNDEFINED, false); 1788 else 1789 _environment.forceSetVarOrConst(func.argNames[i], args[i], false); 1790 } 1791 try 1792 { 1793 result = run(chunk, _environment.getGlobalEnvironment.interpreter.printVMDebugInfo, true); 1794 1795 } 1796 catch(ScriptRuntimeException ex) 1797 { 1798 _exc = ex; 1799 } 1800 finally 1801 { 1802 _environment = oldEnv; 1803 _ip = oldIP; 1804 _tryData = oldTryData; 1805 } 1806 return result; 1807 } 1808 else if(func.type == ScriptFunction.Type.NATIVE_FUNCTION) 1809 { 1810 auto nf = func.nativeFunction; 1811 result = nf(_environment, &thisObj, args, nfe); 1812 } 1813 else if(func.type == ScriptFunction.Type.NATIVE_DELEGATE) 1814 { 1815 auto nd = func.nativeDelegate; 1816 result = nd(_environment, &thisObj, args, nfe); 1817 } 1818 final switch(nfe) 1819 { 1820 case NativeFunctionError.NO_ERROR: 1821 break; 1822 case NativeFunctionError.RETURN_VALUE_IS_EXCEPTION: 1823 _exc = new ScriptRuntimeException(result.toString); 1824 break; 1825 case NativeFunctionError.WRONG_NUMBER_OF_ARGS: 1826 _exc = new ScriptRuntimeException("Wrong number of argumentss to native function"); 1827 break; 1828 case NativeFunctionError.WRONG_TYPE_OF_ARG: 1829 _exc = new ScriptRuntimeException("Wrong type of arguments to native function"); 1830 break; 1831 } 1832 return result; 1833 } 1834 1835 /** 1836 * For coroutines and threads. 1837 */ 1838 VirtualMachine copy(bool copyStack = false) 1839 { 1840 auto vm = new VirtualMachine(_globals); 1841 vm._environment = _environment; 1842 vm._currentConstTable = _currentConstTable; 1843 vm._ops = _ops; 1844 if(copyStack) 1845 vm._stack = _stack; 1846 vm._parent = this; 1847 return vm; 1848 } 1849 1850 private: 1851 1852 enum FuncCallType { NORMAL, NEW } 1853 struct CallData 1854 { 1855 FuncCallType fct; 1856 ubyte[] bc; 1857 size_t ip; 1858 Environment env; 1859 TryData[] tryData; 1860 } 1861 1862 struct TryData 1863 { 1864 int depth; 1865 size_t stackSize; 1866 uint catchGoto; 1867 } 1868 1869 void printInstructionWithConstID(size_t ip, OpCode op, uint constID, Chunk chunk) 1870 { 1871 if(chunk.constTable.get(constID).toString().length < 100) 1872 writefln("%05d: %s #%s // <%s> %s", 1873 ip, op.opCodeToString, constID, chunk.constTable.get(constID).typeToString(), 1874 chunk.constTable.get(constID)); 1875 else 1876 writefln("%05d: %s #%s // <%s>", 1877 ip, op.opCodeToString, constID, chunk.constTable.get(constID).typeToString()); 1878 } 1879 1880 Stack!CallData _callStack; 1881 Environment _environment; 1882 ConstTable _currentConstTable; // for running functions from call and apply 1883 ScriptRuntimeException _exc; // exception flag 1884 Environment _globals; 1885 size_t _ip; 1886 OpCodeFunction[ubyte.max + 1] _ops; 1887 Stack!ScriptAny _stack; 1888 TryData[] _tryData; 1889 VirtualMachine _parent = null; // the VM that spawned this VM 1890 1891 /// stops the machine 1892 bool _stopped; 1893 } 1894 1895 class VMException : Exception 1896 { 1897 this(string msg, size_t iptr, OpCode op, string file = __FILE__, size_t line = __LINE__) 1898 { 1899 super(msg, file, line); 1900 ip = iptr; 1901 opcode = op; 1902 } 1903 1904 override string toString() const 1905 { 1906 import std.format: format; 1907 return msg ~ " at instruction " ~ format("%x", ip) ~ " (" ~ opcode.opCodeToString ~ ")"; 1908 } 1909 1910 size_t ip; 1911 OpCode opcode; 1912 } 1913 1914 unittest 1915 { 1916 auto vm = new VirtualMachine(new Environment(null, "<global>")); 1917 vm = vm.copy(); 1918 auto chunk = new Chunk(); 1919 1920 ubyte[] getConst(T)(T value) 1921 { 1922 return encode(chunk.constTable.addValueUint(ScriptAny(value))); 1923 } 1924 1925 ScriptAny native_tpropGet(Environment env, ScriptAny* thisObj, ScriptAny[] args, ref NativeFunctionError nfe) 1926 { 1927 writeln("in native_tpropGet"); 1928 return ScriptAny(1000); 1929 } 1930 1931 ScriptAny native_tpropSet(Environment env, ScriptAny* thisObj, ScriptAny[] args, ref NativeFunctionError nfe) 1932 { 1933 writeln("Setting value to " ~ args[0].toString); 1934 return ScriptAny.UNDEFINED; 1935 } 1936 1937 /* 1938 1939 */ 1940 auto testObj = new ScriptObject("test", null, null); 1941 testObj.addGetterProperty("tprop", new ScriptFunction("tpropGet", &native_tpropGet)); 1942 testObj.addSetterProperty("tprop", new ScriptFunction("tpropSet", &native_tpropSet)); 1943 1944 chunk.bytecode ~= OpCode.CONST ~ getConst(testObj); 1945 chunk.bytecode ~= OpCode.CONST ~ getConst("tprop"); 1946 chunk.bytecode ~= OpCode.OBJGET; 1947 chunk.bytecode ~= OpCode.CONST ~ getConst(testObj); 1948 chunk.bytecode ~= OpCode.CONST ~ getConst("tprop"); 1949 chunk.bytecode ~= OpCode.CONST_1; 1950 chunk.bytecode ~= OpCode.OBJSET; 1951 1952 vm.printChunk(chunk); 1953 vm.run(chunk); 1954 }