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/compiler.py
ADDED
|
@@ -0,0 +1,1019 @@
|
|
|
1
|
+
"""FlowScript compiler: AST -> Program (bytecode + debug info).
|
|
2
|
+
|
|
3
|
+
Statically typed: every expression has one of the types int, float, str or time
|
|
4
|
+
(values.py). An int is promoted to float where a float is expected; nothing else is
|
|
5
|
+
converted implicitly (use int(), float(), str(), time()).
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import struct
|
|
9
|
+
|
|
10
|
+
from .lexer import FlowError
|
|
11
|
+
from .parser import parse, COMPARE_OPS
|
|
12
|
+
from .bytecode import Program, OPCODES, BUILTIN_EVENTS, EV_BOOT, EV_ENTER, EV_EXIT
|
|
13
|
+
from . import intops
|
|
14
|
+
from . import values as V
|
|
15
|
+
from .values import INT, FLOAT, STR, TIME, TYPE_NAMES, TYPE_CODES, NONE_TYPE
|
|
16
|
+
|
|
17
|
+
VOID = None # the "type" of a call that returns nothing
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def tname(t):
|
|
21
|
+
return "nothing" if t is None else TYPE_NAMES[t]
|
|
22
|
+
|
|
23
|
+
MAX_EVENT_ARGS = 8
|
|
24
|
+
MAX_LOCALS = 32 # params + locals per routine; must stay well below the VM stack (64)
|
|
25
|
+
MAX_PARAMS = 16
|
|
26
|
+
|
|
27
|
+
BIN_OPS = {
|
|
28
|
+
"+": "ADD", "-": "SUB", "*": "MUL", "/": "DIV", "%": "MOD",
|
|
29
|
+
"&": "BAND", "|": "BOR", "^": "BXOR", "<<": "SHL", ">>": "SHR",
|
|
30
|
+
"==": "EQ", "!=": "NE", "<": "LT", "<=": "LE", ">": "GT", ">=": "GE",
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class DebugInfo:
|
|
35
|
+
"""Rich debug info kept in memory by the toolchain/debugger."""
|
|
36
|
+
|
|
37
|
+
def __init__(self):
|
|
38
|
+
self.pc_node = {} # pc -> node id
|
|
39
|
+
self.pc_line = {} # pc -> source line
|
|
40
|
+
self.stmt_of = {} # node id (expr or stmt) -> statement / handler node id
|
|
41
|
+
self.value_marks = {} # pc -> expr node id (TOS after executing pc is node's value)
|
|
42
|
+
self.stmt_starts = {} # pc -> statement id (first instruction of a statement)
|
|
43
|
+
self.routines = [] # dicts: start, end, kind, name, state, locals, node
|
|
44
|
+
self.node_lines = {} # node id -> line
|
|
45
|
+
self.warnings = [] # {"msg", "line", "col"}: compiled anyway
|
|
46
|
+
|
|
47
|
+
def routine_at(self, pc):
|
|
48
|
+
for r in self.routines:
|
|
49
|
+
if r["start"] <= pc < r["end"]:
|
|
50
|
+
return r
|
|
51
|
+
return None
|
|
52
|
+
|
|
53
|
+
def to_dict(self):
|
|
54
|
+
return {
|
|
55
|
+
"pc_node": self.pc_node, "pc_line": self.pc_line, "stmt_of": self.stmt_of,
|
|
56
|
+
"value_marks": self.value_marks, "stmt_starts": self.stmt_starts,
|
|
57
|
+
"routines": self.routines,
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class Routine:
|
|
62
|
+
def __init__(self, kind, name, params, types, returns=None):
|
|
63
|
+
self.kind = kind # "func" or "handler"
|
|
64
|
+
self.name = name
|
|
65
|
+
self.locals = list(params)
|
|
66
|
+
self.types = list(types) # type code per local slot
|
|
67
|
+
self.declared = set(params)
|
|
68
|
+
self.returns = returns # functions: return type code or None
|
|
69
|
+
|
|
70
|
+
def declare(self, name, t, node, comp):
|
|
71
|
+
if name in self.locals:
|
|
72
|
+
raise comp.err("'%s' is already declared in this %s" % (name, "function" if self.kind == "func" else "handler"), node)
|
|
73
|
+
if len(self.locals) >= MAX_LOCALS:
|
|
74
|
+
raise comp.err("too many local variables", node)
|
|
75
|
+
self.locals.append(name)
|
|
76
|
+
self.types.append(t)
|
|
77
|
+
return len(self.locals) - 1
|
|
78
|
+
|
|
79
|
+
def visible(self, name):
|
|
80
|
+
return name in self.declared
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
class Label:
|
|
84
|
+
__slots__ = ("pc", "refs")
|
|
85
|
+
|
|
86
|
+
def __init__(self):
|
|
87
|
+
self.pc = None
|
|
88
|
+
self.refs = []
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
class Compiler:
|
|
92
|
+
def __init__(self, ast):
|
|
93
|
+
self.ast = ast
|
|
94
|
+
self.prog = Program()
|
|
95
|
+
self.dbg = DebugInfo()
|
|
96
|
+
self.code = bytearray()
|
|
97
|
+
self.cur_node = None
|
|
98
|
+
self.cur_stmt = None
|
|
99
|
+
self.routine = None
|
|
100
|
+
# symbol tables
|
|
101
|
+
self.externs = {} # name -> (index, argc, param types, return type or None)
|
|
102
|
+
self.events = {} # name -> (index, argc, params, param types)
|
|
103
|
+
self.timers = {} # name -> (index, event index)
|
|
104
|
+
self.consts = {} # name -> value (int, float, Str, Time)
|
|
105
|
+
self.globals = {} # name -> index (type: self.global_types[index])
|
|
106
|
+
self.global_types = []
|
|
107
|
+
self.funcs = {} # name -> (index, argc, node, param types, return type or None)
|
|
108
|
+
self.states = {} # name -> index
|
|
109
|
+
self.strings = {}
|
|
110
|
+
self.any_state = None # the any_state item (handlers for every state)
|
|
111
|
+
self.state_vars = {} # state name -> {var name: global index}
|
|
112
|
+
self.cur_state = None # name of the state whose handler is being compiled
|
|
113
|
+
|
|
114
|
+
# -- errors / helpers ------------------------------------------------------
|
|
115
|
+
@staticmethod
|
|
116
|
+
def err(msg, node=None):
|
|
117
|
+
return FlowError(msg, node.get("line", 0) if node else 0, 0)
|
|
118
|
+
|
|
119
|
+
def define(self, name, kind, node):
|
|
120
|
+
for table, what in ((self.externs, "extern"), (self.events, "event"),
|
|
121
|
+
(self.timers, "timer"), (self.consts, "const"),
|
|
122
|
+
(self.globals, "var"), (self.funcs, "func"), (self.states, "state")):
|
|
123
|
+
if name in table:
|
|
124
|
+
raise self.err("'%s' is already declared as %s" % (name, what), node)
|
|
125
|
+
if name in BUILTIN_EVENTS:
|
|
126
|
+
raise self.err("'%s' is a built-in event name" % name, node)
|
|
127
|
+
|
|
128
|
+
def string(self, s):
|
|
129
|
+
if isinstance(s, bytes):
|
|
130
|
+
s = s.decode("utf-8", "surrogateescape")
|
|
131
|
+
if s not in self.strings:
|
|
132
|
+
self.strings[s] = len(self.prog.strings)
|
|
133
|
+
self.prog.strings.append(s)
|
|
134
|
+
return self.strings[s]
|
|
135
|
+
|
|
136
|
+
def type_code(self, name, node):
|
|
137
|
+
if name not in TYPE_CODES:
|
|
138
|
+
raise self.err("unknown type '%s'" % name, node)
|
|
139
|
+
return TYPE_CODES[name]
|
|
140
|
+
|
|
141
|
+
# -- emission ----------------------------------------------------------------
|
|
142
|
+
def emit(self, op, arg=None, node=None):
|
|
143
|
+
code, kind = OPCODES[op]
|
|
144
|
+
pc = len(self.code)
|
|
145
|
+
if pc > 0xFFFF:
|
|
146
|
+
raise self.err("program too large (code > 64 KiB)")
|
|
147
|
+
nd = node if node is not None else self.cur_node
|
|
148
|
+
self.code.append(code)
|
|
149
|
+
if kind == "i32":
|
|
150
|
+
self.code += struct.pack("<i", arg)
|
|
151
|
+
elif kind == "i8":
|
|
152
|
+
self.code += struct.pack("<b", arg)
|
|
153
|
+
elif kind == "u8":
|
|
154
|
+
self.code += struct.pack("<B", arg)
|
|
155
|
+
elif kind == "f64":
|
|
156
|
+
self.code += struct.pack("<d", arg)
|
|
157
|
+
elif kind == "i64":
|
|
158
|
+
self.code += struct.pack("<q", arg)
|
|
159
|
+
elif kind == "u16":
|
|
160
|
+
if isinstance(arg, Label):
|
|
161
|
+
arg.refs.append(pc + 1)
|
|
162
|
+
self.code += b"\0\0"
|
|
163
|
+
else:
|
|
164
|
+
self.code += struct.pack("<H", arg)
|
|
165
|
+
if nd is not None:
|
|
166
|
+
self.dbg.pc_node[pc] = nd["id"]
|
|
167
|
+
self.dbg.pc_line[pc] = nd.get("line", 0)
|
|
168
|
+
return pc
|
|
169
|
+
|
|
170
|
+
def place(self, label):
|
|
171
|
+
label.pc = len(self.code)
|
|
172
|
+
for ref in label.refs:
|
|
173
|
+
struct.pack_into("<H", self.code, ref, label.pc)
|
|
174
|
+
|
|
175
|
+
def finish_labels(self, labels):
|
|
176
|
+
for lb in labels:
|
|
177
|
+
for ref in lb.refs:
|
|
178
|
+
struct.pack_into("<H", self.code, ref, lb.pc)
|
|
179
|
+
|
|
180
|
+
def push_const(self, v, node=None):
|
|
181
|
+
"""Push a constant value; returns the pc of the push."""
|
|
182
|
+
t = V.type_of(v)
|
|
183
|
+
if t == INT:
|
|
184
|
+
v = intops.wrap(v)
|
|
185
|
+
return self.emit("PUSH8" if -128 <= v <= 127 else "PUSH", v, node)
|
|
186
|
+
if t == FLOAT:
|
|
187
|
+
if v == int(v) if v == v and abs(v) < 2 ** 31 else False:
|
|
188
|
+
if not (v == 0 and str(v).startswith("-")): # keep -0.0 exact
|
|
189
|
+
iv = int(v)
|
|
190
|
+
self.emit("PUSH8" if -128 <= iv <= 127 else "PUSH", iv, node)
|
|
191
|
+
return self.emit("BUILTIN", 0, node) # float(int): 4-7 bytes instead of 9
|
|
192
|
+
return self.emit("PUSHF", v, node)
|
|
193
|
+
if t == STR:
|
|
194
|
+
return self.emit("PUSHS", self.string(v.b), node)
|
|
195
|
+
return self.emit("PUSHT", v.ms, node)
|
|
196
|
+
|
|
197
|
+
def convert_const(self, v, want, node, what="value"):
|
|
198
|
+
"""A constant converted to type `want` (int -> float only)."""
|
|
199
|
+
t = V.type_of(v)
|
|
200
|
+
if want is None or t == want:
|
|
201
|
+
return v
|
|
202
|
+
if t == INT and want == FLOAT:
|
|
203
|
+
return float(v)
|
|
204
|
+
raise self.err("%s is %s, expected %s" % (what, TYPE_NAMES[t], TYPE_NAMES[want]), node)
|
|
205
|
+
|
|
206
|
+
# -- declarations -------------------------------------------------------------
|
|
207
|
+
def collect(self):
|
|
208
|
+
p = self.prog
|
|
209
|
+
p.name = self.ast.get("name")
|
|
210
|
+
p.version = int(self.ast.get("version") or 0)
|
|
211
|
+
for i, n in enumerate(BUILTIN_EVENTS):
|
|
212
|
+
p.events.append((n, 0, ()))
|
|
213
|
+
for item in self.ast["items"]:
|
|
214
|
+
t = item["type"]
|
|
215
|
+
if t == "comment":
|
|
216
|
+
continue
|
|
217
|
+
if t == "extern":
|
|
218
|
+
self.define(item["name"], t, item)
|
|
219
|
+
self.check_params(item["params"], item)
|
|
220
|
+
ptypes = self.param_types(item)
|
|
221
|
+
ret = self.type_code(item["returns"], item) if item.get("returns") else None
|
|
222
|
+
self.externs[item["name"]] = (len(p.externs), len(item["params"]), ptypes, ret)
|
|
223
|
+
p.externs.append((item["name"], len(item["params"]), tuple(ptypes),
|
|
224
|
+
NONE_TYPE if ret is None else ret))
|
|
225
|
+
elif t == "event":
|
|
226
|
+
self.define(item["name"], t, item)
|
|
227
|
+
params = item.get("params") or []
|
|
228
|
+
self.check_params(params, item)
|
|
229
|
+
if len(params) > MAX_EVENT_ARGS:
|
|
230
|
+
raise self.err("events can have at most %d parameters" % MAX_EVENT_ARGS, item)
|
|
231
|
+
ptypes = self.param_types(item)
|
|
232
|
+
self.events[item["name"]] = (len(p.events), len(params), params, ptypes)
|
|
233
|
+
p.events.append((item["name"], len(params), tuple(ptypes)))
|
|
234
|
+
elif t == "timer":
|
|
235
|
+
self.define(item["name"], t, item)
|
|
236
|
+
ev = len(p.events)
|
|
237
|
+
p.events.append((item["name"], 0, ()))
|
|
238
|
+
self.timers[item["name"]] = (len(p.timers), ev)
|
|
239
|
+
p.timers.append(ev)
|
|
240
|
+
elif t == "const":
|
|
241
|
+
self.define(item["name"], t, item)
|
|
242
|
+
self.consts[item["name"]] = self.const_value(item["value"])
|
|
243
|
+
elif t == "var":
|
|
244
|
+
self.define(item["name"], t, item)
|
|
245
|
+
self.globals[item["name"]] = len(p.globals)
|
|
246
|
+
p.globals.append(self.initial_value(item))
|
|
247
|
+
self.global_types.append(self.type_code(item.get("vtype") or "int", item))
|
|
248
|
+
p.global_names.append(item["name"])
|
|
249
|
+
elif t == "func":
|
|
250
|
+
self.define(item["name"], t, item)
|
|
251
|
+
self.check_params(item["params"], item)
|
|
252
|
+
ret = self.type_code(item["returns"], item) if item.get("returns") else None
|
|
253
|
+
self.funcs[item["name"]] = (len(self.funcs), len(item["params"]), item,
|
|
254
|
+
self.param_types(item), ret)
|
|
255
|
+
elif t == "state":
|
|
256
|
+
self.define(item["name"], t, item)
|
|
257
|
+
self.states[item["name"]] = None # numbered below: the initial state is 0
|
|
258
|
+
elif t == "any_state":
|
|
259
|
+
if self.any_state is not None:
|
|
260
|
+
raise self.err("only one 'any_state' block is allowed (first one at line %d)"
|
|
261
|
+
% self.any_state["line"], item)
|
|
262
|
+
self.any_state = item
|
|
263
|
+
else:
|
|
264
|
+
raise self.err("unknown declaration type '%s'" % t, item)
|
|
265
|
+
# the initial state gets index 0, the VM's start state; the rest keep source order
|
|
266
|
+
states = [i for i in self.ast["items"] if i["type"] == "state"]
|
|
267
|
+
marked = [s for s in states if s.get("initial")]
|
|
268
|
+
if len(marked) > 1:
|
|
269
|
+
raise self.err("only one state can be 'initial' (already: '%s')" % marked[0]["name"],
|
|
270
|
+
marked[1])
|
|
271
|
+
if not marked and len(states) > 1:
|
|
272
|
+
raise self.err("mark the state the program starts in: 'initial state %s:'"
|
|
273
|
+
% states[0]["name"], states[0])
|
|
274
|
+
first = marked[0] if marked else (states[0] if states else None)
|
|
275
|
+
for s in ([first] if first else []) + [s for s in states if s is not first]:
|
|
276
|
+
self.states[s["name"]] = len(p.states)
|
|
277
|
+
p.states.append(s["name"])
|
|
278
|
+
# state variables: after the top level, so any name clash is known
|
|
279
|
+
for item in self.ast["items"]:
|
|
280
|
+
if item["type"] != "state":
|
|
281
|
+
continue
|
|
282
|
+
own = self.state_vars[item["name"]] = {}
|
|
283
|
+
for v in item.get("vars") or []:
|
|
284
|
+
if v["type"] != "var":
|
|
285
|
+
continue
|
|
286
|
+
self.define(v["name"], "var", v)
|
|
287
|
+
if v["name"] in own:
|
|
288
|
+
raise self.err("'%s' is already declared in state '%s'"
|
|
289
|
+
% (v["name"], item["name"]), v)
|
|
290
|
+
own[v["name"]] = len(p.globals)
|
|
291
|
+
p.globals.append(self.initial_value(v))
|
|
292
|
+
self.global_types.append(self.type_code(v.get("vtype") or "int", v))
|
|
293
|
+
p.global_names.append("%s.%s" % (item["name"], v["name"]))
|
|
294
|
+
|
|
295
|
+
def global_index(self, name, node=None):
|
|
296
|
+
"""Slot of a state variable of the current state, else of a global, else None.
|
|
297
|
+
|
|
298
|
+
Functions see no variables at all: data comes in as parameters and goes out
|
|
299
|
+
with 'return' (or 'raise_event'), so only event handlers change program state.
|
|
300
|
+
"""
|
|
301
|
+
own = self.state_vars.get(self.cur_state) or {}
|
|
302
|
+
if name in own:
|
|
303
|
+
return own[name]
|
|
304
|
+
if name in self.globals and self.routine.kind == "func":
|
|
305
|
+
raise self.err("functions cannot use global variables: pass '%s' in as a parameter, "
|
|
306
|
+
"or return a value and let the event handler store it" % name, node)
|
|
307
|
+
return self.globals.get(name)
|
|
308
|
+
|
|
309
|
+
def var_type(self, name, node):
|
|
310
|
+
"""Type of a readable variable/constant name, else raise."""
|
|
311
|
+
r = self.routine
|
|
312
|
+
if name in r.locals and r.visible(name):
|
|
313
|
+
return r.types[r.locals.index(name)]
|
|
314
|
+
gi = self.global_index(name, node)
|
|
315
|
+
if gi is not None:
|
|
316
|
+
return self.global_types[gi]
|
|
317
|
+
if name in self.consts:
|
|
318
|
+
return V.type_of(self.consts[name])
|
|
319
|
+
if name in self.states or name in self.events or name in self.funcs or name in self.externs:
|
|
320
|
+
raise self.err("'%s' is not a value" % name, node)
|
|
321
|
+
raise self.other_state_var(name, node) or self.err("unknown name '%s'" % name, node)
|
|
322
|
+
|
|
323
|
+
def other_state_var(self, name, node):
|
|
324
|
+
owners = [st for st, vs in self.state_vars.items() if name in vs]
|
|
325
|
+
if not owners:
|
|
326
|
+
return None
|
|
327
|
+
return self.err("'%s' is a variable of state %s, only visible in %s handlers"
|
|
328
|
+
% (name, " and ".join("'%s'" % o for o in owners),
|
|
329
|
+
"that state's" if len(owners) == 1 else "those states'"), node)
|
|
330
|
+
|
|
331
|
+
def param_types(self, item):
|
|
332
|
+
types = item.get("ptypes")
|
|
333
|
+
if types is None: # written without types: int (also the AST of older tools)
|
|
334
|
+
types = ["int"] * len(item.get("params") or [])
|
|
335
|
+
return [self.type_code(t, item) for t in types]
|
|
336
|
+
|
|
337
|
+
def initial_value(self, item):
|
|
338
|
+
t = self.type_code(item.get("vtype") or "int", item)
|
|
339
|
+
if item.get("value") is None:
|
|
340
|
+
return V.zero(t)
|
|
341
|
+
return self.convert_const(self.const_value(item["value"]), t, item["value"],
|
|
342
|
+
"the initial value of '%s'" % item["name"])
|
|
343
|
+
|
|
344
|
+
def check_params(self, params, node):
|
|
345
|
+
if len(params) > MAX_PARAMS:
|
|
346
|
+
raise self.err("too many parameters (max %d)" % MAX_PARAMS, node)
|
|
347
|
+
seen = set()
|
|
348
|
+
for prm in params:
|
|
349
|
+
if prm in seen:
|
|
350
|
+
raise self.err("duplicate parameter '%s'" % prm, node)
|
|
351
|
+
seen.add(prm)
|
|
352
|
+
|
|
353
|
+
def const_value(self, e):
|
|
354
|
+
v = self.try_fold(e)
|
|
355
|
+
if v is None:
|
|
356
|
+
raise self.err("value must be a constant expression", e)
|
|
357
|
+
return v
|
|
358
|
+
|
|
359
|
+
def try_fold(self, e):
|
|
360
|
+
"""Evaluate a constant expression (a typed value), or return None."""
|
|
361
|
+
t = e["type"]
|
|
362
|
+
if t == "num":
|
|
363
|
+
v = e["value"]
|
|
364
|
+
return v if isinstance(v, float) else intops.wrap(v)
|
|
365
|
+
if t == "bool":
|
|
366
|
+
return intops.wrap(e["value"])
|
|
367
|
+
if t == "str":
|
|
368
|
+
return V.Str(e["value"])
|
|
369
|
+
if t == "name":
|
|
370
|
+
if self.routine and e["name"] in self.routine.locals:
|
|
371
|
+
return None
|
|
372
|
+
return self.consts.get(e["name"])
|
|
373
|
+
if t == "unary":
|
|
374
|
+
v = self.try_fold(e["operand"])
|
|
375
|
+
if v is None:
|
|
376
|
+
return None
|
|
377
|
+
vt = V.type_of(v)
|
|
378
|
+
if e["op"] == "-" and vt == FLOAT:
|
|
379
|
+
return -v
|
|
380
|
+
if vt != INT:
|
|
381
|
+
return None # a type error: reported when compiled
|
|
382
|
+
return intops.UNARY[e["op"]](v)
|
|
383
|
+
if t == "binary":
|
|
384
|
+
a = self.try_fold(e["left"])
|
|
385
|
+
if a is None:
|
|
386
|
+
return None
|
|
387
|
+
b = self.try_fold(e["right"])
|
|
388
|
+
if b is None:
|
|
389
|
+
return None
|
|
390
|
+
op = e["op"]
|
|
391
|
+
ta, tb = V.type_of(a), V.type_of(b)
|
|
392
|
+
if op in ("and", "or", "xor"):
|
|
393
|
+
return intops.BINARY[op](a, b) if ta == tb == INT else None
|
|
394
|
+
rt = V.binary_type(op, ta, tb)
|
|
395
|
+
if rt is None:
|
|
396
|
+
return None
|
|
397
|
+
if rt[1]:
|
|
398
|
+
a = float(a)
|
|
399
|
+
if rt[2]:
|
|
400
|
+
b = float(b)
|
|
401
|
+
try:
|
|
402
|
+
r = V.binary(op, a, b)
|
|
403
|
+
except intops.DivZero:
|
|
404
|
+
raise self.err("division by zero in constant expression", e)
|
|
405
|
+
return V.Str(r) if isinstance(r, bytes) else r
|
|
406
|
+
if t == "callx" and e["name"] not in self.funcs and e["name"] not in self.externs:
|
|
407
|
+
args = []
|
|
408
|
+
for a in e.get("args") or []:
|
|
409
|
+
v = self.try_fold(a)
|
|
410
|
+
if v is None:
|
|
411
|
+
return None
|
|
412
|
+
args.append(v)
|
|
413
|
+
hit = self.resolve_builtin(e["name"], [V.type_of(a) for a in args], e, quiet=True)
|
|
414
|
+
if hit is None:
|
|
415
|
+
return None
|
|
416
|
+
idx, ptypes, _ret = hit
|
|
417
|
+
if idx == "identity":
|
|
418
|
+
return args[0]
|
|
419
|
+
if V.BUILTINS[idx][0] in V.IMPURE:
|
|
420
|
+
return None
|
|
421
|
+
args = [float(a) if pt == FLOAT and V.type_of(a) == INT else a for a, pt in zip(args, ptypes)]
|
|
422
|
+
try:
|
|
423
|
+
r = V.call_builtin(idx, args)
|
|
424
|
+
except V.ValueFault:
|
|
425
|
+
raise self.err("invalid value in constant expression '%s'" % e["name"], e)
|
|
426
|
+
return V.Str(r) if isinstance(r, bytes) else r
|
|
427
|
+
return None
|
|
428
|
+
|
|
429
|
+
# -- program -------------------------------------------------------------------
|
|
430
|
+
def compile(self):
|
|
431
|
+
self.collect()
|
|
432
|
+
func_offsets = {}
|
|
433
|
+
seen_handlers = set()
|
|
434
|
+
for item in self.ast["items"]:
|
|
435
|
+
t = item["type"]
|
|
436
|
+
if t == "func":
|
|
437
|
+
func_offsets[item["name"]] = self.compile_func(item)
|
|
438
|
+
elif t == "any_state":
|
|
439
|
+
for h in item["handlers"]:
|
|
440
|
+
if h["type"] != "comment":
|
|
441
|
+
self.compile_handler(h, None, seen_handlers)
|
|
442
|
+
elif t == "state":
|
|
443
|
+
for h in item["handlers"]:
|
|
444
|
+
if h["type"] == "comment":
|
|
445
|
+
continue
|
|
446
|
+
self.compile_handler(h, item, seen_handlers)
|
|
447
|
+
if self.state_vars[item["name"]] and \
|
|
448
|
+
(item["name"], EV_ENTER) not in seen_handlers:
|
|
449
|
+
self.compile_reset_handler(item)
|
|
450
|
+
self.shadow_warnings()
|
|
451
|
+
self.magic_number_warnings()
|
|
452
|
+
self.dbg.warnings.sort(key=lambda w: w["line"])
|
|
453
|
+
p = self.prog
|
|
454
|
+
p.funcs = [None] * len(self.funcs)
|
|
455
|
+
for name, (idx, argc, node, _pt, _ret) in self.funcs.items():
|
|
456
|
+
nlocals = node["_nlocals"]
|
|
457
|
+
p.funcs[idx] = (name, argc, nlocals, func_offsets[name])
|
|
458
|
+
del node["_nlocals"]
|
|
459
|
+
p.code = bytes(self.code)
|
|
460
|
+
p.lines = [(pc, self.dbg.pc_line.get(pc, 0), nid) for pc, nid in sorted(self.dbg.pc_node.items())]
|
|
461
|
+
return p
|
|
462
|
+
|
|
463
|
+
def shadow_warnings(self):
|
|
464
|
+
"""A state handler silently replaces the any_state handler for the same event."""
|
|
465
|
+
if self.any_state is None:
|
|
466
|
+
return
|
|
467
|
+
anywhere = {h["event"]: h for h in self.any_state["handlers"] if h["type"] == "on"}
|
|
468
|
+
for item in self.ast["items"]:
|
|
469
|
+
if item["type"] != "state":
|
|
470
|
+
continue
|
|
471
|
+
for h in item["handlers"]:
|
|
472
|
+
if h["type"] == "on" and h["event"] in anywhere:
|
|
473
|
+
self.dbg.warnings.append({
|
|
474
|
+
"msg": "in state '%s', 'on %s' replaces the any_state handler (line %d), "
|
|
475
|
+
"which then does not run in this state"
|
|
476
|
+
% (item["name"], h["event"], anywhere[h["event"]]["line"]),
|
|
477
|
+
"line": h["line"], "col": 1})
|
|
478
|
+
|
|
479
|
+
MAGIC_OK = (-1, 0, 1) # idioms, not magic: n > 0, x == 1, i != -1
|
|
480
|
+
|
|
481
|
+
def magic_number_warnings(self):
|
|
482
|
+
"""A literal number compared directly hides its meaning: suggest a named const."""
|
|
483
|
+
from .printer import expr_to_str
|
|
484
|
+
|
|
485
|
+
def walk(o):
|
|
486
|
+
if isinstance(o, list):
|
|
487
|
+
for x in o:
|
|
488
|
+
walk(x)
|
|
489
|
+
return
|
|
490
|
+
if not isinstance(o, dict):
|
|
491
|
+
return
|
|
492
|
+
if o.get("type") == "binary" and o["op"] in COMPARE_OPS:
|
|
493
|
+
for side in (o["left"], o["right"]):
|
|
494
|
+
if side["type"] == "num" and side["value"] not in self.MAGIC_OK:
|
|
495
|
+
lit = expr_to_str(side)
|
|
496
|
+
self.dbg.warnings.append({
|
|
497
|
+
"msg": "magic number %s in '%s': name it, e.g. 'const NAME = %s'"
|
|
498
|
+
% (lit, expr_to_str(o), lit),
|
|
499
|
+
"line": side.get("line") or o.get("line", 0), "col": 1})
|
|
500
|
+
for k, v in o.items():
|
|
501
|
+
if k != "note" and isinstance(v, (dict, list)):
|
|
502
|
+
walk(v)
|
|
503
|
+
|
|
504
|
+
for item in self.ast["items"]:
|
|
505
|
+
if item["type"] == "func":
|
|
506
|
+
walk(item["body"])
|
|
507
|
+
elif item["type"] in ("state", "any_state"):
|
|
508
|
+
walk(item["handlers"])
|
|
509
|
+
|
|
510
|
+
def begin_routine(self, kind, name, params, types, node, state=None, returns=None):
|
|
511
|
+
self.routine = Routine(kind, name, params, types, returns)
|
|
512
|
+
self.cur_stmt = node
|
|
513
|
+
self.cur_node = node
|
|
514
|
+
self.dbg.stmt_of[node["id"]] = node["id"]
|
|
515
|
+
return len(self.code)
|
|
516
|
+
|
|
517
|
+
def predeclare(self, body, node):
|
|
518
|
+
"""Locals are routine-scoped and start at their type's zero value: give every
|
|
519
|
+
declaration in the body its slot now, and initialise the non-int ones (a stack slot
|
|
520
|
+
starts as int 0), so a local declared in a branch that did not run still has a value
|
|
521
|
+
of its type."""
|
|
522
|
+
found = []
|
|
523
|
+
|
|
524
|
+
def walk(stmts, nested):
|
|
525
|
+
for st in stmts or []:
|
|
526
|
+
if st["type"] == "local":
|
|
527
|
+
found.append((st, nested))
|
|
528
|
+
elif st["type"] == "if":
|
|
529
|
+
for br in st["branches"]:
|
|
530
|
+
walk(br["body"], True)
|
|
531
|
+
walk(st.get("else_body"), True)
|
|
532
|
+
elif st["type"] == "while":
|
|
533
|
+
walk(st["body"], True)
|
|
534
|
+
walk(body, False)
|
|
535
|
+
for st, nested in found:
|
|
536
|
+
if st["name"] in (self.state_vars.get(self.cur_state) or {}):
|
|
537
|
+
raise self.err("local '%s' would hide the state variable of the same name" % st["name"], st)
|
|
538
|
+
if st["name"] in self.globals or st["name"] in self.consts:
|
|
539
|
+
raise self.err("local '%s' would hide a global of the same name" % st["name"], st)
|
|
540
|
+
t = self.type_code(st.get("vtype") or "int", st)
|
|
541
|
+
idx = self.routine.declare(st["name"], t, st, self)
|
|
542
|
+
# a declaration at the top of the body runs before any code that can see it
|
|
543
|
+
if t != INT and nested:
|
|
544
|
+
self.push_const(V.zero(t), node)
|
|
545
|
+
self.emit("STOREL", idx, node)
|
|
546
|
+
|
|
547
|
+
def end_routine(self, start, node, state=None):
|
|
548
|
+
self.dbg.routines.append({
|
|
549
|
+
"start": start, "end": len(self.code), "kind": self.routine.kind,
|
|
550
|
+
"name": self.routine.name, "state": state, "locals": list(self.routine.locals),
|
|
551
|
+
"types": [TYPE_NAMES[t] for t in self.routine.types], "node": node["id"],
|
|
552
|
+
})
|
|
553
|
+
n = len(self.routine.locals)
|
|
554
|
+
self.routine = None
|
|
555
|
+
return n
|
|
556
|
+
|
|
557
|
+
def compile_func(self, f):
|
|
558
|
+
_idx, _argc, _node, ptypes, ret = self.funcs[f["name"]]
|
|
559
|
+
start = self.begin_routine("func", f["name"], f["params"], ptypes, f, returns=ret)
|
|
560
|
+
self.predeclare(f["body"], f)
|
|
561
|
+
self.block(f["body"])
|
|
562
|
+
if not self.ends_flow(f["body"]):
|
|
563
|
+
if ret is None or ret == INT:
|
|
564
|
+
self.emit("RET0", node=f)
|
|
565
|
+
else: # falling off the end returns the zero value of the type
|
|
566
|
+
self.push_const(V.zero(ret), f)
|
|
567
|
+
self.emit("RET", node=f)
|
|
568
|
+
f["_nlocals"] = self.end_routine(start, f)
|
|
569
|
+
return start
|
|
570
|
+
|
|
571
|
+
def compile_handler(self, h, state, seen):
|
|
572
|
+
ev = h["event"]
|
|
573
|
+
sname = state["name"] if state else None
|
|
574
|
+
if ev in ("enter", "exit"):
|
|
575
|
+
if state is None:
|
|
576
|
+
raise self.err("'on %s' is only allowed inside a state (any_state is never "
|
|
577
|
+
"entered or left)" % ev, h)
|
|
578
|
+
evi, argc, eparams, etypes = BUILTIN_EVENTS.index(ev), 0, [], []
|
|
579
|
+
elif ev == "boot":
|
|
580
|
+
if state is not None:
|
|
581
|
+
raise self.err("'on boot' belongs in any_state", h)
|
|
582
|
+
evi, argc, eparams, etypes = EV_BOOT, 0, [], []
|
|
583
|
+
elif ev in self.events:
|
|
584
|
+
evi, argc, eparams, etypes = self.events[ev]
|
|
585
|
+
elif ev in self.timers:
|
|
586
|
+
evi, argc, eparams, etypes = self.timers[ev][1], 0, [], []
|
|
587
|
+
else:
|
|
588
|
+
raise self.err("unknown event '%s' (declare it with 'event %s')" % (ev, ev), h)
|
|
589
|
+
key = (sname, evi)
|
|
590
|
+
if key in seen:
|
|
591
|
+
raise self.err("duplicate handler for '%s'%s" % (ev, (" in state " + sname) if sname else ""), h)
|
|
592
|
+
seen.add(key)
|
|
593
|
+
params = h.get("params")
|
|
594
|
+
if params is None:
|
|
595
|
+
params = list(eparams)
|
|
596
|
+
self.check_params(params, h)
|
|
597
|
+
for prm in params:
|
|
598
|
+
if prm in (self.state_vars.get(sname) or {}):
|
|
599
|
+
raise self.err("parameter '%s' would hide the state variable of the same name" % prm, h)
|
|
600
|
+
if len(params) > argc:
|
|
601
|
+
raise self.err("event '%s' has only %d parameter(s)" % (ev, argc), h)
|
|
602
|
+
label = ("%s.on %s" % (sname, ev)) if sname else ("on " + ev)
|
|
603
|
+
start = self.begin_routine("handler", label, params, etypes[:len(params)], h, sname)
|
|
604
|
+
# pad locals so all event args fit (unnamed ones get hidden names)
|
|
605
|
+
while len(self.routine.locals) < argc:
|
|
606
|
+
self.routine.types.append(etypes[len(self.routine.locals)])
|
|
607
|
+
self.routine.locals.append("$arg%d" % len(self.routine.locals))
|
|
608
|
+
self.cur_state = sname
|
|
609
|
+
self.predeclare(h["body"], h)
|
|
610
|
+
if ev == "enter":
|
|
611
|
+
self.reset_state_vars(state)
|
|
612
|
+
self.block(h["body"])
|
|
613
|
+
self.cur_state = None
|
|
614
|
+
if not self.ends_flow(h["body"]):
|
|
615
|
+
self.emit("RET0", node=h)
|
|
616
|
+
nlocals = self.end_routine(start, h, sname)
|
|
617
|
+
self.prog.handlers.append((self.states[sname] if sname else None, evi, argc, nlocals, start))
|
|
618
|
+
|
|
619
|
+
def reset_state_vars(self, state):
|
|
620
|
+
"""Entering a state sets its variables back to their initial values."""
|
|
621
|
+
for v in state.get("vars") or []:
|
|
622
|
+
if v["type"] != "var":
|
|
623
|
+
continue
|
|
624
|
+
saved = (self.cur_stmt, self.cur_node)
|
|
625
|
+
self.cur_stmt = self.cur_node = v
|
|
626
|
+
self.dbg.stmt_of[v["id"]] = v["id"]
|
|
627
|
+
self.dbg.node_lines[v["id"]] = v.get("line", 0)
|
|
628
|
+
self.dbg.stmt_starts[len(self.code)] = v["id"]
|
|
629
|
+
idx = self.state_vars[state["name"]][v["name"]]
|
|
630
|
+
if v.get("value") is not None:
|
|
631
|
+
self.track(v["value"])
|
|
632
|
+
self.track_subtree(v["value"])
|
|
633
|
+
self.push_const(self.prog.globals[idx], v)
|
|
634
|
+
self.emit("STOREG", idx, v)
|
|
635
|
+
self.cur_stmt, self.cur_node = saved
|
|
636
|
+
|
|
637
|
+
def compile_reset_handler(self, state):
|
|
638
|
+
"""A state with variables but no 'on enter' gets one that only resets them."""
|
|
639
|
+
sname = state["name"]
|
|
640
|
+
first = next(v for v in state["vars"] if v["type"] == "var")
|
|
641
|
+
start = self.begin_routine("handler", "%s.on enter" % sname, [], [], first, sname)
|
|
642
|
+
self.reset_state_vars(state)
|
|
643
|
+
self.emit("RET0", node=first)
|
|
644
|
+
nlocals = self.end_routine(start, first, sname)
|
|
645
|
+
self.prog.handlers.append((self.states[sname], EV_ENTER, 0, nlocals, start))
|
|
646
|
+
|
|
647
|
+
@staticmethod
|
|
648
|
+
def ends_flow(body):
|
|
649
|
+
real = [s for s in body if s["type"] != "comment"]
|
|
650
|
+
return bool(real) and real[-1]["type"] in ("return", "goto")
|
|
651
|
+
|
|
652
|
+
# -- statements ------------------------------------------------------------------
|
|
653
|
+
def block(self, body):
|
|
654
|
+
for s in body or []:
|
|
655
|
+
self.stmt(s)
|
|
656
|
+
|
|
657
|
+
def stmt(self, s):
|
|
658
|
+
t = s["type"]
|
|
659
|
+
if t == "comment":
|
|
660
|
+
return
|
|
661
|
+
saved = (self.cur_stmt, self.cur_node)
|
|
662
|
+
self.cur_stmt = s
|
|
663
|
+
self.cur_node = s
|
|
664
|
+
self.dbg.stmt_of[s["id"]] = s["id"]
|
|
665
|
+
self.dbg.node_lines[s["id"]] = s.get("line", 0)
|
|
666
|
+
self.dbg.stmt_starts[len(self.code)] = s["id"]
|
|
667
|
+
getattr(self, "stmt_" + t)(s)
|
|
668
|
+
self.cur_stmt, self.cur_node = saved
|
|
669
|
+
|
|
670
|
+
def stmt_assign(self, s):
|
|
671
|
+
name = s["target"]
|
|
672
|
+
self.store(name, s, self.target_type(name, s), s["value"])
|
|
673
|
+
|
|
674
|
+
def target_type(self, name, s):
|
|
675
|
+
r = self.routine
|
|
676
|
+
if name in r.locals and r.visible(name):
|
|
677
|
+
return r.types[r.locals.index(name)]
|
|
678
|
+
gi = self.global_index(name, s)
|
|
679
|
+
if gi is not None:
|
|
680
|
+
return self.global_types[gi]
|
|
681
|
+
if name in self.consts:
|
|
682
|
+
raise self.err("cannot assign to constant '%s'" % name, s)
|
|
683
|
+
raise self.other_state_var(name, s) or \
|
|
684
|
+
self.err("unknown variable '%s' (declare it with a type, e.g. 'int %s = 0')" % (name, name), s)
|
|
685
|
+
|
|
686
|
+
def store(self, name, s, t, value):
|
|
687
|
+
self.expr(value, t, "'%s' is %s:" % (name, TYPE_NAMES[t]))
|
|
688
|
+
r = self.routine
|
|
689
|
+
if name in r.locals and r.visible(name):
|
|
690
|
+
self.emit("STOREL", r.locals.index(name), s)
|
|
691
|
+
else:
|
|
692
|
+
self.emit("STOREG", self.global_index(name, s), s)
|
|
693
|
+
|
|
694
|
+
def stmt_local(self, s):
|
|
695
|
+
idx = self.routine.locals.index(s["name"])
|
|
696
|
+
t = self.routine.types[idx]
|
|
697
|
+
if s.get("value") is not None:
|
|
698
|
+
self.expr(s["value"], t, "'%s' is %s:" % (s["name"], TYPE_NAMES[t]))
|
|
699
|
+
else:
|
|
700
|
+
self.push_const(V.zero(t), s)
|
|
701
|
+
self.routine.declared.add(s["name"])
|
|
702
|
+
self.emit("STOREL", idx, s)
|
|
703
|
+
|
|
704
|
+
def stmt_call(self, s):
|
|
705
|
+
self.call(s, s)
|
|
706
|
+
self.emit("POP", node=s)
|
|
707
|
+
|
|
708
|
+
def stmt_if(self, s):
|
|
709
|
+
end = Label()
|
|
710
|
+
labels = [end]
|
|
711
|
+
branches = s["branches"]
|
|
712
|
+
for i, br in enumerate(branches):
|
|
713
|
+
nxt = Label()
|
|
714
|
+
labels.append(nxt)
|
|
715
|
+
self.cjump(br["cond"], nxt, False)
|
|
716
|
+
self.block(br["body"])
|
|
717
|
+
last = (i == len(branches) - 1)
|
|
718
|
+
if not last or s.get("else_body") is not None:
|
|
719
|
+
if not self.ends_flow(br["body"]):
|
|
720
|
+
self.emit("JMP", end, s)
|
|
721
|
+
self.place(nxt)
|
|
722
|
+
if s.get("else_body") is not None:
|
|
723
|
+
self.block(s["else_body"])
|
|
724
|
+
self.place(end)
|
|
725
|
+
self.finish_labels(labels)
|
|
726
|
+
|
|
727
|
+
def stmt_while(self, s):
|
|
728
|
+
top = Label()
|
|
729
|
+
end = Label()
|
|
730
|
+
self.place(top)
|
|
731
|
+
self.dbg.stmt_starts[top.pc] = s["id"]
|
|
732
|
+
self.cjump(s["cond"], end, False)
|
|
733
|
+
self.block(s["body"])
|
|
734
|
+
self.emit("JMP", top, s)
|
|
735
|
+
self.place(end)
|
|
736
|
+
self.finish_labels([top, end])
|
|
737
|
+
|
|
738
|
+
def stmt_goto(self, s):
|
|
739
|
+
if s["state"] not in self.states:
|
|
740
|
+
raise self.err("unknown state '%s'" % s["state"], s)
|
|
741
|
+
self.emit("GOTO", self.states[s["state"]], s)
|
|
742
|
+
|
|
743
|
+
def stmt_emit(self, s):
|
|
744
|
+
name = s["event"]
|
|
745
|
+
if name in self.timers:
|
|
746
|
+
raise self.err("cannot emit timer event '%s' (use start)" % name, s)
|
|
747
|
+
if name not in self.events:
|
|
748
|
+
raise self.err("unknown event '%s'" % name, s)
|
|
749
|
+
idx, argc, params, ptypes = self.events[name]
|
|
750
|
+
args = s.get("args") or []
|
|
751
|
+
if len(args) != argc:
|
|
752
|
+
raise self.err("event '%s' takes %d argument(s), got %d" % (name, argc, len(args)), s)
|
|
753
|
+
for a, prm, pt in zip(args, params, ptypes):
|
|
754
|
+
self.expr(a, pt, "%s(%s %s):" % (name, TYPE_NAMES[pt], prm))
|
|
755
|
+
self.emit("EMIT", idx, s)
|
|
756
|
+
|
|
757
|
+
def timer_index(self, s):
|
|
758
|
+
if s["timer"] not in self.timers:
|
|
759
|
+
raise self.err("unknown timer '%s' (declare it with 'timer %s')" % (s["timer"], s["timer"]), s)
|
|
760
|
+
return self.timers[s["timer"]][0]
|
|
761
|
+
|
|
762
|
+
def stmt_start(self, s):
|
|
763
|
+
idx = self.timer_index(s)
|
|
764
|
+
self.expr(s["ms"], INT, "the timer duration (ms):")
|
|
765
|
+
self.emit("TEVERY" if s.get("every") else "TSTART", idx, s)
|
|
766
|
+
|
|
767
|
+
def stmt_stop(self, s):
|
|
768
|
+
self.emit("TSTOP", self.timer_index(s), s)
|
|
769
|
+
|
|
770
|
+
def stmt_return(self, s):
|
|
771
|
+
v = s.get("value")
|
|
772
|
+
if v is None:
|
|
773
|
+
if self.routine.kind == "func" and self.routine.returns is not None:
|
|
774
|
+
raise self.err("'%s' must return a %s value" % (self.routine.name, TYPE_NAMES[self.routine.returns]), s)
|
|
775
|
+
self.emit("RET0", node=s)
|
|
776
|
+
return
|
|
777
|
+
if self.routine.kind == "handler":
|
|
778
|
+
raise self.err("event handlers cannot return a value", s)
|
|
779
|
+
if self.routine.returns is None:
|
|
780
|
+
raise self.err("'%s' returns nothing: declare its type, e.g. 'func %s(...) -> int:'"
|
|
781
|
+
% (self.routine.name, self.routine.name), s)
|
|
782
|
+
self.expr(v, self.routine.returns, "'%s' returns %s:" % (self.routine.name, TYPE_NAMES[self.routine.returns]))
|
|
783
|
+
self.emit("RET", node=s)
|
|
784
|
+
|
|
785
|
+
# -- expressions -----------------------------------------------------------------
|
|
786
|
+
def mark(self, e, pc):
|
|
787
|
+
self.dbg.value_marks[pc] = e["id"]
|
|
788
|
+
|
|
789
|
+
def track(self, e):
|
|
790
|
+
self.dbg.stmt_of[e["id"]] = self.cur_stmt["id"]
|
|
791
|
+
self.dbg.node_lines[e["id"]] = e.get("line", 0)
|
|
792
|
+
|
|
793
|
+
def typeof(self, e):
|
|
794
|
+
"""Static type of an expression (INT, FLOAT, STR, TIME or VOID); raises on errors."""
|
|
795
|
+
t = e["type"]
|
|
796
|
+
if t == "num":
|
|
797
|
+
return FLOAT if isinstance(e["value"], float) else INT
|
|
798
|
+
if t == "bool":
|
|
799
|
+
return INT
|
|
800
|
+
if t == "str":
|
|
801
|
+
return STR
|
|
802
|
+
if t == "name":
|
|
803
|
+
return self.var_type(e["name"], e)
|
|
804
|
+
if t in ("callx", "call"):
|
|
805
|
+
return self.call_target(e)[3]
|
|
806
|
+
if t == "unary":
|
|
807
|
+
ot = self.value_type(e["operand"])
|
|
808
|
+
if e["op"] == "-" and ot in (INT, FLOAT):
|
|
809
|
+
return ot
|
|
810
|
+
if ot != INT:
|
|
811
|
+
raise self.err("'%s' needs an int operand, not %s" % (e["op"], TYPE_NAMES[ot]), e)
|
|
812
|
+
return INT
|
|
813
|
+
if t == "binary":
|
|
814
|
+
op = e["op"]
|
|
815
|
+
lt, rt = self.value_type(e["left"]), self.value_type(e["right"])
|
|
816
|
+
if op in ("and", "or", "xor"):
|
|
817
|
+
for side, st in ((e["left"], lt), (e["right"], rt)):
|
|
818
|
+
if st != INT:
|
|
819
|
+
raise self.err("'%s' needs conditions (int), not %s: compare it, e.g. x != 0"
|
|
820
|
+
% (op, TYPE_NAMES[st]), side)
|
|
821
|
+
return INT
|
|
822
|
+
r = V.binary_type(op, lt, rt)
|
|
823
|
+
if r is None:
|
|
824
|
+
raise self.err("cannot use '%s' on %s and %s" % (op, TYPE_NAMES[lt], TYPE_NAMES[rt]), e)
|
|
825
|
+
return r[0]
|
|
826
|
+
raise self.err("unknown expression type '%s'" % t, e)
|
|
827
|
+
|
|
828
|
+
def value_type(self, e):
|
|
829
|
+
t = self.typeof(e)
|
|
830
|
+
if t is VOID:
|
|
831
|
+
raise self.err("'%s' returns nothing, so it has no value" % e.get("name", "?"), e)
|
|
832
|
+
return t
|
|
833
|
+
|
|
834
|
+
def expr(self, e, want=None, what="value"):
|
|
835
|
+
"""Compile e in value context; convert it to type `want` (int -> float) if given.
|
|
836
|
+
Returns the type of the value left on the stack."""
|
|
837
|
+
t = self.value_type(e)
|
|
838
|
+
if want is not None and t != want and not (t == INT and want == FLOAT):
|
|
839
|
+
raise self.err("%s got %s, expected %s" % (what, TYPE_NAMES[t], TYPE_NAMES[want]), e)
|
|
840
|
+
self.track(e)
|
|
841
|
+
saved = self.cur_node
|
|
842
|
+
self.cur_node = e
|
|
843
|
+
folded = self.try_fold(e)
|
|
844
|
+
if folded is not None:
|
|
845
|
+
if want == FLOAT and V.type_of(folded) == INT:
|
|
846
|
+
folded = float(folded)
|
|
847
|
+
t = FLOAT
|
|
848
|
+
self.mark(e, self.push_const(folded, e))
|
|
849
|
+
self.track_subtree(e)
|
|
850
|
+
self.cur_node = saved
|
|
851
|
+
return t
|
|
852
|
+
self.expr_code(e, t)
|
|
853
|
+
self.cur_node = saved
|
|
854
|
+
if want == FLOAT and t == INT:
|
|
855
|
+
self.promote(e)
|
|
856
|
+
t = FLOAT
|
|
857
|
+
return t
|
|
858
|
+
|
|
859
|
+
def promote(self, e):
|
|
860
|
+
self.emit("BUILTIN", 0, e) # float(int)
|
|
861
|
+
|
|
862
|
+
def expr_code(self, e, t):
|
|
863
|
+
et = e["type"]
|
|
864
|
+
if et == "str":
|
|
865
|
+
self.mark(e, self.emit("PUSHS", self.string(e["value"])))
|
|
866
|
+
elif et == "name":
|
|
867
|
+
name = e["name"]
|
|
868
|
+
r = self.routine
|
|
869
|
+
if name in r.locals and r.visible(name):
|
|
870
|
+
pc = self.emit("LOADL", r.locals.index(name))
|
|
871
|
+
else:
|
|
872
|
+
pc = self.emit("LOADG", self.global_index(name, e))
|
|
873
|
+
self.mark(e, pc)
|
|
874
|
+
elif et in ("callx", "call"):
|
|
875
|
+
self.mark(e, self.call(e, e))
|
|
876
|
+
elif et == "unary":
|
|
877
|
+
self.expr(e["operand"])
|
|
878
|
+
op = {"-": "NEG", "~": "BNOT", "not": "NOT"}[e["op"]]
|
|
879
|
+
self.mark(e, self.emit(op))
|
|
880
|
+
elif et == "binary":
|
|
881
|
+
op = e["op"]
|
|
882
|
+
if op in ("and", "or"):
|
|
883
|
+
f, end = Label(), Label()
|
|
884
|
+
self.cjump(e, f, False)
|
|
885
|
+
self.cur_node = e
|
|
886
|
+
self.mark(e, self.emit("PUSH8", 1))
|
|
887
|
+
self.emit("JMP", end)
|
|
888
|
+
self.place(f)
|
|
889
|
+
self.mark(e, self.emit("PUSH8", 0))
|
|
890
|
+
self.place(end)
|
|
891
|
+
self.finish_labels([f, end])
|
|
892
|
+
elif op == "xor":
|
|
893
|
+
self.expr(e["left"])
|
|
894
|
+
self.cur_node = e
|
|
895
|
+
self.emit("BOOL")
|
|
896
|
+
self.expr(e["right"])
|
|
897
|
+
self.cur_node = e
|
|
898
|
+
self.emit("BOOL")
|
|
899
|
+
self.mark(e, self.emit("NE"))
|
|
900
|
+
else:
|
|
901
|
+
rt, pl, pr = V.binary_type(op, self.value_type(e["left"]), self.value_type(e["right"]))
|
|
902
|
+
self.expr(e["left"], FLOAT if pl else None)
|
|
903
|
+
self.expr(e["right"], FLOAT if pr else None)
|
|
904
|
+
self.cur_node = e
|
|
905
|
+
self.mark(e, self.emit("CONCAT" if op == "+" and rt == STR else BIN_OPS[op]))
|
|
906
|
+
else:
|
|
907
|
+
raise self.err("unknown expression type '%s'" % et, e)
|
|
908
|
+
|
|
909
|
+
def track_subtree(self, e):
|
|
910
|
+
for k in ("left", "right", "operand"):
|
|
911
|
+
if isinstance(e.get(k), dict):
|
|
912
|
+
self.track(e[k])
|
|
913
|
+
self.track_subtree(e[k])
|
|
914
|
+
for a in e.get("args") or []:
|
|
915
|
+
if isinstance(a, dict):
|
|
916
|
+
self.track(a)
|
|
917
|
+
self.track_subtree(a)
|
|
918
|
+
|
|
919
|
+
def resolve_builtin(self, name, argtypes, node, quiet=False):
|
|
920
|
+
"""-> (builtin id or "identity", param types, return type), None if not a builtin."""
|
|
921
|
+
cands = [(i, b) for i, b in enumerate(V.BUILTINS) if b[0] == name]
|
|
922
|
+
if not cands:
|
|
923
|
+
return None
|
|
924
|
+
if len(argtypes) == 1 and (name, argtypes[0]) in V.IDENTITY:
|
|
925
|
+
return "identity", (argtypes[0],), argtypes[0]
|
|
926
|
+
exact = [c for c in cands if list(c[1][1]) == list(argtypes)]
|
|
927
|
+
if exact:
|
|
928
|
+
i, b = exact[0]
|
|
929
|
+
return i, b[1], b[2]
|
|
930
|
+
loose = [c for c in cands if len(c[1][1]) == len(argtypes) and all(
|
|
931
|
+
a == p or (a == INT and p == FLOAT) for a, p in zip(argtypes, c[1][1]))]
|
|
932
|
+
if loose:
|
|
933
|
+
i, b = loose[0]
|
|
934
|
+
return i, b[1], b[2]
|
|
935
|
+
if quiet:
|
|
936
|
+
return None
|
|
937
|
+
sigs = ", ".join("%s(%s)" % (name, ", ".join(TYPE_NAMES[p] for p in b[1])) for _i, b in cands)
|
|
938
|
+
raise self.err("no %s() takes (%s); there is %s" % (name, ", ".join(TYPE_NAMES[a] for a in argtypes), sigs), node)
|
|
939
|
+
|
|
940
|
+
def call_target(self, e):
|
|
941
|
+
"""-> (kind, index, param types, return type) for a call expression/statement."""
|
|
942
|
+
name = e["name"]
|
|
943
|
+
args = e.get("args") or []
|
|
944
|
+
if name in self.funcs:
|
|
945
|
+
idx, argc, _node, ptypes, ret = self.funcs[name]
|
|
946
|
+
kind = "CALL"
|
|
947
|
+
elif name in self.externs:
|
|
948
|
+
idx, argc, ptypes, ret = self.externs[name]
|
|
949
|
+
kind = "XCALL"
|
|
950
|
+
else:
|
|
951
|
+
hit = self.resolve_builtin(name, [self.value_type(a) for a in args], e)
|
|
952
|
+
if hit is None:
|
|
953
|
+
raise self.err("unknown function '%s' (declare host functions with 'extern %s(...)')" % (name, name), e)
|
|
954
|
+
return ("BUILTIN", hit[0], hit[1], hit[2])
|
|
955
|
+
if len(args) != argc:
|
|
956
|
+
raise self.err("'%s' takes %d argument(s), got %d" % (name, argc, len(args)), e)
|
|
957
|
+
return kind, idx, ptypes, ret
|
|
958
|
+
|
|
959
|
+
def call(self, e, node):
|
|
960
|
+
kind, idx, ptypes, _ret = self.call_target(e)
|
|
961
|
+
args = e.get("args") or []
|
|
962
|
+
for i, (a, pt) in enumerate(zip(args, ptypes)):
|
|
963
|
+
self.expr(a, pt, "argument %d of %s() (%s):" % (i + 1, e["name"], TYPE_NAMES[pt]))
|
|
964
|
+
saved = self.cur_node
|
|
965
|
+
self.cur_node = node
|
|
966
|
+
if idx == "identity":
|
|
967
|
+
pc = len(self.code) - 1 if self.code else 0
|
|
968
|
+
else:
|
|
969
|
+
pc = self.emit(kind, idx)
|
|
970
|
+
self.cur_node = saved
|
|
971
|
+
return pc
|
|
972
|
+
|
|
973
|
+
def cjump(self, e, label, jump_if):
|
|
974
|
+
"""Compile a condition: jump to label if truth(e) == jump_if, else fall through."""
|
|
975
|
+
self.track(e)
|
|
976
|
+
ct = self.value_type(e)
|
|
977
|
+
if ct != INT:
|
|
978
|
+
raise self.err("a condition must be an int (true/false), not %s: compare it, e.g. x != 0"
|
|
979
|
+
% TYPE_NAMES[ct], e)
|
|
980
|
+
t = e["type"]
|
|
981
|
+
folded = self.try_fold(e)
|
|
982
|
+
if folded is not None:
|
|
983
|
+
self.track_subtree(e)
|
|
984
|
+
if bool(folded) == jump_if:
|
|
985
|
+
self.emit("JMP", label, e)
|
|
986
|
+
return
|
|
987
|
+
if t == "unary" and e["op"] == "not":
|
|
988
|
+
self.cjump(e["operand"], label, not jump_if)
|
|
989
|
+
return
|
|
990
|
+
if t == "binary" and e["op"] in ("and", "or"):
|
|
991
|
+
is_and = e["op"] == "and"
|
|
992
|
+
if is_and != jump_if:
|
|
993
|
+
# and/jump-if-false or or/jump-if-true: both operands jump directly
|
|
994
|
+
self.cjump(e["left"], label, jump_if)
|
|
995
|
+
self.cjump(e["right"], label, jump_if)
|
|
996
|
+
else:
|
|
997
|
+
skip = Label()
|
|
998
|
+
self.cjump(e["left"], skip, not jump_if)
|
|
999
|
+
self.cjump(e["right"], label, jump_if)
|
|
1000
|
+
self.place(skip)
|
|
1001
|
+
self.finish_labels([skip])
|
|
1002
|
+
return
|
|
1003
|
+
self.expr(e)
|
|
1004
|
+
self.emit("JNZ" if jump_if else "JZ", label, e)
|
|
1005
|
+
|
|
1006
|
+
|
|
1007
|
+
def compile_ast(ast):
|
|
1008
|
+
c = Compiler(ast)
|
|
1009
|
+
try:
|
|
1010
|
+
prog = c.compile()
|
|
1011
|
+
except RecursionError:
|
|
1012
|
+
raise FlowError("expression is nested too deeply")
|
|
1013
|
+
return prog, c.dbg
|
|
1014
|
+
|
|
1015
|
+
|
|
1016
|
+
def compile_source(src):
|
|
1017
|
+
ast = parse(src)
|
|
1018
|
+
prog, dbg = compile_ast(ast)
|
|
1019
|
+
return ast, prog, dbg
|