rapydscript-ng 0.7.24 → 0.8.1

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 (132) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/README.md +80 -26
  3. package/bin/build.ts +117 -0
  4. package/bin/package.json +1 -0
  5. package/bin/rapydscript +65 -62
  6. package/build_wheels.py +133 -0
  7. package/editor-plugins/README.md +80 -0
  8. package/editor-plugins/nvim.lua +321 -0
  9. package/local-agent.md +28 -0
  10. package/package.json +4 -5
  11. package/publish.py +27 -17
  12. package/release/baselib-plain-pretty.js +448 -326
  13. package/release/baselib-plain-ugly.js +3 -3
  14. package/release/compiler.js +9027 -8139
  15. package/release/signatures.json +27 -27
  16. package/release/stdlib_modules.json +1 -0
  17. package/src/ast.pyj +428 -280
  18. package/src/baselib-builtins.pyj +49 -28
  19. package/src/baselib-containers.pyj +241 -226
  20. package/src/baselib-errors.pyj +8 -1
  21. package/src/baselib-internal.pyj +38 -23
  22. package/src/baselib-itertools.pyj +13 -7
  23. package/src/baselib-str.pyj +59 -84
  24. package/src/compiler.pyj +4 -4
  25. package/src/errors.pyj +3 -4
  26. package/src/lib/aes.pyj +46 -32
  27. package/src/lib/elementmaker.pyj +11 -9
  28. package/src/lib/encodings.pyj +15 -5
  29. package/src/lib/gettext.pyj +9 -4
  30. package/src/lib/math.pyj +106 -45
  31. package/src/lib/operator.pyj +10 -10
  32. package/src/lib/pythonize.pyj +2 -1
  33. package/src/lib/random.pyj +28 -21
  34. package/src/lib/re.pyj +492 -19
  35. package/src/lib/traceback.pyj +171 -29
  36. package/src/lib/uuid.pyj +2 -2
  37. package/src/output/classes.pyj +28 -27
  38. package/src/output/codegen.pyj +87 -83
  39. package/src/output/comments.pyj +8 -8
  40. package/src/output/exceptions.pyj +20 -21
  41. package/src/output/functions.pyj +66 -64
  42. package/src/output/literals.pyj +24 -23
  43. package/src/output/loops.pyj +84 -142
  44. package/src/output/modules.pyj +55 -68
  45. package/src/output/operators.pyj +40 -29
  46. package/src/output/statements.pyj +21 -15
  47. package/src/output/stream.pyj +82 -56
  48. package/src/output/utils.pyj +12 -8
  49. package/src/parse.pyj +971 -620
  50. package/src/string_interpolation.pyj +5 -3
  51. package/src/tokenizer.pyj +188 -148
  52. package/src/utils.pyj +32 -15
  53. package/test/_treeshake_mod.pyj +30 -0
  54. package/test/_treeshake_side_effect.pyj +9 -0
  55. package/test/ast_serialization.pyj +392 -0
  56. package/test/async.pyj +95 -0
  57. package/test/baselib.pyj +1 -2
  58. package/test/embedded_compiler.pyj +57 -0
  59. package/test/fmt.pyj +291 -0
  60. package/test/generic.pyj +16 -3
  61. package/test/imports.pyj +8 -6
  62. package/test/internationalization.pyj +38 -37
  63. package/test/lint.pyj +120 -117
  64. package/test/lsp.pyj +257 -0
  65. package/test/repl.pyj +70 -65
  66. package/test/sourcemaps.pyj +315 -0
  67. package/test/starargs.pyj +46 -39
  68. package/test/str.pyj +8 -0
  69. package/test/traceback.pyj +263 -0
  70. package/test/treeshaking.pyj +181 -0
  71. package/test/web_repl_export.pyj +88 -0
  72. package/tools/ast_serialize.mjs +557 -0
  73. package/tools/cli.mjs +531 -0
  74. package/tools/compile.mjs +205 -0
  75. package/tools/compiler.mjs +159 -0
  76. package/tools/{completer.js → completer.mjs} +6 -6
  77. package/tools/{embedded_compiler.js → embedded_compiler.mjs} +17 -13
  78. package/tools/export.js +109 -56
  79. package/tools/fmt.mjs +975 -0
  80. package/tools/{gettext.js → gettext.mjs} +46 -53
  81. package/tools/ini.mjs +175 -0
  82. package/tools/{lint.js → lint.mjs} +78 -55
  83. package/tools/lsp.mjs +964 -0
  84. package/tools/lsp_protocol.mjs +259 -0
  85. package/tools/lsp_symbols.mjs +418 -0
  86. package/tools/{msgfmt.js → msgfmt.mjs} +18 -18
  87. package/tools/{repl.js → repl.mjs} +56 -42
  88. package/tools/{self.js → self.mjs} +71 -46
  89. package/tools/sourcemap.mjs +123 -0
  90. package/tools/test.mjs +252 -0
  91. package/tools/treeshake.mjs +400 -0
  92. package/tools/{utils.js → utils.mjs} +26 -23
  93. package/tools/{web_repl.js → web_repl.mjs} +15 -13
  94. package/tools/web_repl_export.mjs +97 -0
  95. package/tree-sitter/README.md +101 -0
  96. package/tree-sitter/grammar.js +992 -0
  97. package/tree-sitter/package.json +43 -0
  98. package/tree-sitter/queries/highlights.scm +232 -0
  99. package/tree-sitter/queries/indents.scm +64 -0
  100. package/tree-sitter/queries/injections.scm +14 -0
  101. package/tree-sitter/queries/locals.scm +108 -0
  102. package/tree-sitter/src/grammar.json +4976 -0
  103. package/tree-sitter/src/node-types.json +2901 -0
  104. package/tree-sitter/src/parser.c +196465 -0
  105. package/tree-sitter/src/scanner.c +294 -0
  106. package/tree-sitter/src/tree_sitter/alloc.h +54 -0
  107. package/tree-sitter/src/tree_sitter/array.h +330 -0
  108. package/tree-sitter/src/tree_sitter/parser.h +286 -0
  109. package/tree-sitter/test/corpus/chaining.txt +99 -0
  110. package/tree-sitter/test/corpus/classes.txt +147 -0
  111. package/tree-sitter/test/corpus/comprehensions.txt +155 -0
  112. package/tree-sitter/test/corpus/containers.txt +242 -0
  113. package/tree-sitter/test/corpus/control_flow.txt +361 -0
  114. package/tree-sitter/test/corpus/decorators.txt +64 -0
  115. package/tree-sitter/test/corpus/existential.txt +102 -0
  116. package/tree-sitter/test/corpus/functions.txt +293 -0
  117. package/tree-sitter/test/corpus/imports.txt +117 -0
  118. package/tree-sitter/test/corpus/literals.txt +135 -0
  119. package/tree-sitter/test/corpus/operators.txt +296 -0
  120. package/tree-sitter/test/corpus/statements.txt +189 -0
  121. package/tree-sitter/test/corpus/strings.txt +90 -0
  122. package/tree-sitter/test/corpus/subscripts.txt +227 -0
  123. package/tree-sitter/tree-sitter.json +36 -0
  124. package/web-repl/env.js +35 -23
  125. package/web-repl/main.js +8 -8
  126. package/bin/export +0 -75
  127. package/bin/web-repl-export +0 -102
  128. package/tools/cli.js +0 -523
  129. package/tools/compile.js +0 -184
  130. package/tools/compiler.js +0 -84
  131. package/tools/ini.js +0 -65
  132. package/tools/test.js +0 -110
package/src/utils.pyj CHANGED
@@ -4,27 +4,32 @@ from __python__ import hash_literals
4
4
 
5
5
  has_prop = Object.prototype.hasOwnProperty.call.bind(Object.prototype.hasOwnProperty)
6
6
 
7
+
7
8
  def array_to_hash(a):
8
9
  ret = Object.create(None)
9
10
  for i in range(len(a)):
10
11
  ret[a[i]] = True
11
12
  return ret
12
13
 
14
+
13
15
  def slice(a, start):
14
16
  return Array.prototype.slice.call(a, start or 0)
15
17
 
18
+
16
19
  def characters(str_):
17
- return str_.split("")
20
+ return str_.split('')
21
+
18
22
 
19
23
  def member(name, array):
20
- for i in range(array.length-1, -1, -1):
24
+ for i in range(array.length - 1, -1, -1):
21
25
  if array[i] is name:
22
26
  return True
23
27
  return False
24
28
 
29
+
25
30
  def repeat_string(str_, i):
26
31
  if i <= 0:
27
- return ""
32
+ return ''
28
33
  if i is 1:
29
34
  return str_
30
35
  d = repeat_string(str_, i >> 1)
@@ -33,11 +38,12 @@ def repeat_string(str_, i):
33
38
  d += str_
