rapydscript-ns 0.9.2 → 0.9.3
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/.agignore +1 -1
- package/.github/workflows/ci.yml +38 -38
- package/=template.pyj +5 -5
- package/CHANGELOG.md +19 -0
- package/HACKING.md +103 -103
- package/LICENSE +24 -24
- package/PYTHON_GAPS.md +420 -0
- package/README.md +153 -29
- package/TODO.md +16 -118
- package/add-toc-to-readme +2 -2
- package/bin/export +75 -75
- package/bin/rapydscript +70 -70
- package/bin/web-repl-export +102 -102
- package/build +2 -2
- package/language-service/index.js +237 -8
- package/memory/project_string_impl.md +43 -0
- package/package.json +1 -1
- package/publish.py +37 -37
- package/release/baselib-plain-pretty.js +248 -38
- package/release/baselib-plain-ugly.js +8 -8
- package/release/compiler.js +778 -277
- package/release/signatures.json +30 -30
- package/session.vim +4 -4
- package/setup.cfg +2 -2
- package/src/ast.pyj +4 -1
- package/src/baselib-builtins.pyj +56 -2
- package/src/baselib-containers.pyj +2 -0
- package/src/baselib-errors.pyj +7 -3
- package/src/baselib-internal.pyj +51 -6
- package/src/baselib-str.pyj +5 -3
- package/src/compiler.pyj +36 -36
- package/src/errors.pyj +30 -30
- package/src/lib/aes.pyj +646 -646
- package/src/lib/asyncio.pyj +534 -0
- package/src/lib/base64.pyj +399 -0
- package/src/lib/bisect.pyj +73 -0
- package/src/lib/collections.pyj +1 -1
- package/src/lib/copy.pyj +120 -120
- package/src/lib/csv.pyj +494 -0
- package/src/lib/elementmaker.pyj +83 -83
- package/src/lib/encodings.pyj +126 -126
- package/src/lib/gettext.pyj +569 -569
- package/src/lib/heapq.pyj +98 -0
- package/src/lib/html.pyj +382 -0
- package/src/lib/http/__init__.pyj +98 -0
- package/src/lib/http/client.pyj +304 -0
- package/src/lib/http/cookies.pyj +236 -0
- package/src/lib/itertools.pyj +580 -580
- package/src/lib/logging.pyj +672 -0
- package/src/lib/math.pyj +193 -193
- package/src/lib/operator.pyj +11 -11
- package/src/lib/pythonize.pyj +20 -20
- package/src/lib/random.pyj +118 -118
- package/src/lib/react.pyj +74 -74
- package/src/lib/string.pyj +357 -0
- package/src/lib/textwrap.pyj +329 -0
- package/src/lib/traceback.pyj +63 -63
- package/src/lib/urllib/__init__.pyj +14 -0
- package/src/lib/urllib/error.pyj +66 -0
- package/src/lib/urllib/parse.pyj +475 -0
- package/src/lib/urllib/request.pyj +86 -0
- package/src/lib/uuid.pyj +77 -77
- package/src/monaco-language-service/analyzer.js +5 -2
- package/src/monaco-language-service/completions.js +26 -0
- package/src/monaco-language-service/diagnostics.js +202 -3
- package/src/monaco-language-service/dts.js +550 -550
- package/src/monaco-language-service/scope.js +1 -0
- package/src/output/comments.pyj +45 -45
- package/src/output/exceptions.pyj +201 -201
- package/src/output/functions.pyj +152 -6
- package/src/output/jsx.pyj +164 -164
- package/src/output/loops.pyj +17 -2
- package/src/output/modules.pyj +1 -1
- package/src/output/operators.pyj +15 -0
- package/src/output/stream.pyj +0 -1
- package/src/output/treeshake.pyj +182 -182
- package/src/output/utils.pyj +72 -72
- package/src/parse.pyj +80 -17
- package/src/string_interpolation.pyj +72 -72
- package/src/tokenizer.pyj +1 -1
- package/src/unicode_aliases.pyj +576 -576
- package/src/utils.pyj +192 -192
- package/test/_import_one.pyj +37 -37
- package/test/_import_two/__init__.pyj +11 -11
- package/test/_import_two/level2/deep.pyj +4 -4
- package/test/_import_two/other.pyj +6 -6
- package/test/_import_two/sub.pyj +13 -13
- package/test/aes_vectors.pyj +421 -421
- package/test/annotations.pyj +80 -80
- package/test/async_generators.pyj +144 -0
- package/test/asyncio.pyj +307 -0
- package/test/base64.pyj +202 -0
- package/test/bisect.pyj +178 -0
- package/test/csv.pyj +405 -0
- package/test/decorators.pyj +77 -77
- package/test/docstrings.pyj +39 -39
- package/test/elementmaker_test.pyj +45 -45
- package/test/float_special.pyj +64 -0
- package/test/functions.pyj +151 -151
- package/test/generators.pyj +41 -41
- package/test/generic.pyj +370 -370
- package/test/heapq.pyj +174 -0
- package/test/html.pyj +212 -0
- package/test/http.pyj +259 -0
- package/test/imports.pyj +79 -72
- package/test/internationalization.pyj +73 -73
- package/test/lint.pyj +164 -164
- package/test/logging.pyj +356 -0
- package/test/long.pyj +130 -0
- package/test/loops.pyj +85 -85
- package/test/numpy.pyj +734 -734
- package/test/parenthesized_with.pyj +141 -0
- package/test/python_compat.pyj +3 -5
- package/test/python_modulo.pyj +76 -0
- package/test/python_modulo_off.pyj +21 -0
- package/test/repl.pyj +121 -121
- package/test/scoped_flags.pyj +76 -76
- package/test/str.pyj +14 -0
- package/test/string.pyj +245 -0
- package/test/textwrap.pyj +172 -0
- package/test/type_display.pyj +48 -0
- package/test/type_enforcement.pyj +164 -0
- package/test/unit/index.js +14 -6
- package/test/unit/language-service-completions.js +119 -0
- package/test/unit/language-service-dts.js +543 -543
- package/test/unit/language-service-hover.js +455 -455
- package/test/unit/language-service-scope.js +32 -0
- package/test/unit/language-service.js +127 -3
- package/test/unit/run-language-service.js +17 -3
- package/test/unit/web-repl.js +2094 -29
- package/test/urllib.pyj +193 -0
- package/tools/compile.js +1 -1
- package/tools/compiler.d.ts +367 -367
- package/tools/completer.js +131 -131
- package/tools/embedded_compiler.js +7 -7
- package/tools/gettext.js +185 -185
- package/tools/ini.js +65 -65
- package/tools/msgfmt.js +187 -187
- package/tools/repl.js +223 -223
- package/tools/test.js +118 -118
- package/tools/utils.js +128 -128
- package/tools/web_repl.js +95 -95
- package/try +41 -41
- package/web-repl/env.js +196 -196
- package/web-repl/index.html +163 -163
- package/web-repl/main.js +1 -1
- package/web-repl/prism.css +139 -139
- package/web-repl/prism.js +113 -113
- package/web-repl/rapydscript.js +224 -224
- package/web-repl/sha1.js +25 -25
- package/test/omit_function_metadata.pyj +0 -20
package/src/output/functions.pyj
CHANGED
|
@@ -45,11 +45,69 @@ def function_args(argnames, output, strip_first):
|
|
|
45
45
|
|
|
46
46
|
def function_preamble(node, output, offset):
|
|
47
47
|
a = node.argnames
|
|
48
|
-
if a is None or a is undefined
|
|
48
|
+
if a is None or a is undefined:
|
|
49
|
+
return
|
|
50
|
+
type_enforce = node.type_enforce
|
|
51
|
+
if a.is_simple_func and not type_enforce:
|
|
49
52
|
return
|
|
50
53
|
# If this function has optional parameters/*args/**kwargs declare it differently
|
|
51
54
|
fname = node.name.name if node.name else anonfunc
|
|
52
55
|
kw = 'arguments[arguments.length-1]'
|
|
56
|
+
|
|
57
|
+
# === Type enforcement: max positional-arg count check ===
|
|
58
|
+
if type_enforce:
|
|
59
|
+
max_pos = 0
|
|
60
|
+
for c, arg in enumerate(a):
|
|
61
|
+
if c >= offset and not arg.kwonly:
|
|
62
|
+
max_pos += 1
|
|
63
|
+
if a.is_simple_func:
|
|
64
|
+
# Simple function: named JS params; arguments.length gives true count
|
|
65
|
+
if not a.starargs:
|
|
66
|
+
# Strip a trailing kwargs-marker object so a `*args, **kwargs`
|
|
67
|
+
# caller does not get blamed for an extra positional argument
|
|
68
|
+
# (matches the complex-func variant below).
|
|
69
|
+
output.indent()
|
|
70
|
+
output.spaced('var', 'ρσ_nargs', '=', 'arguments.length')
|
|
71
|
+
output.end_statement()
|
|
72
|
+
output.indent()
|
|
73
|
+
output.print('if (ρσ_nargs > 0 && arguments[ρσ_nargs-1] !== null && typeof arguments[ρσ_nargs-1] === "object" && arguments[ρσ_nargs-1][ρσ_kwargs_symbol] === true) ρσ_nargs--')
|
|
74
|
+
output.end_statement()
|
|
75
|
+
max_msg = fname + '() takes ' + max_pos + ' positional argument' + ('' if max_pos == 1 else 's') + ' but '
|
|
76
|
+
output.indent()
|
|
77
|
+
output.print('if (ρσ_nargs > ' + max_pos + ') throw new TypeError(' + JSON.stringify(max_msg) + ' + ρσ_nargs + " were given")')
|
|
78
|
+
output.end_statement()
|
|
79
|
+
# Required-arg and type-annotation checks (variables are direct JS params)
|
|
80
|
+
for c, arg in enumerate(a):
|
|
81
|
+
if c < offset:
|
|
82
|
+
continue
|
|
83
|
+
if not arg.kwonly:
|
|
84
|
+
miss_msg = fname + '() missing required positional argument: \'' + arg.name + '\''
|
|
85
|
+
output.indent()
|
|
86
|
+
output.print('if (' + arg.name + ' === undefined) throw new TypeError(' + JSON.stringify(miss_msg) + ')')
|
|
87
|
+
output.end_statement()
|
|
88
|
+
if arg.annotation:
|
|
89
|
+
annot_name = arg.annotation.name or ''
|
|
90
|
+
type_msg = fname + '() argument \'' + arg.name + '\' must be ' + annot_name
|
|
91
|
+
output.indent()
|
|
92
|
+
output.print('if (' + arg.name + ' !== undefined && !ρσ_instanceof(' + arg.name + ', ')
|
|
93
|
+
arg.annotation.print(output)
|
|
94
|
+
output.print(')) throw new TypeError(' + JSON.stringify(type_msg) + ')')
|
|
95
|
+
output.end_statement()
|
|
96
|
+
return # simple func: only enforcement, no complex preamble
|
|
97
|
+
|
|
98
|
+
# Complex function: must handle arguments object; strip trailing kwargs marker
|
|
99
|
+
if not a.starargs:
|
|
100
|
+
output.indent()
|
|
101
|
+
output.spaced('var', 'ρσ_nargs', '=', 'arguments.length')
|
|
102
|
+
output.end_statement()
|
|
103
|
+
output.indent()
|
|
104
|
+
output.print('if (ρσ_nargs > 0 && arguments[ρσ_nargs-1] !== null && typeof arguments[ρσ_nargs-1] === "object" && arguments[ρσ_nargs-1][ρσ_kwargs_symbol] === true) ρσ_nargs--')
|
|
105
|
+
output.end_statement()
|
|
106
|
+
max_msg = fname + '() takes ' + max_pos + ' positional argument' + ('' if max_pos == 1 else 's') + ' but '
|
|
107
|
+
output.indent()
|
|
108
|
+
output.print('if (ρσ_nargs > ' + max_pos + ') throw new TypeError(' + JSON.stringify(max_msg) + ' + ρσ_nargs + " were given")')
|
|
109
|
+
output.end_statement()
|
|
110
|
+
|
|
53
111
|
# Define all formal parameters
|
|
54
112
|
positional_count = 0
|
|
55
113
|
for c, arg in enumerate(a):
|
|
@@ -89,6 +147,22 @@ def function_preamble(node, output, offset):
|
|
|
89
147
|
output.indent()
|
|
90
148
|
output.spaced('if', '(' + kw, '===', 'null', '||', 'typeof', kw, '!==', '"object"', '||', kw, '[ρσ_kwargs_symbol]', '!==', 'true)', kw, '=', '{}')
|
|
91
149
|
output.end_statement()
|
|
150
|
+
# === Type enforcement: positional-only args must not appear in kwargs ===
|
|
151
|
+
if type_enforce:
|
|
152
|
+
posonly_count = a.posonly_count or 0
|
|
153
|
+
for pi in range(posonly_count):
|
|
154
|
+
if pi < offset:
|
|
155
|
+
continue
|
|
156
|
+
parg = a[pi]
|
|
157
|
+
posonly_msg = fname + '() got some positional-only arguments passed as keyword arguments: \'' + parg.name + '\''
|
|
158
|
+
output.indent()
|
|
159
|
+
output.spaced('if', '(Object.prototype.hasOwnProperty.call(' + kw + ',', '"' + parg.name + '"))')
|
|
160
|
+
output.with_block(def():
|
|
161
|
+
output.indent()
|
|
162
|
+
output.print('throw new TypeError(' + JSON.stringify(posonly_msg) + ')')
|
|
163
|
+
output.end_statement()
|
|
164
|
+
)
|
|
165
|
+
output.newline()
|
|
92
166
|
# Read values from the kwargs object for non-positional-only formal parameters with defaults
|
|
93
167
|
if a.has_defaults:
|
|
94
168
|
for dname in Object.keys(a.defaults):
|
|
@@ -138,6 +212,29 @@ def function_preamble(node, output, offset):
|
|
|
138
212
|
output.end_statement()
|
|
139
213
|
)
|
|
140
214
|
output.newline()
|
|
215
|
+
elif type_enforce and (a.posonly_count or 0) > 0:
|
|
216
|
+
# No kwargs block, but function has positional-only params.
|
|
217
|
+
# A caller using foo(posonly_name=val) still passes a kwargs object as the
|
|
218
|
+
# last argument; detect and reject it here.
|
|
219
|
+
posonly_count = a.posonly_count or 0
|
|
220
|
+
posonly_names = []
|
|
221
|
+
for pi in range(posonly_count):
|
|
222
|
+
if pi >= offset:
|
|
223
|
+
posonly_names.push(a[pi].name)
|
|
224
|
+
if posonly_names.length > 0:
|
|
225
|
+
output.indent()
|
|
226
|
+
output.spaced('var', 'ρσ_kw_chk', '=', 'arguments[arguments.length-1]')
|
|
227
|
+
output.end_statement()
|
|
228
|
+
output.indent()
|
|
229
|
+
output.spaced('if', '(ρσ_kw_chk !== null && typeof ρσ_kw_chk === "object" && ρσ_kw_chk[ρσ_kwargs_symbol] === true)')
|
|
230
|
+
output.with_block(def():
|
|
231
|
+
for pname in posonly_names:
|
|
232
|
+
posonly_msg = fname + '() got some positional-only arguments passed as keyword arguments: \'' + pname + '\''
|
|
233
|
+
output.indent()
|
|
234
|
+
output.print('if (Object.prototype.hasOwnProperty.call(ρσ_kw_chk, "' + pname + '")) throw new TypeError(' + JSON.stringify(posonly_msg) + ')')
|
|
235
|
+
output.end_statement()
|
|
236
|
+
)
|
|
237
|
+
output.newline()
|
|
141
238
|
|
|
142
239
|
if a.starargs is not undefined:
|
|
143
240
|
# Define the *args parameter, putting in whatever is left after assigning the formal parameters and the options object
|
|
@@ -160,6 +257,44 @@ def function_preamble(node, output, offset):
|
|
|
160
257
|
output.spaced(kw, '=', 'ρσ_kwargs_to_dict(' + kw + ')')
|
|
161
258
|
output.end_statement()
|
|
162
259
|
|
|
260
|
+
# === Type enforcement: required-arg, required-kwonly, and type-annotation checks ===
|
|
261
|
+
# These run after all variable resolution (kwargs reading, starargs setup, dict conversion)
|
|
262
|
+
# so each variable holds its final value when checked.
|
|
263
|
+
if type_enforce:
|
|
264
|
+
# Required positional args (no default, not kwonly)
|
|
265
|
+
for c, arg in enumerate(a):
|
|
266
|
+
if c < offset:
|
|
267
|
+
continue
|
|
268
|
+
if arg.kwonly:
|
|
269
|
+
continue
|
|
270
|
+
if not Object.prototype.hasOwnProperty.call(a.defaults, arg.name):
|
|
271
|
+
miss_msg = fname + '() missing required positional argument: \'' + arg.name + '\''
|
|
272
|
+
output.indent()
|
|
273
|
+
output.print('if (' + arg.name + ' === undefined) throw new TypeError(' + JSON.stringify(miss_msg) + ')')
|
|
274
|
+
output.end_statement()
|
|
275
|
+
# Required keyword-only args (no default)
|
|
276
|
+
for c, arg in enumerate(a):
|
|
277
|
+
if not arg.kwonly:
|
|
278
|
+
continue
|
|
279
|
+
if not Object.prototype.hasOwnProperty.call(a.defaults, arg.name):
|
|
280
|
+
miss_kw = fname + '() missing required keyword-only argument: \'' + arg.name + '\''
|
|
281
|
+
output.indent()
|
|
282
|
+
output.print('if (' + arg.name + ' === undefined) throw new TypeError(' + JSON.stringify(miss_kw) + ')')
|
|
283
|
+
output.end_statement()
|
|
284
|
+
# Type annotation checks (skip *args and **kwargs)
|
|
285
|
+
for c, arg in enumerate(a):
|
|
286
|
+
if c < offset:
|
|
287
|
+
continue
|
|
288
|
+
if not arg.annotation:
|
|
289
|
+
continue
|
|
290
|
+
annot_name = arg.annotation.name or ''
|
|
291
|
+
type_msg = fname + '() argument \'' + arg.name + '\' must be ' + annot_name
|
|
292
|
+
output.indent()
|
|
293
|
+
output.print('if (' + arg.name + ' !== undefined && !ρσ_instanceof(' + arg.name + ', ')
|
|
294
|
+
arg.annotation.print(output)
|
|
295
|
+
output.print(')) throw new TypeError(' + JSON.stringify(type_msg) + ')')
|
|
296
|
+
output.end_statement()
|
|
297
|
+
|
|
163
298
|
def has_annotations(self):
|
|
164
299
|
if self.return_annotation:
|
|
165
300
|
return True
|
|
@@ -169,8 +304,6 @@ def has_annotations(self):
|
|
|
169
304
|
return False
|
|
170
305
|
|
|
171
306
|
def function_annotation(self, output, strip_first, name):
|
|
172
|
-
if output.options.omit_function_metadata:
|
|
173
|
-
return
|
|
174
307
|
fname = name or (self.name.name if self.name else anonfunc)
|
|
175
308
|
props = Object.create(None)
|
|
176
309
|
|
|
@@ -263,27 +396,37 @@ def function_definition(self, output, strip_first, as_expression):
|
|
|
263
396
|
output.set_indentation(output.next_indent())
|
|
264
397
|
output.spaced('(function()', '{'), output.newline()
|
|
265
398
|
output.indent(), output.spaced('var', anonfunc, '='), output.space()
|
|
266
|
-
|
|
399
|
+
# Async generators (async def with yield) are emitted as a sync wrapper that
|
|
400
|
+
# returns the async iterator directly (matching Python semantics, where calling
|
|
401
|
+
# an async generator function returns the iterator immediately without await).
|
|
402
|
+
# The inner js_generator is `async function*`. The outer wrapper is sync.
|
|
403
|
+
if self.is_async and not self.is_generator:
|
|
267
404
|
output.print("async"), output.space()
|
|
268
405
|
output.print("function"), output.space()
|
|
269
406
|
if self.name:
|
|
270
407
|
self.name.print(output)
|
|
271
408
|
|
|
272
409
|
if self.is_generator:
|
|
410
|
+
inner_decl = 'async function* js_generator' if self.is_async else 'function* js_generator'
|
|
273
411
|
output.print('()'), output.space()
|
|
274
412
|
output.with_block(def():
|
|
275
413
|
output.indent()
|
|
276
|
-
output.print(
|
|
414
|
+
output.print(inner_decl)
|
|
277
415
|
function_args(self.argnames, output, strip_first)
|
|
278
416
|
print_bracketed(self, output, True, function_preamble)
|
|
279
417
|
output.newline()
|
|
280
418
|
output.indent()
|
|
281
419
|
output.spaced('var', 'result', '=', 'js_generator.apply(this,', 'arguments)')
|
|
282
420
|
output.end_statement()
|
|
283
|
-
# Python's generator objects use a separate method to send data to the generator
|
|
421
|
+
# Python's generator objects use a separate method to send data to the generator.
|
|
422
|
+
# For async generators, asend() is the async equivalent — alias it to next as well.
|
|
284
423
|
output.indent()
|
|
285
424
|
output.spaced('result.send', '=', 'result.next')
|
|
286
425
|
output.end_statement()
|
|
426
|
+
if self.is_async:
|
|
427
|
+
output.indent()
|
|
428
|
+
output.spaced('result.asend', '=', 'result.next')
|
|
429
|
+
output.end_statement()
|
|
287
430
|
output.indent()
|
|
288
431
|
output.spaced('return', 'result')
|
|
289
432
|
output.end_statement()
|
|
@@ -443,12 +586,15 @@ def print_function_call(self, output):
|
|
|
443
586
|
)
|
|
444
587
|
elif is_node_type(self.expression, AST_SymbolRef) and self.expression.name is 'print':
|
|
445
588
|
# Bare print(...) → console.log(...) to avoid clobbering window.print
|
|
589
|
+
# Wrap each arg in ρσ_str() so type() and other objects display like Python
|
|
446
590
|
output.print('console.log')
|
|
447
591
|
output.with_parens(def():
|
|
448
592
|
for i, a in enumerate(self.args):
|
|
449
593
|
if i:
|
|
450
594
|
output.comma()
|
|
595
|
+
output.print('ρσ_str(')
|
|
451
596
|
a.print(output)
|
|
597
|
+
output.print(')')
|
|
452
598
|
)
|
|
453
599
|
elif (is_node_type(self.expression, AST_SymbolRef) and
|
|
454
600
|
self.expression.name is 'vars' and self.args.length is 0):
|
package/src/output/jsx.pyj
CHANGED
|
@@ -1,164 +1,164 @@
|
|
|
1
|
-
# vim:fileencoding=utf-8
|
|
2
|
-
# License: BSD Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net>
|
|
3
|
-
from __python__ import hash_literals
|
|
4
|
-
|
|
5
|
-
from ast import AST_String, AST_JSXText, AST_JSXSpread, AST_JSXExprContainer, is_node_type
|
|
6
|
-
|
|
7
|
-
def _is_component_tag(tag):
|
|
8
|
-
# Components start with uppercase or use dot notation (e.g. Router.Route)
|
|
9
|
-
first = tag[0]
|
|
10
|
-
return (first >= 'A' and first <= 'Z') or '.' in tag
|
|
11
|
-
|
|
12
|
-
def _needs_quoting(name):
|
|
13
|
-
# Attribute names containing non-identifier chars (e.g. aria-label) need quoting
|
|
14
|
-
return not v'/^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(name)'
|
|
15
|
-
|
|
16
|
-
def _decode_html_entities(text):
|
|
17
|
-
# Decode HTML entities in a single pass to avoid double-decoding (e.g. &lt; -> <)
|
|
18
|
-
return text.replace(/&(?:#x([0-9a-fA-F]+)|#(\d+)|([a-zA-Z]+));/g, def(match, hex, dec, name):
|
|
19
|
-
if hex:
|
|
20
|
-
return String.fromCharCode(parseInt(hex, 16))
|
|
21
|
-
if dec:
|
|
22
|
-
return String.fromCharCode(parseInt(dec, 10))
|
|
23
|
-
if name is 'amp':
|
|
24
|
-
return '&'
|
|
25
|
-
if name is 'lt':
|
|
26
|
-
return '<'
|
|
27
|
-
if name is 'gt':
|
|
28
|
-
return '>'
|
|
29
|
-
if name is 'quot':
|
|
30
|
-
return '"'
|
|
31
|
-
if name is 'apos':
|
|
32
|
-
return "'"
|
|
33
|
-
if name is 'nbsp':
|
|
34
|
-
return '\u00a0'
|
|
35
|
-
return match
|
|
36
|
-
)
|
|
37
|
-
|
|
38
|
-
def _normalize_jsx_whitespace(text):
|
|
39
|
-
# Implements the Babel JSX whitespace algorithm:
|
|
40
|
-
# - Split by newlines; trim leading whitespace from all lines except the first,
|
|
41
|
-
# trailing whitespace from all lines except the last.
|
|
42
|
-
# - Lines that are empty after trimming are dropped.
|
|
43
|
-
# - Remaining non-empty lines are joined; each line except the last non-empty
|
|
44
|
-
# gets a trailing space to separate it from the next.
|
|
45
|
-
lines = text.split('\n')
|
|
46
|
-
last_non_empty = -1
|
|
47
|
-
for i in range(lines.length):
|
|
48
|
-
if /[^ \t]/.test(lines[i]):
|
|
49
|
-
last_non_empty = i
|
|
50
|
-
result = ''
|
|
51
|
-
for i in range(lines.length):
|
|
52
|
-
line = lines[i].replace(/\t/g, ' ')
|
|
53
|
-
is_first = (i is 0)
|
|
54
|
-
is_last = (i is lines.length - 1)
|
|
55
|
-
if not is_first:
|
|
56
|
-
line = line.replace(/^[ ]+/, '')
|
|
57
|
-
if not is_last:
|
|
58
|
-
line = line.replace(/[ ]+$/, '')
|
|
59
|
-
if line:
|
|
60
|
-
if i is not last_non_empty:
|
|
61
|
-
line += ' '
|
|
62
|
-
result += line
|
|
63
|
-
return result
|
|
64
|
-
|
|
65
|
-
def _process_jsx_text(text):
|
|
66
|
-
text = _normalize_jsx_whitespace(text)
|
|
67
|
-
if text:
|
|
68
|
-
text = _decode_html_entities(text)
|
|
69
|
-
return text
|
|
70
|
-
|
|
71
|
-
def _print_tag(tag, output):
|
|
72
|
-
if _is_component_tag(tag):
|
|
73
|
-
output.print(tag)
|
|
74
|
-
else:
|
|
75
|
-
output.print('"')
|
|
76
|
-
output.print(tag)
|
|
77
|
-
output.print('"')
|
|
78
|
-
|
|
79
|
-
def _print_props(props, output):
|
|
80
|
-
if not props or not props.length:
|
|
81
|
-
output.print('null')
|
|
82
|
-
return
|
|
83
|
-
output.print('{')
|
|
84
|
-
first = True
|
|
85
|
-
for prop in props:
|
|
86
|
-
if not first:
|
|
87
|
-
output.print(', ')
|
|
88
|
-
first = False
|
|
89
|
-
if is_node_type(prop, AST_JSXSpread):
|
|
90
|
-
output.print('...')
|
|
91
|
-
prop.expression.print(output)
|
|
92
|
-
else:
|
|
93
|
-
if _needs_quoting(prop.name):
|
|
94
|
-
output.print('"')
|
|
95
|
-
output.print(prop.name)
|
|
96
|
-
output.print('"')
|
|
97
|
-
else:
|
|
98
|
-
output.print(prop.name)
|
|
99
|
-
output.print(': ')
|
|
100
|
-
if prop.value is None:
|
|
101
|
-
output.print('true')
|
|
102
|
-
elif is_node_type(prop.value, AST_String):
|
|
103
|
-
output.print_string(prop.value.value)
|
|
104
|
-
else:
|
|
105
|
-
prop.value.print(output)
|
|
106
|
-
output.print('}')
|
|
107
|
-
|
|
108
|
-
def _print_children(children, output):
|
|
109
|
-
for child in children:
|
|
110
|
-
if is_node_type(child, AST_JSXText):
|
|
111
|
-
text = _process_jsx_text(child.value)
|
|
112
|
-
if text:
|
|
113
|
-
output.print(', ')
|
|
114
|
-
output.print_string(text)
|
|
115
|
-
elif is_node_type(child, AST_JSXExprContainer):
|
|
116
|
-
output.print(', ')
|
|
117
|
-
child.expression.print(output)
|
|
118
|
-
else:
|
|
119
|
-
output.print(', ')
|
|
120
|
-
child.print(output)
|
|
121
|
-
|
|
122
|
-
def print_jsx_element(self, output):
|
|
123
|
-
output.print('React.createElement(')
|
|
124
|
-
_print_tag(self.tag, output)
|
|
125
|
-
output.print(', ')
|
|
126
|
-
_print_props(self.props, output)
|
|
127
|
-
if not self.self_closing:
|
|
128
|
-
_print_children(self.children, output)
|
|
129
|
-
output.print(')')
|
|
130
|
-
|
|
131
|
-
def print_jsx_fragment(self, output):
|
|
132
|
-
output.print('React.createElement(React.Fragment, null')
|
|
133
|
-
_print_children(self.children, output)
|
|
134
|
-
output.print(')')
|
|
135
|
-
|
|
136
|
-
def print_jsx_attribute(self, output):
|
|
137
|
-
# Handled directly by _print_props; kept for completeness
|
|
138
|
-
if _needs_quoting(self.name):
|
|
139
|
-
output.print('"')
|
|
140
|
-
output.print(self.name)
|
|
141
|
-
output.print('"')
|
|
142
|
-
else:
|
|
143
|
-
output.print(self.name)
|
|
144
|
-
if self.value is not None:
|
|
145
|
-
output.print(': ')
|
|
146
|
-
if is_node_type(self.value, AST_String):
|
|
147
|
-
output.print_string(self.value.value)
|
|
148
|
-
else:
|
|
149
|
-
self.value.print(output)
|
|
150
|
-
|
|
151
|
-
def print_jsx_spread(self, output):
|
|
152
|
-
# Handled directly by _print_props; kept for completeness
|
|
153
|
-
output.print('...')
|
|
154
|
-
self.expression.print(output)
|
|
155
|
-
|
|
156
|
-
def print_jsx_text(self, output):
|
|
157
|
-
# Handled directly by _print_children; kept for completeness
|
|
158
|
-
text = _process_jsx_text(self.value)
|
|
159
|
-
if text:
|
|
160
|
-
output.print_string(text)
|
|
161
|
-
|
|
162
|
-
def print_jsx_expr_container(self, output):
|
|
163
|
-
# Handled directly by _print_children; kept for completeness
|
|
164
|
-
self.expression.print(output)
|
|
1
|
+
# vim:fileencoding=utf-8
|
|
2
|
+
# License: BSD Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net>
|
|
3
|
+
from __python__ import hash_literals
|
|
4
|
+
|
|
5
|
+
from ast import AST_String, AST_JSXText, AST_JSXSpread, AST_JSXExprContainer, is_node_type
|
|
6
|
+
|
|
7
|
+
def _is_component_tag(tag):
|
|
8
|
+
# Components start with uppercase or use dot notation (e.g. Router.Route)
|
|
9
|
+
first = tag[0]
|
|
10
|
+
return (first >= 'A' and first <= 'Z') or '.' in tag
|
|
11
|
+
|
|
12
|
+
def _needs_quoting(name):
|
|
13
|
+
# Attribute names containing non-identifier chars (e.g. aria-label) need quoting
|
|
14
|
+
return not v'/^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(name)'
|
|
15
|
+
|
|
16
|
+
def _decode_html_entities(text):
|
|
17
|
+
# Decode HTML entities in a single pass to avoid double-decoding (e.g. &lt; -> <)
|
|
18
|
+
return text.replace(/&(?:#x([0-9a-fA-F]+)|#(\d+)|([a-zA-Z]+));/g, def(match, hex, dec, name):
|
|
19
|
+
if hex:
|
|
20
|
+
return String.fromCharCode(parseInt(hex, 16))
|
|
21
|
+
if dec:
|
|
22
|
+
return String.fromCharCode(parseInt(dec, 10))
|
|
23
|
+
if name is 'amp':
|
|
24
|
+
return '&'
|
|
25
|
+
if name is 'lt':
|
|
26
|
+
return '<'
|
|
27
|
+
if name is 'gt':
|
|
28
|
+
return '>'
|
|
29
|
+
if name is 'quot':
|
|
30
|
+
return '"'
|
|
31
|
+
if name is 'apos':
|
|
32
|
+
return "'"
|
|
33
|
+
if name is 'nbsp':
|
|
34
|
+
return '\u00a0'
|
|
35
|
+
return match
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
def _normalize_jsx_whitespace(text):
|
|
39
|
+
# Implements the Babel JSX whitespace algorithm:
|
|
40
|
+
# - Split by newlines; trim leading whitespace from all lines except the first,
|
|
41
|
+
# trailing whitespace from all lines except the last.
|
|
42
|
+
# - Lines that are empty after trimming are dropped.
|
|
43
|
+
# - Remaining non-empty lines are joined; each line except the last non-empty
|
|
44
|
+
# gets a trailing space to separate it from the next.
|
|
45
|
+
lines = text.split('\n')
|
|
46
|
+
last_non_empty = -1
|
|
47
|
+
for i in range(lines.length):
|
|
48
|
+
if /[^ \t]/.test(lines[i]):
|
|
49
|
+
last_non_empty = i
|
|
50
|
+
result = ''
|
|
51
|
+
for i in range(lines.length):
|
|
52
|
+
line = lines[i].replace(/\t/g, ' ')
|
|
53
|
+
is_first = (i is 0)
|
|
54
|
+
is_last = (i is lines.length - 1)
|
|
55
|
+
if not is_first:
|
|
56
|
+
line = line.replace(/^[ ]+/, '')
|
|
57
|
+
if not is_last:
|
|
58
|
+
line = line.replace(/[ ]+$/, '')
|
|
59
|
+
if line:
|
|
60
|
+
if i is not last_non_empty:
|
|
61
|
+
line += ' '
|
|
62
|
+
result += line
|
|
63
|
+
return result
|
|
64
|
+
|
|
65
|
+
def _process_jsx_text(text):
|
|
66
|
+
text = _normalize_jsx_whitespace(text)
|
|
67
|
+
if text:
|
|
68
|
+
text = _decode_html_entities(text)
|
|
69
|
+
return text
|
|
70
|
+
|
|
71
|
+
def _print_tag(tag, output):
|
|
72
|
+
if _is_component_tag(tag):
|
|
73
|
+
output.print(tag)
|
|
74
|
+
else:
|
|
75
|
+
output.print('"')
|
|
76
|
+
output.print(tag)
|
|
77
|
+
output.print('"')
|
|
78
|
+
|
|
79
|
+
def _print_props(props, output):
|
|
80
|
+
if not props or not props.length:
|
|
81
|
+
output.print('null')
|
|
82
|
+
return
|
|
83
|
+
output.print('{')
|
|
84
|
+
first = True
|
|
85
|
+
for prop in props:
|
|
86
|
+
if not first:
|
|
87
|
+
output.print(', ')
|
|
88
|
+
first = False
|
|
89
|
+
if is_node_type(prop, AST_JSXSpread):
|
|
90
|
+
output.print('...')
|
|
91
|
+
prop.expression.print(output)
|
|
92
|
+
else:
|
|
93
|
+
if _needs_quoting(prop.name):
|
|
94
|
+
output.print('"')
|
|
95
|
+
output.print(prop.name)
|
|
96
|
+
output.print('"')
|
|
97
|
+
else:
|
|
98
|
+
output.print(prop.name)
|
|
99
|
+
output.print(': ')
|
|
100
|
+
if prop.value is None:
|
|
101
|
+
output.print('true')
|
|
102
|
+
elif is_node_type(prop.value, AST_String):
|
|
103
|
+
output.print_string(prop.value.value)
|
|
104
|
+
else:
|
|
105
|
+
prop.value.print(output)
|
|
106
|
+
output.print('}')
|
|
107
|
+
|
|
108
|
+
def _print_children(children, output):
|
|
109
|
+
for child in children:
|
|
110
|
+
if is_node_type(child, AST_JSXText):
|
|
111
|
+
text = _process_jsx_text(child.value)
|
|
112
|
+
if text:
|
|
113
|
+
output.print(', ')
|
|
114
|
+
output.print_string(text)
|
|
115
|
+
elif is_node_type(child, AST_JSXExprContainer):
|
|
116
|
+
output.print(', ')
|
|
117
|
+
child.expression.print(output)
|
|
118
|
+
else:
|
|
119
|
+
output.print(', ')
|
|
120
|
+
child.print(output)
|
|
121
|
+
|
|
122
|
+
def print_jsx_element(self, output):
|
|
123
|
+
output.print('React.createElement(')
|
|
124
|
+
_print_tag(self.tag, output)
|
|
125
|
+
output.print(', ')
|
|
126
|
+
_print_props(self.props, output)
|
|
127
|
+
if not self.self_closing:
|
|
128
|
+
_print_children(self.children, output)
|
|
129
|
+
output.print(')')
|
|
130
|
+
|
|
131
|
+
def print_jsx_fragment(self, output):
|
|
132
|
+
output.print('React.createElement(React.Fragment, null')
|
|
133
|
+
_print_children(self.children, output)
|
|
134
|
+
output.print(')')
|
|
135
|
+
|
|
136
|
+
def print_jsx_attribute(self, output):
|
|
137
|
+
# Handled directly by _print_props; kept for completeness
|
|
138
|
+
if _needs_quoting(self.name):
|
|
139
|
+
output.print('"')
|
|
140
|
+
output.print(self.name)
|
|
141
|
+
output.print('"')
|
|
142
|
+
else:
|
|
143
|
+
output.print(self.name)
|
|
144
|
+
if self.value is not None:
|
|
145
|
+
output.print(': ')
|
|
146
|
+
if is_node_type(self.value, AST_String):
|
|
147
|
+
output.print_string(self.value.value)
|
|
148
|
+
else:
|
|
149
|
+
self.value.print(output)
|
|
150
|
+
|
|
151
|
+
def print_jsx_spread(self, output):
|
|
152
|
+
# Handled directly by _print_props; kept for completeness
|
|
153
|
+
output.print('...')
|
|
154
|
+
self.expression.print(output)
|
|
155
|
+
|
|
156
|
+
def print_jsx_text(self, output):
|
|
157
|
+
# Handled directly by _print_children; kept for completeness
|
|
158
|
+
text = _process_jsx_text(self.value)
|
|
159
|
+
if text:
|
|
160
|
+
output.print_string(text)
|
|
161
|
+
|
|
162
|
+
def print_jsx_expr_container(self, output):
|
|
163
|
+
# Handled directly by _print_children; kept for completeness
|
|
164
|
+
self.expression.print(output)
|
package/src/output/loops.pyj
CHANGED
|
@@ -119,7 +119,10 @@ def print_for_loop_body(output):
|
|
|
119
119
|
if not (self.simple_for_index or is_simple_for_in(self)):
|
|
120
120
|
# if we're using multiple iterators, unpack them
|
|
121
121
|
output.indent()
|
|
122
|
-
|
|
122
|
+
# `async for` always emits ES6+ `for await ... of` syntax (regardless
|
|
123
|
+
# of js_version), so the loop variable holds the value directly —
|
|
124
|
+
# not an integer index into an array.
|
|
125
|
+
if output.options.js_version is 5 and not self.is_async:
|
|
123
126
|
itervar = "ρσ_Iter" + output.index_counter + "[ρσ_Index" + output.index_counter + "]"
|
|
124
127
|
else:
|
|
125
128
|
itervar = "ρσ_Index" + output.index_counter
|
|
@@ -237,7 +240,19 @@ def print_for_in(self, output):
|
|
|
237
240
|
)
|
|
238
241
|
else:
|
|
239
242
|
# regular loop
|
|
240
|
-
if
|
|
243
|
+
if self.is_async:
|
|
244
|
+
# async for: emit native `for await ... of` regardless of js_version.
|
|
245
|
+
# Async iteration is an ES2018 feature; if the user wrote `async for`
|
|
246
|
+
# they have already opted into a modern runtime. The ρσ_Iterable
|
|
247
|
+
# bridge is skipped because async iterators don't expose .length
|
|
248
|
+
# or Map keys — the JS engine handles Symbol.asyncIterator natively.
|
|
249
|
+
itervar = "ρσ_Iter" + output.index_counter
|
|
250
|
+
output.assign("var " + itervar)
|
|
251
|
+
write_object()
|
|
252
|
+
output.end_statement()
|
|
253
|
+
output.indent()
|
|
254
|
+
output.spaced('for', 'await', '(var', 'ρσ_Index' + output.index_counter, 'of', itervar + ')')
|
|
255
|
+
elif output.options.js_version is 5:
|
|
241
256
|
output.assign("var ρσ_Iter" + output.index_counter)
|
|
242
257
|
output.print("ρσ_Iterable")
|
|
243
258
|
output.with_parens(write_object)
|
package/src/output/modules.pyj
CHANGED
|
@@ -94,7 +94,7 @@ def declare_exports(module_id, exports, output, docstrings):
|
|
|
94
94
|
def _inject_pythonize_strings(output):
|
|
95
95
|
str_funcs = ('capitalize strip lstrip rstrip islower isupper isspace lower upper swapcase title'
|
|
96
96
|
' center count endswith startswith find rfind index rindex format join ljust rjust'
|
|
97
|
-
' partition rpartition splitlines zfill').split(' ')
|
|
97
|
+
' partition rpartition replace splitlines zfill').split(' ')
|
|
98
98
|
output.newline()
|
|
99
99
|
output.indent()
|
|
100
100
|
output.print('(function(){var _f=' + JSON.stringify(str_funcs) + ';for(var _i=0;_i<_f.length;_i++)String.prototype[_f[_i]]=\u03c1\u03c3_str.prototype[_f[_i]];})()')
|
package/src/output/operators.pyj
CHANGED
|
@@ -321,6 +321,13 @@ def print_binary_op(self, output):
|
|
|
321
321
|
self.right.print(output)
|
|
322
322
|
output.print(')')
|
|
323
323
|
return
|
|
324
|
+
if self.operator is '%' and self.python_mod:
|
|
325
|
+
output.print('ρσ_op_mod_ns(')
|
|
326
|
+
self.left.print(output)
|
|
327
|
+
output.comma()
|
|
328
|
+
self.right.print(output)
|
|
329
|
+
output.print(')')
|
|
330
|
+
return
|
|
324
331
|
if function_ops[self.operator]:
|
|
325
332
|
output.print(function_ops[self.operator])
|
|
326
333
|
output.with_parens(def():
|
|
@@ -485,6 +492,14 @@ def print_assign(self, output):
|
|
|
485
492
|
self.right.print(output)
|
|
486
493
|
)
|
|
487
494
|
return
|
|
495
|
+
if self.operator is '%=' and self.python_mod:
|
|
496
|
+
output.assign(self.left)
|
|
497
|
+
output.print('ρσ_op_mod_ns(')
|
|
498
|
+
self.left.print(output)
|
|
499
|
+
output.comma()
|
|
500
|
+
self.right.print(output)
|
|
501
|
+
output.print(')')
|
|
502
|
+
return
|
|
488
503
|
if self.operator is '**=':
|
|
489
504
|
output.assign(self.left)
|
|
490
505
|
if output.options.js_version > 6:
|