rapydscript-ns 0.8.3 → 0.8.4

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 (116) hide show
  1. package/.agignore +1 -1
  2. package/.github/workflows/ci.yml +38 -38
  3. package/=template.pyj +5 -5
  4. package/CHANGELOG.md +8 -0
  5. package/HACKING.md +103 -103
  6. package/LICENSE +24 -24
  7. package/PYTHON_DIFFERENCES_REPORT.md +2 -2
  8. package/PYTHON_FEATURE_COVERAGE.md +13 -13
  9. package/README.md +670 -6
  10. package/TODO.md +5 -6
  11. package/add-toc-to-readme +2 -2
  12. package/bin/export +75 -75
  13. package/bin/rapydscript +70 -70
  14. package/bin/web-repl-export +102 -102
  15. package/build +2 -2
  16. package/language-service/index.js +155 -6
  17. package/package.json +1 -1
  18. package/publish.py +37 -37
  19. package/release/baselib-plain-pretty.js +2006 -229
  20. package/release/baselib-plain-ugly.js +70 -3
  21. package/release/compiler.js +11554 -3870
  22. package/release/signatures.json +31 -29
  23. package/session.vim +4 -4
  24. package/setup.cfg +2 -2
  25. package/src/ast.pyj +93 -1
  26. package/src/baselib-builtins.pyj +22 -1
  27. package/src/baselib-containers.pyj +99 -0
  28. package/src/baselib-errors.pyj +44 -0
  29. package/src/baselib-internal.pyj +94 -4
  30. package/src/baselib-itertools.pyj +97 -97
  31. package/src/baselib-str.pyj +24 -0
  32. package/src/compiler.pyj +36 -36
  33. package/src/errors.pyj +30 -30
  34. package/src/lib/aes.pyj +646 -646
  35. package/src/lib/copy.pyj +120 -0
  36. package/src/lib/elementmaker.pyj +83 -83
  37. package/src/lib/encodings.pyj +126 -126
  38. package/src/lib/gettext.pyj +569 -569
  39. package/src/lib/itertools.pyj +580 -580
  40. package/src/lib/math.pyj +193 -193
  41. package/src/lib/operator.pyj +11 -11
  42. package/src/lib/pythonize.pyj +20 -20
  43. package/src/lib/random.pyj +118 -118
  44. package/src/lib/re.pyj +470 -470
  45. package/src/lib/react.pyj +74 -0
  46. package/src/lib/traceback.pyj +63 -63
  47. package/src/lib/uuid.pyj +77 -77
  48. package/src/monaco-language-service/builtins.js +5 -0
  49. package/src/monaco-language-service/diagnostics.js +25 -3
  50. package/src/monaco-language-service/dts.js +550 -550
  51. package/src/output/classes.pyj +108 -8
  52. package/src/output/codegen.pyj +16 -2
  53. package/src/output/comments.pyj +45 -45
  54. package/src/output/exceptions.pyj +201 -105
  55. package/src/output/functions.pyj +9 -0
  56. package/src/output/jsx.pyj +164 -0
  57. package/src/output/literals.pyj +28 -2
  58. package/src/output/modules.pyj +1 -1
  59. package/src/output/operators.pyj +8 -2
  60. package/src/output/statements.pyj +2 -2
  61. package/src/output/stream.pyj +1 -0
  62. package/src/output/treeshake.pyj +182 -182
  63. package/src/output/utils.pyj +72 -72
  64. package/src/parse.pyj +417 -113
  65. package/src/string_interpolation.pyj +72 -72
  66. package/src/tokenizer.pyj +29 -0
  67. package/src/unicode_aliases.pyj +576 -576
  68. package/src/utils.pyj +192 -192
  69. package/test/_import_one.pyj +37 -37
  70. package/test/_import_two/__init__.pyj +11 -11
  71. package/test/_import_two/level2/deep.pyj +4 -4
  72. package/test/_import_two/other.pyj +6 -6
  73. package/test/_import_two/sub.pyj +13 -13
  74. package/test/aes_vectors.pyj +421 -421
  75. package/test/annotations.pyj +80 -80
  76. package/test/decorators.pyj +77 -77
  77. package/test/docstrings.pyj +39 -39
  78. package/test/elementmaker_test.pyj +45 -45
  79. package/test/functions.pyj +151 -151
  80. package/test/generators.pyj +41 -41
  81. package/test/generic.pyj +370 -370
  82. package/test/imports.pyj +72 -72
  83. package/test/internationalization.pyj +73 -73
  84. package/test/lint.pyj +164 -164
  85. package/test/loops.pyj +85 -85
  86. package/test/numpy.pyj +734 -734
  87. package/test/omit_function_metadata.pyj +20 -20
  88. package/test/python_features.pyj +19 -6
  89. package/test/regexp.pyj +55 -55
  90. package/test/repl.pyj +121 -121
  91. package/test/scoped_flags.pyj +76 -76
  92. package/test/unit/index.js +2177 -64
  93. package/test/unit/language-service-dts.js +543 -543
  94. package/test/unit/language-service-hover.js +455 -455
  95. package/test/unit/language-service.js +590 -4
  96. package/test/unit/web-repl.js +303 -0
  97. package/tools/cli.js +547 -547
  98. package/tools/compile.js +219 -219
  99. package/tools/completer.js +131 -131
  100. package/tools/embedded_compiler.js +251 -251
  101. package/tools/gettext.js +185 -185
  102. package/tools/ini.js +65 -65
  103. package/tools/msgfmt.js +187 -187
  104. package/tools/repl.js +223 -223
  105. package/tools/test.js +118 -118
  106. package/tools/utils.js +128 -128
  107. package/tools/web_repl.js +95 -95
  108. package/try +41 -41
  109. package/web-repl/env.js +196 -74
  110. package/web-repl/index.html +163 -163
  111. package/web-repl/main.js +252 -254
  112. package/web-repl/prism.css +139 -139
  113. package/web-repl/prism.js +113 -113
  114. package/web-repl/rapydscript.js +224 -102
  115. package/web-repl/sha1.js +25 -25
  116. package/hack_demo.pyj +0 -112
