rapydscript-ng 0.7.24 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +19 -0
- package/README.md +72 -26
- package/bin/package.json +1 -0
- package/bin/rapydscript +65 -62
- package/editor-plugins/nvim/rapydscript/README.md +184 -0
- package/editor-plugins/nvim/rapydscript/bin/rapydscript.so +0 -0
- package/editor-plugins/nvim/rapydscript/ftdetect/rapydscript.lua +10 -0
- package/editor-plugins/nvim/rapydscript/lua/rapydscript/health.lua +88 -0
- package/editor-plugins/nvim/rapydscript/lua/rapydscript/init.lua +279 -0
- package/local-agent.md +28 -0
- package/package.json +4 -5
- package/release/baselib-plain-pretty.js +448 -326
- package/release/baselib-plain-ugly.js +3 -3
- package/release/compiler.js +2528 -1672
- package/release/signatures.json +17 -17
- package/src/ast.pyj +73 -25
- package/src/baselib-builtins.pyj +2 -2
- package/src/baselib-containers.pyj +153 -151
- package/src/baselib-internal.pyj +3 -3
- package/src/baselib-str.pyj +5 -5
- package/src/lib/aes.pyj +1 -1
- package/src/lib/encodings.pyj +1 -1
- package/src/lib/pythonize.pyj +1 -1
- package/src/lib/re.pyj +2 -2
- package/src/lib/traceback.pyj +165 -28
- package/src/lib/uuid.pyj +1 -1
- package/src/output/codegen.pyj +10 -3
- package/src/output/functions.pyj +31 -40
- package/src/output/literals.pyj +11 -14
- package/src/output/loops.pyj +25 -87
- package/src/output/modules.pyj +5 -47
- package/src/output/statements.pyj +1 -1
- package/src/output/stream.pyj +58 -31
- package/src/parse.pyj +777 -427
- package/src/tokenizer.pyj +16 -4
- package/src/utils.pyj +1 -1
- 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 +201 -0
- package/test/generic.pyj +7 -3
- package/test/imports.pyj +8 -6
- package/test/internationalization.pyj +38 -37
- package/test/lint.pyj +120 -117
- package/test/lsp.pyj +222 -0
- package/test/repl.pyj +70 -65
- package/test/sourcemaps.pyj +315 -0
- package/test/starargs.pyj +46 -39
- 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 +510 -0
- package/tools/compile.mjs +201 -0
- package/tools/compiler.mjs +127 -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 +735 -0
- package/tools/{gettext.js → gettext.mjs} +46 -53
- package/tools/{ini.js → ini.mjs} +9 -10
- package/tools/{lint.js → lint.mjs} +78 -55
- package/tools/lsp.mjs +914 -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} +52 -41
- package/tools/{self.js → self.mjs} +56 -46
- package/tools/sourcemap.mjs +123 -0
- package/tools/test.mjs +152 -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 +93 -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/rapydscript/highlights.scm +232 -0
- package/tree-sitter/queries/rapydscript/indents.scm +64 -0
- package/tree-sitter/queries/rapydscript/injections.scm +14 -0
- package/tree-sitter/queries/rapydscript/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/test.js +0 -110
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
# globals: assrt, RapydScript, require, compiler_dir, test_path, rs_generate_source_map
|
|
2
|
+
|
|
3
|
+
# Comprehensive tests for source map generation.
|
|
4
|
+
# Tests cover:
|
|
5
|
+
# 1. VLQ encoding and generate_source_map output format (via tools/sourcemap.js)
|
|
6
|
+
# 2. Source map segment generation for a wide range of RapydScript constructs
|
|
7
|
+
# 3. Accuracy of source line and column numbers in segments
|
|
8
|
+
|
|
9
|
+
generate_source_map = rs_generate_source_map
|
|
10
|
+
|
|
11
|
+
# ---------------------------------------------------------------------------
|
|
12
|
+
# Helpers
|
|
13
|
+
# ---------------------------------------------------------------------------
|
|
14
|
+
|
|
15
|
+
def new_output_stream(opts):
|
|
16
|
+
"""Wrapper so that RapydScript.OutputStream is called with new."""
|
|
17
|
+
return v'new RapydScript.OutputStream(opts)'
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
async def compile_source_map(code, filename):
|
|
21
|
+
"""Compile RapydScript *code* and return {code, segments}."""
|
|
22
|
+
ast = await RapydScript.parse(code, {'filename': filename})
|
|
23
|
+
output = new_output_stream({
|
|
24
|
+
'omit_baselib': True,
|
|
25
|
+
'beautify': True,
|
|
26
|
+
'js_version': 6,
|
|
27
|
+
'source_map': True,
|
|
28
|
+
})
|
|
29
|
+
ast.print(output)
|
|
30
|
+
return {
|
|
31
|
+
'code': output.get(),
|
|
32
|
+
'segments': output.get_source_map_segments(),
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def has_seg_for(segments, src_line, src_col, filename):
|
|
37
|
+
"""Return True if there is a segment mapping (src_line, src_col, filename)."""
|
|
38
|
+
for seg in segments:
|
|
39
|
+
if seg[3] == src_line and seg[4] == src_col and seg[2] == filename:
|
|
40
|
+
return True
|
|
41
|
+
return False
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def has_seg_for_line(segments, src_line, filename):
|
|
45
|
+
"""Return True if any segment maps to src_line of filename."""
|
|
46
|
+
for seg in segments:
|
|
47
|
+
if seg[3] == src_line and seg[2] == filename:
|
|
48
|
+
return True
|
|
49
|
+
return False
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
FN = 'test_sm.pyj'
|
|
53
|
+
|
|
54
|
+
# ---------------------------------------------------------------------------
|
|
55
|
+
# Part 1: generate_source_map / VLQ encoding unit tests
|
|
56
|
+
# ---------------------------------------------------------------------------
|
|
57
|
+
|
|
58
|
+
# Empty segment list → empty mappings with correct structure
|
|
59
|
+
m = JSON.parse(generate_source_map([], 'out.js', ''))
|
|
60
|
+
assrt.strictEqual(m.version, 3, 'version must be 3')
|
|
61
|
+
assrt.strictEqual(m.mappings, '', 'empty segments → empty mappings')
|
|
62
|
+
assrt.strictEqual(m.file, 'out.js', 'file basename preserved')
|
|
63
|
+
assrt.deepEqual(m.sources, [], 'no sources for empty segments')
|
|
64
|
+
|
|
65
|
+
# Single segment at origin: all deltas are 0, encodes as AAAA
|
|
66
|
+
m = JSON.parse(generate_source_map([[0, 0, 'a.pyj', 0, 0]], 'out.js', ''))
|
|
67
|
+
assrt.strictEqual(m.mappings, 'AAAA', 'origin segment encodes as AAAA')
|
|
68
|
+
assrt.deepEqual(m.sources, ['a.pyj'], 'source file name is recorded')
|
|
69
|
+
|
|
70
|
+
# Two segments on the same generated line
|
|
71
|
+
# Seg1 [0,0,'a.pyj',0,0] → AAAA; Seg2 [0,4,'a.pyj',0,3] → delta=[4,0,0,3] → IAAG
|
|
72
|
+
m = JSON.parse(generate_source_map([[0, 0, 'a.pyj', 0, 0], [0, 4, 'a.pyj', 0, 3]], 'out.js', ''))
|
|
73
|
+
assrt.strictEqual(m.mappings, 'AAAA,IAAG', 'two segs on same line: comma-separated VLQ')
|
|
74
|
+
|
|
75
|
+
# Segments on two separate generated lines: semicolon separator, prev_col resets per line
|
|
76
|
+
# Seg1 [0,0,'a.pyj',0,0] → AAAA; Seg2 [1,0,'a.pyj',1,0] → prev_col=0, delta=[0,0,1,0] → AACA
|
|
77
|
+
m = JSON.parse(generate_source_map([[0, 0, 'a.pyj', 0, 0], [1, 0, 'a.pyj', 1, 0]], 'out.js', ''))
|
|
78
|
+
assrt.strictEqual(m.mappings, 'AAAA;AACA', 'two lines: semicolon-separated, col resets')
|
|
79
|
+
|
|
80
|
+
# Three generated lines → two semicolons
|
|
81
|
+
m = JSON.parse(generate_source_map(
|
|
82
|
+
[[0, 0, 'a.pyj', 0, 0], [1, 0, 'a.pyj', 1, 0], [2, 0, 'a.pyj', 2, 0]], 'out.js', ''))
|
|
83
|
+
assrt.strictEqual(m.mappings, 'AAAA;AACA;AACA', 'three lines: two semicolons')
|
|
84
|
+
|
|
85
|
+
# Gap in generated lines → empty group between semicolons
|
|
86
|
+
# [0,…] and [2,…]: line 1 is empty → ';;'
|
|
87
|
+
m = JSON.parse(generate_source_map([[0, 0, 'a.pyj', 0, 0], [2, 0, 'a.pyj', 2, 0]], 'out.js', ''))
|
|
88
|
+
assrt.ok(m.mappings.indexOf(';;') >= 0, 'gap generates empty group (two consecutive semicolons)')
|
|
89
|
+
|
|
90
|
+
# Negative source column delta (reverse mapping within same line)
|
|
91
|
+
# Seg1 [0,0,'a.pyj',0,5] → delta=[0,0,0,5]='AAAK'
|
|
92
|
+
# Seg2 [0,4,'a.pyj',0,2] → delta=[4,0,0,-3]='IAAH'
|
|
93
|
+
m = JSON.parse(generate_source_map([[0, 0, 'a.pyj', 0, 5], [0, 4, 'a.pyj', 0, 2]], 'out.js', ''))
|
|
94
|
+
assrt.strictEqual(m.mappings, 'AAAK,IAAH', 'negative source column delta encodes correctly')
|
|
95
|
+
|
|
96
|
+
# Two source files on the same generated line
|
|
97
|
+
# Seg1: AAAA; Seg2: delta=[10,1,0,0]='UCAA'; Line1: delta=[0,-1,1,0]='ADCA'
|
|
98
|
+
m = JSON.parse(generate_source_map(
|
|
99
|
+
[[0, 0, 'f1.pyj', 0, 0], [0, 10, 'f2.pyj', 0, 0], [1, 0, 'f1.pyj', 1, 0]], 'out.js', ''))
|
|
100
|
+
assrt.strictEqual(m.mappings, 'AAAA,UCAA;ADCA', 'two source files encode correctly')
|
|
101
|
+
assrt.deepEqual(m.sources, ['f1.pyj', 'f2.pyj'], 'sources list preserves insertion order')
|
|
102
|
+
|
|
103
|
+
# Multi-byte VLQ: col delta of 50 encodes as kD
|
|
104
|
+
m = JSON.parse(generate_source_map(
|
|
105
|
+
[[0, 0, 'a.pyj', 0, 0], [0, 50, 'a.pyj', 0, 50]], 'out.js', ''))
|
|
106
|
+
assrt.strictEqual(m.mappings, 'AAAA,kDAAkD', 'multi-byte VLQ for col delta 50')
|
|
107
|
+
|
|
108
|
+
# Deduplication: two segments at identical generated position → only first kept
|
|
109
|
+
m = JSON.parse(generate_source_map(
|
|
110
|
+
[[0, 0, 'a.pyj', 0, 0], [0, 0, 'a.pyj', 1, 0]], 'out.js', ''))
|
|
111
|
+
assrt.strictEqual(m.mappings, 'AAAA', 'duplicate gen position: first segment wins')
|
|
112
|
+
|
|
113
|
+
# output_file basename is extracted from full path
|
|
114
|
+
m = JSON.parse(generate_source_map([[0, 0, 'a.pyj', 0, 0]], '/path/to/out.js', ''))
|
|
115
|
+
assrt.strictEqual(m.file, 'out.js', 'file basename extracted from path')
|
|
116
|
+
|
|
117
|
+
# source_root is passed through unchanged
|
|
118
|
+
m = JSON.parse(generate_source_map([[0, 0, 'a.pyj', 0, 0]], 'out.js', 'https://example.com/src/'))
|
|
119
|
+
assrt.strictEqual(m.sourceRoot, 'https://example.com/src/', 'sourceRoot preserved')
|
|
120
|
+
|
|
121
|
+
# names array is always empty (RapydScript does not emit symbol names)
|
|
122
|
+
m = JSON.parse(generate_source_map([[0, 0, 'a.pyj', 0, 0]], 'out.js', ''))
|
|
123
|
+
assrt.deepEqual(m.names, [], 'names array is empty')
|
|
124
|
+
|
|
125
|
+
async def run_parse_tests():
|
|
126
|
+
# ---------------------------------------------------------------------------
|
|
127
|
+
# Part 2: OutputStream source_map option / segment format
|
|
128
|
+
# ---------------------------------------------------------------------------
|
|
129
|
+
|
|
130
|
+
# source_map=False → get_source_map_segments() returns a falsy value (null/undefined)
|
|
131
|
+
ast_no_map = await RapydScript.parse('x = 1\n', {'filename': FN})
|
|
132
|
+
out_no_map = new_output_stream({'omit_baselib': True, 'beautify': True, 'js_version': 6, 'source_map': False})
|
|
133
|
+
ast_no_map.print(out_no_map)
|
|
134
|
+
assrt.ok(not out_no_map.get_source_map_segments(), 'source_map=False → no segments')
|
|
135
|
+
|
|
136
|
+
# source_map not set → same as False
|
|
137
|
+
ast_no_map2 = await RapydScript.parse('x = 1\n', {'filename': FN})
|
|
138
|
+
out_no_map2 = new_output_stream({'omit_baselib': True, 'beautify': True, 'js_version': 6})
|
|
139
|
+
ast_no_map2.print(out_no_map2)
|
|
140
|
+
assrt.ok(not out_no_map2.get_source_map_segments(), 'source_map unset → no segments')
|
|
141
|
+
|
|
142
|
+
# source_map=True → segments is an array
|
|
143
|
+
r = await compile_source_map('x = 1\n', FN)
|
|
144
|
+
assrt.ok(Array.isArray(r['segments']), 'source_map=True → segments is an array')
|
|
145
|
+
assrt.ok(r['segments'].length > 0, 'segments array is non-empty for non-empty input')
|
|
146
|
+
|
|
147
|
+
# Each segment is a 5-element array of numbers/string
|
|
148
|
+
r = await compile_source_map('x = 1\ny = 2\n', FN)
|
|
149
|
+
for seg in r['segments']:
|
|
150
|
+
assrt.strictEqual(seg.length, 5, 'each segment has 5 elements')
|
|
151
|
+
assrt.strictEqual(jstype(seg[0]), 'number', 'gen_line is a number')
|
|
152
|
+
assrt.strictEqual(jstype(seg[1]), 'number', 'gen_col is a number')
|
|
153
|
+
assrt.strictEqual(jstype(seg[2]), 'string', 'src_file is a string')
|
|
154
|
+
assrt.strictEqual(jstype(seg[3]), 'number', 'src_line is a number')
|
|
155
|
+
assrt.strictEqual(jstype(seg[4]), 'number', 'src_col is a number')
|
|
156
|
+
assrt.ok(seg[0] >= 0, 'gen_line is non-negative')
|
|
157
|
+
assrt.ok(seg[1] >= 0, 'gen_col is non-negative')
|
|
158
|
+
assrt.ok(seg[3] >= 0, 'src_line is non-negative')
|
|
159
|
+
assrt.ok(seg[4] >= 0, 'src_col is non-negative')
|
|
160
|
+
|
|
161
|
+
# Source file name matches the filename passed to parse
|
|
162
|
+
r = await compile_source_map('x = 1\n', FN)
|
|
163
|
+
for seg in r['segments']:
|
|
164
|
+
assrt.strictEqual(seg[2], FN, 'src_file matches filename in parse options')
|
|
165
|
+
|
|
166
|
+
# Segments are sorted by generated position
|
|
167
|
+
r = await compile_source_map('a = 1\nb = 2\nc = 3\n', FN)
|
|
168
|
+
segs = r['segments']
|
|
169
|
+
for i in range(segs.length - 1):
|
|
170
|
+
cur = segs[i]
|
|
171
|
+
nxt = segs[i + 1]
|
|
172
|
+
assrt.ok(
|
|
173
|
+
cur[0] < nxt[0] or (cur[0] == nxt[0] and cur[1] <= nxt[1]),
|
|
174
|
+
'segments are sorted by (gen_line, gen_col)'
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
# ---------------------------------------------------------------------------
|
|
178
|
+
# Part 3: Correct mappings for specific source constructs
|
|
179
|
+
# ---------------------------------------------------------------------------
|
|
180
|
+
|
|
181
|
+
# -- Simple variable assignment --
|
|
182
|
+
r = await compile_source_map('x = 1\n', FN)
|
|
183
|
+
assrt.ok(has_seg_for(r['segments'], 0, 0, FN), 'assignment: maps line 0 col 0')
|
|
184
|
+
|
|
185
|
+
# -- Two sequential assignments --
|
|
186
|
+
r = await compile_source_map('x = 1\ny = 2\n', FN)
|
|
187
|
+
assrt.ok(has_seg_for_line(r['segments'], 0, FN), '2 assignments: line 0 mapped')
|
|
188
|
+
assrt.ok(has_seg_for_line(r['segments'], 1, FN), '2 assignments: line 1 mapped')
|
|
189
|
+
|
|
190
|
+
# -- Function definition with return --
|
|
191
|
+
r = await compile_source_map('def add(a, b):\n return a + b\n', FN)
|
|
192
|
+
assrt.ok(has_seg_for(r['segments'], 0, 0, FN), 'def: maps def keyword (line 0, col 0)')
|
|
193
|
+
assrt.ok(has_seg_for(r['segments'], 1, 4, FN), 'def: maps return statement (line 1, col 4)')
|
|
194
|
+
|
|
195
|
+
# -- Nested function --
|
|
196
|
+
code = 'def outer():\n def inner():\n return 1\n return inner()\n'
|
|
197
|
+
r = await compile_source_map(code, FN)
|
|
198
|
+
assrt.ok(has_seg_for(r['segments'], 0, 0, FN), 'nested def: outer def mapped (line 0)')
|
|
199
|
+
assrt.ok(has_seg_for(r['segments'], 1, 4, FN), 'nested def: inner def mapped (line 1, col 4)')
|
|
200
|
+
assrt.ok(has_seg_for_line(r['segments'], 2, FN), 'nested def: inner return mapped (line 2)')
|
|
201
|
+
assrt.ok(has_seg_for_line(r['segments'], 3, FN), 'nested def: outer return mapped (line 3)')
|
|
202
|
+
|
|
203
|
+
# -- If / else --
|
|
204
|
+
code = 'x = 5\nif x > 0:\n y = 1\nelse:\n y = -1\n'
|
|
205
|
+
r = await compile_source_map(code, FN)
|
|
206
|
+
assrt.ok(has_seg_for(r['segments'], 0, 0, FN), 'if/else: assignment before if (line 0)')
|
|
207
|
+
assrt.ok(has_seg_for(r['segments'], 1, 0, FN), 'if/else: if keyword (line 1, col 0)')
|
|
208
|
+
assrt.ok(has_seg_for(r['segments'], 2, 4, FN), 'if/else: true branch (line 2, col 4)')
|
|
209
|
+
assrt.ok(has_seg_for(r['segments'], 4, 4, FN), 'if/else: else branch (line 4, col 4)')
|
|
210
|
+
|
|
211
|
+
# -- Elif chain --
|
|
212
|
+
code = 'x = 2\nif x is 1:\n y = 1\nelif x is 2:\n y = 2\nelse:\n y = 0\n'
|
|
213
|
+
r = await compile_source_map(code, FN)
|
|
214
|
+
assrt.ok(has_seg_for_line(r['segments'], 1, FN), 'elif: if branch mapped')
|
|
215
|
+
assrt.ok(has_seg_for_line(r['segments'], 2, FN), 'elif: first body mapped')
|
|
216
|
+
assrt.ok(has_seg_for_line(r['segments'], 3, FN), 'elif: elif branch mapped')
|
|
217
|
+
assrt.ok(has_seg_for_line(r['segments'], 4, FN), 'elif: second body mapped')
|
|
218
|
+
assrt.ok(has_seg_for_line(r['segments'], 6, FN), 'elif: else body mapped')
|
|
219
|
+
|
|
220
|
+
# -- For loop --
|
|
221
|
+
r = await compile_source_map('for i in range(5):\n x = i * 2\n', FN)
|
|
222
|
+
assrt.ok(has_seg_for(r['segments'], 0, 0, FN), 'for: loop header (line 0, col 0)')
|
|
223
|
+
assrt.ok(has_seg_for(r['segments'], 1, 4, FN), 'for: loop body (line 1, col 4)')
|
|
224
|
+
|
|
225
|
+
# -- While loop --
|
|
226
|
+
r = await compile_source_map('x = 0\nwhile x < 10:\n x += 1\n', FN)
|
|
227
|
+
assrt.ok(has_seg_for(r['segments'], 0, 0, FN), 'while: init assignment (line 0)')
|
|
228
|
+
assrt.ok(has_seg_for(r['segments'], 1, 0, FN), 'while: while keyword (line 1, col 0)')
|
|
229
|
+
assrt.ok(has_seg_for(r['segments'], 2, 4, FN), 'while: loop body (line 2, col 4)')
|
|
230
|
+
|
|
231
|
+
# -- Class definition with method --
|
|
232
|
+
code = 'class Counter:\n def __init__(self, start):\n self.n = start\n\n def inc(self):\n self.n += 1\n'
|
|
233
|
+
r = await compile_source_map(code, FN)
|
|
234
|
+
assrt.ok(has_seg_for(r['segments'], 0, 0, FN), 'class: class keyword (line 0, col 0)')
|
|
235
|
+
assrt.ok(has_seg_for(r['segments'], 1, 8, FN), 'class: __init__ method name (line 1, col 8)')
|
|
236
|
+
assrt.ok(has_seg_for(r['segments'], 2, 8, FN), 'class: __init__ body (line 2, col 8)')
|
|
237
|
+
assrt.ok(has_seg_for(r['segments'], 4, 8, FN), 'class: inc method name (line 4, col 8)')
|
|
238
|
+
assrt.ok(has_seg_for(r['segments'], 5, 8, FN), 'class: inc body (line 5, col 8)')
|
|
239
|
+
|
|
240
|
+
# -- Class with inheritance --
|
|
241
|
+
code = 'class Animal:\n def speak(self):\n return "..."\n\nclass Dog(Animal):\n def speak(self):\n return "woof"\n'
|
|
242
|
+
r = await compile_source_map(code, FN)
|
|
243
|
+
assrt.ok(has_seg_for(r['segments'], 0, 0, FN), 'inheritance: base class (line 0)')
|
|
244
|
+
assrt.ok(has_seg_for(r['segments'], 4, 0, FN), 'inheritance: derived class (line 4)')
|
|
245
|
+
|
|
246
|
+
# -- Try / except --
|
|
247
|
+
code = 'try:\n x = int("abc")\nexcept:\n x = 0\n'
|
|
248
|
+
r = await compile_source_map(code, FN)
|
|
249
|
+
assrt.ok(has_seg_for_line(r['segments'], 1, FN), 'try/except: try body (line 1)')
|
|
250
|
+
assrt.ok(has_seg_for_line(r['segments'], 3, FN), 'try/except: except body (line 3)')
|
|
251
|
+
|
|
252
|
+
# -- Try / except with specific exception --
|
|
253
|
+
code = 'try:\n raise ValueError("oops")\nexcept ValueError as e:\n msg = str(e)\n'
|
|
254
|
+
r = await compile_source_map(code, FN)
|
|
255
|
+
assrt.ok(has_seg_for_line(r['segments'], 1, FN), 'try/except ValueError: try body')
|
|
256
|
+
assrt.ok(has_seg_for_line(r['segments'], 3, FN), 'try/except ValueError: handler body')
|
|
257
|
+
|
|
258
|
+
# -- Function call expression --
|
|
259
|
+
r = await compile_source_map('result = len("hello")\n', FN)
|
|
260
|
+
assrt.ok(has_seg_for(r['segments'], 0, 0, FN), 'function call: expression at line 0 col 0')
|
|
261
|
+
|
|
262
|
+
# -- Return statement --
|
|
263
|
+
r = await compile_source_map('def f():\n return 42\n', FN)
|
|
264
|
+
assrt.ok(has_seg_for(r['segments'], 1, 4, FN), 'return: return at line 1, col 4')
|
|
265
|
+
|
|
266
|
+
# -- Lambda / anonymous function --
|
|
267
|
+
r = await compile_source_map('double = def(x): return x * 2\n', FN)
|
|
268
|
+
assrt.ok(has_seg_for(r['segments'], 0, 0, FN), 'lambda: maps lambda expression')
|
|
269
|
+
|
|
270
|
+
# -- List comprehension --
|
|
271
|
+
r = await compile_source_map('xs = [i * 2 for i in range(5)]\n', FN)
|
|
272
|
+
assrt.ok(has_seg_for_line(r['segments'], 0, FN), 'list comprehension: maps line 0')
|
|
273
|
+
|
|
274
|
+
# -- Conditional expression (ternary) --
|
|
275
|
+
r = await compile_source_map('y = 1 if True else 0\n', FN)
|
|
276
|
+
assrt.ok(has_seg_for(r['segments'], 0, 0, FN), 'ternary: maps at line 0 col 0')
|
|
277
|
+
|
|
278
|
+
# -- Augmented assignment --
|
|
279
|
+
r = await compile_source_map('x = 1\nx += 5\n', FN)
|
|
280
|
+
assrt.ok(has_seg_for_line(r['segments'], 0, FN), 'augmented assign: first line')
|
|
281
|
+
assrt.ok(has_seg_for_line(r['segments'], 1, FN), 'augmented assign: second line')
|
|
282
|
+
|
|
283
|
+
# -- Binary operators --
|
|
284
|
+
r = await compile_source_map('a = 2\nb = 3\nc = a * b + 1\n', FN)
|
|
285
|
+
assrt.ok(has_seg_for(r['segments'], 0, 0, FN), 'binop: line 0')
|
|
286
|
+
assrt.ok(has_seg_for(r['segments'], 1, 0, FN), 'binop: line 1')
|
|
287
|
+
assrt.ok(has_seg_for(r['segments'], 2, 0, FN), 'binop: line 2')
|
|
288
|
+
|
|
289
|
+
# -- Multi-line function with several statements --
|
|
290
|
+
code = 'def process(items):\n result = []\n for item in items:\n result.append(item * 2)\n return result\n'
|
|
291
|
+
r = await compile_source_map(code, FN)
|
|
292
|
+
assrt.ok(has_seg_for(r['segments'], 0, 0, FN), 'multi-stmt func: def line')
|
|
293
|
+
assrt.ok(has_seg_for(r['segments'], 1, 4, FN), 'multi-stmt func: result = []')
|
|
294
|
+
assrt.ok(has_seg_for(r['segments'], 2, 4, FN), 'multi-stmt func: for loop')
|
|
295
|
+
assrt.ok(has_seg_for(r['segments'], 3, 8, FN), 'multi-stmt func: append call')
|
|
296
|
+
assrt.ok(has_seg_for(r['segments'], 4, 4, FN), 'multi-stmt func: return')
|
|
297
|
+
|
|
298
|
+
# -- generate_source_map round-trip with real compiler output --
|
|
299
|
+
r = await compile_source_map('x = 1\ny = x + 1\n', FN)
|
|
300
|
+
map_json = generate_source_map(r['segments'], 'out.js', '')
|
|
301
|
+
m = JSON.parse(map_json)
|
|
302
|
+
assrt.strictEqual(m.version, 3, 'round-trip: version=3')
|
|
303
|
+
assrt.deepEqual(m.sources, [FN], 'round-trip: sources=[FN]')
|
|
304
|
+
assrt.strictEqual(m.file, 'out.js', 'round-trip: file=out.js')
|
|
305
|
+
assrt.ok(m.mappings.length > 0, 'round-trip: mappings non-empty')
|
|
306
|
+
# All semicolons count should match (max_gen_line - 1 separators minimum)
|
|
307
|
+
max_gen_line = 0
|
|
308
|
+
for seg in r['segments']:
|
|
309
|
+
if seg[0] > max_gen_line:
|
|
310
|
+
max_gen_line = seg[0]
|
|
311
|
+
semicolons = m.mappings.split(';').length - 1
|
|
312
|
+
assrt.ok(semicolons >= max_gen_line, 'round-trip: enough semicolons for all generated lines')
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
run_parse_tests()
|
package/test/starargs.pyj
CHANGED
|
@@ -9,9 +9,10 @@ def get(obj, name):
|
|
|
9
9
|
|
|
10
10
|
# Test the parsing of function definitions {{{
|
|
11
11
|
|
|
12
|
-
def basic_test(code, arglen, starargs, kwargs, defaults):
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
async def basic_test(code, arglen, starargs, kwargs, defaults):
|
|
13
|
+
result = await RapydScript.parse('def func(' + code + '): pass\n', {'filename':code})
|
|
14
|
+
func = result.body[0]
|
|
15
|
+
eq(func.argnames.args.length, arglen)
|
|
15
16
|
eq(get(func.argnames.starargs, 'name'), starargs)
|
|
16
17
|
eq(get(func.argnames.kwargs, 'name'), kwargs)
|
|
17
18
|
eq(func.argnames.is_simple_func, bool(not starargs and not kwargs and not defaults))
|
|
@@ -22,52 +23,56 @@ def basic_test(code, arglen, starargs, kwargs, defaults):
|
|
|
22
23
|
de(fd, defaults)
|
|
23
24
|
return func
|
|
24
25
|
|
|
25
|
-
|
|
26
|
-
assrt.throws(def():
|
|
27
|
-
RapydScript.parse('def func(' + code + '): pass\n', {'filename':code}).body[0]
|
|
28
|
-
, RapydScript.SyntaxError)
|
|
29
|
-
|
|
30
|
-
basic_test('a, b, c', 3)
|
|
31
|
-
basic_test('*args', 0, 'args')
|
|
32
|
-
basic_test('a, b, *args', 2, 'args')
|
|
33
|
-
throw_test('*args, a')
|
|
34
|
-
throw_test('*args, *a')
|
|
35
|
-
basic_test('**kwargs', 0, undefined, 'kwargs')
|
|
36
|
-
basic_test('*args, **kwargs', 0, 'args', 'kwargs')
|
|
37
|
-
basic_test('a, b, *args, **kwargs', 2, 'args', 'kwargs')
|
|
38
|
-
throw_test('**kw, *a')
|
|
39
|
-
throw_test('**kw, **a')
|
|
40
|
-
basic_test('a=1, b="2"', 2, None, None, {'a':1, 'b':"2"})
|
|
41
|
-
basic_test('x, a=1, b="2", *args, **kw', 3, 'args', 'kw', {'a':1, 'b':"2"})
|
|
42
|
-
throw_test('a=1, b')
|
|
43
|
-
throw_test('**k, a=1')
|
|
44
|
-
throw_test('a, a')
|
|
45
|
-
throw_test('a, a=1')
|
|
46
|
-
throw_test('*a, **a')
|
|
47
|
-
# }}}
|
|
26
|
+
_throw_test_promises = []
|
|
48
27
|
|
|
49
|
-
|
|
28
|
+
def throw_test(code):
|
|
29
|
+
_throw_test_promises.append(assrt.rejects(async def():
|
|
30
|
+
await RapydScript.parse('def func(' + code + '): pass\n', {'filename':code})
|
|
31
|
+
, RapydScript.SyntaxError))
|
|
50
32
|
|
|
51
|
-
def parse_call_test(code, arglen, opts):
|
|
52
|
-
|
|
33
|
+
async def parse_call_test(code, arglen, opts):
|
|
34
|
+
result = await RapydScript.parse('f(' + code + ')', {'filename':code})
|
|
35
|
+
func = result.body[0].body
|
|
53
36
|
opts = opts or {}
|
|
54
|
-
eq(func.args.length, arglen, 'Incorrect len for: ' + code)
|
|
37
|
+
eq(func.args.args.length, arglen, 'Incorrect len for: ' + code)
|
|
55
38
|
if (opts.starargs != undefined):
|
|
56
|
-
si = [[i, x.name] for i, x in enumerate(func.args) if x.is_array]
|
|
39
|
+
si = [[i, x.value.name] for i, x in enumerate(func.args.args) if x.is_array]
|
|
57
40
|
de(opts.starargs, si, 'starargs wrong for: ' + code + ': ' + si + ' != ' + opts.starargs)
|
|
58
41
|
if (opts.kwargs != undefined):
|
|
59
42
|
de(opts.kwargs, [x[0].name for x in func.args.kwargs])
|
|
60
43
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
44
|
+
async def run_parse_tests():
|
|
45
|
+
await basic_test('a, b, c', 3)
|
|
46
|
+
await basic_test('*args', 0, 'args')
|
|
47
|
+
await basic_test('a, b, *args', 2, 'args')
|
|
48
|
+
throw_test('*args, a')
|
|
49
|
+
throw_test('*args, *a')
|
|
50
|
+
await basic_test('**kwargs', 0, undefined, 'kwargs')
|
|
51
|
+
await basic_test('*args, **kwargs', 0, 'args', 'kwargs')
|
|
52
|
+
await basic_test('a, b, *args, **kwargs', 2, 'args', 'kwargs')
|
|
53
|
+
throw_test('**kw, *a')
|
|
54
|
+
throw_test('**kw, **a')
|
|
55
|
+
await basic_test('a=1, b="2"', 2, None, None, {'a':1, 'b':"2"})
|
|
56
|
+
await basic_test('x, a=1, b="2", *args, **kw', 3, 'args', 'kw', {'a':1, 'b':"2"})
|
|
57
|
+
throw_test('a=1, b')
|
|
58
|
+
throw_test('**k, a=1')
|
|
59
|
+
throw_test('a, a')
|
|
60
|
+
throw_test('a, a=1')
|
|
61
|
+
throw_test('*a, **a')
|
|
62
|
+
|
|
63
|
+
await parse_call_test('a, b, c', 3)
|
|
64
|
+
await parse_call_test('*args', 1, {'starargs':[[0, 'args']]})
|
|
65
|
+
await parse_call_test('a, b, *args', 3, {'starargs':[[2, 'args']]})
|
|
66
|
+
await parse_call_test('a, *args, b, *a2', 4, {'starargs':[[1, 'args'], [3, 'a2']]})
|
|
67
|
+
await parse_call_test('a=1', 0, {'kwargs':['a']})
|
|
68
|
+
await parse_call_test('a=1, b', 1, {'kwargs':['a']})
|
|
69
|
+
await parse_call_test('a=1, b, **kwargs, *args, **k2', 2, {'kwargs':['a'], 'kw':['kwargs', 'k2'], 'starargs':[[1,'args']]})
|
|
70
|
+
|
|
71
|
+
await Promise.all(_throw_test_promises)
|
|
69
72
|
# }}}
|
|
70
73
|
|
|
74
|
+
_parse_tests_promise = run_parse_tests()
|
|
75
|
+
|
|
71
76
|
# Test calling {{{
|
|
72
77
|
|
|
73
78
|
def f():
|
|
@@ -340,3 +345,5 @@ class Prnb1(Prnb):
|
|
|
340
345
|
p = Prnb1(1, 2, x=1, y=2)
|
|
341
346
|
de(p.a, [1, 2])
|
|
342
347
|
de(p.k, {'x': 1, 'y':2})
|
|
348
|
+
|
|
349
|
+
_parse_tests_promise
|