/
vm.js
152 lines (143 loc) · 3.81 KB
/
vm.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
import * as Instruction from './instruction.js';
import { createScope } from './object.js';
function lookupObject(scopes, name) {
for (const scope of scopes) {
const object = scope.getObject(name);
if (object) {
return object.value;
}
}
throw new ReferenceError(`[JOKE] ${name}`);
}
const UNARY_OPERATOR = {
'-': a => -a
}
function runUnaryOperator(operator, operand) {
return UNARY_OPERATOR[operator](operand);
}
const BINARY_OPERATOR = {
'+': (a, b) => a + b,
'-': (a, b) => a - b,
'*': (a, b) => a * b,
'/': (a, b) => a / b,
'%': (a, b) => a % b
}
function runBinaryOperator(operator, left, right) {
return BINARY_OPERATOR[operator](left, right);
}
function executeInsn(insn, stack, scopes) {
// shorten name
const I = Instruction;
switch (insn.command) {
case I.PUSH:
stack.push(insn.operand);
break;
case I.POP:
stack.pop();
break;
case I.DUP:
stack.push(stack[stack.length - 1]);
break;
case I.DEFINE:
{
const currentScope = scopes[0];
currentScope.defineObject(insn.operand1, insn.operand2);
}
break;
case I.INITIALIZE:
{
const currentScope = scopes[0];
const objectName = stack.pop();
const value = stack.pop();
currentScope.setObject(objectName, value, true);
}
break;
case I.ASSIGN:
{
const currentScope = scopes[0];
const objectName = stack.pop();
const value = stack.pop();
currentScope.setObject(objectName, value);
// assignment operator value
stack.push(value);
}
break;
case I.PUSH_SCOPE:
{
const newScope = createScope();
scopes.unshift(newScope);
}
break;
case I.POP_SCOPE:
scopes.shift();
break;
case I.LOOKUP:
{
const objectName = stack.pop();
const object = lookupObject(scopes, objectName);
stack.push(object);
}
break;
case I.MEMBER:
{
const propertyName = stack.pop();
const object = stack.pop();
const property = object.getProperty(propertyName);
stack.push(property);
}
break;
case I.CALL:
{
const args = [];
const arglen = insn.operand;
for (let i = 0; i < arglen; i++) {
args.push(stack.pop());
}
args.reverse();
const target = stack.pop();
const ret = target(...args);
stack.push(ret);
}
break;
case I.UNI_OP:
{
const operand = stack.pop();
const result = runUnaryOperator(insn.operator, operand);
stack.push(result);
}
break;
case I.BIN_OP:
{
const right = stack.pop();
const left = stack.pop();
const result = runBinaryOperator(insn.operator, left, right);
stack.push(result);
}
break;
default:
throw new Error(`[JOKE] Unknown instruction: ${insn.command}`);
}
}
function vmMain(insns, stack, scopes) {
let ptr = 0;
while (ptr != insns.length) {
const insn = insns[ptr++];
try {
executeInsn(insn, stack, scopes);
} catch (e) {
// add srcInfo to message
e.message += `: ${insn.srcInfo}`;
throw e;
}
}
}
export default class Vm {
run(insns, globalScope) {
const scopes = [globalScope];
const stack = [];
vmMain(insns, stack, scopes);
if (stack.length != 0) {
throw new Error(`[JOKE] Stack must be empty: ${stack}`);
}
}
}