package/src/parse.pyj CHANGED
@@ -21,16 +21,17 @@ 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_Await, AST_Assert, AST_Existential, AST_NamedExpr, AST_AnnotatedAssign, AST_Super, AST_Starred, is_node_type,
24
+ AST_Yield, AST_Await, AST_Assert, AST_Existential, AST_NamedExpr, AST_AnnotatedAssign, AST_Super, AST_Starred, AST_Spread, is_node_type,
25
25
  AST_Match, AST_MatchCase,
26
26
  AST_MatchWildcard, AST_MatchCapture, AST_MatchLiteral, AST_MatchOr,
27
27
  AST_MatchAs, AST_MatchStar, AST_MatchSequence, AST_MatchMapping, AST_MatchClass,
28
+ AST_JSXElement, AST_JSXFragment, AST_JSXAttribute, AST_JSXSpread, AST_JSXText, AST_JSXExprContainer,
28
29
  TreeWalker
29
30
  )
30
31
  from tokenizer import tokenizer, is_token, RESERVED_WORDS
31
32
 
32
33
  COMPILER_VERSION = '__COMPILER_VERSION__'
33
- PYTHON_FLAGS = {'dict_literals':True, 'overload_getitem':True, 'bound_methods':True, 'hash_literals':True, 'overload_operators':True, 'truthiness':True}
34
+ PYTHON_FLAGS = {'dict_literals':True, 'overload_getitem':True, 'bound_methods':True, 'hash_literals':True, 'overload_operators':True, 'truthiness':True, 'jsx':True}
34
35
 
35
36
 
36
37
  def get_compiler_version():
@@ -1414,6 +1415,7 @@ def create_parser_ctx(S, import_dirs, module_id, baselib_items, imported_module_
1414
1415
  'provisional_classvars': {},
1415
1416
  }
1416
1417
  bases = v'[]'
1418
+ class_kwargs = v'[]'
1417
1419
  class_parent = None
1418
1420
 
1419
1421
  # If this class is nested inside another class, register it under its
@@ -1437,10 +1439,14 @@ def create_parser_ctx(S, import_dirs, module_id, baselib_items, imported_module_
1437
1439
  S.in_parenthesized_expr = False
1438
1440
  next()
1439
1441
  break
1440
- a = expr_atom(False)
1441
- if class_parent is None:
1442
- class_parent = a
1443
- bases.push(a)
1442
+ a = expression(False)
1443
+ if is_node_type(a, AST_Assign):
1444
+ # key=value → class keyword argument for __init_subclass__
1445
+ class_kwargs.push([a.left, a.right])
1446
+ else:
1447
+ if class_parent is None:
1448
+ class_parent = a
1449
+ bases.push(a)
1444
1450
  if is_('punc', ','):
1445
1451
  next()
1446
1452
  continue
@@ -1455,6 +1461,7 @@ def create_parser_ctx(S, import_dirs, module_id, baselib_items, imported_module_
1455
1461
  'dynamic_properties': Object.create(None),
1456
1462
  'parent': class_parent,
1457
1463
  'bases': bases,
1464
+ 'class_kwargs': class_kwargs,
1458
1465
  'localvars': [],
1459
1466
  'classvars': class_details.classvars,
1460
1467
  'static': class_details.static,
@@ -1501,6 +1508,26 @@ def create_parser_ctx(S, import_dirs, module_id, baselib_items, imported_module_
1501
1508
  descriptor['getter' if stmt.is_getter else 'setter'] = stmt
1502
1509
  elif stmt.name.name is "__init__":
1503
1510
  definition.init = stmt
1511
+ elif stmt.name.name is "__new__":
1512
+ definition.has_new = True
1513
+ class_details.has_new = True
1514
+ class_details.static['__new__'] = True
1515
+ stmt.static = True # suppress var cls = this in display_complex_body
1516
+ elif stmt.name.name is "__class_getitem__":
1517
+ stmt.is_classmethod = True
1518
+ class_details.classmethod['__class_getitem__'] = True
1519
+ elif stmt.name.name is "__init_subclass__":
1520
+ stmt.is_classmethod = True
1521
+ class_details.classmethod['__init_subclass__'] = True
1522
+ elif stmt.name.name in ['__getattr__', '__setattr__', '__delattr__', '__getattribute__']:
1523
+ definition.has_attr_dunders = True
1524
+ class_details.has_attr_dunders = True
1525
+ # Propagate has_attr_dunders from parent classes so subclasses are also Proxy-wrapped.
1526
+ if not definition.has_attr_dunders and definition.parent:
1527
+ parent_details = get_class_in_scope(definition.parent)
1528
+ if parent_details and parent_details.has_attr_dunders:
1529
+ definition.has_attr_dunders = True
1530
+ class_details.has_attr_dunders = True
1504
1531
  # find the class variables
1505
1532
  class_var_names = {}
1506
1533
  # Ensure that if a class variable refers to another class variable in
@@ -1730,7 +1757,7 @@ def create_parser_ctx(S, import_dirs, module_id, baselib_items, imported_module_
1730
1757
  S.in_parenthesized_expr = False
1731
1758
  a.defaults = defaults
1732
1759
  a.is_simple_func = not a.starargs and not a.kwargs and not a.has_defaults and not a.bare_star and not a.posonly_count
1733
- if classmethod_flag and a.length > 0:
1760
+ if (classmethod_flag or (name and (name.name is '__new__' or name.name is '__class_getitem__' or name.name is '__init_subclass__'))) and a.length > 0:
1734
1761
  cm_cls_arg.push(a[0].name)
1735
1762
  return a
1736
1763
  )(v'[]'),
@@ -1748,7 +1775,7 @@ def create_parser_ctx(S, import_dirs, module_id, baselib_items, imported_module_
1748
1775
  'body': (def(loop, labels):
1749
1776
  S.in_class.push(False)
1750
1777
  cm_pushed = v'[false]'
1751
- if classmethod_flag and in_class and cm_cls_arg.length > 0:
1778
+ if (classmethod_flag or (name and (name.name is '__new__' or name.name is '__class_getitem__' or name.name is '__init_subclass__'))) and in_class and cm_cls_arg.length > 0:
1752
1779
  cm_ctx_entry = None
1753
1780
  for v'var si = S.classes.length - 1; si >= 0; si--':
1754
1781
  if has_prop(S.classes[si], in_class):
@@ -1978,9 +2005,18 @@ def create_parser_ctx(S, import_dirs, module_id, baselib_items, imported_module_
1978
2005
  bcatch = v'[]'
1979
2006
  bfinally = None
1980
2007
  belse = None
2008
+ has_star = None # None = not yet seen, True/False = seen star/non-star
1981
2009
  while is_("keyword", "except"):
1982
2010
  start = S.token
1983
2011
  next()
2012
+ is_star = False
2013
+ if is_("operator", "*"):
2014
+ is_star = True
2015
+ next()
2016
+ if has_star is None:
2017
+ has_star = is_star
2018
+ elif has_star is not is_star:
2019
+ croak("Cannot mix 'except' and 'except*' in the same try statement")
1984
2020
  exceptions = []
1985
2021
  if not is_("punc", ":") and not is_("keyword", "as"):
1986
2022
  # Accept both: except TypeError, ValueError:
@@ -2006,6 +2042,7 @@ def create_parser_ctx(S, import_dirs, module_id, baselib_items, imported_module_
2006
2042
  'start': start,
2007
2043
  'argname': name,
2008
2044
  'errors': exceptions,
2045
+ 'is_star': is_star,
2009
2046
  'body': block_(),
2010
2047
  'end': prev()
2011
2048
  }))
@@ -2248,6 +2285,13 @@ def create_parser_ctx(S, import_dirs, module_id, baselib_items, imported_module_
2248
2285
  if is_("operator", "*") and func_call:
2249
2286
  saw_starargs = True
2250
2287
  next()
2288
+ elif is_("operator", "*") and not func_call:
2289
+ # Spread element in list literal: [1, *a, 2]
2290
+ start = S.token
2291
+ next()
2292
+ spread_expr = expression(False)
2293
+ a.push(new AST_Spread({'start': start, 'expression': spread_expr, 'end': prev()}))
2294
+ continue
2251
2295
 
2252
2296
  if is_("punc", ",") and allow_empty:
2253
2297
  a.push(new AST_Hole({
@@ -2294,7 +2338,7 @@ def create_parser_ctx(S, import_dirs, module_id, baselib_items, imported_module_
2294
2338
  a.starargs = True
2295
2339
  elif is_('operator', '**'):
2296
2340
  next()
2297
- a.kwarg_items.push(as_symbol(AST_SymbolRef, False))
2341
+ a.kwarg_items.push(expression(False))
2298
2342
  a.starargs = True
2299
2343
  else:
2300
2344
  arg = expression(False)
@@ -2318,10 +2362,17 @@ def create_parser_ctx(S, import_dirs, module_id, baselib_items, imported_module_
2318
2362
  expect("[")
2319
2363
  expr = []
2320
2364
  if not is_("punc", "]"):
2321
- expr.push(expression(False))
2322
- if is_("keyword", "for"):
2323
- # list comprehension
2324
- return read_comprehension(new AST_ListComprehension({'statement': expr[0]}), ']')
2365
+ if is_("operator", "*"):
2366
+ # Spread as first element — no list comprehension possible
2367
+ start = S.token
2368
+ next()
2369
+ spread_expr = expression(False)
2370
+ expr.push(new AST_Spread({'start': start, 'expression': spread_expr, 'end': prev()}))
2371
+ else:
2372
+ expr.push(expression(False))
2373
+ if is_("keyword", "for"):
2374
+ # list comprehension
2375
+ return read_comprehension(new AST_ListComprehension({'statement': expr[0]}), ']')
2325
2376
 
2326
2377
  if not is_("punc", "]"):
2327
2378
  expect(",")
@@ -2356,6 +2407,12 @@ def create_parser_ctx(S, import_dirs, module_id, baselib_items, imported_module_
2356
2407
  }))
2357
2408
  has_non_const_keys = True
2358
2409
  continue
2410
+ if is_('operator', '*'):
2411
+ # Must be a set literal with spread as first item: {*a, ...}
2412
+ next()
2413
+ spread_expr = expression(False)
2414
+ spread_node = new AST_Spread({'start': start, 'expression': spread_expr, 'end': prev()})
2415
+ return _read_set_items([spread_node], start)
2359
2416
  ctx = S.input.context()
2360
2417
  orig = ctx.expecting_object_literal_key
2361
2418
  ctx.expecting_object_literal_key = True
@@ -2388,18 +2445,25 @@ def create_parser_ctx(S, import_dirs, module_id, baselib_items, imported_module_
2388
2445
  'is_jshash': is_jshash,
2389
2446
  })
2390
2447
 
2391
- def set_(start, end, expr):
2392
- ostart = start
2393
- a = [new AST_SetItem({'start':start, 'end':end, 'value':expr})]
2448
+ def _read_set_items(a, ostart):
2449
+ "Read remaining items of a set literal into a (handles * spread)"
2394
2450
  while not is_("punc", "}"):
2395
2451
  expect(",")
2396
- start = S.token
2452
+ item_start = S.token
2397
2453
  if is_("punc", "}"):
2398
2454
  # allow trailing comma
2399
2455
  break
2400
- a.push(new AST_SetItem({'start':start, 'value':expression(False), 'end':prev()}))
2456
+ if is_("operator", "*"):
2457
+ next()
2458
+ spread_expr = expression(False)
2459
+ a.push(new AST_Spread({'start': item_start, 'expression': spread_expr, 'end': prev()}))
2460
+ else:
2461
+ a.push(new AST_SetItem({'start': item_start, 'value': expression(False), 'end': prev()}))
2401
2462
  next()
2402
- return new AST_Set({'items':a, 'start':ostart, 'end':prev()})
2463
+ return new AST_Set({'items': a, 'start': ostart, 'end': prev()})
2464
+
2465
+ def set_(start, end, expr):
2466
+ return _read_set_items([new AST_SetItem({'start':start, 'end':end, 'value':expr})], start)
2403
2467
 
2404
2468
  def _read_comp_conditions():
2405
2469
  # Read zero or more consecutive `if` conditions, combining them with &&.
@@ -2602,6 +2666,20 @@ def create_parser_ctx(S, import_dirs, module_id, baselib_items, imported_module_
2602
2666
  multi_arr = new AST_Array({'start': start, 'elements': multi_items, 'end': prev()})
2603
2667
  multi_arr.is_subscript_tuple = True
2604
2668
  prop = multi_arr
2669
+ # __class_getitem__: Class[item] → Class.__class_getitem__(item)
2670
+ cls_info = get_class_in_scope(expr)
2671
+ if cls_info and is_static_method(cls_info, '__class_getitem__'):
2672
+ return subscripts(new AST_Call({
2673
+ 'start': start,
2674
+ 'expression': new AST_Dot({
2675
+ 'start': start,
2676
+ 'expression': expr,
2677
+ 'property': '__class_getitem__',
2678
+ 'end': prev()
2679
+ }),
2680
+ 'args': [prop],
2681
+ 'end': prev()
2682
+ }), allow_calls)
2605
2683
  if is_py_sub:
2606
2684
  assignment = None
2607
2685
  assign_operator = ''
@@ -2630,22 +2708,68 @@ def create_parser_ctx(S, import_dirs, module_id, baselib_items, imported_module_
2630
2708
  S.in_parenthesized_expr = True
2631
2709
  next()
2632
2710
  if not expr.parens and get_class_in_scope(expr):
2633
- # this is an object being created using a class
2634
- ret = subscripts(new AST_New({
2635
- 'start': start,
2636
- 'expression': expr,
2637
- 'args': func_call_list(),
2638
- 'end': prev()
2639
- }), True)
2711
+ c = get_class_in_scope(expr)
2712
+ # Class instantiation
2713
+ args = func_call_list()
2714
+ if c.has_new:
2715
+ # Class defines __new__: call without 'new' so __new__ controls
2716
+ # instantiation (the constructor dispatches via !(this instanceof ...))
2717
+ ret = subscripts(new AST_Call({
2718
+ 'start': start,
2719
+ 'expression': expr,
2720
+ 'args': args,
2721
+ 'end': prev()
2722
+ }), True)
2723
+ else:
2724
+ ret = subscripts(new AST_New({
2725
+ 'start': start,
2726
+ 'expression': expr,
2727
+ 'args': args,
2728
+ 'end': prev()
2729
+ }), True)
2640
2730
  S.in_parenthesized_expr = False
2641
2731
  return ret
2642
- else:
2643
- if is_node_type(expr, AST_Dot):
2644
- if is_node_type(expr.expression, AST_Super):
2732
+ if is_node_type(expr, AST_Dot):
2733
+ if is_node_type(expr.expression, AST_Super):
2734
+ super_node = expr.expression
2735
+ method_name = expr.property
2736
+ method_args = func_call_list()
2737
+ if method_name is '__new__':
2738
+ if super_node.parent:
2739
+ # super().__new__(cls, ...) → ρσ_new(parent, cls, ...)
2740
+ method_args.unshift(super_node.parent)
2741
+ ret = subscripts(new AST_Call({
2742
+ 'start': start,
2743
+ 'expression': new AST_SymbolRef({
2744
+ 'name': 'ρσ_new',
2745
+ 'start': start,
2746
+ 'end': start
2747
+ }),
2748
+ 'args': method_args,
2749
+ 'end': prev()
2750
+ }), True)
2751
+ else:
2752
+ # Root class: super().__new__(cls) → ρσ_object_new(cls)
2753
+ cls_args = v'[]'
2754
+ if method_args.length > 0:
2755
+ cls_args.push(method_args[0])
2756
+ ret = subscripts(new AST_Call({
2757
+ 'start': start,
2758
+ 'expression': new AST_SymbolRef({
2759
+ 'name': 'ρσ_object_new',
2760
+ 'start': start,
2761
+ 'end': start
2762
+ }),
2763
+ 'args': cls_args,
2764
+ 'end': prev()
2765
+ }), True)
2766
+ else:
2767
+ if not super_node.parent:
2768
+ croak('super() used in a class without a parent class')
2645
2769
  # super().method(args) → ParentClass.prototype.method.call(this, args)
2646
- super_node = expr.expression
2647
- method_name = expr.property
2648
- method_args = func_call_list()
2770
+ # But for classmethods/static methods, use ParentClass.method.call(this, args)
2771
+ parent_cls_info = get_class_in_scope(super_node.parent)
2772
+ super_is_static = parent_cls_info and is_static_method(parent_cls_info, method_name)
2649
2773
  this_node = new AST_This({
2650
2774
  'name': 'this',
2651
2775
  'start': start,
@@ -2656,100 +2780,119 @@ def create_parser_ctx(S, import_dirs, module_id, baselib_items, imported_module_
2656
2780
  'start': start,
2657
2781
  'class': super_node.parent,
2658
2782
  'method': method_name,
2659
- 'static': False,
2783
+ 'static': super_is_static or False,
2660
2784
  'args': method_args,
2661
2785
  'end': prev()
2662
2786
  }), True)
2663
- S.in_parenthesized_expr = False
2664
- return ret
2665
- c = get_class_in_scope(expr.expression)
2666
-
2667
- if c:
2668
- # generate class call
2669
- funcname = expr
2670
-
2671
- ret = subscripts(new AST_ClassCall({
2787
+ S.in_parenthesized_expr = False
2788
+ return ret
2789
+ # Intercept object.__setattr__/object.__getattribute__/object.__delattr__
2790
+ # and compile them to the appropriate bypass helpers.
2791
+ if (is_node_type(expr.expression, AST_SymbolRef) and
2792
+ expr.expression.name is 'object' and
2793
+ expr.property in ['__setattr__', '__getattribute__', '__delattr__']):
2794
+ helper_name = (
2795
+ 'ρσ_object_setattr' if expr.property is '__setattr__' else
2796
+ ('ρσ_object_getattr' if expr.property is '__getattribute__' else 'ρσ_object_delattr')
2797
+ )
2798
+ args = func_call_list()
2799
+ ret = subscripts(new AST_Call({
2672
2800
  'start': start,
2673
- "class": expr.expression,
2674
- 'method': funcname.property,
2675
- "static": is_static_method(c, funcname.property),
2676
- 'args': func_call_list(),
2801
+ 'expression': new AST_SymbolRef({
2802
+ 'name': helper_name,
2803
+ 'start': start,
2804
+ 'end': start
2805
+ }),
2806
+ 'args': args,
2677
2807
  'end': prev()
2678
2808
  }), True)
2679
2809
  S.in_parenthesized_expr = False
2680
2810
  return ret
2681
- elif is_node_type(expr, AST_SymbolRef):
2682
- tmp_ = expr.name
2683
- if tmp_ is "jstype":
2684
- ret = new AST_UnaryPrefix({
2685
- 'start': start,
2686
- 'operator': "typeof",
2687
- 'expression': func_call_list()[0],
2688
- 'end': prev()
2689
- })
2690
- S.in_parenthesized_expr = False
2691
- return ret
2692
- elif tmp_ is "isinstance":
2693
- args = func_call_list()
2694
- if args.length is not 2:
2695
- croak('isinstance() must be called with exactly two arguments')
2696
- ret = new AST_Binary({
2697
- 'start': start,
2698
- 'left': args[0],
2699
- 'operator': 'instanceof',
2700
- 'right': args[1],
2701
- 'end': prev()
2702
- })
2703
- S.in_parenthesized_expr = False
2704
- return ret
2705
- elif tmp_ is "super":
2706
- # Find the innermost enclosing class name
2707
- current_class = None
2708
- for v'var i = S.in_class.length - 1; i >= 0; i--':
2709
- if S.in_class[i]:
2710
- current_class = S.in_class[i]
2711
- break
2712
- if not current_class:
2713
- croak('super() is only valid inside a class method')
2714
- super_args = func_call_list()
2715
- parent_expr = None
2716
- if super_args.length is 0:
2717
- # 0-arg form: use parent of current class
2718
- for v'var s = S.classes.length - 1; s >= 0; s--':
2719
- if has_prop(S.classes[s], current_class):
2720
- parent_expr = S.classes[s][current_class].parent
2721
- break
2722
- if not parent_expr:
2723
- croak('super() used in a class without a parent class')
2724
- elif super_args.length is 2:
2725
- # 2-arg form: super(ClassName, self) — use parent of ClassName
2726
- cls_ref = super_args[0]
2727
- cls_info = get_class_in_scope(cls_ref)
2728
- if not cls_info or not cls_info.parent:
2729
- croak('First argument to super() must be a subclass with a parent')
2730
- parent_expr = cls_info.parent
2731
- else:
2732
- croak('super() takes 0 or 2 arguments (' + super_args.length + ' given)')
2733
- super_node = new AST_Super({
2734
- 'start': start,
2735
- 'parent': parent_expr,
2736
- 'class_name': current_class,
2737
- 'end': prev()
2738
- })
2739
- S.in_parenthesized_expr = False
2740
- # Call subscripts to handle subsequent .method(args) chain
2741
- return subscripts(super_node, True)
2742
-
2743
- # fall-through to basic function call
2744
- ret = subscripts(new AST_Call({
2811
+ c = get_class_in_scope(expr.expression)
2812
+ if c:
2813
+ # generate class call
2814
+ funcname = expr
2815
+ ret = subscripts(new AST_ClassCall({
2745
2816
  'start': start,
2746
- 'expression': expr,
2817
+ "class": expr.expression,
2818
+ 'method': funcname.property,
2819
+ "static": is_static_method(c, funcname.property),
2747
2820
  'args': func_call_list(),
2748
- 'end': prev(),
2749
- 'python_truthiness': S.scoped_flags.get('truthiness', False) and is_node_type(expr, AST_SymbolRef)
2821
+ 'end': prev()
2750
2822
  }), True)
2751
2823
  S.in_parenthesized_expr = False
2752
2824
  return ret
2825
+ elif is_node_type(expr, AST_SymbolRef):
2826
+ tmp_ = expr.name
2827
+ if tmp_ is "jstype":
2828
+ ret = new AST_UnaryPrefix({
2829
+ 'start': start,
2830
+ 'operator': "typeof",
2831
+ 'expression': func_call_list()[0],
2832
+ 'end': prev()
2833
+ })
2834
+ S.in_parenthesized_expr = False
2835
+ return ret
2836
+ elif tmp_ is "isinstance":
2837
+ args = func_call_list()
2838
+ if args.length is not 2:
2839
+ croak('isinstance() must be called with exactly two arguments')
2840
+ ret = new AST_Binary({
2841
+ 'start': start,
2842
+ 'left': args[0],
2843
+ 'operator': 'instanceof',
2844
+ 'right': args[1],
2845
+ 'end': prev()
2846
+ })
2847
+ S.in_parenthesized_expr = False
2848
+ return ret
2849
+ elif tmp_ is "super":
2850
+ # Find the innermost enclosing class name
2851
+ current_class = None
2852
+ for v'var i = S.in_class.length - 1; i >= 0; i--':
2853
+ if S.in_class[i]:
2854
+ current_class = S.in_class[i]
2855
+ break
2856
+ if not current_class:
2857
+ croak('super() is only valid inside a class method')
2858
+ super_args = func_call_list()
2859
+ parent_expr = None
2860
+ if super_args.length is 0:
2861
+ # 0-arg form: use parent of current class
2862
+ for v'var s = S.classes.length - 1; s >= 0; s--':
2863
+ if has_prop(S.classes[s], current_class):
2864
+ parent_expr = S.classes[s][current_class].parent
2865
+ break
2866
+ # parent_expr stays None for root classes; handled at method-call time
2867
+ # (super().__new__(cls) in a root class → ρσ_object_new(cls))
2868
+ elif super_args.length is 2:
2869
+ # 2-arg form: super(ClassName, self) — use parent of ClassName
2870
+ cls_ref = super_args[0]
2871
+ cls_info = get_class_in_scope(cls_ref)
2872
+ if not cls_info or not cls_info.parent:
2873
+ croak('First argument to super() must be a subclass with a parent')
2874
+ parent_expr = cls_info.parent
2875
+ else:
2876
+ croak('super() takes 0 or 2 arguments (' + super_args.length + ' given)')
2877
+ super_node = new AST_Super({
2878
+ 'start': start,
2879
+ 'parent': parent_expr,
2880
+ 'class_name': current_class,
2881
+ 'end': prev()
2882
+ })
2883
+ S.in_parenthesized_expr = False
2884
+ # Call subscripts to handle subsequent .method(args) chain
2885
+ return subscripts(super_node, True)
2886
+ # fall-through to basic function call
2887
+ ret = subscripts(new AST_Call({
2888
+ 'start': start,
2889
+ 'expression': expr,
2890
+ 'args': func_call_list(),
2891
+ 'end': prev(),
2892
+ 'python_truthiness': S.scoped_flags.get('truthiness', False) and is_node_type(expr, AST_SymbolRef)
2893
+ }), True)
2894
+ S.in_parenthesized_expr = False
2895
+ return ret
2753
2896
 
2754
2897
  def get_attr(expr, allow_calls):
2755
2898
  next()
@@ -2816,6 +2959,11 @@ def create_parser_ctx(S, import_dirs, module_id, baselib_items, imported_module_
2816
2959
 
2817
2960
  def maybe_unary(allow_calls):
2818
2961
  start = S.token
2962
+ # JSX: detect <tag or <> in expression position
2963
+ if S.scoped_flags.get('jsx', False) and is_('operator', '<'):
2964
+ nxt = peek()
2965
+ if nxt.type is 'name' or (nxt.type is 'operator' and nxt.value is '>'):
2966
+ return subscripts(read_jsx_element(), allow_calls)
2819
2967
  if is_('operator', '@'):
2820
2968
  if S.parsing_decorator:
2821
2969
  croak('Nested decorators are not allowed')
@@ -2843,6 +2991,162 @@ def create_parser_ctx(S, import_dirs, module_id, baselib_items, imported_module_
2843
2991
  val = expr_atom(allow_calls)
2844
2992
  return val
2845
2993
 
2994
+ # -----[ JSX parsing ]-----
2995
+
2996
+ def read_jsx_tag_name():
2997
+ # reads a tag name (identifier or dot-separated, e.g. "div", "MyComp", "Foo.Bar")
2998
+ if not is_('name'):
2999
+ croak('Expected JSX tag name')
3000
+ name = S.token.value
3001
+ next()
3002
+ while is_('punc', '.'):
3003
+ next() # consume .
3004
+ if not is_('name'):
3005
+ croak('Expected identifier after . in JSX tag name')
3006
+ name += '.' + S.token.value
3007
+ next()
3008
+ return name
3009
+
3010
+ def read_jsx_attr_name():
3011
+ # reads an attribute name; hyphens are allowed (e.g. aria-label, data-id)
3012
+ if not is_('name'):
3013
+ croak('Expected JSX attribute name')
3014
+ name = S.token.value
3015
+ next()
3016
+ while is_('operator', '-'):
3017
+ next() # consume -
3018
+ if not is_('name'):
3019
+ croak('Expected identifier after - in JSX attribute name')
3020
+ name += '-' + S.token.value
3021
+ next()
3022
+ return name
3023
+
3024
+ def read_jsx_attrs():
3025
+ # reads all attributes until > or /> is reached
3026
+ props = []
3027
+ while not is_('operator', '>') and not is_('operator', '/') and not is_('eof'):
3028
+ if is_('punc', '{'):
3029
+ # Spread attribute: {...expr}
3030
+ next() # consume {
3031
+ if not is_('atom', 'Ellipsis'):
3032
+ croak('Expected ... in JSX spread attribute')
3033
+ next() # consume ...
3034
+ expr = expression()
3035
+ expect('}')
3036
+ props.push(new AST_JSXSpread({'expression': expr, 'start': prev(), 'end': prev()}))
3037
+ elif is_('name'):
3038
+ attr_start = S.token
3039
+ name = read_jsx_attr_name()
3040
+ if is_('operator', '='):
3041
+ next() # consume =
3042
+ if is_('string'):
3043
+ val = new AST_String({'value': S.token.value, 'start': S.token, 'end': S.token, 'quote': S.token.quote})
3044
+ next()
3045
+ elif is_('punc', '{'):
3046
+ next() # consume {
3047
+ val = expression()
3048
+ expect('}')
3049
+ else:
3050
+ croak('Expected string or { in JSX attribute value')
3051
+ else:
3052
+ val = None # boolean attribute
3053
+ props.push(new AST_JSXAttribute({'name': name, 'value': val, 'start': attr_start, 'end': prev()}))
3054
+ else:
3055
+ unexpected()
3056
+ return props
3057
+
3058
+ def read_jsx_children_loop(close_tag):
3059
+ # S.token is already the first child token (jsx_text, OP(<), PUNC({))
3060
+ # JSX depth is currently >0 (entered before this call)
3061
+ children = []
3062
+ while True:
3063
+ if S.token.type is 'jsx_text':
3064
+ text = S.token.value
3065
+ children.push(new AST_JSXText({'value': text, 'start': S.token, 'end': S.token}))
3066
+ next() # still in JSX mode
3067
+ elif is_('operator', '<'):
3068
+ # Nested element or closing tag: exit JSX mode to tokenize tag content
3069
+ S.input.jsx_exit()
3070
+ next() # reads what follows < (/ or tag name)
3071
+ if is_('operator', '/'):
3072
+ # Closing tag
3073
+ next() # consume /
3074
+ if close_tag is None:
3075
+ # Fragment closing </> — next should be >
3076
+ if not is_('operator', '>'):
3077
+ croak('Expected > to close JSX fragment')
3078
+ return children # S.token = OP(>), don't consume
3079
+ else:
3080
+ if not is_('name'):
3081
+ croak('Expected closing tag name')
3082
+ if S.token.value is not close_tag:
3083
+ croak('JSX closing tag mismatch: expected </' + close_tag + '>, got </' + S.token.value + '>')
3084
+ next() # consume tag name
3085
+ if not is_('operator', '>'):
3086
+ croak('Expected > in closing JSX tag')
3087
+ return children # S.token = OP(>), don't consume
3088
+ else:
3089
+ # Nested element: S.token is first token of tag (or > for fragment)
3090
+ child = read_jsx_element_inner()
3091
+ children.push(child)
3092
+ # S.token = OP(>) — child's closing >, re-enter JSX mode then consume
3093
+ S.input.jsx_enter()
3094
+ next() # consume child's >, read next sibling in JSX mode
3095
+ elif is_('punc', '{'):
3096
+ # Expression container
3097
+ S.input.jsx_exit()
3098
+ next() # consume {
3099
+ expr = expression()
3100
+ if not is_('punc', '}'):
3101
+ croak('Expected } to close JSX expression')
3102
+ # Re-enter JSX mode BEFORE consuming } so next sibling reads in JSX mode
3103
+ S.input.jsx_enter()
3104
+ next() # consume }, read next sibling in JSX mode
3105
+ children.push(new AST_JSXExprContainer({'expression': expr, 'start': prev(), 'end': prev()}))
3106
+ elif is_('eof'):
3107
+ croak('Unterminated JSX element')
3108
+ else:
3109
+ unexpected()
3110
+
3111
+ def read_jsx_element_inner():
3112
+ # Called when S.token is NAME(tag) or OP(>) for fragment
3113
+ # NOT in JSX mode when entering
3114
+ start = S.prev # the < token (already consumed)
3115
+ if is_('operator', '>'):
3116
+ # Fragment: <>...</>
3117
+ S.input.jsx_enter()
3118
+ next() # consume >, first child in JSX mode
3119
+ children = read_jsx_children_loop(None)
3120
+ # S.token = OP(>) — closing >
3121
+ return new AST_JSXFragment({'start': start, 'children': children, 'end': S.token})
3122
+ else:
3123
+ tag = read_jsx_tag_name()
3124
+ props = read_jsx_attrs()
3125
+ if is_('operator', '/'):
3126
+ next() # consume /
3127
+ # S.token = OP(>) — self-closing
3128
+ return new AST_JSXElement({'start': start, 'tag': tag, 'props': props, 'children': [], 'self_closing': True, 'end': S.token})
3129
+ else:
3130
+ if not is_('operator', '>'):
3131
+ croak('Expected > or /> in JSX element')
3132
+ S.input.jsx_enter()
3133
+ next() # consume >, first child in JSX mode
3134
+ children = read_jsx_children_loop(tag)
3135
+ # S.token = OP(>) — closing >
3136
+ return new AST_JSXElement({'start': start, 'tag': tag, 'props': props, 'children': children, 'self_closing': False, 'end': S.token})
3137
+
3138
+ def read_jsx_element():
3139
+ # S.token = OP(<) — the opening <
3140
+ start = S.token
3141
+ next() # consume <, reads tag name or > for fragment (normal mode)
3142
+ el = read_jsx_element_inner()
3143
+ # S.token = OP(>) — closing >, consume it and return to normal mode
3144
+ next()
3145
+ el.end = prev()
3146
+ return el
3147
+
3148
+ # -----[ end JSX parsing ]-----
3149
+
2846
3150
  def make_unary(ctor, op, expr, is_parenthesized):
2847
3151
  return new ctor({
2848
3152
  'operator': op,