techscript 1.0.3__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.
- techscript/__init__.py +2 -0
- techscript/__main__.py +5 -0
- techscript/ast_nodes.py +239 -0
- techscript/builtins.py +298 -0
- techscript/cli.py +190 -0
- techscript/environment.py +75 -0
- techscript/errors.py +153 -0
- techscript/interpreter.py +674 -0
- techscript/lexer.py +336 -0
- techscript/parser.py +637 -0
- techscript/repl.py +86 -0
- techscript/tokens.py +132 -0
- techscript/transpiler.py +290 -0
- techscript/web.py +143 -0
- techscript-1.0.3.dist-info/METADATA +510 -0
- techscript-1.0.3.dist-info/RECORD +20 -0
- techscript-1.0.3.dist-info/WHEEL +5 -0
- techscript-1.0.3.dist-info/entry_points.txt +2 -0
- techscript-1.0.3.dist-info/licenses/LICENSE +21 -0
- techscript-1.0.3.dist-info/top_level.txt +1 -0
techscript/parser.py
ADDED
|
@@ -0,0 +1,637 @@
|
|
|
1
|
+
"""TechScript Parser — recursive-descent parser with precedence climbing.
|
|
2
|
+
|
|
3
|
+
Consumes a list of ``Token`` objects from the lexer and produces an AST
|
|
4
|
+
(``Program`` node) as defined in ``ast_nodes``.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from techscript.tokens import Token, TokenType
|
|
10
|
+
from techscript.ast_nodes import *
|
|
11
|
+
from techscript.errors import ParseError
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class Parser:
|
|
15
|
+
"""Recursive-descent parser for TechScript."""
|
|
16
|
+
|
|
17
|
+
def __init__(self, tokens: list[Token]) -> None:
|
|
18
|
+
self.tokens = tokens
|
|
19
|
+
self.pos = 0
|
|
20
|
+
|
|
21
|
+
# ------------------------------------------------------------------
|
|
22
|
+
# Helpers
|
|
23
|
+
# ------------------------------------------------------------------
|
|
24
|
+
|
|
25
|
+
def _peek(self) -> Token:
|
|
26
|
+
return self.tokens[self.pos]
|
|
27
|
+
|
|
28
|
+
def _advance(self) -> Token:
|
|
29
|
+
tok = self.tokens[self.pos]
|
|
30
|
+
self.pos += 1
|
|
31
|
+
return tok
|
|
32
|
+
|
|
33
|
+
def _expect(self, tt: TokenType, value: str | None = None) -> Token:
|
|
34
|
+
tok = self._peek()
|
|
35
|
+
if tok.type != tt:
|
|
36
|
+
raise ParseError(
|
|
37
|
+
f"Expected {tt.name}, got {tok.type.name} ('{tok.value}')",
|
|
38
|
+
line=tok.line, column=tok.column,
|
|
39
|
+
)
|
|
40
|
+
if value is not None and tok.value != value:
|
|
41
|
+
raise ParseError(
|
|
42
|
+
f"Expected '{value}', got '{tok.value}'",
|
|
43
|
+
line=tok.line, column=tok.column,
|
|
44
|
+
)
|
|
45
|
+
return self._advance()
|
|
46
|
+
|
|
47
|
+
def _match(self, tt: TokenType, value: str | None = None) -> Token | None:
|
|
48
|
+
tok = self._peek()
|
|
49
|
+
if tok.type == tt and (value is None or tok.value == value):
|
|
50
|
+
return self._advance()
|
|
51
|
+
return None
|
|
52
|
+
|
|
53
|
+
def _kw(self, value: str) -> Token | None:
|
|
54
|
+
"""Match a keyword token with a specific value."""
|
|
55
|
+
return self._match(TokenType.KEYWORD, value)
|
|
56
|
+
|
|
57
|
+
def _skip_nl(self) -> None:
|
|
58
|
+
while self._peek().type == TokenType.NEWLINE:
|
|
59
|
+
self._advance()
|
|
60
|
+
|
|
61
|
+
def _at_end(self) -> bool:
|
|
62
|
+
return self._peek().type == TokenType.EOF
|
|
63
|
+
|
|
64
|
+
# ------------------------------------------------------------------
|
|
65
|
+
# Public entry point
|
|
66
|
+
# ------------------------------------------------------------------
|
|
67
|
+
|
|
68
|
+
def parse(self) -> Program:
|
|
69
|
+
self._skip_nl()
|
|
70
|
+
body: list[Any] = []
|
|
71
|
+
while not self._at_end():
|
|
72
|
+
stmt = self._parse_statement()
|
|
73
|
+
if stmt is not None:
|
|
74
|
+
body.append(stmt)
|
|
75
|
+
self._skip_nl()
|
|
76
|
+
return Program(body=body)
|
|
77
|
+
|
|
78
|
+
# ------------------------------------------------------------------
|
|
79
|
+
# Statement parsing
|
|
80
|
+
# ------------------------------------------------------------------
|
|
81
|
+
|
|
82
|
+
def _parse_statement(self) -> Any:
|
|
83
|
+
tok = self._peek()
|
|
84
|
+
|
|
85
|
+
if tok.type == TokenType.KEYWORD:
|
|
86
|
+
handler = {
|
|
87
|
+
"say": self._parse_say,
|
|
88
|
+
"make": self._parse_set,
|
|
89
|
+
"keep": self._parse_const,
|
|
90
|
+
"when": self._parse_if,
|
|
91
|
+
"unless": self._parse_unless,
|
|
92
|
+
"each": self._parse_for,
|
|
93
|
+
"repeat": self._parse_while,
|
|
94
|
+
"until": self._parse_until,
|
|
95
|
+
"build": self._parse_fn,
|
|
96
|
+
"model": self._parse_class,
|
|
97
|
+
"send": self._parse_return,
|
|
98
|
+
"stop": lambda: (self._advance(), BreakStmt())[1],
|
|
99
|
+
"skip": lambda: (self._advance(), SkipStmt())[1],
|
|
100
|
+
"pass": lambda: (self._advance(), PassStmt())[1],
|
|
101
|
+
"attempt": self._parse_try,
|
|
102
|
+
"fail": self._parse_throw,
|
|
103
|
+
"match": self._parse_match,
|
|
104
|
+
"use": self._parse_import,
|
|
105
|
+
"take": self._parse_from_import,
|
|
106
|
+
"drop": self._parse_del,
|
|
107
|
+
"defer": self._parse_defer,
|
|
108
|
+
}.get(tok.value)
|
|
109
|
+
if handler:
|
|
110
|
+
return handler()
|
|
111
|
+
|
|
112
|
+
return self._parse_expression_statement()
|
|
113
|
+
|
|
114
|
+
# --- simple statements ---
|
|
115
|
+
|
|
116
|
+
def _parse_say(self) -> SayStmt:
|
|
117
|
+
self._advance() # 'say'
|
|
118
|
+
values = [self._parse_expression()]
|
|
119
|
+
while self._match(TokenType.COMMA):
|
|
120
|
+
values.append(self._parse_expression())
|
|
121
|
+
return SayStmt(values=values)
|
|
122
|
+
|
|
123
|
+
def _parse_set(self) -> SetStmt:
|
|
124
|
+
self._advance() # 'make'
|
|
125
|
+
name = self._expect(TokenType.IDENTIFIER).value
|
|
126
|
+
self._expect(TokenType.ASSIGN)
|
|
127
|
+
value = self._parse_expression()
|
|
128
|
+
return SetStmt(name=name, value=value)
|
|
129
|
+
|
|
130
|
+
def _parse_const(self) -> ConstStmt:
|
|
131
|
+
self._advance() # 'keep'
|
|
132
|
+
name = self._expect(TokenType.IDENTIFIER).value
|
|
133
|
+
self._expect(TokenType.ASSIGN)
|
|
134
|
+
value = self._parse_expression()
|
|
135
|
+
return ConstStmt(name=name, value=value)
|
|
136
|
+
|
|
137
|
+
def _parse_return(self) -> ReturnStmt:
|
|
138
|
+
self._advance() # 'send'
|
|
139
|
+
value = None
|
|
140
|
+
if self._peek().type not in (TokenType.NEWLINE, TokenType.EOF, TokenType.RBRACE):
|
|
141
|
+
value = self._parse_expression()
|
|
142
|
+
return ReturnStmt(value=value)
|
|
143
|
+
|
|
144
|
+
def _parse_throw(self) -> ThrowStmt:
|
|
145
|
+
self._advance() # 'fail'
|
|
146
|
+
return ThrowStmt(value=self._parse_expression())
|
|
147
|
+
|
|
148
|
+
def _parse_del(self) -> DelStmt:
|
|
149
|
+
self._advance() # 'drop'
|
|
150
|
+
return DelStmt(name=self._expect(TokenType.IDENTIFIER).value)
|
|
151
|
+
|
|
152
|
+
def _parse_defer(self) -> DeferStmt:
|
|
153
|
+
self._advance() # 'defer'
|
|
154
|
+
return DeferStmt(expression=self._parse_expression())
|
|
155
|
+
|
|
156
|
+
def _parse_import(self) -> ImportStmt:
|
|
157
|
+
self._advance() # 'import'
|
|
158
|
+
module = self._expect(TokenType.IDENTIFIER).value
|
|
159
|
+
while self._match(TokenType.DOT):
|
|
160
|
+
module += "." + self._expect(TokenType.IDENTIFIER).value
|
|
161
|
+
alias = None
|
|
162
|
+
if self._kw("as"):
|
|
163
|
+
alias = self._expect(TokenType.IDENTIFIER).value
|
|
164
|
+
return ImportStmt(module=module, alias=alias)
|
|
165
|
+
|
|
166
|
+
def _parse_from_import(self) -> FromImportStmt:
|
|
167
|
+
self._advance() # 'from'
|
|
168
|
+
module = self._expect(TokenType.IDENTIFIER).value
|
|
169
|
+
while self._match(TokenType.DOT):
|
|
170
|
+
module += "." + self._expect(TokenType.IDENTIFIER).value
|
|
171
|
+
self._expect(TokenType.KEYWORD, "import")
|
|
172
|
+
names = [self._expect(TokenType.IDENTIFIER).value]
|
|
173
|
+
while self._match(TokenType.COMMA):
|
|
174
|
+
names.append(self._expect(TokenType.IDENTIFIER).value)
|
|
175
|
+
return FromImportStmt(module=module, names=names)
|
|
176
|
+
|
|
177
|
+
def _parse_export(self) -> ExportStmt:
|
|
178
|
+
self._advance() # 'export'
|
|
179
|
+
return ExportStmt(declaration=self._parse_statement())
|
|
180
|
+
|
|
181
|
+
# --- compound statements ---
|
|
182
|
+
|
|
183
|
+
def _parse_if(self) -> IfStmt:
|
|
184
|
+
self._advance() # 'when'
|
|
185
|
+
condition = self._parse_expression()
|
|
186
|
+
body = self._parse_block()
|
|
187
|
+
elif_clauses: list[tuple[Any, list]] = []
|
|
188
|
+
while self._kw("alt"):
|
|
189
|
+
ec = self._parse_expression()
|
|
190
|
+
eb = self._parse_block()
|
|
191
|
+
elif_clauses.append((ec, eb))
|
|
192
|
+
else_body = None
|
|
193
|
+
if self._kw("else"):
|
|
194
|
+
else_body = self._parse_block()
|
|
195
|
+
return IfStmt(condition, body, elif_clauses, else_body)
|
|
196
|
+
|
|
197
|
+
def _parse_unless(self) -> IfStmt:
|
|
198
|
+
self._advance() # 'unless'
|
|
199
|
+
condition = self._parse_expression()
|
|
200
|
+
body = self._parse_block()
|
|
201
|
+
# unless X ≡ if not X
|
|
202
|
+
return IfStmt(condition=UnaryOp("not", condition), body=body)
|
|
203
|
+
|
|
204
|
+
def _parse_for(self) -> ForStmt:
|
|
205
|
+
self._advance() # 'each'
|
|
206
|
+
var_name = self._expect(TokenType.IDENTIFIER).value
|
|
207
|
+
self._expect(TokenType.KEYWORD, "in")
|
|
208
|
+
iterable = self._parse_expression()
|
|
209
|
+
body = self._parse_block()
|
|
210
|
+
return ForStmt(var_name=var_name, iterable=iterable, body=body)
|
|
211
|
+
|
|
212
|
+
def _parse_while(self) -> WhileStmt:
|
|
213
|
+
self._advance() # 'repeat'
|
|
214
|
+
condition = self._parse_expression()
|
|
215
|
+
body = self._parse_block()
|
|
216
|
+
return WhileStmt(condition=condition, body=body)
|
|
217
|
+
|
|
218
|
+
def _parse_until(self) -> WhileStmt:
|
|
219
|
+
self._advance() # 'until'
|
|
220
|
+
condition = self._parse_expression()
|
|
221
|
+
body = self._parse_block()
|
|
222
|
+
# until X ≡ while not X
|
|
223
|
+
return WhileStmt(condition=UnaryOp("not", condition), body=body)
|
|
224
|
+
|
|
225
|
+
def _parse_fn(self) -> FnStmt:
|
|
226
|
+
self._advance() # 'build'
|
|
227
|
+
name = self._expect(TokenType.IDENTIFIER).value
|
|
228
|
+
self._expect(TokenType.LPAREN)
|
|
229
|
+
params = self._parse_param_list()
|
|
230
|
+
self._expect(TokenType.RPAREN)
|
|
231
|
+
body = self._parse_block()
|
|
232
|
+
return FnStmt(name=name, params=params, body=body)
|
|
233
|
+
|
|
234
|
+
def _parse_class(self) -> ClassStmt:
|
|
235
|
+
self._advance() # 'model'
|
|
236
|
+
name = self._expect(TokenType.IDENTIFIER).value
|
|
237
|
+
parent = None
|
|
238
|
+
if self._match(TokenType.LPAREN):
|
|
239
|
+
parent = self._expect(TokenType.IDENTIFIER).value
|
|
240
|
+
self._expect(TokenType.RPAREN)
|
|
241
|
+
body = self._parse_block()
|
|
242
|
+
return ClassStmt(name=name, parent=parent, body=body)
|
|
243
|
+
|
|
244
|
+
def _parse_try(self) -> TryStmt:
|
|
245
|
+
self._advance() # 'attempt'
|
|
246
|
+
body = self._parse_block()
|
|
247
|
+
catch_var = None
|
|
248
|
+
catch_body: list[Any] = []
|
|
249
|
+
finally_body: list[Any] | None = None
|
|
250
|
+
if self._kw("rescue"):
|
|
251
|
+
if self._peek().type == TokenType.IDENTIFIER:
|
|
252
|
+
catch_var = self._advance().value
|
|
253
|
+
catch_body = self._parse_block()
|
|
254
|
+
if self._kw("always"):
|
|
255
|
+
finally_body = self._parse_block()
|
|
256
|
+
return TryStmt(body, catch_var, catch_body, finally_body)
|
|
257
|
+
|
|
258
|
+
def _parse_match(self) -> MatchStmt:
|
|
259
|
+
self._advance() # 'match'
|
|
260
|
+
subject = self._parse_expression()
|
|
261
|
+
self._expect(TokenType.LBRACE)
|
|
262
|
+
self._skip_nl()
|
|
263
|
+
cases: list[tuple[Any, list]] = []
|
|
264
|
+
while self._kw("case"):
|
|
265
|
+
pattern = self._parse_expression()
|
|
266
|
+
case_body = self._parse_block()
|
|
267
|
+
cases.append((pattern, case_body))
|
|
268
|
+
self._skip_nl()
|
|
269
|
+
self._expect(TokenType.RBRACE)
|
|
270
|
+
return MatchStmt(subject=subject, cases=cases)
|
|
271
|
+
|
|
272
|
+
def _parse_guard(self) -> GuardStmt:
|
|
273
|
+
self._advance() # 'guard'
|
|
274
|
+
condition = self._parse_expression()
|
|
275
|
+
self._expect(TokenType.KEYWORD, "else")
|
|
276
|
+
body = self._parse_block()
|
|
277
|
+
return GuardStmt(condition=condition, else_body=body)
|
|
278
|
+
|
|
279
|
+
def _parse_with(self) -> WithStmt:
|
|
280
|
+
self._advance() # 'with'
|
|
281
|
+
expr = self._parse_expression()
|
|
282
|
+
self._expect(TokenType.KEYWORD, "as")
|
|
283
|
+
var = self._expect(TokenType.IDENTIFIER).value
|
|
284
|
+
body = self._parse_block()
|
|
285
|
+
return WithStmt(expression=expr, var_name=var, body=body)
|
|
286
|
+
|
|
287
|
+
# --- blocks ---
|
|
288
|
+
|
|
289
|
+
def _parse_block(self) -> list[Any]:
|
|
290
|
+
self._skip_nl()
|
|
291
|
+
self._expect(TokenType.LBRACE)
|
|
292
|
+
stmts: list[Any] = []
|
|
293
|
+
while self._peek().type not in (TokenType.RBRACE, TokenType.EOF):
|
|
294
|
+
self._skip_nl()
|
|
295
|
+
if self._peek().type in (TokenType.RBRACE, TokenType.EOF):
|
|
296
|
+
break
|
|
297
|
+
stmt = self._parse_statement()
|
|
298
|
+
if stmt is not None:
|
|
299
|
+
stmts.append(stmt)
|
|
300
|
+
self._skip_nl()
|
|
301
|
+
if self._peek().type == TokenType.RBRACE:
|
|
302
|
+
self._advance()
|
|
303
|
+
return stmts
|
|
304
|
+
|
|
305
|
+
def _parse_param_list(self) -> list[Param]:
|
|
306
|
+
params: list[Param] = []
|
|
307
|
+
if self._peek().type == TokenType.RPAREN:
|
|
308
|
+
return params
|
|
309
|
+
# skip 'self' as a pseudo-param (kept for class methods)
|
|
310
|
+
if self._peek().type == TokenType.KEYWORD and self._peek().value == "self":
|
|
311
|
+
self._advance()
|
|
312
|
+
params.append(Param(name="self"))
|
|
313
|
+
if not self._match(TokenType.COMMA):
|
|
314
|
+
return params
|
|
315
|
+
params.append(self._parse_one_param())
|
|
316
|
+
while self._match(TokenType.COMMA):
|
|
317
|
+
params.append(self._parse_one_param())
|
|
318
|
+
return params
|
|
319
|
+
|
|
320
|
+
def _parse_one_param(self) -> Param:
|
|
321
|
+
name = self._expect(TokenType.IDENTIFIER).value
|
|
322
|
+
default = None
|
|
323
|
+
if self._match(TokenType.ASSIGN):
|
|
324
|
+
default = self._parse_expression()
|
|
325
|
+
return Param(name=name, default=default)
|
|
326
|
+
|
|
327
|
+
# --- expression statement / assignment ---
|
|
328
|
+
|
|
329
|
+
def _parse_expression_statement(self) -> Any:
|
|
330
|
+
expr = self._parse_expression()
|
|
331
|
+
assign_ops = {
|
|
332
|
+
TokenType.ASSIGN, TokenType.PLUS_ASSIGN,
|
|
333
|
+
TokenType.MINUS_ASSIGN, TokenType.STAR_ASSIGN,
|
|
334
|
+
TokenType.SLASH_ASSIGN,
|
|
335
|
+
}
|
|
336
|
+
if self._peek().type in assign_ops:
|
|
337
|
+
op_tok = self._advance()
|
|
338
|
+
value = self._parse_expression()
|
|
339
|
+
return AssignStmt(target=expr, op=op_tok.value, value=value)
|
|
340
|
+
return ExpressionStmt(expression=expr)
|
|
341
|
+
|
|
342
|
+
# ------------------------------------------------------------------
|
|
343
|
+
# Expression parsing (precedence climbing)
|
|
344
|
+
# ------------------------------------------------------------------
|
|
345
|
+
|
|
346
|
+
def _parse_expression(self) -> Any:
|
|
347
|
+
return self._parse_ternary()
|
|
348
|
+
|
|
349
|
+
def _parse_ternary(self) -> Any:
|
|
350
|
+
expr = self._parse_or()
|
|
351
|
+
if self._kw("when"):
|
|
352
|
+
condition = self._parse_or()
|
|
353
|
+
self._expect(TokenType.KEYWORD, "else")
|
|
354
|
+
false_val = self._parse_ternary()
|
|
355
|
+
return TernaryExpr(true_val=expr, condition=condition, false_val=false_val)
|
|
356
|
+
return expr
|
|
357
|
+
|
|
358
|
+
def _parse_or(self) -> Any:
|
|
359
|
+
left = self._parse_and()
|
|
360
|
+
while self._kw("or"):
|
|
361
|
+
left = BinaryOp(left, "or", self._parse_and())
|
|
362
|
+
return left
|
|
363
|
+
|
|
364
|
+
def _parse_and(self) -> Any:
|
|
365
|
+
left = self._parse_not()
|
|
366
|
+
while self._kw("and"):
|
|
367
|
+
left = BinaryOp(left, "and", self._parse_not())
|
|
368
|
+
return left
|
|
369
|
+
|
|
370
|
+
def _parse_not(self) -> Any:
|
|
371
|
+
if self._kw("not"):
|
|
372
|
+
return UnaryOp("not", self._parse_not())
|
|
373
|
+
return self._parse_comparison()
|
|
374
|
+
|
|
375
|
+
def _parse_comparison(self) -> Any:
|
|
376
|
+
left = self._parse_range()
|
|
377
|
+
_comp = {
|
|
378
|
+
TokenType.EQUAL, TokenType.NOT_EQUAL,
|
|
379
|
+
TokenType.LESS, TokenType.GREATER,
|
|
380
|
+
TokenType.LESS_EQUAL, TokenType.GREATER_EQUAL,
|
|
381
|
+
}
|
|
382
|
+
while True:
|
|
383
|
+
if self._peek().type in _comp:
|
|
384
|
+
op = self._advance()
|
|
385
|
+
left = BinaryOp(left, op.value, self._parse_range())
|
|
386
|
+
elif self._peek().type == TokenType.KEYWORD and self._peek().value in ("is", "in", "has"):
|
|
387
|
+
op = self._advance()
|
|
388
|
+
left = BinaryOp(left, op.value, self._parse_range())
|
|
389
|
+
else:
|
|
390
|
+
break
|
|
391
|
+
return left
|
|
392
|
+
|
|
393
|
+
def _parse_range(self) -> Any:
|
|
394
|
+
left = self._parse_addition()
|
|
395
|
+
if self._match(TokenType.DOTDOT_EQUAL):
|
|
396
|
+
right = self._parse_addition()
|
|
397
|
+
return RangeExpr(start=left, end=right, inclusive=True)
|
|
398
|
+
if self._match(TokenType.DOTDOT):
|
|
399
|
+
right = self._parse_addition()
|
|
400
|
+
return RangeExpr(start=left, end=right, inclusive=False)
|
|
401
|
+
return left
|
|
402
|
+
|
|
403
|
+
def _parse_addition(self) -> Any:
|
|
404
|
+
left = self._parse_multiplication()
|
|
405
|
+
while self._peek().type in (TokenType.PLUS, TokenType.MINUS):
|
|
406
|
+
op = self._advance()
|
|
407
|
+
left = BinaryOp(left, op.value, self._parse_multiplication())
|
|
408
|
+
return left
|
|
409
|
+
|
|
410
|
+
def _parse_multiplication(self) -> Any:
|
|
411
|
+
left = self._parse_unary()
|
|
412
|
+
while self._peek().type in (TokenType.STAR, TokenType.SLASH, TokenType.DOUBLE_SLASH, TokenType.PERCENT):
|
|
413
|
+
op = self._advance()
|
|
414
|
+
left = BinaryOp(left, op.value, self._parse_unary())
|
|
415
|
+
return left
|
|
416
|
+
|
|
417
|
+
def _parse_unary(self) -> Any:
|
|
418
|
+
if self._peek().type in (TokenType.MINUS, TokenType.PLUS):
|
|
419
|
+
op = self._advance()
|
|
420
|
+
return UnaryOp(op.value, self._parse_unary())
|
|
421
|
+
return self._parse_power()
|
|
422
|
+
|
|
423
|
+
def _parse_power(self) -> Any:
|
|
424
|
+
base = self._parse_call()
|
|
425
|
+
if self._match(TokenType.POWER):
|
|
426
|
+
return BinaryOp(base, "**", self._parse_unary())
|
|
427
|
+
return base
|
|
428
|
+
|
|
429
|
+
def _parse_call(self) -> Any:
|
|
430
|
+
expr = self._parse_primary()
|
|
431
|
+
while True:
|
|
432
|
+
if self._match(TokenType.LPAREN):
|
|
433
|
+
args: list[Any] = []
|
|
434
|
+
if self._peek().type != TokenType.RPAREN:
|
|
435
|
+
args.append(self._parse_expression())
|
|
436
|
+
while self._match(TokenType.COMMA):
|
|
437
|
+
args.append(self._parse_expression())
|
|
438
|
+
self._expect(TokenType.RPAREN)
|
|
439
|
+
expr = CallExpr(callee=expr, args=args)
|
|
440
|
+
elif self._match(TokenType.LBRACKET):
|
|
441
|
+
idx = self._parse_expression()
|
|
442
|
+
self._expect(TokenType.RBRACKET)
|
|
443
|
+
expr = IndexExpr(obj=expr, index=idx)
|
|
444
|
+
elif self._match(TokenType.DOT):
|
|
445
|
+
member = self._expect(TokenType.IDENTIFIER).value
|
|
446
|
+
expr = MemberExpr(obj=expr, member=member)
|
|
447
|
+
elif self._match(TokenType.PIPE):
|
|
448
|
+
func = self._parse_primary()
|
|
449
|
+
expr = CallExpr(callee=func, args=[expr])
|
|
450
|
+
else:
|
|
451
|
+
break
|
|
452
|
+
return expr
|
|
453
|
+
|
|
454
|
+
def _parse_primary(self) -> Any:
|
|
455
|
+
tok = self._peek()
|
|
456
|
+
|
|
457
|
+
if tok.type == TokenType.NUMBER_INT:
|
|
458
|
+
self._advance()
|
|
459
|
+
return NumberLit(value=int(tok.value, 0))
|
|
460
|
+
|
|
461
|
+
if tok.type == TokenType.NUMBER_FLOAT:
|
|
462
|
+
self._advance()
|
|
463
|
+
return NumberLit(value=float(tok.value))
|
|
464
|
+
|
|
465
|
+
if tok.type == TokenType.STRING:
|
|
466
|
+
self._advance()
|
|
467
|
+
return StringLit(value=tok.value)
|
|
468
|
+
|
|
469
|
+
if tok.type == TokenType.FSTRING:
|
|
470
|
+
self._advance()
|
|
471
|
+
return FStringLit(raw=tok.value)
|
|
472
|
+
|
|
473
|
+
if tok.type == TokenType.BOOL_TRUE:
|
|
474
|
+
self._advance()
|
|
475
|
+
return BoolLit(value=True)
|
|
476
|
+
|
|
477
|
+
if tok.type == TokenType.BOOL_FALSE:
|
|
478
|
+
self._advance()
|
|
479
|
+
return BoolLit(value=False)
|
|
480
|
+
|
|
481
|
+
if tok.type == TokenType.NONE:
|
|
482
|
+
self._advance()
|
|
483
|
+
return NoneLit()
|
|
484
|
+
|
|
485
|
+
if tok.type == TokenType.IDENTIFIER:
|
|
486
|
+
self._advance()
|
|
487
|
+
return Identifier(name=tok.value)
|
|
488
|
+
|
|
489
|
+
# ask / ?
|
|
490
|
+
if tok.type == TokenType.KEYWORD and tok.value == "ask":
|
|
491
|
+
self._advance()
|
|
492
|
+
return AskExpr(prompt=self._parse_expression())
|
|
493
|
+
if tok.type == TokenType.QUESTION:
|
|
494
|
+
self._advance()
|
|
495
|
+
return AskExpr(prompt=self._parse_expression())
|
|
496
|
+
|
|
497
|
+
# new ClassName(args)
|
|
498
|
+
if tok.type == TokenType.KEYWORD and tok.value == "new":
|
|
499
|
+
self._advance()
|
|
500
|
+
cls_name = self._expect(TokenType.IDENTIFIER).value
|
|
501
|
+
self._expect(TokenType.LPAREN)
|
|
502
|
+
args: list[Any] = []
|
|
503
|
+
if self._peek().type != TokenType.RPAREN:
|
|
504
|
+
args.append(self._parse_expression())
|
|
505
|
+
while self._match(TokenType.COMMA):
|
|
506
|
+
args.append(self._parse_expression())
|
|
507
|
+
self._expect(TokenType.RPAREN)
|
|
508
|
+
return CallExpr(callee=Identifier(cls_name), args=args)
|
|
509
|
+
|
|
510
|
+
# self
|
|
511
|
+
if tok.type == TokenType.KEYWORD and tok.value == "self":
|
|
512
|
+
self._advance()
|
|
513
|
+
return Identifier(name="self")
|
|
514
|
+
|
|
515
|
+
# super
|
|
516
|
+
if tok.type == TokenType.KEYWORD and tok.value == "super":
|
|
517
|
+
self._advance()
|
|
518
|
+
return Identifier(name="super")
|
|
519
|
+
|
|
520
|
+
# typeof(x)
|
|
521
|
+
if tok.type == TokenType.KEYWORD and tok.value == "typeof":
|
|
522
|
+
self._advance()
|
|
523
|
+
self._expect(TokenType.LPAREN)
|
|
524
|
+
arg = self._parse_expression()
|
|
525
|
+
self._expect(TokenType.RPAREN)
|
|
526
|
+
return CallExpr(callee=Identifier("typeof"), args=[arg])
|
|
527
|
+
|
|
528
|
+
# List literal
|
|
529
|
+
if tok.type == TokenType.LBRACKET:
|
|
530
|
+
return self._parse_list_literal()
|
|
531
|
+
|
|
532
|
+
# Map literal
|
|
533
|
+
if tok.type == TokenType.LBRACE:
|
|
534
|
+
return self._parse_map_literal()
|
|
535
|
+
|
|
536
|
+
# Grouped expression or lambda
|
|
537
|
+
if tok.type == TokenType.LPAREN:
|
|
538
|
+
return self._parse_grouped_or_lambda()
|
|
539
|
+
|
|
540
|
+
# Wildcard _ (used in match/case)
|
|
541
|
+
if tok.type == TokenType.IDENTIFIER and tok.value == "_":
|
|
542
|
+
self._advance()
|
|
543
|
+
return Identifier(name="_")
|
|
544
|
+
|
|
545
|
+
raise ParseError(
|
|
546
|
+
f"Unexpected token: '{tok.value}' ({tok.type.name})",
|
|
547
|
+
line=tok.line, column=tok.column,
|
|
548
|
+
)
|
|
549
|
+
|
|
550
|
+
# --- composite primaries ---
|
|
551
|
+
|
|
552
|
+
def _parse_list_literal(self) -> ListLit:
|
|
553
|
+
self._advance() # [
|
|
554
|
+
elements: list[Any] = []
|
|
555
|
+
self._skip_nl()
|
|
556
|
+
if self._peek().type != TokenType.RBRACKET:
|
|
557
|
+
elements.append(self._parse_expression())
|
|
558
|
+
while self._match(TokenType.COMMA):
|
|
559
|
+
self._skip_nl()
|
|
560
|
+
if self._peek().type == TokenType.RBRACKET:
|
|
561
|
+
break
|
|
562
|
+
elements.append(self._parse_expression())
|
|
563
|
+
self._skip_nl()
|
|
564
|
+
self._expect(TokenType.RBRACKET)
|
|
565
|
+
return ListLit(elements=elements)
|
|
566
|
+
|
|
567
|
+
def _parse_map_literal(self) -> MapLit:
|
|
568
|
+
self._advance() # {
|
|
569
|
+
entries: list[tuple[Any, Any]] = []
|
|
570
|
+
self._skip_nl()
|
|
571
|
+
if self._peek().type != TokenType.RBRACE:
|
|
572
|
+
entries.append(self._parse_map_entry())
|
|
573
|
+
while self._match(TokenType.COMMA):
|
|
574
|
+
self._skip_nl()
|
|
575
|
+
if self._peek().type == TokenType.RBRACE:
|
|
576
|
+
break
|
|
577
|
+
entries.append(self._parse_map_entry())
|
|
578
|
+
self._skip_nl()
|
|
579
|
+
self._expect(TokenType.RBRACE)
|
|
580
|
+
return MapLit(entries=entries)
|
|
581
|
+
|
|
582
|
+
def _parse_map_entry(self) -> tuple[Any, Any]:
|
|
583
|
+
self._skip_nl()
|
|
584
|
+
# Allow identifier as shorthand key {name: "Alice"}
|
|
585
|
+
if self._peek().type == TokenType.IDENTIFIER and self.tokens[self.pos + 1].type == TokenType.COLON:
|
|
586
|
+
key = StringLit(value=self._advance().value)
|
|
587
|
+
else:
|
|
588
|
+
key = self._parse_expression()
|
|
589
|
+
self._expect(TokenType.COLON)
|
|
590
|
+
value = self._parse_expression()
|
|
591
|
+
return (key, value)
|
|
592
|
+
|
|
593
|
+
def _parse_grouped_or_lambda(self) -> Any:
|
|
594
|
+
self._advance() # (
|
|
595
|
+
|
|
596
|
+
# () => expr
|
|
597
|
+
if self._peek().type == TokenType.RPAREN:
|
|
598
|
+
self._advance()
|
|
599
|
+
if self._match(TokenType.ARROW):
|
|
600
|
+
body = self._parse_expression()
|
|
601
|
+
return LambdaExpr(params=[], body=body)
|
|
602
|
+
# empty parens — treat as none? shouldn't happen, error
|
|
603
|
+
raise ParseError("Empty parentheses", line=self._peek().line, column=self._peek().column)
|
|
604
|
+
|
|
605
|
+
# Try single-expression group first; peek ahead for comma or arrow
|
|
606
|
+
save = self.pos
|
|
607
|
+
try:
|
|
608
|
+
expr = self._parse_expression()
|
|
609
|
+
|
|
610
|
+
# (expr) — grouped
|
|
611
|
+
if self._match(TokenType.RPAREN):
|
|
612
|
+
# Check for => after )
|
|
613
|
+
if self._match(TokenType.ARROW):
|
|
614
|
+
if isinstance(expr, Identifier):
|
|
615
|
+
body = self._parse_expression()
|
|
616
|
+
return LambdaExpr(params=[Param(name=expr.name)], body=body)
|
|
617
|
+
return expr
|
|
618
|
+
|
|
619
|
+
# (id, id, …) => expr — lambda
|
|
620
|
+
if self._peek().type == TokenType.COMMA and isinstance(expr, Identifier):
|
|
621
|
+
params = [Param(name=expr.name)]
|
|
622
|
+
while self._match(TokenType.COMMA):
|
|
623
|
+
p_name = self._expect(TokenType.IDENTIFIER).value
|
|
624
|
+
params.append(Param(name=p_name))
|
|
625
|
+
self._expect(TokenType.RPAREN)
|
|
626
|
+
self._expect(TokenType.ARROW)
|
|
627
|
+
body = self._parse_expression()
|
|
628
|
+
return LambdaExpr(params=params, body=body)
|
|
629
|
+
|
|
630
|
+
# Fallback: just a grouped expression
|
|
631
|
+
self._expect(TokenType.RPAREN)
|
|
632
|
+
return expr
|
|
633
|
+
|
|
634
|
+
except ParseError:
|
|
635
|
+
# Restore and try harder
|
|
636
|
+
self.pos = save
|
|
637
|
+
raise
|