rapydscript-ns 0.8.2 → 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 (50) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/PYTHON_DIFFERENCES_REPORT.md +291 -0
  3. package/PYTHON_FEATURE_COVERAGE.md +96 -5
  4. package/README.md +161 -46
  5. package/TODO.md +2 -283
  6. package/language-service/index.js +4474 -0
  7. package/language-service/language-service.d.ts +40 -0
  8. package/package.json +9 -7
  9. package/src/baselib-builtins.pyj +77 -1
  10. package/src/baselib-containers.pyj +8 -4
  11. package/src/baselib-internal.pyj +30 -1
  12. package/src/baselib-str.pyj +8 -1
  13. package/src/lib/collections.pyj +1 -1
  14. package/src/lib/numpy.pyj +10 -10
  15. package/src/monaco-language-service/analyzer.js +131 -9
  16. package/src/monaco-language-service/builtins.js +12 -2
  17. package/src/monaco-language-service/completions.js +170 -1
  18. package/src/monaco-language-service/diagnostics.js +1 -1
  19. package/src/monaco-language-service/index.js +17 -0
  20. package/src/monaco-language-service/scope.js +3 -0
  21. package/src/output/classes.pyj +20 -3
  22. package/src/output/codegen.pyj +1 -1
  23. package/src/output/functions.pyj +4 -16
  24. package/src/output/loops.pyj +0 -9
  25. package/src/output/modules.pyj +1 -4
  26. package/src/output/operators.pyj +14 -0
  27. package/src/output/stream.pyj +0 -13
  28. package/src/parse.pyj +17 -1
  29. package/test/baselib.pyj +4 -4
  30. package/test/classes.pyj +56 -17
  31. package/test/collections.pyj +5 -5
  32. package/test/python_compat.pyj +326 -0
  33. package/test/python_features.pyj +110 -23
  34. package/test/slice.pyj +105 -0
  35. package/test/str.pyj +25 -0
  36. package/test/unit/fixtures/fibonacci_expected.js +1 -1
  37. package/test/unit/index.js +119 -7
  38. package/test/unit/language-service-builtins.js +70 -0
  39. package/test/unit/language-service-bundle.js +5 -5
  40. package/test/unit/language-service-completions.js +180 -0
  41. package/test/unit/language-service-index.js +350 -0
  42. package/test/unit/language-service-scope.js +255 -0
  43. package/test/unit/language-service.js +35 -0
  44. package/test/unit/run-language-service.js +1 -0
  45. package/test/unit/web-repl.js +134 -0
  46. package/tools/build-language-service.js +2 -2
  47. package/tools/compiler.js +0 -24
  48. package/tools/export.js +3 -37
  49. package/web-repl/rapydscript.js +6 -40
  50. package/web-repl/language-service.js +0 -4187
