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.
- package/CHANGELOG.md +28 -0
- package/README.md +80 -26
- package/bin/build.ts +117 -0
- package/bin/package.json +1 -0
- package/bin/rapydscript +65 -62
- package/build_wheels.py +133 -0
- package/editor-plugins/README.md +80 -0
- package/editor-plugins/nvim.lua +321 -0
- package/local-agent.md +28 -0
- package/package.json +4 -5
- package/publish.py +27 -17
- package/release/baselib-plain-pretty.js +448 -326
- package/release/baselib-plain-ugly.js +3 -3
- package/release/compiler.js +9027 -8139
- package/release/signatures.json +27 -27
- package/release/stdlib_modules.json +1 -0
- package/src/ast.pyj +428 -280
- package/src/baselib-builtins.pyj +49 -28
- package/src/baselib-containers.pyj +241 -226
- package/src/baselib-errors.pyj +8 -1
- package/src/baselib-internal.pyj +38 -23
- package/src/baselib-itertools.pyj +13 -7
- package/src/baselib-str.pyj +59 -84
- package/src/compiler.pyj +4 -4
- package/src/errors.pyj +3 -4
- package/src/lib/aes.pyj +46 -32
- package/src/lib/elementmaker.pyj +11 -9
- package/src/lib/encodings.pyj +15 -5
- package/src/lib/gettext.pyj +9 -4
- package/src/lib/math.pyj +106 -45
- package/src/lib/operator.pyj +10 -10
- package/src/lib/pythonize.pyj +2 -1
- package/src/lib/random.pyj +28 -21
- package/src/lib/re.pyj +492 -19
- package/src/lib/traceback.pyj +171 -29
- package/src/lib/uuid.pyj +2 -2
- package/src/output/classes.pyj +28 -27
- package/src/output/codegen.pyj +87 -83
- package/src/output/comments.pyj +8 -8
- package/src/output/exceptions.pyj +20 -21
- package/src/output/functions.pyj +66 -64
- package/src/output/literals.pyj +24 -23
- package/src/output/loops.pyj +84 -142
- package/src/output/modules.pyj +55 -68
- package/src/output/operators.pyj +40 -29
- package/src/output/statements.pyj +21 -15
- package/src/output/stream.pyj +82 -56
- package/src/output/utils.pyj +12 -8
- package/src/parse.pyj +971 -620
- package/src/string_interpolation.pyj +5 -3
- package/src/tokenizer.pyj +188 -148
- package/src/utils.pyj +32 -15
- package/test/_treeshake_mod.pyj +30 -0
- package/test/_treeshake_side_effect.pyj +9 -0
- package/test/ast_serialization.pyj +392 -0
- package/test/async.pyj +95 -0
- package/test/baselib.pyj +1 -2
- package/test/embedded_compiler.pyj +57 -0
- package/test/fmt.pyj +291 -0
- package/test/generic.pyj +16 -3
- package/test/imports.pyj +8 -6
- package/test/internationalization.pyj +38 -37
- package/test/lint.pyj +120 -117
- package/test/lsp.pyj +257 -0
- package/test/repl.pyj +70 -65
- package/test/sourcemaps.pyj +315 -0
- package/test/starargs.pyj +46 -39
- package/test/str.pyj +8 -0
- package/test/traceback.pyj +263 -0
- package/test/treeshaking.pyj +181 -0
- package/test/web_repl_export.pyj +88 -0
- package/tools/ast_serialize.mjs +557 -0
- package/tools/cli.mjs +531 -0
- package/tools/compile.mjs +205 -0
- package/tools/compiler.mjs +159 -0
- package/tools/{completer.js → completer.mjs} +6 -6
- package/tools/{embedded_compiler.js → embedded_compiler.mjs} +17 -13
- package/tools/export.js +109 -56
- package/tools/fmt.mjs +975 -0
- package/tools/{gettext.js → gettext.mjs} +46 -53
- package/tools/ini.mjs +175 -0
- package/tools/{lint.js → lint.mjs} +78 -55
- package/tools/lsp.mjs +964 -0
- package/tools/lsp_protocol.mjs +259 -0
- package/tools/lsp_symbols.mjs +418 -0
- package/tools/{msgfmt.js → msgfmt.mjs} +18 -18
- package/tools/{repl.js → repl.mjs} +56 -42
- package/tools/{self.js → self.mjs} +71 -46
- package/tools/sourcemap.mjs +123 -0
- package/tools/test.mjs +252 -0
- package/tools/treeshake.mjs +400 -0
- package/tools/{utils.js → utils.mjs} +26 -23
- package/tools/{web_repl.js → web_repl.mjs} +15 -13
- package/tools/web_repl_export.mjs +97 -0
- package/tree-sitter/README.md +101 -0
- package/tree-sitter/grammar.js +992 -0
- package/tree-sitter/package.json +43 -0
- package/tree-sitter/queries/highlights.scm +232 -0
- package/tree-sitter/queries/indents.scm +64 -0
- package/tree-sitter/queries/injections.scm +14 -0
- package/tree-sitter/queries/locals.scm +108 -0
- package/tree-sitter/src/grammar.json +4976 -0
- package/tree-sitter/src/node-types.json +2901 -0
- package/tree-sitter/src/parser.c +196465 -0
- package/tree-sitter/src/scanner.c +294 -0
- package/tree-sitter/src/tree_sitter/alloc.h +54 -0
- package/tree-sitter/src/tree_sitter/array.h +330 -0
- package/tree-sitter/src/tree_sitter/parser.h +286 -0
- package/tree-sitter/test/corpus/chaining.txt +99 -0
- package/tree-sitter/test/corpus/classes.txt +147 -0
- package/tree-sitter/test/corpus/comprehensions.txt +155 -0
- package/tree-sitter/test/corpus/containers.txt +242 -0
- package/tree-sitter/test/corpus/control_flow.txt +361 -0
- package/tree-sitter/test/corpus/decorators.txt +64 -0
- package/tree-sitter/test/corpus/existential.txt +102 -0
- package/tree-sitter/test/corpus/functions.txt +293 -0
- package/tree-sitter/test/corpus/imports.txt +117 -0
- package/tree-sitter/test/corpus/literals.txt +135 -0
- package/tree-sitter/test/corpus/operators.txt +296 -0
- package/tree-sitter/test/corpus/statements.txt +189 -0
- package/tree-sitter/test/corpus/strings.txt +90 -0
- package/tree-sitter/test/corpus/subscripts.txt +227 -0
- package/tree-sitter/tree-sitter.json +36 -0
- package/web-repl/env.js +35 -23
- package/web-repl/main.js +8 -8
- package/bin/export +0 -75
- package/bin/web-repl-export +0 -102
- package/tools/cli.js +0 -523
- package/tools/compile.js +0 -184
- package/tools/compiler.js +0 -84
- package/tools/ini.js +0 -65
- 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, '/', '-') + '.
|
|
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.
|
|
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]
|