34
39
  return d
35
40
 
36
- class DefaultsError(ValueError):
37
41
 
42
+ class DefaultsError(ValueError):
38
43
  def __init__(self, name, defs):
39
44
  ValueError.__init__(self, name + ' is not a supported option. Supported options are: ' + str(Object.keys(defs)))
40
45
 
46
+
41
47
  def defaults(args, defs, croak):
42
48
  if args is True:
43
49
  args = {}
@@ -51,15 +57,17 @@ def defaults(args, defs, croak):
51
57
  ret[i] = args[i] if args and has_prop(args, i) else defs[i]
52
58
  return ret
53
59
 
60
+
54
61
  def merge(obj, ext):
55
62
  for i in ext:
56
63
  obj[i] = ext[i]
57
64
  return obj
58
65
 
66
+
59
67
  def noop():
60
68
  pass
61
69
 
62
- MAP = def():
70
+ MAP = def ():
63
71
  def MAP(a, f, backwards):
64
72
  ret = []
65
73
  top = []
@@ -84,7 +92,7 @@ MAP = def():
84
92
 
85
93
  if Array.isArray(a):
86
94
  if backwards:
87
- for i in range(a.length-1, -1, -1):
95
+ for i in range(a.length - 1, -1, -1):
88
96
  if doit():
89
97
  break
90
98
  ret.reverse()
@@ -99,14 +107,15 @@ MAP = def():
99
107
  break
100
108
  return top.concat(ret)
101
109
 
102
- MAP.at_top = def(val):
110
+ MAP.at_top = def (val):
103
111
  return new AtTop(val)
104
- MAP.splice = def(val):
112
+ MAP.splice = def (val):
105
113
  return new Splice(val)
106
- MAP.last = def(val):
114
+ MAP.last = def (val):
107
115
  return new Last(val)
108
116
 
109
117
  skip = MAP.skip = {}
118
+
110
119
  def AtTop(val):
111
120
  this.v = val
112
121
 
@@ -119,20 +128,24 @@ MAP = def():
119
128
  return MAP
120
129
  .call(this)
121
130
 
131
+
122
132
  def push_uniq(array, el):
123
133
  if array.indexOf(el) < 0:
124
134
  array.push(el)
125
135
 
136
+
126
137
  def string_template(text, props):
127
- return text.replace(/\{(.+?)\}/g, def(str_, p):
138
+ return text.replace(/\{(.+?)\}/g, def (str_, p):
128
139
  return props[p]
129
140
  )
130
141
 
142
+
131
143
  def remove(array, el):
132
- for i in range(array.length-1, -1, -1):
144
+ for i in range(array.length - 1, -1, -1):
133
145
  if array[i] is el:
134
146
  array.splice(i, 1)
135
147
 
148
+
136
149
  def mergeSort(array, cmp):
137
150
  if array.length < 2:
138
151
  return array.slice()
@@ -167,26 +180,30 @@ def mergeSort(array, cmp):
167
180
  return merge(left, right)
168
181
  return _ms(array)
169
182
 
183
+
170
184
  def set_difference(a, b):
171
- return a.filter(def(el):
185
+ return a.filter(def (el):
172
186
  return b.indexOf(el) < 0
173
187
  )
174
188
 
189
+
175
190
  def set_intersection(a, b):
176
- return a.filter(def(el):
191
+ return a.filter(def (el):
177
192
  return b.indexOf(el) >= 0
178
193
  )
179
194
 
195
+
180
196
  def make_predicate(words):
181
197
  if jstype(words) is 'string':
182
- words = words.split(" ")
198
+ words = words.split(' ')
183
199
  a = Object.create(None)
184
200
  for k in words:
185
201
  a[k] = True
186
202
  return a
187
203
 
204
+
188
205
  def cache_file_name(src, cache_dir):
189
206
  if cache_dir:
190
207
  src = str.replace(src, '\\', '/')
191
- return cache_dir + '/' + str.lstrip(str.replace(src, '/', '-') + '.json', '-')
208
+ return cache_dir + '/' + str.lstrip(str.replace(src, '/', '-') + '.bin', '-')
192
209
  return src + '-cached'
@@ -0,0 +1,30 @@
1
+ # Helper module for tree shaking tests
2
+
3
+ def func_used():
4
+ return 'used'
5
+
6
+ def func_unused():
7
+ return 'unused'
8
+
9
+ class ClassUsed:
10
+ def __init__(self):
11
+ self.val = 'used_class'
12
+
13
+ class ClassUnused:
14
+ def __init__(self):
15
+ self.val = 'unused_class'
16
+
17
+ module_var = 'module_var_value'
18
+
19
+ def helper_a():
20
+ return 'a'
21
+
22
+ def helper_b():
23
+ return helper_a()
24
+
25
+ @no_prune
26
+ def pinned_in_mod():
27
+ return 'pinned'
28
+
29
+ def not_pinned_in_mod():
30
+ return 'not_pinned'
@@ -0,0 +1,9 @@
1
+ # vim:fileencoding=utf-8
2
+ # Helper module for tree shaking side-effect import tests.
3
+ # Mirrors the calibre initialize.pyj pattern: a module-level function call
4
+ # that patches a global object (here String.prototype) as a side effect.
5
+
6
+ def apply_patch():
7
+ v'String.prototype.__treeshake_test = function() { return "side_effect_ran"; }'
8
+
9
+ apply_patch()
@@ -0,0 +1,392 @@
1
+ # vim:fileencoding=utf-8
2
+ # Test AST serialization/deserialization round-trip.
3
+ # Verifies that ast_to_json → ast_from_json produces an AST that generates
4
+ # identical JavaScript output to the original parsed AST.
5
+ # globals: assrt, RapydScript, fs, compiler_dir
6
+
7
+ # ─── Helpers ──────────────────────────────────────────────────────────────────
8
+
9
+ baselib = fs.readFileSync(compiler_dir + '/baselib-plain-pretty.js', {'encoding': 'utf-8'})
10
+
11
+ def make_output():
12
+ return new RapydScript.OutputStream({
13
+ 'baselib_plain': baselib,
14
+ 'beautify': True,
15
+ 'js_version': 6,
16
+ 'write_name': False,
17
+ 'private_scope': False,
18
+ 'keep_docstrings': False,
19
+ })
20
+
21
+ def ast_to_js(ast):
22
+ out = make_output()
23
+ ast.print(out)
24
+ return out.toString()
25
+
26
+ async def round_trip(src, label):
27
+ """Parse src, generate js1, serialize+deserialize AST, generate js2, assert equal."""
28
+ ast1 = await RapydScript.parse(src, {'filename': label})
29
+ js1 = ast_to_js(ast1)
30
+
31
+ serialized = RapydScript.ast_to_json(ast1)
32
+ assrt.ok(serialized and jstype(serialized) is 'object', label + ': ast_to_json returned an object')
33
+
34
+ ast2 = RapydScript.ast_from_json(serialized)
35
+ js2 = ast_to_js(ast2)
36
+
37
+ assrt.equal(js2, js1, label + ': round-trip JS output mismatch')
38
+
39
+ async def assert_json_valid(src, label):
40
+ """Verify ast_to_json produces a well-formed AST object."""
41
+ ast = await RapydScript.parse(src, {'filename': label})
42
+ data = RapydScript.ast_to_json(ast)
43
+ assrt.equal(data.v, 1, label + ': version field')
44
+ assrt.ok(Array.isArray(data.nodes), label + ': nodes is array')
45
+ assrt.equal(data.root, 0, label + ': root is 0')
46
+ assrt.ok(data.nodes.length > 0, label + ': pool is non-empty')
47
+
48
+ async def run_tests():
49
+ # ─── Test 1: Basic literals and atoms ─────────────────────────────────────────
50
+ # Exercises: AST_Number, AST_String, AST_True, AST_False, AST_Null,
51
+ # AST_NaN, AST_Infinity, AST_Undefined, AST_SimpleStatement, AST_Assign
52
+
53
+ await round_trip("""
54
+ a = None
55
+ b = True
56
+ c = False
57
+ d = 42
58
+ e = 3.14
59
+ f = "hello world"
60
+ g = ''
61
+ """, 'literals')
62
+
63
+ # ─── Test 2: RegExp literal (special serialization) ───────────────────────────
64
+ # Exercises: AST_RegExp with source and flags
65
+
66
+ await round_trip("""
67
+ r1 = /[a-z]+/
68
+ r2 = /[A-Z]+/gi
69
+ r3 = /^\\d{3}-\\d{4}$/m
70
+ """, 'regexp')
71
+
72
+ # ─── Test 3: Operators ─────────────────────────────────────────────────────────
73
+ # Exercises: AST_Binary, AST_UnaryPrefix, AST_Assign, AST_Conditional
74
+
75
+ await round_trip("""
76
+ x = 1 + 2 * 3 - 4 / 5
77
+ y = x ** 2
78
+ z = x // 3
79
+ a = True and False or not True
80
+ b = 1 if a else 2
81
+ c = x > 0 and x < 10
82
+ """, 'operators')
83
+
84
+ # ─── Test 4: Collections ──────────────────────────────────────────────────────
85
+ # Exercises: AST_Array, AST_Object, AST_ObjectKeyVal, AST_Set, AST_SetItem
86
+
87
+ await round_trip("""
88
+ arr = [1, 2, 3, "four"]
89
+ obj = {'a': 1, 'b': True, 'c': None}
90
+ st = {1, 2, 3}
91
+ nested = [[1, 2], [3, 4]]
92
+ """, 'collections')
93
+
94
+ # ─── Test 5: Function definitions ─────────────────────────────────────────────
95
+ # Exercises: AST_Function, AST_ArgsDef (simple + defaults + *args + **kwargs),
96
+ # AST_Return, AST_SymbolFunarg, AST_SymbolDefun, AST_Decorator
97
+
98
+ await round_trip("""
99
+ def simple(x):
100
+ return x
101
+
102
+ def with_defaults(a, b=10, c="hi"):
103
+ return a + b
104
+
105
+ def with_star(a, *args, **kwargs):
106
+ return args
107
+
108
+ def annotated(x, y=0):
109
+ pass
110
+
111
+ def no_args():
112
+ return None
113
+
114
+ def decorator_fn(f):
115
+ return f
116
+
117
+ @decorator_fn
118
+ def decorated(x):
119
+ return x * 2
120
+ """, 'functions')
121
+
122
+ # ─── Test 6: Classes ──────────────────────────────────────────────────────────
123
+ # Exercises: AST_Class, AST_Method (static/getter/setter), AST_SymbolDeclaration,
124
+ # AST_This, dynamic_properties, classvars
125
+
126
+ await round_trip("""
127
+ class Animal:
128
+ count = 0
129
+
130
+ def __init__(self, name):
131
+ self.name = name
132
+ Animal.count += 1
133
+
134
+ def speak(self):
135
+ return "..."
136
+
137
+ @staticmethod
138
+ def create(name):
139
+ return Animal(name)
140
+
141
+ class Dog(Animal):
142
+ def __init__(self, name, breed):
143
+ self.breed = breed
144
+
145
+ def speak(self):
146
+ return "Woof"
147
+
148
+ class Cat(Animal):
149
+ pass
150
+ """, 'classes')
151
+
152
+ # ─── Test 7: Control flow ─────────────────────────────────────────────────────
153
+ # Exercises: AST_If, AST_While, AST_ForIn, AST_Break, AST_Continue,
154
+ # AST_EmptyStatement, AST_Do
155
+
156
+ await round_trip("""
157
+ x = 5
158
+
159
+ if x > 10:
160
+ x = 10
161
+ elif x < 0:
162
+ x = 0
163
+ else:
164
+ pass
165
+
166
+ i = 0
167
+ while i < 5:
168
+ if i == 3:
169
+ break
170
+ i += 1
171
+
172
+ for item in [1, 2, 3]:
173
+ if item == 2:
174
+ continue
175
+ x += item
176
+ """, 'control_flow')
177
+
178
+ # ─── Test 8: Try/except ───────────────────────────────────────────────────────
179
+ # Exercises: AST_Try, AST_Catch, AST_Except, AST_Finally, AST_Else,
180
+ # AST_Throw, AST_SymbolCatch
181
+
182
+ await round_trip("""
183
+ def risky(x):
184
+ try:
185
+ if x < 0:
186
+ raise ValueError("negative")
187
+ return x
188
+ except ValueError as e:
189
+ return -1
190
+ except TypeError as e:
191
+ return -2
192
+ else:
193
+ pass
194
+ finally:
195
+ pass
196
+ """, 'try_except')
197
+
198
+ # ─── Test 9: Comprehensions ───────────────────────────────────────────────────
199
+ # Exercises: AST_ListComprehension, AST_DictComprehension, AST_SetComprehension,
200
+ # AST_GeneratorComprehension
201
+
202
+ await round_trip("""
203
+ nums = [1, 2, 3, 4, 5]
204
+ squares = [x**2 for x in nums if x > 1]
205
+ even_sq = {x: x**2 for x in nums if x % 2 == 0}
206
+ sq_set = {x**2 for x in nums}
207
+ """, 'comprehensions')
208
+
209
+ # ─── Test 10: Property access and calls ───────────────────────────────────────
210
+ # Exercises: AST_Dot, AST_Sub, AST_Call, AST_CallArgs, AST_New,
211
+ # AST_SymbolRef
212
+
213
+ await round_trip("""
214
+ obj = {'a': {'b': [1, 2, 3]}}
215
+ v = obj['a']['b'][0]
216
+ s = "hello".upper()
217
+ n = len([1, 2, 3])
218
+ """, 'access_and_calls')
219
+
220
+ # ─── Test 11: Assert ──────────────────────────────────────────────────────────
221
+ # Exercises: AST_Assert
222
+
223
+ await round_trip("""
224
+ x = 42
225
+ assert x > 0
226
+ assert x < 100, "x must be less than 100"
227
+ """, 'assert')
228
+
229
+ # ─── Test 12: Generator function ──────────────────────────────────────────────
230
+ # Exercises: AST_Yield (is_yield_from=False and True)
231
+
232
+ await round_trip("""
233
+ def counter(n):
234
+ for i in range(n):
235
+ yield i
236
+
237
+ def chained(n):
238
+ yield from counter(n)
239
+ yield 99
240
+ """, 'generators')
241
+
242
+ # ─── Test 13: Verbatim JS ─────────────────────────────────────────────────────
243
+ # Exercises: AST_Verbatim
244
+
245
+ await round_trip("""
246
+ raw = v'typeof window !== "undefined"'
247
+ """, 'verbatim')
248
+
249
+ # ─── Test 14: With statement ──────────────────────────────────────────────────
250
+ # Exercises: AST_With, AST_WithClause, AST_SymbolAlias
251
+
252
+ await round_trip("""
253
+ class CM:
254
+ def __enter__(self):
255
+ return self
256
+ def __exit__(self, t, v, tb):
257
+ pass
258
+
259
+ with CM() as ctx:
260
+ pass
261
+ """, 'with_statement')
262
+
263
+ # ─── Test 15: Nested scopes / closures ────────────────────────────────────────
264
+ # Exercises: nested AST_Function, nonlocal
265
+
266
+ await round_trip("""
267
+ def outer(x):
268
+ y = x * 2
269
+ def inner(z):
270
+ nonlocal y
271
+ y += z
272
+ return y
273
+ return inner
274
+ """, 'closures')
275
+
276
+ # ─── Test 16: JSON format sanity checks ───────────────────────────────────────
277
+ # Verify the serialized JSON structure is well-formed
278
+
279
+ await assert_json_valid("x = 1 + 2", 'json_format_simple')
280
+ await assert_json_valid("r = /[a-z]+/gi", 'json_format_regexp')
281
+ await assert_json_valid("""
282
+ def f(a, b=1):
283
+ return a + b
284
+ """, 'json_format_function')
285
+
286
+ # ─── Test 17: RegExp round-trip value check ───────────────────────────────────
287
+ # Verify the deserialized RegExp has the correct source and flags
288
+
289
+ src17 = "r = /^hello\\sworld$/im"
290
+ ast17 = await RapydScript.parse(src17, {'filename': 'regexp_check'})
291
+ serialized17 = RapydScript.ast_to_json(ast17)
292
+ ast17b = RapydScript.ast_from_json(serialized17)
293
+
294
+ # Navigate to the AST_RegExp node: Toplevel → SimpleStatement → Assign → right
295
+ re_node = ast17b.body[0].body.right
296
+ assrt.ok(re_node.constructor.name is 'AST_RegExp', 'deserialized node is AST_RegExp')
297
+ assrt.ok(v'Object.prototype.toString.call(re_node.value) === "[object RegExp]"', 'value is a RegExp object')
298
+ assrt.equal(re_node.value.source, '^hello\\sworld$', 'regexp source matches')
299
+ assrt.ok(re_node.value.ignoreCase, 'regexp ignoreCase flag set')
300
+ assrt.ok(re_node.value.multiline, 'regexp multiline flag set')
301
+
302
+ # ─── Test 18: Shared reference integrity ──────────────────────────────────────
303
+ # A node appearing in multiple places (e.g. AST_Class.init also in body)
304
+ # should deserialize to the SAME JS object (same reference).
305
+
306
+ src18 = """
307
+ class Foo:
308
+ def __init__(self):
309
+ self.x = 1
310
+ def bar(self):
311
+ return self.x
312
+ """
313
+ ast18 = await RapydScript.parse(src18, {'filename': 'shared_ref'})
314
+ data18 = RapydScript.ast_to_json(ast18)
315
+
316
+ # Check that the JSON pool has no duplicate entries for the same node:
317
+ # Every index should appear at most once as a _n value in the pool.
318
+ # (Shared refs should point to the same pool index, not copy the node.)
319
+ index_uses = {}
320
+ def count_refs(val):
321
+ if Array.isArray(val):
322
+ for el in val:
323
+ count_refs(el)
324
+ elif val and jstype(val) is 'object':
325
+ if Object.keys(val).length is 1 and val._n is not None and jstype(val._n) is 'number':
326
+ index_uses[val._n] = (index_uses[val._n] or 0) + 1
327
+ else:
328
+ for k in Object.keys(val):
329
+ count_refs(val[k])
330
+
331
+ for node_entry in data18.nodes:
332
+ if node_entry:
333
+ count_refs(node_entry.p)
334
+
335
+ # init in AST_Class.p should reference the same index as the __init__ method in body.
336
+ # We just verify that the deserialized AST still round-trips correctly.
337
+ ast18b = RapydScript.ast_from_json(data18)
338
+ assrt.equal(ast_to_js(ast18b), ast_to_js(ast18), 'shared ref round-trip')
339
+
340
+ # ─── Test 19: Splat args (*arg) round-trip ────────────────────────────────────
341
+ # Exercises: is_array flag on call-site *arg nodes.
342
+ # Without fix: is_array is lost in serialization, causing func(*arg) to
343
+ # generate func.apply(self, [arg]) instead of func.apply(self, arg).
344
+
345
+ await round_trip("""
346
+ def call_it(f, args, a, b):
347
+ f(*args)
348
+ f(a, *args)
349
+ f(a, b, *args)
350
+ """, 'splat_args')
351
+
352
+ # ─── Test 20: Large/complex combined source ───────────────────────────────────
353
+ # Full round-trip of a more complex program
354
+
355
+ await round_trip("""
356
+ def fib(n):
357
+ if n <= 1:
358
+ return n
359
+ return fib(n - 1) + fib(n - 2)
360
+
361
+ class Stack:
362
+ def __init__(self):
363
+ self._items = []
364
+
365
+ def push(self, item):
366
+ self._items.append(item)
367
+
368
+ def pop(self):
369
+ if len(self._items) == 0:
370
+ raise IndexError("pop from empty stack")
371
+ return self._items.pop()
372
+
373
+ @property
374
+ def size(self):
375
+ return len(self._items)
376
+
377
+ def process(items, transform=None, filter_fn=None):
378
+ result = []
379
+ for item in items:
380
+ if filter_fn and not filter_fn(item):
381
+ continue
382
+ if transform:
383
+ item = transform(item)
384
+ result.append(item)
385
+ return result
386
+
387
+ numbers = [fib(i) for i in range(10)]
388
+ evens = [x for x in numbers if x % 2 == 0]
389
+ mapping = {x: x**2 for x in evens}
390
+ """, 'complex_program')
391
+
392
+ __test_async_done__ = run_tests()
package/test/async.pyj ADDED
@@ -0,0 +1,95 @@
1
+ # Tests for async/await support
2
+
3
+ async def async_identity(x):
4
+ return x
5
+
6
+ async def async_add(x, y):
7
+ a = await async_identity(x)
8
+ b = await async_identity(y)
9
+ return a + b
10
+
11
+ async def async_raises():
12
+ raise TypeError("async error")
13
+
14
+ async def async_chain(n):
15
+ if n <= 0:
16
+ return 0
17
+ prev = await async_chain(n - 1)
18
+ return prev + n
19
+
20
+ class AsyncCounter:
21
+ def __init__(self):
22
+ self.count = 0
23
+
24
+ async def increment(self, amount):
25
+ val = await async_identity(amount)
26
+ self.count += val
27
+ return self.count
28
+
29
+ async def reset(self):
30
+ self.count = 0
31
+ return self.count
32
+
33
+ async def get(self):
34
+ return await async_identity(self.count)
35
+
36
+ async def run_tests():
37
+ # Basic async function returning a value
38
+ result = await async_identity(42)
39
+ assrt.equal(result, 42, "basic await identity")
40
+
41
+ # Multiple awaits in sequence
42
+ result = await async_add(3, 4)
43
+ assrt.equal(result, 7, "sequential awaits")
44
+
45
+ # Calling an async function without await returns a Promise
46
+ p = async_identity(10)
47
+ assrt.ok(v"p instanceof Promise", "async fn returns Promise")
48
+ resolved = await p
49
+ assrt.equal(resolved, 10, "awaiting stored Promise")
50
+
51
+ # Recursive async functions
52
+ result = await async_chain(5)
53
+ assrt.equal(result, 15, "recursive async (1+2+3+4+5)")
54
+
55
+ # Async class methods
56
+ counter = AsyncCounter()
57
+ result = await counter.increment(5)
58
+ assrt.equal(result, 5, "async method first increment")
59
+ result = await counter.increment(3)
60
+ assrt.equal(result, 8, "async method second increment")
61
+ result = await counter.get()
62
+ assrt.equal(result, 8, "async getter method")
63
+ result = await counter.reset()
64
+ assrt.equal(result, 0, "async reset method")
65
+
66
+ # Exception propagation through async boundaries
67
+ caught = False
68
+ try:
69
+ await async_raises()
70
+ except TypeError as e:
71
+ caught = True
72
+ assrt.ok(str(e).indexOf("async error") >= 0, "exception message preserved")
73
+ assrt.ok(caught, "exception propagated through await")
74
+
75
+ # async def as an expression (assigned to variable)
76
+ fn = async def(x): return x * 2
77
+ result = await fn(7)
78
+ assrt.equal(result, 14, "async lambda expression")
79
+
80
+ # Await in a nested async function
81
+ async def outer(x):
82
+ async def inner(y):
83
+ return await async_identity(y + 1)
84
+ return await inner(x)
85
+ result = await outer(9)
86
+ assrt.equal(result, 10, "nested async functions")
87
+
88
+ # Promise.all equivalent via verbatim JS
89
+ p1 = async_identity(100)
90
+ p2 = async_identity(200)
91
+ results = await v"Promise.all([p1, p2])"
92
+ assrt.equal(results[0], 100, "Promise.all first")
93
+ assrt.equal(results[1], 200, "Promise.all second")
94
+
95
+ __test_async_done__ = run_tests()
package/test/baselib.pyj CHANGED
@@ -13,7 +13,7 @@ class CustomIterable:
13
13
  t = []
14
14
  q = [1,2,3]
15
15
  for x in CustomIterable(q):
16
- t.push(x)
16
+ t.append(x)
17
17
  assrt.deepEqual(q, t)
18
18
 
19
19
  assrt.deepEqual(['a', 'b'], list('ab'))
@@ -137,7 +137,6 @@ a.clear()
137
137
  assrt.equal(a.length, 0)
138
138
  assrt.deepEqual(a.as_array(), a)
139
139
  assrt.ok(a is not a.as_array())
140
- assrt.ok(a.as_array().extend == undefined)
141
140
  a = [1, 2, 1]
142
141
  assrt.equal(a.count(1), 2)
143
142
  a = [3, 2, 4, 1]