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/bytecode.py
ADDED
|
@@ -0,0 +1,372 @@
|
|
|
1
|
+
"""Bytecode definitions, .fsb file format (encode/decode) and disassembler.
|
|
2
|
+
|
|
3
|
+
See SPEC.md section 8 for the normative description.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import struct
|
|
7
|
+
|
|
8
|
+
from .values import pack_value, unpack_value, TYPE_NAMES, NONE_TYPE, show, BUILTINS
|
|
9
|
+
|
|
10
|
+
MAGIC = b"FSB1"
|
|
11
|
+
FORMAT_VERSION = 2
|
|
12
|
+
FLAG_DEBUG = 1
|
|
13
|
+
# Features the program needs from the VM. A C VM built without one (FS_NO_FLOAT,
|
|
14
|
+
# FS_NO_TIME, FS_NO_STR) refuses images that set its flag.
|
|
15
|
+
FLAG_FLOAT = 2 # float values
|
|
16
|
+
FLAG_TIME = 4 # time values
|
|
17
|
+
FLAG_STR = 8 # strings made at run time (string slots); literals need no flag
|
|
18
|
+
FLAG_TEXT = 16 # text conversion of floats/times: str(float), str(time), float(s), time(s)
|
|
19
|
+
FEATURES = [(FLAG_FLOAT, "float", "FS_NO_FLOAT"), (FLAG_TIME, "time", "FS_NO_TIME"),
|
|
20
|
+
(FLAG_STR, "run-time strings", "FS_NO_STR"),
|
|
21
|
+
(FLAG_TEXT, "float/time text conversion", "FS_NO_TEXT")]
|
|
22
|
+
TEXT_BUILTINS = (1, 6, 7, 8) # float(str), str(float), str(time), time(str)
|
|
23
|
+
NONE16 = 0xFFFF
|
|
24
|
+
|
|
25
|
+
EV_BOOT, EV_ENTER, EV_EXIT = 0, 1, 2
|
|
26
|
+
BUILTIN_EVENTS = ["boot", "enter", "exit"]
|
|
27
|
+
|
|
28
|
+
# name -> (opcode, operand kind) operand kinds: None, i32, i8, u8, u16, f64, i64
|
|
29
|
+
OPCODES = {
|
|
30
|
+
"NOP": (0x00, None), "PUSH": (0x01, "i32"), "PUSH8": (0x02, "i8"), "PUSHS": (0x03, "u16"),
|
|
31
|
+
"POP": (0x04, None), "DUP": (0x05, None), "PUSHF": (0x06, "f64"), "PUSHT": (0x07, "i64"),
|
|
32
|
+
"LOADG": (0x10, "u16"), "STOREG": (0x11, "u16"), "LOADL": (0x12, "u8"), "STOREL": (0x13, "u8"),
|
|
33
|
+
"ADD": (0x20, None), "SUB": (0x21, None), "MUL": (0x22, None), "DIV": (0x23, None),
|
|
34
|
+
"MOD": (0x24, None), "NEG": (0x25, None), "BAND": (0x26, None), "BOR": (0x27, None),
|
|
35
|
+
"BXOR": (0x28, None), "BNOT": (0x29, None), "SHL": (0x2A, None), "SHR": (0x2B, None),
|
|
36
|
+
"CONCAT": (0x2C, None),
|
|
37
|
+
"EQ": (0x30, None), "NE": (0x31, None), "LT": (0x32, None), "LE": (0x33, None),
|
|
38
|
+
"GT": (0x34, None), "GE": (0x35, None), "NOT": (0x36, None), "BOOL": (0x37, None),
|
|
39
|
+
"JMP": (0x40, "u16"), "JZ": (0x41, "u16"), "JNZ": (0x42, "u16"),
|
|
40
|
+
"CALL": (0x50, "u16"), "RET": (0x51, None), "RET0": (0x52, None), "XCALL": (0x53, "u16"),
|
|
41
|
+
"BUILTIN": (0x54, "u8"),
|
|
42
|
+
"GOTO": (0x60, "u16"), "EMIT": (0x61, "u16"), "TSTART": (0x62, "u16"),
|
|
43
|
+
"TEVERY": (0x63, "u16"), "TSTOP": (0x64, "u16"),
|
|
44
|
+
}
|
|
45
|
+
BY_CODE = {code: (name, kind) for name, (code, kind) in OPCODES.items()}
|
|
46
|
+
OPERAND_SIZE = {None: 0, "i8": 1, "u8": 1, "u16": 2, "i32": 4, "f64": 8, "i64": 8}
|
|
47
|
+
OPERAND_FMT = {"i8": "<b", "u8": "<B", "u16": "<H", "i32": "<i", "f64": "<d", "i64": "<q"}
|
|
48
|
+
|
|
49
|
+
FAULTS = {
|
|
50
|
+
1: "stack overflow", 2: "stack underflow", 3: "division by zero",
|
|
51
|
+
4: "instruction budget exceeded", 5: "bad opcode", 6: "call depth exceeded",
|
|
52
|
+
7: "event queue full", 8: "unbound extern", 9: "dispatch chain limit (script never idle)",
|
|
53
|
+
10: "type mismatch", 11: "out of string memory", 12: "string too long", 13: "invalid value",
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def pack_types(types):
|
|
58
|
+
"""Parameter types, 2 bits each (int 0, float 1, str 2, time 3), first parameter lowest."""
|
|
59
|
+
v = 0
|
|
60
|
+
for i, t in enumerate(types):
|
|
61
|
+
v |= t << (2 * i)
|
|
62
|
+
return v
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def unpack_types(v, n):
|
|
66
|
+
return tuple((v >> (2 * i)) & 3 for i in range(n))
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class Program:
|
|
70
|
+
"""In-memory representation of a compiled program (mirrors the .fsb file)."""
|
|
71
|
+
|
|
72
|
+
def __init__(self):
|
|
73
|
+
self.name = None
|
|
74
|
+
self.version = 0
|
|
75
|
+
self.strings = [] # list[str] (non-UTF-8 bytes kept with surrogateescape)
|
|
76
|
+
self.globals = [] # initial values (int, float, values.Str, values.Time)
|
|
77
|
+
self.externs = [] # (name, argc, param types, return type or NONE_TYPE)
|
|
78
|
+
self.events = [] # (name, argc, param types)
|
|
79
|
+
self.states = [] # name
|
|
80
|
+
self.timers = [] # event index
|
|
81
|
+
self.funcs = [] # (name, argc, nlocals, offset)
|
|
82
|
+
self.handlers = [] # (state|None, event, argc, nlocals, offset)
|
|
83
|
+
self.code = b""
|
|
84
|
+
# debug info (optional)
|
|
85
|
+
self.lines = [] # (pc, line, node)
|
|
86
|
+
self.global_names = []
|
|
87
|
+
# set by flowscript.compile(); None/empty for a program loaded from bytecode
|
|
88
|
+
self.source = None
|
|
89
|
+
self.ast = None
|
|
90
|
+
self.debug_info = None
|
|
91
|
+
self.warnings = []
|
|
92
|
+
|
|
93
|
+
# -- helpers -------------------------------------------------------------
|
|
94
|
+
def event_index(self, name):
|
|
95
|
+
for i, ev in enumerate(self.events):
|
|
96
|
+
if ev[0] == name:
|
|
97
|
+
return i
|
|
98
|
+
return -1
|
|
99
|
+
|
|
100
|
+
def state_index(self, name):
|
|
101
|
+
try:
|
|
102
|
+
return self.states.index(name)
|
|
103
|
+
except ValueError:
|
|
104
|
+
return -1
|
|
105
|
+
|
|
106
|
+
def find_handler(self, state, event):
|
|
107
|
+
glob = None
|
|
108
|
+
for h in self.handlers:
|
|
109
|
+
if h[1] != event:
|
|
110
|
+
continue
|
|
111
|
+
if h[0] == state and state is not None:
|
|
112
|
+
return h
|
|
113
|
+
if h[0] is None and glob is None:
|
|
114
|
+
glob = h
|
|
115
|
+
return glob
|
|
116
|
+
|
|
117
|
+
# -- serialisation ---------------------------------------------------------
|
|
118
|
+
def save(self, path, debug=False):
|
|
119
|
+
"""Write the .fsb image (debug=True adds source lines and global names)."""
|
|
120
|
+
with open(path, "wb") as f:
|
|
121
|
+
f.write(self.to_bytes(debug))
|
|
122
|
+
|
|
123
|
+
def to_bytes(self, debug=False):
|
|
124
|
+
strings = list(self.strings)
|
|
125
|
+
|
|
126
|
+
def sidx(s):
|
|
127
|
+
if s is None:
|
|
128
|
+
return NONE16
|
|
129
|
+
if s not in strings:
|
|
130
|
+
strings.append(s)
|
|
131
|
+
return strings.index(s)
|
|
132
|
+
|
|
133
|
+
name_idx = sidx(self.name)
|
|
134
|
+
ext = b"".join(struct.pack("<HBBI", sidx(n), a, r, pack_types(ts)) for n, a, ts, r in self.externs)
|
|
135
|
+
evs = b"".join(struct.pack("<HBBH", sidx(n), a, 0, pack_types(ts)) for n, a, ts in self.events)
|
|
136
|
+
glb = b"".join(pack_value(v, lambda b: sidx(b.decode("utf-8", "surrogateescape"))) for v in self.globals)
|
|
137
|
+
sts = b"".join(struct.pack("<H", sidx(n)) for n in self.states)
|
|
138
|
+
tms = b"".join(struct.pack("<H", e) for e in self.timers)
|
|
139
|
+
fns = b"".join(struct.pack("<HBBH", sidx(n), a, l, o) for n, a, l, o in self.funcs)
|
|
140
|
+
hds = b"".join(struct.pack("<HHBBH", NONE16 if s is None else s, e, a, l, o)
|
|
141
|
+
for s, e, a, l, o in self.handlers)
|
|
142
|
+
dbg = b""
|
|
143
|
+
if debug:
|
|
144
|
+
dbg = struct.pack("<H", len(self.lines))
|
|
145
|
+
dbg += b"".join(struct.pack("<HHH", pc, ln & 0xFFFF, nd & 0xFFFF) for pc, ln, nd in self.lines)
|
|
146
|
+
names = self.global_names or [""] * len(self.globals)
|
|
147
|
+
dbg += b"".join(struct.pack("<H", sidx(n)) for n in names)
|
|
148
|
+
strs = b""
|
|
149
|
+
for s in strings:
|
|
150
|
+
b = s.encode("utf-8", "surrogateescape")
|
|
151
|
+
strs += struct.pack("<H", len(b)) + b + b"\x00"
|
|
152
|
+
header = MAGIC + struct.pack(
|
|
153
|
+
"<HHHHHHHHHHHHI", FORMAT_VERSION, (FLAG_DEBUG if debug else 0) | features(self), name_idx,
|
|
154
|
+
self.version & 0xFFFF, len(strings), len(self.globals), len(self.externs),
|
|
155
|
+
len(self.events), len(self.states), len(self.timers), len(self.funcs),
|
|
156
|
+
len(self.handlers), len(self.code))
|
|
157
|
+
assert len(header) == 32
|
|
158
|
+
return header + strs + glb + ext + evs + sts + tms + fns + hds + self.code + dbg
|
|
159
|
+
|
|
160
|
+
@classmethod
|
|
161
|
+
def from_bytes(cls, data):
|
|
162
|
+
try:
|
|
163
|
+
return cls._from_bytes(data)
|
|
164
|
+
except ValueError:
|
|
165
|
+
raise
|
|
166
|
+
except (IndexError, struct.error, UnicodeDecodeError) as e:
|
|
167
|
+
raise ValueError("corrupt bytecode image: %s" % e)
|
|
168
|
+
|
|
169
|
+
@classmethod
|
|
170
|
+
def _from_bytes(cls, data):
|
|
171
|
+
if data[:4] != MAGIC:
|
|
172
|
+
raise ValueError("not a FlowScript bytecode file")
|
|
173
|
+
(ver, flags, name_idx, pver, ns, ng, nx, ne, nst, nt, nf, nh,
|
|
174
|
+
csize) = struct.unpack_from("<HHHHHHHHHHHHI", data, 4)
|
|
175
|
+
if ver != FORMAT_VERSION:
|
|
176
|
+
raise ValueError("unsupported bytecode version %d" % ver)
|
|
177
|
+
p = cls()
|
|
178
|
+
off = 32
|
|
179
|
+
for _ in range(ns):
|
|
180
|
+
(ln,) = struct.unpack_from("<H", data, off)
|
|
181
|
+
if len(data) < off + 3 + ln:
|
|
182
|
+
raise ValueError("corrupt bytecode image: string table truncated")
|
|
183
|
+
p.strings.append(data[off + 2:off + 2 + ln].decode("utf-8", "surrogateescape"))
|
|
184
|
+
off += 3 + ln
|
|
185
|
+
for _ in range(ng):
|
|
186
|
+
p.globals.append(unpack_value(data, off, p.strings))
|
|
187
|
+
off += 9
|
|
188
|
+
for _ in range(nx):
|
|
189
|
+
n, a, r, ts = struct.unpack_from("<HBBI", data, off)
|
|
190
|
+
p.externs.append((p.strings[n], a, unpack_types(ts, a), r))
|
|
191
|
+
off += 8
|
|
192
|
+
for _ in range(ne):
|
|
193
|
+
n, a, _r, ts = struct.unpack_from("<HBBH", data, off)
|
|
194
|
+
p.events.append((p.strings[n], a, unpack_types(ts, a)))
|
|
195
|
+
off += 6
|
|
196
|
+
for _ in range(nst):
|
|
197
|
+
p.states.append(p.strings[struct.unpack_from("<H", data, off)[0]])
|
|
198
|
+
off += 2
|
|
199
|
+
for _ in range(nt):
|
|
200
|
+
p.timers.append(struct.unpack_from("<H", data, off)[0])
|
|
201
|
+
off += 2
|
|
202
|
+
for _ in range(nf):
|
|
203
|
+
n, a, l, o = struct.unpack_from("<HBBH", data, off)
|
|
204
|
+
p.funcs.append((p.strings[n], a, l, o))
|
|
205
|
+
off += 6
|
|
206
|
+
for _ in range(nh):
|
|
207
|
+
s, e, a, l, o = struct.unpack_from("<HHBBH", data, off)
|
|
208
|
+
p.handlers.append((None if s == NONE16 else s, e, a, l, o))
|
|
209
|
+
off += 8
|
|
210
|
+
p.code = bytes(data[off:off + csize])
|
|
211
|
+
off += csize
|
|
212
|
+
if flags & FLAG_DEBUG:
|
|
213
|
+
(nl,) = struct.unpack_from("<H", data, off)
|
|
214
|
+
off += 2
|
|
215
|
+
for _ in range(nl):
|
|
216
|
+
p.lines.append(struct.unpack_from("<HHH", data, off))
|
|
217
|
+
off += 6
|
|
218
|
+
for _ in range(ng):
|
|
219
|
+
p.global_names.append(p.strings[struct.unpack_from("<H", data, off)[0]])
|
|
220
|
+
off += 2
|
|
221
|
+
p.name = None if name_idx == NONE16 else p.strings[name_idx]
|
|
222
|
+
p.version = pver
|
|
223
|
+
return p
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
def features(prog):
|
|
227
|
+
"""FLAG_FLOAT | FLAG_TIME | FLAG_STR | FLAG_TEXT bits for what `prog` can ever use.
|
|
228
|
+
|
|
229
|
+
Every value of those kinds starts at a typed declaration (global, extern, event),
|
|
230
|
+
a typed constant push, CONCAT or a built-in, so scanning them is exact.
|
|
231
|
+
"""
|
|
232
|
+
from .values import FLOAT, TIME, STR, type_of
|
|
233
|
+
need = set()
|
|
234
|
+
for v in prog.globals:
|
|
235
|
+
need.add(type_of(v))
|
|
236
|
+
for _n, _a, ts, r in prog.externs:
|
|
237
|
+
need.update(ts)
|
|
238
|
+
if r == STR:
|
|
239
|
+
need.add("str-result")
|
|
240
|
+
need.add(r)
|
|
241
|
+
for _n, _a, ts in prog.events:
|
|
242
|
+
need.update(ts)
|
|
243
|
+
if STR in ts:
|
|
244
|
+
need.add("str-result") # the host's strings are copied into slots
|
|
245
|
+
pc = 0
|
|
246
|
+
while pc < len(prog.code):
|
|
247
|
+
name, val, size = decode(prog.code, pc)
|
|
248
|
+
if name == "PUSHF":
|
|
249
|
+
need.add(FLOAT)
|
|
250
|
+
elif name == "PUSHT":
|
|
251
|
+
need.add(TIME)
|
|
252
|
+
elif name == "CONCAT":
|
|
253
|
+
need.add("str-result")
|
|
254
|
+
elif name == "BUILTIN" and 0 <= val < len(BUILTINS):
|
|
255
|
+
_bn, ps, r = BUILTINS[val]
|
|
256
|
+
need.update(ps)
|
|
257
|
+
need.add(r)
|
|
258
|
+
if r == STR:
|
|
259
|
+
need.add("str-result")
|
|
260
|
+
if val in TEXT_BUILTINS:
|
|
261
|
+
need.add("text")
|
|
262
|
+
pc += size
|
|
263
|
+
return ((FLAG_FLOAT if FLOAT in need else 0) | (FLAG_TIME if TIME in need else 0) |
|
|
264
|
+
(FLAG_STR if "str-result" in need else 0) | (FLAG_TEXT if "text" in need else 0))
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
def decode(code, pc):
|
|
268
|
+
"""Decode one instruction. Returns (name, operand, size)."""
|
|
269
|
+
op = code[pc]
|
|
270
|
+
if op not in BY_CODE:
|
|
271
|
+
return ("???", op, 1)
|
|
272
|
+
name, kind = BY_CODE[op]
|
|
273
|
+
if kind is None:
|
|
274
|
+
return (name, None, 1)
|
|
275
|
+
(val,) = struct.unpack_from(OPERAND_FMT[kind], code, pc + 1)
|
|
276
|
+
return (name, val, 1 + OPERAND_SIZE[kind])
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
def operand_comment(prog, name, val, local_names=None):
|
|
280
|
+
try:
|
|
281
|
+
if name == "PUSHS":
|
|
282
|
+
return show_str(prog.strings[val])
|
|
283
|
+
if name == "PUSHT":
|
|
284
|
+
from .values import Time
|
|
285
|
+
return show(Time(val))
|
|
286
|
+
if name == "BUILTIN":
|
|
287
|
+
b = BUILTINS[val]
|
|
288
|
+
return "%s(%s) -> %s" % (b[0], ", ".join(TYPE_NAMES[t] for t in b[1]), TYPE_NAMES[b[2]])
|
|
289
|
+
if name in ("LOADG", "STOREG"):
|
|
290
|
+
return prog.global_names[val] if prog.global_names else ""
|
|
291
|
+
if name in ("LOADL", "STOREL") and local_names:
|
|
292
|
+
return local_names[val] if val < len(local_names) else ""
|
|
293
|
+
if name == "CALL":
|
|
294
|
+
return prog.funcs[val][0]
|
|
295
|
+
if name == "XCALL":
|
|
296
|
+
return prog.externs[val][0]
|
|
297
|
+
if name == "GOTO":
|
|
298
|
+
return prog.states[val]
|
|
299
|
+
if name == "EMIT":
|
|
300
|
+
return prog.events[val][0]
|
|
301
|
+
if name in ("TSTART", "TEVERY", "TSTOP"):
|
|
302
|
+
return prog.events[prog.timers[val]][0]
|
|
303
|
+
except (IndexError, TypeError):
|
|
304
|
+
pass
|
|
305
|
+
return ""
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
def show_str(s):
|
|
309
|
+
from .values import Str
|
|
310
|
+
return show(Str(s.encode("utf-8", "surrogateescape")))
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
def sig(types, ret=None):
|
|
314
|
+
out = "(%s)" % ", ".join(TYPE_NAMES[t] for t in types)
|
|
315
|
+
if ret is not None and ret != NONE_TYPE:
|
|
316
|
+
out += " -> " + TYPE_NAMES[ret]
|
|
317
|
+
return out
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
def code_labels(prog):
|
|
321
|
+
"""pc -> label for handler/function entry points."""
|
|
322
|
+
labels = {}
|
|
323
|
+
for name, _a, _l, off in prog.funcs:
|
|
324
|
+
labels[off] = "func %s" % name
|
|
325
|
+
for st, ev, _a, _l, off in prog.handlers:
|
|
326
|
+
evn = prog.events[ev][0]
|
|
327
|
+
labels[off] = ("on %s" % evn) if st is None else ("%s: on %s" % (prog.states[st], evn))
|
|
328
|
+
return labels
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
def disassemble(prog, local_names=None):
|
|
332
|
+
"""Returns a list of dicts: pc, size, bytes(hex), op, arg, comment, label."""
|
|
333
|
+
out = []
|
|
334
|
+
labels = code_labels(prog)
|
|
335
|
+
pc = 0
|
|
336
|
+
code = prog.code
|
|
337
|
+
while pc < len(code):
|
|
338
|
+
name, val, size = decode(code, pc)
|
|
339
|
+
out.append({
|
|
340
|
+
"pc": pc, "size": size, "hex": code[pc:pc + size].hex(" "),
|
|
341
|
+
"op": name, "arg": val,
|
|
342
|
+
"comment": operand_comment(prog, name, val, (local_names or {}).get(pc)),
|
|
343
|
+
"label": labels.get(pc),
|
|
344
|
+
})
|
|
345
|
+
pc += size
|
|
346
|
+
return out
|
|
347
|
+
|
|
348
|
+
|
|
349
|
+
def disassemble_text(prog):
|
|
350
|
+
lines = []
|
|
351
|
+
if prog.name:
|
|
352
|
+
lines.append("; flow %s version %d" % (prog.name, prog.version))
|
|
353
|
+
lines.append("; %d bytes code, %d globals, %d externs, %d events, %d states, %d timers"
|
|
354
|
+
% (len(prog.code), len(prog.globals), len(prog.externs), len(prog.events),
|
|
355
|
+
len(prog.states), len(prog.timers)))
|
|
356
|
+
for i, (n, _a, ts, r) in enumerate(prog.externs):
|
|
357
|
+
lines.append("; extern %d: %s%s" % (i, n, sig(ts, r)))
|
|
358
|
+
for i, (n, _a, ts) in enumerate(prog.events):
|
|
359
|
+
lines.append("; event %d: %s%s" % (i, n, sig(ts)))
|
|
360
|
+
for i, v in enumerate(prog.globals):
|
|
361
|
+
name = prog.global_names[i] if i < len(prog.global_names) else ""
|
|
362
|
+
lines.append("; global %d: %s = %s" % (i, name, show(v)))
|
|
363
|
+
for i, n in enumerate(prog.states):
|
|
364
|
+
lines.append("; state %d: %s" % (i, n))
|
|
365
|
+
for ins in disassemble(prog):
|
|
366
|
+
if ins["label"]:
|
|
367
|
+
lines.append("")
|
|
368
|
+
lines.append("%s:" % ins["label"])
|
|
369
|
+
arg = "" if ins["arg"] is None else str(ins["arg"])
|
|
370
|
+
cmt = (" ; " + ins["comment"]) if ins["comment"] else ""
|
|
371
|
+
lines.append(" %04x %-14s %-7s %s%s" % (ins["pc"], ins["hex"], ins["op"], arg, cmt))
|
|
372
|
+
return "\n".join(lines) + "\n"
|
flowscript/cli.py
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
"""Command line front end: python3 -m flowscript <command> ...
|
|
2
|
+
|
|
3
|
+
compile FILE.fs [-o OUT.fsb] [-g] [--size]
|
|
4
|
+
compile to bytecode (-g: include debug section)
|
|
5
|
+
size FILE.fs|FILE.fsb [-g] [--json]
|
|
6
|
+
where the image bytes go, RAM needed by the C VM,
|
|
7
|
+
smallest FS_* build settings, optimization hints
|
|
8
|
+
disasm FILE.fs|FILE.fsb print a disassembly
|
|
9
|
+
fmt FILE.fs [-w] print (or rewrite) in canonical format
|
|
10
|
+
ast FILE.fs print the visual model (AST JSON)
|
|
11
|
+
run FILE.fs|FILE.fsb [SCENARIO] run with stubbed externs, print trace
|
|
12
|
+
mermaid FILE.fs print a Mermaid flowchart
|
|
13
|
+
debug FILE.fs [--port N] start the 3-view web debugger
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
import argparse
|
|
17
|
+
import json
|
|
18
|
+
import os
|
|
19
|
+
import sys
|
|
20
|
+
|
|
21
|
+
from .lexer import FlowError
|
|
22
|
+
from .parser import parse
|
|
23
|
+
from .printer import to_source
|
|
24
|
+
from .compiler import compile_source, compile_ast
|
|
25
|
+
from .bytecode import Program, disassemble_text
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def load_program(path):
|
|
29
|
+
if path.endswith(".fsb"):
|
|
30
|
+
with open(path, "rb") as f:
|
|
31
|
+
return Program.from_bytes(f.read())
|
|
32
|
+
with open(path) as f:
|
|
33
|
+
_ast, prog, _dbg = compile_source(f.read())
|
|
34
|
+
return prog
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def main(argv=None):
|
|
38
|
+
ap = argparse.ArgumentParser(prog="flowscript", description=__doc__,
|
|
39
|
+
formatter_class=argparse.RawDescriptionHelpFormatter)
|
|
40
|
+
sub = ap.add_subparsers(dest="cmd")
|
|
41
|
+
p = sub.add_parser("compile")
|
|
42
|
+
p.add_argument("file")
|
|
43
|
+
p.add_argument("-o", "--output")
|
|
44
|
+
p.add_argument("-g", "--debug", action="store_true")
|
|
45
|
+
p.add_argument("--size", action="store_true", help="print the size report")
|
|
46
|
+
p = sub.add_parser("size")
|
|
47
|
+
p.add_argument("file")
|
|
48
|
+
p.add_argument("-g", "--debug", action="store_true", help="include the debug section")
|
|
49
|
+
p.add_argument("--json", action="store_true")
|
|
50
|
+
p = sub.add_parser("disasm")
|
|
51
|
+
p.add_argument("file")
|
|
52
|
+
p = sub.add_parser("fmt")
|
|
53
|
+
p.add_argument("file")
|
|
54
|
+
p.add_argument("-w", "--write", action="store_true")
|
|
55
|
+
p = sub.add_parser("ast")
|
|
56
|
+
p.add_argument("file")
|
|
57
|
+
p = sub.add_parser("run")
|
|
58
|
+
p.add_argument("file")
|
|
59
|
+
p.add_argument("scenario", nargs="?")
|
|
60
|
+
p = sub.add_parser("mermaid")
|
|
61
|
+
p.add_argument("file")
|
|
62
|
+
p = sub.add_parser("debug")
|
|
63
|
+
p.add_argument("file", nargs="?")
|
|
64
|
+
p.add_argument("--port", type=int, default=8642)
|
|
65
|
+
p.add_argument("--no-browser", action="store_true")
|
|
66
|
+
args = ap.parse_args(argv)
|
|
67
|
+
|
|
68
|
+
try:
|
|
69
|
+
if args.cmd == "compile":
|
|
70
|
+
with open(args.file) as f:
|
|
71
|
+
_ast, prog, dbg = compile_source(f.read())
|
|
72
|
+
for w in dbg.warnings:
|
|
73
|
+
print("%s: line %d: warning: %s" % (args.file, w["line"], w["msg"]), file=sys.stderr)
|
|
74
|
+
out = args.output or os.path.splitext(args.file)[0] + ".fsb"
|
|
75
|
+
data = prog.to_bytes(debug=args.debug)
|
|
76
|
+
with open(out, "wb") as f:
|
|
77
|
+
f.write(data)
|
|
78
|
+
print("%s: %d bytes (%d bytes code)" % (out, len(data), len(prog.code)))
|
|
79
|
+
if args.size:
|
|
80
|
+
from .sizes import size_report, format_report
|
|
81
|
+
print()
|
|
82
|
+
sys.stdout.write(format_report(size_report(prog, args.debug), os.path.basename(args.file)))
|
|
83
|
+
elif args.cmd == "size":
|
|
84
|
+
from .sizes import size_report, format_report
|
|
85
|
+
prog = load_program(args.file)
|
|
86
|
+
image = open(args.file, "rb").read() if args.file.endswith(".fsb") else None
|
|
87
|
+
rep = size_report(prog, args.debug, image)
|
|
88
|
+
if args.json:
|
|
89
|
+
print(json.dumps(rep, indent=1))
|
|
90
|
+
else:
|
|
91
|
+
sys.stdout.write(format_report(rep, os.path.basename(args.file)))
|
|
92
|
+
elif args.cmd == "disasm":
|
|
93
|
+
sys.stdout.write(disassemble_text(load_program(args.file)))
|
|
94
|
+
elif args.cmd == "fmt":
|
|
95
|
+
with open(args.file) as f:
|
|
96
|
+
src = to_source(parse(f.read()))
|
|
97
|
+
if args.write:
|
|
98
|
+
with open(args.file, "w") as f:
|
|
99
|
+
f.write(src)
|
|
100
|
+
else:
|
|
101
|
+
sys.stdout.write(src)
|
|
102
|
+
elif args.cmd == "ast":
|
|
103
|
+
with open(args.file) as f:
|
|
104
|
+
print(json.dumps(parse(f.read()), indent=1))
|
|
105
|
+
elif args.cmd == "run":
|
|
106
|
+
from .runner import run_scenario
|
|
107
|
+
prog = load_program(args.file)
|
|
108
|
+
text = ""
|
|
109
|
+
if args.scenario:
|
|
110
|
+
with open(args.scenario) as f:
|
|
111
|
+
text = f.read()
|
|
112
|
+
run_scenario(prog, text, print)
|
|
113
|
+
elif args.cmd == "mermaid":
|
|
114
|
+
from .mermaid import to_mermaid
|
|
115
|
+
with open(args.file) as f:
|
|
116
|
+
sys.stdout.write(to_mermaid(parse(f.read())))
|
|
117
|
+
elif args.cmd == "debug":
|
|
118
|
+
from .debugger.server import serve
|
|
119
|
+
serve(args.file, args.port, open_browser=not args.no_browser)
|
|
120
|
+
else:
|
|
121
|
+
ap.print_help()
|
|
122
|
+
return 1
|
|
123
|
+
except FlowError as e:
|
|
124
|
+
print("%s: %s" % (getattr(args, "file", ""), e), file=sys.stderr)
|
|
125
|
+
return 1
|
|
126
|
+
return 0
|