rapydscript-ns 0.8.1 → 0.8.2

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.
@@ -468,7 +468,7 @@ Object.defineProperties(ρσ_set.prototype, {
468
468
  return '{' + list(this).join(', ') + '}'
469
469
 
470
470
  ρσ_set.prototype.__eq__ = def(other):
471
- if not v'other instanceof this.constructor':
471
+ if v'!other || !other.jsset':
472
472
  return False
473
473
  if other.size is not this.size:
474
474
  return False
@@ -490,6 +490,142 @@ def ρσ_set_wrap(x):
490
490
  v'var set = ρσ_set, set_wrap = ρσ_set_wrap'
491
491
  # }}}
492
492
 
493
+ # frozenset {{{
494
+ def ρσ_frozenset(iterable):
495
+ if v'this instanceof ρσ_frozenset':
496
+ this.jsset = new ρσ_set_implementation() # noqa:undef
497
+ ans = this
498
+ if iterable is undefined:
499
+ return ans
500
+ s = ans.jsset
501
+ if ρσ_arraylike(iterable):
502
+ for v'var i = 0; i < iterable.length; i++':
503
+ s.add(iterable[i])
504
+ elif jstype(iterable[ρσ_iterator_symbol]) is 'function':
505
+ iterator = iterable.keys() if jstype(Map) is 'function' and v'iterable instanceof Map' else iterable[ρσ_iterator_symbol]()
506
+ result = iterator.next()
507
+ while not result.done:
508
+ s.add(result.value)
509
+ result = iterator.next()
510
+ else:
511
+ keys = Object.keys(iterable)
512
+ for v'var j=0; j < keys.length; j++':
513
+ s.add(keys[j])
514
+ return ans
515
+ else:
516
+ return new ρσ_frozenset(iterable)
517
+ ρσ_frozenset.prototype.__name__ = 'frozenset'
518
+
519
+ Object.defineProperties(ρσ_frozenset.prototype, {
520
+ 'length': { 'get': def(): return this.jsset.size; },
521
+ 'size': { 'get': def(): return this.jsset.size; },
522
+ })
523
+
524
+ ρσ_frozenset.prototype.__len__ = def(): return this.jsset.size
525
+ ρσ_frozenset.prototype.has = ρσ_frozenset.prototype.__contains__ = def(x): return this.jsset.has(x)
526
+ ρσ_frozenset.prototype.copy = def(): return ρσ_frozenset(this)
527
+ ρσ_frozenset.prototype[ρσ_iterator_symbol] = def(): return this.jsset.values()
528
+
529
+ ρσ_frozenset.prototype.difference = def():
530
+ ans = new ρσ_frozenset()
531
+ s = ans.jsset
532
+ iterator = this.jsset.values()
533
+ r = iterator.next()
534
+ while not r.done:
535
+ x = r.value
536
+ has = False
537
+ for v'var i = 0; i < arguments.length; i++':
538
+ if arguments[i].has(x): # noqa:undef
539
+ has = True
540
+ break
541
+ if not has:
542
+ s.add(x)
543
+ r = iterator.next()
544
+ return ans
545
+
546
+ ρσ_frozenset.prototype.intersection = def():
547
+ ans = new ρσ_frozenset()
548
+ s = ans.jsset
549
+ iterator = this.jsset.values()
550
+ r = iterator.next()
551
+ while not r.done:
552
+ x = r.value
553
+ has = True
554
+ for v'var i = 0; i < arguments.length; i++':
555
+ if not arguments[i].has(x): # noqa:undef
556
+ has = False
557
+ break
558
+ if has:
559
+ s.add(x)
560
+ r = iterator.next()
561
+ return ans
562
+
563
+ ρσ_frozenset.prototype.isdisjoint = def(other):
564
+ iterator = this.jsset.values()
565
+ r = iterator.next()
566
+ while not r.done:
567
+ x = r.value
568
+ if other.has(x):
569
+ return False
570
+ r = iterator.next()
571
+ return True
572
+
573
+ ρσ_frozenset.prototype.issubset = def(other):
574
+ iterator = this.jsset.values()
575
+ r = iterator.next()
576
+ while not r.done:
577
+ x = r.value
578
+ if not other.has(x):
579
+ return False
580
+ r = iterator.next()
581
+ return True
582
+
583
+ ρσ_frozenset.prototype.issuperset = def(other):
584
+ s = this.jsset
585
+ iterator = other[ρσ_iterator_symbol]()
586
+ r = iterator.next()
587
+ while not r.done:
588
+ x = r.value
589
+ if not s.has(x):
590
+ return False
591
+ r = iterator.next()
592
+ return True
593
+
594
+ ρσ_frozenset.prototype.symmetric_difference = def(other):
595
+ return this.union(other).difference(this.intersection(other))
596
+
597
+ ρσ_frozenset.prototype.union = def():
598
+ ans = ρσ_frozenset(this)
599
+ s = ans.jsset
600
+ for v'var i=0; i < arguments.length; i++':
601
+ iterator = arguments[i][ρσ_iterator_symbol]() # noqa:undef
602
+ r = iterator.next()
603
+ while not r.done:
604
+ s.add(r.value)
605
+ r = iterator.next()
606
+ return ans
607
+
608
+ ρσ_frozenset.prototype.toString = ρσ_frozenset.prototype.__repr__ = ρσ_frozenset.prototype.__str__ = ρσ_frozenset.prototype.inspect = def():
609
+ return 'frozenset({' + list(this).join(', ') + '})'
610
+
611
+ ρσ_frozenset.prototype.__eq__ = def(other):
612
+ if v'!other || !other.jsset':
613
+ return False
614
+ if other.size is not this.size:
615
+ return False
616
+ if other.size is 0:
617
+ return True
618
+ iterator = other[ρσ_iterator_symbol]()
619
+ r = iterator.next()
620
+ while not r.done:
621
+ if not this.has(r.value):
622
+ return False
623
+ r = iterator.next()
624
+ return True
625
+
626
+ v'var frozenset = ρσ_frozenset'
627
+ # }}}
628
+
493
629
  # dict {{{
494
630
  v'var ρσ_dict_implementation'
495
631
 
@@ -704,6 +840,15 @@ Object.defineProperties(ρσ_dict.prototype, {
704
840
  r = iterator.next()
705
841
  return True
706
842
 
843
+ ρσ_dict.prototype.__or__ = def(other):
844
+ result = ρσ_dict(this)
845
+ result.update(other)
846
+ return result
847
+
848
+ ρσ_dict.prototype.__ior__ = def(other):
849
+ this.update(other)
850
+ return this
851
+
707
852
  ρσ_dict.prototype.as_object = def(other):
708
853
  ans = {}
709
854
  iterator = this.jsmap.entries()
@@ -35,3 +35,6 @@ class AssertionError(Exception):
35
35
 
36
36
  class ZeroDivisionError(Exception):
37
37
  pass
38
+
39
+ class StopIteration(Exception):
40
+ pass
@@ -282,6 +282,16 @@ def ρσ_op_mul(a, b):
282
282
  if b is not None and jstype(b.__rmul__) is 'function': return b.__rmul__(a)
283
283
  if (jstype(a) is 'string' or v'a instanceof String') and jstype(b) is 'number': return a.repeat(b)
284
284
  if (jstype(b) is 'string' or v'b instanceof String') and jstype(a) is 'number': return b.repeat(a)
285
+ if Array.isArray(a) and jstype(b) is 'number':
286
+ result = v'[]'
287
+ for v'var ρσ_mi = 0; ρσ_mi < b; ρσ_mi++':
288
+ result = result.concat(a) # noqa:undef
289
+ return ρσ_list_constructor(result)
290
+ if Array.isArray(b) and jstype(a) is 'number':
291
+ result = v'[]'
292
+ for v'var ρσ_mi = 0; ρσ_mi < a; ρσ_mi++':
293
+ result = result.concat(b) # noqa:undef
294
+ return ρσ_list_constructor(result)
285
295
  return a * b
286
296
 
287
297
  def ρσ_op_truediv(a, b):
@@ -525,6 +525,40 @@ define_str_func('isspace', def():
525
525
  return this.length > 0 and /^\s+$/.test(this)
526
526
  )
527
527
 
528
+ define_str_func('isalpha', def():
529
+ return this.length > 0 and /^[a-zA-Z]+$/.test(this)
530
+ )
531
+
532
+ define_str_func('isdigit', def():
533
+ return this.length > 0 and /^\d+$/.test(this)
534
+ )
535
+
536
+ define_str_func('isalnum', def():
537
+ return this.length > 0 and /^[a-zA-Z0-9]+$/.test(this)
538
+ )
539
+
540
+ define_str_func('isidentifier', def():
541
+ return this.length > 0 and /^[a-zA-Z_][a-zA-Z0-9_]*$/.test(this)
542
+ )
543
+
544
+ define_str_func('casefold', def():
545
+ return this.toLowerCase()
546
+ )
547
+
548
+ define_str_func('removeprefix', def(prefix):
549
+ s = this.toString()
550
+ if s.startsWith(prefix):
551
+ return s.slice(prefix.length)
552
+ return s
553
+ )
554
+
555
+ define_str_func('removesuffix', def(suffix):
556
+ s = this.toString()
557
+ if suffix.length and s.endsWith(suffix):
558
+ return s.slice(0, s.length - suffix.length)
559
+ return s
560
+ )
561
+
528
562
  define_str_func('join', def(iterable):
529
563
  if Array.isArray(iterable):
530
564
  return iterable.join(this)
@@ -6,24 +6,12 @@
6
6
  // const scopeMap = analyzer.analyze(sourceCode, { virtualFiles: {...} });
7
7
 
8
8
  import { ScopeMap, ScopeFrame, SymbolInfo } from './scope.js';
9
+ import { build_scoped_flags } from './diagnostics.js';
9
10
 
10
11
  // ---------------------------------------------------------------------------
11
12
  // Helpers
12
13
  // ---------------------------------------------------------------------------
13
14
 
14
- function build_scoped_flags(flags_str) {
15
- const result = Object.create(null);
16
- if (!flags_str) return result;
17
- flags_str.split(',').forEach(flag => {
18
- flag = flag.trim();
19
- if (!flag) return;
20
- let val = true;
21
- if (flag.startsWith('no_')) { val = false; flag = flag.slice(3); }
22
- result[flag] = val;
23
- });
24
- return result;
25
- }
26
-
27
15
  /** Convert AST token position {line (1-indexed), col (0-indexed)} → {line, column} (both 1-indexed). */
28
16
  function pos_from_token(tok) {
29
17
  if (!tok) return { line: 1, column: 1 };
@@ -114,6 +114,11 @@ const STUBS = [
114
114
  return_type: 'any',
115
115
  doc: 'Return the value of the named attribute of obj. If not found, return default (or raise AttributeError).' }),
116
116
 
117
+ new BuiltinInfo({ name: 'hash',
118
+ params: [p('obj')],
119
+ return_type: 'int',
120
+ doc: 'Return the hash value of obj. Strings and numbers are hashed by value; class instances by identity. Raises TypeError for unhashable types (list, set, dict). Objects with __hash__ dispatch to it.' }),
121
+
117
122
  new BuiltinInfo({ name: 'hasattr',
118
123
  params: [p('obj'), p('name', { type: 'str' })],
119
124
  return_type: 'bool',
@@ -139,6 +144,11 @@ const STUBS = [
139
144
  return_type: 'bool',
140
145
  doc: 'Return True if obj is an instance of classinfo (or a subclass thereof).' }),
141
146
 
147
+ new BuiltinInfo({ name: 'issubclass',
148
+ params: [p('cls'), p('classinfo')],
149
+ return_type: 'bool',
150
+ doc: 'Return True if cls is a subclass of classinfo. classinfo may be a class or tuple of classes.' }),
151
+
142
152
  new BuiltinInfo({ name: 'iter',
143
153
  params: [p('obj')],
144
154
  return_type: 'iterator',
@@ -8,6 +8,25 @@
8
8
  import { SourceAnalyzer } from './analyzer.js';
9
9
  import { resolve_first_type } from './dts.js';
10
10
 
11
+ // ---------------------------------------------------------------------------
12
+ // Language keywords
13
+ // ---------------------------------------------------------------------------
14
+
15
+ /**
16
+ * RapydScript / Python keywords that are valid identifier-completion tokens.
17
+ * Shown in the `identifier` completion context only (not dot / import contexts).
18
+ */
19
+ export const KEYWORDS = [
20
+ 'and', 'as', 'assert', 'async', 'await',
21
+ 'break', 'class', 'continue', 'def', 'del',
22
+ 'elif', 'else', 'except', 'finally', 'for',
23
+ 'from', 'global', 'if', 'import', 'in', 'is',
24
+ 'lambda', 'nonlocal', 'not', 'or', 'pass',
25
+ 'raise', 'return', 'try', 'while', 'with',
26
+ 'yield',
27
+ 'True', 'False', 'None',
28
+ ];
29
+
11
30
  // ---------------------------------------------------------------------------
12
31
  // Context detection
13
32
  // ---------------------------------------------------------------------------
@@ -267,7 +286,25 @@ export class CompletionEngine {
267
286
  const seen = new Set();
268
287
 
269
288
  if (scopeMap) {
270
- const symbols = scopeMap.getSymbolsAtPosition(position.lineNumber, position.column);
289
+ let symbols = scopeMap.getSymbolsAtPosition(position.lineNumber, position.column);
290
+
291
+ // Fallback: cursor is on a new line not yet covered by the last debounced parse
292
+ // (e.g. the user typed Return and is now on a line past the end of the last AST).
293
+ // Collect all symbols from all frames, innermost-first, so locals remain visible.
294
+ if (symbols.length === 0 && scopeMap.frames.length > 0) {
295
+ const all_frames = scopeMap.frames.slice().sort((a, b) => b.depth - a.depth);
296
+ const fallback_seen = new Set();
297
+ symbols = [];
298
+ for (const frame of all_frames) {
299
+ for (const [name, sym] of frame.symbols) {
300
+ if (!fallback_seen.has(name)) {
301
+ fallback_seen.add(name);
302
+ symbols.push(sym);
303
+ }
304
+ }
305
+ }
306
+ }
307
+
271
308
  for (const sym of symbols) {
272
309
  if (!ctx.prefix || sym.name.startsWith(ctx.prefix)) {
273
310
  if (!seen.has(sym.name)) {
@@ -279,7 +316,7 @@ export class CompletionEngine {
279
316
  }
280
317
  }
281
318
 
282
- // Builtins (lowest priority) — use rich stub item when available
319
+ // Builtins (lower priority) — use rich stub item when available
283
320
  for (const name of this._builtinNames) {
284
321
  if (!seen.has(name) && (!ctx.prefix || name.startsWith(ctx.prefix))) {
285
322
  seen.add(name);
@@ -291,6 +328,21 @@ export class CompletionEngine {
291
328
  }
292
329
  }
293
330
 
331
+ // Keywords (lowest priority)
332
+ const kw_kind = monacoKind.Keyword !== undefined ? monacoKind.Keyword : monacoKind.Variable;
333
+ for (const kw of KEYWORDS) {
334
+ if (!seen.has(kw) && (!ctx.prefix || kw.startsWith(ctx.prefix))) {
335
+ seen.add(kw);
336
+ items.push({
337
+ label: kw,
338
+ kind: kw_kind,
339
+ sortText: '3_' + kw,
340
+ insertText: kw,
341
+ range,
342
+ });
343
+ }
344
+ }
345
+
294
346
  return items;
295
347
  }
296
348
 
@@ -16,6 +16,7 @@ const MESSAGES = {
16
16
  'func-in-branch': 'Named functions/classes inside a branch are not allowed in strict mode',
17
17
  'syntax-err': 'Syntax error: {name}',
18
18
  'import-err': 'Import error: {name}',
19
+ 'bad-import': 'Unknown module: "{name}"',
19
20
  'def-after-use': 'The symbol "{name}" is defined (at line {line}) after it is used',
20
21
  'dup-key': 'Duplicate key "{name}" in object literal',
21
22
  'dup-method': 'The method "{name}" was defined previously at line: {line}',
@@ -27,7 +28,7 @@ export const BASE_BUILTINS = (
27
28
  ' eval undefined arguments abs max min enumerate pow callable reversed sum' +
28
29
  ' getattr isFinite setattr hasattr parseInt parseFloat options_object' +
29
30
  ' isNaN JSON Math list set list_wrap ρσ_modules require bool int bin' +
30
- ' float iter Error EvalError set_wrap RangeError ReferenceError SyntaxError' +
31
+ ' float iter Error EvalError set_wrap frozenset RangeError ReferenceError SyntaxError' +
31
32
  ' str TypeError URIError Exception AssertionError IndexError AttributeError KeyError' +
32
33
  ' ValueError ZeroDivisionError map hex filter zip dict dict_wrap UnicodeDecodeError HTMLCollection' +
33
34
  ' NodeList alert console Node Symbol NamedNodeMap ρσ_eslice ρσ_delslice Number' +
@@ -44,7 +45,7 @@ function has_prop(obj, name) {
44
45
  return Object.prototype.hasOwnProperty.call(obj, name);
45
46
  }
46
47
 
47
- function build_scoped_flags(flags_str) {
48
+ export function build_scoped_flags(flags_str) {
48
49
  const result = Object.create(null);
49
50
  if (!flags_str) return result;
50
51
  flags_str.split(',').forEach(flag => {
@@ -205,7 +206,7 @@ Scope.prototype.messages = function() {
205
206
  // Linter — mirrors the Linter in tools/lint.js
206
207
  // ---------------------------------------------------------------------------
207
208
 
208
- function Linter(RS, toplevel, code, builtins) {
209
+ function Linter(RS, toplevel, code, builtins, knownModules) {
209
210
  this.RS = RS;
210
211
  this.scopes = [];
211
212
  this.walked_scopes = [];
@@ -213,6 +214,7 @@ function Linter(RS, toplevel, code, builtins) {
213
214
  this.branches = [];
214
215
  this.messages = [];
215
216
  this.builtins = builtins;
217
+ this._knownModules = knownModules || null;
216
218
 
217
219
  this.add_binding = function(name, binding_node) {
218
220
  const scope = this.scopes[this.scopes.length - 1];
@@ -240,6 +242,26 @@ function Linter(RS, toplevel, code, builtins) {
240
242
  const name = node.alias ? node.alias.name : node.key.split('.', 1)[0];
241
243
  this.add_binding(name, node.alias || node);
242
244
  }
245
+ // Flag imports of modules not present in the known-module registry.
246
+ // Only active when at least one virtualFile or stdlibFile is registered,
247
+ // to avoid false positives when no module system is configured.
248
+ if (this._knownModules) {
249
+ const root = node.key.split('.')[0];
250
+ if (!has_prop(this._knownModules, root)) {
251
+ const sline = node.start ? node.start.line : 1;
252
+ const scol = node.start ? node.start.col : 0;
253
+ this.messages.push({
254
+ start_line: sline,
255
+ start_col: scol,
256
+ end_line: sline,
257
+ end_col: scol + node.key.length - 1,
258
+ ident: 'bad-import',
259
+ message: MESSAGES['bad-import'].replace('{name}', node.key),
260
+ level: ERROR,
261
+ name: node.key,
262
+ });
263
+ }
264
+ }
243
265
  };
244
266
 
245
267
  this.handle_imported_var = function() {
@@ -312,6 +334,23 @@ function Linter(RS, toplevel, code, builtins) {
312
334
  }
313
335
  };
314
336
 
337
+ this.handle_annotated_assign = function() {
338
+ const node = this.current_node;
339
+ // Variable type annotation: `x: int = value` or `x: int`
340
+ // Register the target as a binding (with or without a value).
341
+ if (node.target instanceof RS.AST_SymbolRef) {
342
+ node.target.lint_visited = true;
343
+ this.current_node = node.target;
344
+ this.add_binding(node.target.name);
345
+ this.current_node = node;
346
+ }
347
+ // Mark annotation symbol refs as visited so they aren't treated as uses
348
+ // of undefined variables (e.g. `int`, `str` are type names, not runtime refs).
349
+ if (node.annotation instanceof RS.AST_SymbolRef) {
350
+ node.annotation.lint_visited = true;
351
+ }
352
+ };
353
+
315
354
  this.handle_vardef = function() {
316
355
  const node = this.current_node;
317
356
  if (node.name instanceof RS.AST_SymbolNonlocal) {
@@ -461,6 +500,7 @@ function Linter(RS, toplevel, code, builtins) {
461
500
  else if (node instanceof RS.AST_Assign) this.handle_assign();
462
501
  else if (node instanceof RS.AST_NamedExpr) this.handle_named_expr();
463
502
  else if (node instanceof RS.AST_VarDef) this.handle_vardef();
503
+ else if (RS.AST_AnnotatedAssign && node instanceof RS.AST_AnnotatedAssign) this.handle_annotated_assign();
464
504
  else if (node instanceof RS.AST_SymbolRef) this.handle_symbol_ref();
465
505
  else if (node instanceof RS.AST_Decorator) this.handle_decorator();
466
506
  else if (node instanceof RS.AST_SymbolFunarg) this.handle_symbol_funarg();
@@ -634,7 +674,17 @@ export class Diagnostics {
634
674
  }
635
675
 
636
676
  // --- Lint ---
637
- const linter = new Linter(RS, toplevel, code, this._builtins);
677
+ // Build known-module set for import validation.
678
+ // Only activates when the caller has registered at least one module.
679
+ let knownModules = null;
680
+ const vf = options.virtualFiles;
681
+ const sf = options.stdlibFiles;
682
+ if ((vf && Object.keys(vf).length > 0) || (sf && Object.keys(sf).length > 0)) {
683
+ knownModules = Object.create(null);
684
+ if (vf) Object.keys(vf).forEach(k => { knownModules[k] = true; });
685
+ if (sf) Object.keys(sf).forEach(k => { knownModules[k] = true; });
686
+ }
687
+ const linter = new Linter(RS, toplevel, code, this._builtins, knownModules);
638
688
  toplevel.walk(linter);
639
689
  messages = linter.resolve(noqa);
640
690
 
@@ -53,7 +53,7 @@ class ModelState {
53
53
  _run() {
54
54
  const service = this._service;
55
55
  const code = this._model.getValue();
56
- const opts = { virtualFiles: service._virtualFiles };
56
+ const opts = { virtualFiles: service._virtualFiles, stdlibFiles: service._stdlibFiles };
57
57
 
58
58
  // Diagnostics (syntax errors + lint markers)
59
59
  const markers = service._diagnostics.check(code, {
@@ -108,9 +108,12 @@ class RapydScriptLanguageService {
108
108
  this._builtins = new BuiltinsRegistry();
109
109
  this._builtins.enablePythonizeStrings();
110
110
 
111
- // Merge BASE_BUILTINS + extra globals + DTS globals for completions
111
+ // Merge BASE_BUILTINS + DTS globals for completions.
112
+ // NOTE: _extraBuiltinNames (NS API keys like grow, growthAnalyze, etc.) are
113
+ // intentionally excluded here — they are handled by the DTS registry as dot-
114
+ // completions on ns.* and should NOT appear as free-floating identifier
115
+ // suggestions. They are still used for tokenization and diagnostics suppression.
112
116
  const builtin_names = BASE_BUILTINS
113
- .concat(this._extraBuiltinNames)
114
117
  .concat(this._dts.getGlobalNames());
115
118
 
116
119
  this._completions = new CompletionEngine(this._analyzer, {
@@ -318,8 +321,9 @@ class RapydScriptLanguageService {
318
321
  const new_names = this._dts.getGlobalNames();
319
322
  // Suppress undef warnings for newly added globals
320
323
  this._diagnostics.addGlobals(new_names);
321
- // Rebuild builtin list so completions include new DTS names (preserve extraBuiltins)
322
- const builtin_names = BASE_BUILTINS.concat(this._extraBuiltinNames).concat(new_names);
324
+ // Rebuild builtin list so completions include new DTS names.
325
+ // _extraBuiltinNames are excluded — they are dot-completions only (see constructor).
326
+ const builtin_names = BASE_BUILTINS.concat(new_names);
323
327
  this._completions = new CompletionEngine(this._analyzer, {
324
328
  virtualFiles: this._virtualFiles,
325
329
  stdlibFiles: this._stdlibFiles,
@@ -279,7 +279,28 @@ def generate_code():
279
279
  self._do_print(output, "return")
280
280
  )
281
281
  DEFPRINT(AST_Throw, def(self, output):
282
- self._do_print(output, "throw")
282
+ if self.cause:
283
+ # raise X from Y: set __cause__ on exception before throwing
284
+ output.print('var ρσ_throw_tmp')
285
+ output.space()
286
+ output.print('=')
287
+ output.space()
288
+ self.value.print(output)
289
+ output.semicolon()
290
+ output.newline()
291
+ output.indent()
292
+ output.print('ρσ_throw_tmp.__cause__')
293
+ output.space()
294
+ output.print('=')
295
+ output.space()
296
+ self.cause.print(output)
297
+ output.semicolon()
298
+ output.newline()
299
+ output.indent()
300
+ output.print('throw ρσ_throw_tmp')
301
+ output.semicolon()
302
+ else:
303
+ self._do_print(output, "throw")
283
304
  )
284
305
 
285
306
  # -----[ loop control ]-----
@@ -292,6 +313,17 @@ def generate_code():
292
313
  output.semicolon()
293
314
 
294
315
  DEFPRINT(AST_Break, def(self, output):
316
+ # If we are directly inside a for/else labeled block and this break has
317
+ # no explicit label, redirect it to the forelse label so the else clause
318
+ # is skipped.
319
+ if not self.label and output.forelse_stack and output.forelse_stack.length:
320
+ top = output.forelse_stack[output.forelse_stack.length - 1]
321
+ if top:
322
+ output.print('break')
323
+ output.space()
324
+ output.print(top)
325
+ output.semicolon()
326
+ return
295
327
  self._do_print(output, "break")
296
328
  )
297
329
  DEFPRINT(AST_Continue, def(self, output):
@@ -340,7 +372,10 @@ def generate_code():
340
372
  DEFPRINT(AST_If, def(self, output):
341
373
  output.print("if")
342
374
  output.space()
343
- output.with_parens(def(): self.condition.print(output);)
375
+ if self.python_truthiness:
376
+ output.with_parens(def(): output.print('ρσ_bool('), self.condition.print(output), output.print(')');)
377
+ else:
378
+ output.with_parens(def(): self.condition.print(output);)
344
379
  output.space()
345
380
  if self.alternative:
346
381
  make_then(self, output)
@@ -45,7 +45,7 @@ def function_args(argnames, output, strip_first):
45
45
 
46
46
  def function_preamble(node, output, offset):
47
47
  a = node.argnames
48
- if not a or a.is_simple_func:
48
+ if a is None or a is undefined or a.is_simple_func:
49
49
  return
50
50
  # If this function has optional parameters/*args/**kwargs declare it differently
51
51
  fname = node.name.name if node.name else anonfunc
@@ -433,20 +433,42 @@ def print_function_call(self, output):
433
433
  return # new A is the same as new A() in javascript
434
434
 
435
435
  if not has_kwargs and not self.args.starargs:
436
- # A simple function call, do nothing special
436
+ # A simple function call
437
437
  if is_new:
438
438
  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':
439
+ print_function_name()
440
+ output.with_parens(def():
441
+ for i, a in enumerate(self.args):
442
+ if i:
443
+ output.comma()
444
+ a.print(output)
445
+ )
446
+ elif is_node_type(self.expression, AST_SymbolRef) and self.expression.name is 'print':
447
+ # Bare print(...) → console.log(...) to avoid clobbering window.print
441
448
  output.print('console.log')
442
- else:
449
+ output.with_parens(def():
450
+ for i, a in enumerate(self.args):
451
+ if i:
452
+ output.comma()
453
+ a.print(output)
454
+ )
455
+ elif is_node_type(self.expression, AST_SymbolRef) and self.python_truthiness:
456
+ # __call__-aware dispatch when from __python__ import truthiness is active
457
+ output.print('ρσ_callable_call(')
443
458
  print_function_name()
444
- output.with_parens(def():
445
459
  for i, a in enumerate(self.args):
446
- if i:
447
- output.comma()
460
+ output.comma()
448
461
  a.print(output)
449
- )
462
+ output.print(')')
463
+ else:
464
+ # Method calls and other complex expressions — keep existing behaviour
465
+ print_function_name()
466
+ output.with_parens(def():
467
+ for i, a in enumerate(self.args):
468
+ if i:
469
+ output.comma()
470
+ a.print(output)
471
+ )
450
472
  return
451
473
 
452
474
  is_repeatable = is_new or not has_calls(self.expression)