rapydscript-ns 0.8.1 → 0.8.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.
Files changed (58) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/CONTRIBUTORS +3 -2
  3. package/PYTHON_DIFFERENCES_REPORT.md +291 -0
  4. package/PYTHON_FEATURE_COVERAGE.md +200 -0
  5. package/README.md +480 -79
  6. package/TODO.md +6 -318
  7. package/hack_demo.pyj +112 -0
  8. package/language-service/index.js +4474 -0
  9. package/language-service/language-service.d.ts +40 -0
  10. package/package.json +9 -10
  11. package/src/ast.pyj +30 -6
  12. package/src/baselib-builtins.pyj +181 -11
  13. package/src/baselib-containers.pyj +154 -5
  14. package/src/baselib-errors.pyj +3 -0
  15. package/src/baselib-internal.pyj +40 -1
  16. package/src/baselib-str.pyj +42 -1
  17. package/src/lib/collections.pyj +1 -1
  18. package/src/lib/numpy.pyj +10 -10
  19. package/src/monaco-language-service/analyzer.js +132 -22
  20. package/src/monaco-language-service/builtins.js +22 -2
  21. package/src/monaco-language-service/completions.js +224 -3
  22. package/src/monaco-language-service/diagnostics.js +55 -5
  23. package/src/monaco-language-service/index.js +26 -5
  24. package/src/monaco-language-service/scope.js +3 -0
  25. package/src/output/classes.pyj +20 -3
  26. package/src/output/codegen.pyj +38 -3
  27. package/src/output/functions.pyj +35 -25
  28. package/src/output/loops.pyj +64 -11
  29. package/src/output/modules.pyj +1 -4
  30. package/src/output/operators.pyj +67 -1
  31. package/src/output/statements.pyj +7 -3
  32. package/src/output/stream.pyj +6 -13
  33. package/src/parse.pyj +94 -14
  34. package/src/tokenizer.pyj +1 -0
  35. package/test/baselib.pyj +4 -4
  36. package/test/classes.pyj +56 -17
  37. package/test/collections.pyj +5 -5
  38. package/test/python_compat.pyj +326 -0
  39. package/test/python_features.pyj +1271 -0
  40. package/test/slice.pyj +105 -0
  41. package/test/str.pyj +25 -0
  42. package/test/unit/fixtures/fibonacci_expected.js +1 -1
  43. package/test/unit/index.js +119 -7
  44. package/test/unit/language-service-builtins.js +70 -0
  45. package/test/unit/language-service-bundle.js +83 -0
  46. package/test/unit/language-service-completions.js +289 -0
  47. package/test/unit/language-service-index.js +350 -0
  48. package/test/unit/language-service-scope.js +255 -0
  49. package/test/unit/language-service.js +158 -1
  50. package/test/unit/run-language-service.js +2 -0
  51. package/test/unit/web-repl.js +134 -0
  52. package/tools/build-language-service.js +2 -2
  53. package/tools/compiler.js +0 -24
  54. package/tools/export.js +3 -37
  55. package/tools/lint.js +1 -1
  56. package/tools/self.js +1 -9
  57. package/web-repl/rapydscript.js +6 -40
  58. package/web-repl/language-service.js +0 -4084
@@ -1,6 +1,5 @@
1
1
  # vim:fileencoding=utf-8
2
2
  # License: BSD Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net>
3
- # globals:regenerate
4
3
  from __python__ import hash_literals
5
4
 
6
5
  from ast import AST_ClassCall, AST_New, has_calls, AST_Dot, AST_SymbolRef, is_node_type
@@ -45,7 +44,7 @@ def function_args(argnames, output, strip_first):
45
44
 
46
45
  def function_preamble(node, output, offset):
47
46
  a = node.argnames
48
- if not a or a.is_simple_func:
47
+ if a is None or a is undefined or a.is_simple_func:
49
48
  return
50
49
  # If this function has optional parameters/*args/**kwargs declare it differently
51
50
  fname = node.name.name if node.name else anonfunc
@@ -263,21 +262,10 @@ def function_definition(self, output, strip_first, as_expression):
263
262
  if self.is_generator:
264
263
  output.print('()'), output.space()
