rapydscript-ns 0.8.4 → 0.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (141) hide show
  1. package/.agignore +1 -1
  2. package/.github/workflows/ci.yml +38 -38
  3. package/=template.pyj +5 -5
  4. package/CHANGELOG.md +26 -0
  5. package/HACKING.md +103 -103
  6. package/LICENSE +24 -24
  7. package/README.md +716 -169
  8. package/TODO.md +7 -2
  9. package/add-toc-to-readme +2 -2
  10. package/bin/export +75 -75
  11. package/bin/rapydscript +70 -70
  12. package/bin/web-repl-export +102 -102
  13. package/build +2 -2
  14. package/language-service/index.js +36 -27
  15. package/package.json +1 -1
  16. package/publish.py +37 -37
  17. package/release/baselib-plain-pretty.js +2358 -168
  18. package/release/baselib-plain-ugly.js +73 -3
  19. package/release/compiler.js +6283 -3093
  20. package/release/signatures.json +31 -30
  21. package/session.vim +4 -4
  22. package/setup.cfg +2 -2
  23. package/src/ast.pyj +1 -0
  24. package/src/baselib-builtins.pyj +340 -2
  25. package/src/baselib-bytes.pyj +664 -0
  26. package/src/baselib-errors.pyj +1 -1
  27. package/src/baselib-internal.pyj +267 -60
  28. package/src/baselib-itertools.pyj +110 -97
  29. package/src/baselib-str.pyj +22 -4
  30. package/src/compiler.pyj +36 -36
  31. package/src/errors.pyj +30 -30
  32. package/src/lib/abc.pyj +317 -0
  33. package/src/lib/aes.pyj +646 -646
  34. package/src/lib/contextlib.pyj +379 -0
  35. package/src/lib/copy.pyj +120 -120
  36. package/src/lib/dataclasses.pyj +532 -0
  37. package/src/lib/datetime.pyj +712 -0
  38. package/src/lib/elementmaker.pyj +83 -83
  39. package/src/lib/encodings.pyj +126 -126
  40. package/src/lib/enum.pyj +125 -0
  41. package/src/lib/gettext.pyj +569 -569
  42. package/src/lib/io.pyj +500 -0
  43. package/src/lib/itertools.pyj +580 -580
  44. package/src/lib/json.pyj +227 -0
  45. package/src/lib/math.pyj +193 -193
  46. package/src/lib/operator.pyj +11 -11
  47. package/src/lib/pythonize.pyj +20 -20
  48. package/src/lib/random.pyj +118 -118
  49. package/src/lib/re.pyj +504 -470
  50. package/src/lib/react.pyj +74 -74
  51. package/src/lib/traceback.pyj +63 -63
  52. package/src/lib/typing.pyj +577 -0
  53. package/src/lib/uuid.pyj +77 -77
  54. package/src/monaco-language-service/builtins.js +14 -4
  55. package/src/monaco-language-service/diagnostics.js +19 -20
  56. package/src/monaco-language-service/dts.js +550 -550
  57. package/src/output/classes.pyj +62 -26
  58. package/src/output/comments.pyj +45 -45
  59. package/src/output/exceptions.pyj +201 -201
  60. package/src/output/functions.pyj +78 -5
  61. package/src/output/jsx.pyj +164 -164
  62. package/src/output/loops.pyj +5 -2
  63. package/src/output/operators.pyj +100 -34
  64. package/src/output/treeshake.pyj +182 -182
  65. package/src/output/utils.pyj +72 -72
  66. package/src/parse.pyj +80 -16
  67. package/src/string_interpolation.pyj +72 -72
  68. package/src/tokenizer.pyj +10 -5
  69. package/src/unicode_aliases.pyj +576 -576
  70. package/src/utils.pyj +192 -192
  71. package/test/_import_one.pyj +37 -37
  72. package/test/_import_two/__init__.pyj +11 -11
  73. package/test/_import_two/level2/deep.pyj +4 -4
  74. package/test/_import_two/other.pyj +6 -6
  75. package/test/_import_two/sub.pyj +13 -13
  76. package/test/abc.pyj +291 -0
  77. package/test/aes_vectors.pyj +421 -421
  78. package/test/annotations.pyj +80 -80
  79. package/test/arithmetic_nostrict.pyj +88 -0
  80. package/test/arithmetic_types.pyj +169 -0
  81. package/test/baselib.pyj +91 -0
  82. package/test/bytes.pyj +467 -0
  83. package/test/classes.pyj +1 -0
  84. package/test/comparison_ops.pyj +173 -0
  85. package/test/contextlib.pyj +362 -0
  86. package/test/dataclasses.pyj +253 -0
  87. package/test/datetime.pyj +500 -0
  88. package/test/debugger_stmt.pyj +41 -0
  89. package/test/decorators.pyj +77 -77
  90. package/test/docstrings.pyj +39 -39
  91. package/test/elementmaker_test.pyj +45 -45
  92. package/test/enum.pyj +134 -0
  93. package/test/eval_exec.pyj +56 -0
  94. package/test/format.pyj +148 -0
  95. package/test/functions.pyj +151 -151
  96. package/test/generators.pyj +41 -41
  97. package/test/generic.pyj +370 -370
  98. package/test/imports.pyj +72 -72
  99. package/test/internationalization.pyj +73 -73
  100. package/test/io.pyj +316 -0
  101. package/test/json.pyj +196 -0
  102. package/test/lint.pyj +164 -164
  103. package/test/loops.pyj +85 -85
  104. package/test/numpy.pyj +734 -734
  105. package/test/object.pyj +64 -0
  106. package/test/omit_function_metadata.pyj +20 -20
  107. package/test/python_compat.pyj +17 -15
  108. package/test/python_features.pyj +70 -15
  109. package/test/regexp.pyj +83 -55
  110. package/test/repl.pyj +121 -121
  111. package/test/scoped_flags.pyj +76 -76
  112. package/test/tuples.pyj +96 -0
  113. package/test/typing.pyj +469 -0
  114. package/test/unit/index.js +116 -7
  115. package/test/unit/language-service-dts.js +543 -543
  116. package/test/unit/language-service-hover.js +455 -455
  117. package/test/unit/language-service.js +84 -0
  118. package/test/unit/web-repl.js +1337 -1
  119. package/test/vars_locals_globals.pyj +94 -0
  120. package/tools/cli.js +558 -547
  121. package/tools/compile.js +224 -219
  122. package/tools/completer.js +131 -131
  123. package/tools/embedded_compiler.js +262 -251
  124. package/tools/gettext.js +185 -185
  125. package/tools/ini.js +65 -65
  126. package/tools/lint.js +16 -19
  127. package/tools/msgfmt.js +187 -187
  128. package/tools/repl.js +223 -223
  129. package/tools/test.js +118 -118
  130. package/tools/utils.js +128 -128
  131. package/tools/web_repl.js +95 -95
  132. package/try +41 -41
  133. package/web-repl/env.js +196 -196
  134. package/web-repl/index.html +163 -163
  135. package/web-repl/main.js +252 -252
  136. package/web-repl/prism.css +139 -139
  137. package/web-repl/prism.js +113 -113
  138. package/web-repl/rapydscript.js +224 -224
  139. package/web-repl/sha1.js +25 -25
  140. package/PYTHON_DIFFERENCES_REPORT.md +0 -291
  141. package/PYTHON_FEATURE_COVERAGE.md +0 -200
