rapydscript-ns 0.8.3 → 0.9.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 (72) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/README.md +1351 -141
  3. package/TODO.md +12 -6
  4. package/language-service/index.js +184 -26
  5. package/package.json +1 -1
  6. package/release/baselib-plain-pretty.js +5895 -1928
  7. package/release/baselib-plain-ugly.js +140 -3
  8. package/release/compiler.js +16282 -5408
  9. package/release/signatures.json +25 -22
  10. package/src/ast.pyj +94 -1
  11. package/src/baselib-builtins.pyj +362 -3
  12. package/src/baselib-bytes.pyj +664 -0
  13. package/src/baselib-containers.pyj +99 -0
  14. package/src/baselib-errors.pyj +45 -1
  15. package/src/baselib-internal.pyj +346 -49
  16. package/src/baselib-itertools.pyj +17 -4
  17. package/src/baselib-str.pyj +46 -4
  18. package/src/lib/abc.pyj +317 -0
  19. package/src/lib/copy.pyj +120 -0
  20. package/src/lib/dataclasses.pyj +532 -0
  21. package/src/lib/enum.pyj +125 -0
  22. package/src/lib/pythonize.pyj +1 -1
  23. package/src/lib/re.pyj +35 -1
  24. package/src/lib/react.pyj +74 -0
  25. package/src/lib/typing.pyj +577 -0
  26. package/src/monaco-language-service/builtins.js +19 -4
  27. package/src/monaco-language-service/diagnostics.js +40 -19
  28. package/src/output/classes.pyj +161 -25
  29. package/src/output/codegen.pyj +16 -2
  30. package/src/output/exceptions.pyj +97 -1
  31. package/src/output/functions.pyj +87 -5
  32. package/src/output/jsx.pyj +164 -0
  33. package/src/output/literals.pyj +28 -2
  34. package/src/output/loops.pyj +5 -2
  35. package/src/output/modules.pyj +1 -1
  36. package/src/output/operators.pyj +108 -36
  37. package/src/output/statements.pyj +2 -2
  38. package/src/output/stream.pyj +1 -0
  39. package/src/parse.pyj +496 -128
  40. package/src/tokenizer.pyj +38 -4
  41. package/test/abc.pyj +291 -0
  42. package/test/arithmetic_nostrict.pyj +88 -0
  43. package/test/arithmetic_types.pyj +169 -0
  44. package/test/baselib.pyj +91 -0
  45. package/test/bytes.pyj +467 -0
  46. package/test/classes.pyj +1 -0
  47. package/test/comparison_ops.pyj +173 -0
  48. package/test/dataclasses.pyj +253 -0
  49. package/test/enum.pyj +134 -0
  50. package/test/eval_exec.pyj +56 -0
  51. package/test/format.pyj +148 -0
  52. package/test/object.pyj +64 -0
  53. package/test/python_compat.pyj +17 -15
  54. package/test/python_features.pyj +89 -21
  55. package/test/regexp.pyj +29 -1
  56. package/test/tuples.pyj +96 -0
  57. package/test/typing.pyj +469 -0
  58. package/test/unit/index.js +2292 -70
  59. package/test/unit/language-service.js +674 -4
  60. package/test/unit/web-repl.js +1106 -0
  61. package/test/vars_locals_globals.pyj +94 -0
  62. package/tools/cli.js +11 -0
  63. package/tools/compile.js +5 -0
  64. package/tools/embedded_compiler.js +15 -4
  65. package/tools/lint.js +16 -19
  66. package/tools/repl.js +1 -1
  67. package/web-repl/env.js +122 -0
  68. package/web-repl/main.js +1 -3
  69. package/web-repl/rapydscript.js +125 -3
  70. package/PYTHON_DIFFERENCES_REPORT.md +0 -291
  71. package/PYTHON_FEATURE_COVERAGE.md +0 -200
  72. package/hack_demo.pyj +0 -112
@@ -22,19 +22,33 @@ const MESSAGES = {
22
22
  'dup-method': 'The method "{name}" was defined previously at line: {line}',
23
23
  };
24
24
 
25
+ // Built-in stdlib modules that are always available in RapydScript (bundled
26
+ // with the compiler from src/lib/). These should never produce 'Unknown module'
27
+ // errors regardless of what virtualFiles or stdlibFiles are configured.
28
+ export const STDLIB_MODULES = [
29
+ 'abc', 'aes', 'collections', 'copy', 'dataclasses', 'elementmaker', 'encodings', 'enum',
30
+ 'functools', 'gettext', 'itertools', 'math', 'numpy', 'operator',
31
+ 'pythonize', 'random', 're', 'react', 'traceback', 'typing', 'uuid',
32
+ // Pseudo-modules for language feature flags (from __python__ import ...)
33
+ '__python__', '__builtins__',
34
+ ];
35
+
36
+ const _STDLIB_MODULE_SET = Object.create(null);
37
+ STDLIB_MODULES.forEach(m => { _STDLIB_MODULE_SET[m] = true; });
38
+
25
39
  // Symbols always available in RapydScript (from tools/lint.js BUILTINS list).
26
40
  export const BASE_BUILTINS = (
27
41
  'this self window document chr ord iterator_symbol print len range dir' +
28
- ' eval undefined arguments abs max min enumerate pow callable reversed sum' +
42
+ ' eval exec undefined arguments abs max min enumerate pow callable reversed sum' +
29
43
  ' getattr isFinite setattr hasattr parseInt parseFloat options_object' +
30
44
  ' isNaN JSON Math list set list_wrap ρσ_modules require bool int bin' +
31
- ' float iter Error EvalError set_wrap frozenset RangeError ReferenceError SyntaxError' +
45
+ ' float complex iter Error EvalError set_wrap frozenset RangeError ReferenceError SyntaxError' +
32
46
  ' str TypeError URIError Exception AssertionError IndexError AttributeError KeyError' +
33
- ' ValueError ZeroDivisionError map hex filter zip dict dict_wrap UnicodeDecodeError HTMLCollection' +
47
+ ' ValueError ZeroDivisionError ImportError ModuleNotFoundError StopIteration map hex filter zip dict dict_wrap UnicodeDecodeError HTMLCollection' +
34
48
  ' NodeList alert console Node Symbol NamedNodeMap ρσ_eslice ρσ_delslice Number' +
35
49
  ' Boolean encodeURIComponent decodeURIComponent setTimeout setInterval' +
36
50
  ' setImmediate clearTimeout clearInterval clearImmediate requestAnimationFrame' +
37
- ' id repr sorted __name__ equals get_module ρσ_str jstype divmod NaN super Ellipsis slice'
51
+ ' id repr sorted __name__ equals get_module ρσ_str jstype divmod NaN super Ellipsis slice all any next __import__ ρσ_new ρσ_object_new hash ρσ_object_setattr ρσ_object_getattr ρσ_object_delattr ExceptionGroup BaseExceptionGroup tuple bytes bytearray format object vars locals globals'
38
52
  ).split(' ');
39
53
 
40
54
  // ---------------------------------------------------------------------------
@@ -384,7 +398,13 @@ function Linter(RS, toplevel, code, builtins, knownModules) {
384
398
  };
385
399
 
386
400
  this.handle_symbol_funarg = function() {
387
- this.add_binding(this.current_node.name);
401
+ const node = this.current_node;
402
+ this.add_binding(node.name);
403
+ // Suppress undefined-symbol errors for type names used in argument
404
+ // annotations: `def foo(x: MyType):` — MyType is a hint, not a ref.
405
+ if (node.annotation instanceof RS.AST_SymbolRef) {
406
+ node.annotation.lint_visited = true;
407
+ }
388
408
  };
389
409
 
