mirror of
https://github.com/captbaritone/webamp.git
synced 2026-07-21 00:59:29 +00:00
Make interpreter a class
This will make it easier to build a debugger.
This commit is contained in:
parent
40fb24903b
commit
89cd90a2fa
1 changed files with 324 additions and 311 deletions
|
|
@ -1,6 +1,330 @@
|
|||
import Variable from "./variable";
|
||||
import { isPromise, unimplementedWarning } from "../utils";
|
||||
|
||||
export function interpret(start, program) {
|
||||
const interpreter = new Interpreter(program);
|
||||
return interpreter.interpret(start);
|
||||
}
|
||||
|
||||
class Interpreter {
|
||||
constructor(program) {
|
||||
const { commands, methods, variables, classes } = program;
|
||||
this.commands = commands;
|
||||
this.methods = methods;
|
||||
this.variables = variables;
|
||||
this.classes = classes;
|
||||
|
||||
this.stack = [];
|
||||
this.callStack = [];
|
||||
}
|
||||
|
||||
interpret(start) {
|
||||
// Instruction Pointer
|
||||
let ip = start;
|
||||
while (ip < this.commands.length) {
|
||||
const command = this.commands[ip];
|
||||
|
||||
switch (command.opcode) {
|
||||
// push
|
||||
case 1: {
|
||||
const offsetIntoVariables = command.arg;
|
||||
this.stack.push(this.variables[offsetIntoVariables]);
|
||||
break;
|
||||
}
|
||||
// pop
|
||||
case 2: {
|
||||
this.stack.pop();
|
||||
break;
|
||||
}
|
||||
// popTo
|
||||
case 3: {
|
||||
const aValue = this.popStackValue();
|
||||
const offsetIntoVariables = command.arg;
|
||||
const toVar = this.variables[offsetIntoVariables];
|
||||
toVar.setValue(aValue);
|
||||
break;
|
||||
}
|
||||
// ==
|
||||
case 8: {
|
||||
this.twoArgCoercingOperator((b, a) => b === a);
|
||||
break;
|
||||
}
|
||||
// !=
|
||||
case 9: {
|
||||
this.twoArgCoercingOperator((b, a) => b !== a);
|
||||
break;
|
||||
}
|
||||
// >
|
||||
case 10: {
|
||||
this.twoArgCoercingOperator((b, a) => b > a);
|
||||
break;
|
||||
}
|
||||
// >=
|
||||
case 11: {
|
||||
this.twoArgCoercingOperator((b, a) => b >= a);
|
||||
break;
|
||||
}
|
||||
// <
|
||||
case 12: {
|
||||
this.twoArgCoercingOperator((b, a) => b < a);
|
||||
break;
|
||||
}
|
||||
// <=
|
||||
case 13: {
|
||||
this.twoArgCoercingOperator((b, a) => b <= a);
|
||||
break;
|
||||
}
|
||||
// jumpIf
|
||||
case 16: {
|
||||
const value = this.popStackValue();
|
||||
// This seems backwards. Seems like we're doing a "jump if not"
|
||||
if (value) {
|
||||
break;
|
||||
}
|
||||
ip = command.arg - 1;
|
||||
break;
|
||||
}
|
||||
// jumpIfNot
|
||||
case 17: {
|
||||
const value = this.popStackValue();
|
||||
// This seems backwards. Same as above
|
||||
if (!value) {
|
||||
break;
|
||||
}
|
||||
ip = command.arg - 1;
|
||||
break;
|
||||
}
|
||||
// jump
|
||||
case 18: {
|
||||
ip = command.arg - 1;
|
||||
break;
|
||||
}
|
||||
// call
|
||||
// strangeCall (seems to behave just like regular call)
|
||||
case 24:
|
||||
case 112: {
|
||||
const methodOffset = command.arg;
|
||||
const method = this.methods[methodOffset];
|
||||
let methodName = method.name;
|
||||
const classesOffset = method.typeOffset;
|
||||
methodName = methodName.toLowerCase();
|
||||
|
||||
const klass = this.classes[classesOffset];
|
||||
if (!klass) {
|
||||
throw new Error("Need to add a missing class to runtime");
|
||||
}
|
||||
// This is a bit awkward. Because the variables are stored on the stack
|
||||
// before the object, we have to find the number of arguments without
|
||||
// actually having access to the object instance.
|
||||
if (!klass.prototype[methodName]) {
|
||||
throw new Error(
|
||||
`Need to add missing function (${methodName}) to ${klass.name}`
|
||||
);
|
||||
}
|
||||
let argCount = klass.prototype[methodName].length;
|
||||
|
||||
const methodArgs = [];
|
||||
while (argCount--) {
|
||||
const aValue = this.popStackValue();
|
||||
methodArgs.push(aValue);
|
||||
}
|
||||
const obj = this.popStackValue();
|
||||
let value = obj[methodName](...methodArgs);
|
||||
if (isPromise(value)) {
|
||||
throw new Error("Did not expect maki method to return promise");
|
||||
}
|
||||
if (value === null) {
|
||||
// variables[1] holds global NULL value
|
||||
value = this.variables[1];
|
||||
}
|
||||
this.stack.push(value);
|
||||
break;
|
||||
}
|
||||
// callGlobal
|
||||
case 25: {
|
||||
this.callStack.push(ip);
|
||||
const offset = command.arg;
|
||||
|
||||
ip = offset - 1; // -1 because we ++ after the switch
|
||||
break;
|
||||
}
|
||||
// return
|
||||
case 33: {
|
||||
ip = this.callStack.pop();
|
||||
// TODO: Stack protection?
|
||||
break;
|
||||
}
|
||||
// complete
|
||||
case 40: {
|
||||
// noop for now
|
||||
unimplementedWarning("OPCODE: complete");
|
||||
break;
|
||||
}
|
||||
// mov
|
||||
case 48: {
|
||||
const a = this.stack.pop();
|
||||
const b = this.stack.pop();
|
||||
let aValue = a instanceof Variable ? a.getValue() : a;
|
||||
if (b.type === "INT") {
|
||||
aValue = Math.floor(aValue);
|
||||
}
|
||||
b.setValue(aValue);
|
||||
this.stack.push(aValue);
|
||||
break;
|
||||
}
|
||||
// postinc
|
||||
case 56: {
|
||||
const a = this.stack.pop();
|
||||
const aValue = a.getValue();
|
||||
a.setValue(aValue + 1);
|
||||
this.stack.push(aValue);
|
||||
break;
|
||||
}
|
||||
// postdec
|
||||
case 57: {
|
||||
const a = this.stack.pop();
|
||||
const aValue = a.getValue();
|
||||
a.setValue(aValue - 1);
|
||||
this.stack.push(aValue);
|
||||
break;
|
||||
}
|
||||
// preinc
|
||||
case 58: {
|
||||
const a = this.stack.pop();
|
||||
const aValue = a.getValue() + 1;
|
||||
a.setValue(aValue);
|
||||
this.stack.push(aValue);
|
||||
break;
|
||||
}
|
||||
// predec
|
||||
case 59: {
|
||||
const a = this.stack.pop();
|
||||
const aValue = a.getValue() - 1;
|
||||
a.setValue(aValue);
|
||||
this.stack.push(aValue);
|
||||
break;
|
||||
}
|
||||
// + (add)
|
||||
case 64: {
|
||||
this.twoArgOperator((b, a) => b + a);
|
||||
break;
|
||||
}
|
||||
// - (subtract)
|
||||
case 65: {
|
||||
this.twoArgOperator((b, a) => b - a);
|
||||
break;
|
||||
}
|
||||
// * (multiply)
|
||||
case 66: {
|
||||
this.twoArgOperator((b, a) => b * a);
|
||||
break;
|
||||
}
|
||||
// / (divide)
|
||||
case 67: {
|
||||
this.twoArgOperator((b, a) => b / a);
|
||||
break;
|
||||
}
|
||||
// % (mod)
|
||||
case 68: {
|
||||
const a = this.stack.pop();
|
||||
const b = this.stack.pop();
|
||||
const aValue = a instanceof Variable ? a.getValue() : a;
|
||||
let bValue = b instanceof Variable ? b.getValue() : b;
|
||||
// Need to coerce LHS if not int, RHS is always int (enforced by compiler)
|
||||
if (b.type === "FLOAT" || b.type === "DOUBLE") {
|
||||
bValue = Math.floor(bValue);
|
||||
}
|
||||
this.stack.push(bValue % aValue);
|
||||
break;
|
||||
}
|
||||
// & (binary and)
|
||||
case 72: {
|
||||
this.twoArgOperator((b, a) => b & a);
|
||||
break;
|
||||
}
|
||||
// | (binary or)
|
||||
case 73: {
|
||||
this.twoArgOperator((b, a) => b | a);
|
||||
break;
|
||||
}
|
||||
// ! (not)
|
||||
case 74: {
|
||||
const aValue = this.popStackValue();
|
||||
this.stack.push(aValue ? 0 : 1);
|
||||
break;
|
||||
}
|
||||
// - (negative)
|
||||
case 76: {
|
||||
const aValue = this.popStackValue();
|
||||
this.stack.push(-aValue);
|
||||
break;
|
||||
}
|
||||
// logAnd (&&)
|
||||
case 80: {
|
||||
this.twoArgOperator((b, a) => b && a);
|
||||
break;
|
||||
}
|
||||
// logOr ||
|
||||
case 81: {
|
||||
this.twoArgOperator((b, a) => b || a);
|
||||
break;
|
||||
}
|
||||
// <<
|
||||
case 88: {
|
||||
this.twoArgOperator((b, a) => b << a);
|
||||
break;
|
||||
}
|
||||
// >>
|
||||
case 89: {
|
||||
this.twoArgOperator((b, a) => b >> a);
|
||||
break;
|
||||
}
|
||||
// new
|
||||
case 96: {
|
||||
const classesOffset = command.arg;
|
||||
const Klass = this.classes[classesOffset];
|
||||
const system = this.variables[0].getValue();
|
||||
const klassInst = new Klass(null, system.getscriptgroup());
|
||||
this.stack.push(klassInst);
|
||||
break;
|
||||
}
|
||||
// delete
|
||||
case 97: {
|
||||
const aValue = this.popStackValue();
|
||||
aValue.js_delete();
|
||||
break;
|
||||
}
|
||||
default:
|
||||
throw new Error(`Unhandled opcode ${command.opcode}`);
|
||||
}
|
||||
|
||||
ip++;
|
||||
}
|
||||
}
|
||||
|
||||
popStackValue() {
|
||||
const v = this.stack.pop();
|
||||
return v instanceof Variable ? v.getValue() : v;
|
||||
}
|
||||
|
||||
twoArgCoercingOperator(operator) {
|
||||
const a = this.stack.pop();
|
||||
const b = this.stack.pop();
|
||||
let aValue = a instanceof Variable ? a.getValue() : a;
|
||||
const bValue = b instanceof Variable ? b.getValue() : b;
|
||||
|
||||
aValue = coerceTypes(a, b, aValue, bValue);
|
||||
this.stack.push(operator(bValue, aValue));
|
||||
}
|
||||
|
||||
twoArgOperator(operator) {
|
||||
const aValue = this.popStackValue();
|
||||
const bValue = this.popStackValue();
|
||||
|
||||
this.stack.push(operator(bValue, aValue));
|
||||
}
|
||||
}
|
||||
|
||||
function coerceTypes(var1, var2, val1 /* val2 */) {
|
||||
if (var2.type === "INT") {
|
||||
if (var1.type === "FLOAT" || var1.type === "DOUBLE") {
|
||||
|
|
@ -10,314 +334,3 @@ function coerceTypes(var1, var2, val1 /* val2 */) {
|
|||
|
||||
return val1;
|
||||
}
|
||||
|
||||
export function interpret(start, program) {
|
||||
const { commands, methods, variables, classes } = program;
|
||||
|
||||
const stack = [];
|
||||
const callStack = [];
|
||||
|
||||
function popStackValue() {
|
||||
const v = stack.pop();
|
||||
return v instanceof Variable ? v.getValue() : v;
|
||||
}
|
||||
|
||||
function twoArgCoercingOperator(operator) {
|
||||
const a = stack.pop();
|
||||
const b = stack.pop();
|
||||
let aValue = a instanceof Variable ? a.getValue() : a;
|
||||
const bValue = b instanceof Variable ? b.getValue() : b;
|
||||
|
||||
aValue = coerceTypes(a, b, aValue, bValue);
|
||||
stack.push(operator(bValue, aValue));
|
||||
}
|
||||
|
||||
function twoArgOperator(operator) {
|
||||
const aValue = popStackValue();
|
||||
const bValue = popStackValue();
|
||||
|
||||
stack.push(operator(bValue, aValue));
|
||||
}
|
||||
|
||||
// Instruction Pointer
|
||||
let ip = start;
|
||||
while (ip < commands.length) {
|
||||
const command = commands[ip];
|
||||
|
||||
switch (command.opcode) {
|
||||
// push
|
||||
case 1: {
|
||||
const offsetIntoVariables = command.arg;
|
||||
stack.push(variables[offsetIntoVariables]);
|
||||
break;
|
||||
}
|
||||
// pop
|
||||
case 2: {
|
||||
stack.pop();
|
||||
break;
|
||||
}
|
||||
// popTo
|
||||
case 3: {
|
||||
const aValue = popStackValue();
|
||||
const offsetIntoVariables = command.arg;
|
||||
const toVar = variables[offsetIntoVariables];
|
||||
toVar.setValue(aValue);
|
||||
break;
|
||||
}
|
||||
// ==
|
||||
case 8: {
|
||||
twoArgCoercingOperator((b, a) => b === a);
|
||||
break;
|
||||
}
|
||||
// !=
|
||||
case 9: {
|
||||
twoArgCoercingOperator((b, a) => b !== a);
|
||||
break;
|
||||
}
|
||||
// >
|
||||
case 10: {
|
||||
twoArgCoercingOperator((b, a) => b > a);
|
||||
break;
|
||||
}
|
||||
// >=
|
||||
case 11: {
|
||||
twoArgCoercingOperator((b, a) => b >= a);
|
||||
break;
|
||||
}
|
||||
// <
|
||||
case 12: {
|
||||
twoArgCoercingOperator((b, a) => b < a);
|
||||
break;
|
||||
}
|
||||
// <=
|
||||
case 13: {
|
||||
twoArgCoercingOperator((b, a) => b <= a);
|
||||
break;
|
||||
}
|
||||
// jumpIf
|
||||
case 16: {
|
||||
const value = popStackValue();
|
||||
// This seems backwards. Seems like we're doing a "jump if not"
|
||||
if (value) {
|
||||
break;
|
||||
}
|
||||
ip = command.arg - 1;
|
||||
break;
|
||||
}
|
||||
// jumpIfNot
|
||||
case 17: {
|
||||
const value = popStackValue();
|
||||
// This seems backwards. Same as above
|
||||
if (!value) {
|
||||
break;
|
||||
}
|
||||
ip = command.arg - 1;
|
||||
break;
|
||||
}
|
||||
// jump
|
||||
case 18: {
|
||||
ip = command.arg - 1;
|
||||
break;
|
||||
}
|
||||
// call
|
||||
// strangeCall (seems to behave just like regular call)
|
||||
case 24:
|
||||
case 112: {
|
||||
const methodOffset = command.arg;
|
||||
const method = methods[methodOffset];
|
||||
let methodName = method.name;
|
||||
const classesOffset = method.typeOffset;
|
||||
methodName = methodName.toLowerCase();
|
||||
|
||||
const klass = classes[classesOffset];
|
||||
if (!klass) {
|
||||
throw new Error("Need to add a missing class to runtime");
|
||||
}
|
||||
// This is a bit awkward. Because the variables are stored on the stack
|
||||
// before the object, we have to find the number of arguments without
|
||||
// actually having access to the object instance.
|
||||
if (!klass.prototype[methodName]) {
|
||||
throw new Error(
|
||||
`Need to add missing function (${methodName}) to ${klass.name}`
|
||||
);
|
||||
}
|
||||
let argCount = klass.prototype[methodName].length;
|
||||
|
||||
const methodArgs = [];
|
||||
while (argCount--) {
|
||||
const aValue = popStackValue();
|
||||
methodArgs.push(aValue);
|
||||
}
|
||||
const obj = popStackValue();
|
||||
let value = obj[methodName](...methodArgs);
|
||||
if (isPromise(value)) {
|
||||
throw new Error("Did not expect maki method to return promise");
|
||||
}
|
||||
if (value === null) {
|
||||
// variables[1] holds global NULL value
|
||||
value = variables[1];
|
||||
}
|
||||
stack.push(value);
|
||||
break;
|
||||
}
|
||||
// callGlobal
|
||||
case 25: {
|
||||
callStack.push(ip);
|
||||
const offset = command.arg;
|
||||
|
||||
ip = offset - 1; // -1 because we ++ after the switch
|
||||
break;
|
||||
}
|
||||
// return
|
||||
case 33: {
|
||||
ip = callStack.pop();
|
||||
// TODO: Stack protection?
|
||||
break;
|
||||
}
|
||||
// complete
|
||||
case 40: {
|
||||
// noop for now
|
||||
unimplementedWarning("OPCODE: complete");
|
||||
break;
|
||||
}
|
||||
// mov
|
||||
case 48: {
|
||||
const a = stack.pop();
|
||||
const b = stack.pop();
|
||||
let aValue = a instanceof Variable ? a.getValue() : a;
|
||||
if (b.type === "INT") {
|
||||
aValue = Math.floor(aValue);
|
||||
}
|
||||
b.setValue(aValue);
|
||||
stack.push(aValue);
|
||||
break;
|
||||
}
|
||||
// postinc
|
||||
case 56: {
|
||||
const a = stack.pop();
|
||||
const aValue = a.getValue();
|
||||
a.setValue(aValue + 1);
|
||||
stack.push(aValue);
|
||||
break;
|
||||
}
|
||||
// postdec
|
||||
case 57: {
|
||||
const a = stack.pop();
|
||||
const aValue = a.getValue();
|
||||
a.setValue(aValue - 1);
|
||||
stack.push(aValue);
|
||||
break;
|
||||
}
|
||||
// preinc
|
||||
case 58: {
|
||||
const a = stack.pop();
|
||||
const aValue = a.getValue() + 1;
|
||||
a.setValue(aValue);
|
||||
stack.push(aValue);
|
||||
break;
|
||||
}
|
||||
// predec
|
||||
case 59: {
|
||||
const a = stack.pop();
|
||||
const aValue = a.getValue() - 1;
|
||||
a.setValue(aValue);
|
||||
stack.push(aValue);
|
||||
break;
|
||||
}
|
||||
// + (add)
|
||||
case 64: {
|
||||
twoArgOperator((b, a) => b + a);
|
||||
break;
|
||||
}
|
||||
// - (subtract)
|
||||
case 65: {
|
||||
twoArgOperator((b, a) => b - a);
|
||||
break;
|
||||
}
|
||||
// * (multiply)
|
||||
case 66: {
|
||||
twoArgOperator((b, a) => b * a);
|
||||
break;
|
||||
}
|
||||
// / (divide)
|
||||
case 67: {
|
||||
twoArgOperator((b, a) => b / a);
|
||||
break;
|
||||
}
|
||||
// % (mod)
|
||||
case 68: {
|
||||
const a = stack.pop();
|
||||
const b = stack.pop();
|
||||
const aValue = a instanceof Variable ? a.getValue() : a;
|
||||
let bValue = b instanceof Variable ? b.getValue() : b;
|
||||
// Need to coerce LHS if not int, RHS is always int (enforced by compiler)
|
||||
if (b.type === "FLOAT" || b.type === "DOUBLE") {
|
||||
bValue = Math.floor(bValue);
|
||||
}
|
||||
stack.push(bValue % aValue);
|
||||
break;
|
||||
}
|
||||
// & (binary and)
|
||||
case 72: {
|
||||
twoArgOperator((b, a) => b & a);
|
||||
break;
|
||||
}
|
||||
// | (binary or)
|
||||
case 73: {
|
||||
twoArgOperator((b, a) => b | a);
|
||||
break;
|
||||
}
|
||||
// ! (not)
|
||||
case 74: {
|
||||
const aValue = popStackValue();
|
||||
stack.push(aValue ? 0 : 1);
|
||||
break;
|
||||
}
|
||||
// - (negative)
|
||||
case 76: {
|
||||
const aValue = popStackValue();
|
||||
stack.push(-aValue);
|
||||
break;
|
||||
}
|
||||
// logAnd (&&)
|
||||
case 80: {
|
||||
twoArgOperator((b, a) => b && a);
|
||||
break;
|
||||
}
|
||||
// logOr ||
|
||||
case 81: {
|
||||
twoArgOperator((b, a) => b || a);
|
||||
break;
|
||||
}
|
||||
// <<
|
||||
case 88: {
|
||||
twoArgOperator((b, a) => b << a);
|
||||
break;
|
||||
}
|
||||
// >>
|
||||
case 89: {
|
||||
twoArgOperator((b, a) => b >> a);
|
||||
break;
|
||||
}
|
||||
// new
|
||||
case 96: {
|
||||
const classesOffset = command.arg;
|
||||
const Klass = classes[classesOffset];
|
||||
const system = variables[0].getValue();
|
||||
const klassInst = new Klass(null, system.getscriptgroup());
|
||||
stack.push(klassInst);
|
||||
break;
|
||||
}
|
||||
// delete
|
||||
case 97: {
|
||||
const aValue = popStackValue();
|
||||
aValue.js_delete();
|
||||
break;
|
||||
}
|
||||
default:
|
||||
throw new Error(`Unhandled opcode ${command.opcode}`);
|
||||
}
|
||||
|
||||
ip++;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue