cpyte 2.7.1__tar.gz → 2.7.2__tar.gz

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.
Files changed (38) hide show
  1. {cpyte-2.7.1/source/cpyte.egg-info → cpyte-2.7.2}/PKG-INFO +1 -1
  2. {cpyte-2.7.1 → cpyte-2.7.2}/pyproject.toml +1 -1
  3. cpyte-2.7.2/source/cpyte/__init__.py +1 -0
  4. {cpyte-2.7.1 → cpyte-2.7.2}/source/cpyte/astparse.py +303 -52
  5. {cpyte-2.7.1 → cpyte-2.7.2}/source/cpyte/bytecoding.py +202 -14
  6. {cpyte-2.7.1 → cpyte-2.7.2}/source/cpyte/clib.py +21 -4
  7. {cpyte-2.7.1 → cpyte-2.7.2}/source/cpyte/compiling.py +11 -8
  8. {cpyte-2.7.1 → cpyte-2.7.2}/source/cpyte/formatter.py +8 -7
  9. {cpyte-2.7.1 → cpyte-2.7.2}/source/cpyte/generate_bc.py +6 -6
  10. {cpyte-2.7.1 → cpyte-2.7.2}/source/cpyte/linker.py +55 -4
  11. {cpyte-2.7.1 → cpyte-2.7.2}/source/cpyte/lsp_server.py +14 -5
  12. {cpyte-2.7.1 → cpyte-2.7.2}/source/cpyte/mainpie.py +14 -6
  13. {cpyte-2.7.1 → cpyte-2.7.2}/source/cpyte/semantic_analasis.py +278 -25
  14. {cpyte-2.7.1 → cpyte-2.7.2/source/cpyte.egg-info}/PKG-INFO +1 -1
  15. cpyte-2.7.1/source/cpyte/__init__.py +0 -1
  16. {cpyte-2.7.1 → cpyte-2.7.2}/MANIFEST.in +0 -0
  17. {cpyte-2.7.1 → cpyte-2.7.2}/readme.md +0 -0
  18. {cpyte-2.7.1 → cpyte-2.7.2}/setup.cfg +0 -0
  19. {cpyte-2.7.1 → cpyte-2.7.2}/source/cpyte/__main__.py +0 -0
  20. {cpyte-2.7.1 → cpyte-2.7.2}/source/cpyte/_bignum_bc.py +0 -0
  21. {cpyte-2.7.1 → cpyte-2.7.2}/source/cpyte/_gc_bc.py +0 -0
  22. {cpyte-2.7.1 → cpyte-2.7.2}/source/cpyte/_runtime_bc.py +0 -0
  23. {cpyte-2.7.1 → cpyte-2.7.2}/source/cpyte/bignum.c +0 -0
  24. {cpyte-2.7.1 → cpyte-2.7.2}/source/cpyte/extension_hooks.py +0 -0
  25. {cpyte-2.7.1 → cpyte-2.7.2}/source/cpyte/gc_runtime.c +0 -0
  26. {cpyte-2.7.1 → cpyte-2.7.2}/source/cpyte/lexar.py +0 -0
  27. {cpyte-2.7.1 → cpyte-2.7.2}/source/cpyte/package_manifest.py +0 -0
  28. {cpyte-2.7.1 → cpyte-2.7.2}/source/cpyte/runtime.c +0 -0
  29. {cpyte-2.7.1 → cpyte-2.7.2}/source/cpyte/runtime_scorpion.c +0 -0
  30. {cpyte-2.7.1 → cpyte-2.7.2}/source/cpyte/sef.py +0 -0
  31. {cpyte-2.7.1 → cpyte-2.7.2}/source/cpyte/ui.py +0 -0
  32. {cpyte-2.7.1 → cpyte-2.7.2}/source/cpyte/update_check.py +0 -0
  33. {cpyte-2.7.1 → cpyte-2.7.2}/source/cpyte.egg-info/SOURCES.txt +0 -0
  34. {cpyte-2.7.1 → cpyte-2.7.2}/source/cpyte.egg-info/dependency_links.txt +0 -0
  35. {cpyte-2.7.1 → cpyte-2.7.2}/source/cpyte.egg-info/entry_points.txt +0 -0
  36. {cpyte-2.7.1 → cpyte-2.7.2}/source/cpyte.egg-info/requires.txt +0 -0
  37. {cpyte-2.7.1 → cpyte-2.7.2}/source/cpyte.egg-info/top_level.txt +0 -0
  38. {cpyte-2.7.1 → cpyte-2.7.2}/test/test_bignum_jit.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cpyte