390
410
  this.handle_comprehension = function() {
@@ -403,24 +423,23 @@ function Linter(RS, toplevel, code, builtins, knownModules) {
403
423
 
404
424
  this.handle_for_in = function() {
405
425
  const node = this.current_node;
426
+ const add_loop_var = (cnode) => {
427
+ if (cnode instanceof RS.AST_Seq) {
428
+ cnode.to_array().forEach(add_loop_var);
429
+ } else if (cnode instanceof RS.AST_Array) {
430
+ cnode.elements.forEach(add_loop_var);
431
+ } else if (cnode instanceof RS.AST_SymbolRef) {
432
+ this.current_node = cnode;
433
+ cnode.lint_visited = true;
434
+ this.add_binding(cnode.name).is_loop = true;
435
+ this.current_node = node;
436
+ }
437
+ };
406
438
  if (node.init instanceof RS.AST_SymbolRef) {
407
439
  this.add_binding(node.init.name).is_loop = true;
408
440
  node.init.lint_visited = true;
409
441
  } else if (node.init instanceof RS.AST_Array) {
410
- node.init.elements.forEach(cnode => {
411
- if (cnode instanceof RS.AST_Seq) cnode = cnode.to_array();
412
- if (cnode instanceof RS.AST_SymbolRef) cnode = [cnode];
413
- if (Array.isArray(cnode)) {
414
- cnode.forEach(elem => {
415
- if (elem instanceof RS.AST_SymbolRef) {
416
- this.current_node = elem;
417
- elem.lint_visited = true;
418
- this.add_binding(elem.name).is_loop = true;
419
- this.current_node = node;
420
- }
421
- });
422
- }
423
- });
442
+ node.init.elements.forEach(add_loop_var);
424
443
  }
425
444
  };
426
445
 
@@ -681,6 +700,8 @@ export class Diagnostics {
681
700
  const sf = options.stdlibFiles;
682
701
  if ((vf && Object.keys(vf).length > 0) || (sf && Object.keys(sf).length > 0)) {
683
702
  knownModules = Object.create(null);
703
+ // Always include built-in stdlib so they never produce bad-import errors.
704
+ Object.assign(knownModules, _STDLIB_MODULE_SET);
684
705
  if (vf) Object.keys(vf).forEach(k => { knownModules[k] = true; });
685
706
  if (sf) Object.keys(sf).forEach(k => { knownModules[k] = true; });
686
707
  }
@@ -2,7 +2,7 @@
2
2
  # License: BSD Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net>
3
3
  from __python__ import hash_literals
4
4
 
5
- from ast import AST_Class, AST_Method, is_node_type
5
+ from ast import AST_Class, AST_Method, AST_AnnotatedAssign, AST_SymbolRef, is_node_type
6
6
  from output.functions import decorate, function_definition, function_annotation
7
7
  from output.utils import create_doctring
8
8
  from utils import has_prop
@@ -82,19 +82,75 @@ def print_class(output):
82
82
  output.with_block(def():
83
83
  output.indent()
84
84
  cname = self.name.name
85
- output.print('if (!(this instanceof ' + cname + ')) return new ' + cname + '(...arguments);')
86
- output.newline()
85
+ if self.has_new:
86
+ # When __new__ is defined, call it to get the instance
87
+ output.print('if (!(this instanceof ' + cname + '))')
88
+ output.space()
89
+ output.with_block(def():
90
+ output.indent()
91
+ output.print('var ρσ_instance = ')
92
+ self.name.print(output)
93
+ output.print('.__new__(')
94
+ self.name.print(output)
95
+ output.print(', ...arguments)')
96
+ output.end_statement()
97
+ output.indent()
98
+ output.print('if (ρσ_instance instanceof ')
99
+ self.name.print(output)
100
+ output.print(')')
101
+ output.space()
102
+ output.with_block(def():
103
+ output.indent()
104
+ output.spaced('if', '(ρσ_instance.ρσ_object_id', '===', 'undefined)', 'Object.defineProperty(ρσ_instance,', '"ρσ_object_id",', '{"value":++ρσ_object_counter})')
105
+ output.end_statement()
106
+ if self.bound.length:
107
+ output.indent()
108
+ self.name.print(output), output.print('.prototype.__bind_methods__ && ')
109
+ self.name.print(output), output.print('.prototype.__bind_methods__.call(ρσ_instance)')
110
+ output.end_statement()
111
+ output.indent()
112
+ self.name.print(output)
113
+ output.print('.prototype.__init__.apply(ρσ_instance'), output.comma(), output.print('arguments)')
114
+ output.end_statement()
115
+ )
116
+ output.newline()
117
+ output.indent()
118
+ output.print('return ρσ_instance')
119
+ output.end_statement()
120
+ )
121
+ output.newline()
122
+ else:
123
+ output.print('if (!(this instanceof ' + cname + ')) return new ' + cname + '(...arguments);')
124
+ output.newline()
87
125
  output.indent()
88
126
  output.spaced('if', '(this.ρσ_object_id', '===', 'undefined)', 'Object.defineProperty(this,', '"ρσ_object_id",', '{"value":++ρσ_object_counter})')
89
127
  output.end_statement()
90
- if self.bound.length:
128
+ if self.has_attr_dunders:
129
+ # Wrap in a Proxy so __getattr__/__setattr__/__delattr__/__getattribute__ are triggered.
91
130
  output.indent()
92
- self.name.print(output), output.print(".prototype.__bind_methods__.call(this)")
131
+ output.print('var ρσ_proxy = ρσ_JS_Proxy ? new ρσ_JS_Proxy(this, ρσ_attr_proxy_handler) : this')
132
+ output.end_statement()
133
+ if self.bound.length:
134
+ output.indent()
135
+ self.name.print(output), output.print(".prototype.__bind_methods__ && ")
136
+ self.name.print(output), output.print(".prototype.__bind_methods__.call(ρσ_proxy)")
137
+ output.end_statement()
138
+ output.indent()
139
+ self.name.print(output)
140
+ output.print(".prototype.__init__.apply(ρσ_proxy"), output.comma(), output.print('arguments)')
141
+ output.end_statement()
142
+ output.indent()
143
+ output.print('return ρσ_proxy')
144
+ output.end_statement()
145
+ else:
146
+ if self.bound.length:
147
+ output.indent()
148
+ self.name.print(output), output.print(".prototype.__bind_methods__.call(this)")
149
+ output.end_statement()
150
+ output.indent()
151
+ self.name.print(output)
152
+ output.print(".prototype.__init__.apply(this"), output.comma(), output.print('arguments)')
93
153
  output.end_statement()
94
- output.indent()
95
- self.name.print(output)
96
- output.print(".prototype.__init__.apply(this"), output.comma(), output.print('arguments)')
97
- output.end_statement()
98
154
  )
99
155
 
100
156
  decorators = self.decorators or v'[]'
@@ -235,6 +291,24 @@ def print_class(output):
235
291
  output.end_statement()
236
292
  )
237
293
 
294
+ if not defined_methods['__format__']:
295
+ define_default_method('__format__', def():
296
+ if self.parent:
297
+ output.print('if('), self.parent.print(output), output.spaced('.prototype.__format__)', 'return', self.parent)
298
+ output.print('.prototype.__format__.call(this, arguments[0])'), output.end_statement()
299
+ output.indent(), output.spaced('if', '(!arguments[0])', 'return', 'this.__str__()')
300
+ output.end_statement()
301
+ output.indent(), output.spaced('throw', 'new TypeError("unsupported format specification")')
302
+ output.end_statement()
303
+ )
304
+
305
+ # Python semantics: defining __eq__ without __hash__ makes the class unhashable
306
+ if defined_methods['__eq__'] and not defined_methods['__hash__']:
307
+ output.indent()
308
+ self.name.print(output)
309
+ output.print('.prototype.__hash__ = null')
310
+ output.end_statement()
311
+
238
312
  # Multiple inheritance
239
313
  add_hidden_property('__bases__', def():
240
314
  output.print('[')
@@ -256,22 +330,10 @@ def print_class(output):
256
330
  output.print('.prototype, "__class__", {get: function() { return this.constructor; }, configurable: true})')
257
331
  output.end_statement()
258
332
 
259
- if self.bases.length > 1:
260
- output.indent()
261
- output.print("ρσ_mixin(")
262
- self.name.print(output)
263
- for v'var i = 1; i < self.bases.length; i++':
264
- output.comma()
265
- self.bases[i].print(output)
266
- output.print(')'), output.end_statement()
267
-
268
- # Docstring
269
- if self.docstrings and self.docstrings.length and output.options.keep_docstrings:
270
- add_hidden_property('__doc__', def():
271
- output.print(JSON.stringify(create_doctring(self.docstrings)))
272
- )
273
-
274
- # Other statements in the class context (including nested class definitions)
333
+ # Other statements in the class context (including nested class definitions).
334
+ # Emitted BEFORE __init_subclass__ so that the hook can see class variables.
335
+ # This matches Python's behaviour: the class body executes first, then the
336
+ # hook is called with the fully-populated class namespace.
275
337
  for stmt in self.statements:
276
338
  if is_node_type(stmt, AST_Class):
277
339
  # Print the nested class in full, then attach it to the outer class.
@@ -295,6 +357,80 @@ def print_class(output):
295
357
  stmt.print(output)
296
358
  output.newline()
297
359
 
360
+ # Emit __annotations__ for annotated class variables so that @dataclass and
361
+ # similar decorators can introspect field names and their declaration order.
362
+ # Only annotation-with-value statements (x: T = v) AND annotation-only
363
+ # statements (x: T) are collected; the value (if any) is already emitted
364
+ # above as a prototype assignment. We store null as the type since type
365
+ # information is erased in the JS output.
366
+ annotated_field_names = []
367
+ for stmt in self.statements:
368
+ if is_node_type(stmt, AST_AnnotatedAssign) and is_node_type(stmt.target, AST_SymbolRef):
369
+ annotated_field_names.push(stmt.target.name)
370
+ if annotated_field_names.length:
371
+ output.indent()
372
+ self.name.print(output)
373
+ output.print('.__annotations__ = {')
374
+ for i in range(annotated_field_names.length):
375
+ if i > 0:
376
+ output.comma()
377
+ output.print(JSON.stringify(annotated_field_names[i]) + ': null')
378
+ output.print('}')
379
+ output.end_statement()
380
+
381
+ # __init_subclass__ hook: call after identity properties and class variables
382
+ # so cls.__name__ is correct and cls.prototype reflects the full class body.
383
+ if self.parent:
384
+ output.indent()
385
+ output.print('if (typeof ')
386
+ self.parent.print(output)
387
+ output.print('.__init_subclass__ === "function")')
388
+ output.space()
389
+ kws = self.class_kwargs
390
+ if kws and kws.length:
391
+ output.with_block(def():
392
+ output.indent()
393
+ output.print('var ρσ_isc_kw = {')
394
+ for v'var ρσ_kwi = 0; ρσ_kwi < kws.length; ρσ_kwi++':
395
+ if ρσ_kwi > 0:
396
+ output.comma()
397
+ output.print(kws[ρσ_kwi][0].name + ':')
398
+ output.space()
399
+ kws[ρσ_kwi][1].print(output)
400
+ output.print('}')
401
+ output.end_statement()
402
+ output.indent()
403
+ output.print('ρσ_isc_kw[ρσ_kwargs_symbol] = true')
404
+ output.end_statement()
405
+ output.indent()
406
+ self.parent.print(output)
407
+ output.print('.__init_subclass__.call(')
408
+ self.name.print(output)
409
+ output.print(', ρσ_isc_kw)')
410
+ output.end_statement()
411
+ )
412
+ else:
413
+ self.parent.print(output)
414
+ output.print('.__init_subclass__.call(')
415
+ self.name.print(output)
416
+ output.print(')')
417
+ output.end_statement()
418
+
419
+ if self.bases.length > 1:
420
+ output.indent()
421
+ output.print("ρσ_mixin(")
422
+ self.name.print(output)
423
+ for v'var i = 1; i < self.bases.length; i++':
424
+ output.comma()
425
+ self.bases[i].print(output)
426
+ output.print(')'), output.end_statement()
427
+
428
+ # Docstring
429
+ if self.docstrings and self.docstrings.length and output.options.keep_docstrings:
430
+ add_hidden_property('__doc__', def():
431
+ output.print(JSON.stringify(create_doctring(self.docstrings)))
432
+ )
433
+
298
434
  if decorators.length:
299
435
  output.indent()
300
436
  output.assign(self.name)
@@ -13,13 +13,14 @@ from ast import (
13
13
  AST_EmptyStatement, AST_Exit, AST_ExpressiveObject, AST_ForIn,
14
14
  AST_ForJS, AST_Function, AST_Hole, AST_If, AST_Imports, AST_Infinity,
15
15
  AST_Lambda, AST_ListComprehension, AST_LoopControl, AST_NaN, AST_NamedExpr, AST_New, AST_Node,
16
- AST_Number, AST_Object, AST_ObjectKeyVal, AST_ObjectProperty, AST_ObjectSpread, AST_PropAccess,
16
+ AST_Number, AST_Object, AST_ObjectKeyVal, AST_ObjectProperty, AST_ObjectSpread, AST_Spread, AST_PropAccess,
17
17
  AST_RegExp, AST_Return, AST_Set, AST_Seq, AST_SimpleStatement, AST_Splice,
18
18
  AST_Statement, AST_StatementWithBody, AST_String, AST_Sub, AST_ItemAccess,
19
19
  AST_Symbol, AST_This, AST_Throw, AST_Toplevel, AST_Try, AST_Unary,
20
20
  AST_UnaryPrefix, AST_Undefined, AST_Var, AST_VarDef, AST_Assert,
21
21
  AST_Verbatim, AST_While, AST_With, AST_Yield, AST_Await, TreeWalker, AST_Existential,
22
- AST_Match, AST_AnnotatedAssign, AST_Super
22
+ AST_Match, AST_AnnotatedAssign, AST_Super,
23
+ AST_JSXElement, AST_JSXFragment, AST_JSXAttribute, AST_JSXSpread, AST_JSXText, AST_JSXExprContainer
23
24
  )
24
25
  from output.exceptions import print_try
25
26
  from output.classes import print_class
@@ -34,6 +35,7 @@ from output.operators import (
34
35
  )
35
36
  from output.functions import print_function, print_function_call
36
37
  from output.statements import print_bracketed, first_in_statement, force_statement, print_with, print_assert, print_match, print_annotated_assign
38
+ from output.jsx import print_jsx_element, print_jsx_fragment, print_jsx_attribute, print_jsx_spread, print_jsx_text, print_jsx_expr_container
37
39
  from output.utils import make_block, make_num
38
40
 
39
41
  # -----[ code generators ]-----
@@ -486,6 +488,10 @@ def generate_code():
486
488
  output.print('**')
487
489
  self.value.print(output)
488
490
  )
491
+ DEFPRINT(AST_Spread, def(self, output):
492
+ output.print('...')
493
+ self.expression.print(output)
494
+ )
489
495
  DEFPRINT(AST_Set, print_set)
490
496
 
491
497
  AST_Symbol.prototype.definition = def():
@@ -525,3 +531,11 @@ def generate_code():
525
531
  output.print(make_num(self.value))
526
532
  )
527
533
  DEFPRINT(AST_RegExp, print_regexp)
534
+
535
+ # -----[ JSX ]-----
536
+ DEFPRINT(AST_JSXElement, print_jsx_element)
537
+ DEFPRINT(AST_JSXFragment, print_jsx_fragment)
538
+ DEFPRINT(AST_JSXAttribute, print_jsx_attribute)
539
+ DEFPRINT(AST_JSXSpread, print_jsx_spread)
540
+ DEFPRINT(AST_JSXText, print_jsx_text)
541
+ DEFPRINT(AST_JSXExprContainer, print_jsx_expr_container)
@@ -2,7 +2,7 @@
2
2
  # License: BSD Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net>
3
3
  from __python__ import hash_literals
4
4
 
5
- from output.statements import print_bracketed
5
+ from output.statements import print_bracketed, display_body
6
6
 
7
7
 
8
8
  def print_try(self, output):
@@ -28,6 +28,10 @@ def print_try(self, output):
28
28
 
29
29
 
30
30
  def print_catch(self, output):
31
+ # Dispatch to except* handler when any clause is an exception group handler
32
+ if self.body.length and self.body[0].is_star:
33
+ print_catch_star(self, output)
34
+ return
31
35
  output.print("catch")
32
36
  output.space()
33
37
  output.with_parens(def():
@@ -83,6 +87,98 @@ def print_catch(self, output):
83
87
  )
84
88
 
85
89
 
90
+ def _eg_type_cond(errors, varname):
91
+ # Build a JS condition string testing varname against each error type
92
+ parts = []
93
+ for err in errors:
94
+ if err.name is 'Exception':
95
+ parts.push(varname + " instanceof Error")
96
+ else:
97
+ parts.push(varname + " instanceof " + err.name)
98
+ return parts.join(" || ")
99
+
100
+
101
+ def print_catch_star(self, output):
102
+ output.print("catch")
103
+ output.space()
104
+ output.with_parens(def():
105
+ output.print("ρσ_Exception")
106
+ )
107
+ output.space()
108
+ output.with_block(def():
109
+ output.indent()
110
+ output.spaced('ρσ_last_exception', '=', 'ρσ_Exception'), output.end_statement()
111
+
112
+ # Normalise: wrap non-ExceptionGroup exception into a one-element array
113
+ output.indent()
114
+ output.print("var ρσ_eg_exceptions = (ρσ_Exception instanceof ExceptionGroup) ? ρσ_Exception.exceptions.slice() : [ρσ_Exception]")
115
+ output.semicolon(), output.newline()
116
+ output.indent()
117
+ output.print("var ρσ_eg_raised = []")
118
+ output.semicolon(), output.newline()
119
+
120
+ for ei, exception in enumerate(self.body):
121
+ idx = str(ei)
122
+ if exception.errors.length:
123
+ cond = _eg_type_cond(exception.errors, "ρσ_et")
124
+ output.indent()
125
+ output.print("var ρσ_eg_matched_" + idx + " = ρσ_eg_exceptions.filter(function(ρσ_et) { return " + cond + "; })")
126
+ output.semicolon(), output.newline()
127
+ output.indent()
128
+ output.print("ρσ_eg_exceptions = ρσ_eg_exceptions.filter(function(ρσ_et) { return !(" + cond + "); })")
129
+ output.semicolon(), output.newline()
130
+ else:
131
+ # Bare except*: take all remaining
132
+ output.indent()
133
+ output.print("var ρσ_eg_matched_" + idx + " = ρσ_eg_exceptions.slice()")
134
+ output.semicolon(), output.newline()
135
+ output.indent()
136
+ output.print("ρσ_eg_exceptions = []")
137
+ output.semicolon(), output.newline()
138
+
139
+ output.indent()
140
+ output.print("if (ρσ_eg_matched_" + idx + ".length > 0)")
141
+ output.space()
142
+ output.with_block(def():
143
+ if exception.argname:
144
+ output.indent()
145
+ output.print("var ")
146
+ output.assign(exception.argname)
147
+ output.print("(ρσ_Exception instanceof ExceptionGroup) ? new ExceptionGroup(ρσ_Exception.message, ρσ_eg_matched_" + idx + ") : ρσ_eg_matched_" + idx + "[0]")
148
+ output.semicolon(), output.newline()
149
+ output.indent()
150
+ output.print("try")
151
+ output.space()
152
+ output.with_block(def():
153
+ display_body(exception.body, False, output)
154
+ )
155
+ output.print(" catch (ρσ_eg_exc_" + idx + ")")
156
+ output.space()
157
+ output.with_block(def():
158
+ output.indent()
159
+ output.print("ρσ_eg_raised.push(ρσ_eg_exc_" + idx + ")")
160
+ output.semicolon(), output.newline()
161
+ )
162
+ output.newline()
163
+ )
164
+ output.newline()
165
+
166
+ # Re-raise unmatched exceptions and any raised by handlers
167
+ output.indent()
168
+ output.print("var ρσ_eg_unhandled = ρσ_eg_exceptions.concat(ρσ_eg_raised)")
169
+ output.semicolon(), output.newline()
170
+ output.indent()
171
+ output.print("if (ρσ_eg_unhandled.length > 0)")
172
+ output.space()
173
+ output.with_block(def():
174
+ output.indent()
175
+ output.print("throw (ρσ_eg_unhandled.length === 1 && !(ρσ_Exception instanceof ExceptionGroup)) ? ρσ_eg_unhandled[0] : new ExceptionGroup((ρσ_Exception instanceof ExceptionGroup) ? ρσ_Exception.message : \"unhandled exceptions\", ρσ_eg_unhandled)")
176
+ output.semicolon(), output.newline()
177
+ )
178
+ output.newline()
179
+ )
180
+
181
+
86
182
  def print_finally(self, output, belse, else_var_name):
87
183
  output.print("finally")
88
184
  output.space()
@@ -2,8 +2,9 @@
2
2
  # License: BSD Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net>
3
3
  from __python__ import hash_literals
4
4
 
5
- from ast import AST_ClassCall, AST_New, has_calls, AST_Dot, AST_SymbolRef, is_node_type
5
+ from ast import AST_ClassCall, AST_New, has_calls, AST_Dot, AST_SymbolRef, AST_String, is_node_type
6
6
  from output.stream import OutputStream
7
+ from parse import parse as ρσ_rs_parse
7
8
  from output.statements import print_bracketed
8
9
  from output.utils import create_doctring
9
10
  from output.operators import print_getattr
@@ -150,6 +151,15 @@ def function_preamble(node, output, offset):
150
151
  output.print('.pop()')
151
152
  output.end_statement()
152
153
 
154
+ # Convert the plain JS kwargs object to a proper Python dict so that
155
+ # kw.items(), kw.keys(), etc. work in the function body.
156
+ # This must happen AFTER defaults/kwonly/starargs processing which rely on
157
+ # plain property access (kw.name) and ρσ_kwargs_symbol checks.
158
+ if a.kwargs:
159
+ output.indent()
160
+ output.spaced(kw, '=', 'ρσ_kwargs_to_dict(' + kw + ')')
161
+ output.end_statement()
162
+
153
163
  def has_annotations(self):
154
164
  if self.return_annotation:
155
165
  return True
@@ -440,6 +450,73 @@ def print_function_call(self, output):
440
450
  output.comma()
441
451
  a.print(output)
442
452
  )
