jaclang 0.8.0__py3-none-any.whl → 0.8.2__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.

Potentially problematic release.


This version of jaclang might be problematic. Click here for more details.

Files changed (124) hide show
  1. jaclang/__init__.py +6 -0
  2. jaclang/cli/cli.py +23 -50
  3. jaclang/compiler/codeinfo.py +0 -1
  4. jaclang/compiler/jac.lark +14 -22
  5. jaclang/compiler/larkparse/jac_parser.py +2 -2
  6. jaclang/compiler/parser.py +378 -531
  7. jaclang/compiler/passes/main/__init__.py +0 -14
  8. jaclang/compiler/passes/main/annex_pass.py +2 -8
  9. jaclang/compiler/passes/main/cfg_build_pass.py +39 -13
  10. jaclang/compiler/passes/main/def_impl_match_pass.py +14 -13
  11. jaclang/compiler/passes/main/def_use_pass.py +4 -7
  12. jaclang/compiler/passes/main/import_pass.py +6 -14
  13. jaclang/compiler/passes/main/inheritance_pass.py +2 -2
  14. jaclang/compiler/passes/main/pyast_gen_pass.py +428 -799
  15. jaclang/compiler/passes/main/pyast_load_pass.py +115 -311
  16. jaclang/compiler/passes/main/pyjac_ast_link_pass.py +8 -7
  17. jaclang/compiler/passes/main/sym_tab_build_pass.py +3 -3
  18. jaclang/compiler/passes/main/sym_tab_link_pass.py +6 -9
  19. jaclang/compiler/passes/main/tests/fixtures/symtab_link_tests/action/actions.jac +1 -5
  20. jaclang/compiler/passes/main/tests/fixtures/symtab_link_tests/main.jac +1 -8
  21. jaclang/compiler/passes/main/tests/test_cfg_build_pass.py +5 -9
  22. jaclang/compiler/passes/main/tests/test_decl_impl_match_pass.py +7 -8
  23. jaclang/compiler/passes/main/tests/test_import_pass.py +5 -18
  24. jaclang/compiler/passes/main/tests/test_pyast_gen_pass.py +2 -6
  25. jaclang/compiler/passes/main/tests/test_sub_node_pass.py +1 -3
  26. jaclang/compiler/passes/main/tests/test_sym_tab_link_pass.py +20 -17
  27. jaclang/compiler/passes/tool/doc_ir_gen_pass.py +425 -216
  28. jaclang/compiler/passes/tool/jac_formatter_pass.py +2 -0
  29. jaclang/compiler/passes/tool/tests/fixtures/archetype_frmt.jac +14 -0
  30. jaclang/compiler/passes/tool/tests/fixtures/general_format_checks/triple_quoted_string.jac +5 -4
  31. jaclang/compiler/passes/tool/tests/fixtures/import_fmt.jac +6 -0
  32. jaclang/compiler/passes/tool/tests/fixtures/simple_walk_fmt.jac +3 -3
  33. jaclang/compiler/passes/tool/tests/fixtures/tagbreak.jac +9 -0
  34. jaclang/compiler/passes/tool/tests/test_jac_format_pass.py +18 -3
  35. jaclang/compiler/passes/tool/tests/test_unparse_validate.py +2 -2
  36. jaclang/compiler/program.py +22 -66
  37. jaclang/compiler/tests/fixtures/fam.jac +2 -2
  38. jaclang/compiler/tests/fixtures/pkg_import_lib/__init__.jac +1 -0
  39. jaclang/compiler/tests/fixtures/pkg_import_lib/sub/__init__.jac +1 -0
  40. jaclang/compiler/tests/fixtures/pkg_import_lib/sub/helper.jac +3 -0
  41. jaclang/compiler/tests/fixtures/pkg_import_lib/tools.jac +3 -0
  42. jaclang/compiler/tests/fixtures/pkg_import_lib_py/__init__.py +5 -0
  43. jaclang/compiler/tests/fixtures/pkg_import_lib_py/sub/__init__.py +3 -0
  44. jaclang/compiler/tests/fixtures/pkg_import_lib_py/sub/helper.jac +3 -0
  45. jaclang/compiler/tests/fixtures/pkg_import_lib_py/tools.jac +3 -0
  46. jaclang/compiler/tests/fixtures/pkg_import_main.jac +10 -0
  47. jaclang/compiler/tests/fixtures/pkg_import_main_py.jac +11 -0
  48. jaclang/compiler/tests/test_importer.py +30 -13
  49. jaclang/compiler/tests/test_parser.py +1 -0
  50. jaclang/compiler/unitree.py +488 -320
  51. jaclang/langserve/__init__.jac +1 -0
  52. jaclang/langserve/engine.jac +503 -0
  53. jaclang/langserve/sem_manager.jac +309 -0
  54. jaclang/langserve/server.jac +201 -0
  55. jaclang/langserve/tests/server_test/test_lang_serve.py +139 -48
  56. jaclang/langserve/tests/server_test/utils.py +35 -6
  57. jaclang/langserve/tests/session.jac +294 -0
  58. jaclang/langserve/tests/test_sem_tokens.py +2 -2
  59. jaclang/langserve/tests/test_server.py +8 -7
  60. jaclang/langserve/utils.jac +51 -30
  61. jaclang/runtimelib/archetype.py +128 -6
  62. jaclang/runtimelib/builtin.py +17 -14
  63. jaclang/runtimelib/importer.py +51 -76
  64. jaclang/runtimelib/machine.py +469 -305
  65. jaclang/runtimelib/meta_importer.py +86 -0
  66. jaclang/runtimelib/tests/fixtures/graph_purger.jac +24 -26
  67. jaclang/runtimelib/tests/fixtures/other_root_access.jac +25 -16
  68. jaclang/runtimelib/tests/fixtures/traversing_save.jac +7 -5
  69. jaclang/runtimelib/tests/test_jaseci.py +3 -1
  70. jaclang/runtimelib/utils.py +3 -3
  71. jaclang/tests/fixtures/arch_rel_import_creation.jac +23 -23
  72. jaclang/tests/fixtures/async_ability.jac +43 -10
  73. jaclang/tests/fixtures/async_function.jac +18 -0
  74. jaclang/tests/fixtures/async_walker.jac +17 -12
  75. jaclang/tests/fixtures/backward_edge_visit.jac +31 -0
  76. jaclang/tests/fixtures/builtin_printgraph.jac +85 -0
  77. jaclang/tests/fixtures/builtin_printgraph_json.jac +21 -0
  78. jaclang/tests/fixtures/builtin_printgraph_mermaid.jac +16 -0
  79. jaclang/tests/fixtures/chandra_bugs2.jac +20 -13
  80. jaclang/tests/fixtures/concurrency.jac +1 -1
  81. jaclang/tests/fixtures/create_dynamic_archetype.jac +25 -28
  82. jaclang/tests/fixtures/deep/deeper/deep_outer_import.jac +7 -4
  83. jaclang/tests/fixtures/deep/deeper/snd_lev.jac +2 -2
  84. jaclang/tests/fixtures/deep/deeper/snd_lev_dup.jac +6 -0
  85. jaclang/tests/fixtures/deep/one_lev.jac +2 -2
  86. jaclang/tests/fixtures/deep/one_lev_dup.jac +4 -3
  87. jaclang/tests/fixtures/dynamic_archetype.jac +19 -12
  88. jaclang/tests/fixtures/edge_ability.jac +49 -0
  89. jaclang/tests/fixtures/foo.jac +14 -22
  90. jaclang/tests/fixtures/guess_game.jac +1 -1
  91. jaclang/tests/fixtures/here_usage_error.jac +21 -0
  92. jaclang/tests/fixtures/here_visitor_usage.jac +21 -0
  93. jaclang/tests/fixtures/jac_from_py.py +1 -1
  94. jaclang/tests/fixtures/jp_importer.jac +6 -6
  95. jaclang/tests/fixtures/jp_importer_auto.jac +5 -3
  96. jaclang/tests/fixtures/node_del.jac +30 -36
  97. jaclang/tests/fixtures/unicode_strings.jac +24 -0
  98. jaclang/tests/fixtures/visit_traversal.jac +47 -0
  99. jaclang/tests/fixtures/walker_update.jac +5 -7
  100. jaclang/tests/test_cli.py +12 -7
  101. jaclang/tests/test_language.py +218 -145
  102. jaclang/tests/test_reference.py +9 -4
  103. jaclang/tests/test_typecheck.py +13 -26
  104. jaclang/utils/helpers.py +14 -6
  105. jaclang/utils/lang_tools.py +9 -8
  106. jaclang/utils/module_resolver.py +23 -0
  107. jaclang/utils/tests/test_lang_tools.py +2 -1
  108. jaclang/utils/treeprinter.py +3 -4
  109. {jaclang-0.8.0.dist-info → jaclang-0.8.2.dist-info}/METADATA +4 -3
  110. {jaclang-0.8.0.dist-info → jaclang-0.8.2.dist-info}/RECORD +112 -94
  111. {jaclang-0.8.0.dist-info → jaclang-0.8.2.dist-info}/WHEEL +1 -1
  112. jaclang/compiler/passes/main/tests/fixtures/main_err.jac +0 -6
  113. jaclang/compiler/passes/main/tests/fixtures/second_err.jac +0 -4
  114. jaclang/compiler/passes/tool/tests/fixtures/corelib.jac +0 -644
  115. jaclang/compiler/passes/tool/tests/test_doc_ir_gen_pass.py +0 -29
  116. jaclang/langserve/__init__.py +0 -1
  117. jaclang/langserve/engine.py +0 -553
  118. jaclang/langserve/sem_manager.py +0 -383
  119. jaclang/langserve/server.py +0 -167
  120. jaclang/langserve/tests/session.py +0 -255
  121. jaclang/tests/fixtures/builtin_dotgen.jac +0 -42
  122. jaclang/tests/fixtures/builtin_dotgen_json.jac +0 -21
  123. jaclang/tests/fixtures/deep/deeper/__init__.jac +0 -1
  124. {jaclang-0.8.0.dist-info → jaclang-0.8.2.dist-info}/entry_points.txt +0 -0
@@ -5,10 +5,10 @@ from __future__ import annotations
5
5
  import keyword
6
6
  import logging
7
7
  import os
8
- from typing import Callable, TYPE_CHECKING, TypeAlias, TypeVar
8
+ from typing import Callable, Sequence, TYPE_CHECKING, TypeAlias, TypeVar, cast
9
9
 
10
10
  import jaclang.compiler.unitree as uni
11
- from jaclang.compiler import jac_lark as jl # type: ignore
11
+ from jaclang.compiler import jac_lark as jl
12
12
  from jaclang.compiler.constant import EdgeDir, Tokens as Tok
13
13
  from jaclang.compiler.passes.main import Transform
14
14
  from jaclang.vendor.lark import Lark, Transformer, Tree, logger
@@ -17,6 +17,7 @@ if TYPE_CHECKING:
17
17
  from jaclang.compiler.program import JacProgram
18
18
 
19
19
  T = TypeVar("T", bound=uni.UniNode)
20
+ TL = TypeVar("TL", bound=(uni.UniNode | list))
20
21
 
21
22
 
22
23
  class JacParser(Transform[uni.Source, uni.Module]):
@@ -183,7 +184,13 @@ class JacParser(Transform[uni.Source, uni.Module]):
183
184
  # Parser Helper functions. #
184
185
  # ******************************************************************* #
185
186
 
186
- def match(self, ty: type[T]) -> T | None:
187
+ def extract_from_list(
188
+ self, nd_list: list[uni.UniNode], ty: type[T] | tuple[type[T], ...]
189
+ ) -> list[T]:
190
+ """Extract a list of nodes of type 'ty' from the current nodes."""
191
+ return cast(list[T], [node for node in nd_list if isinstance(node, ty)])
192
+
193
+ def match(self, ty: type[TL]) -> TL | None:
187
194
  """Return a node matching type 'ty' if possible from the current nodes."""
