godcode-engine 4.0.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.
- godcode/__init__.py +86 -0
- godcode/__main__.py +7 -0
- godcode/agentics.py +235 -0
- godcode/ast.py +227 -0
- godcode/chain.py +210 -0
- godcode/cli.py +698 -0
- godcode/environment.py +74 -0
- godcode/errors.py +69 -0
- godcode/interpreter.py +1104 -0
- godcode/ledger.py +88 -0
- godcode/lexer.py +218 -0
- godcode/lsp.py +677 -0
- godcode/parser.py +525 -0
- godcode/plugins.py +255 -0
- godcode/registry.py +515 -0
- godcode/sandbox.py +385 -0
- godcode/scrolls/covenant.god +10 -0
- godcode/scrolls/covenant.toml +6 -0
- godcode/scrolls/lists.god +53 -0
- godcode/scrolls/lists.toml +6 -0
- godcode/scrolls/math.god +64 -0
- godcode/scrolls/math.toml +6 -0
- godcode/scrolls/prophecy.god +14 -0
- godcode/scrolls/prophecy.toml +6 -0
- godcode/scrolls/strings.god +32 -0
- godcode/scrolls/strings.toml +6 -0
- godcode/scrolls/time.god +10 -0
- godcode/scrolls/time.toml +6 -0
- godcode/spirit.py +180 -0
- godcode/tokens.py +91 -0
- godcode/tools.py +378 -0
- godcode/values.py +56 -0
- godcode_engine-4.0.0.dist-info/METADATA +212 -0
- godcode_engine-4.0.0.dist-info/RECORD +38 -0
- godcode_engine-4.0.0.dist-info/WHEEL +5 -0
- godcode_engine-4.0.0.dist-info/entry_points.txt +2 -0
- godcode_engine-4.0.0.dist-info/licenses/LICENSE +201 -0
- godcode_engine-4.0.0.dist-info/top_level.txt +1 -0
godcode/parser.py
ADDED
|
@@ -0,0 +1,525 @@
|
|
|
1
|
+
"""Recursive-descent parser for God Code v2.0.
|
|
2
|
+
|
|
3
|
+
``Parser(tokens).parse()`` returns a ``Program``. A program is either a
|
|
4
|
+
``BEGIN CREATION ... END CREATION`` block (wrapped in a Program) or a
|
|
5
|
+
bare sequence of statements (for scrolls / fragments).
|
|
6
|
+
|
|
7
|
+
Statement forms (spec §4):
|
|
8
|
+
- DECLARE name AS expr (, expr)* -> Declare (multi-expr -> ListLiteral)
|
|
9
|
+
- BREATHE LIFE INTO name -> Breathe
|
|
10
|
+
- REVEAL(expr) -> Reveal
|
|
11
|
+
- PROPHESY <rest of line> -> Prophesy
|
|
12
|
+
- ASCEND / REFLECT -> Ascend / Reflect
|
|
13
|
+
- BLESS name / ANOINT name -> Bless / Anoint
|
|
14
|
+
- SEAL expr / TESTIFY expr -> SealStmt / Testify
|
|
15
|
+
- IF expr THEN ... (block: ENDIF / inline) -> IfStmt
|
|
16
|
+
- FOR name IN expr ... (block: ENDFOR / legacy open / inline) -> ForLoop
|
|
17
|
+
- WHILE expr DO ... (block: ENDWHILE / inline) -> WhileLoop
|
|
18
|
+
- DEFINE RITE name(params) ... END RITE (block or inline) -> DefineRite
|
|
19
|
+
- RETURN expr? -> Return
|
|
20
|
+
- IMPORT "path" -> Import
|
|
21
|
+
- INVOKE name(args) | any expression -> CallExpr / ExprStmt
|
|
22
|
+
|
|
23
|
+
Expression precedence (low -> high):
|
|
24
|
+
or -> and -> not -> comparison (IS [NOT], =, ==, !=, <, >, <=, >=)
|
|
25
|
+
-> additive (+ -) -> multiplicative (* / %) -> unary (-, NOT) -> primary
|
|
26
|
+
``IS``/``IS NOT`` become ``==``/``!=`` BinaryOps.
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
from __future__ import annotations
|
|
30
|
+
|
|
31
|
+
from . import ast as A
|
|
32
|
+
from .errors import ParseError
|
|
33
|
+
from .tokens import Token, TokenType
|
|
34
|
+
|
|
35
|
+
TT = TokenType
|
|
36
|
+
|
|
37
|
+
_EXPR_STARTS = {
|
|
38
|
+
TT.NUMBER, TT.STRING, TT.IDENT, TT.LPAREN, TT.LBRACKET,
|
|
39
|
+
TT.MINUS, TT.NOT, TT.TRUE, TT.FALSE, TT.VOID, TT.INVOKE,
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
# Tokens after which RETURN takes no expression.
|
|
43
|
+
_RETURN_TERMINATORS = {
|
|
44
|
+
TT.NEWLINE, TT.EOF, TT.END, TT.ENDIF, TT.ELSE, TT.ENDFOR, TT.ENDWHILE,
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class Parser:
|
|
49
|
+
def __init__(self, tokens: list[Token]):
|
|
50
|
+
if not tokens:
|
|
51
|
+
raise ParseError("The scroll is empty; there is nothing to reveal")
|
|
52
|
+
self.tokens = tokens
|
|
53
|
+
self.pos = 0
|
|
54
|
+
|
|
55
|
+
# -- public ------------------------------------------------------------
|
|
56
|
+
|
|
57
|
+
def parse(self) -> A.Program:
|
|
58
|
+
self._skip_newlines()
|
|
59
|
+
first = self._peek()
|
|
60
|
+
if self._check(TT.BEGIN):
|
|
61
|
+
block = self._parse_creation_block()
|
|
62
|
+
self._skip_newlines()
|
|
63
|
+
t = self._peek()
|
|
64
|
+
if t.type is not TT.EOF:
|
|
65
|
+
raise ParseError(
|
|
66
|
+
f"The creation is sealed, yet {self._describe(t)} remains; "
|
|
67
|
+
"a scroll holds only one creation",
|
|
68
|
+
line=t.line, col=t.col,
|
|
69
|
+
)
|
|
70
|
+
return A.Program(statements=[block], line=block.line, col=block.col)
|
|
71
|
+
stmts = self._parse_body(
|
|
72
|
+
end={TT.EOF},
|
|
73
|
+
missing="the end of the scroll",
|
|
74
|
+
opening=None,
|
|
75
|
+
)
|
|
76
|
+
return A.Program(statements=stmts, line=first.line, col=first.col)
|
|
77
|
+
|
|
78
|
+
# -- cursor helpers ----------------------------------------------------
|
|
79
|
+
|
|
80
|
+
def _peek(self) -> Token:
|
|
81
|
+
return self.tokens[self.pos]
|
|
82
|
+
|
|
83
|
+
def _peek_next(self) -> Token:
|
|
84
|
+
if self.pos + 1 < len(self.tokens):
|
|
85
|
+
return self.tokens[self.pos + 1]
|
|
86
|
+
return self.tokens[-1]
|
|
87
|
+
|
|
88
|
+
def _advance(self) -> Token:
|
|
89
|
+
t = self.tokens[self.pos]
|
|
90
|
+
if self.pos < len(self.tokens) - 1:
|
|
91
|
+
self.pos += 1
|
|
92
|
+
return t
|
|
93
|
+
|
|
94
|
+
def _check(self, tt: TokenType) -> bool:
|
|
95
|
+
return self._peek().type is tt
|
|
96
|
+
|
|
97
|
+
def _expect(self, tt: TokenType, what: str | None = None) -> Token:
|
|
98
|
+
t = self._peek()
|
|
99
|
+
if t.type is not tt:
|
|
100
|
+
raise ParseError(
|
|
101
|
+
f"Expected {what or tt.name} but found {self._describe(t)}",
|
|
102
|
+
line=t.line, col=t.col,
|
|
103
|
+
)
|
|
104
|
+
return self._advance()
|
|
105
|
+
|
|
106
|
+
def _skip_newlines(self) -> None:
|
|
107
|
+
while self._check(TT.NEWLINE):
|
|
108
|
+
self._advance()
|
|
109
|
+
|
|
110
|
+
@staticmethod
|
|
111
|
+
def _describe(t: Token) -> str:
|
|
112
|
+
if t.type is TT.EOF:
|
|
113
|
+
return "the end of the scroll"
|
|
114
|
+
if t.type is TT.NEWLINE:
|
|
115
|
+
return "the end of the line"
|
|
116
|
+
if t.type is TT.IDENT:
|
|
117
|
+
return f"the name '{t.value}'"
|
|
118
|
+
if t.type is TT.NUMBER:
|
|
119
|
+
return f"the number {t.value!r}"
|
|
120
|
+
if t.type is TT.STRING:
|
|
121
|
+
return f"the text {t.value!r}"
|
|
122
|
+
return f"'{t.value}'"
|
|
123
|
+
|
|
124
|
+
# -- statement lists ---------------------------------------------------
|
|
125
|
+
|
|
126
|
+
def _parse_body(self, *, end: set[TokenType], missing: str,
|
|
127
|
+
opening: Token | None, legacy_for: bool = False) -> list:
|
|
128
|
+
"""Parse statements until a token in ``end`` (left unconsumed).
|
|
129
|
+
|
|
130
|
+
With ``legacy_for``, END CREATION / END RITE / EOF also end the body
|
|
131
|
+
without being consumed (the original sample.godcode's open FOR).
|
|
132
|
+
Otherwise hitting them raises ParseError naming what was missing.
|
|
133
|
+
"""
|
|
134
|
+
stmts: list = []
|
|
135
|
+
while True:
|
|
136
|
+
self._skip_newlines()
|
|
137
|
+
t = self._peek()
|
|
138
|
+
if t.type in end:
|
|
139
|
+
return stmts
|
|
140
|
+
if t.type is TT.EOF:
|
|
141
|
+
if legacy_for:
|
|
142
|
+
return stmts
|
|
143
|
+
raise ParseError(
|
|
144
|
+
f"The scroll ends before {missing}; "
|
|
145
|
+
+ (f"the {opening.value} opened on line {opening.line} "
|
|
146
|
+
"was never closed" if opening else
|
|
147
|
+
"every opened block must be closed"),
|
|
148
|
+
line=t.line, col=t.col,
|
|
149
|
+
)
|
|
150
|
+
if t.type is TT.END and self._peek_next().type in (TT.CREATION, TT.RITE):
|
|
151
|
+
if legacy_for:
|
|
152
|
+
return stmts
|
|
153
|
+
raise ParseError(
|
|
154
|
+
f"Found {t.value} {self._peek_next().value} before {missing}; "
|
|
155
|
+
"every opened block must be closed in its own time",
|
|
156
|
+
line=t.line, col=t.col,
|
|
157
|
+
)
|
|
158
|
+
stmts.append(self._parse_statement())
|
|
159
|
+
|
|
160
|
+
# -- top level ---------------------------------------------------------
|
|
161
|
+
|
|
162
|
+
def _parse_creation_block(self) -> A.CreationBlock:
|
|
163
|
+
b = self._expect(TT.BEGIN)
|
|
164
|
+
self._expect(TT.CREATION)
|
|
165
|
+
self._skip_newlines()
|
|
166
|
+
stmts = self._parse_body(end={TT.END}, missing="END CREATION", opening=b)
|
|
167
|
+
self._expect(TT.END)
|
|
168
|
+
self._expect(TT.CREATION)
|
|
169
|
+
return A.CreationBlock(statements=stmts, line=b.line, col=b.col)
|
|
170
|
+
|
|
171
|
+
# -- statements --------------------------------------------------------
|
|
172
|
+
|
|
173
|
+
def _parse_statement(self):
|
|
174
|
+
t = self._peek()
|
|
175
|
+
tt = t.type
|
|
176
|
+
if tt is TT.DECLARE:
|
|
177
|
+
return self._parse_declare()
|
|
178
|
+
if tt is TT.BREATHE:
|
|
179
|
+
return self._parse_breathe()
|
|
180
|
+
if tt is TT.REVEAL:
|
|
181
|
+
return self._parse_reveal()
|
|
182
|
+
if tt is TT.PROPHESY:
|
|
183
|
+
return self._parse_prophesy()
|
|
184
|
+
if tt is TT.ASCEND:
|
|
185
|
+
self._advance()
|
|
186
|
+
return A.Ascend(line=t.line, col=t.col)
|
|
187
|
+
if tt is TT.REFLECT:
|
|
188
|
+
self._advance()
|
|
189
|
+
return A.Reflect(line=t.line, col=t.col)
|
|
190
|
+
if tt is TT.BLESS:
|
|
191
|
+
return self._parse_named_single(TT.BLESS, A.Bless)
|
|
192
|
+
if tt is TT.ANOINT:
|
|
193
|
+
return self._parse_named_single(TT.ANOINT, A.Anoint)
|
|
194
|
+
if tt is TT.SEAL:
|
|
195
|
+
self._advance()
|
|
196
|
+
return A.SealStmt(expr=self._parse_expr(), line=t.line, col=t.col)
|
|
197
|
+
if tt is TT.TESTIFY:
|
|
198
|
+
self._advance()
|
|
199
|
+
return A.Testify(expr=self._parse_expr(), line=t.line, col=t.col)
|
|
200
|
+
if tt is TT.IF:
|
|
201
|
+
return self._parse_if()
|
|
202
|
+
if tt is TT.FOR:
|
|
203
|
+
return self._parse_for()
|
|
204
|
+
if tt is TT.WHILE:
|
|
205
|
+
return self._parse_while()
|
|
206
|
+
if tt is TT.DEFINE:
|
|
207
|
+
return self._parse_define_rite()
|
|
208
|
+
if tt is TT.RETURN:
|
|
209
|
+
return self._parse_return()
|
|
210
|
+
if tt is TT.IMPORT:
|
|
211
|
+
return self._parse_import()
|
|
212
|
+
if tt in _EXPR_STARTS:
|
|
213
|
+
expr = self._parse_expr()
|
|
214
|
+
return A.ExprStmt(expr=expr, line=t.line, col=t.col)
|
|
215
|
+
raise ParseError(
|
|
216
|
+
f"Unexpected {self._describe(t)}; a decree must begin with "
|
|
217
|
+
"a holy word (DECLARE, IF, FOR, REVEAL, ...)",
|
|
218
|
+
line=t.line, col=t.col,
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
def _parse_named_single(self, tt: TokenType, node_cls):
|
|
222
|
+
kw = self._expect(tt)
|
|
223
|
+
name = self._expect(TT.IDENT, "a name").value
|
|
224
|
+
return node_cls(name=name, line=kw.line, col=kw.col)
|
|
225
|
+
|
|
226
|
+
def _parse_declare(self) -> A.Declare:
|
|
227
|
+
d = self._expect(TT.DECLARE)
|
|
228
|
+
# --- v4.0: DECLARE INTENT "words..." ON rite_name ---
|
|
229
|
+
# INTENT and ON are soft keywords (plain identifiers by text), so
|
|
230
|
+
# `DECLARE intent AS x` keeps working: only DECLARE followed by the
|
|
231
|
+
# word INTENT and then a quoted string takes the intent path.
|
|
232
|
+
nxt, nxt2 = self._peek(), self._peek_next()
|
|
233
|
+
if (nxt.type is TT.IDENT and nxt.value.upper() == "INTENT"
|
|
234
|
+
and nxt2.type is TT.STRING):
|
|
235
|
+
self._advance() # the word INTENT
|
|
236
|
+
text = self._expect(TT.STRING, "the intent in quotes").value
|
|
237
|
+
on = self._peek()
|
|
238
|
+
if not (on.type is TT.IDENT and on.value.upper() == "ON"):
|
|
239
|
+
raise ParseError(
|
|
240
|
+
'DECLARE INTENT needs ON and a rite name, as in '
|
|
241
|
+
'DECLARE INTENT "bring peace" ON evening_blessing',
|
|
242
|
+
line=on.line, col=on.col,
|
|
243
|
+
)
|
|
244
|
+
self._advance() # the word ON
|
|
245
|
+
rite = self._expect(TT.IDENT, "a rite name").value
|
|
246
|
+
return A.DeclareIntent(text=text, rite=rite, line=d.line, col=d.col)
|
|
247
|
+
# --- end v4.0 ---
|
|
248
|
+
name = self._expect(TT.IDENT, "a name to declare").value
|
|
249
|
+
self._expect(TT.AS)
|
|
250
|
+
items = [self._parse_expr()]
|
|
251
|
+
while self._check(TT.COMMA):
|
|
252
|
+
self._advance()
|
|
253
|
+
items.append(self._parse_expr())
|
|
254
|
+
if len(items) == 1:
|
|
255
|
+
value = items[0]
|
|
256
|
+
else:
|
|
257
|
+
value = A.ListLiteral(items=items, line=items[0].line, col=items[0].col)
|
|
258
|
+
return A.Declare(name=name, value=value, line=d.line, col=d.col)
|
|
259
|
+
|
|
260
|
+
def _parse_breathe(self) -> A.Breathe:
|
|
261
|
+
b = self._expect(TT.BREATHE)
|
|
262
|
+
self._expect(TT.LIFE)
|
|
263
|
+
self._expect(TT.INTO)
|
|
264
|
+
name = self._expect(TT.IDENT, "a name to breathe into").value
|
|
265
|
+
return A.Breathe(name=name, line=b.line, col=b.col)
|
|
266
|
+
|
|
267
|
+
def _parse_reveal(self) -> A.Reveal:
|
|
268
|
+
r = self._expect(TT.REVEAL)
|
|
269
|
+
self._expect(TT.LPAREN)
|
|
270
|
+
expr = self._parse_expr()
|
|
271
|
+
self._expect(TT.RPAREN)
|
|
272
|
+
return A.Reveal(expr=expr, line=r.line, col=r.col)
|
|
273
|
+
|
|
274
|
+
def _parse_prophesy(self) -> A.Prophesy:
|
|
275
|
+
p = self._expect(TT.PROPHESY)
|
|
276
|
+
line_no = p.line
|
|
277
|
+
parts: list[str] = []
|
|
278
|
+
while True:
|
|
279
|
+
t = self._peek()
|
|
280
|
+
if t.type in (TT.NEWLINE, TT.EOF) or t.line != line_no:
|
|
281
|
+
break
|
|
282
|
+
parts.append(str(t.value))
|
|
283
|
+
self._advance()
|
|
284
|
+
return A.Prophesy(text=" ".join(parts), line=p.line, col=p.col)
|
|
285
|
+
|
|
286
|
+
def _parse_if(self) -> A.IfStmt:
|
|
287
|
+
i = self._expect(TT.IF)
|
|
288
|
+
cond = self._parse_expr()
|
|
289
|
+
self._expect(TT.THEN)
|
|
290
|
+
if self._check(TT.NEWLINE):
|
|
291
|
+
self._skip_newlines()
|
|
292
|
+
then_body = self._parse_body(end={TT.ENDIF, TT.ELSE},
|
|
293
|
+
missing="ENDIF", opening=i)
|
|
294
|
+
else_body: list = []
|
|
295
|
+
if self._check(TT.ELSE):
|
|
296
|
+
self._advance()
|
|
297
|
+
self._skip_newlines()
|
|
298
|
+
else_body = self._parse_body(end={TT.ENDIF},
|
|
299
|
+
missing="ENDIF", opening=i)
|
|
300
|
+
self._expect(TT.ENDIF)
|
|
301
|
+
else:
|
|
302
|
+
then_body = [self._parse_statement()]
|
|
303
|
+
else_body = []
|
|
304
|
+
if self._check(TT.ELSE):
|
|
305
|
+
self._advance()
|
|
306
|
+
else_body = [self._parse_statement()]
|
|
307
|
+
return A.IfStmt(cond=cond, then_body=then_body, else_body=else_body,
|
|
308
|
+
line=i.line, col=i.col)
|
|
309
|
+
|
|
310
|
+
def _parse_for(self) -> A.ForLoop:
|
|
311
|
+
f = self._expect(TT.FOR)
|
|
312
|
+
var = self._expect(TT.IDENT, "a name for the traveler").value
|
|
313
|
+
self._expect(TT.IN)
|
|
314
|
+
iterable = self._parse_expr()
|
|
315
|
+
if self._check(TT.NEWLINE):
|
|
316
|
+
body = self._parse_body(end={TT.ENDFOR}, missing="ENDFOR",
|
|
317
|
+
opening=f, legacy_for=True)
|
|
318
|
+
if self._check(TT.ENDFOR):
|
|
319
|
+
self._advance()
|
|
320
|
+
else:
|
|
321
|
+
body = [self._parse_statement()]
|
|
322
|
+
return A.ForLoop(var=var, iterable=iterable, body=body,
|
|
323
|
+
line=f.line, col=f.col)
|
|
324
|
+
|
|
325
|
+
def _parse_while(self) -> A.WhileLoop:
|
|
326
|
+
w = self._expect(TT.WHILE)
|
|
327
|
+
cond = self._parse_expr()
|
|
328
|
+
self._expect(TT.DO)
|
|
329
|
+
if self._check(TT.NEWLINE):
|
|
330
|
+
body = self._parse_body(end={TT.ENDWHILE}, missing="ENDWHILE",
|
|
331
|
+
opening=w)
|
|
332
|
+
self._expect(TT.ENDWHILE)
|
|
333
|
+
else:
|
|
334
|
+
body = [self._parse_statement()]
|
|
335
|
+
return A.WhileLoop(cond=cond, body=body, line=w.line, col=w.col)
|
|
336
|
+
|
|
337
|
+
def _parse_define_rite(self) -> A.DefineRite:
|
|
338
|
+
d = self._expect(TT.DEFINE)
|
|
339
|
+
self._expect(TT.RITE)
|
|
340
|
+
name = self._expect(TT.IDENT, "a name for the rite").value
|
|
341
|
+
self._expect(TT.LPAREN)
|
|
342
|
+
params: list[str] = []
|
|
343
|
+
if not self._check(TT.RPAREN):
|
|
344
|
+
params.append(self._expect(TT.IDENT, "a parameter name").value)
|
|
345
|
+
while self._check(TT.COMMA):
|
|
346
|
+
self._advance()
|
|
347
|
+
params.append(self._expect(TT.IDENT, "a parameter name").value)
|
|
348
|
+
self._expect(TT.RPAREN)
|
|
349
|
+
if self._check(TT.NEWLINE):
|
|
350
|
+
self._skip_newlines()
|
|
351
|
+
body = self._parse_body(end={TT.END}, missing="END RITE", opening=d)
|
|
352
|
+
else:
|
|
353
|
+
# inline single-statement body, e.g. DEFINE RITE f(x) SEAL x END RITE
|
|
354
|
+
body = [self._parse_statement()]
|
|
355
|
+
self._skip_newlines()
|
|
356
|
+
self._expect(TT.END)
|
|
357
|
+
self._expect(TT.RITE)
|
|
358
|
+
return A.DefineRite(name=name, params=params, body=body,
|
|
359
|
+
line=d.line, col=d.col)
|
|
360
|
+
|
|
361
|
+
def _parse_return(self) -> A.Return:
|
|
362
|
+
r = self._expect(TT.RETURN)
|
|
363
|
+
if self._peek().type in _RETURN_TERMINATORS:
|
|
364
|
+
return A.Return(expr=None, line=r.line, col=r.col)
|
|
365
|
+
return A.Return(expr=self._parse_expr(), line=r.line, col=r.col)
|
|
366
|
+
|
|
367
|
+
def _parse_import(self) -> A.Import:
|
|
368
|
+
im = self._expect(TT.IMPORT)
|
|
369
|
+
path = self._expect(TT.STRING, "a scroll path in quotes").value
|
|
370
|
+
return A.Import(path=path, line=im.line, col=im.col)
|
|
371
|
+
|
|
372
|
+
# -- expressions -------------------------------------------------------
|
|
373
|
+
|
|
374
|
+
def _parse_expr(self):
|
|
375
|
+
return self._parse_or()
|
|
376
|
+
|
|
377
|
+
def _parse_or(self):
|
|
378
|
+
left = self._parse_and()
|
|
379
|
+
while self._check(TT.OR):
|
|
380
|
+
op = self._advance()
|
|
381
|
+
right = self._parse_and()
|
|
382
|
+
left = A.BinaryOp(op="or", left=left, right=right,
|
|
383
|
+
line=op.line, col=op.col)
|
|
384
|
+
return left
|
|
385
|
+
|
|
386
|
+
def _parse_and(self):
|
|
387
|
+
left = self._parse_not_low()
|
|
388
|
+
while self._check(TT.AND):
|
|
389
|
+
op = self._advance()
|
|
390
|
+
right = self._parse_not_low()
|
|
391
|
+
left = A.BinaryOp(op="and", left=left, right=right,
|
|
392
|
+
line=op.line, col=op.col)
|
|
393
|
+
return left
|
|
394
|
+
|
|
395
|
+
def _parse_not_low(self):
|
|
396
|
+
if self._check(TT.NOT):
|
|
397
|
+
op = self._advance()
|
|
398
|
+
return A.UnaryOp(op="not", operand=self._parse_not_low(),
|
|
399
|
+
line=op.line, col=op.col)
|
|
400
|
+
return self._parse_comparison()
|
|
401
|
+
|
|
402
|
+
_COMPARISON_OPS = {
|
|
403
|
+
TT.EQ: "==", TT.NEQ: "!=", TT.LT: "<",
|
|
404
|
+
TT.GT: ">", TT.LTE: "<=", TT.GTE: ">=",
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
def _parse_comparison(self):
|
|
408
|
+
left = self._parse_additive()
|
|
409
|
+
while True:
|
|
410
|
+
t = self._peek()
|
|
411
|
+
if t.type is TT.IS:
|
|
412
|
+
self._advance()
|
|
413
|
+
if self._check(TT.NOT):
|
|
414
|
+
self._advance()
|
|
415
|
+
op = "!="
|
|
416
|
+
else:
|
|
417
|
+
op = "=="
|
|
418
|
+
elif t.type in self._COMPARISON_OPS:
|
|
419
|
+
self._advance()
|
|
420
|
+
op = self._COMPARISON_OPS[t.type]
|
|
421
|
+
else:
|
|
422
|
+
break
|
|
423
|
+
right = self._parse_additive()
|
|
424
|
+
left = A.BinaryOp(op=op, left=left, right=right,
|
|
425
|
+
line=t.line, col=t.col)
|
|
426
|
+
return left
|
|
427
|
+
|
|
428
|
+
def _parse_additive(self):
|
|
429
|
+
left = self._parse_multiplicative()
|
|
430
|
+
while self._peek().type in (TT.PLUS, TT.MINUS):
|
|
431
|
+
op = self._advance()
|
|
432
|
+
right = self._parse_multiplicative()
|
|
433
|
+
left = A.BinaryOp(op=op.value, left=left, right=right,
|
|
434
|
+
line=op.line, col=op.col)
|
|
435
|
+
return left
|
|
436
|
+
|
|
437
|
+
def _parse_multiplicative(self):
|
|
438
|
+
left = self._parse_unary()
|
|
439
|
+
while self._peek().type in (TT.STAR, TT.SLASH, TT.PERCENT):
|
|
440
|
+
op = self._advance()
|
|
441
|
+
right = self._parse_unary()
|
|
442
|
+
left = A.BinaryOp(op=op.value, left=left, right=right,
|
|
443
|
+
line=op.line, col=op.col)
|
|
444
|
+
return left
|
|
445
|
+
|
|
446
|
+
def _parse_unary(self):
|
|
447
|
+
t = self._peek()
|
|
448
|
+
if t.type is TT.MINUS:
|
|
449
|
+
self._advance()
|
|
450
|
+
return A.UnaryOp(op="-", operand=self._parse_unary(),
|
|
451
|
+
line=t.line, col=t.col)
|
|
452
|
+
if t.type is TT.NOT:
|
|
453
|
+
self._advance()
|
|
454
|
+
return A.UnaryOp(op="not", operand=self._parse_unary(),
|
|
455
|
+
line=t.line, col=t.col)
|
|
456
|
+
return self._parse_postfix()
|
|
457
|
+
|
|
458
|
+
def _parse_postfix(self):
|
|
459
|
+
obj = self._parse_primary()
|
|
460
|
+
while self._check(TT.LBRACKET):
|
|
461
|
+
lb = self._advance()
|
|
462
|
+
index = self._parse_expr()
|
|
463
|
+
self._expect(TT.RBRACKET)
|
|
464
|
+
obj = A.Index(obj=obj, index=index, line=lb.line, col=lb.col)
|
|
465
|
+
return obj
|
|
466
|
+
|
|
467
|
+
def _parse_primary(self):
|
|
468
|
+
t = self._peek()
|
|
469
|
+
tt = t.type
|
|
470
|
+
if tt is TT.NUMBER or tt is TT.STRING:
|
|
471
|
+
self._advance()
|
|
472
|
+
return A.Literal(value=t.value, line=t.line, col=t.col)
|
|
473
|
+
if tt is TT.TRUE:
|
|
474
|
+
self._advance()
|
|
475
|
+
return A.Literal(value=True, line=t.line, col=t.col)
|
|
476
|
+
if tt is TT.FALSE:
|
|
477
|
+
self._advance()
|
|
478
|
+
return A.Literal(value=False, line=t.line, col=t.col)
|
|
479
|
+
if tt is TT.VOID:
|
|
480
|
+
self._advance()
|
|
481
|
+
return A.Literal(value=None, line=t.line, col=t.col)
|
|
482
|
+
if tt is TT.IDENT:
|
|
483
|
+
self._advance()
|
|
484
|
+
if self._check(TT.LPAREN):
|
|
485
|
+
self._advance()
|
|
486
|
+
args = self._parse_args()
|
|
487
|
+
self._expect(TT.RPAREN)
|
|
488
|
+
return A.CallExpr(callee=t.value, args=args,
|
|
489
|
+
line=t.line, col=t.col)
|
|
490
|
+
return A.Identifier(name=t.value, line=t.line, col=t.col)
|
|
491
|
+
if tt is TT.INVOKE:
|
|
492
|
+
self._advance()
|
|
493
|
+
name = self._expect(TT.IDENT, "a rite name").value
|
|
494
|
+
self._expect(TT.LPAREN)
|
|
495
|
+
args = self._parse_args()
|
|
496
|
+
self._expect(TT.RPAREN)
|
|
497
|
+
return A.CallExpr(callee=name, args=args, line=t.line, col=t.col)
|
|
498
|
+
if tt is TT.LPAREN:
|
|
499
|
+
self._advance()
|
|
500
|
+
expr = self._parse_expr()
|
|
501
|
+
self._expect(TT.RPAREN)
|
|
502
|
+
return expr
|
|
503
|
+
if tt is TT.LBRACKET:
|
|
504
|
+
self._advance()
|
|
505
|
+
items = []
|
|
506
|
+
if not self._check(TT.RBRACKET):
|
|
507
|
+
items.append(self._parse_expr())
|
|
508
|
+
while self._check(TT.COMMA):
|
|
509
|
+
self._advance()
|
|
510
|
+
items.append(self._parse_expr())
|
|
511
|
+
self._expect(TT.RBRACKET)
|
|
512
|
+
return A.ListLiteral(items=items, line=t.line, col=t.col)
|
|
513
|
+
raise ParseError(
|
|
514
|
+
f"Expected a value or name but found {self._describe(t)}",
|
|
515
|
+
line=t.line, col=t.col,
|
|
516
|
+
)
|
|
517
|
+
|
|
518
|
+
def _parse_args(self) -> list:
|
|
519
|
+
args = []
|
|
520
|
+
if not self._check(TT.RPAREN):
|
|
521
|
+
args.append(self._parse_expr())
|
|
522
|
+
while self._check(TT.COMMA):
|
|
523
|
+
self._advance()
|
|
524
|
+
args.append(self._parse_expr())
|
|
525
|
+
return args
|