453
+ elif (is_node_type(self.expression, AST_SymbolRef) and
454
+ self.expression.name is 'vars' and self.args.length is 0):
455
+ # vars() with no args → vars(this) so it captures the caller's instance
456
+ output.print('ρσ_vars')
457
+ output.with_parens(def():
458
+ output.print('this')
459
+ )
460
+ elif (is_node_type(self.expression, AST_SymbolRef) and
461
+ (self.expression.name is 'eval' or self.expression.name is 'exec') and
462
+ self.args.length >= 1 and is_node_type(self.args[0], AST_String)):
463
+ # Compile-time RapydScript → JS transformation for eval/exec string literals.
464
+ # The RapydScript source string is parsed and compiled to JS now; the emitted
465
+ # call receives the compiled JS string instead of the original RapydScript.
466
+ fn_name = self.expression.name
467
+ source = self.args[0].value
468
+ inner_ast = ρσ_rs_parse(source, {
469
+ 'filename': '<eval>',
470
+ 'module_id': '__eval__',
471
+ 'for_linting': True,
472
+ 'discard_asserts': output.options.discard_asserts,
473
+ })
474
+ inner_os = OutputStream({
475
+ 'beautify': False,
476
+ 'js_version': output.options.js_version,
477
+ 'private_scope': False,
478
+ 'write_name': False,
479
+ 'omit_baselib': True,
480
+ 'discard_asserts': output.options.discard_asserts,
481
+ })
482
+ inner_ast.print(inner_os)
483
+ compiled_js = v'inner_os.get().trim()'
484
+
485
+ def _print_extra_args():
486
+ for a in self.args.slice(1):
487
+ output.comma()
488
+ a.print(output)
489
+
490
+ if fn_name is 'eval' and self.args.length is 1:
491
+ # eval("expr") → native direct eval for scope access
492
+ output.print('eval')
493
+ output.with_parens(def():
494
+ output.print(JSON.stringify(compiled_js))
495
+ )
496
+ elif fn_name is 'eval':
497
+ # eval("expr", globals, locals) → ρσ_eval(compiled_js, globals, locals)
498
+ output.print('ρσ_eval')
499
+ output.with_parens(def():
500
+ output.print(JSON.stringify(compiled_js))
501
+ _print_extra_args()
502
+ )
503
+ else:
504
+ # exec("code", ...) → ρσ_exec(compiled_js, ...)
505
+ output.print('ρσ_exec')
506
+ output.with_parens(def():
507
+ output.print(JSON.stringify(compiled_js))
508
+ _print_extra_args()
509
+ )
510
+ elif is_node_type(self.expression, AST_SymbolRef) and self.expression.name is 'eval' and self.args.length > 1:
511
+ # eval(dynamic_expr, globals, locals) → ρσ_eval(dynamic_expr, globals, locals)
512
+ # The 1-arg form eval(expr) is left as native JS direct eval for scope access.
513
+ output.print('ρσ_eval')
514
+ output.with_parens(def():
515
+ for i, a in enumerate(self.args):
516
+ if i:
517
+ output.comma()
518
+ a.print(output)
519
+ )
443
520
  elif is_node_type(self.expression, AST_SymbolRef) and self.python_truthiness:
444
521
  # __call__-aware dispatch when from __python__ import truthiness is active
445
522
  output.print('ρσ_callable_call(')
@@ -452,10 +529,15 @@ def print_function_call(self, output):
452
529
  # Method calls and other complex expressions — keep existing behaviour
453
530
  print_function_name()
454
531
  output.with_parens(def():
455
- for i, a in enumerate(self.args):
456
- if i:
457
- output.comma()
458
- a.print(output)
532
+ # .split() with no args: default to ' ' so JS splits on space
533
+ # rather than returning [whole_string] (native JS no-arg behavior)
534
+ if is_node_type(self.expression, AST_Dot) and self.expression.property is 'split' and not self.args.length:
535
+ output.print('" "')
536
+ else:
537
+ for i, a in enumerate(self.args):
538
+ if i:
539
+ output.comma()
540
+ a.print(output)
459
541
  )
460
542
  return
461
543