188
195
  if (self.node_idx < len(self.cur_nodes)) and isinstance(
189
196
  self.cur_nodes[self.node_idx], ty
@@ -192,7 +199,7 @@ class JacParser(Transform[uni.Source, uni.Module]):
192
199
  return self.cur_nodes[self.node_idx - 1] # type: ignore[return-value]
193
200
  return None
194
201
 
195
- def consume(self, ty: type[T]) -> T:
202
+ def consume(self, ty: type[TL]) -> TL:
196
203
  """Consume and return the specified type, if it's not exists, will be an internal compiler error."""
197
204
  if node := self.match(ty):
198
205
  return node
@@ -228,6 +235,17 @@ class JacParser(Transform[uni.Source, uni.Module]):
228
235
  nodes.append(node)
229
236
  return nodes # type: ignore[return-value]
230
237
 
238
+ @property
239
+ def flat_cur_nodes(self) -> list[uni.UniNode]:
240
+ """Flatten the current nodes."""
241
+ flat_nodes: list[uni.UniNode] = []
242
+ for node in self.cur_nodes:
243
+ if isinstance(node, list):
244
+ flat_nodes.extend(node)
245
+ else:
246
+ flat_nodes.append(node)
247
+ return flat_nodes
248
+
231
249
  # ******************************************************************* #
232
250
  # Parsing Rules #
233
251
  # ******************************************************************* #
@@ -293,12 +311,12 @@ class JacParser(Transform[uni.Source, uni.Module]):
293
311
  """
294
312
  is_frozen = self.consume(uni.Token).name == Tok.KW_LET
295
313
  access_tag = self.match(uni.SubTag)
296
- assignments = self.consume(uni.SubNodeList)
314
+ assignments_list = self.consume(list)
297
315
  return uni.GlobalVars(
298
316
  access=access_tag,
299
- assignments=assignments,
317
+ assignments=self.extract_from_list(assignments_list, uni.Assignment),
300
318
  is_frozen=is_frozen,
301
- kid=self.cur_nodes,
319
+ kid=self.flat_cur_nodes,
302
320
  )
303
321
 
304
322
  def access_tag(self, _: None) -> uni.SubTag[uni.Token]:
@@ -318,11 +336,11 @@ class JacParser(Transform[uni.Source, uni.Module]):
318
336
  # Q(thakee): Why the name should be KW_TEST if no name present?
319
337
  test_tok = self.consume_token(Tok.KW_TEST)
320
338
  name = self.match(uni.Name) or test_tok
321
- codeblock = self.consume(uni.SubNodeList)
339
+ codeblock = self.consume(list)
322
340
  return uni.Test(
323
341
  name=name,
324
- body=codeblock,
325
- kid=self.cur_nodes,
342
+ body=self.extract_from_list(codeblock, uni.CodeBlockStmt),
343
+ kid=self.flat_cur_nodes,
326
344
  )
327
345
 
328
346
  def free_code(self, _: None) -> uni.ModuleCode:
@@ -335,11 +353,11 @@ class JacParser(Transform[uni.Source, uni.Module]):
335
353
  name = None
336
354
  if self.match_token(Tok.COLON):
337
355
  name = self.consume(uni.Name)
338
- codeblock = self.consume(uni.SubNodeList)
356
+ codeblock = self.consume(list)
339
357
  return uni.ModuleCode(
340
358
  name=name,
341
- body=codeblock,
342
- kid=self.cur_nodes,
359
+ body=self.extract_from_list(codeblock, uni.CodeBlockStmt),
360
+ kid=self.flat_cur_nodes,
343
361
  )
344
362
 
345
363
  def py_code_block(self, _: None) -> uni.PyInlineCode:
@@ -357,26 +375,17 @@ class JacParser(Transform[uni.Source, uni.Module]):
357
375
  """Grammar rule.
358
376
 
359
377
  import_stmt: KW_IMPORT KW_FROM from_path LBRACE import_items RBRACE
360
- | KW_IMPORT KW_FROM from_path COMMA import_items SEMI //Deprecated
361
378
  | KW_IMPORT import_path (COMMA import_path)* SEMI
362
379
  | KW_INCLUDE import_path SEMI
363
380
  """
364
- # TODO: kid will be removed so let's keep as it is for now.
365
- kid = self.cur_nodes
366
-
367
381
  if self.match_token(Tok.KW_INCLUDE):
368
382
  # Handle include statement
369
383
  import_path_obj = self.consume(uni.ModulePath)
370
- items = uni.SubNodeList[uni.ModulePath](
371
- items=[import_path_obj], delim=Tok.COMMA, kid=[import_path_obj]
372
- )
373
- kid = (kid[:1]) + [items] + kid[-1:] # TODO: Will be removed.
374
- self.consume_token(Tok.SEMI)
375
384
  return uni.Import(
376
385
  from_loc=None,
377
- items=items,
386
+ items=[import_path_obj],
378
387
  is_absorb=True,
379
- kid=kid,
388
+ kid=self.cur_nodes,
380
389
  )
381
390
 
382
391
  from_path: uni.ModulePath | None = None
@@ -385,31 +394,25 @@ class JacParser(Transform[uni.Source, uni.Module]):
385
394
  if self.match_token(Tok.KW_FROM):
386
395
  from_path = self.consume(uni.ModulePath)
387
396
  self.consume(uni.Token) # LBRACE or COMMA
388
- items = self.consume(uni.SubNodeList)
389
- if self.consume(uni.Token).name == Tok.SEMI: # RBRACE or SEMI
390
- self.parse_ref.log_warning(
391
- "Deprecated syntax, use braces for multiple imports (e.g, import from mymod {a, b, c})",
392
- )
397
+ items = self.extract_from_list(self.consume(list), uni.ModuleItem)
398
+ self.consume(uni.Token)
399
+ return uni.Import(
400
+ from_loc=from_path,
401
+ items=items,
402
+ is_absorb=False,
403
+ kid=self.flat_cur_nodes,
404
+ )
393
405
  else:
394
406
  paths = [self.consume(uni.ModulePath)]
395
407
  while self.match_token(Tok.COMMA):
396
408
  paths.append(self.consume(uni.ModulePath))
397
409
  self.consume_token(Tok.SEMI)
398
- items = uni.SubNodeList[uni.ModulePath](
410
+ return uni.Import(
411
+ from_loc=from_path,
399
412
  items=paths,
400
- delim=Tok.COMMA,
401
- # TODO: kid will be removed so let's keep as it is for now.
402
- kid=self.cur_nodes[1:-1],
413
+ is_absorb=False,
414
+ kid=self.flat_cur_nodes,
403
415
  )
404
- kid = kid[:1] + [items] + kid[-1:]
405
-
406
- is_absorb = False
407
- return uni.Import(
408
- from_loc=from_path,
409
- items=items,
410
- is_absorb=is_absorb,
411
- kid=kid,
412
- )
413
416
 
414
417
  def from_path(self, _: None) -> uni.ModulePath:
415
418
  """Grammar rule.
@@ -443,44 +446,28 @@ class JacParser(Transform[uni.Source, uni.Module]):
443
446
 
444
447
  import_path: dotted_name (KW_AS NAME)?
445
448
  """
446
- valid_path = self.consume(uni.SubNodeList)
449
+ valid_path = self.extract_from_list(self.consume(list), uni.Name)
447
450
  alias = self.consume(uni.Name) if self.match_token(Tok.KW_AS) else None
448
451
  return uni.ModulePath(
449
452
  path=valid_path,
450
453
  level=0,
451
454
  alias=alias,
452
- kid=self.cur_nodes,
455
+ kid=self.flat_cur_nodes,
453
456
  )
454
457
 
455
- def dotted_name(self, _: None) -> uni.SubNodeList[uni.Name]:
458
+ def dotted_name(self, _: None) -> list[uni.UniNode]:
456
459
  """Grammar rule.
457
460
 
458
461
  dotted_name: named_ref (DOT named_ref)*
459
462
  """
460
- valid_path = [self.consume(uni.Name)]
461
- while self.match_token(Tok.DOT):
462
- valid_path.append(self.consume(uni.Name))
463
- return uni.SubNodeList[uni.Name](
464
- items=valid_path,
465
- delim=Tok.DOT,
466
- kid=self.cur_nodes,
467
- )
463
+ return self.cur_nodes
468
464
 
469
- def import_items(self, _: None) -> uni.SubNodeList[uni.ModuleItem]:
465
+ def import_items(self, _: None) -> list[uni.UniNode]:
470
466
  """Grammar rule.
471
467
 
472
468
  import_items: (import_item COMMA)* import_item COMMA?
473
469
  """
474
- items = [self.consume(uni.ModuleItem)]
475
- while self.match_token(Tok.COMMA):
476
- if module_item := self.match(uni.ModuleItem):
477
- items.append(module_item)
478
- ret = uni.SubNodeList[uni.ModuleItem](
479
- items=items,
480
- delim=Tok.COMMA,
481
- kid=self.cur_nodes,
482
- )
483
- return ret
470
+ return self.flat_cur_nodes
484
471
 
485
472
  def import_item(self, _: None) -> uni.ModuleItem:
486
473
  """Grammar rule.
@@ -504,12 +491,13 @@ class JacParser(Transform[uni.Source, uni.Module]):
504
491
  """
505
492
  archspec: uni.ArchSpec | uni.Enum | None = None
506
493
 
507
- decorators = self.match(uni.SubNodeList)
494
+ decorators_node = self.match(list)
508
495
  is_async = self.match_token(Tok.KW_ASYNC)
509
- if decorators is not None:
496
+ if decorators_node is not None:
510
497
  archspec = self.consume(uni.ArchSpec)
498
+ decorators = self.extract_from_list(decorators_node, uni.Expr)
511
499
  archspec.decorators = decorators
512
- archspec.add_kids_left([decorators])
500
+ archspec.add_kids_left(decorators_node)
513
501
  else:
514
502
  archspec = self.match(uni.ArchSpec) or self.consume(uni.Enum)
515
503
  if is_async and isinstance(archspec, uni.ArchSpec):
@@ -527,37 +515,46 @@ class JacParser(Transform[uni.Source, uni.Module]):
527
515
 
528
516
  impl_def: decorators? KW_IMPL dotted_name impl_spec? impl_tail
529
517
  """
530
- decorators = self.match(uni.SubNodeList)
518
+ decorators_node = self.match(list)
531
519
  self.consume_token(Tok.KW_IMPL)
532
- target = self.consume(uni.SubNodeList)
520
+ target = self.extract_from_list(self.consume(list), uni.NameAtom)
533
521
  spec = (
534
- self.match(uni.SubNodeList)
522
+ self.match(list)
535
523
  or self.match(uni.FuncSignature)
536
524
  or self.match(uni.EventSignature)
537
525
  )
538
- tail = self.match(uni.SubNodeList) or self.match(uni.FuncCall)
526
+ tail = self.match(list) or self.match(uni.FuncCall)
539
527
  valid_tail = spec if tail is None else tail
540
528
  valid_spec = None if tail is None else spec
541
- assert isinstance(valid_tail, (uni.SubNodeList, uni.FuncCall))
542
-
543
529
  impl = uni.ImplDef(
544
- decorators=decorators,
530
+ body=(
531
+ self.extract_from_list(
532
+ valid_tail,
533
+ (uni.EnumBlockStmt, uni.CodeBlockStmt), # type: ignore[arg-type]
534
+ )
535
+ if isinstance(valid_tail, list)
536
+ else valid_tail
537
+ ),
545
538
  target=target,
539
+ decorators=(
540
+ self.extract_from_list(decorators_node, uni.Expr)
541
+ if decorators_node
542
+ else None
543
+ ),
546
544
  spec=valid_spec,
547
- body=valid_tail,
548
- kid=self.cur_nodes,
545
+ kid=self.flat_cur_nodes,
549
546
  )
550
547
  return impl
551
548
 
552
549
  def impl_spec(
553
550
  self, _: None
554
- ) -> uni.SubNodeList[uni.Expr] | uni.FuncSignature | uni.EventSignature:
551
+ ) -> Sequence[uni.Expr] | uni.FuncSignature | uni.EventSignature:
555
552
  """Grammar rule.
556
553
 
557
554
  impl_spec: inherited_archs | func_decl | event_clause
558
555
  """
559
556
  spec = (
560
- self.match(uni.SubNodeList) # inherited_archs
557
+ self.match(list) # inherited_archs
561
558
  or self.match(uni.FuncSignature) # func_decl
562
559
  or self.consume(uni.EventSignature) # event_clause
563
560
  )
@@ -565,16 +562,14 @@ class JacParser(Transform[uni.Source, uni.Module]):
565
562
 
566
563
  def impl_tail(
567
564
  self, _: None
568
- ) -> uni.SubNodeList[uni.CodeBlockStmt] | uni.FuncCall:
565
+ ) -> Sequence[uni.EnumBlockStmt] | list[uni.CodeBlockStmt] | uni.FuncCall:
569
566
  """Grammar rule.
570
567
 
571
568
  impl_tail: enum_block | block_tail
572
569
  """
573
- tail = (
574
- self.match(uni.SubNodeList) # enum_block
575
- or self.match(uni.SubNodeList) # block_tail (code_block)
576
- or self.consume(uni.FuncCall) # block_tail (KW_BY atomic_call)
577
- )
570
+ tail = self.match(list) or self.consume( # enum_block or code_block
571
+ uni.FuncCall
572
+ ) # block_tail (KW_BY atomic_call)
578
573
  return tail
579
574
 
580
575
  def archetype_decl(self, _: None) -> uni.ArchSpec:
@@ -585,22 +580,23 @@ class JacParser(Transform[uni.Source, uni.Module]):
585
580
  arch_type = self.consume(uni.Token)
586
581
  access = self.match(uni.SubTag)
587
582
  name = self.consume(uni.Name)
588
- sub_list1 = self.match(uni.SubNodeList)
589
- sub_list2 = self.match(uni.SubNodeList)
590
- if self.match_token(Tok.SEMI):
591
- inh, body = sub_list1, None
583
+ inh_sn = self.match(list)
584
+ body_list = self.match(list)
585
+ body: list[uni.ArchBlockStmt] | None
586
+ if body_list is None and self.match_token(Tok.SEMI):
587
+ body = None
588
+ elif body_list is None:
589
+ body = self.extract_from_list(inh_sn or [], uni.ArchBlockStmt)
590
+ inh_sn = None
592
591
  else:
593
- body = (
594
- sub_list2 or sub_list1
595
- ) # if sub_list2 is None then body is sub_list1
596
- inh = sub_list2 and sub_list1 # if sub_list2 is None then inh is None.
592
+ body = self.extract_from_list(body_list or [], uni.ArchBlockStmt)
597
593
  return uni.Archetype(
598
594
  arch_type=arch_type,
599
595
  name=name,
600
596
  access=access,
601
- base_classes=inh,
597
+ base_classes=self.extract_from_list(inh_sn or [], uni.Expr) or [],
602
598
  body=body,
603
- kid=self.cur_nodes,
599
+ kid=self.flat_cur_nodes,
604
600
  )
605
601
 
606
602
  def arch_type(self, _: None) -> uni.Token:
@@ -613,30 +609,19 @@ class JacParser(Transform[uni.Source, uni.Module]):
613
609
  """
614
610
  return self.consume(uni.Token)
615
611
 
616
- def decorators(self, _: None) -> uni.SubNodeList[uni.Expr]:
612
+ def decorators(self, _: None) -> list[uni.UniNode]:
617
613
  """Grammar rule.
618
614
 
619
615
  decorators: (DECOR_OP atomic_chain)+
620
616
  """
621
- self.consume_token(Tok.DECOR_OP)
622
- return uni.SubNodeList[uni.Expr](
623
- items=self.consume_many(uni.Expr),
624
- delim=Tok.DECOR_OP,
625
- kid=self.cur_nodes,
626
- )
617
+ return self.cur_nodes
627
618
 
628
- def inherited_archs(self, kid: list[uni.UniNode]) -> uni.SubNodeList[uni.Expr]:
619
+ def inherited_archs(self, kid: list[uni.UniNode]) -> list[uni.UniNode]:
629
620
  """Grammar rule.
630
621
 
631
622
  inherited_archs: LPAREN (atomic_chain COMMA)* atomic_chain RPAREN
632
623
  """
633
- self.match_token(Tok.LPAREN)
634
- items: list = []
635
- while inherited_arch := self.match(uni.Expr):
636
- items.append(inherited_arch)
637
- self.match_token(Tok.COMMA)
638
- self.match_token(Tok.RPAREN)
639
- return uni.SubNodeList[uni.Expr](items=items, delim=Tok.COMMA, kid=kid)
624
+ return self.flat_cur_nodes
640
625
 
641
626
  def named_ref(self, _: None) -> uni.NameAtom:
642
627
  """Grammar rule.
@@ -666,10 +651,10 @@ class JacParser(Transform[uni.Source, uni.Module]):
666
651
  enum: decorators? enum_decl
667
652
  | enum_def
668
653
  """
669
- if decorator := self.match(uni.SubNodeList):
654
+ if decorator := self.match(list):
670
655
  enum_decl = self.consume(uni.Enum)
671
- enum_decl.decorators = decorator
672
- enum_decl.add_kids_left([decorator])
656
+ enum_decl.decorators = self.extract_from_list(decorator, uni.Expr)
657
+ enum_decl.add_kids_left(decorator)
673
658
  return enum_decl
674
659
  return self.consume(uni.Enum)
675
660
 
@@ -681,48 +666,48 @@ class JacParser(Transform[uni.Source, uni.Module]):
681
666
  self.consume_token(Tok.KW_ENUM)
682
667
  access = self.match(uni.SubTag)
683
668
  name = self.consume(uni.Name)
684
- sub_list1 = self.match(uni.SubNodeList)
685
- sub_list2 = self.match(uni.SubNodeList)
686
- if self.match_token(Tok.SEMI):
669
+ sub_list1 = self.match(list)
670
+ enum_body = self.match(list)
671
+ body: list[uni.UniNode] | None = None
672
+ if enum_body is None and self.match_token(Tok.SEMI):
687
673
  inh, body = sub_list1, None
674
+ elif enum_body is None:
675
+ body = self.extract_from_list(sub_list1 or [], uni.EnumBlockStmt)
676
+ inh = None
688
677
  else:
689
- body = sub_list2 or sub_list1
690
- inh = sub_list2 and sub_list1
678
+ body = enum_body
679
+ inh = sub_list1
691
680
  return uni.Enum(
692
681
  name=name,
693
682
  access=access,
694
- base_classes=inh,
695
- body=body,
696
- kid=self.cur_nodes,
683
+ base_classes=self.extract_from_list(inh or [], uni.Expr) or [],
684
+ body=self.extract_from_list(body, uni.EnumBlockStmt) if body else None,
685
+ kid=self.flat_cur_nodes,
697
686
  )
698
687
 
699
- def enum_block(self, _: None) -> uni.SubNodeList[uni.EnumBlockStmt]:
688
+ def enum_block(self, _: None) -> list[uni.UniNode]:
700
689
  """Grammar rule.
701
690
 
702
691
  enum_block: LBRACE assignment_list COMMA? (py_code_block | free_code)* RBRACE
703
692
  """
704
693
  left_enc = self.consume_token(Tok.LBRACE)
705
- assignments = self.consume(uni.SubNodeList)
694
+ assignments = self.consume(list)
706
695
  self.match_token(Tok.COMMA)
707
696
  while item := self.match(uni.EnumBlockStmt):
708
- assignments.add_kids_right([item])
709
- assignments.items.append(item)
697
+ item.is_enum_stmt = True
698
+ assignments.append(item)
710
699
  right_enc = self.consume_token(Tok.RBRACE)
711
- assignments.add_kids_left([left_enc])
712
- assignments.add_kids_right([right_enc])
713
- assignments.left_enc = left_enc
714
- assignments.right_enc = right_enc
715
- for i in assignments.kid:
700
+ for i in assignments:
716
701
  if isinstance(i, uni.Assignment):
717
702
  i.is_enum_stmt = True
718
- return assignments
703
+ return [left_enc, *assignments, right_enc]
719
704
 
720
705
  def ability(self, _: None) -> uni.Ability | uni.FuncCall:
721
706
  """Grammar rule.
722
707
 
723
708
  ability: decorators? KW_ASYNC? (ability_decl | function_decl)
724
709
  """
725
- decorators = self.match(uni.SubNodeList)
710
+ decorators_node = self.match(list)
726
711
  is_async = self.match_token(Tok.KW_ASYNC)
727
712
 
728
713
  # Try to match ability_decl or function_decl
@@ -731,8 +716,9 @@ class JacParser(Transform[uni.Source, uni.Module]):
731
716
  ability.is_async = True
732
717
  ability.add_kids_left([is_async])
733
718
 
734
- if decorators:
735
- for dec in decorators.items:
719
+ if decorators_node:
720
+ decorators = self.extract_from_list(decorators_node, uni.Expr)
721
+ for dec in decorators[:]:
736
722
  if (
737
723
  isinstance(dec, uni.NameAtom)
738
724
  and dec.sym_name == "staticmethod"
@@ -742,7 +728,7 @@ class JacParser(Transform[uni.Source, uni.Module]):
742
728
  static_kw.line_no = dec.loc.first_line
743
729
  static_kw.c_start = dec.loc.col_start
744
730
  static_kw.c_end = static_kw.c_start + len(static_kw.name)
745
- decorators.items.remove(dec) # noqa: B038
731
+ decorators.remove(dec) # noqa: B038
746
732
  if not ability.is_static:
747
733
  ability.is_static = True
748
734
  if not ability.is_override:
@@ -750,9 +736,9 @@ class JacParser(Transform[uni.Source, uni.Module]):
750
736
  else:
751
737
  ability.insert_kids_at_pos([static_kw], 1)
752
738
  break
753
- if decorators.items:
739
+ if decorators:
754
740
  ability.decorators = decorators
755
- ability.add_kids_left([decorators])
741
+ ability.add_kids_left(decorators_node)
756
742
 
757
743
  return ability
758
744
 
@@ -770,12 +756,18 @@ class JacParser(Transform[uni.Source, uni.Module]):
770
756
  signature = self.consume(uni.EventSignature)
771
757
 
772
758
  # Handle block_tail
773
- body = self.match(uni.SubNodeList) or self.match(uni.FuncCall)
774
- if body is None:
759
+ body_sn_or_call = self.match(list) or self.match(uni.FuncCall)
760
+ if body_sn_or_call is None:
775
761
  is_abstract = self.match_token(Tok.KW_ABSTRACT) is not None
776
762
  self.consume_token(Tok.SEMI)
763
+ body = None
777
764
  else:
778
765
  is_abstract = False
766
+ body = (
767
+ self.extract_from_list(body_sn_or_call, uni.CodeBlockStmt)
768
+ if isinstance(body_sn_or_call, list)
769
+ else body_sn_or_call
770
+ )
779
771
 
780
772
  return uni.Ability(
781
773
  name_ref=name,
@@ -786,7 +778,7 @@ class JacParser(Transform[uni.Source, uni.Module]):
786
778
  access=access,
787
779
  signature=signature,
788
780
  body=body,
789
- kid=self.cur_nodes,
781
+ kid=self.flat_cur_nodes,
790
782
  )
791
783
 
792
784
  def function_decl(self, _: None) -> uni.Ability:
@@ -804,12 +796,18 @@ class JacParser(Transform[uni.Source, uni.Module]):
804
796
  signature = self.match(uni.FuncSignature)
805
797
 
806
798
  # Handle block_tail
807
- body = self.match(uni.SubNodeList) or self.match(uni.FuncCall)
808
- if body is None:
799
+ body_sn_or_call = self.match(list) or self.match(uni.FuncCall)
800
+ if body_sn_or_call is None:
809
801
  is_abstract = self.match_token(Tok.KW_ABSTRACT) is not None
810
802
  self.consume_token(Tok.SEMI)
803
+ body = None
811
804
  else:
812
805
  is_abstract = False
806
+ body = (
807
+ self.extract_from_list(body_sn_or_call, uni.CodeBlockStmt)
808
+ if isinstance(body_sn_or_call, list)
809
+ else body_sn_or_call
810
+ )
813
811
 
814
812
  return uni.Ability(
815
813
  name_ref=name,
@@ -820,7 +818,7 @@ class JacParser(Transform[uni.Source, uni.Module]):
820
818
  access=access,
821
819
  signature=signature,
822
820
  body=body,
823
- kid=self.cur_nodes,
821
+ kid=self.flat_cur_nodes,
824
822
  )
825
823
 
826
824
  def func_decl(self, _: None) -> uni.FuncSignature:
@@ -829,44 +827,38 @@ class JacParser(Transform[uni.Source, uni.Module]):
829
827
  func_decl: (LPAREN func_decl_params? RPAREN) (RETURN_HINT expression)?
830
828
  | (RETURN_HINT expression)
831
829
  """
