flowscript-vm 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- flowscript/__init__.py +35 -0
- flowscript/__main__.py +4 -0
- flowscript/api.py +286 -0
- flowscript/bytecode.py +372 -0
- flowscript/cli.py +126 -0
- flowscript/compiler.py +1019 -0
- flowscript/debugger/__init__.py +0 -0
- flowscript/debugger/server.py +505 -0
- flowscript/debugger/static/app.js +626 -0
- flowscript/debugger/static/chart.js +620 -0
- flowscript/debugger/static/edit.js +513 -0
- flowscript/debugger/static/index.html +115 -0
- flowscript/debugger/static/style.css +216 -0
- flowscript/intops.py +63 -0
- flowscript/lexer.py +265 -0
- flowscript/mermaid.py +109 -0
- flowscript/parser.py +578 -0
- flowscript/printer.py +225 -0
- flowscript/runner.py +164 -0
- flowscript/sizes.py +512 -0
- flowscript/svg.py +766 -0
- flowscript/values.py +724 -0
- flowscript/vm.py +577 -0
- flowscript_vm-0.1.0.dist-info/METADATA +400 -0
- flowscript_vm-0.1.0.dist-info/RECORD +29 -0
- flowscript_vm-0.1.0.dist-info/WHEEL +5 -0
- flowscript_vm-0.1.0.dist-info/entry_points.txt +2 -0
- flowscript_vm-0.1.0.dist-info/licenses/LICENSE +21 -0
- flowscript_vm-0.1.0.dist-info/top_level.txt +1 -0
flowscript/vm.py
ADDED
|
@@ -0,0 +1,577 @@
|
|
|
1
|
+
"""Reference FlowScript virtual machine (Python).
|
|
2
|
+
|
|
3
|
+
Behaviour is normative: the C VM (c/flowscript.c) must produce identical traces.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import struct
|
|
7
|
+
|
|
8
|
+
from .bytecode import Program, BY_CODE, OPERAND_SIZE, EV_BOOT, EV_ENTER, EV_EXIT, FAULTS
|
|
9
|
+
from .intops import wrap, DivZero
|
|
10
|
+
from . import values as V
|
|
11
|
+
from .values import INT, FLOAT, STR, TIME, NONE_TYPE, Str, Time
|
|
12
|
+
|
|
13
|
+
STACK_SIZE = 64
|
|
14
|
+
MAX_FRAMES = 16
|
|
15
|
+
QUEUE_SIZE = 16
|
|
16
|
+
BUDGET = 10000
|
|
17
|
+
MAX_CHAIN = 256 # dispatches in a row without the VM becoming idle
|
|
18
|
+
STR_SLOTS = V.STR_SLOTS # run-time strings alive at once
|
|
19
|
+
STR_LEN = V.STR_LEN # bytes per run-time string
|
|
20
|
+
|
|
21
|
+
# step() results
|
|
22
|
+
IDLE = "idle" # nothing to do
|
|
23
|
+
DISPATCHED = "dispatch" # a handler was started (no instruction executed)
|
|
24
|
+
RUNNING = "running" # an instruction was executed, handler still active
|
|
25
|
+
DONE = "done" # an instruction was executed and the handler finished
|
|
26
|
+
FAULT = "fault" # the instruction faulted, handler aborted
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class Fault(Exception):
|
|
30
|
+
def __init__(self, code):
|
|
31
|
+
super().__init__(FAULTS.get(code, "fault"))
|
|
32
|
+
self.code = code
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class VM:
|
|
36
|
+
"""
|
|
37
|
+
externs: dict name -> callable(vm, args) -> value. Values are int, float, values.Str
|
|
38
|
+
(.b bytes, .text) and values.Time (.ms); a str extern may also return a
|
|
39
|
+
Python str/bytes. An extern declared without a return type may return anything.
|
|
40
|
+
clock: callable(vm) -> int ms since 1970 (UTC) for now(); default: always 0
|
|
41
|
+
on_emit: callable(vm, event_name, args) or None
|
|
42
|
+
on_fault: callable(vm, code, pc) or None
|
|
43
|
+
on_timer: callable(vm, timer, start, ms, periodic) or None. When set the host runs the
|
|
44
|
+
timers: the VM asks it to start/stop them and the host calls
|
|
45
|
+
timer_expired() when one fires; tick() then does nothing.
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
def __init__(self, prog, externs=None, on_emit=None, on_fault=None, on_timer=None,
|
|
49
|
+
stack_size=STACK_SIZE, max_frames=MAX_FRAMES, queue_size=QUEUE_SIZE, budget=BUDGET,
|
|
50
|
+
max_chain=MAX_CHAIN, str_slots=STR_SLOTS, str_len=STR_LEN, clock=None):
|
|
51
|
+
if isinstance(prog, (bytes, bytearray)):
|
|
52
|
+
prog = Program.from_bytes(prog)
|
|
53
|
+
self.prog = prog
|
|
54
|
+
self.code = prog.code
|
|
55
|
+
self.externs = externs or {}
|
|
56
|
+
self.on_emit = on_emit
|
|
57
|
+
self.on_fault = on_fault
|
|
58
|
+
self.on_timer = on_timer
|
|
59
|
+
self.stack_size = stack_size
|
|
60
|
+
self.max_frames = max_frames
|
|
61
|
+
self.queue_size = queue_size
|
|
62
|
+
self.budget = budget
|
|
63
|
+
self.max_chain = max_chain
|
|
64
|
+
self.str_slots = str_slots
|
|
65
|
+
self.str_len = str_len
|
|
66
|
+
self.clock = clock
|
|
67
|
+
self.bound = []
|
|
68
|
+
for ext in prog.externs:
|
|
69
|
+
self.bound.append(self.externs.get(ext[0]))
|
|
70
|
+
self.reset()
|
|
71
|
+
|
|
72
|
+
def unbound_externs(self):
|
|
73
|
+
return [self.prog.externs[i][0] for i, f in enumerate(self.bound) if f is None]
|
|
74
|
+
|
|
75
|
+
def reset(self):
|
|
76
|
+
for i, t in enumerate(getattr(self, "timers", [])):
|
|
77
|
+
if t[0] and self.on_timer:
|
|
78
|
+
self.on_timer(self, i, 0, 0, 0)
|
|
79
|
+
self.globals = list(self.prog.globals)
|
|
80
|
+
self.pool = [None] * self.str_slots # run-time strings: slot -> bytes, None = free
|
|
81
|
+
self.stack = []
|
|
82
|
+
self.frames = [] # [return_pc, base]
|
|
83
|
+
self.pc = 0
|
|
84
|
+
self.queue = [] # (event, args)
|
|
85
|
+
self.state = None # current state index or None
|
|
86
|
+
self.pending = None # requested goto_state target
|
|
87
|
+
self.transition = None # target being entered (after exit handler)
|
|
88
|
+
self.in_exit = False
|
|
89
|
+
self.handler = None # active handler tuple
|
|
90
|
+
self.steps = 0 # instructions executed in this handler
|
|
91
|
+
# active, periodic, period, remaining, owner (state whose handler started it, or None)
|
|
92
|
+
self.timers = [[False, False, 0, 0, None] for _ in self.prog.timers]
|
|
93
|
+
self.boot_pending = False
|
|
94
|
+
self.cur_pc = -1
|
|
95
|
+
self.faults = 0
|
|
96
|
+
self.chain = 0 # dispatches since the VM was last idle
|
|
97
|
+
|
|
98
|
+
# -- host API ------------------------------------------------------------------
|
|
99
|
+
def start(self):
|
|
100
|
+
"""Queue boot and the entry into the initial state."""
|
|
101
|
+
self.boot_pending = True
|
|
102
|
+
if self.prog.states:
|
|
103
|
+
self.pending = 0
|
|
104
|
+
return self
|
|
105
|
+
|
|
106
|
+
def post(self, event, *args):
|
|
107
|
+
"""Queue an event. Arguments are converted to the event's parameter types (an int
|
|
108
|
+
is accepted for a float, a Python str/bytes for a str). Returns False if the queue
|
|
109
|
+
or the string memory is full (a fault is reported). Raises TypeError for a wrong type."""
|
|
110
|
+
if isinstance(event, str):
|
|
111
|
+
idx = self.prog.event_index(event)
|
|
112
|
+
if idx < 0:
|
|
113
|
+
raise KeyError("unknown event %r" % event)
|
|
114
|
+
event = idx
|
|
115
|
+
pc = self.cur_pc if self.handler is not None else -1
|
|
116
|
+
if len(self.queue) >= self.queue_size:
|
|
117
|
+
self.report_fault(7, pc)
|
|
118
|
+
return False
|
|
119
|
+
_name, argc, ptypes = self.prog.events[event]
|
|
120
|
+
entry = (event, [0] * argc)
|
|
121
|
+
self.queue.append(entry) # a root while its strings are allocated
|
|
122
|
+
try:
|
|
123
|
+
for i in range(argc):
|
|
124
|
+
v = args[i] if i < len(args) else V.zero(ptypes[i])
|
|
125
|
+
entry[1][i] = self.host_value(v, ptypes[i])
|
|
126
|
+
except V.ValueFault as f:
|
|
127
|
+
self.queue.remove(entry)
|
|
128
|
+
if f.code == V.TYPE_MISMATCH:
|
|
129
|
+
raise TypeError("event %s: argument has the wrong type" % _name)
|
|
130
|
+
self.report_fault(f.code, pc)
|
|
131
|
+
return False
|
|
132
|
+
return True
|
|
133
|
+
|
|
134
|
+
def host_value(self, v, t):
|
|
135
|
+
"""A value from the host as type t (allocates a str). Raises ValueFault."""
|
|
136
|
+
if t == STR and isinstance(v, (str, bytes)):
|
|
137
|
+
return self.alloc(v.encode("utf-8") if isinstance(v, str) else v)
|
|
138
|
+
try:
|
|
139
|
+
vt = V.type_of(v)
|
|
140
|
+
except TypeError:
|
|
141
|
+
raise V.ValueFault(V.TYPE_MISMATCH)
|
|
142
|
+
if t == FLOAT and vt == INT:
|
|
143
|
+
return float(v)
|
|
144
|
+
if vt != t:
|
|
145
|
+
raise V.ValueFault(V.TYPE_MISMATCH)
|
|
146
|
+
if vt == INT:
|
|
147
|
+
return wrap(int(v))
|
|
148
|
+
if vt == STR and v.slot is not None and self.pool[v.slot] is None:
|
|
149
|
+
raise V.ValueFault(V.TYPE_MISMATCH) # a stale run-time string
|
|
150
|
+
return v
|
|
151
|
+
|
|
152
|
+
def set_global(self, index, value):
|
|
153
|
+
"""Debugger/host: set a global (converted to its current type)."""
|
|
154
|
+
self.globals[index] = self.host_value(value, V.type_of(self.globals[index]))
|
|
155
|
+
|
|
156
|
+
# -- strings -------------------------------------------------------------------------
|
|
157
|
+
def alloc(self, b):
|
|
158
|
+
"""A new run-time string. Raises ValueFault 12 (too long) or 11 (no free slot)."""
|
|
159
|
+
if len(b) > self.str_len:
|
|
160
|
+
raise V.ValueFault(V.STRING_TOO_LONG)
|
|
161
|
+
for attempt in (0, 1):
|
|
162
|
+
for i, used in enumerate(self.pool):
|
|
163
|
+
if used is None:
|
|
164
|
+
self.pool[i] = b
|
|
165
|
+
return Str(b, i)
|
|
166
|
+
if attempt == 0:
|
|
167
|
+
self.collect()
|
|
168
|
+
raise V.ValueFault(V.OUT_OF_STRINGS)
|
|
169
|
+
|
|
170
|
+
def collect(self):
|
|
171
|
+
"""Mark and sweep: free every slot not referenced by a global, the stack or the queue."""
|
|
172
|
+
live = set()
|
|
173
|
+
for v in self.globals + self.stack:
|
|
174
|
+
if isinstance(v, Str) and v.slot is not None:
|
|
175
|
+
live.add(v.slot)
|
|
176
|
+
for _ev, args in self.queue:
|
|
177
|
+
for v in args:
|
|
178
|
+
if isinstance(v, Str) and v.slot is not None:
|
|
179
|
+
live.add(v.slot)
|
|
180
|
+
for i in range(len(self.pool)):
|
|
181
|
+
if i not in live:
|
|
182
|
+
self.pool[i] = None
|
|
183
|
+
|
|
184
|
+
def strings_in_use(self):
|
|
185
|
+
return sum(1 for b in self.pool if b is not None)
|
|
186
|
+
|
|
187
|
+
def timer_index(self, timer):
|
|
188
|
+
if isinstance(timer, str):
|
|
189
|
+
for i, ev in enumerate(self.prog.timers):
|
|
190
|
+
if self.prog.events[ev][0] == timer:
|
|
191
|
+
return i
|
|
192
|
+
raise KeyError("unknown timer %r" % timer)
|
|
193
|
+
return timer
|
|
194
|
+
|
|
195
|
+
def timer_expired(self, timer):
|
|
196
|
+
"""Host-managed timers: the host's timer fired. Returns True if the event was queued."""
|
|
197
|
+
t = self.timers[self.timer_index(timer)]
|
|
198
|
+
if not t[0]:
|
|
199
|
+
return False # stopped by the script meanwhile: ignore
|
|
200
|
+
if not t[1]:
|
|
201
|
+
t[0] = False
|
|
202
|
+
return self.post(self.prog.timers[self.timer_index(timer)])
|
|
203
|
+
|
|
204
|
+
def stop_timer(self, i):
|
|
205
|
+
if self.timers[i][0]:
|
|
206
|
+
self.timers[i][0] = False
|
|
207
|
+
if self.on_timer:
|
|
208
|
+
self.on_timer(self, i, 0, 0, 0)
|
|
209
|
+
|
|
210
|
+
def tick(self, ms):
|
|
211
|
+
if self.on_timer:
|
|
212
|
+
return
|
|
213
|
+
for i, t in enumerate(self.timers):
|
|
214
|
+
if not t[0]:
|
|
215
|
+
continue
|
|
216
|
+
t[3] -= ms
|
|
217
|
+
if t[3] <= 0:
|
|
218
|
+
self.post(self.prog.timers[i])
|
|
219
|
+
if t[1]:
|
|
220
|
+
t[3] += t[2]
|
|
221
|
+
if t[3] <= 0:
|
|
222
|
+
t[3] = t[2]
|
|
223
|
+
else:
|
|
224
|
+
t[0] = False
|
|
225
|
+
|
|
226
|
+
def run(self, max_steps=1000000):
|
|
227
|
+
"""Run until idle. Returns number of steps."""
|
|
228
|
+
n = 0
|
|
229
|
+
while n < max_steps:
|
|
230
|
+
r = self.step()
|
|
231
|
+
if r == IDLE:
|
|
232
|
+
break
|
|
233
|
+
n += 1
|
|
234
|
+
return n
|
|
235
|
+
|
|
236
|
+
@property
|
|
237
|
+
def active(self):
|
|
238
|
+
return self.handler is not None
|
|
239
|
+
|
|
240
|
+
# -- dispatch --------------------------------------------------------------------
|
|
241
|
+
def begin(self, h, args):
|
|
242
|
+
_st, _ev, argc, nlocals, off = h
|
|
243
|
+
self.handler = h
|
|
244
|
+
self.stack = list(args[:argc]) + [0] * (argc - len(args))
|
|
245
|
+
self.stack += [0] * (nlocals - len(self.stack))
|
|
246
|
+
self.frames = [[-1, 0]]
|
|
247
|
+
self.pc = off
|
|
248
|
+
self.steps = 0
|
|
249
|
+
|
|
250
|
+
def dispatch(self):
|
|
251
|
+
"""Start the next handler. Returns True if one was started or a transition progressed."""
|
|
252
|
+
p = self.prog
|
|
253
|
+
if self.boot_pending:
|
|
254
|
+
self.boot_pending = False
|
|
255
|
+
h = p.find_handler(None, EV_BOOT)
|
|
256
|
+
if h is not None:
|
|
257
|
+
self.begin(h, [])
|
|
258
|
+
return True
|
|
259
|
+
if self.pending is not None:
|
|
260
|
+
self.transition = self.pending
|
|
261
|
+
self.pending = None
|
|
262
|
+
if self.state is not None:
|
|
263
|
+
h = p.find_handler(self.state, EV_EXIT)
|
|
264
|
+
if h is not None and h[0] == self.state:
|
|
265
|
+
self.in_exit = True
|
|
266
|
+
self.begin(h, [])
|
|
267
|
+
return True
|
|
268
|
+
if self.transition is not None:
|
|
269
|
+
if self.state is not None: # leaving: stop the old state's timers
|
|
270
|
+
for i, t in enumerate(self.timers):
|
|
271
|
+
if t[4] == self.state:
|
|
272
|
+
self.stop_timer(i)
|
|
273
|
+
self.state = self.transition
|
|
274
|
+
self.transition = None
|
|
275
|
+
h = p.find_handler(self.state, EV_ENTER)
|
|
276
|
+
if h is not None and h[0] == self.state:
|
|
277
|
+
self.begin(h, [])
|
|
278
|
+
return True
|
|
279
|
+
while self.queue:
|
|
280
|
+
ev, args = self.queue.pop(0)
|
|
281
|
+
h = p.find_handler(self.state, ev)
|
|
282
|
+
if h is not None:
|
|
283
|
+
self.begin(h, args)
|
|
284
|
+
return True
|
|
285
|
+
return False
|
|
286
|
+
|
|
287
|
+
def finish(self):
|
|
288
|
+
self.handler = None
|
|
289
|
+
self.frames = []
|
|
290
|
+
self.stack = []
|
|
291
|
+
self.in_exit = False
|
|
292
|
+
|
|
293
|
+
def report_fault(self, code, pc):
|
|
294
|
+
self.faults += 1
|
|
295
|
+
if self.on_fault:
|
|
296
|
+
self.on_fault(self, code, pc)
|
|
297
|
+
|
|
298
|
+
# -- execution -------------------------------------------------------------------
|
|
299
|
+
def has_work(self):
|
|
300
|
+
return (self.boot_pending or self.pending is not None or self.transition is not None
|
|
301
|
+
or bool(self.queue))
|
|
302
|
+
|
|
303
|
+
def step(self):
|
|
304
|
+
if self.handler is None:
|
|
305
|
+
if self.chain >= self.max_chain and self.has_work():
|
|
306
|
+
# the script keeps itself busy (goto_state/emit loop): drop the pending work
|
|
307
|
+
self.pending = self.transition = None
|
|
308
|
+
self.boot_pending = False
|
|
309
|
+
self.queue = []
|
|
310
|
+
self.chain = 0
|
|
311
|
+
self.report_fault(9, -1)
|
|
312
|
+
return IDLE
|
|
313
|
+
if self.dispatch():
|
|
314
|
+
self.chain += 1
|
|
315
|
+
return DISPATCHED
|
|
316
|
+
self.chain = 0
|
|
317
|
+
return IDLE
|
|
318
|
+
try:
|
|
319
|
+
return self.execute()
|
|
320
|
+
except Fault as f:
|
|
321
|
+
self.report_fault(f.code, self.cur_pc)
|
|
322
|
+
self.finish()
|
|
323
|
+
return FAULT
|
|
324
|
+
|
|
325
|
+
def pop(self):
|
|
326
|
+
if not self.stack or len(self.stack) <= 0:
|
|
327
|
+
raise Fault(2)
|
|
328
|
+
return self.stack.pop()
|
|
329
|
+
|
|
330
|
+
def push(self, v):
|
|
331
|
+
if len(self.stack) >= self.stack_size:
|
|
332
|
+
raise Fault(1)
|
|
333
|
+
self.stack.append(v)
|
|
334
|
+
|
|
335
|
+
def execute(self):
|
|
336
|
+
code = self.code
|
|
337
|
+
pc = self.pc
|
|
338
|
+
self.cur_pc = pc
|
|
339
|
+
self.steps += 1
|
|
340
|
+
if self.steps > self.budget:
|
|
341
|
+
raise Fault(4)
|
|
342
|
+
if pc >= len(code):
|
|
343
|
+
raise Fault(5)
|
|
344
|
+
op = code[pc]
|
|
345
|
+
ent = BY_CODE.get(op)
|
|
346
|
+
if ent is None:
|
|
347
|
+
raise Fault(5)
|
|
348
|
+
name, kind = ent
|
|
349
|
+
if pc + OPERAND_SIZE[kind] >= len(code): # operand cut off by end of code
|
|
350
|
+
raise Fault(5)
|
|
351
|
+
if kind in ("f64", "i64"):
|
|
352
|
+
arg = struct.unpack_from("<d" if kind == "f64" else "<q", code, pc + 1)[0]
|
|
353
|
+
npc = pc + 9
|
|
354
|
+
elif kind is None:
|
|
355
|
+
arg = None
|
|
356
|
+
npc = pc + 1
|
|
357
|
+
elif kind == "u16":
|
|
358
|
+
arg = code[pc + 1] | (code[pc + 2] << 8)
|
|
359
|
+
npc = pc + 3
|
|
360
|
+
elif kind == "u8":
|
|
361
|
+
arg = code[pc + 1]
|
|
362
|
+
npc = pc + 2
|
|
363
|
+
elif kind == "i8":
|
|
364
|
+
arg = struct.unpack_from("<b", code, pc + 1)[0]
|
|
365
|
+
npc = pc + 2
|
|
366
|
+
elif kind == "i32":
|
|
367
|
+
arg = struct.unpack_from("<i", code, pc + 1)[0]
|
|
368
|
+
npc = pc + 5
|
|
369
|
+
self.pc = npc
|
|
370
|
+
push, pop = self.push, self.pop
|
|
371
|
+
|
|
372
|
+
if name in ("PUSH", "PUSH8"):
|
|
373
|
+
push(arg)
|
|
374
|
+
elif name == "PUSHF":
|
|
375
|
+
push(arg)
|
|
376
|
+
elif name == "PUSHT":
|
|
377
|
+
push(Time(arg))
|
|
378
|
+
elif name == "PUSHS":
|
|
379
|
+
if arg >= len(self.prog.strings):
|
|
380
|
+
raise Fault(5)
|
|
381
|
+
push(Str(self.prog.strings[arg].encode("utf-8", "surrogateescape")))
|
|
382
|
+
elif name == "POP":
|
|
383
|
+
pop()
|
|
384
|
+
elif name == "DUP":
|
|
385
|
+
v = pop()
|
|
386
|
+
push(v)
|
|
387
|
+
push(v)
|
|
388
|
+
elif name == "LOADG":
|
|
389
|
+
if arg >= len(self.globals):
|
|
390
|
+
raise Fault(5)
|
|
391
|
+
push(self.globals[arg])
|
|
392
|
+
elif name == "STOREG":
|
|
393
|
+
if arg >= len(self.globals):
|
|
394
|
+
raise Fault(5)
|
|
395
|
+
self.globals[arg] = pop()
|
|
396
|
+
elif name == "LOADL":
|
|
397
|
+
i = self.frames[-1][1] + arg
|
|
398
|
+
if i >= len(self.stack):
|
|
399
|
+
raise Fault(5)
|
|
400
|
+
push(self.stack[i])
|
|
401
|
+
elif name == "STOREL":
|
|
402
|
+
v = pop()
|
|
403
|
+
i = self.frames[-1][1] + arg
|
|
404
|
+
if i >= len(self.stack):
|
|
405
|
+
raise Fault(5)
|
|
406
|
+
self.stack[i] = v
|
|
407
|
+
elif name in BINOPS:
|
|
408
|
+
if len(self.stack) < 2:
|
|
409
|
+
raise Fault(2)
|
|
410
|
+
a, b = self.stack[-2], self.stack[-1]
|
|
411
|
+
op = BINOPS[name]
|
|
412
|
+
ta, tb = V.type_of(a), V.type_of(b)
|
|
413
|
+
if op in ("&", "|", "^", "<<", ">>") and not (ta == tb == INT):
|
|
414
|
+
raise Fault(10)
|
|
415
|
+
if (name == "CONCAT") != (ta == STR and tb == STR and op == "+"):
|
|
416
|
+
raise Fault(10) # strings are joined by CONCAT, numbers by ADD
|
|
417
|
+
try:
|
|
418
|
+
r = V.binary(op, a, b)
|
|
419
|
+
except DivZero:
|
|
420
|
+
raise Fault(3)
|
|
421
|
+
except V.ValueFault as f:
|
|
422
|
+
raise Fault(f.code)
|
|
423
|
+
if isinstance(r, bytes):
|
|
424
|
+
r = self.alloc_or_fault(r) # the operands are still on the stack (roots)
|
|
425
|
+
del self.stack[-2:]
|
|
426
|
+
push(r)
|
|
427
|
+
elif name == "NEG":
|
|
428
|
+
v = pop()
|
|
429
|
+
t = V.type_of(v)
|
|
430
|
+
if t == INT:
|
|
431
|
+
push(wrap(-v))
|
|
432
|
+
elif t == FLOAT:
|
|
433
|
+
push(-v)
|
|
434
|
+
else:
|
|
435
|
+
raise Fault(10)
|
|
436
|
+
elif name == "BNOT":
|
|
437
|
+
push(wrap(~self.pop_int()))
|
|
438
|
+
elif name == "NOT":
|
|
439
|
+
push(0 if self.pop_int() else 1)
|
|
440
|
+
elif name == "BOOL":
|
|
441
|
+
push(1 if self.pop_int() else 0)
|
|
442
|
+
elif name == "JMP":
|
|
443
|
+
self.pc = arg
|
|
444
|
+
elif name == "JZ":
|
|
445
|
+
if self.pop_int() == 0:
|
|
446
|
+
self.pc = arg
|
|
447
|
+
elif name == "JNZ":
|
|
448
|
+
if self.pop_int() != 0:
|
|
449
|
+
self.pc = arg
|
|
450
|
+
elif name == "CALL":
|
|
451
|
+
if arg >= len(self.prog.funcs):
|
|
452
|
+
raise Fault(5)
|
|
453
|
+
_n, argc, nlocals, off = self.prog.funcs[arg]
|
|
454
|
+
if len(self.frames) >= self.max_frames:
|
|
455
|
+
raise Fault(6)
|
|
456
|
+
base = len(self.stack) - argc
|
|
457
|
+
if base < 0:
|
|
458
|
+
raise Fault(2)
|
|
459
|
+
for _ in range(nlocals - argc):
|
|
460
|
+
push(0)
|
|
461
|
+
self.frames.append([npc, base])
|
|
462
|
+
self.pc = off
|
|
463
|
+
elif name in ("RET", "RET0"):
|
|
464
|
+
v = pop() if name == "RET" else 0
|
|
465
|
+
ret_pc, base = self.frames.pop()
|
|
466
|
+
del self.stack[base:]
|
|
467
|
+
if not self.frames:
|
|
468
|
+
self.finish()
|
|
469
|
+
return DONE
|
|
470
|
+
self.pc = ret_pc
|
|
471
|
+
push(v)
|
|
472
|
+
elif name == "XCALL":
|
|
473
|
+
if arg >= len(self.bound):
|
|
474
|
+
raise Fault(5)
|
|
475
|
+
_xn, argc, ptypes, ret = self.prog.externs[arg]
|
|
476
|
+
if len(self.stack) < argc:
|
|
477
|
+
raise Fault(2)
|
|
478
|
+
args = self.stack[len(self.stack) - argc:] if argc else []
|
|
479
|
+
self.check_types(args, ptypes)
|
|
480
|
+
fn = self.bound[arg]
|
|
481
|
+
if fn is None:
|
|
482
|
+
raise Fault(8)
|
|
483
|
+
r = fn(self, list(args)) # the arguments stay on the stack (roots) meanwhile
|
|
484
|
+
if ret == NONE_TYPE:
|
|
485
|
+
r = 0
|
|
486
|
+
else:
|
|
487
|
+
try:
|
|
488
|
+
r = self.host_value(r, ret)
|
|
489
|
+
except V.ValueFault as f:
|
|
490
|
+
raise Fault(f.code)
|
|
491
|
+
del self.stack[len(self.stack) - argc:]
|
|
492
|
+
push(r)
|
|
493
|
+
elif name == "BUILTIN":
|
|
494
|
+
if arg >= len(V.BUILTINS):
|
|
495
|
+
raise Fault(5)
|
|
496
|
+
_bn, ptypes, _ret = V.BUILTINS[arg]
|
|
497
|
+
argc = len(ptypes)
|
|
498
|
+
if len(self.stack) < argc:
|
|
499
|
+
raise Fault(2)
|
|
500
|
+
args = self.stack[len(self.stack) - argc:] if argc else []
|
|
501
|
+
self.check_types(args, ptypes)
|
|
502
|
+
try:
|
|
503
|
+
r = V.call_builtin(arg, args, (lambda: self.clock(self)) if self.clock else None)
|
|
504
|
+
except V.ValueFault as f:
|
|
505
|
+
raise Fault(f.code)
|
|
506
|
+
if isinstance(r, bytes):
|
|
507
|
+
r = self.alloc_or_fault(r)
|
|
508
|
+
del self.stack[len(self.stack) - argc:]
|
|
509
|
+
push(r)
|
|
510
|
+
elif name == "GOTO":
|
|
511
|
+
if arg >= len(self.prog.states):
|
|
512
|
+
raise Fault(5)
|
|
513
|
+
if not self.in_exit:
|
|
514
|
+
self.pending = arg
|
|
515
|
+
self.finish()
|
|
516
|
+
return DONE
|
|
517
|
+
elif name == "EMIT":
|
|
518
|
+
if arg >= len(self.prog.events):
|
|
519
|
+
raise Fault(5)
|
|
520
|
+
_en, argc, ptypes = self.prog.events[arg]
|
|
521
|
+
if len(self.stack) < argc:
|
|
522
|
+
raise Fault(2)
|
|
523
|
+
args = list(self.stack[len(self.stack) - argc:]) if argc else []
|
|
524
|
+
self.check_types(args, ptypes)
|
|
525
|
+
if self.on_emit:
|
|
526
|
+
self.on_emit(self, self.prog.events[arg][0], list(args))
|
|
527
|
+
self.post(arg, *args)
|
|
528
|
+
del self.stack[len(self.stack) - argc:]
|
|
529
|
+
elif name in ("TSTART", "TEVERY"):
|
|
530
|
+
if arg >= len(self.timers):
|
|
531
|
+
raise Fault(5)
|
|
532
|
+
ms = self.pop_int()
|
|
533
|
+
t = self.timers[arg]
|
|
534
|
+
t[0] = True
|
|
535
|
+
t[1] = name == "TEVERY"
|
|
536
|
+
t[2] = ms
|
|
537
|
+
t[3] = ms
|
|
538
|
+
t[4] = self.handler[0]
|
|
539
|
+
if self.on_timer:
|
|
540
|
+
self.on_timer(self, arg, 1, ms, 1 if t[1] else 0)
|
|
541
|
+
elif name == "TSTOP":
|
|
542
|
+
if arg >= len(self.timers):
|
|
543
|
+
raise Fault(5)
|
|
544
|
+
self.stop_timer(arg)
|
|
545
|
+
elif name == "NOP":
|
|
546
|
+
pass
|
|
547
|
+
else:
|
|
548
|
+
raise Fault(5)
|
|
549
|
+
return RUNNING
|
|
550
|
+
|
|
551
|
+
def pop_int(self):
|
|
552
|
+
v = self.pop()
|
|
553
|
+
if V.type_of(v) != INT:
|
|
554
|
+
raise Fault(10)
|
|
555
|
+
return v
|
|
556
|
+
|
|
557
|
+
def check_types(self, args, ptypes):
|
|
558
|
+
for v, t in zip(args, ptypes):
|
|
559
|
+
if V.type_of(v) != t:
|
|
560
|
+
raise Fault(10)
|
|
561
|
+
|
|
562
|
+
def alloc_or_fault(self, b):
|
|
563
|
+
try:
|
|
564
|
+
return self.alloc(b)
|
|
565
|
+
except V.ValueFault as f:
|
|
566
|
+
raise Fault(f.code)
|
|
567
|
+
|
|
568
|
+
# -- introspection -----------------------------------------------------------------
|
|
569
|
+
def state_name(self):
|
|
570
|
+
return None if self.state is None else self.prog.states[self.state]
|
|
571
|
+
|
|
572
|
+
|
|
573
|
+
BINOPS = {
|
|
574
|
+
"ADD": "+", "CONCAT": "+", "SUB": "-", "MUL": "*", "DIV": "/", "MOD": "%",
|
|
575
|
+
"BAND": "&", "BOR": "|", "BXOR": "^", "SHL": "<<", "SHR": ">>",
|
|
576
|
+
"EQ": "==", "NE": "!=", "LT": "<", "LE": "<=", "GT": ">", "GE": ">=",
|
|
577
|
+
}
|