@@ -2,8 +2,9 @@
2
2
  # License: BSD Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net>
3
3
  from __python__ import hash_literals
4
4
 
5
- from ast import AST_ClassCall, AST_New, has_calls, AST_Dot, AST_SymbolRef, is_node_type
5
+ from ast import AST_ClassCall, AST_New, has_calls, AST_Dot, AST_SymbolRef, AST_String, is_node_type
6
6
  from output.stream import OutputStream
7
+ from parse import parse as ρσ_rs_parse
7
8
  from output.statements import print_bracketed
8
9
  from output.utils import create_doctring
9
10
  from output.operators import print_getattr
@@ -449,6 +450,73 @@ def print_function_call(self, output):
449
450
  output.comma()
450
451
  a.print(output)
451
452
  )
453
+ elif (is_node_type(self.expression, AST_SymbolRef) and
454
+ self.expression.name is 'vars' and self.args.length is 0):
455
+ # vars() with no args → vars(this) so it captures the caller's instance
456
+ output.print('ρσ_vars')
457
+ output.with_parens(def():
458
+ output.print('this')
459
+ )
460
+ elif (is_node_type(self.expression, AST_SymbolRef) and
461
+ (self.expression.name is 'eval' or self.expression.name is 'exec') and
462
+ self.args.length >= 1 and is_node_type(self.args[0], AST_String)):
463
+ # Compile-time RapydScript → JS transformation for eval/exec string literals.
464
+ # The RapydScript source string is parsed and compiled to JS now; the emitted
465
+ # call receives the compiled JS string instead of the original RapydScript.
466
+ fn_name = self.expression.name
467
+ source = self.args[0].value
468
+ inner_ast = ρσ_rs_parse(source, {
469
+ 'filename': '<eval>',
470
+ 'module_id': '__eval__',
471
+ 'for_linting': True,
472
+ 'discard_asserts': output.options.discard_asserts,
473
+ })
474
+ inner_os = OutputStream({
475
+ 'beautify': False,
476
+ 'js_version': output.options.js_version,
477
+ 'private_scope': False,
478
+ 'write_name': False,
479
+ 'omit_baselib': True,
480
+ 'discard_asserts': output.options.discard_asserts,
481
+ })
482
+ inner_ast.print(inner_os)
483
+ compiled_js = v'inner_os.get().trim()'
484
+
485
+ def _print_extra_args():
486
+ for a in self.args.slice(1):
487
+ output.comma()
488
+ a.print(output)
489
+
490
+ if fn_name is 'eval' and self.args.length is 1:
491
+ # eval("expr") → native direct eval for scope access
492
+ output.print('eval')
493
+ output.with_parens(def():
494
+ output.print(JSON.stringify(compiled_js))
495
+ )
496
+ elif fn_name is 'eval':
497
+ # eval("expr", globals, locals) → ρσ_eval(compiled_js, globals, locals)
498
+ output.print('ρσ_eval')
499
+ output.with_parens(def():
500
+ output.print(JSON.stringify(compiled_js))
501
+ _print_extra_args()
502
+ )
503
+ else:
504
+ # exec("code", ...) → ρσ_exec(compiled_js, ...)
505
+ output.print('ρσ_exec')
506
+ output.with_parens(def():
507
+ output.print(JSON.stringify(compiled_js))
508
+ _print_extra_args()
509
+ )
510
+ elif is_node_type(self.expression, AST_SymbolRef) and self.expression.name is 'eval' and self.args.length > 1:
511
+ # eval(dynamic_expr, globals, locals) → ρσ_eval(dynamic_expr, globals, locals)
512
+ # The 1-arg form eval(expr) is left as native JS direct eval for scope access.
513
+ output.print('ρσ_eval')
514
+ output.with_parens(def():
515
+ for i, a in enumerate(self.args):
516
+ if i:
517
+ output.comma()
518
+ a.print(output)
519
+ )
452
520
  elif is_node_type(self.expression, AST_SymbolRef) and self.python_truthiness:
453
521
  # __call__-aware dispatch when from __python__ import truthiness is active
454
522
  output.print('ρσ_callable_call(')
@@ -461,10 +529,15 @@ def print_function_call(self, output):
461
529
  # Method calls and other complex expressions — keep existing behaviour
462
530
  print_function_name()
463
531
  output.with_parens(def():
464
- for i, a in enumerate(self.args):
465
- if i:
466
- output.comma()
467
- a.print(output)
532
+ # .split() with no args: default to ' ' so JS splits on space
533
+ # rather than returning [whole_string] (native JS no-arg behavior)
534
+ if is_node_type(self.expression, AST_Dot) and self.expression.property is 'split' and not self.args.length:
535
+ output.print('" "')
536
+ else:
537
+ for i, a in enumerate(self.args):
538
+ if i:
539
+ output.comma()
540
+ a.print(output)
468
541
  )
469
542
  return
470
543
 
@@ -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. &amp;lt; -> &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. &amp;lt; -> &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)
@@ -456,10 +456,13 @@ def print_list_comprehension(self, output):
456
456
  body_out.print(result_obj)
457
457
  # Locally scope all loop variables (outer and inner clauses)
458
458
  def decl_loop_vars(init_node):
459
+ # Recursively declare all leaf variable names from nested tuples/seqs
459
460
  if is_node_type(init_node, AST_Array):
460
461
  for i in init_node.elements:
461
- body_out.comma()
462
- i.print(body_out)
462
+ decl_loop_vars(i)
463
+ elif is_node_type(init_node, AST_Seq):
464
+ for i in init_node.to_array():
465
+ decl_loop_vars(i)
463
466
  else:
464
467
  body_out.comma()
465
468
  init_node.print(body_out)
@@ -43,7 +43,7 @@ def print_getitem(self, output): # AST_Sub
43
43
  expr.print(output)
44
44
  output.print('['), prop.print(output), output.print(']')
45
45
  return
46
- is_negative_number = is_node_type(prop, AST_Unary) and prop.operator is "-" and is_node_type(prop.expression, AST_Number)
46
+ is_negative_number = is_node_type(prop, AST_Unary) and prop.operator is "-" and is_node_type(prop.expression, AST_Number) and not prop.overloaded
47
47
  is_repeatable = is_node_type(expr, AST_SymbolRef)
48
48
  if is_repeatable:
49
49
  expr.print(output)
@@ -187,7 +187,7 @@ function_ops = {
187
187
  'nin': '!ρσ_in',
188
188
  }
189
189
 
190
- # Maps binary operators to their overloading helper function names.
190
+ # Maps binary operators to their overloading helper function names (strict_arithmetic on).
191
191
  overloaded_binary_ops = {
192
192
  '+': 'ρσ_op_add',
193
193
  '-': 'ρσ_op_sub',
@@ -201,9 +201,34 @@ overloaded_binary_ops = {
201
201
  '^': 'ρσ_op_xor',
202
202
  '<<': 'ρσ_op_lshift',
203
203
  '>>': 'ρσ_op_rshift',
204
+ '<': 'ρσ_op_lt',
205
+ '>': 'ρσ_op_gt',
206
+ '<=': 'ρσ_op_le',
207
+ '>=': 'ρσ_op_ge',
204
208
  }
205
209
 
206
- # Maps augmented-assignment operators to their overloading helper names.
210
+ # Maps binary operators to no-strict helpers (overload_operators on, strict_arithmetic off).
211
+ # Operators that never threw TypeError reuse the same helper; others use _ns variants.
212
+ overloaded_binary_ops_ns = {
213
+ '+': 'ρσ_op_add_ns',
214
+ '-': 'ρσ_op_sub_ns',
215
+ '*': 'ρσ_op_mul_ns',
216
+ '/': 'ρσ_op_truediv_ns',
217
+ '//': 'ρσ_op_floordiv_ns',
218
+ '%': 'ρσ_op_mod_ns',
219
+ '**': 'ρσ_op_pow_ns',
220
+ '&': 'ρσ_op_and',
221
+ '|': 'ρσ_op_or',
222
+ '^': 'ρσ_op_xor',
223
+ '<<': 'ρσ_op_lshift',
224
+ '>>': 'ρσ_op_rshift',
225
+ '<': 'ρσ_op_lt_ns',
226
+ '>': 'ρσ_op_gt_ns',
227
+ '<=': 'ρσ_op_le_ns',
228
+ '>=': 'ρσ_op_ge_ns',
229
+ }
230
+
231
+ # Maps augmented-assignment operators to their overloading helper names (strict_arithmetic on).
207
232
  overloaded_augmented_ops = {
208
233
  '+=': 'ρσ_op_iadd',
209
234
  '-=': 'ρσ_op_isub',
@@ -219,10 +244,78 @@ overloaded_augmented_ops = {
219
244
  '>>=': 'ρσ_op_irshift',
220
245
  }
221
246
 
247
+ # Maps augmented-assignment operators to no-strict helpers (strict_arithmetic off).
248
+ overloaded_augmented_ops_ns = {
249
+ '+=': 'ρσ_op_iadd_ns',
250
+ '-=': 'ρσ_op_isub_ns',
251
+ '*=': 'ρσ_op_imul_ns',
252
+ '/=': 'ρσ_op_itruediv_ns',
253
+ '//=': 'ρσ_op_ifloordiv_ns',
254
+ '%=': 'ρσ_op_imod_ns',
255
+ '**=': 'ρσ_op_ipow_ns',
256
+ '&=': 'ρσ_op_iand',
257
+ '|=': 'ρσ_op_ior',
258
+ '^=': 'ρσ_op_ixor',
259
+ '<<=': 'ρσ_op_ilshift',
260
+ '>>=': 'ρσ_op_irshift',
261
+ }
262
+
222
263
 
223
264
  def print_binary_op(self, output):
224
- if self.overloaded and overloaded_binary_ops[self.operator]:
225
- output.print(overloaded_binary_ops[self.operator] + '(')
265
+ if comparators[self.operator] and is_node_type(self.left, AST_Binary) and comparators[self.left.operator]:
266
+ # A chained comparison such as a < b < c
267
+ # With overload_operators: ρσ_op_lt(a, b) && ρσ_op_lt(b, c)
268
+ # Without: a < b && b < c
269
+ if is_node_type(self.left.right, AST_Symbol):
270
+ # Middle is a simple variable — no caching needed.
271
+ # self.left.print recurses into print_binary_op and handles overloaded case.
272
+ self.left.print(output)
273
+ leftvar = self.left.right.name
274
+ else:
275
+ # Middle is a complex expression — must cache to avoid double evaluation.
276
+ left_ops_map = overloaded_binary_ops if self.left.strict_arith else overloaded_binary_ops_ns
277
+ if self.left.overloaded and left_ops_map[self.left.operator]:
278
+ output.print(left_ops_map[self.left.operator] + '(')
279
+ self.left.left.print(output)
280
+ output.comma()
281
+ output.with_parens(def():
282
+ nonlocal leftvar
283
+ output.assign("ρσ_cond_temp")
284
+ self.left.right.print(output)
285
+ leftvar = "ρσ_cond_temp"
286
+ )
287
+ output.print(')')
288
+ else:
289
+ self.left.left.print(output)
290
+ output.space()
291
+ output.print(self.left.operator)
292
+ output.space()
293
+ output.with_parens(def():
294
+ nonlocal leftvar
295
+ output.assign("ρσ_cond_temp")
296
+ self.left.right.print(output)
297
+ leftvar = "ρσ_cond_temp"
298
+ )
299
+ ops_map = overloaded_binary_ops if self.strict_arith else overloaded_binary_ops_ns
300
+ output.space()
301
+ output.print("&&")
302
+ output.space()
303
+ if self.overloaded and ops_map[self.operator]:
304
+ output.print(ops_map[self.operator] + '(')
305
+ output.print(leftvar)
306
+ output.comma()
307
+ self.right.print(output)
308
+ output.print(')')
309
+ else:
310
+ output.print(leftvar)
311
+ output.space()
312
+ output.print(self.operator)
313
+ output.space()
314
+ self.right.print(output)
315
+ return
316
+ ops_map = overloaded_binary_ops if self.strict_arith else overloaded_binary_ops_ns
317
+ if self.overloaded and ops_map[self.operator]:
318
+ output.print(ops_map[self.operator] + '(')
226
319
  self.left.print(output)
227
320
  output.comma()
228
321
  self.right.print(output)
@@ -235,34 +328,6 @@ def print_binary_op(self, output):
235
328
  output.comma()
236
329
  self.right.print(output)
237
330
  )
238
- elif comparators[self.operator] and is_node_type(self.left, AST_Binary) and comparators[self.left.operator]:
239
- # A chained comparison such as a < b < c
240
- if is_node_type(self.left.right, AST_Symbol):
241
- # left side compares against a regular variable,
242
- # no caching needed
243
- self.left.print(output)
244
- leftvar = self.left.right.name
245
- else:
246
- # some logic is being performed, let's cache it
247
- self.left.left.print(output)
248
- output.space()
249
- output.print(self.left.operator)
250
- output.space()
251
- output.with_parens(def():
252
- nonlocal leftvar
253
- output.assign("ρσ_cond_temp")
254
- self.left.right.print(output)
255
- leftvar = "ρσ_cond_temp"
256
- )
257
-
258
- output.space()
259
- output.print("&&")
260
- output.space()
261
- output.print(leftvar)
262
- output.space()
263
- output.print(self.operator)
264
- output.space()
265
- self.right.print(output)
266
331
  elif self.operator is '//':
267
332
  output.print('Math.floor')
268
333
  output.with_parens(def():
@@ -400,7 +465,8 @@ def print_assignment(self, output):
400
465
 
401
466
  def print_assign(self, output):
402
467
  if self.overloaded and overloaded_augmented_ops[self.operator]:
403
- helper = overloaded_augmented_ops[self.operator]
468
+ aug_map = overloaded_augmented_ops if self.strict_arith else overloaded_augmented_ops_ns
469
+ helper = aug_map[self.operator]
404
470
  output.assign(self.left)
405
471
  output.print(helper + '(')
406
472
  self.left.print(output)