3
- Version: 2.7.1
3
+ Version: 2.7.2
4
4
  Summary: The Cpyte programming language compiler
5
5
  Author: Hoang Duy Tung
6
6
  License: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "cpyte"
7
- version = "2.7.1"
7
+ version = "2.7.2"
8
8
  description = "The Cpyte programming language compiler"
9
9
  readme = "readme.md"
10
10
  requires-python = ">=3.11"
@@ -0,0 +1 @@
1
+ __version__ = "2.7.2"
@@ -65,6 +65,11 @@ class Variable:
65
65
  def __repr__(self):
66
66
  return f'Variable({self.name})'
67
67
 
68
+ @property
69
+ def token(self):
70
+ return self._token
71
+
72
+
68
73
  class Call:
69
74
  __slots__ = ('callee', 'args', '_token')
70
75
  def __init__(self, callee, args: list, token=None):
@@ -134,6 +139,45 @@ _PREC = {
134
139
 
135
140
  _BINARY_OPS = set(_PREC.keys())
136
141
 
142
+ # Maximum nesting depth for recursive expression parsing. Past this limit the
143
+ # parser switches to a fully-iterative Pratt fallback (_parse_expr_iterative)
144
+ # so that pathologically deep expressions can never overflow the interpreter
145
+ # stack. Kept well below Python's default recursion limit (1000): each nesting
146
+ # level costs up to ~6 interpreter frames.
147
+ _EXPR_DEPTH_LIMIT = 120
148
+
149
+ _expr_depth = 0
150
+
151
+ _UNARY_PREFIX_TYPES = {
152
+ TokenType.MINUS, TokenType.PLUS, TokenType.NOT, TokenType.TILDE,
153
+ TokenType.POW, TokenType.STAR, TokenType.AMPERSAND, TokenType.MINUS_MINUS,
154
+ }
155
+
156
+
157
+ def _skip_expr_newlines(tokens: list[Token], pos: int) -> int:
158
+ while pos < len(tokens) and tokens[pos].type in (TokenType.NEWLINE, TokenType.INDENT, TokenType.DEDENT):
159
+ pos += 1
160
+ return pos
161
+
162
+
163
+ def _wrap_unary_prefixes(prefixes, node):
164
+ """Apply a collected run of unary prefix operators to a node, outside-in.
165
+
166
+ Mirrors the recursive _parse_unary wrapping exactly: the outermost prefix is
167
+ applied last. POW denotes the double-deref `**x`.
168
+ """
169
+ for op in reversed(prefixes):
170
+ if op.type == TokenType.STAR:
171
+ node = Deref(node, token=op)
172
+ elif op.type == TokenType.AMPERSAND:
173
+ node = AddrOf(node, token=op)
174
+ elif op.type == TokenType.POW:
175
+ inner = Deref(node)
176
+ node = Deref(inner)
177
+ else:
178
+ node = UnaryOp(op.type, node, token=op)
179
+ return node
180
+
137
181
 
138
182
  class ParseError(Exception):
139
183
  def __init__(self, msg: str, token: Token | None = None):
@@ -151,61 +195,41 @@ def parse_expression(tokens: list[Token], pos: int = 0):
151
195
 
152
196
 
153
197
  def _parse_binary(tokens: list[Token], pos: int, min_prec: int):
154
- left, pos = _parse_unary(tokens, pos)
198
+ global _expr_depth
199
+ _expr_depth += 1
200
+ try:
201
+ if _expr_depth > _EXPR_DEPTH_LIMIT:
202
+ return _parse_expr_iterative(tokens, pos, min_prec)
155
203
 
156
- while pos < len(tokens) and tokens[pos].type in _BINARY_OPS and _prec(tokens[pos]) >= min_prec:
157
- op = tokens[pos]
158
- pos += 1
159
- while pos < len(tokens) and tokens[pos].type in (TokenType.NEWLINE, TokenType.INDENT, TokenType.DEDENT):
204
+ left, pos = _parse_unary(tokens, pos)
205
+
206
+ while pos < len(tokens) and tokens[pos].type in _BINARY_OPS and _prec(tokens[pos]) >= min_prec:
207
+ op = tokens[pos]
160
208
  pos += 1
161
- next_prec = _prec(op) if op.type == TokenType.POW else _prec(op) + 1
162
- right, pos = _parse_binary(tokens, pos, next_prec)
163
- left = BinOp(left, op.type, right, token=op)
209
+ while pos < len(tokens) and tokens[pos].type in (TokenType.NEWLINE, TokenType.INDENT, TokenType.DEDENT):
210
+ pos += 1
211
+ next_prec = _prec(op) if op.type == TokenType.POW else _prec(op) + 1
212
+ right, pos = _parse_binary(tokens, pos, next_prec)
213
+ left = BinOp(left, op.type, right, token=op)
164
214
 
165
- return left, pos
215
+ return left, pos
216
+ finally:
217
+ _expr_depth -= 1
166
218
 
167
219
 
168
220
  def _parse_unary(tokens: list[Token], pos: int):
169
- while pos < len(tokens) and tokens[pos].type in (TokenType.NEWLINE, TokenType.INDENT, TokenType.DEDENT):
170
- pos += 1
221
+ pos = _skip_expr_newlines(tokens, pos)
171
222
  if pos >= len(tokens):
172
223
  raise ParseError('Unexpected end of expression')
173
224
 
174
- if tokens[pos].type == TokenType.MINUS:
175
- op = tokens[pos]
176
- pos += 1
177
- operand, pos = _parse_unary(tokens, pos)
178
- return _parse_postfix(tokens, pos, UnaryOp(op.type, operand, token=op))
179
-
180
- if tokens[pos].type == TokenType.PLUS:
181
- op = tokens[pos]
182
- pos += 1
183
- operand, pos = _parse_unary(tokens, pos)
184
- return _parse_postfix(tokens, pos, UnaryOp(op.type, operand, token=op))
185
-
186
- if tokens[pos].type in (TokenType.NOT, TokenType.TILDE):
187
- op = tokens[pos]
188
- pos += 1
189
- operand, pos = _parse_unary(tokens, pos)
190
- return _parse_postfix(tokens, pos, UnaryOp(op.type, operand, token=op))
191
-
192
- if tokens[pos].type == TokenType.POW:
193
- pos += 1
194
- operand, pos = _parse_unary(tokens, pos)
195
- inner = Deref(operand)
196
- return _parse_postfix(tokens, pos, Deref(inner))
197
- if tokens[pos].type in (TokenType.STAR, TokenType.AMPERSAND, TokenType.MINUS_MINUS):
198
- op = tokens[pos]
225
+ prefixes = []
226
+ while pos < len(tokens) and tokens[pos].type in _UNARY_PREFIX_TYPES:
227
+ prefixes.append(tokens[pos])
199
228
  pos += 1
200
- operand, pos = _parse_unary(tokens, pos)
201
- if op.type == TokenType.STAR:
202
- return _parse_postfix(tokens, pos, Deref(operand, token=op))
203
- elif op.type == TokenType.AMPERSAND:
204
- return _parse_postfix(tokens, pos, AddrOf(operand, token=op))
205
- return _parse_postfix(tokens, pos, UnaryOp(op.type, operand, token=op))
206
229
 
207
230
  node, pos = _parse_atom(tokens, pos)
208
- return _parse_postfix(tokens, pos, node)
231
+ node, pos = _parse_postfix(tokens, pos, node)
232
+ return _wrap_unary_prefixes(prefixes, node), pos
209
233
 
210
234
 
211
235
  def _parse_atom(tokens: list[Token], pos: int):
@@ -335,6 +359,217 @@ def _parse_postfix(tokens: list[Token], pos: int, node):
335
359
  return node, pos
336
360
 
337
361
 
362
+ def _parse_expr_iterative(tokens: list[Token], pos: int, min_prec: int):
363
+ """Fully-iterative Pratt parser used when recursion depth is exceeded.
364
+
365
+ Produces exactly the same AST as the recursive _parse_binary/_parse_unary/
366
+ _parse_postfix/_parse_atom combination (same associativity, precedence and
367
+ postfix wrapping), but never recurses into the interpreter: nested parens,
368
+ calls, indexes and right-associative chains are all handled through an
369
+ explicit continuation stack, so arbitrarily deep expressions are safe.
370
+ """
371
+ vals: list = []
372
+ frames: list = [('binary', min_prec)]
373
+
374
+ while frames:
375
+ frame = frames.pop()
376
+ kind = frame[0]
377
+
378
+ if kind == 'binary':
379
+ _, mp = frame
380
+ frames.append(('binary_loop', mp))
381
+ frames.append(('unary', []))
382
+
383
+ elif kind == 'unary':
384
+ prefixes = frame[1]
385
+ pos = _skip_expr_newlines(tokens, pos)
386
+ if pos >= len(tokens):
387
+ raise ParseError('Unexpected end of expression')
388
+ while pos < len(tokens) and tokens[pos].type in _UNARY_PREFIX_TYPES:
389
+ prefixes.append(tokens[pos])
390
+ pos += 1
391
+ frames.append(('postfix', prefixes))
392
+ frames.append(('atom',))
393
+
394
+ elif kind == 'atom':
395
+ tok = tokens[pos]
396
+ if tok.type == TokenType.NUMBER and tok.value == '67':
397
+ pos += 1
398
+ if pos < len(tokens) and tokens[pos].type == TokenType.LPAREN:
399
+ pos += 1
400
+ if pos >= len(tokens) or tokens[pos].type != TokenType.RPAREN:
401
+ raise ParseError('Expected ")" after 67()', tok)
402
+ pos += 1
403
+ vals.append(Signed67(token=tok))
404
+ continue
405
+ vals.append(Number('67', token=tok))
406
+ continue
407
+ if tok.type == TokenType.NUMBER:
408
+ assert tok.value is not None
409
+ pos += 1
410
+ vals.append(Number(tok.value, token=tok))
411
+ continue
412
+ if tok.type == TokenType.STRING:
413
+ assert tok.value is not None
414
+ pos += 1
415
+ vals.append(String(tok.value, token=tok))
416
+ continue
417
+ if tok.type == TokenType.IDENTIFIER:
418
+ assert tok.value is not None
419
+ pos += 1
420
+ vals.append(Variable(tok.value, token=tok))
421
+ continue
422
+ if tok.type == TokenType.KEYWORD and tok.value in ('true', 'false', 'True', 'False', 'null'):
423
+ val = '1' if tok.value in ('true', 'True') else '0'
424
+ pos += 1
425
+ vals.append(Number(val, token=tok))
426
+ continue
427
+ if tok.type == TokenType.KEYWORD and tok.value == 'input':
428
+ pos += 1
429
+ if pos < len(tokens) and tokens[pos].type == TokenType.LPAREN:
430
+ pos += 1
431
+ if pos >= len(tokens) or tokens[pos].type != TokenType.RPAREN:
432
+ raise ParseError('Expected ")" after input()', tok)
433
+ pos += 1
434
+ vals.append(Input(token=tok))
435
+ continue
436
+ raise ParseError('Expected "(" after input', tok)
437
+ if tok.type == TokenType.KEYWORD and tok.value == 'input_str':
438
+ pos += 1
439
+ if pos < len(tokens) and tokens[pos].type == TokenType.LPAREN:
440
+ pos += 1
441
+ if pos >= len(tokens) or tokens[pos].type != TokenType.RPAREN:
442
+ raise ParseError('Expected ")" after input_str()', tok)
443
+ pos += 1
444
+ vals.append(InputStr(token=tok))
445
+ continue
446
+ raise ParseError('Expected "(" after input_str', tok)
447
+ if tok.type == TokenType.KEYWORD and tok.value == 'new':
448
+ pos += 1
449
+ base_type, pos = parse_type(tokens, pos)
450
+ type_str = _type_to_str(base_type) if isinstance(base_type, tuple) else base_type
451
+ if pos < len(tokens) and tokens[pos].type == TokenType.LBRACKET:
452
+ pos += 1
453
+ frames.append(('new_size_done', tok, type_str))
454
+ frames.append(('binary', 0))
455
+ continue
456
+ vals.append(NewExpr(type_str, None, token=tok))
457
+ continue
458
+ if tok.type == TokenType.KEYWORD and tok.value == 'asm':
459
+ node, pos = parse_inline_asm(tokens, pos)
460
+ vals.append(node)
461
+ continue
462
+ if tok.type == TokenType.KEYWORD and tok.value == 'sizeof':
463
+ pos += 1
464
+ if pos >= len(tokens) or tokens[pos].type != TokenType.LPAREN:
465
+ raise ParseError('Expected "("', tok)
466
+ pos += 1
467
+ type_expr, pos = parse_type(tokens, pos)
468
+ if pos >= len(tokens) or tokens[pos].type != TokenType.RPAREN:
469
+ raise ParseError('Expected ")"', tok)
470
+ pos += 1
471
+ type_str = _type_to_str(type_expr) if isinstance(type_expr, tuple) else type_expr
472
+ vals.append(SizeOf(type_str, token=tok))
473
+ continue
474
+ if tok.type == TokenType.LPAREN:
475
+ pos += 1
476
+ frames.append(('paren_rparen', tok))
477
+ frames.append(('binary', 0))
478
+ continue
479
+ raise ParseError(f'Unexpected token in expression: {tok.type.name} "{tok.value}"', tok)
480
+
481
+ elif kind == 'paren_rparen':
482
+ tok = frame[1]
483
+ if pos >= len(tokens) or tokens[pos].type != TokenType.RPAREN:
484
+ raise ParseError('Expected closing parenthesis', tok)
485
+ pos += 1
486
+
487
+ elif kind == 'new_size_done':
488
+ tok, type_str = frame[1], frame[2]
489
+ size = vals.pop()
490
+ if pos >= len(tokens) or tokens[pos].type != TokenType.RBRACKET:
491
+ raise ParseError('Expected "]"', tok)
492
+ pos += 1
493
+ vals.append(NewExpr(type_str, size, token=tok))
494
+
495
+ elif kind == 'postfix':
496
+ prefixes = frame[1]
497
+ node = vals.pop()
498
+ while pos < len(tokens) and tokens[pos].type in (TokenType.LPAREN, TokenType.LBRACKET, TokenType.DOT):
499
+ ptok = tokens[pos]
500
+ if ptok.type == TokenType.LPAREN:
501
+ pos += 1
502
+ frames.append(('call_done', prefixes))
503
+ frames.append(('callargs', node, [], ptok))
504
+ break
505
+ elif ptok.type == TokenType.LBRACKET:
506
+ pos += 1
507
+ frames.append(('index_done', prefixes, node, ptok))
508
+ frames.append(('binary', 0))
509
+ break
510
+ else:
511
+ pos += 1
512
+ if pos >= len(tokens) or tokens[pos].type != TokenType.IDENTIFIER:
513
+ raise ParseError('Expected attribute name', tokens[pos] if pos < len(tokens) else None)
514
+ name = tokens[pos].value
515
+ assert name is not None
516
+ pos += 1
517
+ node = Attr(node, name, token=ptok)
518
+ else:
519
+ vals.append(_wrap_unary_prefixes(prefixes, node))
520
+
521
+ elif kind == 'call_done':
522
+ prefixes = frame[1]
523
+ frames.append(('postfix', prefixes))
524
+
525
+ elif kind == 'callargs':
526
+ callee, args, call_tok = frame[1], frame[2], frame[3]
527
+ if pos >= len(tokens):
528
+ raise ParseError('Expected ")" after arguments', None)
529
+ if tokens[pos].type == TokenType.RPAREN:
530
+ pos += 1
531
+ vals.append(Call(callee, args, token=call_tok))
532
+ continue
533
+ frames.append(('callarg_done', callee, args, call_tok))
534
+ frames.append(('binary', 0))
535
+
536
+ elif kind == 'callarg_done':
537
+ callee, args, call_tok = frame[1], frame[2], frame[3]
538
+ args.append(vals.pop())
539
+ if pos < len(tokens) and tokens[pos].type == TokenType.COMMA:
540
+ pos += 1
541
+ frames.append(('callargs', callee, args, call_tok))
542
+
543
+ elif kind == 'index_done':
544
+ prefixes, base, tok = frame[1], frame[2], frame[3]
545
+ index = vals.pop()
546
+ if pos >= len(tokens) or tokens[pos].type != TokenType.RBRACKET:
547
+ raise ParseError('Expected "]"', tokens[pos] if pos < len(tokens) else None)
548
+ pos += 1
549
+ vals.append(Index(base, index, token=tok))
550
+ frames.append(('postfix', prefixes))
551
+
552
+ elif kind == 'binary_loop':
553
+ mp = frame[1]
554
+ if pos < len(tokens) and tokens[pos].type in _BINARY_OPS and _prec(tokens[pos]) >= mp:
555
+ op = tokens[pos]
556
+ pos += 1
557
+ pos = _skip_expr_newlines(tokens, pos)
558
+ next_prec = _prec(op) if op.type == TokenType.POW else _prec(op) + 1
559
+ frames.append(('binary_combine', mp, op))
560
+ frames.append(('binary', next_prec))
561
+
562
+ elif kind == 'binary_combine':
563
+ mp, op = frame[1], frame[2]
564
+ right = vals.pop()
565
+ left = vals.pop()
566
+ vals.append(BinOp(left, op.type, right, token=op))
567
+ frames.append(('binary_loop', mp))
568
+
569
+ node = vals.pop()
570
+ return node, pos
571
+
572
+
338
573
  def parse_file(tokens: list[Token], pos: int = 0, enable_extensions: bool = True):
339
574
  nodes = []
340
575
  while pos < len(tokens) and tokens[pos].type not in (TokenType.EOF, TokenType.DEDENT):
@@ -428,10 +663,10 @@ def _parse_func_with_visibility(tokens: list[Token], pos: int, visibility: str |
428
663
  if pos < len(tokens) and tokens[pos].type == TokenType.KEYWORD and tokens[pos].value == 'def':
429
664
  pos += 1
430
665
  name, pos = _parse_func_name(tokens, pos)
431
- params, pos = _parse_func_params(tokens, pos)
666
+ params, const_params, pos = _parse_func_params(tokens, pos)
432
667
  rettype, pos = _parse_func_rettype(tokens, pos)
433
668
  body, pos = parse_suite(tokens, pos)
434
- return FuncDef(name, params, body, rettype, visibility, token=tok), pos
669
+ return FuncDef(name, params, body, rettype, visibility, const_params=const_params, token=tok), pos
435
670
 
436
671
 
437
672
  def _parse_func_name(tokens: list[Token], pos: int):
@@ -447,12 +682,26 @@ def _parse_func_params(tokens: list[Token], pos: int):
447
682
  raise ParseError('Expected "("', tokens[pos] if pos < len(tokens) else None)
448
683
  pos += 1
449
684
  params = {}
685
+ const_params = []
450
686
  while pos < len(tokens) and tokens[pos].type != TokenType.RPAREN:
451
- if tokens[pos].type != TokenType.IDENTIFIER:
687
+ tok = tokens[pos]
688
+ is_const_view = tok.type == TokenType.LPAREN
689
+ if is_const_view:
690
+ # Constant-view parameter: `(name)` gives a read-only view of the
691
+ # argument. It is only const internally — it never becomes a module
692
+ # constant and never shadows a real one.
693
+ pos += 1
694
+ if pos >= len(tokens) or tokens[pos].type != TokenType.IDENTIFIER:
452
695
  pos += 1
453
696
  continue
454
697
  param_name = tokens[pos].value
455
698
  pos += 1
699
+ if is_const_view:
700
+ if pos >= len(tokens) or tokens[pos].type != TokenType.RPAREN:
701
+ raise ParseError('Expected ")" to close constant-view parameter name',
702
+ tokens[pos] if pos < len(tokens) else tok)
703
+ pos += 1
704
+ const_params.append(param_name)
456
705
  if pos < len(tokens) and tokens[pos].type == TokenType.COLON:
457
706
  pos += 1
458
707
  if pos < len(tokens) and tokens[pos].type not in (TokenType.COMMA, TokenType.RPAREN):
@@ -465,7 +714,7 @@ def _parse_func_params(tokens: list[Token], pos: int):
465
714
  pos += 1
466
715
  if pos >= len(tokens) or tokens[pos].type != TokenType.RPAREN:
467
716
  raise ParseError('Expected ")"', tokens[pos] if pos < len(tokens) else None)
468
- return params, pos + 1
717
+ return params, const_params, pos + 1
469
718
 
470
719
 
471
720
  def _parse_func_rettype(tokens: list[Token], pos: int):
@@ -724,14 +973,15 @@ class If:
724
973
  return f'If({self.cond}, {self.body}, {self.orelse})'
725
974
 
726
975
  class FuncDef:
727
- __slots__ = ('name', 'params', 'rettype', 'body', 'visibility', 'generic_params', '_token')
728
- def __init__(self, name: str, params: dict, body, rettype: str | None = None, visibility: str | None = None, generic_params: list | None = None, token=None):
976
+ __slots__ = ('name', 'params', 'const_params', 'rettype', 'body', 'visibility', 'generic_params', '_token')
977
+ def __init__(self, name: str, params: dict, body, rettype: str | None = None, visibility: str | None = None, generic_params: list | None = None, const_params: list | None = None, token=None):
729
978
  self.name = name
730
979
  self.params = params
731
980
  self.rettype = rettype
732
981
  self.body = body
733
982
  self.visibility = visibility
734
983
  self.generic_params = generic_params or []
984
+ self.const_params = const_params or []
735
985
  self._token = token
736
986
  def __repr__(self):
737
987
  vis = f'{self.visibility} ' if self.visibility else ''
@@ -1075,7 +1325,8 @@ def _looks_like_type(tokens, pos):
1075
1325
  # Constant syntax: <type> (NAME) = <value>
1076
1326
  if i < len(tokens) and tokens[i].type == TokenType.LPAREN:
1077
1327
  if (i + 2) < len(tokens) and tokens[i + 1].type == TokenType.IDENTIFIER and tokens[i + 2].type == TokenType.RPAREN:
1078
- return True
1328
+ if (i + 3) < len(tokens) and tokens[i + 3].type == TokenType.EQUAL:
1329
+ return True
1079
1330
  return i > pos + 1
1080
1331
 
1081
1332
 
@@ -1464,10 +1715,10 @@ def parse_def(tokens: list[Token], pos: int):
1464
1715
  pos += 1
1465
1716
  name, pos = _parse_func_name(tokens, pos)
1466
1717
  generic_params, pos = parse_generic_params(tokens, pos)
1467
- params, pos = _parse_func_params(tokens, pos)
1718
+ params, const_params, pos = _parse_func_params(tokens, pos)
1468
1719
  rettype, pos = _parse_func_rettype(tokens, pos)
1469
1720
  body, pos = parse_suite(tokens, pos)
1470
- return FuncDef(name, params, body, rettype, generic_params=generic_params, token=tok), pos
1721
+ return FuncDef(name, params, body, rettype, generic_params=generic_params, const_params=const_params, token=tok), pos
1471
1722
 
1472
1723
 
1473
1724
  def _is_assignable(expr):