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/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