@@ -45,6 +45,13 @@ export const KEYWORDS = [
45
45
  * @returns {{ type: string, objectName?: string, moduleName?: string, prefix: string }}
46
46
  */
47
47
  export function detect_context(linePrefix) {
48
+ // `obj.method().attr` — dot access on a call result (single-level call expression).
49
+ // Must be checked before the plain dot match since `[\w.]+` cannot span `()`.
50
+ const call_result_match = linePrefix.match(/([\w.]+)\([^)]*\)\.([\w]*)$/);
51
+ if (call_result_match) {
52
+ return { type: 'call_result', callPath: call_result_match[1], prefix: call_result_match[2] };
53
+ }
54
+
48
55
  // `obj.attr` or `obj.sub.attr` — dot access (captures multi-level path)
49
56
  const dot_match = linePrefix.match(/([\w.]+)\.([\w]*)$/);
50
57
  if (dot_match) {
@@ -265,6 +272,9 @@ export class CompletionEngine {
265
272
  getCompletions(scopeMap, position, linePrefix, monacoKind) {
266
273
  const ctx = detect_context(linePrefix);
267
274
 
275
+ if (ctx.type === 'call_result') {
276
+ return { suggestions: this._call_result_completions(position, ctx, monacoKind, scopeMap) };
277
+ }
268
278
  if (ctx.type === 'dot') {
269
279
  return { suggestions: this._dot_completions(scopeMap, position, ctx, monacoKind) };
270
280
  }
@@ -346,6 +356,82 @@ export class CompletionEngine {
346
356
  return items;
347
357
  }
348
358
 
359
+ // ---- Return-type helpers -----------------------------------------------
360
+
361
+ /**
362
+ * Analyze a module and return the `return_type` of a named function.
363
+ * @param {string} func_name
364
+ * @param {string} module_name
365
+ * @returns {string|null}
366
+ */
367
+ _get_return_type_from_module(func_name, module_name) {
368
+ const src = this._virtualFiles[module_name] || this._stdlibFiles[module_name];
369
+ if (!src) return null;
370
+ let mod_map;
371
+ try {
372
+ mod_map = this._analyzer.analyze(src, {});
373
+ } catch (_e) {
374
+ return null;
375
+ }
376
+ const mod_frame = mod_map.frames.find(f => f.kind === 'module');
377
+ if (!mod_frame) return null;
378
+ const fn_sym = mod_frame.getSymbol(func_name);
379
+ if (fn_sym && fn_sym.return_type) return fn_sym.return_type;
380
+ return null;
381
+ }
382
+
383
+ /**
384
+ * Resolve a type name (e.g. from inferred_class) through function return annotations,
385
+ * including cross-file imports. Returns the resolved type name, or null if unresolvable.
386
+ * @param {string} name
387
+ * @param {import('./scope.js').ScopeMap} scopeMap
388
+ * @returns {string|null}
389
+ */
390
+ _resolve_type_through_functions(name, scopeMap) {
391
+ let sym = null;
392
+ for (const frame of scopeMap.frames) {
393
+ const s = frame.getSymbol(name);
394
+ if (s) { sym = s; break; }
395
+ }
396
+ if (!sym) return null;
397
+
398
+ if (sym.kind === 'function' || sym.kind === 'method') {
399
+ // return_type is set by explicit annotation or inferred by the analyzer
400
+ if (sym.return_type) return sym.return_type;
401
+ return null;
402
+ }
403
+
404
+ if (sym.kind === 'import' && sym.source_module) {
405
+ const orig = sym.original_name || sym.name;
406
+ return this._get_return_type_from_module(orig, sym.source_module);
407
+ }
408
+
409
+ return null;
410
+ }
411
+
412
+ /**
413
+ * Get the member map for a given type name.
414
+ * Checks builtins, then user class frames. Returns null if unknown.
415
+ * @param {string} type_name
416
+ * @param {import('./scope.js').ScopeMap|null} scopeMap
417
+ * @returns {Map|null}
418
+ */
419
+ _get_members_for_type(type_name, scopeMap) {
420
+ if (!type_name) return null;
421
+ if (this._builtins) {
422
+ const m = this._builtins.getTypeMembers(type_name);
423
+ if (m) return m;
424
+ }
425
+ if (scopeMap) {
426
+ for (const frame of scopeMap.frames) {
427
+ if (frame.kind === 'class' && frame.name === type_name) {
428
+ return frame.symbols;
429
+ }
430
+ }
431
+ }
432
+ return null;
433
+ }
434
+
349
435
  // ---- Dot completions ---------------------------------------------------
350
436
 
351
437
  _dot_completions(scopeMap, position, ctx, monacoKind) {
@@ -413,6 +499,13 @@ export class CompletionEngine {
413
499
  }
414
500
  }
415
501
 
502
+ // Resolve class_name through function return type annotations.
503
+ // e.g. x = getAllServers() → inferred_class='getAllServers' → resolve 'list'
504
+ if (class_name) {
505
+ const resolved = this._resolve_type_through_functions(class_name, scopeMap);
506
+ if (resolved) class_name = resolved;
507
+ }
508
+
416
509
  if (class_name) {
417
510
  for (const frame of scopeMap.frames) {
418
511
  if (frame.kind === 'class' && frame.name === class_name) {
@@ -433,7 +526,13 @@ export class CompletionEngine {
433
526
  // 1.5. Built-in type members — list, str, dict, number.
434
527
  // Used when inferred_class names a built-in type, not a user class.
435
528
  if (!scope_matched && this._builtins && obj_sym && obj_sym.inferred_class) {
436
- const members = this._builtins.getTypeMembers(obj_sym.inferred_class);
529
+ // Re-resolve in case inferred_class itself is a function name.
530
+ let type_name = obj_sym.inferred_class;
531
+ if (scopeMap) {
532
+ const resolved = this._resolve_type_through_functions(type_name, scopeMap);
533
+ if (resolved) type_name = resolved;
534
+ }
535
+ const members = this._builtins.getTypeMembers(type_name);
437
536
  if (members) {
438
537
  scope_matched = true;
439
538
  for (const [name, member] of members) {
@@ -496,6 +595,76 @@ export class CompletionEngine {
496
595
  return items;
497
596
  }
498
597
 
598
+ // ---- Call-result dot completions (e.g. ns.self().attr) -----------------
599
+
600
+ _call_result_completions(position, ctx, monacoKind, scopeMap) {
601
+ const range = word_range(position, ctx.prefix);
602
+ const items = [];
603
+ const seen = new Set();
604
+ const call_path = ctx.callPath;
605
+ const last_dot = call_path.lastIndexOf('.');
606
+
607
+ // --- DTS path (existing logic) ---
608
+ if (this._dts) {
609
+ let member_ti = null;
610
+ if (last_dot > 0) {
611
+ const object_path = call_path.slice(0, last_dot);
612
+ const method_name = call_path.slice(last_dot + 1);
613
+ member_ti = this._dts.getMemberInfo(object_path, method_name);
614
+ } else {
615
+ member_ti = this._dts.getGlobal(call_path);
616
+ }
617
+ if (member_ti && member_ti.return_type) {
618
+ const return_ti = this._dts.getGlobal(resolve_first_type(member_ti.return_type));
619
+ if (return_ti && return_ti.members) {
620
+ for (const [name, member] of return_ti.members) {
621
+ if (!ctx.prefix || name.startsWith(ctx.prefix)) {
622
+ seen.add(name);
623
+ items.push(_dts_member_to_item(member, range, monacoKind));
624
+ }
625
+ }
626
+ }
627
+ }
628
+ }
629
+
630
+ // --- User-defined function return types (scopeMap) ---
631
+ if (scopeMap && last_dot < 0) {
632
+ // Simple call like getAllServers() — look up function in scope
633
+ let sym = null;
634
+ for (const frame of scopeMap.frames) {
635
+ const s = frame.getSymbol(call_path);
636
+ if (s) { sym = s; break; }
637
+ }
638
+ if (sym) {
639
+ let return_type = null;
640
+ if (sym.kind === 'function' || sym.kind === 'method') {
641
+ return_type = sym.return_type;
642
+ } else if (sym.kind === 'import' && sym.source_module) {
643
+ const orig = sym.original_name || sym.name;
644
+ return_type = this._get_return_type_from_module(orig, sym.source_module);
645
+ }
646
+ if (return_type) {
647
+ const members = this._get_members_for_type(return_type, scopeMap);
648
+ if (members) {
649
+ for (const [name, member] of members) {
650
+ if (!seen.has(name) && (!ctx.prefix || name.startsWith(ctx.prefix))) {
651
+ seen.add(name);
652
+ // members may be SymbolInfo (class frame) or TypeInfo/BuiltinInfo
653
+ const item = member.kind === 'function' || member.kind === 'method' ||
654
+ member.kind === 'variable' || member.kind === 'parameter'
655
+ ? symbol_to_item(member, range, monacoKind, '0')
656
+ : _dts_member_to_item(member, range, monacoKind);
657
+ items.push(item);
658
+ }
659
+ }
660
+ }
661
+ }
662
+ }
663
+ }
664
+
665
+ return items;
666
+ }
667
+
499
668
  // ---- from X import completions -----------------------------------------
500
669
 
501
670
  _from_import_completions(position, ctx, monacoKind) {
@@ -34,7 +34,7 @@ export const BASE_BUILTINS = (
34
34
  ' NodeList alert console Node Symbol NamedNodeMap ρσ_eslice ρσ_delslice Number' +
35
35
  ' Boolean encodeURIComponent decodeURIComponent setTimeout setInterval' +
36
36
  ' setImmediate clearTimeout clearInterval clearImmediate requestAnimationFrame' +
37
- ' id repr sorted __name__ equals get_module ρσ_str jstype divmod NaN super Ellipsis'
37
+ ' id repr sorted __name__ equals get_module ρσ_str jstype divmod NaN super Ellipsis slice'
38
38
  ).split(' ');
39
39
 
40
40
  // ---------------------------------------------------------------------------
@@ -36,6 +36,13 @@ class ModelState {
36
36
  this._timer = null;
37
37
  this._scopeMap = null; // most recent ScopeMap for this model
38
38
 
39
+ // Derive the module name from the model URI so this model's content can
40
+ // be registered in _virtualFiles for cross-file import resolution.
41
+ // e.g. URI path "/scripts/utils.py" → module name "utils"
42
+ const uri_path = (model.uri && (model.uri.path || model.uri.fsPath)) || '';
43
+ const basename = uri_path.split('/').pop() || '';
44
+ this._module_name = basename.replace(/\.pyj?x?$/, '') || null;
45
+
39
46
  // Run immediately on first attach
40
47
  this._schedule(0);
41
48
 
@@ -55,6 +62,12 @@ class ModelState {
55
62
  const code = this._model.getValue();
56
63
  const opts = { virtualFiles: service._virtualFiles, stdlibFiles: service._stdlibFiles };
57
64
 
65
+ // Register this model's content in the shared virtualFiles pool so other
66
+ // open models can resolve cross-file imports from it.
67
+ if (this._module_name) {
68
+ service._virtualFiles[this._module_name] = code;
69
+ }
70
+
58
71
  // Diagnostics (syntax errors + lint markers)
59
72
  const markers = service._diagnostics.check(code, {
60
73
  ...opts,
@@ -69,6 +82,10 @@ class ModelState {
69
82
  dispose() {
70
83
  clearTimeout(this._timer);
71
84
  this._subscription.dispose();
85
+ // Remove this model's content from the virtualFiles pool on detach.
86
+ if (this._module_name) {
87
+ delete this._service._virtualFiles[this._module_name];
88
+ }
72
89
  // Clear markers when detaching
73
90
  this._service._monaco.editor.setModelMarkers(this._model, LANGUAGE_ID, []);
74
91
  }
@@ -30,6 +30,9 @@ export class SymbolInfo {
30
30
  this.inferred_class = opts.inferred_class || null; // 'ClassName' when x = ClassName(...)
31
31
  this.dts_call_path = opts.dts_call_path || null; // 'ns.getServer' when x = ns.getServer(...)
32
32
  this.type = null; // Phase 6: TypeInfo
33
+ this.return_type = opts.return_type || null; // annotated return type, e.g. 'list', 'str', 'MyClass'
34
+ this.source_module = opts.source_module || null; // module name when kind='import' (from X import Y)
35
+ this.original_name = opts.original_name || null; // pre-alias name for imports (from X import Y as Z → 'Y')
33
36
  }
34
37
  }
35
38
 
@@ -214,7 +214,7 @@ def print_class(output):
214
214
  output.end_statement()
215
215
 
216
216
  elif is_node_type(stmt, AST_Class):
217
- console.error('Nested classes aren\'t supported yet') # noqa:undef
217
+ pass # nested classes are emitted in the statements section below
218
218
 
219
219
  if not defined_methods['__repr__']:
220
220
  define_default_method('__repr__', def():
@@ -271,9 +271,26 @@ def print_class(output):
271
271
  output.print(JSON.stringify(create_doctring(self.docstrings)))
272
272
  )
273
273
 
274
- # Other statements in the class context
274
+ # Other statements in the class context (including nested class definitions)
275
275
  for stmt in self.statements:
276
- if not is_node_type(stmt, AST_Method):
276
+ if is_node_type(stmt, AST_Class):
277
+ # Print the nested class in full, then attach it to the outer class.
278
+ # Two assignments mirror Python semantics:
279
+ # Outer.Inner — class-level access (Outer.Inner())
280
+ # Outer.prototype.Inner — instance-level access (self.Inner() inside methods)
281
+ output.indent()
282
+ stmt.print(output)
283
+ output.newline()
284
+ nested_name = stmt.name.name
285
+ output.indent()
286
+ self.name.print(output)
287
+ output.print('.' + nested_name + ' = ' + nested_name)
288
+ output.end_statement()
289
+ output.indent()
290
+ self.name.print(output)
291
+ output.print('.prototype.' + nested_name + ' = ' + nested_name)
292
+ output.end_statement()
293
+ elif not is_node_type(stmt, AST_Method):
277
294
  output.indent()
278
295
  stmt.print(output)
279
296
  output.newline()
@@ -2,7 +2,7 @@
2
2
  # License: BSD Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net>
3
3
  from __python__ import hash_literals
4
4
 
5
- # globals:console,regenerate,writefile
5
+ # globals:console,writefile
6
6
 
7
7
  from utils import noop
8
8
  from parse import PRECEDENCE
@@ -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
@@ -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)')
@@ -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
@@ -436,8 +435,6 @@ def print_list_comprehension(self, output):
436
435
  output.with_block(def():
437
436
  body_out = output
438
437
  if is_generator:
439
- if es5:
440
- body_out = OutputStream({'beautify':True})
441
438
  body_out.indent()
442
439
  body_out.print('function* js_generator()'), body_out.space(), body_out.print('{')
443
440
  body_out.newline()
@@ -485,12 +482,6 @@ def print_list_comprehension(self, output):
485
482
  if is_generator:
486
483
  output.set_indentation(previous_indentation)
487
484
  body_out.newline(), body_out.indent(), body_out.print('}') # end js_generator
488
- if es5:
489
- transpiled = regenerate(body_out.get(), output.options.beautify).replace(/regeneratorRuntime.(wrap|mark)/g, 'ρσ_regenerator.regeneratorRuntime.$1')
490
- if output.options.beautify:
491
- ci = output.make_indent(0)
492
- transpiled = [ci + x for x in transpiled.split('\n')].join('\n')
493
- output.print(transpiled)
494
485
  output.newline(), output.indent()
495
486
  output.spaced('var', 'result', '=', 'js_generator.call(this)')
496
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')
@@ -280,6 +280,12 @@ def print_binary_op(self, output):
280
280
  write_smart_equality(self, output)
281
281
  elif self.operator is 'instanceof':
282
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(')')
283
289
  elif self.operator is '*' and is_node_type(self.left, AST_String):
284
290
  self.left.print(output), output.print('.repeat('), self.right.print(output), output.print(')')
285
291
  elif self.operator is '===' or self.operator is '!==':
@@ -415,6 +421,14 @@ def print_assign(self, output):
415
421
  else:
416
422
  output.print('Math.pow('), self.left.print(output), output.comma(), self.right.print(output), output.print(')')
417
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
418
432
  if self.operator is '=' and self.is_chained():
419
433
  left_hand_sides, rhs = self.traverse_chain()
420
434
  is_compound_assign = False
@@ -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
@@ -255,18 +254,6 @@ class OutputStream:
255
254
  if self.options.space_colon:
256
255
  self.space()
257
256
 
258
- def dump_yield(self):
259
- self.indent()
260
- self.spaced('var', 'ρσ_regenerator', '=', '{}')
261
- self.end_statement()
262
- code = 'ρσ_regenerator.regeneratorRuntime = ' + regenerate(False, self.options.beautify)
263
- if self.options.beautify:
264
- code = code.replace(/\/\/.*$/mg, '\n').replace(/^\s*$/gm, '') # strip comments
265
- ci = self.make_indent(0)
266
- code = [ci + x for x in code.split('\n')].join('\n')
267
- self.print(code + '})(ρσ_regenerator)')
268
- self.end_statement()
269
-
270
257
  def get(self):
271
258
  return self.OUTPUT
272
259
  toString = get
package/src/parse.pyj CHANGED
@@ -1416,6 +1416,18 @@ def create_parser_ctx(S, import_dirs, module_id, baselib_items, imported_module_
1416
1416
  bases = v'[]'
1417
1417
  class_parent = None
1418
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
+
1419
1431
  # read the bases of the class, if any
1420
1432
  if is_("punc", "("):
1421
1433
  S.in_parenthesized_expr = True
@@ -1521,7 +1533,11 @@ def create_parser_ctx(S, import_dirs, module_id, baselib_items, imported_module_
1521
1533
  visitor = new walker()
1522
1534
 
1523
1535
  for stmt in definition.body:
1524
- 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:
1525
1541
  stmt.walk(visitor)
1526
1542
  definition.statements.push(stmt)
1527
1543
  return definition
package/test/baselib.pyj CHANGED
@@ -117,8 +117,8 @@ assrt.throws(def():a.index(8);, ValueError)
117
117
  assrt.throws(def():a.index(1, 1);, ValueError)
118
118
  assrt.throws(def():a.index(4, 1, 2);, ValueError)
119
119
  assrt.equal(1, a.index(2, 1, 2))
120
- assrt.throws(def():a.pypop(10);, IndexError)
121
- assrt.equal(a.pypop(-1), 4)
120
+ assrt.throws(def():a.pop(10);, IndexError)
121
+ assrt.equal(a.pop(-1), 4)
122
122
  assrt.deepEqual(a, [1,2,3])
123
123
  assrt.equal(a.remove(2), None)
124
124
  assrt.deepEqual(a, [1, 3])
@@ -141,9 +141,9 @@ assrt.ok(a.as_array().extend == undefined)
141
141
  a = [1, 2, 1]
142
142
  assrt.equal(a.count(1), 2)
143
143
  a = [3, 2, 4, 1]
144
- a.pysort()
144
+ a.sort()
145
145
  assrt.deepEqual(a, [1,2,3,4])
146
- a.pysort(reverse=True)
146
+ a.sort(reverse=True)
147
147
  assrt.deepEqual(a, [4,3,2,1])
148
148
  assrt.deepEqual(a, a.slice())
149
149
  assrt.ok(a is not a.slice())
package/test/classes.pyj CHANGED
@@ -67,7 +67,7 @@ bound = angela.get_bound_method()
67
67
  assrt.equal(bound(), angela.how_long())
68
68
 
69
69
  # function methods
70
- assrt.deepEqual(dir(angela).sort(), [
70
+ assrt.deepEqual(sorted(dir(angela)), [
71
71
  'HAIRS',
72
72
  "__init__",
73
73
  '__repr__',
@@ -137,22 +137,61 @@ inc()
137
137
  assrt.equal(c.count, 6)
138
138
 
139
139
  # nested classes
140
- # not yet fully implemented
141
- #class Molecule:
142
- # class Atom:
143
- # def __init__(self, element):
144
- # self.element = element
145
- #
146
- # def __init__(self, elements):
147
- # self.structure = []
148
- # for e in elements:
149
- # self.structure.push(Molecule.Atom(e))
150
- #
151
- #water = Molecule(['H', "H", 'O'])
152
- #assrt.equal(len(water.structure), 3)
153
- #assrt.equal(water.structure[0].element, 'H')
154
- #for atom in water.structure:
155
- # assrt.ok(isinstance(atom, Molecule.Atom))
140
+ class Molecule:
141
+ class Atom:
142
+ def __init__(self, element):
143
+ self.element = element
144
+ def __repr__(self):
145
+ return 'Atom(' + self.element + ')'
146
+
147
+ def __init__(self, elements):
148
+ self.structure = []
149
+ for e in elements:
150
+ self.structure.push(Molecule.Atom(e))
151
+
152
+ water = Molecule(['H', 'H', 'O'])
153
+ assrt.equal(len(water.structure), 3)
154
+ assrt.equal(water.structure[0].element, 'H')
155
+ assrt.equal(water.structure[2].element, 'O')
156
+ for atom in water.structure:
157
+ assrt.ok(isinstance(atom, Molecule.Atom))
158
+
159
+ # nested class accessible via class reference and via instance attribute
160
+ assrt.ok(Molecule.Atom is water.structure[0].__class__)
161
+ # nested class can also be accessed via instance (self.Inner semantics)
162
+ mol2 = Molecule(['C'])
163
+ assrt.ok(isinstance(mol2.structure[0], mol2.Atom))
164
+
165
+ # two levels of nesting
166
+ class Universe:
167
+ class Galaxy:
168
+ class Star:
169
+ def __init__(self, name):
170
+ self.name = name
171
+ def __init__(self, star_name):
172
+ self.star = Universe.Galaxy.Star(star_name)
173
+ def __init__(self, star_name):
174
+ self.galaxy = Universe.Galaxy(star_name)
175
+
176
+ u = Universe('Sol')
177
+ assrt.equal(u.galaxy.star.name, 'Sol')
178
+ assrt.ok(isinstance(u.galaxy.star, Universe.Galaxy.Star))
179
+
180
+ # nested class with inheritance from outer-scope class
181
+ class Animal:
182
+ def __init__(self, sound):
183
+ self.sound = sound
184
+
185
+ class Zoo:
186
+ class Dog(Animal):
187
+ def __init__(self):
188
+ Animal.__init__(self, 'woof')
189
+
190
+ zoo = Zoo()
191
+ fido = Zoo.Dog()
192
+ assrt.equal(fido.sound, 'woof')
193
+ assrt.ok(isinstance(fido, Animal))
194
+ assrt.ok(isinstance(fido, Zoo.Dog))
156
195
 
157
196
  # starargs and method decorators
158
197
  def negate(fn):
@@ -62,18 +62,18 @@ assrt.equal(s[4:1:-2], 'ec')
62
62
 
63
63
  # sorting
64
64
  a = [2,1,3]
65
- a.pysort(key=def(x):return 0;)
65
+ a.sort(key=def(x):return 0;)
66
66
  assrt.deepEqual(a, [2,1,3]) # stable sort
67
- a.pysort(reverse=True)
67
+ a.sort(reverse=True)
68
68
  assrt.deepEqual(a, [3,2,1])
69
- a.pysort()
69
+ a.sort()
70
70
  assrt.deepEqual(a, [1,2,3])
71
71
 
72
72
  # misc interface
73
73
  a = [1,2,3]
74
- assrt.equal(a.pypop(), 3)
74
+ assrt.equal(a.pop(), 3)
75
75
  assrt.deepEqual(a, [1,2])
76
- assrt.equal(a.pypop(0), 1)
76
+ assrt.equal(a.pop(0), 1)
77
77
  assrt.deepEqual(a, [2])
78
78
 
79
79
  # strings