mirror of
https://github.com/captbaritone/webamp.git
synced 2026-07-20 16:49:52 +00:00
Rework interpreter to use tagged union for variables
This commit is contained in:
parent
a9466f878c
commit
1e86945f1e
17 changed files with 790 additions and 571 deletions
1
packages/webamp-modern-2/.gitignore
vendored
Normal file
1
packages/webamp-modern-2/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
build/
|
||||
|
|
@ -1,11 +1,13 @@
|
|||
# TODO Next
|
||||
|
||||
- [ ] SystemObject.getruntimeversion
|
||||
- [ ] SystemObject.getskinname
|
||||
- [ ] Ensure only wrapped variables go on the stack
|
||||
- [ ] I suspect that this will require type-aware coersion
|
||||
Meanding we don't just always convert to number, but match the type coersion of Maki?
|
||||
- [ ] What is the root node?
|
||||
- [ ] Where do Layers actually go?
|
||||
- [ ] Where are scripts initialized?
|
||||
- [ ]
|
||||
|
||||
---
|
||||
- [ ] When parsing skins, where is the root state accumulated?
|
||||
- [ ] How do includes work when parsing skins? Do they create new context?
|
||||
|
||||
|
|
|
|||
|
|
@ -10,6 +10,10 @@ async function main() {
|
|||
|
||||
await parser.parse();
|
||||
|
||||
for (const container of parser._containers) {
|
||||
container.init();
|
||||
}
|
||||
|
||||
for (const container of parser._containers) {
|
||||
document.body.appendChild(container.getDebugDom());
|
||||
}
|
||||
|
|
|
|||
71
packages/webamp-modern-2/src/maki/MakiFile.ts
Normal file
71
packages/webamp-modern-2/src/maki/MakiFile.ts
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
// Holds a buffer and a pointer. Consumers can consume bytesoff the end of the
|
||||
// file. When we want to run in the browser, we can refactor this class to use a
|
||||
// typed array: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Typed_arrays
|
||||
export default class MakiFile {
|
||||
_arr: Uint8Array;
|
||||
_i: number;
|
||||
constructor(data: ArrayBuffer) {
|
||||
this._arr = new Uint8Array(data);
|
||||
this._i = 0;
|
||||
}
|
||||
|
||||
readInt32LE(): number {
|
||||
const offset = this._i >>> 0;
|
||||
this._i += 4;
|
||||
|
||||
return (
|
||||
this._arr[offset] |
|
||||
(this._arr[offset + 1] << 8) |
|
||||
(this._arr[offset + 2] << 16) |
|
||||
(this._arr[offset + 3] << 24)
|
||||
);
|
||||
}
|
||||
|
||||
readUInt32LE(): number {
|
||||
const int = this.peekUInt32LE();
|
||||
this._i += 4;
|
||||
return int;
|
||||
}
|
||||
|
||||
peekUInt32LE(): number {
|
||||
const offset = this._i >>> 0;
|
||||
|
||||
return (
|
||||
(this._arr[offset] |
|
||||
(this._arr[offset + 1] << 8) |
|
||||
(this._arr[offset + 2] << 16)) +
|
||||
this._arr[offset + 3] * 0x1000000
|
||||
);
|
||||
}
|
||||
|
||||
readUInt16LE(): number {
|
||||
const offset = this._i >>> 0;
|
||||
this._i += 2;
|
||||
return this._arr[offset] | (this._arr[offset + 1] << 8);
|
||||
}
|
||||
|
||||
readUInt8(): number {
|
||||
const int = this._arr[this._i];
|
||||
this._i++;
|
||||
return int;
|
||||
}
|
||||
|
||||
readStringOfLength(length: number): string {
|
||||
let ret = "";
|
||||
const end = Math.min(this._arr.length, this._i + length);
|
||||
|
||||
for (let i = this._i; i < end; ++i) {
|
||||
ret += String.fromCharCode(this._arr[i]);
|
||||
}
|
||||
this._i += length;
|
||||
return ret;
|
||||
}
|
||||
|
||||
readString(): string {
|
||||
return this.readStringOfLength(this.readUInt16LE());
|
||||
}
|
||||
|
||||
getPosition(): number {
|
||||
return this._i;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,360 +0,0 @@
|
|||
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: {
|
||||
const a = this.stack.pop();
|
||||
const b = this.stack.pop();
|
||||
switch (a.type) {
|
||||
case "INT":
|
||||
case "FLOAT":
|
||||
case "DOUBLE":
|
||||
case "BOOLEAN": {
|
||||
break;
|
||||
}
|
||||
case "STRING": {
|
||||
break;
|
||||
}
|
||||
default:
|
||||
throw new Error(`Unexpected type: ${a}`);
|
||||
}
|
||||
let aValue = this.getValue(a);
|
||||
const bValue = this.getValue(b);
|
||||
|
||||
aValue = this.coerceTypes__DEPRECATED(a, b);
|
||||
const result = Variable.newInt(bValue === aValue);
|
||||
this.stack.push(result);
|
||||
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++;
|
||||
}
|
||||
}
|
||||
|
||||
getValue(v) {
|
||||
return v instanceof Variable ? v.getValue() : v;
|
||||
}
|
||||
|
||||
popStackValue() {
|
||||
const v = this.stack.pop();
|
||||
return this.getValue(v);
|
||||
}
|
||||
|
||||
twoArgCoercingOperator(operator) {
|
||||
const a = this.stack.pop();
|
||||
const b = this.stack.pop();
|
||||
let aValue = this.getValue(a);
|
||||
const bValue = this.getValue(b);
|
||||
|
||||
aValue = this.coerceTypes__DEPRECATED(a, b);
|
||||
this.stack.push(operator(bValue, aValue));
|
||||
}
|
||||
|
||||
twoArgOperator(operator) {
|
||||
const aValue = this.popStackValue();
|
||||
const bValue = this.popStackValue();
|
||||
|
||||
this.stack.push(operator(bValue, aValue));
|
||||
}
|
||||
|
||||
coerceTypes__DEPRECATED(var1, var2) {
|
||||
if (var2.type === "INT") {
|
||||
if (var1.type === "FLOAT" || var1.type === "DOUBLE") {
|
||||
return Math.floor(this.getValue(var1));
|
||||
}
|
||||
}
|
||||
|
||||
return this.getValue(var1);
|
||||
}
|
||||
}
|
||||
513
packages/webamp-modern-2/src/maki/interpreter.ts
Normal file
513
packages/webamp-modern-2/src/maki/interpreter.ts
Normal file
|
|
@ -0,0 +1,513 @@
|
|||
import { V, Variable } from "./v";
|
||||
import { assert, assume } from "../utils";
|
||||
import { ParsedMaki, Command, Method } from "./parser";
|
||||
|
||||
export function interpret(
|
||||
start: number,
|
||||
program: ParsedMaki,
|
||||
classResolver: (guid: string) => any
|
||||
) {
|
||||
const interpreter = new Interpreter(program, classResolver);
|
||||
return interpreter.interpret(start);
|
||||
}
|
||||
|
||||
class Interpreter {
|
||||
stack: Variable[];
|
||||
callStack: number[];
|
||||
classes: string[];
|
||||
variables: Variable[];
|
||||
methods: Method[];
|
||||
commands: Command[];
|
||||
classResolver: (guid: string) => any;
|
||||
constructor(program: ParsedMaki, classResolver: (guid: string) => any) {
|
||||
const { commands, methods, variables, classes } = program;
|
||||
this.classResolver = classResolver;
|
||||
this.commands = commands;
|
||||
this.methods = methods;
|
||||
this.variables = variables;
|
||||
this.classes = classes;
|
||||
|
||||
this.stack = [];
|
||||
this.callStack = [];
|
||||
}
|
||||
|
||||
interpret(start: number) {
|
||||
// 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.stack.pop();
|
||||
const offsetIntoVariables = command.arg;
|
||||
const current = this.variables[offsetIntoVariables];
|
||||
assume(
|
||||
aValue.type === current.type,
|
||||
"Assigned from one type to a different type."
|
||||
);
|
||||
|
||||
current.value = aValue.value;
|
||||
break;
|
||||
}
|
||||
// ==
|
||||
case 8: {
|
||||
const a = this.stack.pop();
|
||||
const b = this.stack.pop();
|
||||
assume(
|
||||
a.type == b.type,
|
||||
`Tried to compare a ${a.type} to a ${b.type}.`
|
||||
);
|
||||
const result = V.newInt(a.value === b.value);
|
||||
this.stack.push(result);
|
||||
break;
|
||||
}
|
||||
// !=
|
||||
case 9: {
|
||||
const a = this.stack.pop();
|
||||
const b = this.stack.pop();
|
||||
assume(
|
||||
a.type == b.type,
|
||||
`Tried to compare a ${a.type} to a ${b.type}.`
|
||||
);
|
||||
const result = V.newInt(a.value !== b.value);
|
||||
this.stack.push(result);
|
||||
break;
|
||||
}
|
||||
// >
|
||||
case 10: {
|
||||
const a = this.stack.pop();
|
||||
const b = this.stack.pop();
|
||||
switch (a.type) {
|
||||
case "STRING":
|
||||
case "OBJECT":
|
||||
case "BOOL":
|
||||
case "NULL":
|
||||
throw new Error("Tried to add non-numbers.");
|
||||
}
|
||||
switch (b.type) {
|
||||
case "STRING":
|
||||
case "OBJECT":
|
||||
case "BOOL":
|
||||
case "NULL":
|
||||
throw new Error("Tried to add non-numbers.");
|
||||
}
|
||||
this.stack.push(V.newInt(a.value > b.value));
|
||||
break;
|
||||
}
|
||||
// >=
|
||||
case 11: {
|
||||
assume(false, "Unimplimented >= operator");
|
||||
break;
|
||||
}
|
||||
// <
|
||||
case 12: {
|
||||
const a = this.stack.pop();
|
||||
const b = this.stack.pop();
|
||||
switch (a.type) {
|
||||
case "STRING":
|
||||
case "OBJECT":
|
||||
case "BOOL":
|
||||
case "NULL":
|
||||
throw new Error("Tried to add non-numbers.");
|
||||
}
|
||||
switch (b.type) {
|
||||
case "STRING":
|
||||
case "OBJECT":
|
||||
case "BOOL":
|
||||
case "NULL":
|
||||
throw new Error("Tried to add non-numbers.");
|
||||
}
|
||||
this.stack.push(V.newInt(a.value < b.value));
|
||||
break;
|
||||
}
|
||||
// <=
|
||||
case 13: {
|
||||
assume(false, "Unimplimented <= operator");
|
||||
break;
|
||||
}
|
||||
// jumpIf
|
||||
case 16: {
|
||||
const value = this.stack.pop();
|
||||
// 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.stack.pop();
|
||||
// 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 returnType = method.returnType;
|
||||
const classesOffset = method.typeOffset;
|
||||
methodName = methodName.toLowerCase();
|
||||
|
||||
const guid = this.classes[classesOffset];
|
||||
const klass = this.classResolver(guid);
|
||||
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 method "${methodName}" to ${klass.name}`
|
||||
);
|
||||
}
|
||||
let argCount = klass.prototype[methodName].length;
|
||||
|
||||
const methodArgs = [];
|
||||
while (argCount--) {
|
||||
const a = this.stack.pop();
|
||||
methodArgs.push(a.value);
|
||||
}
|
||||
const obj = this.stack.pop();
|
||||
assert(
|
||||
obj.type === "OBJECT",
|
||||
"Tried to call a method on a primitive."
|
||||
);
|
||||
let value = obj.value[methodName](...methodArgs);
|
||||
if (typeof value.then === "function") {
|
||||
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({ type: returnType, value } as any);
|
||||
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
|
||||
assume(false, "OPCODE: complete");
|
||||
break;
|
||||
}
|
||||
// mov
|
||||
case 48: {
|
||||
const a = this.stack.pop();
|
||||
const b = this.stack.pop();
|
||||
|
||||
assume(
|
||||
a.type === b.type,
|
||||
`Type mismatch: ${a.type} != ${b.type} at ip: ${ip}`
|
||||
);
|
||||
b.value = a.value;
|
||||
this.stack.push(a);
|
||||
break;
|
||||
}
|
||||
// postinc
|
||||
case 56: {
|
||||
const a = this.stack.pop();
|
||||
switch (a.type) {
|
||||
case "STRING":
|
||||
case "OBJECT":
|
||||
case "BOOL":
|
||||
case "NULL":
|
||||
throw new Error("Tried to increment a non-number.");
|
||||
}
|
||||
const aValue = a.value;
|
||||
a.value = aValue + 1;
|
||||
this.stack.push({ type: a.type, value: aValue });
|
||||
break;
|
||||
}
|
||||
// postdec
|
||||
case 57: {
|
||||
const a = this.stack.pop();
|
||||
switch (a.type) {
|
||||
case "STRING":
|
||||
case "OBJECT":
|
||||
case "BOOL":
|
||||
case "NULL":
|
||||
throw new Error("Tried to decrement a non-number.");
|
||||
}
|
||||
const aValue = a.value;
|
||||
a.value = aValue - 1;
|
||||
this.stack.push({ type: a.type, value: aValue });
|
||||
break;
|
||||
}
|
||||
// preinc
|
||||
case 58: {
|
||||
const a = this.stack.pop();
|
||||
switch (a.type) {
|
||||
case "STRING":
|
||||
case "OBJECT":
|
||||
case "BOOL":
|
||||
case "NULL":
|
||||
throw new Error("Tried to increment a non-number.");
|
||||
}
|
||||
a.value++;
|
||||
this.stack.push(a);
|
||||
break;
|
||||
}
|
||||
// predec
|
||||
case 59: {
|
||||
const a = this.stack.pop();
|
||||
switch (a.type) {
|
||||
case "STRING":
|
||||
case "OBJECT":
|
||||
case "BOOL":
|
||||
case "NULL":
|
||||
throw new Error("Tried to increment a non-number.");
|
||||
}
|
||||
a.value--;
|
||||
this.stack.push(a);
|
||||
break;
|
||||
}
|
||||
// + (add)
|
||||
case 64: {
|
||||
const a = this.stack.pop();
|
||||
const b = this.stack.pop();
|
||||
switch (a.type) {
|
||||
case "STRING":
|
||||
case "OBJECT":
|
||||
case "BOOL":
|
||||
case "NULL":
|
||||
throw new Error("Tried to add non-numbers.");
|
||||
}
|
||||
switch (b.type) {
|
||||
case "STRING":
|
||||
case "OBJECT":
|
||||
case "BOOL":
|
||||
throw new Error("Tried to add non-numbers.");
|
||||
}
|
||||
// TODO: Do we need to round the value if INT?
|
||||
this.stack.push({ type: a.type, value: a.value + b.value });
|
||||
break;
|
||||
}
|
||||
// - (subtract)
|
||||
case 65: {
|
||||
const a = this.stack.pop();
|
||||
const b = this.stack.pop();
|
||||
switch (a.type) {
|
||||
case "STRING":
|
||||
case "OBJECT":
|
||||
case "BOOL":
|
||||
case "NULL":
|
||||
throw new Error("Tried to add non-numbers.");
|
||||
}
|
||||
switch (b.type) {
|
||||
case "STRING":
|
||||
case "OBJECT":
|
||||
case "BOOL":
|
||||
case "NULL":
|
||||
throw new Error("Tried to add non-numbers.");
|
||||
}
|
||||
// TODO: Do we need to round the value if INT?
|
||||
this.stack.push({ type: a.type, value: a.value - b.value });
|
||||
break;
|
||||
}
|
||||
// * (multiply)
|
||||
case 66: {
|
||||
const a = this.stack.pop();
|
||||
const b = this.stack.pop();
|
||||
switch (a.type) {
|
||||
case "STRING":
|
||||
case "OBJECT":
|
||||
case "BOOL":
|
||||
case "NULL":
|
||||
throw new Error("Tried to add non-numbers.");
|
||||
}
|
||||
switch (b.type) {
|
||||
case "STRING":
|
||||
case "OBJECT":
|
||||
case "BOOL":
|
||||
case "NULL":
|
||||
throw new Error("Tried to add non-numbers.");
|
||||
}
|
||||
// TODO: Do we need to round the value if INT?
|
||||
this.stack.push({ type: a.type, value: a.value * b.value });
|
||||
break;
|
||||
}
|
||||
// / (divide)
|
||||
case 67: {
|
||||
const a = this.stack.pop();
|
||||
const b = this.stack.pop();
|
||||
switch (a.type) {
|
||||
case "STRING":
|
||||
case "OBJECT":
|
||||
case "BOOL":
|
||||
case "NULL":
|
||||
throw new Error("Tried to add non-numbers.");
|
||||
}
|
||||
switch (b.type) {
|
||||
case "STRING":
|
||||
case "OBJECT":
|
||||
case "BOOL":
|
||||
throw new Error("Tried to add non-numbers.");
|
||||
}
|
||||
// TODO: Do we need to round the value if INT?
|
||||
this.stack.push({ type: a.type, value: a.value / b.value });
|
||||
break;
|
||||
}
|
||||
// % (mod)
|
||||
case 68: {
|
||||
const a = this.stack.pop();
|
||||
const b = this.stack.pop();
|
||||
switch (a.type) {
|
||||
case "STRING":
|
||||
case "OBJECT":
|
||||
case "BOOL":
|
||||
case "NULL":
|
||||
throw new Error("Tried to add non-numbers.");
|
||||
}
|
||||
switch (b.type) {
|
||||
case "STRING":
|
||||
case "OBJECT":
|
||||
case "BOOL":
|
||||
throw new Error("Tried to add non-numbers.");
|
||||
// Need to coerce LHS if not int, RHS is always int (enforced by compiler)
|
||||
case "FLOAT":
|
||||
case "DOUBLE":
|
||||
const value = Math.floor(b.value) % a.value;
|
||||
this.stack.push({ type: a.type, value });
|
||||
break;
|
||||
case "INT":
|
||||
this.stack.push({ type: a.type, value: b.value % a.value });
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
// & (binary and)
|
||||
case 72: {
|
||||
assume(false, "Unimplimented & operator");
|
||||
break;
|
||||
}
|
||||
// | (binary or)
|
||||
case 73: {
|
||||
assume(false, "Unimplimented | operator");
|
||||
break;
|
||||
}
|
||||
// ! (not)
|
||||
case 74: {
|
||||
const a = this.stack.pop();
|
||||
switch (a.type) {
|
||||
case "STRING":
|
||||
case "OBJECT":
|
||||
throw new Error("Tried ! a string or object.");
|
||||
}
|
||||
this.stack.push(V.newInt(!a.value));
|
||||
break;
|
||||
}
|
||||
// - (negative)
|
||||
case 76: {
|
||||
const a = this.stack.pop();
|
||||
switch (a.type) {
|
||||
case "STRING":
|
||||
case "OBJECT":
|
||||
case "BOOL":
|
||||
case "NULL":
|
||||
throw new Error("Tried to add non-numbers.");
|
||||
}
|
||||
this.stack.push({ type: a.type, value: -a.value });
|
||||
break;
|
||||
}
|
||||
// logAnd (&&)
|
||||
case 80: {
|
||||
assume(false, "Unimplimented && operator");
|
||||
break;
|
||||
}
|
||||
// logOr ||
|
||||
case 81: {
|
||||
const a = this.stack.pop();
|
||||
const b = this.stack.pop();
|
||||
// Some of these are probably valid, but we'll enable them once we see usage.
|
||||
switch (a.type) {
|
||||
case "STRING":
|
||||
case "OBJECT":
|
||||
case "BOOL":
|
||||
case "NULL":
|
||||
throw new Error("Tried to add non-numbers.");
|
||||
}
|
||||
switch (b.type) {
|
||||
case "STRING":
|
||||
case "OBJECT":
|
||||
case "BOOL":
|
||||
case "NULL":
|
||||
throw new Error("Tried to add non-numbers.");
|
||||
}
|
||||
if (a.value) {
|
||||
return a;
|
||||
}
|
||||
return b;
|
||||
}
|
||||
// <<
|
||||
case 88: {
|
||||
assume(false, "Unimplimented << operator");
|
||||
break;
|
||||
}
|
||||
// >>
|
||||
case 89: {
|
||||
assume(false, "Unimplimented >> operator");
|
||||
break;
|
||||
}
|
||||
// new
|
||||
case 96: {
|
||||
const classesOffset = command.arg;
|
||||
const guid = this.classes[classesOffset];
|
||||
const Klass = this.classResolver(guid);
|
||||
const klassInst = new Klass();
|
||||
this.stack.push(klassInst);
|
||||
break;
|
||||
}
|
||||
// delete
|
||||
case 97: {
|
||||
const aValue = this.stack.pop();
|
||||
// TODO: Cleanup the object?
|
||||
break;
|
||||
}
|
||||
default:
|
||||
throw new Error(`Unhandled opcode ${command.opcode}`);
|
||||
}
|
||||
|
||||
ip++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
const std = require("./std.json");
|
||||
import std from "./std.json";
|
||||
|
||||
const NAME_TO_DEF = {};
|
||||
|
||||
|
|
@ -60,4 +60,4 @@ Found 1 error.
|
|||
|
||||
*/
|
||||
|
||||
module.exports = std;
|
||||
export default std;
|
||||
|
|
@ -1,13 +1,57 @@
|
|||
const stdPatched = require("./objectData/stdPatched");
|
||||
const pldir = require("./objectData/pldir.json");
|
||||
const config = require("./objectData/config.json");
|
||||
import stdPatched from "./objectData/stdPatched";
|
||||
import pldir from "./objectData/pldir.json";
|
||||
import config from "./objectData/config.json";
|
||||
import { DataType } from "./v";
|
||||
|
||||
const objects = { ...stdPatched, ...pldir, ...config };
|
||||
type MethodDefinition = {
|
||||
name: string;
|
||||
result: string;
|
||||
parameters: string[][];
|
||||
};
|
||||
|
||||
type ObjectDefinition = {
|
||||
parent: string;
|
||||
name: string;
|
||||
functions: MethodDefinition[];
|
||||
parentClass?: ObjectDefinition;
|
||||
};
|
||||
|
||||
const objects: { [key: string]: ObjectDefinition } = {
|
||||
...stdPatched,
|
||||
...pldir,
|
||||
...config,
|
||||
};
|
||||
|
||||
export function getClass(id: String): ObjectDefinition {
|
||||
return normalizedObjects[getFormattedId(id)];
|
||||
}
|
||||
|
||||
export function getReturnType(classId: string, methodName: string): DataType {
|
||||
const method = getMethod(classId, methodName);
|
||||
const upper = method.result.toUpperCase();
|
||||
switch (upper) {
|
||||
case "INT":
|
||||
case "DOUBLE":
|
||||
case "STRING":
|
||||
case "FLOAT":
|
||||
case "BOOL":
|
||||
return upper as any;
|
||||
case "":
|
||||
return "NULL" as any;
|
||||
default:
|
||||
return "OBJECT" as any;
|
||||
}
|
||||
}
|
||||
|
||||
function getMethod(classId: string, methodName: string): MethodDefinition {
|
||||
const klass = getClass(classId);
|
||||
return getObjectFunction(klass, methodName);
|
||||
}
|
||||
|
||||
// TODO: We could probably just fix the keys used in this file to already be normalized
|
||||
// We might even want to normalize the to match the formatting we get out the file. That could
|
||||
// avoid the awkward regex inside `getClass()`.
|
||||
const normalizedObjects = {};
|
||||
const normalizedObjects: { [key: string]: ObjectDefinition } = {};
|
||||
Object.keys(objects).forEach((key) => {
|
||||
normalizedObjects[key.toLowerCase()] = objects[key];
|
||||
});
|
||||
|
|
@ -37,14 +81,14 @@ function getFormattedId(id) {
|
|||
return formattedId.toLowerCase();
|
||||
}
|
||||
|
||||
function getClass(id) {
|
||||
return normalizedObjects[getFormattedId(id)];
|
||||
}
|
||||
|
||||
function getObjectFunction(klass, functionName) {
|
||||
function getObjectFunction(
|
||||
klass: ObjectDefinition,
|
||||
functionName: string
|
||||
): MethodDefinition {
|
||||
const lowerName = functionName.toLowerCase();
|
||||
const method = klass.functions.find((func) => {
|
||||
// TODO: This could probably be normalized at load time, or evern sooner.
|
||||
return func.name.toLowerCase() === functionName.toLowerCase();
|
||||
return func.name.toLowerCase() === lowerName;
|
||||
});
|
||||
if (method != null) {
|
||||
return method;
|
||||
|
|
@ -54,25 +98,3 @@ function getObjectFunction(klass, functionName) {
|
|||
}
|
||||
return getObjectFunction(klass.parentClass, functionName);
|
||||
}
|
||||
|
||||
function getFunctionObject(klass, functionName) {
|
||||
const method = klass.functions.find((func) => {
|
||||
// TODO: This could probably be normalized at load time, or evern sooner.
|
||||
return func.name.toLowerCase() === functionName.toLowerCase();
|
||||
});
|
||||
if (method != null) {
|
||||
return klass;
|
||||
}
|
||||
if (klass.parentClass == null) {
|
||||
throw new Error(`Could not find method ${functionName} on ${klass.name}.`);
|
||||
}
|
||||
return getFunctionObject(klass.parentClass, functionName);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
objects,
|
||||
getFormattedId,
|
||||
getClass,
|
||||
getObjectFunction,
|
||||
getFunctionObject,
|
||||
};
|
||||
|
|
@ -1,5 +1,32 @@
|
|||
import { assert, assume } from "../utils";
|
||||
import { COMMANDS } from "./constants";
|
||||
import Variable from "./variable";
|
||||
import { DataType, Variable } from "./v";
|
||||
import MakiFile from "./MakiFile";
|
||||
import { getMethod, getReturnType } from "./objects";
|
||||
|
||||
export type Command = {
|
||||
opcode: number;
|
||||
arg: number;
|
||||
};
|
||||
|
||||
export type Method = {
|
||||
name: string;
|
||||
typeOffset: number;
|
||||
returnType: DataType;
|
||||
};
|
||||
|
||||
export type ParsedMaki = {
|
||||
commands: Command[];
|
||||
methods: Method[];
|
||||
variables: Variable[];
|
||||
classes: string[];
|
||||
bindings: Binding[];
|
||||
version: number;
|
||||
};
|
||||
|
||||
export type Binding = {
|
||||
commandOffset: number;
|
||||
};
|
||||
|
||||
const MAGIC = "FG";
|
||||
|
||||
|
|
@ -11,7 +38,7 @@ const PRIMITIVE_TYPES = {
|
|||
6: "STRING",
|
||||
};
|
||||
|
||||
export function parse(data) {
|
||||
export function parse(data: ArrayBuffer): ParsedMaki {
|
||||
const makiFile = new MakiFile(data);
|
||||
|
||||
const magic = readMagic(makiFile);
|
||||
|
|
@ -22,7 +49,7 @@ export function parse(data) {
|
|||
// Maybe it's additional version info?
|
||||
const extraVersion = makiFile.readUInt32LE();
|
||||
const classes = readClasses(makiFile);
|
||||
const methods = readMethods(makiFile);
|
||||
const methods = readMethods(makiFile, classes);
|
||||
const variables = readVariables({ makiFile, classes });
|
||||
readConstants({ makiFile, variables });
|
||||
const bindings = readBindings(makiFile);
|
||||
|
|
@ -42,33 +69,30 @@ export function parse(data) {
|
|||
}
|
||||
});
|
||||
|
||||
const resolvedBindings = bindings.map((binding) => {
|
||||
const resolvedBindings = bindings.map((binding): Binding => {
|
||||
return Object.assign({}, binding, {
|
||||
commandOffset: offsetToCommand[binding.binaryOffset],
|
||||
binaryOffset: undefined,
|
||||
});
|
||||
});
|
||||
|
||||
const resolvedCommands = commands.map((command) => {
|
||||
const resolvedCommands = commands.map((command): Command => {
|
||||
if (command.argType === "COMMAND_OFFSET") {
|
||||
return Object.assign({}, command, { arg: offsetToCommand[command.arg] });
|
||||
}
|
||||
return command;
|
||||
});
|
||||
return {
|
||||
magic,
|
||||
classes,
|
||||
methods,
|
||||
variables,
|
||||
bindings: resolvedBindings,
|
||||
commands: resolvedCommands,
|
||||
version,
|
||||
extraVersion,
|
||||
};
|
||||
}
|
||||
|
||||
// TODO: Don't depend upon COMMANDS
|
||||
function opcodeToArgType(opcode) {
|
||||
function opcodeToArgType(opcode: number) {
|
||||
const command = COMMANDS[opcode];
|
||||
if (command == null) {
|
||||
throw new Error(`Unknown opcode ${opcode}`);
|
||||
|
|
@ -87,77 +111,7 @@ function opcodeToArgType(opcode) {
|
|||
}
|
||||
}
|
||||
|
||||
// Holds a buffer and a pointer. Consumers can consume bytesoff the end of the
|
||||
// file. When we want to run in the browser, we can refactor this class to use a
|
||||
// typed array: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Typed_arrays
|
||||
class MakiFile {
|
||||
constructor(data) {
|
||||
this._arr = new Uint8Array(data);
|
||||
this._i = 0;
|
||||
}
|
||||
|
||||
readInt32LE() {
|
||||
const offset = this._i >>> 0;
|
||||
this._i += 4;
|
||||
|
||||
return (
|
||||
this._arr[offset] |
|
||||
(this._arr[offset + 1] << 8) |
|
||||
(this._arr[offset + 2] << 16) |
|
||||
(this._arr[offset + 3] << 24)
|
||||
);
|
||||
}
|
||||
|
||||
readUInt32LE() {
|
||||
const int = this.peekUInt32LE();
|
||||
this._i += 4;
|
||||
return int;
|
||||
}
|
||||
|
||||
peekUInt32LE() {
|
||||
const offset = this._i >>> 0;
|
||||
|
||||
return (
|
||||
(this._arr[offset] |
|
||||
(this._arr[offset + 1] << 8) |
|
||||
(this._arr[offset + 2] << 16)) +
|
||||
this._arr[offset + 3] * 0x1000000
|
||||
);
|
||||
}
|
||||
|
||||
readUInt16LE() {
|
||||
const offset = this._i >>> 0;
|
||||
this._i += 2;
|
||||
return this._arr[offset] | (this._arr[offset + 1] << 8);
|
||||
}
|
||||
|
||||
readUInt8() {
|
||||
const int = this._arr[this._i];
|
||||
this._i++;
|
||||
return int;
|
||||
}
|
||||
|
||||
readStringOfLength(length) {
|
||||
let ret = "";
|
||||
const end = Math.min(this._arr.length, this._i + length);
|
||||
|
||||
for (let i = this._i; i < end; ++i) {
|
||||
ret += String.fromCharCode(this._arr[i]);
|
||||
}
|
||||
this._i += length;
|
||||
return ret;
|
||||
}
|
||||
|
||||
readString() {
|
||||
return this.readStringOfLength(this.readUInt16LE());
|
||||
}
|
||||
|
||||
getPosition() {
|
||||
return this._i;
|
||||
}
|
||||
}
|
||||
|
||||
function readMagic(makiFile) {
|
||||
function readMagic(makiFile: MakiFile): string {
|
||||
const magic = makiFile.readStringOfLength(MAGIC.length);
|
||||
if (magic !== MAGIC) {
|
||||
throw new Error(
|
||||
|
|
@ -167,12 +121,12 @@ function readMagic(makiFile) {
|
|||
return magic;
|
||||
}
|
||||
|
||||
function readVersion(makiFile) {
|
||||
function readVersion(makiFile: MakiFile): number {
|
||||
// No idea what we're actually expecting here.
|
||||
return makiFile.readUInt16LE();
|
||||
}
|
||||
|
||||
function readClasses(makiFile) {
|
||||
function readClasses(makiFile: MakiFile): string[] {
|
||||
let count = makiFile.readUInt32LE();
|
||||
const classes = [];
|
||||
while (count--) {
|
||||
|
|
@ -186,9 +140,9 @@ function readClasses(makiFile) {
|
|||
return classes;
|
||||
}
|
||||
|
||||
function readMethods(makiFile) {
|
||||
function readMethods(makiFile: MakiFile, classes: string[]): Method[] {
|
||||
let count = makiFile.readUInt32LE();
|
||||
const methods = [];
|
||||
const methods: Method[] = [];
|
||||
while (count--) {
|
||||
const classCode = makiFile.readUInt16LE();
|
||||
// Offset into our parsed types
|
||||
|
|
@ -196,7 +150,12 @@ function readMethods(makiFile) {
|
|||
// This is probably the second half of a uint32
|
||||
makiFile.readUInt16LE();
|
||||
const name = makiFile.readString();
|
||||
methods.push({ name, typeOffset });
|
||||
|
||||
const className = classes[typeOffset];
|
||||
|
||||
const returnType = getReturnType(className, name);
|
||||
|
||||
methods.push({ name, typeOffset, returnType });
|
||||
}
|
||||
return methods;
|
||||
}
|
||||
|
|
@ -220,17 +179,14 @@ function readVariables({ makiFile, classes }) {
|
|||
if (variable == null) {
|
||||
throw new Error("Invalid type");
|
||||
}
|
||||
variables.push(
|
||||
new Variable({ type: variable, typeName: "SUBCLASS", global: !!global })
|
||||
);
|
||||
// assume(false, "Unimplemented subclass variable type");
|
||||
variables.push({ type: "OBJECT", value: object });
|
||||
} else if (object) {
|
||||
const klass = classes[typeOffset];
|
||||
if (klass == null) {
|
||||
throw new Error("Invalid type");
|
||||
}
|
||||
variables.push(
|
||||
new Variable({ type: klass, typeName: "OBJECT", global: !!global })
|
||||
);
|
||||
variables.push({ type: "OBJECT", value: object });
|
||||
} else {
|
||||
const typeName = PRIMITIVE_TYPES[typeOffset];
|
||||
if (typeName == null) {
|
||||
|
|
@ -259,12 +215,10 @@ function readVariables({ makiFile, classes }) {
|
|||
default:
|
||||
throw new Error("Invalid primitive type");
|
||||
}
|
||||
const variable = new Variable({
|
||||
const variable = {
|
||||
type: typeName,
|
||||
typeName,
|
||||
global: !!global,
|
||||
});
|
||||
variable.setValue(value);
|
||||
value,
|
||||
};
|
||||
variables.push(variable);
|
||||
}
|
||||
}
|
||||
|
|
@ -279,7 +233,7 @@ function readConstants({ makiFile, variables }) {
|
|||
// TODO: Assert this is of type string.
|
||||
const value = makiFile.readString();
|
||||
// TODO: Don't mutate
|
||||
variable.setValue(value);
|
||||
variable.value = value;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -346,8 +300,7 @@ function parseComand({ start, makiFile, length }) {
|
|||
makiFile.peekUInt32LE() >= 0xffff0000 &&
|
||||
makiFile.peekUInt32LE() <= 0xffff000f
|
||||
) {
|
||||
command.foo = true;
|
||||
command.stackProtection = makiFile.readUInt32LE();
|
||||
// command.stackProtection = makiFile.readUInt32LE();
|
||||
}
|
||||
|
||||
// TODO: What even is this?
|
||||
|
|
@ -1,55 +0,0 @@
|
|||
import Variable from "./variable";
|
||||
import runtime from "../runtime";
|
||||
|
||||
// Debug utility to pretty print a value/variable
|
||||
function printValue(value) {
|
||||
if (!(value instanceof Variable)) {
|
||||
return value;
|
||||
}
|
||||
const variable = value;
|
||||
let type = "UNKOWN";
|
||||
switch (variable.typeName) {
|
||||
case "OBJECT":
|
||||
const obj = runtime[variable.type];
|
||||
if (obj == null) {
|
||||
type = "Unknown object";
|
||||
} else {
|
||||
type = obj.getclassname();
|
||||
}
|
||||
break;
|
||||
case "STRING":
|
||||
const str = variable.getValue();
|
||||
type = `STRING(${str})`;
|
||||
break;
|
||||
case "INT":
|
||||
type = "INT";
|
||||
break;
|
||||
case "FLOAT":
|
||||
type = "FLOAT";
|
||||
break;
|
||||
case "DOUBLE":
|
||||
type = "DOUBLE";
|
||||
break;
|
||||
case "BOOLEAN":
|
||||
type = "BOOLEAN";
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Unknown variable type ${variable.typeName}`);
|
||||
}
|
||||
return `Variable(${type})`;
|
||||
}
|
||||
|
||||
function printCommand({ i, command, stack, variables }) {
|
||||
console.log(
|
||||
`${i} (${command.start} + ${command.offset})`,
|
||||
command.command.name.toUpperCase(),
|
||||
command.opcode,
|
||||
printValue(variables[command.arg])
|
||||
);
|
||||
stack.forEach((value, j) => {
|
||||
const name = printValue(value, { runtime });
|
||||
console.log(" ", j + 1, name);
|
||||
});
|
||||
}
|
||||
|
||||
export default { printCommand };
|
||||
32
packages/webamp-modern-2/src/maki/v.ts
Normal file
32
packages/webamp-modern-2/src/maki/v.ts
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
import GuiObj from "../skin/GuiObj";
|
||||
|
||||
export type Variable =
|
||||
| {
|
||||
type: "BOOL";
|
||||
value: boolean;
|
||||
}
|
||||
| {
|
||||
type: "INT" | "FLOAT" | "DOUBLE";
|
||||
value: number;
|
||||
}
|
||||
| {
|
||||
type: "STRING";
|
||||
value: string;
|
||||
}
|
||||
| {
|
||||
type: "OBJECT";
|
||||
value: GuiObj;
|
||||
}
|
||||
| {
|
||||
type: "NULL";
|
||||
value: null;
|
||||
};
|
||||
|
||||
export type DataType = Pick<Variable, "type">;
|
||||
|
||||
export const V = {
|
||||
// TODO: Split boolean out into its own method
|
||||
newInt(value: number | boolean): Variable {
|
||||
return { type: "INT", value: Number(value) };
|
||||
},
|
||||
};
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
class Variable {
|
||||
constructor({ type, typeName, global }) {
|
||||
this.type = type;
|
||||
this.typeName = typeName;
|
||||
this.global = global;
|
||||
}
|
||||
|
||||
getValue() {
|
||||
return this._value;
|
||||
}
|
||||
|
||||
setValue(value) {
|
||||
this._value = value;
|
||||
}
|
||||
}
|
||||
|
||||
export default Variable;
|
||||
|
|
@ -31,6 +31,12 @@ export default class Container extends XmlObj {
|
|||
return true;
|
||||
}
|
||||
|
||||
init() {
|
||||
for (const layout of this._layouts) {
|
||||
layout.init();
|
||||
}
|
||||
}
|
||||
|
||||
addLayout(layout: Layout) {
|
||||
layout.setParent(this);
|
||||
this._layouts.push(layout);
|
||||
|
|
|
|||
|
|
@ -43,6 +43,15 @@ export default class Group extends GuiObj {
|
|||
return true;
|
||||
}
|
||||
|
||||
init() {
|
||||
for (const systemObject of this._systemObjects) {
|
||||
systemObject.init();
|
||||
}
|
||||
for (const child of this._children) {
|
||||
child.init();
|
||||
}
|
||||
}
|
||||
|
||||
addSystemObject(systemObj: SystemObject) {
|
||||
systemObj.setParentGroup(this);
|
||||
this._systemObjects.push(systemObj);
|
||||
|
|
|
|||
|
|
@ -40,6 +40,10 @@ export default class GuiObj extends XmlObj {
|
|||
return true;
|
||||
}
|
||||
|
||||
init() {
|
||||
// pass
|
||||
}
|
||||
|
||||
getDebugDom(): HTMLDivElement {
|
||||
const div = window.document.createElement("div");
|
||||
div.style.display = this._visible ? "inline-block" : "none";
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
import Group from "./Group";
|
||||
import { interpret } from "../maki/interpreter";
|
||||
import { getClass } from "../maki/objects";
|
||||
import { ParsedMaki } from "../maki/parser";
|
||||
|
||||
type ParsedMaki = Object; // Not typed yet.
|
||||
import Group from "./Group";
|
||||
|
||||
export default class SystemObject {
|
||||
_parentGroup: Group;
|
||||
|
|
@ -10,7 +12,39 @@ export default class SystemObject {
|
|||
this._parsedScript = parsedScript;
|
||||
}
|
||||
|
||||
init() {
|
||||
const initialVariable = this._parsedScript.variables[0];
|
||||
if (initialVariable.type !== "OBJECT") {
|
||||
throw new Error("First variable was not SystemObject.");
|
||||
}
|
||||
initialVariable.value = this;
|
||||
// TODO: How should we setup bindings?
|
||||
// console.log(this._parsedScript.bindings);
|
||||
interpret(0, this._parsedScript, classResover);
|
||||
}
|
||||
|
||||
setParentGroup(group: Group) {
|
||||
this._parentGroup = group;
|
||||
}
|
||||
|
||||
/* Required for Maki */
|
||||
getruntimeversion(): number {
|
||||
return 5.666;
|
||||
}
|
||||
|
||||
getskinname() {
|
||||
return "TODO: Get the Real skin name";
|
||||
}
|
||||
}
|
||||
|
||||
function classResover(guid: string): any {
|
||||
switch (guid) {
|
||||
case "d6f50f6449b793fa66baf193983eaeef":
|
||||
return SystemObject;
|
||||
default:
|
||||
console.log();
|
||||
throw new Error(
|
||||
`Unresolvable class "${getClass(guid).name}" (guid: ${guid})`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue