rapydscript-ng 0.7.24 → 0.8.0

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 (114) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/README.md +72 -26
  3. package/bin/package.json +1 -0
  4. package/bin/rapydscript +65 -62
  5. package/editor-plugins/nvim/rapydscript/README.md +184 -0
  6. package/editor-plugins/nvim/rapydscript/bin/rapydscript.so +0 -0
  7. package/editor-plugins/nvim/rapydscript/ftdetect/rapydscript.lua +10 -0
  8. package/editor-plugins/nvim/rapydscript/lua/rapydscript/health.lua +88 -0
  9. package/editor-plugins/nvim/rapydscript/lua/rapydscript/init.lua +279 -0
  10. package/local-agent.md +28 -0
  11. package/package.json +4 -5
  12. package/release/baselib-plain-pretty.js +448 -326
  13. package/release/baselib-plain-ugly.js +3 -3
  14. package/release/compiler.js +2528 -1672
  15. package/release/signatures.json +17 -17
  16. package/src/ast.pyj +73 -25
  17. package/src/baselib-builtins.pyj +2 -2
  18. package/src/baselib-containers.pyj +153 -151
  19. package/src/baselib-internal.pyj +3 -3
  20. package/src/baselib-str.pyj +5 -5
  21. package/src/lib/aes.pyj +1 -1
  22. package/src/lib/encodings.pyj +1 -1
  23. package/src/lib/pythonize.pyj +1 -1
  24. package/src/lib/re.pyj +2 -2
  25. package/src/lib/traceback.pyj +165 -28
  26. package/src/lib/uuid.pyj +1 -1
  27. package/src/output/codegen.pyj +10 -3
  28. package/src/output/functions.pyj +31 -40
  29. package/src/output/literals.pyj +11 -14
  30. package/src/output/loops.pyj +25 -87
  31. package/src/output/modules.pyj +5 -47
  32. package/src/output/statements.pyj +1 -1
  33. package/src/output/stream.pyj +58 -31
  34. package/src/parse.pyj +777 -427
  35. package/src/tokenizer.pyj +16 -4
  36. package/src/utils.pyj +1 -1
  37. package/test/_treeshake_mod.pyj +30 -0
  38. package/test/_treeshake_side_effect.pyj +9 -0
  39. package/test/ast_serialization.pyj +392 -0
  40. package/test/async.pyj +95 -0
  41. package/test/baselib.pyj +1 -2
  42. package/test/embedded_compiler.pyj +57 -0
  43. package/test/fmt.pyj +201 -0
  44. package/test/generic.pyj +7 -3
  45. package/test/imports.pyj +8 -6
  46. package/test/internationalization.pyj +38 -37
  47. package/test/lint.pyj +120 -117
  48. package/test/lsp.pyj +222 -0
  49. package/test/repl.pyj +70 -65
  50. package/test/sourcemaps.pyj +315 -0
  51. package/test/starargs.pyj +46 -39
  52. package/test/traceback.pyj +263 -0
  53. package/test/treeshaking.pyj +181 -0
  54. package/test/web_repl_export.pyj +88 -0
  55. package/tools/ast_serialize.mjs +557 -0
  56. package/tools/cli.mjs +510 -0
  57. package/tools/compile.mjs +201 -0
  58. package/tools/compiler.mjs +127 -0
  59. package/tools/{completer.js → completer.mjs} +6 -6
  60. package/tools/{embedded_compiler.js → embedded_compiler.mjs} +17 -13
  61. package/tools/export.js +109 -56
  62. package/tools/fmt.mjs +735 -0
  63. package/tools/{gettext.js → gettext.mjs} +46 -53
  64. package/tools/{ini.js → ini.mjs} +9 -10
  65. package/tools/{lint.js → lint.mjs} +78 -55
  66. package/tools/lsp.mjs +914 -0
  67. package/tools/lsp_protocol.mjs +259 -0
  68. package/tools/lsp_symbols.mjs +418 -0
  69. package/tools/{msgfmt.js → msgfmt.mjs} +18 -18
  70. package/tools/{repl.js → repl.mjs} +52 -41
  71. package/tools/{self.js → self.mjs} +56 -46
  72. package/tools/sourcemap.mjs +123 -0
  73. package/tools/test.mjs +152 -0
  74. package/tools/treeshake.mjs +400 -0
  75. package/tools/{utils.js → utils.mjs} +26 -23
  76. package/tools/{web_repl.js → web_repl.mjs} +15 -13
  77. package/tools/web_repl_export.mjs +93 -0
  78. package/tree-sitter/README.md +101 -0
  79. package/tree-sitter/grammar.js +992 -0
  80. package/tree-sitter/package.json +43 -0
  81. package/tree-sitter/queries/rapydscript/highlights.scm +232 -0
  82. package/tree-sitter/queries/rapydscript/indents.scm +64 -0
  83. package/tree-sitter/queries/rapydscript/injections.scm +14 -0
  84. package/tree-sitter/queries/rapydscript/locals.scm +108 -0
  85. package/tree-sitter/src/grammar.json +4976 -0
  86. package/tree-sitter/src/node-types.json +2901 -0
  87. package/tree-sitter/src/parser.c +196465 -0
  88. package/tree-sitter/src/scanner.c +294 -0
  89. package/tree-sitter/src/tree_sitter/alloc.h +54 -0
  90. package/tree-sitter/src/tree_sitter/array.h +330 -0
  91. package/tree-sitter/src/tree_sitter/parser.h +286 -0
  92. package/tree-sitter/test/corpus/chaining.txt +99 -0
  93. package/tree-sitter/test/corpus/classes.txt +147 -0
  94. package/tree-sitter/test/corpus/comprehensions.txt +155 -0
  95. package/tree-sitter/test/corpus/containers.txt +242 -0
  96. package/tree-sitter/test/corpus/control_flow.txt +361 -0
  97. package/tree-sitter/test/corpus/decorators.txt +64 -0
  98. package/tree-sitter/test/corpus/existential.txt +102 -0
  99. package/tree-sitter/test/corpus/functions.txt +293 -0
  100. package/tree-sitter/test/corpus/imports.txt +117 -0
  101. package/tree-sitter/test/corpus/literals.txt +135 -0
  102. package/tree-sitter/test/corpus/operators.txt +296 -0
  103. package/tree-sitter/test/corpus/statements.txt +189 -0
  104. package/tree-sitter/test/corpus/strings.txt +90 -0
  105. package/tree-sitter/test/corpus/subscripts.txt +227 -0
  106. package/tree-sitter/tree-sitter.json +36 -0
  107. package/web-repl/env.js +35 -23
  108. package/web-repl/main.js +8 -8
  109. package/bin/export +0 -75
  110. package/bin/web-repl-export +0 -102
  111. package/tools/cli.js +0 -523
  112. package/tools/compile.js +0 -184
  113. package/tools/compiler.js +0 -84
  114. package/tools/test.js +0 -110
package/src/parse.pyj CHANGED
@@ -1,13 +1,13 @@
1
1
  # vim:fileencoding=utf-8
2
2
  # License: BSD Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net>
3
- # globals: readfile
3
+ # globals: readfile, writefile, stat_file, sha1sum, ast_from_json, ast_to_json, make_lazy_ast_module, decode_cache, encode_cache
4
4
  from __python__ import hash_literals
5
5
 
6
6
  from utils import make_predicate, array_to_hash, defaults, has_prop, cache_file_name
7
7
  from errors import SyntaxError, ImportError
8
8
  from ast import (
9
- AST_Array, AST_Assign, AST_Binary, AST_BlockStatement, AST_Break,
10
- AST_Call, AST_Catch, AST_Class, AST_ClassCall, AST_Conditional,
9
+ AST_ArgsDef, AST_Array, AST_Assign, AST_Binary, AST_BlockStatement, AST_Break,
10
+ AST_Call, AST_CallArg, AST_CallArgs, AST_Catch, AST_Class, AST_ClassCall, AST_Conditional,
11
11
  AST_Constant, AST_Continue, AST_DWLoop, AST_Debugger, AST_Decorator,
12
12
  AST_Definitions, AST_DictComprehension, AST_Directive, AST_Do, AST_Dot,
13
13
  AST_Else, AST_EmptyStatement, AST_Except, AST_ExpressiveObject, AST_False, AST_Finally,
@@ -21,13 +21,14 @@ AST_SymbolCatch, AST_SymbolDefun, AST_SymbolFunarg,
21
21
  AST_SymbolLambda, AST_SymbolNonlocal, AST_SymbolRef, AST_SymbolVar, AST_This,
22
22
  AST_Throw, AST_Toplevel, AST_True, AST_Try, AST_UnaryPrefix,
23
23
  AST_Undefined, AST_Var, AST_VarDef, AST_Verbatim, AST_While, AST_With, AST_WithClause,
24
- AST_Yield, AST_Assert, AST_Existential, is_node_type
24
+ AST_Yield, AST_Await, AST_Assert, AST_Existential, is_node_type, TreeWalker
25
25
  )
26
26
  from tokenizer import tokenizer, is_token, RESERVED_WORDS
27
27
 
28
28
 
29
29
  COMPILER_VERSION = '__COMPILER_VERSION__'
30
30
  PYTHON_FLAGS = {'dict_literals':True, 'overload_getitem':True, 'bound_methods':True, 'hash_literals':True}
31
+ CACHE_VERSION = 1
31
32
 
32
33
 
33
34
  def get_compiler_version():
