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/parser.py
ADDED
|
@@ -0,0 +1,578 @@
|
|
|
1
|
+
"""FlowScript parser: tokens -> AST.
|
|
2
|
+
|
|
3
|
+
The AST is made of plain dicts (JSON ready). It *is* the visual model: the flowchart
|
|
4
|
+
is rendered from it and the formatter turns it back into source. Every node has
|
|
5
|
+
``id`` (unique per parse), ``type`` and ``line``; statements and declarations may
|
|
6
|
+
carry a ``note`` (comment text shown as an annotation).
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import sys
|
|
10
|
+
|
|
11
|
+
from .lexer import tokenize, FlowError, TYPE_KEYWORDS
|
|
12
|
+
|
|
13
|
+
BINARY_LEVELS = [
|
|
14
|
+
("or",),
|
|
15
|
+
("xor",),
|
|
16
|
+
("and",),
|
|
17
|
+
None, # not (prefix)
|
|
18
|
+
("==", "!=", "<", "<=", ">", ">="),
|
|
19
|
+
("|",),
|
|
20
|
+
("^",),
|
|
21
|
+
("&",),
|
|
22
|
+
("<<", ">>"),
|
|
23
|
+
("+", "-"),
|
|
24
|
+
("*", "/", "%"),
|
|
25
|
+
]
|
|
26
|
+
COMPARE_OPS = ("==", "!=", "<", "<=", ">", ">=")
|
|
27
|
+
MAX_NESTING = 100 # parenthesis depth; keeps parser/compiler recursion bounded
|
|
28
|
+
|
|
29
|
+
# expressions are parsed/compiled/printed recursively (~15 frames per nesting level)
|
|
30
|
+
if sys.getrecursionlimit() < 6000:
|
|
31
|
+
sys.setrecursionlimit(6000)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class Parser:
|
|
35
|
+
def __init__(self, src):
|
|
36
|
+
self.toks = tokenize(src)
|
|
37
|
+
self.pos = 0
|
|
38
|
+
self.next_id = 1
|
|
39
|
+
self.depth = 0
|
|
40
|
+
|
|
41
|
+
# -- helpers -----------------------------------------------------------
|
|
42
|
+
def node(self, type_, tok, **fields):
|
|
43
|
+
d = {"id": self.next_id, "type": type_, "line": tok.line if tok else 0}
|
|
44
|
+
self.next_id += 1
|
|
45
|
+
d.update(fields)
|
|
46
|
+
return d
|
|
47
|
+
|
|
48
|
+
@property
|
|
49
|
+
def tok(self):
|
|
50
|
+
return self.toks[self.pos]
|
|
51
|
+
|
|
52
|
+
def peek(self, k=1):
|
|
53
|
+
return self.toks[min(self.pos + k, len(self.toks) - 1)]
|
|
54
|
+
|
|
55
|
+
def advance(self):
|
|
56
|
+
t = self.toks[self.pos]
|
|
57
|
+
self.pos += 1
|
|
58
|
+
return t
|
|
59
|
+
|
|
60
|
+
def is_kw(self, *words):
|
|
61
|
+
return self.tok.kind == "KW" and self.tok.value in words
|
|
62
|
+
|
|
63
|
+
def is_op(self, *ops):
|
|
64
|
+
return self.tok.kind == "OP" and self.tok.value in ops
|
|
65
|
+
|
|
66
|
+
def error(self, msg, tok=None):
|
|
67
|
+
tok = tok or self.tok
|
|
68
|
+
return FlowError(msg, tok.line, tok.col)
|
|
69
|
+
|
|
70
|
+
def expect_kw(self, word):
|
|
71
|
+
if not self.is_kw(word):
|
|
72
|
+
raise self.error("expected '%s'" % word)
|
|
73
|
+
return self.advance()
|
|
74
|
+
|
|
75
|
+
def expect_op(self, op):
|
|
76
|
+
if not self.is_op(op):
|
|
77
|
+
raise self.error("expected '%s'" % op)
|
|
78
|
+
return self.advance()
|
|
79
|
+
|
|
80
|
+
def expect_name(self, what="name"):
|
|
81
|
+
if self.tok.kind != "NAME":
|
|
82
|
+
if self.tok.kind == "KW":
|
|
83
|
+
raise self.error("'%s' is a keyword and cannot be used as %s" % (self.tok.value, what))
|
|
84
|
+
raise self.error("expected %s" % what)
|
|
85
|
+
return self.advance().value
|
|
86
|
+
|
|
87
|
+
def end_of_line(self, node=None):
|
|
88
|
+
"""Consume an optional trailing comment and the newline."""
|
|
89
|
+
if self.tok.kind == "COMMENT":
|
|
90
|
+
text = self.advance().value
|
|
91
|
+
if node is not None and text:
|
|
92
|
+
node["note"] = (node["note"] + "\n" + text) if node.get("note") else text
|
|
93
|
+
if self.tok.kind == "EOF":
|
|
94
|
+
return
|
|
95
|
+
if self.tok.kind != "NEWLINE":
|
|
96
|
+
raise self.error("unexpected %s at end of statement" % self.describe(self.tok))
|
|
97
|
+
self.advance()
|
|
98
|
+
|
|
99
|
+
@staticmethod
|
|
100
|
+
def describe(tok):
|
|
101
|
+
if tok.kind == "INDENT":
|
|
102
|
+
return "indentation"
|
|
103
|
+
if tok.kind in ("OP", "KW"):
|
|
104
|
+
return "'%s'" % tok.value
|
|
105
|
+
if tok.kind == "NAME":
|
|
106
|
+
return "name '%s'" % tok.value
|
|
107
|
+
if tok.kind in ("NUM", "FLOAT"):
|
|
108
|
+
return "number"
|
|
109
|
+
return tok.kind.lower()
|
|
110
|
+
|
|
111
|
+
def gather(self, out):
|
|
112
|
+
"""Skip blank lines and comments.
|
|
113
|
+
|
|
114
|
+
Returns the note (comment lines directly above the next construct) or None.
|
|
115
|
+
Comment groups followed by a blank line or by the end of a block are emitted
|
|
116
|
+
into ``out`` as stand-alone comment nodes.
|
|
117
|
+
"""
|
|
118
|
+
group = []
|
|
119
|
+
group_tok = None
|
|
120
|
+
prev_newline = True
|
|
121
|
+
while True:
|
|
122
|
+
t = self.tok
|
|
123
|
+
if t.kind == "COMMENT":
|
|
124
|
+
if not group:
|
|
125
|
+
group_tok = t
|
|
126
|
+
group.append(t.value)
|
|
127
|
+
self.advance()
|
|
128
|
+
prev_newline = False
|
|
129
|
+
continue
|
|
130
|
+
if t.kind == "NEWLINE":
|
|
131
|
+
self.advance()
|
|
132
|
+
if prev_newline and group:
|
|
133
|
+
out.append(self.node("comment", group_tok, text="\n".join(group)))
|
|
134
|
+
group = []
|
|
135
|
+
prev_newline = True
|
|
136
|
+
continue
|
|
137
|
+
break
|
|
138
|
+
if group:
|
|
139
|
+
if t.kind in ("EOF", "DEDENT"):
|
|
140
|
+
out.append(self.node("comment", group_tok, text="\n".join(group)))
|
|
141
|
+
return None
|
|
142
|
+
return "\n".join(group)
|
|
143
|
+
return None
|
|
144
|
+
|
|
145
|
+
def colon(self, what):
|
|
146
|
+
if not self.is_op(":"):
|
|
147
|
+
raise self.error("expected ':' at the end of the %s line" % what)
|
|
148
|
+
self.advance()
|
|
149
|
+
|
|
150
|
+
def open_block(self, node, what):
|
|
151
|
+
"""``:`` + end of line + INDENT."""
|
|
152
|
+
self.colon(what)
|
|
153
|
+
self.end_of_line(node)
|
|
154
|
+
if self.tok.kind != "INDENT":
|
|
155
|
+
raise self.error("expected an indented block after the %s line" % what)
|
|
156
|
+
self.advance()
|
|
157
|
+
|
|
158
|
+
def skip_pass(self, out, note):
|
|
159
|
+
"""'pass' marks an empty block; a note above it is kept as a comment node."""
|
|
160
|
+
t = self.advance()
|
|
161
|
+
if note:
|
|
162
|
+
out.append(self.node("comment", t, text=note))
|
|
163
|
+
self.end_of_line()
|
|
164
|
+
|
|
165
|
+
@staticmethod
|
|
166
|
+
def attach(node, note):
|
|
167
|
+
if note:
|
|
168
|
+
node["note"] = note + ("\n" + node["note"] if node.get("note") else "")
|
|
169
|
+
return node
|
|
170
|
+
|
|
171
|
+
# -- program -----------------------------------------------------------
|
|
172
|
+
def parse_program(self):
|
|
173
|
+
prog = {"id": 0, "type": "program", "line": 1, "name": None, "version": 0, "items": []}
|
|
174
|
+
items = prog["items"]
|
|
175
|
+
pre = []
|
|
176
|
+
note = self.gather(pre)
|
|
177
|
+
if self.is_kw("flow"):
|
|
178
|
+
prog["pre"] = pre
|
|
179
|
+
self.advance()
|
|
180
|
+
prog["name"] = self.expect_name("program name")
|
|
181
|
+
if self.is_kw("version"):
|
|
182
|
+
self.advance()
|
|
183
|
+
if self.tok.kind != "NUM":
|
|
184
|
+
raise self.error("expected version number")
|
|
185
|
+
prog["version"] = self.advance().value
|
|
186
|
+
if note:
|
|
187
|
+
prog["note"] = note
|
|
188
|
+
self.end_of_line(prog)
|
|
189
|
+
note = self.gather(items)
|
|
190
|
+
else:
|
|
191
|
+
items.extend(pre)
|
|
192
|
+
while self.tok.kind != "EOF":
|
|
193
|
+
item = self.parse_item()
|
|
194
|
+
self.attach(item, note)
|
|
195
|
+
items.append(item)
|
|
196
|
+
note = self.gather(items)
|
|
197
|
+
return prog
|
|
198
|
+
|
|
199
|
+
def parse_params(self, typed=False):
|
|
200
|
+
"""'(a, b)' -> ([names], None) or, typed, '(int a, float b)' -> ([names], [types]).
|
|
201
|
+
|
|
202
|
+
Returns (None, None) if there is no parameter list."""
|
|
203
|
+
params, types = [], []
|
|
204
|
+
if self.is_op("("):
|
|
205
|
+
self.advance()
|
|
206
|
+
if not self.is_op(")"):
|
|
207
|
+
while True:
|
|
208
|
+
if typed:
|
|
209
|
+
if not self.is_type():
|
|
210
|
+
if self.tok.kind == "NAME":
|
|
211
|
+
raise self.error("give the parameter a type: 'int %s' (int, float, str or time)"
|
|
212
|
+
% self.tok.value)
|
|
213
|
+
raise self.error("expected a parameter type (int, float, str or time)")
|
|
214
|
+
types.append(self.advance().value)
|
|
215
|
+
params.append(self.expect_name("parameter name"))
|
|
216
|
+
if self.is_op(","):
|
|
217
|
+
self.advance()
|
|
218
|
+
continue
|
|
219
|
+
break
|
|
220
|
+
self.expect_op(")")
|
|
221
|
+
return params, (types if typed else None)
|
|
222
|
+
return None, None
|
|
223
|
+
|
|
224
|
+
def is_type(self):
|
|
225
|
+
return self.tok.kind == "KW" and self.tok.value in TYPE_KEYWORDS
|
|
226
|
+
|
|
227
|
+
def parse_returns(self):
|
|
228
|
+
"""Optional '-> type'."""
|
|
229
|
+
if not self.is_op("->"):
|
|
230
|
+
return None
|
|
231
|
+
self.advance()
|
|
232
|
+
if not self.is_type():
|
|
233
|
+
raise self.error("expected a return type after '->' (int, float, str or time)")
|
|
234
|
+
return self.advance().value
|
|
235
|
+
|
|
236
|
+
def var_hint(self):
|
|
237
|
+
"""'var x' was FlowScript v1: point to the typed declarations."""
|
|
238
|
+
if self.tok.kind == "NAME" and self.tok.value == "var" and self.peek().kind == "NAME":
|
|
239
|
+
raise self.error("declare variables with their type: 'int %s', 'float %s', 'str %s' or 'time %s'"
|
|
240
|
+
% ((self.peek().value,) * 4))
|
|
241
|
+
|
|
242
|
+
def parse_decl(self, node_type):
|
|
243
|
+
"""'TYPE name [= expr]' (globals, state variables, locals)."""
|
|
244
|
+
t = self.advance()
|
|
245
|
+
name = self.expect_name("variable name")
|
|
246
|
+
value = None
|
|
247
|
+
if self.is_op("="):
|
|
248
|
+
self.advance()
|
|
249
|
+
value = self.parse_expr()
|
|
250
|
+
n = self.node(node_type, t, name=name, vtype=t.value, value=value)
|
|
251
|
+
self.end_of_line(n)
|
|
252
|
+
return n
|
|
253
|
+
|
|
254
|
+
def parse_item(self):
|
|
255
|
+
t = self.tok
|
|
256
|
+
if t.kind == "INDENT":
|
|
257
|
+
raise self.error("unexpected indentation")
|
|
258
|
+
self.check_end()
|
|
259
|
+
self.var_hint()
|
|
260
|
+
if t.kind != "KW":
|
|
261
|
+
raise self.error("expected a declaration (extern, event, timer, const, int/float/str/time "
|
|
262
|
+
"variable, func, state, initial state, any_state)")
|
|
263
|
+
kw = t.value
|
|
264
|
+
if kw in TYPE_KEYWORDS:
|
|
265
|
+
return self.parse_decl("var")
|
|
266
|
+
if kw == "extern":
|
|
267
|
+
self.advance()
|
|
268
|
+
name = self.expect_name("extern name")
|
|
269
|
+
params, types = self.parse_params(typed=True)
|
|
270
|
+
if params is None:
|
|
271
|
+
raise self.error("extern needs a parameter list, e.g. extern %s()" % name)
|
|
272
|
+
n = self.node("extern", t, name=name, params=params, ptypes=types, returns=self.parse_returns())
|
|
273
|
+
self.end_of_line(n)
|
|
274
|
+
return n
|
|
275
|
+
if kw == "event":
|
|
276
|
+
self.advance()
|
|
277
|
+
name = self.expect_name("event name")
|
|
278
|
+
params, types = self.parse_params(typed=True)
|
|
279
|
+
n = self.node("event", t, name=name, params=params or [], ptypes=types or [])
|
|
280
|
+
self.end_of_line(n)
|
|
281
|
+
return n
|
|
282
|
+
if kw == "timer":
|
|
283
|
+
self.advance()
|
|
284
|
+
n = self.node("timer", t, name=self.expect_name("timer name"))
|
|
285
|
+
self.end_of_line(n)
|
|
286
|
+
return n
|
|
287
|
+
if kw == "const":
|
|
288
|
+
self.advance()
|
|
289
|
+
name = self.expect_name()
|
|
290
|
+
if not self.is_op("="):
|
|
291
|
+
raise self.error("const needs a value")
|
|
292
|
+
self.advance()
|
|
293
|
+
n = self.node(kw, t, name=name, value=self.parse_expr())
|
|
294
|
+
self.end_of_line(n)
|
|
295
|
+
return n
|
|
296
|
+
if kw == "func":
|
|
297
|
+
self.advance()
|
|
298
|
+
name = self.expect_name("function name")
|
|
299
|
+
params, types = self.parse_params(typed=True)
|
|
300
|
+
if params is None:
|
|
301
|
+
raise self.error("func needs a parameter list, e.g. func %s()" % name)
|
|
302
|
+
n = self.node("func", t, name=name, params=params, ptypes=types, returns=self.parse_returns(), body=[])
|
|
303
|
+
self.open_block(n, "func")
|
|
304
|
+
n["body"] = self.parse_block()
|
|
305
|
+
return n
|
|
306
|
+
if kw == "on":
|
|
307
|
+
raise self.error("event handlers go inside a state, or inside 'any_state:' to "
|
|
308
|
+
"handle the event in every state")
|
|
309
|
+
if kw == "initial":
|
|
310
|
+
self.advance()
|
|
311
|
+
if not self.is_kw("state"):
|
|
312
|
+
raise self.error("'initial' marks a state: write 'initial state NAME:'")
|
|
313
|
+
n = self.parse_item()
|
|
314
|
+
n["initial"] = True
|
|
315
|
+
return n
|
|
316
|
+
if kw in ("state", "any_state"):
|
|
317
|
+
self.advance()
|
|
318
|
+
if kw == "state":
|
|
319
|
+
n = self.node("state", t, name=self.expect_name("state name"), vars=[], handlers=[])
|
|
320
|
+
else:
|
|
321
|
+
n = self.node("any_state", t, handlers=[])
|
|
322
|
+
self.open_block(n, kw)
|
|
323
|
+
while True:
|
|
324
|
+
loose = []
|
|
325
|
+
note = self.gather(loose)
|
|
326
|
+
# state variables come first; comments above them stay with them
|
|
327
|
+
in_vars = kw == "state" and self.is_type() and not n["handlers"]
|
|
328
|
+
(n["vars"] if in_vars else n["handlers"]).extend(loose)
|
|
329
|
+
if self.tok.kind == "DEDENT":
|
|
330
|
+
self.advance()
|
|
331
|
+
break
|
|
332
|
+
if self.is_kw("pass"):
|
|
333
|
+
self.skip_pass(n["handlers"], note)
|
|
334
|
+
continue
|
|
335
|
+
self.var_hint()
|
|
336
|
+
if self.is_type():
|
|
337
|
+
if kw == "any_state":
|
|
338
|
+
raise self.error("variables shared by every state are ordinary globals: "
|
|
339
|
+
"declare them at the top level")
|
|
340
|
+
if n["handlers"]:
|
|
341
|
+
raise self.error("state variables go at the top of the state, "
|
|
342
|
+
"before its handlers")
|
|
343
|
+
n["vars"].append(self.attach(self.parse_decl("var"), note))
|
|
344
|
+
continue
|
|
345
|
+
if not self.is_kw("on"):
|
|
346
|
+
raise self.error("only variables (int/float/str/time) and 'on' handlers are allowed inside a state" if kw == "state"
|
|
347
|
+
else "only 'on' handlers are allowed inside any_state")
|
|
348
|
+
n["handlers"].append(self.attach(self.parse_handler(), note))
|
|
349
|
+
return n
|
|
350
|
+
raise self.error("unexpected '%s' at top level" % kw)
|
|
351
|
+
|
|
352
|
+
def parse_handler(self):
|
|
353
|
+
t = self.expect_kw("on")
|
|
354
|
+
if self.tok.kind == "NAME" or self.is_kw("timer", "event"):
|
|
355
|
+
event = self.advance().value
|
|
356
|
+
else:
|
|
357
|
+
raise self.error("expected event name")
|
|
358
|
+
params, _ = self.parse_params()
|
|
359
|
+
n = self.node("on", t, event=event, params=params, body=[])
|
|
360
|
+
self.open_block(n, "on")
|
|
361
|
+
n["body"] = self.parse_block()
|
|
362
|
+
return n
|
|
363
|
+
|
|
364
|
+
# -- statements --------------------------------------------------------
|
|
365
|
+
def check_end(self):
|
|
366
|
+
t = self.tok
|
|
367
|
+
if t.kind == "NAME" and t.value == "goto" and self.peek().kind == "NAME":
|
|
368
|
+
raise self.error("use 'goto_state %s' to change state" % self.peek().value)
|
|
369
|
+
if t.kind == "NAME" and t.value == "emit" and self.peek().kind == "NAME":
|
|
370
|
+
raise self.error("use 'raise_event %s' to raise an event" % self.peek().value)
|
|
371
|
+
if t.kind == "NAME" and t.value == "end" and self.peek().kind in ("NEWLINE", "COMMENT", "EOF"):
|
|
372
|
+
raise self.error("'end' is not used: a block ends where the indentation goes back")
|
|
373
|
+
|
|
374
|
+
def parse_block(self):
|
|
375
|
+
"""Statements up to the DEDENT that closes the block (INDENT already consumed)."""
|
|
376
|
+
body = []
|
|
377
|
+
while True:
|
|
378
|
+
note = self.gather(body)
|
|
379
|
+
if self.tok.kind == "DEDENT":
|
|
380
|
+
self.advance()
|
|
381
|
+
return body
|
|
382
|
+
if self.is_kw("pass"):
|
|
383
|
+
self.skip_pass(body, note)
|
|
384
|
+
continue
|
|
385
|
+
body.append(self.attach(self.parse_stmt(), note))
|
|
386
|
+
|
|
387
|
+
def parse_stmt(self):
|
|
388
|
+
t = self.tok
|
|
389
|
+
if t.kind == "INDENT":
|
|
390
|
+
raise self.error("unexpected indentation")
|
|
391
|
+
self.check_end()
|
|
392
|
+
self.var_hint()
|
|
393
|
+
if t.kind == "KW":
|
|
394
|
+
kw = t.value
|
|
395
|
+
if kw in TYPE_KEYWORDS and self.peek().kind == "NAME":
|
|
396
|
+
return self.parse_decl("local")
|
|
397
|
+
if kw in TYPE_KEYWORDS and self.peek().kind == "OP" and self.peek().value == "(":
|
|
398
|
+
call = self.parse_primary()
|
|
399
|
+
n = self.node("call", t, name=call["name"], args=call["args"])
|
|
400
|
+
self.end_of_line(n)
|
|
401
|
+
return n
|
|
402
|
+
if kw == "if":
|
|
403
|
+
self.advance()
|
|
404
|
+
n = self.node("if", t, branches=[], else_body=None)
|
|
405
|
+
cond = self.parse_expr()
|
|
406
|
+
self.open_block(n, "if")
|
|
407
|
+
n["branches"].append({"cond": cond, "body": self.parse_block()})
|
|
408
|
+
while self.is_kw("elif"):
|
|
409
|
+
self.advance()
|
|
410
|
+
cond = self.parse_expr()
|
|
411
|
+
self.open_block(None, "elif")
|
|
412
|
+
n["branches"].append({"cond": cond, "body": self.parse_block()})
|
|
413
|
+
if self.is_kw("else"):
|
|
414
|
+
self.advance()
|
|
415
|
+
self.open_block(None, "else")
|
|
416
|
+
n["else_body"] = self.parse_block()
|
|
417
|
+
return n
|
|
418
|
+
if kw == "while":
|
|
419
|
+
self.advance()
|
|
420
|
+
n = self.node("while", t, cond=None, body=[])
|
|
421
|
+
n["cond"] = self.parse_expr()
|
|
422
|
+
self.open_block(n, "while")
|
|
423
|
+
n["body"] = self.parse_block()
|
|
424
|
+
return n
|
|
425
|
+
if kw == "goto_state":
|
|
426
|
+
self.advance()
|
|
427
|
+
n = self.node("goto", t, state=self.expect_name("state name"))
|
|
428
|
+
self.end_of_line(n)
|
|
429
|
+
return n
|
|
430
|
+
if kw == "raise_event":
|
|
431
|
+
self.advance()
|
|
432
|
+
name = self.expect_name("event name")
|
|
433
|
+
args = self.parse_args() if self.is_op("(") else []
|
|
434
|
+
n = self.node("emit", t, event=name, args=args)
|
|
435
|
+
self.end_of_line(n)
|
|
436
|
+
return n
|
|
437
|
+
if kw == "start":
|
|
438
|
+
self.advance()
|
|
439
|
+
name = self.expect_name("timer name")
|
|
440
|
+
every = False
|
|
441
|
+
if self.is_kw("every"):
|
|
442
|
+
self.advance()
|
|
443
|
+
every = True
|
|
444
|
+
n = self.node("start", t, timer=name, every=every, ms=self.parse_expr())
|
|
445
|
+
self.end_of_line(n)
|
|
446
|
+
return n
|
|
447
|
+
if kw == "stop":
|
|
448
|
+
self.advance()
|
|
449
|
+
n = self.node("stop", t, timer=self.expect_name("timer name"))
|
|
450
|
+
self.end_of_line(n)
|
|
451
|
+
return n
|
|
452
|
+
if kw in ("elif", "else"):
|
|
453
|
+
raise self.error("'%s' without a matching 'if' (check the indentation)" % kw)
|
|
454
|
+
if kw == "return":
|
|
455
|
+
self.advance()
|
|
456
|
+
value = None
|
|
457
|
+
if self.tok.kind not in ("NEWLINE", "COMMENT", "EOF"):
|
|
458
|
+
value = self.parse_expr()
|
|
459
|
+
n = self.node("return", t, value=value)
|
|
460
|
+
self.end_of_line(n)
|
|
461
|
+
return n
|
|
462
|
+
raise self.error("unexpected '%s'" % kw)
|
|
463
|
+
if t.kind == "NAME":
|
|
464
|
+
nxt = self.peek()
|
|
465
|
+
if nxt.kind == "OP" and nxt.value == "=":
|
|
466
|
+
name = self.advance().value
|
|
467
|
+
self.advance()
|
|
468
|
+
n = self.node("assign", t, target=name, value=self.parse_expr())
|
|
469
|
+
self.end_of_line(n)
|
|
470
|
+
return n
|
|
471
|
+
if nxt.kind == "OP" and nxt.value == "(":
|
|
472
|
+
call = self.parse_primary()
|
|
473
|
+
n = self.node("call", t, name=call["name"], args=call["args"])
|
|
474
|
+
self.end_of_line(n)
|
|
475
|
+
return n
|
|
476
|
+
raise self.error("expected '=' or '(' after '%s'" % t.value, nxt)
|
|
477
|
+
raise self.error("expected a statement")
|
|
478
|
+
|
|
479
|
+
# -- expressions -------------------------------------------------------
|
|
480
|
+
def parse_args(self):
|
|
481
|
+
self.expect_op("(")
|
|
482
|
+
args = []
|
|
483
|
+
if not self.is_op(")"):
|
|
484
|
+
while True:
|
|
485
|
+
args.append(self.parse_expr())
|
|
486
|
+
if self.is_op(","):
|
|
487
|
+
self.advance()
|
|
488
|
+
continue
|
|
489
|
+
break
|
|
490
|
+
self.expect_op(")")
|
|
491
|
+
return args
|
|
492
|
+
|
|
493
|
+
def parse_expr(self):
|
|
494
|
+
return self.parse_level(0)
|
|
495
|
+
|
|
496
|
+
def parse_level(self, level):
|
|
497
|
+
if level >= len(BINARY_LEVELS):
|
|
498
|
+
return self.parse_unary()
|
|
499
|
+
ops = BINARY_LEVELS[level]
|
|
500
|
+
if ops is None: # prefix 'not'
|
|
501
|
+
if self.is_kw("not"):
|
|
502
|
+
t = self.advance()
|
|
503
|
+
n = self.node("unary", t, op="not", operand=None)
|
|
504
|
+
n["operand"] = self.parse_level(level)
|
|
505
|
+
return n
|
|
506
|
+
return self.parse_level(level + 1)
|
|
507
|
+
left = self.parse_level(level + 1)
|
|
508
|
+
while self.tok.kind in ("OP", "KW") and self.tok.value in ops:
|
|
509
|
+
t = self.advance()
|
|
510
|
+
right = self.parse_level(level + 1)
|
|
511
|
+
n = self.node("binary", t, op=t.value, left=left, right=right)
|
|
512
|
+
if t.value in COMPARE_OPS and self.tok.kind == "OP" and self.tok.value in COMPARE_OPS:
|
|
513
|
+
raise self.error("comparisons cannot be chained; use 'and'")
|
|
514
|
+
left = n
|
|
515
|
+
return left
|
|
516
|
+
|
|
517
|
+
def parse_unary(self):
|
|
518
|
+
if self.is_op("-", "~"):
|
|
519
|
+
t = self.advance()
|
|
520
|
+
operand = self.parse_unary()
|
|
521
|
+
if t.value == "-" and operand["type"] == "num" and operand["value"] >= 0:
|
|
522
|
+
operand["value"] = -operand["value"]
|
|
523
|
+
operand["text"] = "-" + operand.get("text", str(-operand["value"]))
|
|
524
|
+
return operand
|
|
525
|
+
n = self.node("unary", t, op=t.value, operand=operand)
|
|
526
|
+
return n
|
|
527
|
+
return self.parse_primary()
|
|
528
|
+
|
|
529
|
+
def parse_primary(self):
|
|
530
|
+
t = self.tok
|
|
531
|
+
if t.kind in ("NUM", "FLOAT"):
|
|
532
|
+
self.advance()
|
|
533
|
+
return self.node("num", t, value=t.value, text=t.text)
|
|
534
|
+
if t.kind == "KW" and t.value in TYPE_KEYWORDS and self.peek().kind == "OP" and self.peek().value == "(":
|
|
535
|
+
self.advance()
|
|
536
|
+
n = self.node("callx", t, name=t.value, args=[])
|
|
537
|
+
n["args"] = self.parse_args()
|
|
538
|
+
return n
|
|
539
|
+
if t.kind == "STR":
|
|
540
|
+
self.advance()
|
|
541
|
+
return self.node("str", t, value=t.value)
|
|
542
|
+
if t.kind == "KW" and t.value in ("true", "false"):
|
|
543
|
+
self.advance()
|
|
544
|
+
return self.node("bool", t, value=1 if t.value == "true" else 0)
|
|
545
|
+
if t.kind == "NAME":
|
|
546
|
+
self.advance()
|
|
547
|
+
if self.is_op("("):
|
|
548
|
+
n = self.node("callx", t, name=t.value, args=[])
|
|
549
|
+
n["args"] = self.parse_args()
|
|
550
|
+
return n
|
|
551
|
+
return self.node("name", t, name=t.value)
|
|
552
|
+
if self.is_op("("):
|
|
553
|
+
self.advance()
|
|
554
|
+
self.depth += 1
|
|
555
|
+
if self.depth > MAX_NESTING:
|
|
556
|
+
raise self.error("expression nested too deeply (max %d levels)" % MAX_NESTING)
|
|
557
|
+
e = self.parse_expr()
|
|
558
|
+
self.depth -= 1
|
|
559
|
+
self.expect_op(")")
|
|
560
|
+
return e
|
|
561
|
+
raise self.error("expected an expression, got %s" % self.describe(t))
|
|
562
|
+
|
|
563
|
+
|
|
564
|
+
def parse(src):
|
|
565
|
+
try:
|
|
566
|
+
return Parser(src).parse_program()
|
|
567
|
+
except RecursionError:
|
|
568
|
+
raise FlowError("program is nested too deeply")
|
|
569
|
+
|
|
570
|
+
|
|
571
|
+
def parse_expression(text):
|
|
572
|
+
p = Parser(text.strip())
|
|
573
|
+
while p.tok.kind == "NEWLINE":
|
|
574
|
+
p.advance()
|
|
575
|
+
e = p.parse_expr()
|
|
576
|
+
if p.tok.kind not in ("NEWLINE", "DEDENT", "EOF"):
|
|
577
|
+
raise p.error("unexpected %s after expression" % p.describe(p.tok))
|
|
578
|
+
return e
|