832
- params: uni.SubNodeList | None = None
830
+ params: list[uni.UniNode] | None = None
833
831
  return_spec: uni.Expr | None = None
834
832
 
835
833
  # Check if starting with RETURN_HINT
836
834
  if self.match_token(Tok.RETURN_HINT):
837
835
  return_spec = self.consume(uni.Expr)
838
836
  return uni.FuncSignature(
839
- params=None,
837
+ params=[],
840
838
  return_type=return_spec,
841
- kid=self.cur_nodes,
839
+ kid=self.flat_cur_nodes,
842
840
  )
843
841
  # Otherwise, parse the traditional parameter list form
844
842
  else:
845
843
  self.consume_token(Tok.LPAREN)
846
- params = self.match(uni.SubNodeList)
844
+ params = self.match(list)
847
845
  self.consume_token(Tok.RPAREN)
848
846
  if self.match_token(Tok.RETURN_HINT):
849
847
  return_spec = self.consume(uni.Expr)
850
848
  return uni.FuncSignature(
851
- params=params,
849
+ params=(
850
+ self.extract_from_list(params, uni.ParamVar) if params else []
851
+ ),
852
852
  return_type=return_spec,
853
- kid=self.cur_nodes,
853
+ kid=self.flat_cur_nodes,
854
854
  )
855
855
 
856
- def func_decl_params(self, _: None) -> uni.SubNodeList[uni.ParamVar]:
856
+ def func_decl_params(self, _: None) -> list[uni.UniNode]:
857
857
  """Grammar rule.
858
858
 
859
859
  func_decl_params: (param_var COMMA)* param_var COMMA?
860
860
  """
861
- paramvar: list = []
862
- while param_stmt := self.match(uni.ParamVar):
863
- paramvar.append(param_stmt)
864
- self.match_token(Tok.COMMA)
865
- return uni.SubNodeList[uni.ParamVar](
866
- items=paramvar,
867
- delim=Tok.COMMA,
868
- kid=self.cur_nodes,
869
- )
861
+ return self.cur_nodes
870
862
 
871
863
  def param_var(self, _: None) -> uni.ParamVar:
872
864
  """Grammar rule.
@@ -885,22 +877,12 @@ class JacParser(Transform[uni.Source, uni.Module]):
885
877
  kid=self.cur_nodes,
886
878
  )
887
879
 
888
- def member_block(self, _: None) -> uni.SubNodeList[uni.ArchBlockStmt]:
880
+ def member_block(self, _: None) -> list[uni.UniNode]:
889
881
  """Grammar rule.
890
882
 
891
883
  member_block: LBRACE member_stmt* RBRACE
892
884
  """
893
- left_enc = self.consume_token(Tok.LBRACE)
894
- items = self.match_many(uni.ArchBlockStmt)
895
- right_enc = self.consume_token(Tok.RBRACE)
896
- ret = uni.SubNodeList[uni.ArchBlockStmt](
897
- items=items,
898
- delim=Tok.WS,
899
- kid=self.cur_nodes,
900
- )
901
- ret.left_enc = left_enc
902
- ret.right_enc = right_enc
903
- return ret
885
+ return self.cur_nodes
904
886
 
905
887
  def member_stmt(self, _: None) -> uni.ArchBlockStmt:
906
888
  """Grammar rule.
@@ -929,35 +911,24 @@ class JacParser(Transform[uni.Source, uni.Module]):
929
911
  access = chomp[0] if isinstance(chomp[0], uni.SubTag) else None
930
912
  chomp = chomp[1:] if access else chomp
931
913
  assign = chomp[0]
932
- if isinstance(assign, uni.SubNodeList):
914
+ if isinstance(assign, list):
915
+ assigns = self.extract_from_list(assign, uni.HasVar)
933
916
  return uni.ArchHas(
934
- vars=assign,
917
+ vars=assigns,
935
918
  is_static=is_static,
936
919
  is_frozen=is_freeze,
937
920
  access=access,
938
- kid=kid,
921
+ kid=self.flat_cur_nodes,
939
922
  )
940
923
  else:
941
924
  raise self.ice()
942
925
 
943
- def has_assign_list(self, _: None) -> uni.SubNodeList[uni.HasVar]:
926
+ def has_assign_list(self, _: None) -> list[uni.UniNode]:
944
927
  """Grammar rule.
945
928
 
946
929
  has_assign_list: (has_assign_list COMMA)? typed_has_clause
947
930
  """
948
- if consume := self.match(uni.SubNodeList):
949
- comma = self.consume_token(Tok.COMMA)
950
- assign = self.consume(uni.HasVar)
951
- new_kid = [*consume.kid, comma, assign]
952
- else:
953
- assign = self.consume(uni.HasVar)
954
- new_kid = [assign]
955
- valid_kid = [i for i in new_kid if isinstance(i, uni.HasVar)]
956
- return uni.SubNodeList[uni.HasVar](
957
- items=valid_kid,
958
- delim=Tok.COMMA,
959
- kid=new_kid,
960
- )
931
+ return self.flat_cur_nodes
961
932
 
962
933
  def typed_has_clause(self, _: None) -> uni.HasVar:
963
934
  """Grammar rule.
@@ -1017,26 +988,12 @@ class JacParser(Transform[uni.Source, uni.Module]):
1017
988
  pos_end=token.pos_end,
1018
989
  )
1019
990
 
1020
- def code_block(
1021
- self, kid: list[uni.UniNode]
1022
- ) -> uni.SubNodeList[uni.CodeBlockStmt]:
991
+ def code_block(self, kid: list[uni.UniNode]) -> list[uni.UniNode]:
1023
992
  """Grammar rule.
1024
993
 
1025
994
  code_block: LBRACE statement* RBRACE
1026
995
  """
1027
- left_enc = kid[0] if isinstance(kid[0], uni.Token) else None
1028
- right_enc = kid[-1] if isinstance(kid[-1], uni.Token) else None
1029
- valid_stmt = [i for i in kid if isinstance(i, uni.CodeBlockStmt)]
1030
- if len(valid_stmt) == len(kid) - 2:
1031
- return uni.SubNodeList[uni.CodeBlockStmt](
1032
- items=valid_stmt,
1033
- delim=Tok.WS,
1034
- left_enc=left_enc,
1035
- right_enc=right_enc,
1036
- kid=kid,
1037
- )
1038
- else:
1039
- raise self.ice()
996
+ return self.flat_cur_nodes
1040
997
 
1041
998
  def statement(self, kid: list[uni.UniNode]) -> uni.CodeBlockStmt:
1042
999
  """Grammar rule.
@@ -1102,11 +1059,11 @@ class JacParser(Transform[uni.Source, uni.Module]):
1102
1059
  """
1103
1060
  self.consume_token(Tok.RETURN_HINT)
1104
1061
  ctx = self.consume(uni.Expr)
1105
- body = self.consume(uni.SubNodeList)
1062
+ body = self.consume(list)
1106
1063
  return uni.TypedCtxBlock(
1107
1064
  type_ctx=ctx,
1108
- body=body,
1109
- kid=self.cur_nodes,
1065
+ body=self.extract_from_list(body, uni.CodeBlockStmt),
1066
+ kid=self.flat_cur_nodes,
1110
1067
  )
1111
1068
 
1112
1069
  def if_stmt(self, _: None) -> uni.IfStmt:
@@ -1116,13 +1073,13 @@ class JacParser(Transform[uni.Source, uni.Module]):
1116
1073
  """
1117
1074
  self.consume_token(Tok.KW_IF)
1118
1075
  condition = self.consume(uni.Expr)
1119
- body = self.consume(uni.SubNodeList)
1076
+ body = self.consume(list)
1120
1077
  else_body = self.match(uni.ElseStmt) or self.match(uni.ElseIf)
1121
1078
  return uni.IfStmt(
1122
1079
  condition=condition,
1123
- body=body,
1080
+ body=self.extract_from_list(body, uni.CodeBlockStmt),
1124
1081
  else_body=else_body,
1125
- kid=self.cur_nodes,
1082
+ kid=self.flat_cur_nodes,
1126
1083
  )
1127
1084
 
1128
1085
  def elif_stmt(self, _: None) -> uni.ElseIf:
@@ -1132,13 +1089,13 @@ class JacParser(Transform[uni.Source, uni.Module]):
1132
1089
  """
1133
1090
  self.consume_token(Tok.KW_ELIF)
1134
1091
  condition = self.consume(uni.Expr)
1135
- body = self.consume(uni.SubNodeList)
1092
+ body = self.consume(list)
1136
1093
  else_body = self.match(uni.ElseStmt) or self.match(uni.ElseIf)
1137
1094
  return uni.ElseIf(
1138
1095
  condition=condition,
1139
- body=body,
1096
+ body=self.extract_from_list(body, uni.CodeBlockStmt),
1140
1097
  else_body=else_body,
1141
- kid=self.cur_nodes,
1098
+ kid=self.flat_cur_nodes,
1142
1099
  )
1143
1100
 
1144
1101
  def else_stmt(self, _: None) -> uni.ElseStmt:
@@ -1147,10 +1104,10 @@ class JacParser(Transform[uni.Source, uni.Module]):
1147
1104
  else_stmt: KW_ELSE code_block
1148
1105
  """
1149
1106
  self.consume_token(Tok.KW_ELSE)
1150
- body = self.consume(uni.SubNodeList)
1107
+ body = self.consume(list)
1151
1108
  return uni.ElseStmt(
1152
- body=body,
1153
- kid=self.cur_nodes,
1109
+ body=self.extract_from_list(body, uni.CodeBlockStmt),
1110
+ kid=self.flat_cur_nodes,
1154
1111
  )
1155
1112
 
1156
1113
  def try_stmt(self, _: None) -> uni.TryStmt:
@@ -1159,31 +1116,28 @@ class JacParser(Transform[uni.Source, uni.Module]):
1159
1116
  try_stmt: KW_TRY code_block except_list? else_stmt? finally_stmt?