@@ -208,6 +209,41 @@ def create_parser_ctx(S, import_dirs, module_id, baselib_items, imported_module_
208
209
  token = S.token
209
210
  token_error(token, "Unexpected token: " + token.type + " «" + token.value + "»")
210
211
 
212
+ # --- Error recovery (only active when options.recover_errors is True) --------
213
+ # These helpers let the LSP obtain a usable AST from a file that contains
214
+ # syntax errors. They are never invoked on the normal compile path, so they
215
+ # cannot introduce a performance or behavior regression there.
216
+
217
+ def is_recoverable_error(e):
218
+ return v'e instanceof SyntaxError'
219
+
220
+ def record_recovery(e):
221
+ S.recovered_errors.push({
222
+ 'message': e.message, 'line': e.line, 'col': e.col, 'pos': e.pos,
223
+ 'endpos': e.endpos, 'is_eof': e.is_eof,
224
+ })
225
+
226
+ async def skip_to_recovery_point():
227
+ # Resynchronize after a syntax error: always consume at least one token
228
+ # (guaranteeing forward progress) then stop at the start of the next
229
+ # logical line, a block dedent, or eof. next() may itself raise a
230
+ # tokenizer level SyntaxError (e.g. an unterminated string on the skipped
231
+ # line); such errors are recorded and skipping continues. The tokenizer
232
+ # advances its position for every such error (and never raises for an
233
+ # unexpected character in recover mode), so this loop always terminates.
234
+ while not is_("eof"):
235
+ try:
236
+ next()
237
+ except as e:
238
+ if is_recoverable_error(e):
239
+ record_recovery(e)
240
+ if is_("eof"):
241
+ return
242
+ continue
243
+ raise
244
+ if is_("eof") or is_("punc", "}") or S.token.nlb:
245
+ return
246
+
211
247
  def expect_token(type, val):
212
248
  if is_(type, val):
213
249
  return next()
@@ -223,9 +259,9 @@ def create_parser_ctx(S, import_dirs, module_id, baselib_items, imported_module_
223
259
  S.token.nlb = True
224
260
 
225
261
  def embed_tokens(parser):
226
- def with_embedded_tokens():
262
+ async def with_embedded_tokens():
227
263
  start = S.token
228
- expr = parser()
264
+ expr = await parser()
229
265
  if expr is undefined:
230
266
  unexpected()
231
267
  end = prev()
@@ -404,7 +440,7 @@ def create_parser_ctx(S, import_dirs, module_id, baselib_items, imported_module_
404
440
 
405
441
  return vars
406
442
 
407
- def return_():
443
+ async def return_():
408
444
  if is_('punc', ';'):
409
445
  semicolon()
410
446
  value = None
@@ -413,12 +449,12 @@ def create_parser_ctx(S, import_dirs, module_id, baselib_items, imported_module_
413
449
  if is_end_of_statement:
414
450
  value = None
415
451
  else:
416
- value = expression(True)
452
+ value = await expression(True)
417
453
  semicolon()
418
454
  return value
419
455
 
420
456
  @embed_tokens
421
- def statement():
457
+ async def statement():
422
458
  # From Kovid: The next three lines were a hack to try to support statements
423
459
  # starting with a regexp literal. However, it did not work, for example:
424
460
  # echo 'f=1\n/asd/.test()' | rs -> parse error
@@ -434,7 +470,7 @@ def create_parser_ctx(S, import_dirs, module_id, baselib_items, imported_module_
434
470
  if p and not S.token.nlb and ATOMIC_START_TOKEN[p.type] and not is_('punc', ':'):
435
471
  unexpected()
436
472
  if tmp_ is "string":
437
- return simple_statement()
473
+ return await simple_statement()
438
474
  elif tmp_ is "shebang":
439
475
  tmp_ = S.token.value
440
476
  next()
@@ -442,17 +478,18 @@ def create_parser_ctx(S, import_dirs, module_id, baselib_items, imported_module_
442
478
  'value': tmp_
443
479
  })
444
480
  elif tmp_ is "num" or tmp_ is "regexp" or tmp_ is "operator" or tmp_ is "atom" or tmp_ is "js":
445
- return simple_statement()
481
+ return await simple_statement()
446
482
  elif tmp_ is "punc":
447
483
  tmp_ = S.token.value
448
484
  if tmp_ is ":":
485
+ bs_body = await block_()
449
486
  return new AST_BlockStatement({
450
487
  'start': S.token,
451
- 'body': block_(),
488
+ 'body': bs_body,
452
489
  'end': prev()
453
490
  })
454
491
  elif tmp_ is "{" or tmp_ is "[" or tmp_ is "(":
455
- return simple_statement()
492
+ return await simple_statement()
456
493
  elif tmp_ is ";":
457
494
  next()
458
495
  return new AST_EmptyStatement({'stype':';', 'start':prev(), 'end':prev()})
@@ -460,7 +497,7 @@ def create_parser_ctx(S, import_dirs, module_id, baselib_items, imported_module_
460
497
  unexpected()
461
498
  elif tmp_ is "name":
462
499
  if (is_token(peek(), 'punc', ':')) token_error(peek(), 'invalid syntax, colon not allowed here')
463
- return simple_statement()
500
+ return await simple_statement()
464
501
  elif tmp_ is "keyword":
465
502
  tmp_ = S.token.value
466
503
  next()
@@ -472,44 +509,64 @@ def create_parser_ctx(S, import_dirs, module_id, baselib_items, imported_module_
472
509
  semicolon()
473
510
  return new AST_Debugger()
474
511
  elif tmp_ is "do":
512
+ do_body = await in_loop(statement)
513
+ do_cond = await (async def():
514
+ expect(".")
515
+ expect_token("keyword", "while")
516
+ tmp = await expression(True)
517
+ if is_node_type(tmp, AST_Assign):
518
+ croak('Assignments in do loop conditions are not allowed')
519
+ semicolon()
520
+ return tmp
521
+ )()
475
522
  return new AST_Do({
476
- 'body': in_loop(statement),
477
- 'condition': (def():
478
- expect(".")
479
- expect_token("keyword", "while")
480
- tmp = expression(True)
481
- if is_node_type(tmp, AST_Assign):
482
- croak('Assignments in do loop conditions are not allowed')
483
- semicolon()
484
- return tmp
485
- )()
523
+ 'body': do_body,
524
+ 'condition': do_cond,
486
525
  })
487
526
  elif tmp_ is "while":
488
- while_cond = expression(True)
527
+ while_cond = await expression(True)
489
528
  if is_node_type(while_cond, AST_Assign):
490
529
  croak('Assignments in while loop conditions are not allowed')
491
530
  if not is_('punc', ':'):
492
531
  croak('Expected a colon after the while statement')
532
+ while_body = await in_loop(statement)
493
533
  return new AST_While({
494
534
  'condition': while_cond,
495
- 'body': in_loop(statement)
535
+ 'body': while_body
496
536
  })
497
537
  elif tmp_ is "for":
498
538
  if is_('js'):
499
- return for_js()
500
- return for_()
539
+ return await for_js()
540
+ return await for_()
501
541
  elif tmp_ is "from":
502
- return import_(True)
542
+ return await import_(True)
503
543
  elif tmp_ is "import":
504
- return import_(False)
544
+ return await import_(False)
505
545
  elif tmp_ is "class":
506
- return class_()
546
+ return await class_()
547
+ elif tmp_ is "async":
548
+ if not is_('keyword', 'def'):
549
+ croak("Expected 'def' after 'async'")
550
+ next()
551
+ start = prev()
552
+ func = await function_(S.in_class[-1], False, True)
553
+ func.start = start
554
+ func.end = prev()
555
+ chain = await subscripts(func, True)
556
+ if chain is func:
557
+ return func
558
+ else:
559
+ return new AST_SimpleStatement({
560
+ 'start': start,
561
+ 'body': chain,
562
+ 'end': prev()
563
+ })
507
564
  elif tmp_ is "def":
508
565
  start = prev()
509
- func = function_(S.in_class[-1], False)
566
+ func = await function_(S.in_class[-1], False)
510
567
  func.start = start
511
568
  func.end = prev()
512
- chain = subscripts(func, True)
569
+ chain = await subscripts(func, True)
513
570
  if chain is func:
514
571
  return func
515
572
  else:
@@ -520,14 +577,14 @@ def create_parser_ctx(S, import_dirs, module_id, baselib_items, imported_module_
520
577
  })
521
578
  elif tmp_ is 'assert':
522
579
  start = prev()
523
- cond = expression(False)
580
+ cond = await expression(False)
524
581
  msg = None
525
582
  if is_('punc', ','):
526
583
  next()
527
- msg = expression(False)
584
+ msg = await expression(False)
528
585
  return new AST_Assert({'start': start, 'condition':cond, 'message':msg, 'end':prev()})
529
586
  elif tmp_ is "if":
530
- return if_()
587
+ return await if_()
531
588
  elif tmp_ is "pass":
532
589
  semicolon()
533
590
  return new AST_EmptyStatement({'stype':'pass', 'start':prev(), 'end':prev()})
@@ -538,9 +595,17 @@ def create_parser_ctx(S, import_dirs, module_id, baselib_items, imported_module_
538
595
  croak("'return' not allowed in a function with yield")
539
596
  S.functions[-1].is_generator = False
540
597
 
541
- return new AST_Return({'value':return_()})
598
+ ret_val = await return_()
599
+ return new AST_Return({'value':ret_val})
600
+ elif tmp_ is "await":
601
+ if S.in_function is 0 or not S.functions[-1].is_async:
602
+ croak("'await' outside of async function")
603
+ value = await expr_atom(True)
604
+ node = new AST_Await({'value': value})
605
+ semicolon()
606
+ return new AST_SimpleStatement({'body': node})
542
607
  elif tmp_ is "yield":
543
- return yield_()
608
+ return await yield_()
544
609
  elif tmp_ is "raise":
545
610
  if S.token.nlb:
546
611
  return new AST_Throw({
@@ -549,33 +614,33 @@ def create_parser_ctx(S, import_dirs, module_id, baselib_items, imported_module_
549
614
  })
550
615
  })
551
616
 
552
- tmp = expression(True)
617
+ tmp = await expression(True)
553
618
  semicolon()
554
619
  return new AST_Throw({
555
620
  'value': tmp
556
621
  })
557
622
  elif tmp_ is "try":
558
- return try_()
623
+ return await try_()
559
624
  elif tmp_ is "nonlocal":
560
- tmp = nonlocal_()
625
+ tmp = await nonlocal_()
561
626
  semicolon()
562
627
  return tmp
563
628
  elif tmp_ is 'global':
564
- tmp = nonlocal_(True)
629
+ tmp = await nonlocal_(True)
565
630
  semicolon()
566
631
  return tmp
567
632
  elif tmp_ is "with":
568
- return with_()
633
+ return await with_()
569
634
  else:
570
635
  unexpected()
571
636
 
572
- def with_():
637
+ async def with_():
573
638
  clauses = v'[]'
574
639
  start = S.token
575
640
  while True:
576
641
  if is_('eof'):
577
642
  unexpected()
578
- expr = expression()
643
+ expr = await expression()
579
644
  alias = None
580
645
  if is_('keyword', 'as'):
581
646
  next()
@@ -590,15 +655,15 @@ def create_parser_ctx(S, import_dirs, module_id, baselib_items, imported_module_
590
655
 
591
656
  if not clauses.length:
592
657
  token_error(start, 'with statement must have at least one clause')
593
- body = statement()
658
+ body = await statement()
594
659
 
595
660
  return new AST_With({
596
661
  'clauses': clauses,
597
662
  'body': body
598
663
  })
599
664
 
600
- def simple_statement(tmp):
601
- tmp = expression(True)
665
+ async def simple_statement(tmp):
666
+ tmp = await expression(True)
602
667
  semicolon()
603
668
  return new AST_SimpleStatement({
604
669
  'body': tmp
@@ -610,22 +675,25 @@ def create_parser_ctx(S, import_dirs, module_id, baselib_items, imported_module_
610
675
  semicolon()
611
676
  return new t()
612
677
 
613
- def yield_():
678
+ async def yield_():
614
679
  if S.in_function is 0:
615
680
  croak("'yield' outside of function")
681
+ if S.functions[-1].is_async:
682
+ croak("'yield' not allowed in an async function")
616
683
  if S.functions[-1].is_generator is False:
617
684
  croak("'yield' not allowed in a function with return")
618
685
  S.functions[-1].is_generator = True
619
686
  is_yield_from = is_('keyword', 'from')
620
687
  if is_yield_from:
621
688
  next()
622
- return new AST_Yield({'is_yield_from':is_yield_from, 'value': return_()})
689
+ yld_val = await return_()
690
+ return new AST_Yield({'is_yield_from':is_yield_from, 'value': yld_val})
623
691
 
624
- def for_(list_comp):
692
+ async def for_(list_comp):
625
693
  # expect("(")
626
694
  init = None
627
695
  if not is_("punc", ";"):
628
- init = expression(True, True)
696
+ init = await expression(True, True)
629
697
  # standardize AST_Seq into array now for consistency
630
698
  if is_node_type(init, AST_Seq):
631
699
  if is_node_type(init.car, AST_SymbolRef) and is_node_type(init.cdr, AST_SymbolRef):
@@ -643,13 +711,13 @@ def create_parser_ctx(S, import_dirs, module_id, baselib_items, imported_module_
643
711
  if is_node_type(init, AST_Var) and init.definitions.length > 1:
644
712
  croak("Only one variable declaration allowed in for..in loop")
645
713
  next()
646
- return for_in(init, list_comp)
714
+ return await for_in(init, list_comp)
647
715
 
648
716
  unexpected()
649
717
 
650
- def for_in(init, list_comp):
718
+ async def for_in(init, list_comp):
651
719
  lhs = init.definitions[0].name if is_node_type(init, AST_Var) else None
652
- obj = expression(True)
720
+ obj = await expression(True)
653
721
  # expect(")")
654
722
  if list_comp:
655
723
  return {
@@ -658,19 +726,21 @@ def create_parser_ctx(S, import_dirs, module_id, baselib_items, imported_module_
658
726
  'object': obj
659
727
  }
660
728
 
729
+ fi_body = await in_loop(statement)
661
730
  return new AST_ForIn({
662
731
  'init': init,
663
732
  'name': lhs,
664
733
  'object': obj,
665
- 'body': in_loop(statement)
734
+ 'body': fi_body
666
735
  })
667
736
 
668
737
  # A native JavaScript for loop - for v"var i=0; i<5000; i++":
669
- def for_js():
738
+ async def for_js():
670
739
  condition = as_atom_node()
740
+ fj_body = await in_loop(statement)
671
741
  return new AST_ForJS({
672
742
  'condition': condition,
673
- 'body': in_loop(statement)
743
+ 'body': fj_body
674
744
  })
675
745
 
676
746
  # scan function/class body for nested class declarations
@@ -712,72 +782,144 @@ def create_parser_ctx(S, import_dirs, module_id, baselib_items, imported_module_
712
782
  ctx = S.input.context()
713
783
  raise new ImportError(message, ctx.filename, ctx.tokline, ctx.tokcol, ctx.tokpos)
714
784
 
715
- def do_import(key):
785
+ async def do_import(key):
716
786
  if has_prop(imported_modules, key):
717
787
  return
788
+ # Circular import check must come BEFORE loading_promises so we detect cycles
789
+ # even when the same module is being loaded by a parallel chain.
718
790
  if has_prop(importing_modules, key) and importing_modules[key]:
719
791
  import_error('Detected a recursive import of: ' + key + ' while importing: ' + module_id)
792
+ # If a parallel chain is already loading this module, wait for it instead of
793
+ # starting a duplicate load. loading_promises is shared across the entire
794
+ # compilation via options.loading_promises.
795
+ loading_promises = options.loading_promises
796
+ if has_prop(loading_promises, key):
797
+ await loading_promises[key]
798
+ return
720
799
 
721
- # Ensure that the package containing this module is also imported
722
- package_module_id = key.split('.')[:-1].join('.')
723
- if len(package_module_id) > 0:
724
- do_import(package_module_id)
800
+ # Register this load BEFORE the first await so that any parallel caller
801
+ # that reaches this point sees the in-progress entry and waits.
802
+ # Use a single-element list to capture the resolve function from the Promise
803
+ # executor without needing nonlocal (mutating a container, not rebinding).
804
+ _rfn = v'[null]'
805
+ def _capture_resolve(resolve):
806
+ _rfn[0] = resolve
807
+ loading_promises[key] = new Promise(_capture_resolve)
725
808
 
726
- if options.for_linting:
727
- imported_modules[key] = {'is_cached':True, 'classes':{}, 'module_id':key, 'exports':[],
728
- 'nonlocalvars':[], 'baselib':{}, 'outputs':{}, 'discard_asserts':options.discard_asserts}
729
- return
809
+ try:
810
+ # Ensure that the package containing this module is also imported
811
+ package_module_id = key.split('.')[:-1].join('.')
812
+ if len(package_module_id) > 0:
813
+ await do_import(package_module_id)
814
+
815
+ if options.for_linting:
816
+ imported_modules[key] = {'is_cached':True, 'classes':{}, 'module_id':key, 'exports':[],
817
+ 'nonlocalvars':[], 'baselib':{}, 'outputs':{}, 'discard_asserts':options.discard_asserts}
818
+ return
730
819
 
731
- def safe_read(base_path):
732
- for i, path in enumerate([base_path + '.pyj', base_path + '/__init__.pyj']):
733
- try:
734
- return [readfile(path, "utf-8"), path] # noqa:undef
735
- except as e:
736
- if e.code is 'ENOENT' or e.code is 'EPERM' or e.code is 'EACCESS':
820
+ # stat_file returns {mtimeMs, content?}. For real filesystems mtimeMs is a
821
+ # number and content is absent; for virtual filesystems content holds the
822
+ # already-read source to avoid a double read.
823
+ async def safe_stat(base_path): # noqa
824
+ for i, path in enumerate([base_path + '.pyj', base_path + '/__init__.pyj']):
825
+ try:
826
+ st = await stat_file(path)
827
+ return [st.mtimeMs, path, st.content if has_prop(st, 'content') else None]
828
+ except as e:
829
+ if e.code is 'ENOENT' or e.code is 'EPERM' or e.code is 'EACCESS':
830
+ if i is 1:
831
+ return None, None, None
737
832
  if i is 1:
738
- return None, None
739
- if i is 1:
740
- raise
833
+ raise
741
834
 
742
- src_code = filename = None
743
- modpath = key.replace(/\./g, '/')
835
+ file_mtime = filename = prefetched_src = None
836
+ modpath = key.replace(/\./g, '/')
744
837
 
745
- for location in import_dirs:
746
- if location:
747
- data, filename = safe_read(location + '/' + modpath)
748
- if data is not None:
749
- src_code = data
750
- break
751
- if src_code is None:
752
- import_error("Failed Import: '" + key + "' module doesn't exist in any of the import directories: " + import_dirs.join(':'))
838
+ for location in import_dirs:
839
+ if location:
840
+ file_mtime, filename, prefetched_src = await safe_stat(location + '/' + modpath)
841
+ if filename is not None:
842
+ break
843
+ if filename is None:
844
+ import_error("Failed Import: '" + key + "' module doesn't exist in any of the import directories: " + import_dirs.join(':'))
753
845
 
754
- try:
755
- cached = JSON.parse(readfile(cache_file_name(filename, options.module_cache_dir), 'utf-8'))
756
- except:
757
- cached = None
846
+ # Read the cache file. I/O starts here; any CPU work done before the
847
+ # await can overlap with the disk read.
848
+ cache_read = readfile(cache_file_name(filename, options.module_cache_dir))
758
849
 
759
- srchash = sha1sum(src_code) # noqa:undef
760
- if cached and cached.version is COMPILER_VERSION and cached.signature is srchash and cached.discard_asserts is v'!!options.discard_asserts':
761
- for ikey in cached.imported_module_ids:
762
- do_import(ikey) # Ensure all modules imported by the cached module are also imported
763
- imported_modules[key] = {
764
- 'is_cached':True, 'classes':cached.classes, 'outputs':cached.outputs, 'module_id':key, 'import_order':Object.keys(imported_modules).length,
765
- 'nonlocalvars':cached.nonlocalvars, 'baselib':cached.baselib, 'exports':cached.exports, 'discard_asserts':options.discard_asserts,
766
- 'imported_module_ids':cached.imported_module_ids,
767
- }
768
- else:
769
- parse(src_code, {
770
- 'filename': filename,
771
- 'toplevel': None,
772
- 'basedir': options.basedir,
773
- 'libdir': options.libdir,
774
- 'import_dirs': options.import_dirs,
775
- 'module_id': key,
776
- 'imported_modules': imported_modules,
777
- 'importing_modules': importing_modules,
778
- 'discard_asserts': options.discard_asserts,
779
- 'module_cache_dir': options.module_cache_dir,
780
- }) # This function will add the module to imported_modules itself
850
+ cached = None
851
+ ast_json_str = None
852
+ try:
853
+ _split = decode_cache(await cache_read)
854
+ if _split is not None:
855
+ cached = JSON.parse(_split.meta)
856
+ ast_json_str = _split.ast
857
+ except:
858
+ cached = None
859
+
860
+ srchash = src_code = None
861
+ cache_ok = cached and cached.v is CACHE_VERSION and cached.compiler_version is COMPILER_VERSION
862
+ if cache_ok and file_mtime is not None and cached.mtime is not None and cached.mtime is file_mtime:
863
+ # Fast path: mtime matches — source file is unchanged, skip reading and hashing it
864
+ srchash = cached.signature
865
+ else:
866
+ # Need source content to validate with sha1sum (or to re-parse on miss)
867
+ src_code = prefetched_src or await readfile(filename, 'utf-8')
868
+ srchash = sha1sum(src_code)
869
+ if not cache_ok or cached.signature is not srchash:
870
+ cached = None
871
+
872
+ if cached is not None:
873
+ # Load all deps in parallel — cached module deps have been pre-validated as
874
+ # non-circular so Promise.all is safe. The loading_promises dict ensures
875
+ # that if two parallel chains try to load the same dep, only one actually
876
+ # does the work; the other waits.
877
+ pending = v'[]'
878
+ for ikey in cached.imported_module_ids:
879
+ if not has_prop(imported_modules, ikey):
880
+ pending.push(do_import(ikey))
881
+ if pending.length > 0:
882
+ await Promise.all(pending)
883
+ # Reconstruct classes dict from the simplified cache (no full AST nodes).
884
+ classes = {}
885
+ for cname in Object.keys(cached.classes or {}):
886
+ ci = cached.classes[cname]
887
+ classes[cname] = {'name': {'name': cname}, 'static': ci.static or {}, 'bound': ci.bound or [], 'classvars': ci.classvars or {}}
888
+ meta = {
889
+ 'module_id': key,
890
+ 'filename': filename,
891
+ 'import_order': Object.keys(imported_modules).length,
892
+ 'imports': imported_modules,
893
+ 'exports': [{'name': n} for n in cached.exported_names],
894
+ 'baselib': cached.baselib or {},
895
+ 'classes': classes,
896
+ }
897
+ if cached.has_side_effects is not undefined:
898
+ meta['_has_side_effects'] = cached.has_side_effects
899
+ if cached.index is not undefined:
900
+ meta['_cache_index'] = cached.index
901
+ module = make_lazy_ast_module(ast_json_str, meta)
902
+ imported_modules[key] = module
903
+ else:
904
+ await parse(src_code, {
905
+ 'filename': filename,
906
+ 'toplevel': None,
907
+ 'basedir': options.basedir,
908
+ 'libdir': options.libdir,
909
+ 'import_dirs': options.import_dirs,
910
+ 'module_id': key,
911
+ 'imported_modules': imported_modules,
912
+ 'importing_modules': importing_modules,
913
+ 'discard_asserts': options.discard_asserts,
914
+ 'module_cache_dir': options.module_cache_dir,
915
+ '_src_hash': srchash,
916
+ '_file_mtime': file_mtime,
917
+ 'loading_promises': loading_promises,
918
+ }) # This function will add the module to imported_modules itself
919
+ finally:
920
+ del loading_promises[key]
921
+ if _rfn[0]:
922
+ _rfn[0](None)
781
923
 
782
924
  imported_modules[key].srchash = srchash
783
925
 
@@ -811,10 +953,10 @@ def create_parser_ctx(S, import_dirs, module_id, baselib_items, imported_module_
811
953
  break
812
954
  return new AST_EmptyStatement({'stype':'scoped_flags', 'start':prev(), 'end':prev()})
813
955
 
814
- def import_(from_import):
956
+ async def import_(from_import):
815
957
  ans = new AST_Imports({'imports':[]})
816
958
  while True:
817
- tok = tmp = name = last_tok = expression(False)
959
+ tok = tmp = name = last_tok = await expression(False)
818
960
  key = ''
819
961
  while is_node_type(tmp, AST_Dot):
820
962
  key = "." + tmp.property + key
@@ -844,7 +986,7 @@ def create_parser_ctx(S, import_dirs, module_id, baselib_items, imported_module_
844
986
  break
845
987
 
846
988
  for imp in ans['imports']:
847
- do_import(imp.key)
989
+ await do_import(imp.key)
848
990
  if imported_module_ids.indexOf(imp.key) is -1:
849
991
  imported_module_ids.push(imp.key)
850
992
  classes = imported_modules[key].classes
@@ -889,7 +1031,7 @@ def create_parser_ctx(S, import_dirs, module_id, baselib_items, imported_module_
889
1031
 
890
1032
  return ans
891
1033
 
892
- def class_():
1034
+ async def class_():
893
1035
  name = as_symbol(AST_SymbolDefun)
894
1036
  if not name:
895
1037
  unexpected()
@@ -916,7 +1058,7 @@ def create_parser_ctx(S, import_dirs, module_id, baselib_items, imported_module_
916
1058
  S.in_parenthesized_expr = False
917
1059
  next()
918
1060
  break
919
- a = expr_atom(False)
1061
+ a = await expr_atom(False)
920
1062
  if class_parent is None:
921
1063
  class_parent = a
922
1064
  bases.push(a)
@@ -925,6 +1067,33 @@ def create_parser_ctx(S, import_dirs, module_id, baselib_items, imported_module_
925
1067
  continue
926
1068
 
927
1069
  docstrings = v'[]'
1070
+ cls_decorators = (def():
1071
+ d = []
1072
+ for decorator in S.decorators:
1073
+ d.push(new AST_Decorator({
1074
+ 'expression': decorator
1075
+ }))
1076
+ S.decorators = v'[]'
1077
+ return d
1078
+ )()
1079
+ cls_body = await (async def(loop, labels):
1080
+ # navigate to correct location in the module tree and append the class
1081
+ S.in_class.push(name.name)
1082
+ S.classes[S.classes.length - 1][name.name] = class_details
1083
+ S.classes.push({})
1084
+ S.scoped_flags.push()
1085
+ S.in_function += 1
1086
+ S.in_loop = 0
1087
+ S.labels = []
1088
+ a = await block_(docstrings)
1089
+ S.in_function -= 1
1090
+ S.scoped_flags.pop()
1091
+ S.classes.pop()
1092
+ S.in_class.pop()
1093
+ S.in_loop = loop
1094
+ S.labels = labels
1095
+ return a
1096
+ )(S.in_loop, S.labels)
928
1097
  definition = new AST_Class({
929
1098
  'name': name,
930
1099
  'docstrings': docstrings,
@@ -938,33 +1107,8 @@ def create_parser_ctx(S, import_dirs, module_id, baselib_items, imported_module_
938
1107
  'external': externaldecorator,
939
1108
  'bound': class_details.bound,
940
1109
  'statements': [],
941
- 'decorators': (def():
942
- d = []
943
- for decorator in S.decorators:
944
- d.push(new AST_Decorator({
945
- 'expression': decorator
946
- }))
947
- S.decorators = v'[]'
948
- return d
949
- )(),
950
- 'body': (def(loop, labels):
951
- # navigate to correct location in the module tree and append the class
952
- S.in_class.push(name.name)
953
- S.classes[S.classes.length - 1][name.name] = class_details
954
- S.classes.push({})
955
- S.scoped_flags.push()
956
- S.in_function += 1
957
- S.in_loop = 0
958
- S.labels = []
959
- a = block_(docstrings)
960
- S.in_function -= 1
961
- S.scoped_flags.pop()
962
- S.classes.pop()
963
- S.in_class.pop()
964
- S.in_loop = loop
965
- S.labels = labels
966
- return a
967
- )(S.in_loop, S.labels)
1110
+ 'decorators': cls_decorators,
1111
+ 'body': cls_body,
968
1112
  })
969
1113
  class_details.processing = False
970
1114
  # find the constructor
@@ -1008,7 +1152,7 @@ def create_parser_ctx(S, import_dirs, module_id, baselib_items, imported_module_
1008
1152
  definition.statements.push(stmt)
1009
1153
  return definition
1010
1154
 
1011
- def function_(in_class, is_expression):
1155
+ async def function_(in_class, is_expression, is_async=False):
1012
1156
  name = as_symbol(AST_SymbolDefun if in_class else AST_SymbolLambda) if is_('name') else None
1013
1157
  if in_class and not name:
1014
1158
  croak('Cannot use anonymous function as class methods')
@@ -1033,169 +1177,193 @@ def create_parser_ctx(S, import_dirs, module_id, baselib_items, imported_module_
1033
1177
  return_annotation = None
1034
1178
  is_generator = v'[]'
1035
1179
  docstrings = v'[]'
1036
- definition = new ctor({
1037
- 'name': name,
1038
- 'is_expression': is_expression,
1039
- 'is_anonymous': is_anonymous,
1040
- 'argnames': (def(a):
1041
- defaults = {}
1042
- first = True
1043
- seen_names = {}
1044
- def_line = S.input.context().tokline
1045
- current_arg_name = None
1046
- name_token = None
1047
-
1048
- def get_arg():
1049
- nonlocal current_arg_name, name_token
1050
- current_arg_name = S.token.value
1051
- if has_prop(seen_names, current_arg_name):
1052
- token_error(prev(), "Can't repeat parameter names")
1053
- if current_arg_name is 'arguments':
1054
- token_error(prev(), "Can't use the name arguments as a parameter name, it is reserved by JavaScript")
1055
- seen_names[current_arg_name] = True
1056
- # save these in order to move back if we have an annotation
1057
- name_token = S.token
1058
- name_ctx = S.input.context()
1059
- # check if we have an argument annotation
1060
- ntok = peek()
1061
- if ntok.type is 'punc' and ntok.value is ':':
1062
- next()
1063
- expect(':')
1064
- annotation = maybe_conditional()
1065
-
1066
- # and now, do as_symbol without the next() at the end
1067
- # since we are already at the next comma (or end bracket)
1068
- if not is_token(name_token, "name"):
1069
- # assuming the previous context in case
1070
- # the annotation was over the line
1071
- croak("Name expected", name_ctx.tokline)
1072
- return None
1073
1180
 
1074
- sym = new AST_SymbolFunarg({
1075
- 'name': name_token.value,
1076
- 'start': S.token,
1077
- 'end': S.token,
1078
- 'annotation': annotation
1079
- })
1080
- return sym
1081
- else:
1082
- if not is_("name"):
1083
- # there is no name, which is an error we should report on the
1084
- # same line as the definition, so move to that is we're not already there.
1085
- if S.input.context().tokline is not def_line:
1086
- croak("Name expected", def_line)
1087
- else:
1088
- croak("Name expected")
1089
- return None
1090
-
1091
- sym = new AST_SymbolFunarg({
1092
- 'name': current_arg_name,
1093
- 'start': S.token,
1094
- 'end': S.token,
1095
- 'annotation': None
1096
- })
1097
- next()
1098
- return sym
1181
+ fn_argnames = await (async def():
1182
+ a = v'[]'
1183
+ starargs_sym = undefined
1184
+ kwargs_sym = undefined
1185
+ defaults = {}
1186
+ has_defaults = False
1187
+ first = True
1188
+ seen_names = {}
1189
+ def_line = S.input.context().tokline
1190
+ current_arg_name = None
1191
+ name_token = None
1192
+
1193
+ async def get_arg():
1194
+ nonlocal current_arg_name, name_token
1195
+ current_arg_name = S.token.value
1196
+ if has_prop(seen_names, current_arg_name):
1197
+ token_error(prev(), "Can't repeat parameter names")
1198
+ if current_arg_name is 'arguments':
1199
+ token_error(prev(), "Can't use the name arguments as a parameter name, it is reserved by JavaScript")
1200
+ seen_names[current_arg_name] = True
1201
+ # save these in order to move back if we have an annotation
1202
+ name_token = S.token
1203
+ name_ctx = S.input.context()
1204
+ # check if we have an argument annotation
1205
+ ntok = peek()
1206
+ if ntok.type is 'punc' and ntok.value is ':':
1207
+ next()
1208
+ expect(':')
1209
+ annotation = await maybe_conditional()
1210
+
1211
+ # and now, do as_symbol without the next() at the end
1212
+ # since we are already at the next comma (or end bracket)
1213
+ if not is_token(name_token, "name"):
1214
+ # assuming the previous context in case
1215
+ # the annotation was over the line
1216
+ if S.input.context().tokline is not def_line:
1217
+ croak("Name expected", name_ctx.tokline)
1218
+ else:
1219
+ croak("Name expected")
1220
+ return None
1221
+
1222
+ sym = new AST_SymbolFunarg({
1223
+ 'name': name_token.value,
1224
+ 'start': S.token,
1225
+ 'end': S.token,
1226
+ 'annotation': annotation
1227
+ })
1228
+ return sym
1229
+ else:
1230
+ if not is_("name"):
1231
+ # there is no name, which is an error we should report on the
1232
+ # same line as the definition, so move to that is we're not already there.
1233
+ if S.input.context().tokline is not def_line:
1234
+ croak("Name expected", def_line)
1235
+ else:
1236
+ croak("Name expected")
1237
+ return None
1238
+
1239
+ sym = new AST_SymbolFunarg({
1240
+ 'name': current_arg_name,
1241
+ 'start': S.token,
1242
+ 'end': S.token,
1243
+ 'annotation': None
1244
+ })
1245
+ next()
1246
+ return sym
1099
1247
 
1100
- while not is_("punc", ")"):
1101
- if first:
1102
- first = False
1103
- else:
1104
- expect(",")
1105
- if is_('punc', ')'):
1106
- break
1107
- if is_('operator', '**'):
1108
- # **kwargs
1109
- next()
1110
- if a.kwargs:
1111
- token_error(name_token, "Can't define multiple **kwargs in function definition")
1112
- a.kwargs = get_arg()
1113
- elif is_('operator', '*'):
1114
- # *args
1248
+ while not is_("punc", ")"):
1249
+ if first:
1250
+ first = False
1251
+ else:
1252
+ expect(",")
1253
+ if is_('punc', ')'):
1254
+ break
1255
+ if is_('operator', '**'):
1256
+ # **kwargs
1257
+ next()
1258
+ if kwargs_sym:
1259
+ token_error(name_token, "Can't define multiple **kwargs in function definition")
1260
+ kwargs_sym = await get_arg()
1261
+ elif is_('operator', '*'):
1262
+ # *args
1263
+ next()
1264
+ if starargs_sym:
1265
+ token_error(name_token, "Can't define multiple *args in function definition")
1266
+ if kwargs_sym:
1267
+ token_error(name_token, "Can't define *args after **kwargs in function definition")
1268
+ starargs_sym = await get_arg()
1269
+ else:
1270
+ if starargs_sym or kwargs_sym:
1271
+ token_error(name_token, "Can't define a formal parameter after *args or **kwargs")
1272
+ a.push(await get_arg())
1273
+ if is_("operator", "="):
1274
+ if kwargs_sym:
1275
+ token_error(name_token, "Can't define an optional formal parameter after **kwargs")
1115
1276
  next()
1116
- if a.starargs:
1117
- token_error(name_token, "Can't define multiple *args in function definition")
1118
- if a.kwargs:
1119
- token_error(name_token, "Can't define *args after **kwargs in function definition")
1120
- a.starargs = get_arg()
1277
+ defaults[current_arg_name] = await expression(False)
1278
+ has_defaults = True
1121
1279
  else:
1122
- if a.starargs or a.kwargs:
1123
- token_error(name_token, "Can't define a formal parameter after *args or **kwargs")
1124
- a.push(get_arg())
1125
- if is_("operator", "="):
1126
- if a.kwargs:
1127
- token_error(name_token, "Can't define an optional formal parameter after **kwargs")
1128
- next()
1129
- defaults[current_arg_name] = expression(False)
1130
- a.has_defaults = True
1131
- else:
1132
- if a.has_defaults:
1133
- token_error(name_token, "Can't define required formal parameters after optional formal parameters")
1280
+ if has_defaults:
1281
+ token_error(name_token, "Can't define required formal parameters after optional formal parameters")
1134
1282
 
1283
+ next()
1284
+ # check if we have a return type annotation
1285
+ if is_("punc", "->"):
1135
1286
  next()
1136
- # check if we have a return type annotation
1137
- if is_("punc", "->"):
1138
- next()
1139
- nonlocal return_annotation
1140
- return_annotation = maybe_conditional()
1141
- S.in_parenthesized_expr = False
1142
- a.defaults = defaults
1143
- a.is_simple_func = not a.starargs and not a.kwargs and not a.has_defaults
1144
- return a
1145
- )(v'[]'),
1287
+ nonlocal return_annotation
1288
+ return_annotation = await maybe_conditional()
1289
+ S.in_parenthesized_expr = False
1290
+ return new AST_ArgsDef({
1291
+ 'args': a,
1292
+ 'starargs': starargs_sym,
1293
+ 'kwargs': kwargs_sym,
1294
+ 'defaults': defaults,
1295
+ 'has_defaults': has_defaults,
1296
+ 'is_simple_func': not starargs_sym and not kwargs_sym and not has_defaults,
1297
+ })
1298
+ )()
1299
+
1300
+ fn_decorators = (def():
1301
+ d = v'[]'
1302
+ for decorator in S.decorators:
1303
+ d.push(new AST_Decorator({
1304
+ 'expression': decorator
1305
+ }))
1306
+ S.decorators = v'[]'
1307
+ return d
1308
+ )()
1309
+
1310
+ fn_body = await (async def(loop, labels):
1311
+ S.in_class.push(False)
1312
+ S.classes.push({})
1313
+ S.scoped_flags.push()
1314
+ S.in_function += 1
1315
+ S.functions.push({'is_async': is_async})
1316
+ S.in_loop = 0
1317
+ S.labels = []
1318
+ a = await block_(docstrings)
1319
+ S.in_function -= 1
1320
+ S.scoped_flags.pop()
1321
+ is_generator.push(bool(S.functions.pop().is_generator))
1322
+ S.classes.pop()
1323
+ S.in_class.pop()
1324
+ S.in_loop = loop
1325
+ S.labels = labels
1326
+ return a
1327
+ )(S.in_loop, S.labels)
1328
+
1329
+ definition = new ctor({
1330
+ 'name': name,
1331
+ 'is_expression': is_expression,
1332
+ 'is_anonymous': is_anonymous,
1333
+ 'argnames': fn_argnames,
1146
1334
  'localvars': [],
1147
- 'decorators': (def():
1148
- d = v'[]'
1149
- for decorator in S.decorators:
1150
- d.push(new AST_Decorator({
1151
- 'expression': decorator
1152
- }))
1153
- S.decorators = v'[]'
1154
- return d
1155
- )(),
1335
+ 'decorators': fn_decorators,
1156
1336
  'docstrings': docstrings,
1157
- 'body': (def(loop, labels):
1158
- S.in_class.push(False)
1159
- S.classes.push({})
1160
- S.scoped_flags.push()
1161
- S.in_function += 1
1162
- S.functions.push({})
1163
- S.in_loop = 0
1164
- S.labels = []
1165
- a = block_(docstrings)
1166
- S.in_function -= 1
1167
- S.scoped_flags.pop()
1168
- is_generator.push(bool(S.functions.pop().is_generator))
1169
- S.classes.pop()
1170
- S.in_class.pop()
1171
- S.in_loop = loop
1172
- S.labels = labels
1173
- return a
1174
- )(S.in_loop, S.labels)
1337
+ 'body': fn_body,
1175
1338
  })
1176
1339
  definition.return_annotation = return_annotation
1177
1340
  definition.is_generator = is_generator[0]
1341
+ definition.is_async = is_async
1178
1342
  if is_node_type(definition, AST_Method):
1179
1343
  definition.static = staticmethod
1180
1344
  definition.is_getter = property_getter
1181
1345
  definition.is_setter = property_setter
1182
- if definition.argnames.length < 1 and not definition.static:
1346
+ if definition.argnames.args.length < 1 and not definition.static:
1183
1347
  croak('Methods of a class must have at least one argument, traditionally named self')
1184
1348
  if definition.name and definition.name.name is '__init__':
1185
1349
  if definition.is_generator:
1186
1350
  croak('The __init__ method of a class cannot be a generator (yield not allowed)')
1351
+ if definition.is_async:
1352
+ croak('The __init__ method of a class cannot be async')
1187
1353
  if property_getter or property_setter:
1188
1354
  croak('The __init__ method of a class cannot be a property getter/setter')
1355
+ if definition.is_async and definition.is_generator:
1356
+ croak('A function cannot be both async and a generator')
1189
1357
  if definition.is_generator:
1190
1358
  baselib_items['yield'] = True
1191
1359
 
1192
1360
  # detect local variables, strip function arguments
1193
1361
  assignments = scan_for_local_vars(definition.body)
1194
1362
  for i in range(assignments.length):
1195
- for j in range(definition.argnames.length+1):
1196
- if j is definition.argnames.length:
1363
+ for j in range(definition.argnames.args.length+1):
1364
+ if j is definition.argnames.args.length:
1197
1365
  definition.localvars.push(new_symbol(AST_SymbolVar, assignments[i]))
1198
- elif j < definition.argnames.length and assignments[i] is definition.argnames[j].name:
1366
+ elif j < definition.argnames.args.length and assignments[i] is definition.argnames.args[j].name:
1199
1367
  break
1200
1368
 
1201
1369
  nonlocals = scan_for_nonlocal_defs(definition.body)
@@ -1203,9 +1371,9 @@ def create_parser_ctx(S, import_dirs, module_id, baselib_items, imported_module_
1203
1371
  definition.localvars = definition.localvars.filter(def(v): return not nonlocals.has(v.name);)
1204
1372
  return definition
1205
1373
 
1206
- def if_():
1207
- cond = expression(True)
1208
- body = statement()
1374
+ async def if_():
1375
+ cond = await expression(True)
1376
+ body = await statement()
1209
1377
  belse = None
1210
1378
  if is_("keyword", "elif") or is_("keyword", "else"):
1211
1379
  if is_("keyword", "else"):
@@ -1213,7 +1381,7 @@ def create_parser_ctx(S, import_dirs, module_id, baselib_items, imported_module_
1213
1381
  else:
1214
1382
  S.token.value = "if"
1215
1383
  # effectively converts 'elif' to 'else if'
1216
- belse = statement()
1384
+ belse = await statement()
1217
1385
 
1218
1386
  return new AST_If({
1219
1387
  'condition': cond,
@@ -1227,15 +1395,27 @@ def create_parser_ctx(S, import_dirs, module_id, baselib_items, imported_module_
1227
1395
  return stmt.body
1228
1396
  return False
1229
1397
 
1230
- def block_(docstrings):
1398
+ async def block_(docstrings):
1231
1399
  prev_whitespace = S.token.leading_whitespace
1232
1400
  expect(":")
1233
1401
  a = v'[]'
1234
1402
  if not S.token.nlb:
1235
1403
  while not S.token.nlb:
1236
1404
  if is_("eof"):
1405
+ if options.recover_errors:
1406
+ break
1237
1407
  unexpected()
1238
- stmt = statement()
1408
+ if options.recover_errors:
1409
+ try:
1410
+ stmt = await statement()
1411
+ except as berr:
1412
+ if is_recoverable_error(berr):
1413
+ record_recovery(berr)
1414
+ await skip_to_recovery_point()
1415
+ continue
1416
+ raise
1417
+ else:
1418
+ stmt = await statement()
1239
1419
  if docstrings:
1240
1420
  ds = is_docstring(stmt)
1241
1421
  if ds:
@@ -1250,7 +1430,17 @@ def create_parser_ctx(S, import_dirs, module_id, baselib_items, imported_module_
1250
1430
  if is_("eof"):
1251
1431
  # end of file, terminate block automatically
1252
1432
  return a
1253
- stmt = statement()
1433
+ if options.recover_errors:
1434
+ try:
1435
+ stmt = await statement()
1436
+ except as berr:
1437
+ if is_recoverable_error(berr):
1438
+ record_recovery(berr)
1439
+ await skip_to_recovery_point()
1440
+ continue
1441
+ raise
1442
+ else:
1443
+ stmt = await statement()
1254
1444
  if docstrings:
1255
1445
  ds = is_docstring(stmt)
1256
1446
  if ds:
@@ -1260,8 +1450,8 @@ def create_parser_ctx(S, import_dirs, module_id, baselib_items, imported_module_
1260
1450
  next()
1261
1451
  return a
1262
1452
 
1263
- def try_():
1264
- body = block_()
1453
+ async def try_():
1454
+ body = await block_()
1265
1455
  bcatch = v'[]'
1266
1456
  bfinally = None
1267
1457
  belse = None
@@ -1280,29 +1470,32 @@ def create_parser_ctx(S, import_dirs, module_id, baselib_items, imported_module_
1280
1470
  next()
1281
1471
  name = as_symbol(AST_SymbolCatch)
1282
1472
 
1473
+ except_body = await block_()
1283
1474
  bcatch.push(new AST_Except({
1284
1475
  'start': start,
1285
1476
  'argname': name,
1286
1477
  'errors': exceptions,
1287
- 'body': block_(),
1478
+ 'body': except_body,
1288
1479
  'end': prev()
1289
1480
  }))
1290
1481
 
1291
1482
  if is_("keyword", "else"):
1292
1483
  start = S.token
1293
1484
  next()
1485
+ belse_body = await block_()
1294
1486
  belse = new AST_Else({
1295
1487
  'start': start,
1296
- 'body': block_(),
1488
+ 'body': belse_body,
1297
1489
  'end': prev()
1298
1490
  })
1299
1491
 
1300
1492
  if is_("keyword", "finally"):
1301
1493
  start = S.token
1302
1494
  next()
1495
+ bfinally_body = await block_()
1303
1496
  bfinally = new AST_Finally({
1304
1497
  'start': start,
1305
- 'body': block_(),
1498
+ 'body': bfinally_body,
1306
1499
  'end': prev()
1307
1500
  })
1308
1501
 
@@ -1316,13 +1509,20 @@ def create_parser_ctx(S, import_dirs, module_id, baselib_items, imported_module_
1316
1509
  'belse': belse
1317
1510
  })
1318
1511
 
1319
- def vardefs(symbol_class):
1512
+ async def vardefs(symbol_class):
1320
1513
  a = []
1321
1514
  while True:
1515
+ vd_start = S.token
1516
+ vd_name = as_symbol(symbol_class)
1517
+ if is_('operator', '='):
1518
+ next()
1519
+ varval = await expression(False)
1520
+ else:
1521
+ varval = None
1322
1522
  a.push(new AST_VarDef({
1323
- 'start': S.token,
1324
- 'name': as_symbol(symbol_class),
1325
- 'value': (next(), expression(False)) if is_('operator', '=') else None,
1523
+ 'start': vd_start,
1524
+ 'name': vd_name,
1525
+ 'value': varval,
1326
1526
  'end': prev()
1327
1527
  }))
1328
1528
  if not is_("punc", ","):
@@ -1331,8 +1531,8 @@ def create_parser_ctx(S, import_dirs, module_id, baselib_items, imported_module_
1331
1531
 
1332
1532
  return a
1333
1533
 
1334
- def nonlocal_(is_global):
1335
- defs = vardefs(AST_SymbolNonlocal)
1534
+ async def nonlocal_(is_global):
1535
+ defs = await vardefs(AST_SymbolNonlocal)
1336
1536
  if is_global:
1337
1537
  for vardef in defs:
1338
1538
  S.globals.push(vardef.name.name)
@@ -1342,19 +1542,19 @@ def create_parser_ctx(S, import_dirs, module_id, baselib_items, imported_module_
1342
1542
  'end': prev()
1343
1543
  })
1344
1544
 
1345
- def new_():
1545
+ async def new_():
1346
1546
  start = S.token
1347
1547
  expect_token("operator", "new")
1348
- newexp = expr_atom(False)
1548
+ newexp = await expr_atom(False)
1349
1549
 
1350
1550
  if is_("punc", "("):
1351
1551
  S.in_parenthesized_expr = True
1352
1552
  next()
1353
- args = func_call_list()
1553
+ args = await func_call_list()
1354
1554
  S.in_parenthesized_expr = False
1355
1555
  else:
1356
- args = func_call_list(True)
1357
- return subscripts(new AST_New({
1556
+ args = await func_call_list(True)
1557
+ return await subscripts(new AST_New({
1358
1558
  'start': start,
1359
1559
  'expression': newexp,
1360
1560
  'args': args,
@@ -1424,9 +1624,9 @@ def create_parser_ctx(S, import_dirs, module_id, baselib_items, imported_module_
1424
1624
  next()
1425
1625
  return ret
1426
1626
 
1427
- def expr_atom(allow_calls):
1627
+ async def expr_atom(allow_calls):
1428
1628
  if is_("operator", "new"):
1429
- return new_()
1629
+ return await new_()
1430
1630
 
1431
1631
  start = S.token
1432
1632
  if is_("punc"):
@@ -1437,9 +1637,9 @@ def create_parser_ctx(S, import_dirs, module_id, baselib_items, imported_module_
1437
1637
  if is_('punc', ')'):
1438
1638
  next()
1439
1639
  return new AST_Array({'elements':[]})
1440
- ex = expression(True)
1640
+ ex = await expression(True)
1441
1641
  if is_('keyword', 'for'):
1442
- ret = read_comprehension(new AST_GeneratorComprehension({'statement': ex}), ')')
1642
+ ret = await read_comprehension(new AST_GeneratorComprehension({'statement': ex}), ')')
1443
1643
  S.in_parenthesized_expr = False
1444
1644
  return ret
1445
1645
  ex.start = start
@@ -1451,38 +1651,55 @@ def create_parser_ctx(S, import_dirs, module_id, baselib_items, imported_module_
1451
1651
  if is_node_type(ex, AST_UnaryPrefix):
1452
1652
  ex.parenthesized = True
1453
1653
  S.in_parenthesized_expr = False
1454
- return subscripts(ex, allow_calls)
1654
+ return await subscripts(ex, allow_calls)
1455
1655
  elif tmp_ is "[":
1456
- return subscripts(array_(), allow_calls)
1656
+ return await subscripts(await array_(), allow_calls)
1457
1657
  elif tmp_ is "{":
1458
- return subscripts(object_(), allow_calls)
1658
+ return await subscripts(await object_(), allow_calls)
1459
1659
 
1460
1660
  unexpected()
1461
1661
 
1462
1662
  if is_("keyword", "class"):
1463
1663
  next()
1464
- cls = class_()
1664
+ cls = await class_()
1465
1665
  cls.start = start
1466
1666
  cls.end = prev()
1467
- return subscripts(cls, allow_calls)
1667
+ return await subscripts(cls, allow_calls)
1668
+
1669
+ if is_("keyword", "async"):
1670
+ next()
1671
+ if not is_("keyword", "def"):
1672
+ croak("Expected 'def' after 'async'")
1673
+ next()
1674
+ func = await function_(False, True, True)
1675
+ func.start = start
1676
+ func.end = prev()
1677
+ return await subscripts(func, allow_calls)
1468
1678
 
1469
1679
  if is_("keyword", "def"):
1470
1680
  next()
1471
- func = function_(False, True)
1681
+ func = await function_(False, True)
1472
1682
  func.start = start
1473
1683
  func.end = prev()
1474
- return subscripts(func, allow_calls)
1684
+ return await subscripts(func, allow_calls)
1685
+
1686
+ if is_('keyword', 'await'):
1687
+ next()
1688
+ if S.in_function is 0 or not S.functions[-1].is_async:
1689
+ croak("'await' outside of async function")
1690
+ value = await expr_atom(True)
1691
+ return new AST_Await({'value': value})
1475
1692
 
1476
1693
  if is_('keyword', 'yield'):
1477
1694
  next()
1478
- return yield_()
1695
+ return await yield_()
1479
1696
 
1480
1697
  if ATOMIC_START_TOKEN[S.token.type]:
1481
- return subscripts(as_atom_node(), allow_calls)
1698
+ return await subscripts(as_atom_node(), allow_calls)
1482
1699
 
1483
1700
  unexpected()
1484
1701
 
1485
- def expr_list(closing, allow_trailing_comma, allow_empty, func_call):
1702
+ async def expr_list(closing, allow_trailing_comma, allow_empty, func_call):
1486
1703
  first = True
1487
1704
  a = []
1488
1705
  saw_starargs = False
@@ -1507,31 +1724,28 @@ def create_parser_ctx(S, import_dirs, module_id, baselib_items, imported_module_
1507
1724
  'end': S.token
1508
1725
  }))
1509
1726
  else:
1510
- a.push(expression(False))
1727
+ a.push(await expression(False))
1511
1728
 
1729
+ next()
1512
1730
  if func_call:
1513
- tmp = []
1514
- tmp.kwargs = []
1731
+ pargs = []
1732
+ kwargs = []
1515
1733
  for arg in a:
1516
1734
  if is_node_type(arg, AST_Assign):
1517
- tmp.kwargs.push([arg.left, arg.right])
1735
+ kwargs.push([arg.left, arg.right])
1518
1736
  else:
1519
- tmp.push(arg)
1520
- a = tmp
1521
-
1522
- next()
1523
- if saw_starargs:
1524
- a.starargs = True
1737
+ pargs.push(arg)
1738
+ return new AST_CallArgs({'args': pargs, 'kwargs': kwargs, 'kwarg_items': v'[]', 'starargs': saw_starargs})
1525
1739
  return a
1526
1740
 
1527
- def func_call_list(empty):
1741
+ async def func_call_list(empty):
1528
1742
  a = v'[]'
1529
1743
  first = True
1530
- a.kwargs = v'[]'
1531
- a.kwarg_items = v'[]'
1532
- a.starargs = False
1744
+ kwargs = v'[]'
1745
+ kwarg_items = v'[]'
1746
+ starargs = False
1533
1747
  if empty:
1534
- return a
1748
+ return new AST_CallArgs({'args': a, 'kwargs': kwargs, 'kwarg_items': kwarg_items, 'starargs': starargs})
1535
1749
  single_comprehension = False
1536
1750
  while not is_("punc", ')') and not is_('eof'):
1537
1751
  if not first:
@@ -1540,50 +1754,51 @@ def create_parser_ctx(S, import_dirs, module_id, baselib_items, imported_module_
1540
1754
  break
1541
1755
  if is_('operator', '*'):
1542
1756
  next()
1543
- arg = expression(False)
1544
- arg.is_array = True
1545
- a.push(arg)
1546
- a.starargs = True
1757
+ arg = await expression(False)
1758
+ a.push(new AST_CallArg({'value': arg, 'is_array': True}))
1759
+ starargs = True
1547
1760
  elif is_('operator', '**'):
1548
1761
  next()
1549
- a.kwarg_items.push(as_symbol(AST_SymbolRef, False))
1550
- a.starargs = True
1762
+ kwarg_items.push(as_symbol(AST_SymbolRef, False))
1763
+ starargs = True
1551
1764
  else:
1552
- arg = expression(False)
1765
+ arg = await expression(False)
1553
1766
  if is_node_type(arg, AST_Assign):
1554
- a.kwargs.push([arg.left, arg.right])
1767
+ kwargs.push([arg.left, arg.right])
1555
1768
  else:
1556
1769
  if is_('keyword', 'for'):
1557
1770
  if not first:
1558
1771
  croak('Generator expression must be parenthesized if not sole argument')
1559
- a.push(read_comprehension(new AST_GeneratorComprehension({'statement': arg}), ')'))
1772
+ comp = await read_comprehension(new AST_GeneratorComprehension({'statement': arg}), ')')
1773
+ a.push(new AST_CallArg({'value': comp, 'is_array': False}))
1560
1774
  single_comprehension = True
1561
1775
  break
1562
- a.push(arg)
1776
+ a.push(new AST_CallArg({'value': arg, 'is_array': False}))
1563
1777
  first = False
1564
1778
  if not single_comprehension:
1565
1779
  next()
1566
- return a
1780
+ return new AST_CallArgs({'args': a, 'kwargs': kwargs, 'kwarg_items': kwarg_items, 'starargs': starargs})
1567
1781
 
1568
1782
  @embed_tokens
1569
- def array_():
1783
+ async def array_():
1570
1784
  expect("[")
1571
1785
  expr = []
1572
1786
  if not is_("punc", "]"):
1573
- expr.push(expression(False))
1787
+ expr.push(await expression(False))
1574
1788
  if is_("keyword", "for"):
1575
1789
  # list comprehension
1576
- return read_comprehension(new AST_ListComprehension({'statement': expr[0]}), ']')
1790
+ return await read_comprehension(new AST_ListComprehension({'statement': expr[0]}), ']')
1577
1791
 
1578
1792
  if not is_("punc", "]"):
1579
1793
  expect(",")
1580
1794
 
1795
+ arr_rest = await expr_list("]", True, True)
1581
1796
  return new AST_Array({
1582
- 'elements': expr.concat(expr_list("]", True, True))
1797
+ 'elements': expr.concat(arr_rest)
1583
1798
  })
1584
1799
 
1585
1800
  @embed_tokens
1586
- def object_():
1801
+ async def object_():
1587
1802
  expect("{")
1588
1803
  first = True
1589
1804
  has_non_const_keys = False
@@ -1603,26 +1818,27 @@ def create_parser_ctx(S, import_dirs, module_id, baselib_items, imported_module_
1603
1818
  orig = ctx.expecting_object_literal_key
1604
1819
  ctx.expecting_object_literal_key = True
1605
1820
  try:
1606
- left = expression(False)
1821
+ left = await expression(False)
1607
1822
  finally:
1608
1823
  ctx.expecting_object_literal_key = orig
1609
1824
  if is_('keyword', 'for'):
1610
1825
  # is_pydict is irrelevant here
1611
- return read_comprehension(new AST_SetComprehension({'statement':left}), '}')
1826
+ return await read_comprehension(new AST_SetComprehension({'statement':left}), '}')
1612
1827
  if a.length is 0 and (is_('punc', ',') or is_('punc', '}')):
1613
1828
  end = prev()
1614
- return set_(start, end, left)
1829
+ return await set_(start, end, left)
1615
1830
  if not is_node_type(left, AST_Constant):
1616
1831
  has_non_const_keys = True
1617
1832
  expect(":")
1833
+ obj_val = await expression(False)
1618
1834
  a.push(new AST_ObjectKeyVal({
1619
1835
  'start': start,
1620
1836
  'key': left,
1621
- 'value': expression(False),
1837
+ 'value': obj_val,
1622
1838
  'end': prev()
1623
1839
  }))
1624
1840
  if a.length is 1 and is_('keyword', 'for'):
1625
- return dict_comprehension(a, is_pydict, is_jshash)
1841
+ return await dict_comprehension(a, is_pydict, is_jshash)
1626
1842
 
1627
1843
  next()
1628
1844
  return new (AST_ExpressiveObject if has_non_const_keys else AST_Object)({
@@ -1631,7 +1847,7 @@ def create_parser_ctx(S, import_dirs, module_id, baselib_items, imported_module_
1631
1847
  'is_jshash': is_jshash,
1632
1848
  })
1633
1849
 
1634
- def set_(start, end, expr):
1850
+ async def set_(start, end, expr):
1635
1851
  ostart = start
1636
1852
  a = [new AST_SetItem({'start':start, 'end':end, 'value':expr})]
1637
1853
  while not is_("punc", "}"):
@@ -1640,35 +1856,40 @@ def create_parser_ctx(S, import_dirs, module_id, baselib_items, imported_module_
1640
1856
  if is_("punc", "}"):
1641
1857
  # allow trailing comma
1642
1858
  break
1643
- a.push(new AST_SetItem({'start':start, 'value':expression(False), 'end':prev()}))
1859
+ set_val = await expression(False)
1860
+ a.push(new AST_SetItem({'start':start, 'value':set_val, 'end':prev()}))
1644
1861
  next()
1645
1862
  return new AST_Set({'items':a, 'start':ostart, 'end':prev()})
1646
1863
 
1647
- def read_comprehension(obj, terminator):
1864
+ async def read_comprehension(obj, terminator):
1648
1865
  if is_node_type(obj, AST_GeneratorComprehension):
1649
1866
  baselib_items['yield'] = True
1650
1867
  S.in_comprehension = True
1651
1868
  S.in_parenthesized_expr = False # in case we are already in a parenthesized expression
1652
1869
  expect_token('keyword', 'for')
1653
- forloop = for_(True)
1870
+ forloop = await for_(True)
1654
1871
  obj.init = forloop.init
1655
1872
  obj.name = forloop.name
1656
1873
  obj.object = forloop.object
1657
- obj.condition = None if is_('punc', terminator) else (expect_token("keyword", "if"), expression(True))
1874
+ if is_('punc', terminator):
1875
+ obj.condition = None
1876
+ else:
1877
+ expect_token("keyword", "if")
1878
+ obj.condition = await expression(True)
1658
1879
  expect(terminator)
1659
1880
  S.in_comprehension = False
1660
1881
  return obj
1661
1882
 
1662
- def dict_comprehension(a, is_pydict, is_jshash):
1883
+ async def dict_comprehension(a, is_pydict, is_jshash):
1663
1884
  if a.length:
1664
1885
  left, right = a[0].key, a[0].value
1665
1886
  else:
1666
- left = expression(False)
1887
+ left = await expression(False)
1667
1888
  if not is_('punc', ':'):
1668
- return read_comprehension(new AST_SetComprehension({'statement':left}), '}')
1889
+ return await read_comprehension(new AST_SetComprehension({'statement':left}), '}')
1669
1890
  expect(':')
1670
- right = expression(False)
1671
- return read_comprehension(new AST_DictComprehension({'statement':left, 'value_statement':right, 'is_pydict':is_pydict, 'is_jshash':is_jshash}), '}')
1891
+ right = await expression(False)
1892
+ return await read_comprehension(new AST_DictComprehension({'statement':left, 'value_statement':right, 'is_pydict':is_pydict, 'is_jshash':is_jshash}), '}')
1672
1893
 
1673
1894
  def as_name():
1674
1895
  tmp = S.token
@@ -1714,7 +1935,7 @@ def create_parser_ctx(S, import_dirs, module_id, baselib_items, imported_module_
1714
1935
  else:
1715
1936
  return False
1716
1937
 
1717
- def getitem(expr, allow_calls):
1938
+ async def getitem(expr, allow_calls):
1718
1939
  start = expr.start
1719
1940
  next()
1720
1941
  is_py_sub = S.scoped_flags.get('overload_getitem', False)
@@ -1724,7 +1945,7 @@ def create_parser_ctx(S, import_dirs, module_id, baselib_items, imported_module_
1724
1945
  # slice [:n]
1725
1946
  slice_bounds.push(None)
1726
1947
  else:
1727
- slice_bounds.push(expression(False))
1948
+ slice_bounds.push(await expression(False))
1728
1949
 
1729
1950
  if is_("punc", ":"):
1730
1951
  # slice [n:m?]
@@ -1733,7 +1954,7 @@ def create_parser_ctx(S, import_dirs, module_id, baselib_items, imported_module_
1733
1954
  if is_("punc", ":"):
1734
1955
  slice_bounds.push(None)
1735
1956
  elif not is_("punc", "]"):
1736
- slice_bounds.push(expression(False))
1957
+ slice_bounds.push(await expression(False))
1737
1958
 
1738
1959
  if is_("punc", ":"):
1739
1960
  # slice [n:m:o?]
@@ -1741,7 +1962,7 @@ def create_parser_ctx(S, import_dirs, module_id, baselib_items, imported_module_
1741
1962
  if is_("punc", "]"):
1742
1963
  unexpected()
1743
1964
  else:
1744
- slice_bounds.push(expression(False))
1965
+ slice_bounds.push(await expression(False))
1745
1966
 
1746
1967
  expect("]")
1747
1968
 
@@ -1749,14 +1970,15 @@ def create_parser_ctx(S, import_dirs, module_id, baselib_items, imported_module_
1749
1970
  if is_("operator", '='):
1750
1971
  # splice-assignment (arr[start:end] = ...)
1751
1972
  next() # swallow the assignment
1752
- return subscripts(new AST_Splice({
1973
+ splice_assign = await expression(True)
1974
+ return await subscripts(new AST_Splice({
1753
1975
  'start': start,
1754
1976
  'expression': expr,
1755
1977
  'property': slice_bounds[0] or new AST_Number({
1756
1978
  'value': 0
1757
1979
  }),
1758
1980
  'property2': slice_bounds[1],
1759
- 'assignment': expression(True),
1981
+ 'assignment': splice_assign,
1760
1982
  'end': prev()
1761
1983
  }), allow_calls)
1762
1984
  elif slice_bounds.length is 3:
@@ -1768,26 +1990,38 @@ def create_parser_ctx(S, import_dirs, module_id, baselib_items, imported_module_
1768
1990
  slice_bounds.pop()
1769
1991
  elif not slice_bounds[-2]:
1770
1992
  slice_bounds[-2] = new AST_Undefined()
1771
- return subscripts(new AST_Call({
1993
+ _ca = v'[]'
1994
+ _ca.push(new AST_CallArg({'value': expr, 'is_array': False}))
1995
+ for _sb in slice_bounds:
1996
+ _ca.push(new AST_CallArg({'value': _sb, 'is_array': False}))
1997
+ return await subscripts(new AST_Call({
1772
1998
  'start': start,
1773
1999
  'expression': new AST_SymbolRef({
1774
2000
  'name': 'ρσ_delslice' if S.in_delete else "ρσ_eslice"
1775
2001
  }),
1776
- 'args': [expr].concat(slice_bounds),
2002
+ 'args': new AST_CallArgs({'args': _ca, 'kwargs': v'[]', 'kwarg_items': v'[]', 'starargs': False}),
1777
2003
  'end': prev()
1778
2004
  }), allow_calls)
1779
2005
  else:
1780
2006
  # regular slice (arr[start:end])
1781
2007
  slice_bounds = [new AST_Number({'value':0}) if i is None else i for i in slice_bounds]
1782
2008
  if S.in_delete:
1783
- return subscripts(new AST_Call({
2009
+ _ca2 = v'[]'
2010
+ _ca2.push(new AST_CallArg({'value': expr, 'is_array': False}))
2011
+ _ca2.push(new AST_CallArg({'value': new AST_Number({'value': 1}), 'is_array': False}))
2012
+ for _sb in slice_bounds:
2013
+ _ca2.push(new AST_CallArg({'value': _sb, 'is_array': False}))
2014
+ return await subscripts(new AST_Call({
1784
2015
  'start': start,
1785
2016
  'expression': new AST_SymbolRef({'name': 'ρσ_delslice'}),
1786
- 'args': [expr, new AST_Number({'value':1})].concat(slice_bounds),
2017
+ 'args': new AST_CallArgs({'args': _ca2, 'kwargs': v'[]', 'kwarg_items': v'[]', 'starargs': False}),
1787
2018
  'end': prev()
1788
2019
  }), allow_calls)
1789
2020
 
1790
- return subscripts(new AST_Call({
2021
+ _ca3 = v'[]'
2022
+ for _sb in slice_bounds:
2023
+ _ca3.push(new AST_CallArg({'value': _sb, 'is_array': False}))
2024
+ return await subscripts(new AST_Call({
1791
2025
  'start': start,
1792
2026
  'expression': new AST_Dot({
1793
2027
  'start': start,
@@ -1795,7 +2029,7 @@ def create_parser_ctx(S, import_dirs, module_id, baselib_items, imported_module_
1795
2029
  'property': "slice",
1796
2030
  'end': prev()
1797
2031
  }),
1798
- 'args': slice_bounds,
2032
+ 'args': new AST_CallArgs({'args': _ca3, 'kwargs': v'[]', 'kwarg_items': v'[]', 'starargs': False}),
1799
2033
  'end': prev()
1800
2034
  }), allow_calls)
1801
2035
  else:
@@ -1806,8 +2040,8 @@ def create_parser_ctx(S, import_dirs, module_id, baselib_items, imported_module_
1806
2040
  if is_("operator") and ASSIGNMENT[S.token.value]:
1807
2041
  assign_operator = S.token.value[:-1]
1808
2042
  next()
1809
- assignment = expression(True)
1810
- return subscripts(new AST_ItemAccess({
2043
+ assignment = await expression(True)
2044
+ return await subscripts(new AST_ItemAccess({
1811
2045
  'start': start,
1812
2046
  'expression': expr,
1813
2047
  'property': slice_bounds[0] or new AST_Number({
@@ -1818,7 +2052,7 @@ def create_parser_ctx(S, import_dirs, module_id, baselib_items, imported_module_
1818
2052
  'end': prev()
1819
2053
  }), allow_calls)
1820
2054
 
1821
- return subscripts(new AST_Sub({
2055
+ return await subscripts(new AST_Sub({
1822
2056
  'start': start,
1823
2057
  'expression': expr,
1824
2058
  'property': slice_bounds[0] or new AST_Number({
@@ -1827,16 +2061,17 @@ def create_parser_ctx(S, import_dirs, module_id, baselib_items, imported_module_
1827
2061
  'end': prev()
1828
2062
  }), allow_calls)
1829
2063
 
1830
- def call_(expr):
2064
+ async def call_(expr):
1831
2065
  start = expr.start
1832
2066
  S.in_parenthesized_expr = True
1833
2067
  next()
1834
2068
  if not expr.parens and get_class_in_scope(expr):
1835
2069
  # this is an object being created using a class
1836
- ret = subscripts(new AST_New({
2070
+ call_args = await func_call_list()
2071
+ ret = await subscripts(new AST_New({
1837
2072
  'start': start,
1838
2073
  'expression': expr,
1839
- 'args': func_call_list(),
2074
+ 'args': call_args,
1840
2075
  'end': prev()
1841
2076
  }), True)
1842
2077
  S.in_parenthesized_expr = False
@@ -1849,12 +2084,13 @@ def create_parser_ctx(S, import_dirs, module_id, baselib_items, imported_module_
1849
2084
  # generate class call
1850
2085
  funcname = expr
1851
2086
 
1852
- ret = subscripts(new AST_ClassCall({
2087
+ classcall_args = await func_call_list()
2088
+ ret = await subscripts(new AST_ClassCall({
1853
2089
  'start': start,
1854
2090
  "class": expr.expression,
1855
2091
  'method': funcname.property,
1856
2092
  "static": is_static_method(c, funcname.property),
1857
- 'args': func_call_list(),
2093
+ 'args': classcall_args,
1858
2094
  'end': prev()
1859
2095
  }), True)
1860
2096
  S.in_parenthesized_expr = False
@@ -1862,39 +2098,41 @@ def create_parser_ctx(S, import_dirs, module_id, baselib_items, imported_module_
1862
2098
  elif is_node_type(expr, AST_SymbolRef):
1863
2099
  tmp_ = expr.name
1864
2100
  if tmp_ is "jstype":
2101
+ jstype_args = await func_call_list()
1865
2102
  ret = new AST_UnaryPrefix({
1866
2103
  'start': start,
1867
2104
  'operator': "typeof",
1868
- 'expression': func_call_list()[0],
2105
+ 'expression': jstype_args.args[0].value,
1869
2106
  'end': prev()
1870
2107
  })
1871
2108
  S.in_parenthesized_expr = False
1872
2109
  return ret
1873
2110
  elif tmp_ is "isinstance":
1874
- args = func_call_list()
1875
- if args.length is not 2:
2111
+ args = await func_call_list()
2112
+ if args.args.length is not 2:
1876
2113
  croak('isinstance() must be called with exactly two arguments')
1877
2114
  ret = new AST_Binary({
1878
2115
  'start': start,
1879
- 'left': args[0],
2116
+ 'left': args.args[0].value,
1880
2117
  'operator': 'instanceof',
1881
- 'right': args[1],
2118
+ 'right': args.args[1].value,
1882
2119
  'end': prev()
1883
2120
  })
1884
2121
  S.in_parenthesized_expr = False
1885
2122
  return ret
1886
2123
 
1887
2124
  # fall-through to basic function call
1888
- ret = subscripts(new AST_Call({
2125
+ basic_args = await func_call_list()
2126
+ ret = await subscripts(new AST_Call({
1889
2127
  'start': start,
1890
2128
  'expression': expr,
1891
- 'args': func_call_list(),
2129
+ 'args': basic_args,
1892
2130
  'end': prev()
1893
2131
  }), True)
1894
2132
  S.in_parenthesized_expr = False
1895
2133
  return ret
1896
2134
 
1897
- def get_attr(expr, allow_calls):
2135
+ async def get_attr(expr, allow_calls):
1898
2136
  next()
1899
2137
  prop = as_name()
1900
2138
  c = get_class_in_scope(expr)
@@ -1903,14 +2141,14 @@ def create_parser_ctx(S, import_dirs, module_id, baselib_items, imported_module_
1903
2141
  if classvars and v'classvars[prop]':
1904
2142
  prop = 'prototype.' + prop
1905
2143
 
1906
- return subscripts(new AST_Dot({
2144
+ return await subscripts(new AST_Dot({
1907
2145
  'start': expr.start,
1908
2146
  'expression': expr,
1909
2147
  'property': prop,
1910
2148
  'end': prev()
1911
2149
  }), allow_calls)
1912
2150
 
1913
- def existential(expr, allow_calls):
2151
+ async def existential(expr, allow_calls):
1914
2152
  ans = new AST_Existential({'start':expr.start, 'end':S.token, 'expression':expr})
1915
2153
  next()
1916
2154
  ttype = S.token.type
@@ -1931,34 +2169,34 @@ def create_parser_ctx(S, import_dirs, module_id, baselib_items, imported_module_
1931
2169
  else:
1932
2170
  ans.after = None
1933
2171
  return ans
1934
- return subscripts(ans, allow_calls)
2172
+ return await subscripts(ans, allow_calls)
1935
2173
 
1936
- ans.after = expression()
2174
+ ans.after = await expression()
1937
2175
  return ans
1938
2176
 
1939
- def subscripts(expr, allow_calls):
2177
+ async def subscripts(expr, allow_calls):
1940
2178
  if is_("punc", "."):
1941
- return get_attr(expr, allow_calls)
2179
+ return await get_attr(expr, allow_calls)
1942
2180
 
1943
2181
  if is_("punc", "[") and not S.token.nlb:
1944
- return getitem(expr, allow_calls)
2182
+ return await getitem(expr, allow_calls)
1945
2183
 
1946
2184
  if allow_calls and is_("punc", "(") and not S.token.nlb:
1947
- return call_(expr)
2185
+ return await call_(expr)
1948
2186
 
1949
2187
  if is_('punc', '?'):
1950
- return existential(expr, allow_calls)
2188
+ return await existential(expr, allow_calls)
1951
2189
 
1952
2190
  return expr
1953
2191
 
1954
- def maybe_unary(allow_calls):
2192
+ async def maybe_unary(allow_calls):
1955
2193
  start = S.token
1956
2194
  if is_('operator', '@'):
1957
2195
  if S.parsing_decorator:
1958
2196
  croak('Nested decorators are not allowed')
1959
2197
  next()
1960
2198
  S.parsing_decorator = True
1961
- expr = expression()
2199
+ expr = await expression()
1962
2200
  S.parsing_decorator = False
1963
2201
  S.decorators.push(expr)
1964
2202
  return new AST_EmptyStatement({'stype':'@', 'start':prev(), 'end':prev()})
@@ -1966,14 +2204,14 @@ def create_parser_ctx(S, import_dirs, module_id, baselib_items, imported_module_
1966
2204
  next()
1967
2205
  is_parenthesized = is_('punc', '(')
1968
2206
  S.in_delete = start.value is 'delete'
1969
- expr = maybe_unary(allow_calls)
2207
+ expr = await maybe_unary(allow_calls)
1970
2208
  S.in_delete = False
1971
2209
  ex = make_unary(AST_UnaryPrefix, start.value, expr, is_parenthesized)
1972
2210
  ex.start = start
1973
2211
  ex.end = prev()
1974
2212
  return ex
1975
2213
 
1976
- val = expr_atom(allow_calls)
2214
+ val = await expr_atom(allow_calls)
1977
2215
  return val
1978
2216
 
1979
2217
  def make_unary(ctor, op, expr, is_parenthesized):
@@ -1983,7 +2221,7 @@ def create_parser_ctx(S, import_dirs, module_id, baselib_items, imported_module_
1983
2221
  'parenthesized': is_parenthesized
1984
2222
  })
1985
2223
 
1986
- def expr_op(left, min_prec, no_in):
2224
+ async def expr_op(left, min_prec, no_in):
1987
2225
  op = S.token.value if is_('operator') else None
1988
2226
  if op is "!" and peek().type is "operator" and peek().value is "in":
1989
2227
  next()
@@ -1995,7 +2233,7 @@ def create_parser_ctx(S, import_dirs, module_id, baselib_items, imported_module_
1995
2233
  prec = PRECEDENCE[op] if op is not None else None
1996
2234
  if prec is not None and prec > min_prec:
1997
2235
  next()
1998
- right = expr_op(maybe_unary(True), prec, no_in)
2236
+ right = await expr_op(await maybe_unary(True), prec, no_in)
1999
2237
  ret = new AST_Binary({
2000
2238
  'start': left.start,
2001
2239
  'left': left,
@@ -2003,24 +2241,25 @@ def create_parser_ctx(S, import_dirs, module_id, baselib_items, imported_module_
2003
2241
  'right': right,
2004
2242
  'end': right.end
2005
2243
  })
2006
- return expr_op(ret, min_prec, no_in)
2244
+ return await expr_op(ret, min_prec, no_in)
2007
2245
  return left
2008
2246
 
2009
- def expr_ops(no_in):
2010
- return expr_op(maybe_unary(True), 0, no_in)
2247
+ async def expr_ops(no_in):
2248
+ return await expr_op(await maybe_unary(True), 0, no_in)
2011
2249
 
2012
- def maybe_conditional(no_in):
2250
+ async def maybe_conditional(no_in):
2013
2251
  start = S.token
2014
- expr = expr_ops(no_in)
2252
+ expr = await expr_ops(no_in)
2015
2253
  if (is_('keyword', 'if') and (S.in_parenthesized_expr or (S.statement_starting_token is not S.token and not S.in_comprehension and not S.token.nlb))):
2016
2254
  next()
2017
- ne = expression(False)
2255
+ ne = await expression(False)
2018
2256
  expect_token('keyword', 'else')
2257
+ cond_alt = await expression(False, no_in)
2019
2258
  conditional = new AST_Conditional({
2020
2259
  'start': start,
2021
2260
  'condition': ne,
2022
2261
  'consequent': expr,
2023
- 'alternative': expression(False, no_in),
2262
+ 'alternative': cond_alt,
2024
2263
  'end': peek()
2025
2264
  })
2026
2265
  return conditional
@@ -2044,28 +2283,29 @@ def create_parser_ctx(S, import_dirs, module_id, baselib_items, imported_module_
2044
2283
  c.provisional_classvars[ans.left.name] = True
2045
2284
  return ans
2046
2285
 
2047
- def maybe_assign(no_in, only_plain_assignment):
2286
+ async def maybe_assign(no_in, only_plain_assignment):
2048
2287
  start = S.token
2049
- left = maybe_conditional(no_in)
2288
+ left = await maybe_conditional(no_in)
2050
2289
  val = S.token.value
2051
2290
  if is_("operator") and ASSIGNMENT[val]:
2052
2291
  if only_plain_assignment and val is not '=':
2053
2292
  croak('Invalid assignment operator for chained assignment: ' + val)
2054
2293
  next()
2294
+ assign_right = await maybe_assign(no_in, True)
2055
2295
  return create_assign({
2056
2296
  'start': start,
2057
2297
  'left': left,
2058
2298
  'operator': val,
2059
- 'right': maybe_assign(no_in, True),
2299
+ 'right': assign_right,
2060
2300
  'end': prev()
2061
2301
  })
2062
2302
  return left
2063
2303
 
2064
- def expression(commas, no_in):
2304
+ async def expression(commas, no_in):
2065
2305
  # if there is an assignment, we want the sequences to pivot
2066
2306
  # around it to allow for tuple packing/unpacking
2067
2307
  start = S.token
2068
- expr = maybe_assign(no_in)
2308
+ expr = await maybe_assign(no_in)
2069
2309
  def build_seq(a):
2070
2310
  if a.length is 1:
2071
2311
  return a[0]
@@ -2082,6 +2322,7 @@ def create_parser_ctx(S, import_dirs, module_id, baselib_items, imported_module_
2082
2322
  next()
2083
2323
  if is_node_type(expr, AST_Assign):
2084
2324
  left[-1] = left[-1].left
2325
+ seq_cdr = await expression(True, no_in)
2085
2326
  return create_assign({
2086
2327
  'start': start,
2087
2328
  'left': (left[0] if left.length is 1 else new AST_Array({
@@ -2090,12 +2331,12 @@ def create_parser_ctx(S, import_dirs, module_id, baselib_items, imported_module_
2090
2331
  'operator': expr.operator,
2091
2332
  'right': new AST_Seq({
2092
2333
  'car': expr.right,
2093
- 'cdr': expression(True, no_in)
2334
+ 'cdr': seq_cdr
2094
2335
  }),
2095
2336
  'end': peek()
2096
2337
  })
2097
2338
 
2098
- expr = maybe_assign(no_in)
2339
+ expr = await maybe_assign(no_in)
2099
2340
  left.push(expr)
2100
2341
 
2101
2342
  # if last one was an assignment, fix it
@@ -2114,20 +2355,30 @@ def create_parser_ctx(S, import_dirs, module_id, baselib_items, imported_module_
2114
2355
  return build_seq(left)
2115
2356
  return expr
2116
2357
 
2117
- def in_loop(cont):
2358
+ async def in_loop(cont):
2118
2359
  S.in_loop += 1
2119
- ret = cont()
2360
+ ret = await cont()
2120
2361
  S.in_loop -= 1
2121
2362
  return ret
2122
2363
 
2123
- def run_parser():
2364
+ async def run_parser():
2124
2365
  start = S.token = next()
2125
2366
  body = v'[]'
2126
2367
  docstrings = v'[]'
2127
2368
  first_token = True
2128
2369
  toplevel = options.toplevel
2129
2370
  while not is_("eof"):
2130
- element = statement()
2371
+ if options.recover_errors:
2372
+ try:
2373
+ element = await statement()
2374
+ except as rerr:
2375
+ if is_recoverable_error(rerr):
2376
+ record_recovery(rerr)
2377
+ await skip_to_recovery_point()
2378
+ continue
2379
+ raise
2380
+ else:
2381
+ element = await statement()
2131
2382
  if first_token and is_node_type(element, AST_Directive) and element.value.indexOf('#!') is 0:
2132
2383
  shebang = element.value
2133
2384
  else:
@@ -2152,6 +2403,18 @@ def create_parser_ctx(S, import_dirs, module_id, baselib_items, imported_module_
2152
2403
  'docstrings': docstrings,
2153
2404
  })
2154
2405
 
2406
+ if options.recover_errors:
2407
+ # Combine parser-level recovered errors with any collected by the
2408
+ # tokenizer (e.g. skipped unexpected characters) for the LSP to report.
2409
+ recovered = S.recovered_errors
2410
+ try:
2411
+ tok_ctx = S.input.context()
2412
+ if tok_ctx and tok_ctx.recovered_errors and tok_ctx.recovered_errors.length:
2413
+ recovered = recovered.concat(tok_ctx.recovered_errors)
2414
+ except:
2415
+ pass
2416
+ toplevel.recovered_errors = recovered
2417
+
2155
2418
  toplevel.nonlocalvars = scan_for_nonlocal_defs(toplevel.body).concat(S.globals)
2156
2419
  toplevel.localvars = []
2157
2420
  toplevel.exports = []
@@ -2182,11 +2445,89 @@ def create_parser_ctx(S, import_dirs, module_id, baselib_items, imported_module_
2182
2445
  toplevel.scoped_flags = S.scoped_flags.stack[0]
2183
2446
  importing_modules[module_id] = False
2184
2447
  toplevel.comments_after = S.token.comments_before or v'[]'
2448
+
2449
+ if options._src_hash and options.filename and module_id is not '__main__':
2450
+ try:
2451
+ # Simplified class info (no full AST nodes) for lazy-shell reconstruction.
2452
+ classes_cache = {}
2453
+ for cname in Object.keys(toplevel.classes or {}):
2454
+ cls = toplevel.classes[cname]
2455
+ classes_cache[cname] = {'static': cls.static or {}, 'bound': cls.bound or [], 'classvars': cls.classvars or {}}
2456
+ # Single pass: compute has_side_effects + tree-shaking index.
2457
+ # The index lets the tree-shaker resolve the fixpoint iteration
2458
+ # (steps 4-5) without accessing mod.body — no AST deserialization
2459
+ # until step 6 (pruning), which needs actual nodes anyway.
2460
+ has_se = False
2461
+ idx_exec_refs_set = {}
2462
+ idx_import_bindings = {}
2463
+ idx_top_defs = {}
2464
+
2465
+ def _collect_refs_into(node, refs_set): # noqa
2466
+ def _rv(n): # noqa
2467
+ if is_node_type(n, AST_SymbolRef):
2468
+ refs_set[n.name] = True
2469
+ node.walk(new TreeWalker(_rv))
2470
+
2471
+ for idx_stmt in toplevel.body:
2472
+ if is_node_type(idx_stmt, AST_Function) or is_node_type(idx_stmt, AST_Class):
2473
+ if idx_stmt.name:
2474
+ idx_pinned = False
2475
+ if idx_stmt.decorators:
2476
+ for idx_dec in idx_stmt.decorators:
2477
+ if idx_dec.expression and is_node_type(idx_dec.expression, AST_SymbolRef) and idx_dec.expression.name == 'no_prune':
2478
+ idx_pinned = True
2479
+ break
2480
+ idx_def_refs = {}
2481
+ _collect_refs_into(idx_stmt, idx_def_refs)
2482
+ idx_top_defs[idx_stmt.name.name] = {'pinned': idx_pinned, 'refs': Object.keys(idx_def_refs)}
2483
+ elif is_node_type(idx_stmt, AST_Imports):
2484
+ for idx_imp in idx_stmt.imports:
2485
+ if idx_imp.argnames and idx_imp.argnames.length:
2486
+ for idx_arg in idx_imp.argnames:
2487
+ idx_local = idx_arg.alias.name if idx_arg.alias else idx_arg.name
2488
+ idx_import_bindings[idx_local] = {'source': idx_imp.key, 'imported_name': idx_arg.name, 'is_namespace': False}
2489
+ else:
2490
+ idx_local = idx_imp.alias.name if idx_imp.alias else idx_imp.key.split('.')[0]
2491
+ idx_import_bindings[idx_local] = {'source': idx_imp.key, 'imported_name': None, 'is_namespace': True}
2492
+ else:
2493
+ if not has_se:
2494
+ se_found = v'[false]'
2495
+ def se_check(se_node, se_descend): # noqa
2496
+ if is_node_type(se_node, AST_Call):
2497
+ se_found[0] = True
2498
+ return True
2499
+ idx_stmt.walk(new TreeWalker(se_check))
2500
+ if se_found[0]:
2501
+ has_se = True
2502
+ _collect_refs_into(idx_stmt, idx_exec_refs_set)
2503
+
2504
+ cache_meta = {
2505
+ 'v': CACHE_VERSION,
2506
+ 'signature': options._src_hash,
2507
+ 'compiler_version': COMPILER_VERSION,
2508
+ 'imported_module_ids': toplevel.imported_module_ids,
2509
+ 'exported_names': [sym.name for sym in toplevel.exports],
2510
+ 'baselib': toplevel.baselib or {},
2511
+ 'classes': classes_cache,
2512
+ 'has_side_effects': has_se,
2513
+ 'index': {
2514
+ 'exec_refs': Object.keys(idx_exec_refs_set),
2515
+ 'import_bindings': idx_import_bindings,
2516
+ 'top_defs': idx_top_defs,
2517
+ },
2518
+ }
2519
+ if options._file_mtime is not None and options._file_mtime is not undefined:
2520
+ cache_meta['mtime'] = options._file_mtime
2521
+ # Binary format: metadata JSON followed by newline and binary AST pool.
2522
+ await writefile(cache_file_name(options.filename, options.module_cache_dir), encode_cache(JSON.stringify(cache_meta), toplevel))
2523
+ except as e:
2524
+ console.error('Failed to write AST cache file:', options.filename, 'with error:', e)
2525
+
2185
2526
  return toplevel
2186
2527
 
2187
2528
  return run_parser
2188
2529
 
2189
- def parse(text, options):
2530
+ async def parse(text, options):
2190
2531
  options = defaults(options, {
2191
2532
  'filename': None, # name of the file being parsed
2192
2533
  'module_id':'__main__', # The id of the module being parsed
@@ -2197,6 +2538,7 @@ def parse(text, options):
2197
2538
  'scoped_flags': {}, # Global scoped flags (used by the REPL)
2198
2539
  'discard_asserts': False,
2199
2540
  'module_cache_dir': '',
2541
+ 'recover_errors': False, # When True, continue parsing past syntax errors (used by the LSP). See run_parser/block_.
2200
2542
  })
2201
2543
  import_dirs = [x for x in options.import_dirs]
2202
2544
  for location in v'[options.libdir, options.basedir]':
@@ -2207,11 +2549,16 @@ def parse(text, options):
2207
2549
  imported_module_ids = []
2208
2550
  imported_modules = options.imported_modules or {}
2209
2551
  importing_modules = options.importing_modules or {}
2552
+ # loading_promises tracks modules currently being loaded across the entire compilation.
2553
+ # It is shared by passing it through options so parallel sub-parse() calls all see it.
2554
+ if not options.loading_promises:
2555
+ options.loading_promises = {}
2210
2556
  importing_modules[module_id] = True
2211
2557
 
2212
2558
  # The internal state of the parser
2213
2559
  S = {
2214
- 'input': tokenizer(text, options.filename) if jstype(text) is 'string' else text,
2560
+ 'input': tokenizer(text, options.filename, options.recover_errors) if jstype(text) is 'string' else text,
2561
+ 'recovered_errors': v'[]', # syntax errors swallowed during recovery (recover_errors mode only)
2215
2562
  'token': None,
2216
2563
  'prev': None,
2217
2564
  'peeked': [],
@@ -2249,4 +2596,7 @@ def parse(text, options):
2249
2596
  obj = options.classes[cname]
2250
2597
  S.classes[0][cname] = { 'static':obj.static, 'bound':obj.bound, 'classvars': obj.classvars }
2251
2598
 
2252
- return create_parser_ctx(S, import_dirs, module_id, baselib_items, imported_module_ids, imported_modules, importing_modules, options)()
2599
+ if jstype(text) is 'string' and not options._src_hash:
2600
+ options._src_hash = sha1sum(text)
2601
+
2602
+ return await create_parser_ctx(S, import_dirs, module_id, baselib_items, imported_module_ids, imported_modules, importing_modules, options)()