265
264
  output.with_block(def():
266
- if output.options.js_version >= 6:
267
- output.indent()
268
- output.print('function* js_generator')
269
- function_args(self.argnames, output, strip_first)
270
- print_bracketed(self, output, True, function_preamble)
271
- else:
272
- temp = OutputStream({'beautify':True})
273
- temp.print('function* js_generator')
274
- function_args(self.argnames, temp, strip_first)
275
- print_bracketed(self, temp, True, function_preamble)
276
- transpiled = regenerate(temp.get(), output.options.beautify).replace(/regeneratorRuntime.(wrap|mark)/g, 'ρσ_regenerator.regeneratorRuntime.$1')
277
- if output.options.beautify:
278
- ci = output.make_indent(0)
279
- transpiled = [ci + x for x in transpiled.split('\n')].join('\n')
280
- output.print(transpiled)
265
+ output.indent()
266
+ output.print('function* js_generator')
267
+ function_args(self.argnames, output, strip_first)
268
+ print_bracketed(self, output, True, function_preamble)
281
269
  output.newline()
282
270
  output.indent()
283
271
  output.spaced('var', 'result', '=', 'js_generator.apply(this,', 'arguments)')
@@ -433,20 +421,42 @@ def print_function_call(self, output):
433
421
  return # new A is the same as new A() in javascript
434
422
 
435
423
  if not has_kwargs and not self.args.starargs:
436
- # A simple function call, do nothing special
424
+ # A simple function call
437
425
  if is_new:
438
426
  output.print('new'), output.space()
439
- # Bare print(...) → console.log(...) to avoid clobbering window.print
440
- if not is_new and is_node_type(self.expression, AST_SymbolRef) and self.expression.name is 'print':
427
+ print_function_name()
428
+ output.with_parens(def():
429
+ for i, a in enumerate(self.args):
430
+ if i:
431
+ output.comma()
432
+ a.print(output)
433
+ )
434
+ elif is_node_type(self.expression, AST_SymbolRef) and self.expression.name is 'print':
435
+ # Bare print(...) → console.log(...) to avoid clobbering window.print
441
436
  output.print('console.log')
442
- else:
437
+ output.with_parens(def():
438
+ for i, a in enumerate(self.args):
439
+ if i:
440
+ output.comma()
441
+ a.print(output)
442
+ )
443
+ elif is_node_type(self.expression, AST_SymbolRef) and self.python_truthiness:
444
+ # __call__-aware dispatch when from __python__ import truthiness is active
445
+ output.print('ρσ_callable_call(')
443
446
  print_function_name()
444
- output.with_parens(def():
445
447
  for i, a in enumerate(self.args):
446
- if i:
447
- output.comma()
448
+ output.comma()
448
449
  a.print(output)
449
- )
450
+ output.print(')')
451
+ else:
452
+ # Method calls and other complex expressions — keep existing behaviour
453
+ print_function_name()
454
+ output.with_parens(def():
455
+ for i, a in enumerate(self.args):
456
+ if i:
457
+ output.comma()
458
+ a.print(output)
459
+ )
450
460
  return
451
461
 
452
462
  is_repeatable = is_new or not has_calls(self.expression)
@@ -1,6 +1,5 @@
1
1
  # vim:fileencoding=utf-8
2
2
  # License: BSD Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net>
3
- # globals: regenerate
4
3
  from __python__ import hash_literals
5
4
 
6
5
  from ast import AST_BaseCall, AST_SymbolRef, AST_Array, AST_Unary, AST_Number, has_calls, AST_Seq, AST_ListComprehension, AST_Starred, is_node_type
@@ -47,16 +46,49 @@ def print_do_loop(self, output):
47
46
  output.space()
48
47
  output.print("while")
49
48
  output.space()
50
- output.with_parens(def(): self.condition.print(output);)
49
+ if self.python_truthiness:
50
+ output.with_parens(def(): output.print('ρσ_bool('), self.condition.print(output), output.print(')');)
51
+ else:
52
+ output.with_parens(def(): self.condition.print(output);)
51
53
  output.semicolon()
52
54
 
53
55
  def print_while_loop(self, output):
56
+ # while/else: wrap in a labeled block; break inside will skip the else
57
+ forelse_label = None
58
+ if self.belse:
59
+ forelse_label = output.new_forelse_label()
60
+ output.forelse_stack.push(forelse_label)
61
+ output.print(forelse_label + ':')
62
+ output.space()
63
+ output.print('{')
64
+ output.newline()
65
+ output.set_indentation(output.next_indent())
66
+ output.indent()
67
+ else:
68
+ output.forelse_stack.push(None)
69
+
54
70
  output.print("while")
55
71
  output.space()
56
- output.with_parens(def(): self.condition.print(output);)
72
+ if self.python_truthiness:
73
+ output.with_parens(def(): output.print('ρσ_bool('), self.condition.print(output), output.print(')');)
74
+ else:
75
+ output.with_parens(def(): self.condition.print(output);)
57
76
  output.space()
58
77
  self._do_print_body(output)
59
78
 
79
+ output.forelse_stack.pop()
80
+
81
+ if forelse_label:
82
+ if self.belse and self.belse.body and self.belse.body.length:
83
+ output.newline()
84
+ for stmt in self.belse.body:
85
+ output.indent()
86
+ stmt.print(output)
87
+ output.newline()
88
+ output.set_indentation(output._indentation - output.options.indent_level)
89
+ output.indent()
90
+ output.print('}')
91
+
60
92
  def is_simple_for_in(self):
61
93
  # return true if this loop can be simplified into a basic for (i in j) loop
62
94
  if is_node_type(self.object, AST_BaseCall)
@@ -131,6 +163,21 @@ def print_for_in(self, output):
131
163
  else:
132
164
  self.object.print(output)
133
165
 
166
+ # for/else: wrap in a labeled block; break inside will use the label to skip else
167
+ forelse_label = None
168
+ if self.belse:
169
+ forelse_label = output.new_forelse_label()
170
+ output.forelse_stack.push(forelse_label)
171
+ output.print(forelse_label + ':')
172
+ output.space()
173
+ output.print('{')
174
+ output.newline()
175
+ output.set_indentation(output.next_indent())
176
+ output.indent()
177
+ else:
178
+ # Push null so nested for loops don't mistakenly grab an outer forelse label
179
+ output.forelse_stack.push(None)
180
+
134
181
  if is_simple_for(self):
135
182
  # optimize range() into a simple for loop
136
183
  increment = None
@@ -227,6 +274,20 @@ def print_for_in(self, output):
227
274
  output.space()
228
275
  self._do_print_body(output)
229
276
 
277
+ output.forelse_stack.pop()
278
+
279
+ if forelse_label:
280
+ # Emit else body, then close the labeled block
281
+ if self.belse and self.belse.body and self.belse.body.length:
282
+ output.newline()
283
+ for stmt in self.belse.body:
284
+ output.indent()
285
+ stmt.print(output)
286
+ output.newline()
287
+ output.set_indentation(output._indentation - output.options.indent_level)
288
+ output.indent()
289
+ output.print('}')
290
+
230
291
  def print_list_comprehension(self, output):
231
292
  tname = self.constructor.name.slice(4)
232
293
  result_obj = {'ListComprehension':'[]', 'DictComprehension':('Object.create(null)' if self.is_jshash else '{}'), 'SetComprehension':'ρσ_set()'}[tname]
@@ -374,8 +435,6 @@ def print_list_comprehension(self, output):
374
435
  output.with_block(def():
375
436
  body_out = output
376
437
  if is_generator:
377
- if es5:
378
- body_out = OutputStream({'beautify':True})
379
438
  body_out.indent()
380
439
  body_out.print('function* js_generator()'), body_out.space(), body_out.print('{')
381
440
  body_out.newline()
@@ -423,12 +482,6 @@ def print_list_comprehension(self, output):
423
482
  if is_generator:
424
483
  output.set_indentation(previous_indentation)
425
484
  body_out.newline(), body_out.indent(), body_out.print('}') # end js_generator
426
- if es5:
427
- transpiled = regenerate(body_out.get(), output.options.beautify).replace(/regeneratorRuntime.(wrap|mark)/g, 'ρσ_regenerator.regeneratorRuntime.$1')
428
- if output.options.beautify:
429
- ci = output.make_indent(0)
430
- transpiled = [ci + x for x in transpiled.split('\n')].join('\n')
431
- output.print(transpiled)
432
485
  output.newline(), output.indent()
433
486
  output.spaced('var', 'result', '=', 'js_generator.call(this)')
434
487
  output.end_statement()
@@ -18,7 +18,7 @@ def write_imports(module, output):
18
18
  imports = []
19
19
  for import_id in Object.keys(module.imports):
20
20
  imports.push(module.imports[import_id])
21
- imports.sort(def(a, b):
21
+ imports.jssort(def(a, b):
22
22
  a, b = a.import_order, b.import_order
23
23
  return -1 if a < b else (1 if a > b else 0)
24
24
  )
@@ -122,9 +122,6 @@ def prologue(module, output):
122
122
  output.indent(), output.spaced('if(', 'typeof', 'HTMLCollection', '!==', '"undefined"', '&&', 'typeof', 'Symbol', '===', '"function")',
123
123
  'NodeList.prototype[Symbol.iterator]', '=', 'HTMLCollection.prototype[Symbol.iterator]', '=', 'NamedNodeMap.prototype[Symbol.iterator]', '=', 'Array.prototype[Symbol.iterator]')
124
124
  output.end_statement()
125
- needs_yield = output.options.js_version < 6 and module.baselib['yield']
126
- if needs_yield:
127
- output.dump_yield()
128
125
  # output the baselib
129
126
  if not output.options.baselib_plain:
130
127
  raise ValueError('The baselib is missing! Remember to set the baselib_plain field on the options for OutputStream')
@@ -110,6 +110,16 @@ def print_unary_prefix(self, output):
110
110
  if op is '~':
111
111
  output.print('ρσ_op_invert('), self.expression.print(output), output.print(')')
112
112
  return
113
+ if op is '!' and self.python_truthiness:
114
+ output.print('!ρσ_bool(')
115
+ if self.parenthesized:
116
+ output.with_parens(def():
117
+ self.expression.print(output)
118
+ )
119
+ else:
120
+ self.expression.print(output)
121
+ output.print(')')
122
+ return
113
123
  output.print(op)
114
124
  if /^[a-z]/i.test(op):
115
125
  output.space()
@@ -270,6 +280,12 @@ def print_binary_op(self, output):
270
280
  write_smart_equality(self, output)
271
281
  elif self.operator is 'instanceof':
272
282
  write_instanceof(self.left, self.right, output)
283
+ elif self.operator is '+':
284
+ output.print('ρσ_list_add(')
285
+ self.left.print(output)
286
+ output.comma()
287
+ self.right.print(output)
288
+ output.print(')')
273
289
  elif self.operator is '*' and is_node_type(self.left, AST_String):
274
290
  self.left.print(output), output.print('.repeat('), self.right.print(output), output.print(')')
275
291
  elif self.operator is '===' or self.operator is '!==':
@@ -284,6 +300,36 @@ def print_binary_op(self, output):
284
300
  output.spaced(nan_check, '!==' if self.operator is '===' else '===', nan_check)
285
301
  else:
286
302
  output.spaced(self.left, self.operator, self.right)
303
+ elif (self.operator is '&&' or self.operator is '||') and self.python_truthiness:
304
+ # Python `and`/`or` use Python truthiness (only when from __python__ import truthiness is set).
305
+ # `a and b` → `ρσ_bool(a) ? b : a` (returns a if falsy, else b)
306
+ # `a or b` → `ρσ_bool(a) ? a : b` (returns a if truthy, else b)
307
+ # For non-simple left operands, cache in ρσ_expr_temp to avoid double evaluation.
308
+ is_and = self.operator is '&&'
309
+ if is_node_type(self.left, AST_SymbolRef):
310
+ output.print('(ρσ_bool(')
311
+ self.left.print(output)
312
+ output.print(') ? ')
313
+ if is_and:
314
+ self.right.print(output)
315
+ output.print(' : ')
316
+ self.left.print(output)
317
+ else:
318
+ self.left.print(output)
319
+ output.print(' : ')
320
+ self.right.print(output)
321
+ output.print(')')
322
+ else:
323
+ output.print('(ρσ_expr_temp = ')
324
+ self.left.print(output)
325
+ output.print(', ρσ_bool(ρσ_expr_temp) ? ')
326
+ if is_and:
327
+ self.right.print(output)
328
+ output.print(' : ρσ_expr_temp)')
329
+ else:
330
+ output.print('ρσ_expr_temp : ')
331
+ self.right.print(output)
332
+ output.print(')')
287
333
  else:
288
334
  output.spaced(self.left, self.operator, self.right)
289
335
 
@@ -368,6 +414,21 @@ def print_assign(self, output):
368
414
  self.right.print(output)
369
415
  )
370
416
  return
417
+ if self.operator is '**=':
418
+ output.assign(self.left)
419
+ if output.options.js_version > 6:
420
+ output.print('(('), self.left.print(output), output.spaced(')', '**', '('), self.right.print(output), output.print('))')
421
+ else:
422
+ output.print('Math.pow('), self.left.print(output), output.comma(), self.right.print(output), output.print(')')
423
+ return
424
+ if self.operator is '+=':
425
+ output.assign(self.left)
426
+ output.print('ρσ_list_iadd(')
427
+ self.left.print(output)
428
+ output.comma()
429
+ self.right.print(output)
430
+ output.print(')')
431
+ return
371
432
  if self.operator is '=' and self.is_chained():
372
433
  left_hand_sides, rhs = self.traverse_chain()
373
434
  is_compound_assign = False
@@ -390,7 +451,12 @@ def print_assign(self, output):
390
451
 
391
452
  def print_conditional(self, output, condition, consequent, alternative):
392
453
  condition, consequent, alternative = self.condition, self.consequent, self.alternative
393
- output.with_parens(def():condition.print(output);)
454
+ if self.python_truthiness:
455
+ output.print('ρσ_bool(')
456
+ condition.print(output)
457
+ output.print(')')
458
+ else:
459
+ output.with_parens(def(): condition.print(output);)
394
460
  output.space()
395
461
  output.print("?")
396
462
  output.space()
@@ -156,15 +156,16 @@ def print_with(self, output):
156
156
  output.with_block(def():
157
157
  output.indent(), output.assign('ρσ_with_exception'), output.print('e'), output.end_statement()
158
158
  )
159
+ reversed_exits = exits.slice().reverse()
159
160
  output.newline(), output.indent(), output.spaced('if', '(ρσ_with_exception', '===', 'undefined)')
160
161
  output.with_block(def():
161
- for clause in exits:
162
+ for clause in reversed_exits:
162
163
  output.indent(), output.print(clause + '.__exit__()'), output.end_statement()
163
164
  )
164
165
  output.space(), output.print('else'), output.space()
165
166
  output.with_block(def():
166
167
  output.indent(), output.assign('ρσ_with_suppress'), output.print('false'), output.end_statement()
167
- for clause in exits:
168
+ for clause in reversed_exits:
168
169
  output.indent()
169
170
  output.spaced('ρσ_with_suppress', '|=', 'ρσ_bool(' + clause + '.__exit__(ρσ_with_exception.constructor,',
170
171
  'ρσ_with_exception,', 'ρσ_with_exception.stack))')
@@ -175,7 +176,10 @@ def print_with(self, output):
175
176
  def print_assert(self, output):
176
177
  if output.options.discard_asserts:
177
178
  return
178
- output.spaced('if', '(!('), self.condition.print(output), output.spaced('))', 'throw new AssertionError')
179
+ if self.python_truthiness:
180
+ output.spaced('if', '(!ρσ_bool('), self.condition.print(output), output.spaced('))', 'throw new AssertionError')
181
+ else:
182
+ output.spaced('if', '(!('), self.condition.print(output), output.spaced('))', 'throw new AssertionError')
179
183
  if self.message:
180
184
  output.print('(')
181
185
  self.message.print(output)
@@ -1,6 +1,5 @@
1
1
  # vim:fileencoding=utf-8
2
2
  # License: BSD Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net>
3
- # globals:regenerate
4
3
  from __python__ import hash_literals
5
4
 
6
5
  from utils import make_predicate, defaults, repeat_string
@@ -76,6 +75,12 @@ class OutputStream:
76
75
  self.with_counter = 0
77
76
  self.try_else_counter = 0
78
77
  self.match_counter = 0
78
+ self.forelse_counter = 0
79
+ self.forelse_stack = v'[]'
80
+
81
+ def new_forelse_label(self):
82
+ self.forelse_counter += 1
83
+ return 'ρσ_forelse_' + self.forelse_counter
79
84
 
80
85
  def new_try_else_counter(self):
81
86
  self.try_else_counter += 1
@@ -249,18 +254,6 @@ class OutputStream:
249
254
  if self.options.space_colon:
250
255
  self.space()
251
256
 
252
- def dump_yield(self):
253
- self.indent()
254
- self.spaced('var', 'ρσ_regenerator', '=', '{}')
255
- self.end_statement()
256
- code = 'ρσ_regenerator.regeneratorRuntime = ' + regenerate(False, self.options.beautify)
257
- if self.options.beautify:
258
- code = code.replace(/\/\/.*$/mg, '\n').replace(/^\s*$/gm, '') # strip comments
259
- ci = self.make_indent(0)
260
- code = [ci + x for x in code.split('\n')].join('\n')
261
- self.print(code + '})(ρσ_regenerator)')
262
- self.end_statement()
263
-
264
257
  def get(self):
265
258
  return self.OUTPUT
266
259
  toString = get
package/src/parse.pyj CHANGED
@@ -24,12 +24,13 @@ AST_Undefined, AST_Var, AST_VarDef, AST_Verbatim, AST_While, AST_With, AST_WithC
24
24
  AST_Yield, AST_Await, AST_Assert, AST_Existential, AST_NamedExpr, AST_AnnotatedAssign, AST_Super, AST_Starred, is_node_type,
25
25
  AST_Match, AST_MatchCase,
26
26
  AST_MatchWildcard, AST_MatchCapture, AST_MatchLiteral, AST_MatchOr,
27
- AST_MatchAs, AST_MatchStar, AST_MatchSequence, AST_MatchMapping, AST_MatchClass
27
+ AST_MatchAs, AST_MatchStar, AST_MatchSequence, AST_MatchMapping, AST_MatchClass,
28
+ TreeWalker
28
29
  )
29
30
  from tokenizer import tokenizer, is_token, RESERVED_WORDS
30
31
 
31
32
  COMPILER_VERSION = '__COMPILER_VERSION__'
32
- PYTHON_FLAGS = {'dict_literals':True, 'overload_getitem':True, 'bound_methods':True, 'hash_literals':True, 'overload_operators':True}
33
+ PYTHON_FLAGS = {'dict_literals':True, 'overload_getitem':True, 'bound_methods':True, 'hash_literals':True, 'overload_operators':True, 'truthiness':True}
33
34
 
34
35
 
35
36
  def get_compiler_version():
@@ -116,7 +117,7 @@ FORBIDDEN_CLASS_VARS = 'prototype constructor'.split(' ')
116
117
  # -----[ Parser (constants) ]-----
117
118
  UNARY_PREFIX = make_predicate('typeof void delete ~ - + ! @')
118
119
 
119
- ASSIGNMENT = make_predicate('= += -= /= //= *= %= >>= <<= >>>= |= ^= &=')
120
+ ASSIGNMENT = make_predicate('= += -= /= //= *= **= %= >>= <<= >>>= |= ^= &=')
120
121
 
121
122
  PRECEDENCE = (def(a, ret):
122
123
  for i in range(a.length):
@@ -336,7 +337,7 @@ def create_parser_ctx(S, import_dirs, module_id, baselib_items, imported_module_
336
337
  continue
337
338
 
338
339
  # recursive descent into conditional, loop and exception bodies
339
- for option in ('body', 'alternative', 'bcatch', 'condition'):
340
+ for option in ('body', 'alternative', 'bcatch', 'belse', 'condition'):
340
341
  opt = stmt[option]
341
342
  if opt:
342
343
  extend(scan_for_local_vars(opt))
@@ -402,6 +403,23 @@ def create_parser_ctx(S, import_dirs, module_id, baselib_items, imported_module_
402
403
 
403
404
  elif is_node_type(body, AST_ForIn):
404
405
  add_for_in(body)
406
+ # Also scan conditions for walrus operators (e.g. comprehension filters)
407
+ if body.condition:
408
+ extend(scan_for_local_vars(body.condition))
409
+ if body.clauses:
410
+ for clause in body.clauses:
411
+ if clause.condition:
412
+ extend(scan_for_local_vars(clause.condition))
413
+
414
+ else:
415
+ # Generic expression node — walk for walrus operators, stopping at scope boundaries
416
+ if body.walk:
417
+ body.walk(new TreeWalker(def(node):
418
+ if is_node_type(node, AST_NamedExpr):
419
+ push(node.name.name)
420
+ if is_node_type(node, AST_Scope):
421
+ return True # prune: don't descend into inner function scopes
422
+ ))
405
423
 
406
424
  return localvars
407
425
 
@@ -550,7 +568,8 @@ def create_parser_ctx(S, import_dirs, module_id, baselib_items, imported_module_
550
568
  croak('Assignments in do loop conditions are not allowed')
551
569
  semicolon()
552
570
  return tmp
553
- )()
571
+ )(),
572
+ 'python_truthiness': S.scoped_flags.get('truthiness', False)
554
573
  })
555
574
  elif tmp_ is "while":
556
575
  while_cond = expression(True)
@@ -558,9 +577,21 @@ def create_parser_ctx(S, import_dirs, module_id, baselib_items, imported_module_
558
577
  croak('Assignments in while loop conditions are not allowed')
559
578
  if not is_('punc', ':'):
560
579
  croak('Expected a colon after the while statement')
580
+ while_body = in_loop(statement)
581
+ while_belse = None
582
+ if is_("keyword", "else"):
583
+ start = S.token
584
+ next()
585
+ while_belse = new AST_Else({
586
+ 'start': start,
587
+ 'body': block_(),
588
+ 'end': prev()
589
+ })
561
590
  return new AST_While({
562
591
  'condition': while_cond,
563
- 'body': in_loop(statement)
592
+ 'body': while_body,
593
+ 'belse': while_belse,
594
+ 'python_truthiness': S.scoped_flags.get('truthiness', False)
564
595
  })
565
596
  elif tmp_ is "for":
566
597
  if is_('js'):
@@ -622,7 +653,7 @@ def create_parser_ctx(S, import_dirs, module_id, baselib_items, imported_module_
622
653
  if is_('punc', ','):
623
654
  next()
624
655
  msg = expression(False)
625
- return new AST_Assert({'start': start, 'condition':cond, 'message':msg, 'end':prev()})
656
+ return new AST_Assert({'start': start, 'condition':cond, 'message':msg, 'end':prev(), 'python_truthiness': S.scoped_flags.get('truthiness', False)})
626
657
  elif tmp_ is "if":
627
658
  return if_()
628
659
  elif tmp_ is "pass":
@@ -647,10 +678,17 @@ def create_parser_ctx(S, import_dirs, module_id, baselib_items, imported_module_
647
678
  })
648
679
 
649
680
  tmp = expression(True)
681
+ cause = None
682
+ if is_("keyword", "from"):
683
+ next()
684
+ cause = expression(True)
650
685
  semicolon()
651
- return new AST_Throw({
686
+ node = new AST_Throw({
652
687
  'value': tmp
653
688
  })
689
+ if cause is not None:
690
+ node.cause = cause
691
+ return node
654
692
  elif tmp_ is "try":
655
693
  return try_()
656
694
  elif tmp_ is "nonlocal":
@@ -1116,11 +1154,22 @@ def create_parser_ctx(S, import_dirs, module_id, baselib_items, imported_module_
1116
1154
  'object': obj
1117
1155
  }
1118
1156
 
1157
+ body = in_loop(statement)
1158
+ belse = None
1159
+ if is_("keyword", "else"):
1160
+ start = S.token
1161
+ next()
1162
+ belse = new AST_Else({
1163
+ 'start': start,
1164
+ 'body': block_(),
1165
+ 'end': prev()
1166
+ })
1119
1167
  return new AST_ForIn({
1120
1168
  'init': init,
1121
1169
  'name': lhs,
1122
1170
  'object': obj,
1123
- 'body': in_loop(statement)
1171
+ 'body': body,
1172
+ 'belse': belse
1124
1173
  })
1125
1174
 
1126
1175
  # A native JavaScript for loop - for v"var i=0; i<5000; i++":
@@ -1367,6 +1416,18 @@ def create_parser_ctx(S, import_dirs, module_id, baselib_items, imported_module_
1367
1416
  bases = v'[]'
1368
1417
  class_parent = None
1369
1418
 
1419
+ # If this class is nested inside another class, register it under its
1420
+ # full dotted path (e.g. "Outer.Inner") in the module-level class scope
1421
+ # (S.classes[0]). This lets get_class_in_scope() find "Outer.Inner" as
1422
+ # a class and produce a correct AST_New constructor call instead of
1423
+ # treating it as a method call on Outer.
1424
+ outer_class_parts = []
1425
+ for _outer in S.in_class:
1426
+ if _outer:
1427
+ outer_class_parts.push(_outer)
1428
+ if outer_class_parts.length > 0:
1429
+ S.classes[0][outer_class_parts.join('.') + '.' + name.name] = class_details
1430
+
1370
1431
  # read the bases of the class, if any
1371
1432
  if is_("punc", "("):
1372
1433
  S.in_parenthesized_expr = True
@@ -1472,7 +1533,11 @@ def create_parser_ctx(S, import_dirs, module_id, baselib_items, imported_module_
1472
1533
  visitor = new walker()
1473
1534
 
1474
1535
  for stmt in definition.body:
1475
- if not is_node_type(stmt, AST_Class):
1536
+ if is_node_type(stmt, AST_Class):
1537
+ # Nested class: include in statements but don't walk through the
1538
+ # class-var mangling visitor (nested classes have their own scope).
1539
+ definition.statements.push(stmt)
1540
+ else:
1476
1541
  stmt.walk(visitor)
1477
1542
  definition.statements.push(stmt)
1478
1543
  return definition
@@ -1865,7 +1930,8 @@ def create_parser_ctx(S, import_dirs, module_id, baselib_items, imported_module_
1865
1930
  return new AST_If({
1866
1931
  'condition': cond,
1867
1932
  'body': body,
1868
- 'alternative': belse
1933
+ 'alternative': belse,
1934
+ 'python_truthiness': S.scoped_flags.get('truthiness', False)
1869
1935
  })
1870
1936
 
1871
1937
  def is_docstring(stmt):
@@ -1917,10 +1983,19 @@ def create_parser_ctx(S, import_dirs, module_id, baselib_items, imported_module_
1917
1983
  next()
1918
1984
  exceptions = []
1919
1985
  if not is_("punc", ":") and not is_("keyword", "as"):
1986
+ # Accept both: except TypeError, ValueError:
1987
+ # and: except (TypeError, ValueError):
1988
+ paren_wrapped = is_("punc", "(")
1989
+ if paren_wrapped:
1990
+ next()
1920
1991
  exceptions.push(as_symbol(AST_SymbolVar))
1921
1992
  while is_("punc", ","):
1922
1993
  next()
1994
+ if paren_wrapped and is_("punc", ")"):
1995
+ break # trailing comma inside parens
1923
1996
  exceptions.push(as_symbol(AST_SymbolVar))
1997
+ if paren_wrapped:
1998
+ expect(")")
1924
1999
 
1925
2000
  name = None
1926
2001
  if is_("keyword", "as"):
@@ -2670,7 +2745,8 @@ def create_parser_ctx(S, import_dirs, module_id, baselib_items, imported_module_
2670
2745
  'start': start,
2671
2746
  'expression': expr,
2672
2747
  'args': func_call_list(),
2673
- 'end': prev()
2748
+ 'end': prev(),
2749
+ 'python_truthiness': S.scoped_flags.get('truthiness', False) and is_node_type(expr, AST_SymbolRef)
2674
2750
  }), True)
2675
2751
  S.in_parenthesized_expr = False
2676
2752
  return ret
@@ -2760,6 +2836,8 @@ def create_parser_ctx(S, import_dirs, module_id, baselib_items, imported_module_
2760
2836
  ex.end = prev()
2761
2837
  if S.scoped_flags.get('overload_operators', False) and (start.value is '-' or start.value is '+' or start.value is '~'):
2762
2838
  ex.overloaded = True
2839
+ if S.scoped_flags.get('truthiness', False) and start.value is '!':
2840
+ ex.python_truthiness = True
2763
2841
  return ex
2764
2842
 
2765
2843
  val = expr_atom(allow_calls)
@@ -2795,7 +2873,8 @@ def create_parser_ctx(S, import_dirs, module_id, baselib_items, imported_module_
2795
2873
  'operator': op,
2796
2874
  'right': right,
2797
2875
  'end': right.end,
2798
- 'overloaded': S.scoped_flags.get('overload_operators', False)
2876
+ 'overloaded': S.scoped_flags.get('overload_operators', False),
2877
+ 'python_truthiness': S.scoped_flags.get('truthiness', False) and (op is '&&' or op is '||')
2799
2878
  })
2800
2879
  return expr_op(ret, min_prec, no_in)
2801
2880
  return left
@@ -2815,7 +2894,8 @@ def create_parser_ctx(S, import_dirs, module_id, baselib_items, imported_module_
2815
2894
  'condition': ne,
2816
2895
  'consequent': expr,
2817
2896
  'alternative': expression(False, no_in),
2818
- 'end': peek()
2897
+ 'end': peek(),
2898
+ 'python_truthiness': S.scoped_flags.get('truthiness', False)
2819
2899
  })
2820
2900
  return conditional
2821
2901
  return expr
package/src/tokenizer.pyj CHANGED
@@ -53,6 +53,7 @@ OPERATORS = make_predicate([
53
53
  "//=",
54
54
  "/=",
55
55
  "*=",
56
+ "**=",
56
57
  "%=",
57
58
  ">>=",
58
59
  "<<=",