1160
1117
  """
1161
1118
  self.consume_token(Tok.KW_TRY)
1162
- block = self.consume(uni.SubNodeList)
1163
- except_list = self.match(uni.SubNodeList)
1119
+ block = self.consume(list)
1120
+ except_list = self.match(list)
1164
1121
  else_stmt = self.match(uni.ElseStmt)
1165
1122
  finally_stmt = self.match(uni.FinallyStmt)
1166
1123
  return uni.TryStmt(
1167
- body=block,
1168
- excepts=except_list,
1124
+ body=self.extract_from_list(block, uni.CodeBlockStmt),
1125
+ excepts=(
1126
+ self.extract_from_list(except_list, uni.Except)
1127
+ if except_list
1128
+ else []
1129
+ ),
1169
1130
  else_body=else_stmt,
1170
1131
  finally_body=finally_stmt,
1171
- kid=self.cur_nodes,
1132
+ kid=self.flat_cur_nodes,
1172
1133
  )
1173
1134
 
1174
- def except_list(self, _: None) -> uni.SubNodeList[uni.Except]:
1135
+ def except_list(self, _: None) -> list[uni.UniNode]:
1175
1136
  """Grammar rule.
1176
1137
 
1177
1138
  except_list: except_def+
1178
1139
  """
1179
- items = [self.consume(uni.Except)]
1180
- while expt := self.match(uni.Except):
1181
- items.append(expt)
1182
- return uni.SubNodeList[uni.Except](
1183
- items=items,
1184
- delim=Tok.WS,
1185
- kid=self.cur_nodes,
1186
- )
1140
+ return self.flat_cur_nodes
1187
1141
 
1188
1142
  def except_def(self, _: None) -> uni.Except:
1189
1143
  """Grammar rule.
@@ -1195,12 +1149,12 @@ class JacParser(Transform[uni.Source, uni.Module]):
1195
1149
  ex_type = self.consume(uni.Expr)
1196
1150
  if self.match_token(Tok.KW_AS):
1197
1151
  name = self.consume(uni.Name)
1198
- body = self.consume(uni.SubNodeList)
1152
+ body_node = self.consume(list)
1199
1153
  return uni.Except(
1200
1154
  ex_type=ex_type,
1201
1155
  name=name,
1202
- body=body,
1203
- kid=self.cur_nodes,
1156
+ body=self.extract_from_list(body_node, uni.CodeBlockStmt),
1157
+ kid=self.flat_cur_nodes,
1204
1158
  )
1205
1159
 
1206
1160
  def finally_stmt(self, _: None) -> uni.FinallyStmt:
@@ -1209,10 +1163,10 @@ class JacParser(Transform[uni.Source, uni.Module]):
1209
1163
  finally_stmt: KW_FINALLY code_block
1210
1164
  """
1211
1165
  self.consume_token(Tok.KW_FINALLY)
1212
- body = self.consume(uni.SubNodeList)
1166
+ body = self.consume(list)
1213
1167
  return uni.FinallyStmt(
1214
- body=body,
1215
- kid=self.cur_nodes,
1168
+ body=self.extract_from_list(body, uni.CodeBlockStmt),
1169
+ kid=self.flat_cur_nodes,
1216
1170
  )
1217
1171
 
1218
1172
  def for_stmt(self, _: None) -> uni.IterForStmt | uni.InForStmt:
@@ -1228,29 +1182,29 @@ class JacParser(Transform[uni.Source, uni.Module]):
1228
1182
  condition = self.consume(uni.Expr)
1229
1183
  self.consume_token(Tok.KW_BY)
1230
1184
  count_by = self.consume(uni.Assignment)
1231
- body = self.consume(uni.SubNodeList)
1185
+ body = self.consume(list)
1232
1186
  else_body = self.match(uni.ElseStmt)
1233
1187
  return uni.IterForStmt(
1234
1188
  is_async=is_async,
1235
1189
  iter=iter,
1236
1190
  condition=condition,
1237
1191
  count_by=count_by,
1238
- body=body,
1192
+ body=self.extract_from_list(body, uni.CodeBlockStmt),
1239
1193
  else_body=else_body,
1240
- kid=self.cur_nodes,
1194
+ kid=self.flat_cur_nodes,
1241
1195
  )
1242
1196
  target = self.consume(uni.Expr)
1243
1197
  self.consume_token(Tok.KW_IN)
1244
1198
  collection = self.consume(uni.Expr)
1245
- body = self.consume(uni.SubNodeList)
1199
+ body = self.consume(list)
1246
1200
  else_body = self.match(uni.ElseStmt)
1247
1201
  return uni.InForStmt(
1248
1202
  is_async=is_async,
1249
1203
  target=target,
1250
1204
  collection=collection,
1251
- body=body,
1205
+ body=self.extract_from_list(body, uni.CodeBlockStmt),
1252
1206
  else_body=else_body,
1253
- kid=self.cur_nodes,
1207
+ kid=self.flat_cur_nodes,
1254
1208
  )
1255
1209
 
1256
1210
  def while_stmt(self, _: None) -> uni.WhileStmt:
@@ -1260,11 +1214,11 @@ class JacParser(Transform[uni.Source, uni.Module]):
1260
1214
  """
1261
1215
  self.consume_token(Tok.KW_WHILE)
1262
1216
  condition = self.consume(uni.Expr)
1263
- body = self.consume(uni.SubNodeList)
1217
+ body = self.consume(list)
1264
1218
  return uni.WhileStmt(
1265
1219
  condition=condition,
1266
- body=body,
1267
- kid=self.cur_nodes,
1220
+ body=self.extract_from_list(body, uni.CodeBlockStmt),
1221
+ kid=self.flat_cur_nodes,
1268
1222
  )
1269
1223
 
1270
1224
  def with_stmt(self, _: None) -> uni.WithStmt:
@@ -1274,28 +1228,21 @@ class JacParser(Transform[uni.Source, uni.Module]):
1274
1228
  """
1275
1229
  is_async = bool(self.match_token(Tok.KW_ASYNC))
1276
1230
  self.consume_token(Tok.KW_WITH)
1277
- exprs = self.consume(uni.SubNodeList)
1278
- body = self.consume(uni.SubNodeList)
1231
+ exprs_node = self.extract_from_list(self.consume(list), uni.ExprAsItem)
1232
+ body = self.consume(list)
1279
1233
  return uni.WithStmt(
1280
1234
  is_async=is_async,
1281
- exprs=exprs,
1282
- body=body,
1283
- kid=self.cur_nodes,
1235
+ exprs=exprs_node,
1236
+ body=self.extract_from_list(body, uni.CodeBlockStmt),
1237
+ kid=self.flat_cur_nodes,
1284
1238
  )
1285
1239
 
1286
- def expr_as_list(self, _: None) -> uni.SubNodeList[uni.ExprAsItem]:
1240
+ def expr_as_list(self, _: None) -> list[uni.UniNode]:
1287
1241
  """Grammar rule.
1288
1242
 
1289
1243
  expr_as_list: (expr_as COMMA)* expr_as
1290
1244
  """
1291
- items = [self.consume(uni.ExprAsItem)]
1292
- while self.match_token(Tok.COMMA):
1293
- items.append(self.consume(uni.ExprAsItem))
1294
- return uni.SubNodeList[uni.ExprAsItem](
1295
- items=items,
1296
- delim=Tok.COMMA,
1297
- kid=self.cur_nodes,
1298
- )
1245
+ return self.cur_nodes
1299
1246
 
1300
1247
  def expr_as(self, _: None) -> uni.ExprAsItem:
1301
1248
  """Grammar rule.
@@ -1466,10 +1413,10 @@ class JacParser(Transform[uni.Source, uni.Module]):
1466
1413
  global_ref: GLOBAL_OP name_list
1467
1414
  """
1468
1415
  self.consume_token(Tok.GLOBAL_OP)
1469
- target = self.consume(uni.SubNodeList)
1416
+ target = self.consume(list)
1470
1417
  return uni.GlobalStmt(
1471
- target=target,
1472
- kid=self.cur_nodes,
1418
+ target=self.extract_from_list(target, uni.Name),
1419
+ kid=self.flat_cur_nodes,
1473
1420
  )
1474
1421
 
1475
1422
  def nonlocal_ref(self, _: None) -> uni.NonLocalStmt:
@@ -1478,10 +1425,10 @@ class JacParser(Transform[uni.Source, uni.Module]):
1478
1425
  nonlocal_ref: NONLOCAL_OP name_list
1479
1426
  """
1480
1427
  self.consume_token(Tok.NONLOCAL_OP)
1481
- target = self.consume(uni.SubNodeList)
1428
+ target = self.consume(list)
1482
1429
  return uni.NonLocalStmt(
1483
- target=target,
1484
- kid=self.cur_nodes,
1430
+ target=self.extract_from_list(target, uni.Name),
1431
+ kid=self.flat_cur_nodes,
1485
1432
  )
1486
1433
 
1487
1434
  def assignment(self, _: None) -> uni.Assignment:
@@ -1516,28 +1463,21 @@ class JacParser(Transform[uni.Source, uni.Module]):
1516
1463
  value = self.consume(uni.Expr) if self.match_token(Tok.EQ) else None
1517
1464
 
1518
1465
  valid_assignees = [i for i in assignees if isinstance(i, (uni.Expr))]
1519
- new_targ = uni.SubNodeList[uni.Expr](
1520
- items=valid_assignees,
1521
- delim=Tok.EQ,
1522
- kid=assignees,
1523
- )
1524
- kid = [x for x in self.cur_nodes if x not in assignees]
1525
- kid.insert(1, new_targ) if is_frozen else kid.insert(0, new_targ)
1526
1466
  if is_aug:
1527
1467
  return uni.Assignment(
1528
- target=new_targ,
1468
+ target=valid_assignees,
1529
1469
  type_tag=type_tag,
1530
1470
  value=value,
1531
1471
  mutable=is_frozen,
1532
1472
  aug_op=is_aug,
1533
- kid=kid,
1473
+ kid=self.flat_cur_nodes,
1534
1474
  )
1535
1475
  return uni.Assignment(
1536
- target=new_targ,
1476
+ target=valid_assignees,
1537
1477
  type_tag=type_tag,
1538
1478
  value=value,
1539
1479
  mutable=is_frozen,
1540
- kid=kid,
1480
+ kid=self.flat_cur_nodes,
1541
1481
  )
1542
1482
 
1543
1483
  def expression(self, _: None) -> uni.Expr:
@@ -1591,18 +1531,20 @@ class JacParser(Transform[uni.Source, uni.Module]):
1591
1531
  return_type: uni.Expr | None = None
1592
1532
  sig_kid: list[uni.UniNode] = []
1593
1533
  self.consume_token(Tok.KW_LAMBDA)
1594
- params = self.match(uni.SubNodeList)
1534
+ params = self.match(list)
1595
1535
  if self.match_token(Tok.RETURN_HINT):
1596
1536
  return_type = self.consume(uni.Expr)
1597
1537
  self.consume_token(Tok.COLON)
1598
1538
  body = self.consume(uni.Expr)
1599
1539
  if params:
1600
- sig_kid.append(params)
1540
+ sig_kid.extend(params)
1601
1541
  if return_type:
1602
1542
  sig_kid.append(return_type)
1603
1543
  signature = (
1604
1544
  uni.FuncSignature(
1605
- params=params,
1545
+ params=(
1546
+ self.extract_from_list(params, uni.ParamVar) if params else []
1547
+ ),
1606
1548
  return_type=return_type,
1607
1549
  kid=sig_kid,
1608
1550
  )
@@ -1807,10 +1749,10 @@ class JacParser(Transform[uni.Source, uni.Module]):
1807
1749
  """
1808
1750
  return self._binary_expr_unwind(self.cur_nodes)
1809
1751
 
1810
- def ds_spawn(self, _: None) -> uni.Expr:
1752
+ def os_spawn(self, _: None) -> uni.Expr:
1811
1753
  """Grammar rule.
1812
1754
 
1813
- ds_spawn: (ds_spawn KW_SPAWN)? unpack
1755
+ os_spawn: (os_spawn KW_SPAWN)? unpack
1814
1756
  """
1815
1757
  return self._binary_expr_unwind(self.cur_nodes)
1816
1758
 
@@ -1920,22 +1862,38 @@ class JacParser(Transform[uni.Source, uni.Module]):
1920
1862
  def atomic_call(self, _: None) -> uni.FuncCall:
1921
1863
  """Grammar rule.
1922
1864
 
1923
- atomic_call: atomic_chain LPAREN param_list? (KW_BY atomic_call)? RPAREN
1865
+ atomic_call: atomic_chain LPAREN param_list? by_call? RPAREN by_call?
1924
1866
  """
1925
1867
  genai_call: uni.FuncCall | None = None
1926
1868
  target = self.consume(uni.Expr)
1927
1869
  self.consume_token(Tok.LPAREN)
1928
- params = self.match(uni.SubNodeList)
1929
- if self.match_token(Tok.KW_BY):
1930
- genai_call = self.consume(uni.FuncCall)
1870
+ params_sn = self.match(list)
1871
+ genai_call = self.match(uni.FuncCall)
1931
1872
  self.consume_token(Tok.RPAREN)
1873
+ body_genai_call = self.match(uni.FuncCall)
1874
+
1932
1875
  return uni.FuncCall(
1933
1876
  target=target,
1934
- params=params,
1877
+ params=(
1878
+ self.extract_from_list(params_sn, (uni.Expr, uni.KWPair)) # type: ignore[arg-type]
1879
+ if params_sn
1880
+ else []
1881
+ ),
1935
1882
  genai_call=genai_call,
1936
- kid=self.cur_nodes,
1883
+ body_genai_call=body_genai_call,
1884
+ kid=self.flat_cur_nodes,
1937
1885
  )
1938
1886
 
1887
+ def by_call(self, _: None) -> uni.FuncCall:
1888
+ """Grammar rule.
1889
+
1890
+ by_call: KW_BY expression
1891
+ """
1892
+ self.consume_token(Tok.KW_BY)
1893
+ if call := self.match(uni.FuncCall):
1894
+ return call
1895
+ raise ValueError("Expected a function call")
1896
+
1939
1897
  def index_slice(self, _: None) -> uni.IndexSlice:
1940
1898
  """Grammar rule.
1941
1899
 
@@ -1947,16 +1905,11 @@ class JacParser(Transform[uni.Source, uni.Module]):
1947
1905
  """
1948
1906
  if len(self.cur_nodes) == 1:
1949
1907
  index = self.consume(uni.ListVal)
1950
- if not index.values:
1951
- raise self.ice()
1952
- if len(index.values.items) == 1:
1953
- expr = index.values.items[0] if index.values else None
1908
+ if len(index.values) == 1:
1909
+ expr = index.values[0]
1954
1910
  kid = self.cur_nodes
1955
1911
  else:
1956
- sublist = uni.SubNodeList[uni.Expr | uni.KWPair](
1957
- items=[*index.values.items], delim=Tok.COMMA, kid=index.kid
1958
- )
1959
- expr = uni.TupleVal(values=sublist, kid=[sublist])
1912
+ expr = uni.TupleVal(values=index.values, kid=index.kid)
1960
1913
  kid = [expr]
1961
1914
  return uni.IndexSlice(
1962
1915
  slices=[uni.IndexSlice.Slice(start=expr, stop=None, step=None)],
@@ -2062,52 +2015,54 @@ class JacParser(Transform[uni.Source, uni.Module]):
2062
2015
  | FSTR_SQ_START fstr_sq_parts FSTR_SQ_END
2063
2016
  """
2064
2017
  self.match_token(Tok.FSTR_START) or self.consume_token(Tok.FSTR_SQ_START)
2065
- target = self.match(uni.SubNodeList)
2018
+ target = self.match(list)
2066
2019
  self.match_token(Tok.FSTR_END) or self.consume_token(Tok.FSTR_SQ_END)
2067
2020
  return uni.FString(
2068
- parts=target,
2069
- kid=self.cur_nodes,
2021
+ parts=(
2022
+ self.extract_from_list(target, (uni.String, uni.ExprStmt))
2023
+ if target
2024
+ else []
2025
+ ),
2026
+ kid=self.flat_cur_nodes,
2070
2027
  )
2071
2028
 
2072
- def fstr_parts(self, _: None) -> uni.SubNodeList[uni.String | uni.ExprStmt]:
2029
+ def fstr_parts(self, _: None) -> list[uni.UniNode]:
2073
2030
  """Grammar rule.
2074
2031
 
2075
2032
  fstr_parts: (FSTR_PIECE | FSTR_BESC | LBRACE expression RBRACE )*
2076
2033
  """
2077
- valid_parts: list[uni.String | uni.ExprStmt] = [
2034
+ valid_parts: list[uni.UniNode] = [
2078
2035
  (
2079
2036
  i
2080
2037
  if isinstance(i, uni.String)
2081
- else uni.ExprStmt(expr=i, in_fstring=True, kid=[i])
2038
+ else (
2039
+ uni.ExprStmt(expr=i, in_fstring=True, kid=[i])
2040
+ if isinstance(i, uni.Expr)
2041
+ else i
2042
+ )
2082
2043
  )
2083
2044
  for i in self.cur_nodes
2084
- if isinstance(i, uni.Expr)
2085
2045
  ]
2086
- return uni.SubNodeList[uni.String | uni.ExprStmt](
2087
- items=valid_parts,
2088
- delim=None,
2089
- kid=valid_parts,
2090
- )
2046
+ return valid_parts
2091
2047
 
2092
- def fstr_sq_parts(self, _: None) -> uni.SubNodeList[uni.String | uni.ExprStmt]:
2048
+ def fstr_sq_parts(self, _: None) -> list[uni.UniNode]:
2093
2049
  """Grammar rule.
2094
2050
 
2095
2051
  fstr_sq_parts: (FSTR_SQ_PIECE | FSTR_BESC | LBRACE expression RBRACE )*
2096
2052
  """
2097
- valid_parts: list[uni.String | uni.ExprStmt] = [
2053
+ valid_parts: list[uni.UniNode] = [
2098
2054
  (
2099
2055
  i
2100
2056
  if isinstance(i, uni.String)
2101
- else uni.ExprStmt(expr=i, in_fstring=True, kid=[i])
2057
+ else (
2058
+ uni.ExprStmt(expr=i, in_fstring=True, kid=[i])
2059
+ if isinstance(i, uni.Expr)
2060
+ else i
2061
+ )
2102
2062
  )
2103
2063
  for i in self.cur_nodes
2104
- if isinstance(i, uni.Expr)
2105
2064
  ]
2106
- return uni.SubNodeList[uni.String | uni.ExprStmt](
2107
- items=valid_parts,
2108
- delim=None,
2109
- kid=valid_parts,
2110
- )
2065
+ return valid_parts
2111
2066
 
2112
2067
  def list_val(self, _: None) -> uni.ListVal:
2113
2068
  """Grammar rule.
@@ -2115,12 +2070,15 @@ class JacParser(Transform[uni.Source, uni.Module]):
2115
2070
  list_val: LSQUARE (expr_list COMMA?)? RSQUARE
2116
2071
  """
2117
2072
  self.consume_token(Tok.LSQUARE)
2118
- values = self.match(uni.SubNodeList)
2073
+ values_node = self.match(list)
2119
2074
  self.match_token(Tok.COMMA)
2120
2075
  self.consume_token(Tok.RSQUARE)
2076
+ values = (
2077
+ self.extract_from_list(values_node, uni.Expr) if values_node else []
2078
+ )
2121
2079
  return uni.ListVal(
2122
2080
  values=values,
2123
- kid=self.cur_nodes,
2081
+ kid=self.flat_cur_nodes,
2124
2082
  )
2125
2083
 
2126
2084
  def tuple_val(self, _: None) -> uni.TupleVal:
@@ -2129,11 +2087,15 @@ class JacParser(Transform[uni.Source, uni.Module]):
2129
2087
  tuple_val: LPAREN tuple_list? RPAREN
2130
2088
  """
2131
2089
  self.consume_token(Tok.LPAREN)
2132
- target = self.match(uni.SubNodeList)
2090
+ target = self.match(list)
2133
2091
  self.consume_token(Tok.RPAREN)
2134
2092
  return uni.TupleVal(
2135
- values=target,
2136
- kid=self.cur_nodes,
2093
+ values=(
2094
+ self.extract_from_list(target, (uni.Expr, uni.KWPair))
2095
+ if target
2096
+ else []
2097
+ ),
2098
+ kid=self.flat_cur_nodes,
2137
2099
  )
2138
2100
 
2139
2101
  def set_val(self, _: None) -> uni.SetVal:
@@ -2142,50 +2104,28 @@ class JacParser(Transform[uni.Source, uni.Module]):
2142
2104
  set_val: LBRACE expr_list COMMA? RBRACE
2143
2105
  """
2144
2106
  self.match_token(Tok.LBRACE)
2145
- expr_list = self.match(uni.SubNodeList)
2107
+ expr_list = self.match(list)
2146
2108
  self.match_token(Tok.COMMA)
2147
2109
  self.match_token(Tok.RBRACE)
2110
+ values = self.extract_from_list(expr_list, uni.Expr) if expr_list else []
2148
2111
  return uni.SetVal(
2149
- values=expr_list,
2150
- kid=self.cur_nodes,
2112
+ values=values,
2113
+ kid=self.flat_cur_nodes,
2151
2114
  )
2152
2115
 
2153
- def expr_list(self, kid: list[uni.UniNode]) -> uni.SubNodeList[uni.Expr]:
2116
+ def expr_list(self, kid: list[uni.UniNode]) -> list[uni.UniNode]:
2154
2117
  """Grammar rule.
2155
2118
 
2156
2119
  expr_list: (expr_list COMMA)? expression
2157
2120
  """
2158
- new_kid: list = []
2159
- if consume := self.match(uni.SubNodeList):
2160
- comma = self.consume_token(Tok.COMMA)
2161
- new_kid.extend([*consume.kid, comma])
2162
- expr = self.consume(uni.Expr)
2163
- new_kid.extend([expr])
2164
- valid_kid = [i for i in new_kid if isinstance(i, uni.Expr)]
2165
- return uni.SubNodeList[uni.Expr](
2166
- items=valid_kid,
2167
- delim=Tok.COMMA,
2168
- kid=new_kid,
2169
- )
2121
+ return self.flat_cur_nodes
2170
2122
 
2171
- def kw_expr_list(self, kid: list[uni.UniNode]) -> uni.SubNodeList[uni.KWPair]:
2123
+ def kw_expr_list(self, kid: list[uni.UniNode]) -> list[uni.UniNode]:
2172
2124
  """Grammar rule.
2173
2125
 
2174
2126
  kw_expr_list: (kw_expr_list COMMA)? kw_expr
2175
2127
  """
2176
- if consume := self.match(uni.SubNodeList):
2177
- comma = self.consume_token(Tok.COMMA)
2178
- expr = self.consume(uni.KWPair)
2179
- new_kid = [*consume.kid, comma, expr]
2180
- else:
2181
- expr = self.consume(uni.KWPair)
2182
- new_kid = [expr]
2183
- valid_kid = [i for i in new_kid if isinstance(i, uni.KWPair)]
2184
- return uni.SubNodeList[uni.KWPair](
2185
- items=valid_kid,
2186
- delim=Tok.COMMA,
2187
- kid=new_kid,
2188
- )
2128
+ return self.flat_cur_nodes
2189
2129
 
2190
2130
  def kw_expr(self, _: None) -> uni.KWPair:
2191
2131
  """Grammar rule.
@@ -2205,21 +2145,14 @@ class JacParser(Transform[uni.Source, uni.Module]):
2205
2145
  kid=self.cur_nodes,
2206
2146
  )
2207
2147
 
2208
- def name_list(self, _: None) -> uni.SubNodeList[uni.Name]:
2148
+ def name_list(self, _: None) -> list[uni.UniNode]:
2209
2149
  """Grammar rule.
2210
2150
 
2211
2151
  name_list: (named_ref COMMA)* named_ref
2212
2152
  """
2213
- valid_kid = [self.consume(uni.Name)]
2214
- while self.match_token(Tok.COMMA):
2215
- valid_kid.append(self.consume(uni.Name))
2216
- return uni.SubNodeList[uni.Name](
2217
- items=valid_kid,
2218
- delim=Tok.COMMA,
2219
- kid=self.cur_nodes,
2220
- )
2153
+ return self.flat_cur_nodes
2221
2154
 
2222
- def tuple_list(self, _: None) -> uni.SubNodeList[uni.Expr | uni.KWPair]:
2155
+ def tuple_list(self, _: None) -> list[uni.UniNode]:
2223
2156
  """Grammar rule.
2224
2157
 
2225
2158
  tuple_list: expression COMMA expr_list COMMA kw_expr_list COMMA?
@@ -2228,29 +2161,7 @@ class JacParser(Transform[uni.Source, uni.Module]):
2228
2161
  | expression COMMA
2229
2162
  | kw_expr_list COMMA?
2230
2163
  """
2231
- if first_expr := self.match(uni.SubNodeList):
2232
- comma = self.match_token(Tok.COMMA)
2233
- if comma:
2234
- first_expr.kid.append(comma)
2235
- return first_expr
2236
- expr = self.consume(uni.Expr)
2237
- self.consume_token(Tok.COMMA)
2238
- second_expr = self.match(uni.SubNodeList)
2239
- self.match_token(Tok.COMMA)
2240
- kw_expr_list = self.match(uni.SubNodeList)
2241
- self.match_token(Tok.COMMA)
2242
- expr_list: list = []
2243
- if second_expr:
2244
- expr_list = second_expr.kid
2245
- if kw_expr_list:
2246
- expr_list = [*expr_list, *kw_expr_list.kid]
2247
- expr_list = [expr, *expr_list]
2248
- valid_kid = [i for i in expr_list if isinstance(i, (uni.Expr, uni.KWPair))]
2249
- return uni.SubNodeList[uni.Expr | uni.KWPair](
2250
- items=valid_kid,
2251
- delim=Tok.COMMA,
2252
- kid=self.cur_nodes,
2253
- )
2164
+ return self.flat_cur_nodes
2254
2165
 
2255
2166
  def dict_val(self, _: None) -> uni.DictVal:
2256
2167
  """Grammar rule.
@@ -2370,67 +2281,36 @@ class JacParser(Transform[uni.Source, uni.Module]):
2370
2281
  kid=self.cur_nodes,
2371
2282
  )
2372
2283
 
2373
- def param_list(self, _: None) -> uni.SubNodeList[uni.Expr | uni.KWPair]:
2284
+ def param_list(self, _: None) -> list[uni.UniNode]:
2374
2285
  """Grammar rule.
2375
2286
 
2376
2287
  param_list: expr_list COMMA kw_expr_list COMMA?
2377
2288
  | kw_expr_list COMMA?
2378
2289
  | expr_list COMMA?
2379
2290
  """
2380
- kw_expr_list: uni.SubNodeList | None = None
2381
- expr_list = self.consume(uni.SubNodeList)
2382
- if len(self.cur_nodes) > 2:
2383
- self.consume_token(Tok.COMMA)
2384
- kw_expr_list = self.consume(uni.SubNodeList)
2385
- ends_comma = self.match_token(Tok.COMMA)
2386
- if kw_expr_list:
2387
- valid_kid = [
2388
- i
2389
- for i in [*expr_list.items, *kw_expr_list.items]
2390
- if isinstance(i, (uni.Expr, uni.KWPair))
2391
- ]
2392
- return uni.SubNodeList[uni.Expr | uni.KWPair](
2393
- items=valid_kid,
2394
- delim=Tok.COMMA,
2395
- kid=self.cur_nodes,
2396
- )
2397
- else:
2398
- if ends_comma:
2399
- expr_list.kid.append(ends_comma)
2400
- return expr_list
2291
+ return self.flat_cur_nodes
2401
2292
 
2402
- def assignment_list(self, _: None) -> uni.SubNodeList[uni.Assignment]:
2293
+ def assignment_list(self, _: None) -> list[uni.UniNode]:
2403
2294
  """Grammar rule.
2404
2295
 
2405
2296
  assignment_list: (assignment_list COMMA)? (assignment | NAME)
2406
2297
  """
2407
2298
 
2408
2299
  def name_to_assign(name_consume: uni.NameAtom) -> uni.Assignment:
2409
- target = uni.SubNodeList[uni.Expr](
2410
- items=[name_consume], delim=Tok.EQ, kid=[name_consume]
2411
- )
2412
2300
  return uni.Assignment(
2413
- target=target, value=None, type_tag=None, kid=[target]
2301
+ target=[name_consume], value=None, type_tag=None, kid=[name_consume]
2414
2302
  )
2415
2303
 
2416
- if consume := self.match(uni.SubNodeList):
2417
- comma = self.consume_token(Tok.COMMA)
2418
- assign = self.match(uni.Assignment) or self.consume(uni.NameAtom)
2419
- if isinstance(assign, uni.NameAtom):
2420
- assign = name_to_assign(assign)
2421
- new_kid = [*consume.kid, comma, assign]
2304
+ if self.match(list):
2305
+ self.consume_token(Tok.COMMA)
2306
+ if self.match(uni.Assignment):
2307
+ pass
2422
2308
  elif name_consume := self.match(uni.NameAtom):
2423
- name_assign = name_to_assign(name_consume)
2424
- new_kid = [name_assign]
2309
+ self.cur_nodes[self.node_idx - 1] = name_to_assign(name_consume)
2425
2310
  else:
2426
2311
  assign = self.consume(uni.Assignment)
2427
- new_kid = [assign]
2428
- valid_kid = [i for i in new_kid if isinstance(i, uni.Assignment)]
2429
- return uni.SubNodeList[uni.Assignment](
2430
- items=valid_kid,
2431
- delim=Tok.COMMA,
2432
- kid=new_kid,
2433
- )
2312
+ self.cur_nodes[self.node_idx - 1] = assign
2313
+ return self.flat_cur_nodes
2434
2314
 
2435
2315
  def type_ref(self, kid: list[uni.UniNode]) -> uni.TypeRef:
2436
2316
  """Grammar rule.
@@ -2542,19 +2422,20 @@ class JacParser(Transform[uni.Source, uni.Module]):
2542
2422
  connect_to: CARROW_R | CARROW_R_P1 expression (COLON kw_expr_list)? CARROW_R_P2
2543
2423
  """
2544
2424
  conn_type: uni.Expr | None = None
2545
- conn_assign_sub: uni.SubNodeList | None = None
2425
+ conn_assign_sub: list[uni.UniNode] | None = None
2546
2426
  if self.match_token(Tok.CARROW_R_P1):
2547
2427
  conn_type = self.consume(uni.Expr)
2548
2428
  conn_assign_sub = (
2549
- self.consume(uni.SubNodeList)
2550
- if self.match_token(Tok.COLON)
2551
- else None
2429
+ self.consume(list) if self.match_token(Tok.COLON) else None
2552
2430
  )
2553
2431
  self.consume_token(Tok.CARROW_R_P2)
2554
2432
  else:
2555
2433
  self.consume_token(Tok.CARROW_R)
2556
2434
  conn_assign = (
2557
- uni.AssignCompr(assigns=conn_assign_sub, kid=[conn_assign_sub])
2435
+ uni.AssignCompr(
2436
+ assigns=self.extract_from_list(conn_assign_sub, uni.KWPair),
2437
+ kid=conn_assign_sub,
2438
+ )
2558
2439
  if conn_assign_sub
2559
2440
  else None
2560
2441
  )
@@ -2564,7 +2445,7 @@ class JacParser(Transform[uni.Source, uni.Module]):
2564
2445
  conn_type=conn_type,
2565
2446
  conn_assign=conn_assign,
2566
2447
  edge_dir=EdgeDir.OUT,
2567
- kid=self.cur_nodes,
2448
+ kid=self.flat_cur_nodes,
2568
2449
  )
2569
2450
 
2570
2451
  def connect_from(self, _: None) -> uni.ConnectOp:
@@ -2573,19 +2454,20 @@ class JacParser(Transform[uni.Source, uni.Module]):
2573
2454
  connect_from: CARROW_L | CARROW_L_P1 expression (COLON kw_expr_list)? CARROW_L_P2
2574
2455
  """
2575
2456
  conn_type: uni.Expr | None = None
2576
- conn_assign_sub: uni.SubNodeList | None = None
2457
+ conn_assign_sub: list[uni.UniNode] | None = None
2577
2458
  if self.match_token(Tok.CARROW_L_P1):
2578
2459
  conn_type = self.consume(uni.Expr)
2579
2460
  conn_assign_sub = (
2580
- self.consume(uni.SubNodeList)
2581
- if self.match_token(Tok.COLON)
2582
- else None
2461
+ self.consume(list) if self.match_token(Tok.COLON) else None
2583
2462
  )
2584
2463
  self.consume_token(Tok.CARROW_L_P2)
2585
2464
  else:
2586
2465
  self.consume_token(Tok.CARROW_L)
2587
2466
  conn_assign = (
2588
- uni.AssignCompr(assigns=conn_assign_sub, kid=[conn_assign_sub])
2467
+ uni.AssignCompr(
2468
+ assigns=self.extract_from_list(conn_assign_sub, uni.KWPair),
2469
+ kid=conn_assign_sub,
2470
+ )
2589
2471
  if conn_assign_sub
2590
2472
  else None
2591
2473
  )
@@ -2595,7 +2477,7 @@ class JacParser(Transform[uni.Source, uni.Module]):
2595
2477
  conn_type=conn_type,
2596
2478
  conn_assign=conn_assign,
2597
2479
  edge_dir=EdgeDir.IN,
2598
- kid=self.cur_nodes,
2480
+ kid=self.flat_cur_nodes,
2599
2481
  )
2600
2482
 
2601
2483
  def connect_any(self, _: None) -> uni.ConnectOp:
@@ -2604,19 +2486,20 @@ class JacParser(Transform[uni.Source, uni.Module]):
2604
2486
  connect_any: CARROW_BI | CARROW_L_P1 expression (COLON kw_expr_list)? CARROW_R_P2
2605
2487
  """
2606
2488
  conn_type: uni.Expr | None = None
2607
- conn_assign_sub: uni.SubNodeList | None = None
2489
+ conn_assign_sub: list[uni.UniNode] | None = None
2608
2490
  if self.match_token(Tok.CARROW_L_P1):
2609
2491
  conn_type = self.consume(uni.Expr)
2610
2492
  conn_assign_sub = (
2611
- self.consume(uni.SubNodeList)
2612
- if self.match_token(Tok.COLON)
2613
- else None
2493
+ self.consume(list) if self.match_token(Tok.COLON) else None
2614
2494
  )
2615
2495
  self.consume_token(Tok.CARROW_R_P2)
2616
2496
  else:
2617
2497
  self.consume_token(Tok.CARROW_BI)
2618
2498
  conn_assign = (
2619
- uni.AssignCompr(assigns=conn_assign_sub, kid=[conn_assign_sub])
2499
+ uni.AssignCompr(
2500
+ assigns=self.extract_from_list(conn_assign_sub, uni.KWPair),
2501
+ kid=conn_assign_sub,
2502
+ )
2620
2503
  if conn_assign_sub
2621
2504
  else None
2622
2505
  )
@@ -2626,7 +2509,7 @@ class JacParser(Transform[uni.Source, uni.Module]):
2626
2509
  conn_type=conn_type,
2627
2510
  conn_assign=conn_assign,
2628
2511
  edge_dir=EdgeDir.ANY,
2629
- kid=self.cur_nodes,
2512
+ kid=self.flat_cur_nodes,
2630
2513
  )
2631
2514
 
2632
2515
  def filter_compr(self, _: None) -> uni.FilterCompr:
@@ -2645,43 +2528,35 @@ class JacParser(Transform[uni.Source, uni.Module]):
2645
2528
  self.consume_token(Tok.RPAREN)
2646
2529
  return f_type
2647
2530
  self.consume_token(Tok.NULL_OK)
2648
- compares = self.consume(uni.SubNodeList)
2531
+ compares_list = self.consume(list)
2649
2532
  self.consume_token(Tok.RPAREN)
2650
2533
  return uni.FilterCompr(
2651
- compares=compares,
2534
+ compares=self.extract_from_list(compares_list, uni.CompareExpr),
2652
2535
  f_type=None,
2653
- kid=self.cur_nodes,
2536
+ kid=self.flat_cur_nodes,
2654
2537
  )
2655
2538
 
2656
- def filter_compare_list(self, _: None) -> uni.SubNodeList[uni.CompareExpr]:
2539
+ def filter_compare_list(self, _: None) -> list[uni.UniNode]:
2657
2540
  """Grammar rule.
2658
2541
 
2659
2542
  filter_compare_list: (filter_compare_list COMMA)? filter_compare_item
2660
2543
  """
2661
- if consume := self.match(uni.SubNodeList):
2662
- comma = self.consume_token(Tok.COMMA)
2663
- expr = self.consume(uni.CompareExpr)
2664
- new_kid = [*consume.kid, comma, expr]
2665
- else:
2666
- expr = self.consume(uni.CompareExpr)
2667
- new_kid = [expr]
2668
- valid_kid = [i for i in new_kid if isinstance(i, uni.CompareExpr)]
2669
- return uni.SubNodeList[uni.CompareExpr](
2670
- items=valid_kid,
2671
- delim=Tok.COMMA,
2672
- kid=new_kid,
2673
- )
2544
+ return self.flat_cur_nodes
2674
2545
 
2675
2546
  def typed_filter_compare_list(self, _: None) -> uni.FilterCompr:
2676
2547
  """Grammar rule.
2677
2548
 
2678
2549
  typed_filter_compare_list: expression (COLON filter_compare_list)?
2679
2550
  """
2680
- compares: uni.SubNodeList | None = None
2551
+ compares_list: list[uni.UniNode] | None = None
2681
2552
  expr = self.consume(uni.Expr)
2682
2553
  if self.match_token(Tok.COLON):
2683
- compares = self.consume(uni.SubNodeList)
2684
- return uni.FilterCompr(compares=compares, f_type=expr, kid=self.cur_nodes)
2554
+ compares_list = self.consume(list)
2555
+ return uni.FilterCompr(
2556
+ compares=self.extract_from_list(compares_list or [], uni.CompareExpr),
2557
+ f_type=expr,
2558
+ kid=self.flat_cur_nodes,
2559
+ )
2685
2560
 
2686
2561
  def filter_compare_item(self, _: None) -> uni.CompareExpr:
2687
2562
  """Grammar rule.
@@ -2702,9 +2577,9 @@ class JacParser(Transform[uni.Source, uni.Module]):
2702
2577
  """
2703
2578
  self.consume_token(Tok.LPAREN)
2704
2579
  self.consume_token(Tok.EQ)
2705
- assigns = self.consume(uni.SubNodeList)
2580
+ assigns_sn = self.extract_from_list(self.consume(list), uni.KWPair)
2706
2581
  self.consume_token(Tok.RPAREN)
2707
- return uni.AssignCompr(assigns=assigns, kid=self.cur_nodes)
2582
+ return uni.AssignCompr(assigns=assigns_sn, kid=self.flat_cur_nodes)
2708
2583
 
2709
2584
  def match_stmt(self, _: None) -> uni.MatchStmt:
2710
2585
  """Grammar rule.
@@ -2900,11 +2775,14 @@ class JacParser(Transform[uni.Source, uni.Module]):
2900
2775
  class_pattern: NAME (DOT NAME)* LPAREN kw_pattern_list? RPAREN
2901
2776
  | NAME (DOT NAME)* LPAREN pattern_list (COMMA kw_pattern_list)? RPAREN
2902
2777
  """
2778
+ name_idx = 0
2903
2779
  cur_element = self.consume(uni.NameAtom)
2780
+ name_idx += 1
2904
2781
  trailer: uni.AtomTrailer | None = None
2905
2782
  while dot := self.match_token(Tok.DOT):
2906
2783
  target = trailer if trailer else cur_element
2907
- right = self.consume(uni.Expr)
2784
+ right = self.consume(uni.NameAtom)
2785
+ name_idx += 2
2908
2786
  trailer = uni.AtomTrailer(
2909
2787
  target=target,
2910
2788
  right=right,
@@ -2917,82 +2795,55 @@ class JacParser(Transform[uni.Source, uni.Module]):
2917
2795
  raise TypeError(
2918
2796
  f"Expected name to be either NameAtom or AtomTrailer, got {type(name)}"
2919
2797
  )
2920
- lparen = self.consume_token(Tok.LPAREN)
2921
- first = self.match(uni.SubNodeList)
2922
- second = (
2923
- self.consume(uni.SubNodeList)
2924
- if (comma := self.match_token(Tok.COMMA))
2925
- else None
2926
- )
2927
- rparen = self.consume_token(Tok.RPAREN)
2928
- arg = (
2929
- first
2930
- if (first and isinstance(first.items[0], uni.MatchPattern))
2931
- else None
2932
- )
2933
- kw = (
2934
- second
2935
- if (second and isinstance(second.items[0], uni.MatchKVPair))
2936
- else (
2937
- first
2938
- if (first and isinstance(first.items[0], uni.MatchKVPair))
2939
- else None
2940
- )
2941
- )
2942
- kid_nodes: list = [name, lparen]
2943
- if arg:
2944
- kid_nodes.append(arg)
2945
- if kw:
2946
- kid_nodes.extend([comma, kw]) if comma else kid_nodes.append(kw)
2947
- elif kw:
2948
- kid_nodes.append(kw)
2949
- kid_nodes.append(rparen)
2798
+ self.consume_token(Tok.LPAREN)
2799
+ first = self.match(list)
2800
+ second: list[uni.UniNode] | None = None
2801
+ has_kw = bool(first and any(isinstance(i, uni.MatchKVPair) for i in first))
2802
+ if first and not has_kw and self.match_token(Tok.COMMA):
2803
+ second = self.consume(list)
2804
+ self.consume_token(Tok.RPAREN)
2805
+ if has_kw:
2806
+ arg = None
2807
+ kw_list = first
2808
+ else:
2809
+ arg = first
2810
+ kw_list = second
2950
2811
  return uni.MatchArch(
2951
2812
  name=name,
2952
- arg_patterns=arg,
2953
- kw_patterns=kw,
2954
- kid=kid_nodes,
2813
+ arg_patterns=(
2814
+ self.extract_from_list(arg, uni.MatchPattern) if arg else None
2815
+ ),
2816
+ kw_patterns=(
2817
+ self.extract_from_list(kw_list, uni.MatchKVPair)
2818
+ if kw_list
2819
+ else None
2820
+ ),
2821
+ kid=[name, *self.flat_cur_nodes[name_idx:]],
2955
2822
  )
2956
2823
 
2957
- def pattern_list(self, _: None) -> uni.SubNodeList[uni.MatchPattern]:
2824
+ def pattern_list(self, _: None) -> list[uni.UniNode]:
2958
2825
  """Grammar rule.
2959
2826
 
2960
2827
  pattern_list: (pattern_list COMMA)? pattern_seq
2961
2828
  """
2962
- if consume := self.match(uni.SubNodeList):
2963
- comma = self.consume_token(Tok.COMMA)
2964
- pattern = self.consume(uni.MatchPattern)
2965
- else:
2966
- pattern = self.consume(uni.MatchPattern)
2967
- new_kid = [*consume.kid, comma, pattern] if consume else [pattern]
2968
- valid_kid = [i for i in new_kid if isinstance(i, uni.MatchPattern)]
2969
- return uni.SubNodeList[uni.MatchPattern](
2970
- items=valid_kid,
2971
- delim=Tok.COMMA,
2972
- kid=new_kid,
2973
- )
2829
+ return self.flat_cur_nodes
2974
2830
 
2975
- def kw_pattern_list(self, _: None) -> uni.SubNodeList[uni.MatchKVPair]:
2831
+ def kw_pattern_list(self, _: None) -> list[uni.UniNode]:
2976
2832
  """Grammar rule.
2977
2833
 
2978
2834
  kw_pattern_list: (kw_pattern_list COMMA)? named_ref EQ pattern_seq
2979
2835
  """
2980
- new_kid: list = []
2981
- if consume := self.match(uni.SubNodeList):
2836
+ new_kid: list[uni.UniNode] = []
2837
+ if consume := self.match(list):
2982
2838
  comma = self.consume_token(Tok.COMMA)
2983
- new_kid.extend([*consume.kid, comma])
2839
+ new_kid.extend([*consume, comma])
2984
2840
  name = self.consume(uni.NameAtom)
2985
2841
  eq = self.consume_token(Tok.EQ)
2986
2842
  value = self.consume(uni.MatchPattern)
2987
- new_kid.extend(
2988
- [uni.MatchKVPair(key=name, value=value, kid=[name, eq, value])]
2989
- )
2990
- valid_kid = [i for i in new_kid if isinstance(i, uni.MatchKVPair)]
2991
- return uni.SubNodeList[uni.MatchKVPair](
2992
- items=valid_kid,
2993
- delim=Tok.COMMA,
2994
- kid=new_kid,
2843
+ new_kid.append(
2844
+ uni.MatchKVPair(key=name, value=value, kid=[name, eq, value])
2995
2845
  )
2846
+ return new_kid
2996
2847
 
2997
2848
  def __default_token__(self, token: jl.Token) -> uni.Token:
2998
2849
  """Token handler."""
@@ -3057,28 +2908,24 @@ class JacParser(Transform[uni.Source, uni.Module]):
3057
2908
  def event_clause(self, _: None) -> uni.EventSignature:
3058
2909
  """Grammar rule.
3059
2910
 
3060
- event_clause: KW_WITH expression? (KW_EXIT | KW_ENTRY) (RETURN_HINT expression)?
2911
+ event_clause: KW_WITH expression? (KW_EXIT | KW_ENTRY)
3061
2912
  """
3062
- return_spec: uni.Expr | None = None
3063
2913
  self.consume_token(Tok.KW_WITH)
3064
2914
  type_specs = self.match(uni.Expr)
3065
2915
  event = self.match_token(Tok.KW_EXIT) or self.consume_token(Tok.KW_ENTRY)
3066
- if self.match_token(Tok.RETURN_HINT):
3067
- return_spec = self.consume(uni.Expr)
3068
2916
  return uni.EventSignature(
3069
2917
  event=event,
3070
2918
  arch_tag_info=type_specs,
3071
- return_type=return_spec,
3072
2919
  kid=self.cur_nodes,
3073
2920
  )
3074
2921
 
3075
- def block_tail(self, _: None) -> uni.SubNodeList | uni.FuncCall:
2922
+ def block_tail(self, _: None) -> list[uni.CodeBlockStmt] | uni.FuncCall:
3076
2923
  """Grammar rule.
3077
2924
 
3078
2925
  block_tail: code_block | KW_BY atomic_call SEMI | KW_ABSTRACT? SEMI
3079
2926
  """
3080
2927
  # Try to match code_block first
3081
- if code_block := self.match(uni.SubNodeList):
2928
+ if code_block := self.match(list):
3082
2929
  return code_block
3083
2930
 
3084
2931
  # Otherwise, it must be KW_BY atomic_call SEMI