naider 1.17.4 → 1.19.0

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.
@@ -164,6 +164,8 @@ export class PythonGenerator {
164
164
  case 'BlockchainDecl': return this.visitBlockchain(node);
165
165
  case 'EnumDecl': return this.visitEnum(node);
166
166
  case 'Swap': return this.visitSwap(node);
167
+ case 'Destructure': return this.visitDestructure(node);
168
+ case 'ClassDecl': return this.visitClassDecl(node);
167
169
  default:
168
170
  this.emit(`# unknown: ${node.type}`);
169
171
  }
@@ -2494,6 +2496,83 @@ export class PythonGenerator {
2494
2496
  this.emit(`${a}, ${b} = ${b}, ${a}`);
2495
2497
  }
2496
2498
 
2499
+ // ===== Destructure =====
2500
+ visitDestructure(node) {
2501
+ const val = this.expr(node.value);
2502
+ if (node.pattern === 'array') {
2503
+ const names = node.names.map(n => {
2504
+ if (n.rest) return `*${n.name}`;
2505
+ return n.name;
2506
+ });
2507
+ this.emit(`${names.join(', ')} = ${val}`);
2508
+ // Handle defaults for array destructuring
2509
+ for (const n of node.names) {
2510
+ if (n.defaultValue && !n.rest) {
2511
+ this.emit(`${n.name} = ${n.name} if ${n.name} is not None else ${this.expr(n.defaultValue)}`);
2512
+ }
2513
+ }
2514
+ } else {
2515
+ // object destructuring — per-field assignment
2516
+ for (const n of node.names) {
2517
+ if (n.rest) {
2518
+ this.emit(`${n.name} = {k: v for k, v in ${val}.items() if k not in {${node.names.filter(x => !x.rest).map(x => JSON.stringify(x.name)).join(', ')}}}`);
2519
+ } else {
2520
+ const varName = n.alias || n.name;
2521
+ if (n.defaultValue) {
2522
+ this.emit(`${varName} = ${val}.get(${JSON.stringify(n.name)}, ${this.expr(n.defaultValue)})`);
2523
+ } else {
2524
+ this.emit(`${varName} = ${val}[${JSON.stringify(n.name)}]`);
2525
+ }
2526
+ }
2527
+ }
2528
+ }
2529
+ }
2530
+
2531
+ // ===== ClassDecl =====
2532
+ visitClassDecl(node) {
2533
+ const ext = node.parent ? `(${node.parent})` : '';
2534
+ this.emit(`class ${node.name}${ext}:`);
2535
+ this.indent++;
2536
+ if (node.init) {
2537
+ const params = node.init.params.map(p => p.name).join(', ');
2538
+ const selfParams = params ? `self, ${params}` : 'self';
2539
+ this.emit(`def __init__(${selfParams}):`);
2540
+ this.indent++;
2541
+ if (node.parent) this.emit('super().__init__()');
2542
+ if (node.init.body.length === 0 && !node.parent) {
2543
+ this.emit('pass');
2544
+ } else {
2545
+ for (const stmt of node.init.body) this.visitStatement(stmt);
2546
+ }
2547
+ this.indent--;
2548
+ this.emitRaw('');
2549
+ }
2550
+ for (const field of node.fields) {
2551
+ if (field.defaultValue) {
2552
+ this.emit(`${field.name} = ${this.expr(field.defaultValue)}`);
2553
+ }
2554
+ }
2555
+ for (const method of node.methods) {
2556
+ const async = method.isAsync ? 'async ' : '';
2557
+ const params = method.params.map(p => p.name).join(', ');
2558
+ const selfParams = params ? `self, ${params}` : 'self';
2559
+ this.emit(`${async}def ${method.name}(${selfParams}):`);
2560
+ this.indent++;
2561
+ if (method.body.length === 0) {
2562
+ this.emit('pass');
2563
+ } else {
2564
+ for (const stmt of method.body) this.visitStatement(stmt);
2565
+ }
2566
+ this.indent--;
2567
+ this.emitRaw('');
2568
+ }
2569
+ if (!node.init && node.fields.length === 0 && node.methods.length === 0) {
2570
+ this.emit('pass');
2571
+ }
2572
+ this.indent--;
2573
+ this.emitRaw('');
2574
+ }
2575
+
2497
2576
  // ===== Builtins =====
2498
2577
  generateBuiltin(name, args) {
2499
2578
  switch (name) {
@@ -2536,6 +2615,13 @@ export class PythonGenerator {
2536
2615
  case 'now': { this.addImport('time'); return `int(time.time() * 1000)`; }
2537
2616
  case 'time': { this.addFromImport('datetime', 'datetime'); return `datetime.now().isoformat()`; }
2538
2617
  case 'chunk': return `[${args[0]}[i:i+${args[1]}] for i in range(0, len(${args[0]}), ${args[1]})]`;
2618
+ case 'map': return `list(map(${args[1]}, ${args[0]}))`;
2619
+ case 'filter': return `list(filter(${args[1]}, ${args[0]}))`;
2620
+ case 'reduce': { this.addFromImport('functools', 'reduce'); return args[2] ? `reduce(${args[1]}, ${args[0]}, ${args[2]})` : `reduce(${args[1]}, ${args[0]})`; }
2621
+ case 'find': return `next((x for x in ${args[0]} if ${args[1]}(x)), None)`;
2622
+ case 'every': return `all(${args[1]}(x) for x in ${args[0]})`;
2623
+ case 'some': return `any(${args[1]}(x) for x in ${args[0]})`;
2624
+ case 'foreach': return `[${args[1]}(x) for x in ${args[0]}]`;
2539
2625
  default: return null;
2540
2626
  }
2541
2627
  }
@@ -113,6 +113,8 @@ export class RubyGenerator {
113
113
  case 'BlockchainDecl': return this.visitBlockchain(node);
114
114
  case 'EnumDecl': return this.visitEnum(node);
115
115
  case 'Swap': return this.visitSwap(node);
116
+ case 'Destructure': return this.visitDestructure(node);
117
+ case 'ClassDecl': return this.visitClassDecl(node);
116
118
  default:
117
119
  this.emit(`# unknown: ${node.type}`);
118
120
  }
@@ -1553,6 +1555,51 @@ export class RubyGenerator {
1553
1555
  this.emit(`${a}, ${b} = ${b}, ${a}`);
1554
1556
  }
1555
1557
 
1558
+ visitDestructure(node) {
1559
+ const val = this.expr(node.value);
1560
+ if (node.pattern === 'array') {
1561
+ const names = node.names.map(n => {
1562
+ if (n.rest) return `*${n.name}`;
1563
+ return n.alias || n.name;
1564
+ });
1565
+ this.emit(`${names.join(', ')} = ${val}`);
1566
+ } else {
1567
+ const keys = node.names.filter(n => !n.rest).map(n => `:${n.name}`);
1568
+ const vars = node.names.filter(n => !n.rest).map(n => n.alias || n.name);
1569
+ this.emit(`${vars.join(', ')} = ${val}.values_at(${keys.join(', ')})`);
1570
+ }
1571
+ }
1572
+
1573
+ visitClassDecl(node) {
1574
+ const ext = node.parent ? ` < ${node.parent}` : '';
1575
+ this.emit(`class ${node.name}${ext}`);
1576
+ this.indent++;
1577
+ if (node.init) {
1578
+ const params = node.init.params.map(p => p.name).join(', ');
1579
+ this.emit(`def initialize(${params})`);
1580
+ this.indent++;
1581
+ if (node.parent) this.emit('super()');
1582
+ for (const stmt of node.init.body) this.visitStatement(stmt);
1583
+ this.indent--;
1584
+ this.emit('end');
1585
+ }
1586
+ for (const method of node.methods) {
1587
+ const params = method.params.map(p => p.name).join(', ');
1588
+ this.emit(`def ${method.name}(${params})`);
1589
+ this.indent++;
1590
+ if (method.body.length === 0) {
1591
+ this.emit('nil');
1592
+ } else {
1593
+ for (const stmt of method.body) this.visitStatement(stmt);
1594
+ }
1595
+ this.indent--;
1596
+ this.emit('end');
1597
+ }
1598
+ this.indent--;
1599
+ this.emit('end');
1600
+ this.emitRaw('');
1601
+ }
1602
+
1556
1603
  generateBuiltin(name, args) {
1557
1604
  switch (name) {
1558
1605
  case 'len': return `${args[0]}.length`;
@@ -1593,6 +1640,13 @@ export class RubyGenerator {
1593
1640
  case 'read': return `File.read(${args[0]})`;
1594
1641
  case 'write': return `File.write(${args[0]}, ${args[1]})`;
1595
1642
  case 'ask': return `(print(${args[0] || '""'}); gets.chomp)`;
1643
+ case 'map': return `${args[0]}.map { |_x| ${args[1]}.call(_x) }`;
1644
+ case 'filter': return `${args[0]}.select { |_x| ${args[1]}.call(_x) }`;
1645
+ case 'reduce': return args.length >= 3 ? `${args[0]}.reduce(${args[2]}) { |_acc, _x| ${args[1]}.call(_acc, _x) }` : `${args[0]}.reduce { |_acc, _x| ${args[1]}.call(_acc, _x) }`;
1646
+ case 'find': return `${args[0]}.find { |_x| ${args[1]}.call(_x) }`;
1647
+ case 'every': return `${args[0]}.all? { |_x| ${args[1]}.call(_x) }`;
1648
+ case 'some': return `${args[0]}.any? { |_x| ${args[1]}.call(_x) }`;
1649
+ case 'foreach': return `${args[0]}.each { |_x| ${args[1]}.call(_x) }`;
1596
1650
  default: return null;
1597
1651
  }
1598
1652
  }
@@ -209,6 +209,8 @@ export class RustGenerator {
209
209
  case 'BlockchainDecl': return this.visitBlockchain(node);
210
210
  case 'EnumDecl': return this.visitEnum(node);
211
211
  case 'Swap': return this.visitSwap(node);
212
+ case 'Destructure': return this.visitDestructure(node);
213
+ case 'ClassDecl': return this.visitClassDecl(node);
212
214
  default:
213
215
  this.emit(`/* unknown: ${node.type} */`);
214
216
  }
@@ -1911,6 +1913,71 @@ export class RustGenerator {
1911
1913
  this.emit(`std::mem::swap(&mut ${a}, &mut ${b});`);
1912
1914
  }
1913
1915
 
1916
+ visitDestructure(node) {
1917
+ const val = this.expr(node.value);
1918
+ const keyword = node.isMut ? 'let mut' : 'let';
1919
+ if (node.pattern === 'array') {
1920
+ const names = node.names.filter(n => !n.rest).map(n => n.alias || n.name);
1921
+ const indexedVals = names.map((name, i) => `${val}[${i}]`);
1922
+ this.emit(`${keyword} (${names.join(', ')}) = (${indexedVals.join(', ')});`);
1923
+ } else {
1924
+ for (const n of node.names) {
1925
+ if (!n.rest) {
1926
+ const varName = n.alias || n.name;
1927
+ if (n.defaultValue) {
1928
+ this.emit(`${keyword} ${varName} = ${val}.get("${n.name}").unwrap_or(&${this.expr(n.defaultValue)});`);
1929
+ } else {
1930
+ this.emit(`${keyword} ${varName} = ${val}["${n.name}"];`);
1931
+ }
1932
+ }
1933
+ }
1934
+ }
1935
+ }
1936
+
1937
+ visitClassDecl(node) {
1938
+ // Rust uses struct + impl
1939
+ this.emit(`struct ${node.name} {`);
1940
+ this.indent++;
1941
+ for (const field of node.fields) {
1942
+ this.emit(`${field.name}: Box<dyn std::any::Any>,`);
1943
+ }
1944
+ if (node.fields.length === 0) this.emit(`_marker: (),`);
1945
+ this.indent--;
1946
+ this.emit(`}`);
1947
+ this.emitRaw('');
1948
+ this.emit(`impl ${node.name} {`);
1949
+ this.indent++;
1950
+ if (node.init) {
1951
+ const params = node.init.params.map(p => `${p.name}: impl std::any::Any`).join(', ');
1952
+ this.emit(`fn new(${params}) -> Self {`);
1953
+ this.indent++;
1954
+ for (const stmt of node.init.body) this.visitStatement(stmt);
1955
+ if (node.fields.length > 0) {
1956
+ this.emit(`${node.name} { ${node.fields.map(f => f.name).join(', ')} }`);
1957
+ } else {
1958
+ this.emit(`${node.name} { _marker: () }`);
1959
+ }
1960
+ this.indent--;
1961
+ this.emit(`}`);
1962
+ }
1963
+ for (const method of node.methods) {
1964
+ const mutable = method.body.some(s => JSON.stringify(s).includes('"Assignment"')) ? '&mut self' : '&self';
1965
+ const params = method.params.length > 0 ? `, ${method.params.map(p => `${p.name}: impl std::any::Any`).join(', ')}` : '';
1966
+ this.emit(`fn ${method.name}(${mutable}${params}) {`);
1967
+ this.indent++;
1968
+ if (method.body.length === 0) {
1969
+ this.emit('// empty');
1970
+ } else {
1971
+ for (const stmt of method.body) this.visitStatement(stmt);
1972
+ }
1973
+ this.indent--;
1974
+ this.emit(`}`);
1975
+ }
1976
+ this.indent--;
1977
+ this.emit(`}`);
1978
+ this.emitRaw('');
1979
+ }
1980
+
1914
1981
  generateBuiltin(name, args) {
1915
1982
  switch (name) {
1916
1983
  case 'len': return `${args[0]}.len()`;
@@ -1942,6 +2009,13 @@ export class RustGenerator {
1942
2009
  case 'values': return `${args[0]}.values().cloned().collect::<Vec<_>>()`;
1943
2010
  case 'range': return args.length >= 2 ? `(${args[0]}..${args[1]}).collect::<Vec<_>>()` : `(0..${args[0]}).collect::<Vec<_>>()`;
1944
2011
  case 'flat': return `${args[0]}.into_iter().flatten().collect::<Vec<_>>()`;
2012
+ case 'map': return `${args[0]}.iter().map(${args[1]}).collect::<Vec<_>>()`;
2013
+ case 'filter': return `${args[0]}.iter().filter(${args[1]}).collect::<Vec<_>>()`;
2014
+ case 'reduce': return `${args[0]}.iter().fold(${args[2] || '0'}, ${args[1]})`;
2015
+ case 'find': return `${args[0]}.iter().find(${args[1]})`;
2016
+ case 'every': return `${args[0]}.iter().all(${args[1]})`;
2017
+ case 'some': return `${args[0]}.iter().any(${args[1]})`;
2018
+ case 'foreach': return `${args[0]}.iter().for_each(${args[1]})`;
1945
2019
  default: return null;
1946
2020
  }
1947
2021
  }
@@ -141,6 +141,8 @@ export class SwiftGenerator {
141
141
  case 'BlockchainDecl': return this.visitBlockchain(node);
142
142
  case 'EnumDecl': return this.visitEnum(node);
143
143
  case 'Swap': return this.visitSwap(node);
144
+ case 'Destructure': return this.visitDestructure(node);
145
+ case 'ClassDecl': return this.visitClassDecl(node);
144
146
  default:
145
147
  this.emit(`// unknown: ${node.type}`);
146
148
  }
@@ -1758,6 +1760,64 @@ export class SwiftGenerator {
1758
1760
  this.emit(`swap(&${a}, &${b})`);
1759
1761
  }
1760
1762
 
1763
+ visitDestructure(node) {
1764
+ const val = this.expr(node.value);
1765
+ const keyword = node.isMut ? 'var' : 'let';
1766
+ if (node.pattern === 'array') {
1767
+ const names = node.names.filter(n => !n.rest).map(n => n.alias || n.name);
1768
+ const indexedVals = names.map((name, i) => `${val}[${i}]`);
1769
+ this.emit(`${keyword} (${names.join(', ')}) = (${indexedVals.join(', ')})`);
1770
+ } else {
1771
+ for (const n of node.names) {
1772
+ if (!n.rest) {
1773
+ const varName = n.alias || n.name;
1774
+ if (n.defaultValue) {
1775
+ this.emit(`${keyword} ${varName} = ${val}["${n.name}"] ?? ${this.expr(n.defaultValue)}`);
1776
+ } else {
1777
+ this.emit(`${keyword} ${varName} = ${val}["${n.name}"]`);
1778
+ }
1779
+ }
1780
+ }
1781
+ }
1782
+ }
1783
+
1784
+ visitClassDecl(node) {
1785
+ const ext = node.parent ? `: ${node.parent}` : '';
1786
+ this.emit(`class ${node.name}${ext ? ' ' + ext : ''} {`);
1787
+ this.indent++;
1788
+ for (const field of node.fields) {
1789
+ if (field.defaultValue) {
1790
+ this.emit(`var ${field.name}: Any = ${this.expr(field.defaultValue)}`);
1791
+ } else {
1792
+ this.emit(`var ${field.name}: Any?`);
1793
+ }
1794
+ }
1795
+ if (node.init) {
1796
+ const params = node.init.params.map(p => `_ ${p.name}: Any`).join(', ');
1797
+ this.emit(`init(${params}) {`);
1798
+ this.indent++;
1799
+ if (node.parent) this.emit('super.init()');
1800
+ for (const stmt of node.init.body) this.visitStatement(stmt);
1801
+ this.indent--;
1802
+ this.emit('}');
1803
+ }
1804
+ for (const method of node.methods) {
1805
+ const params = method.params.map(p => `_ ${p.name}: Any`).join(', ');
1806
+ this.emit(`func ${method.name}(${params}) -> Any? {`);
1807
+ this.indent++;
1808
+ if (method.body.length === 0) {
1809
+ this.emit('return nil');
1810
+ } else {
1811
+ for (const stmt of method.body) this.visitStatement(stmt);
1812
+ }
1813
+ this.indent--;
1814
+ this.emit('}');
1815
+ }
1816
+ this.indent--;
1817
+ this.emit('}');
1818
+ this.emitRaw('');
1819
+ }
1820
+
1761
1821
  generateBuiltin(name, args) {
1762
1822
  switch (name) {
1763
1823
  case 'len': return `${args[0]}.count`;
@@ -1796,6 +1856,13 @@ export class SwiftGenerator {
1796
1856
  case 'random': return args.length >= 2 ? `Int.random(in: ${args[0]}...${args[1]})` : `Double.random(in: 0...1)`;
1797
1857
  case 'read': return `try! String(contentsOfFile: ${args[0]})`;
1798
1858
  case 'write': return `try! ${args[1]}.write(toFile: ${args[0]}, atomically: true, encoding: .utf8)`;
1859
+ case 'map': return `${args[0]}.map { ${args[1]}($0) }`;
1860
+ case 'filter': return `${args[0]}.filter { ${args[1]}($0) }`;
1861
+ case 'reduce': return `${args[0]}.reduce(${args[2] || '0'}) { ${args[1]}($0, $1) }`;
1862
+ case 'find': return `${args[0]}.first { ${args[1]}($0) }`;
1863
+ case 'every': return `${args[0]}.allSatisfy { ${args[1]}($0) }`;
1864
+ case 'some': return `${args[0]}.contains { ${args[1]}($0) }`;
1865
+ case 'foreach': return `${args[0]}.forEach { ${args[1]}($0) }`;
1799
1866
  default: return null;
1800
1867
  }
1801
1868
  }
package/src/generator.js CHANGED
@@ -94,6 +94,8 @@ export class Generator {
94
94
  case 'ReturnStatus': return this.visitReturnStatus(node);
95
95
  case 'ReturnMethod': return this.visitReturnMethod(node);
96
96
  case 'TypedVar': return this.visitTypedVar(node);
97
+ case 'Destructure': return this.visitDestructure(node);
98
+ case 'ClassDecl': return this.visitClassDecl(node);
97
99
  case 'If': return this.visitIf(node);
98
100
  case 'Each': return this.visitEach(node);
99
101
  case 'For': return this.visitFor(node);
@@ -244,6 +246,51 @@ export class Generator {
244
246
  this.emit(`${exp}${keyword} ${node.name} = ${this.expr(node.value)};`);
245
247
  }
246
248
 
249
+ visitDestructure(node) {
250
+ const keyword = node.isMut ? 'let' : 'const';
251
+ const parts = node.names.map(n => {
252
+ if (n.rest) return `...${n.name}`;
253
+ let s = n.name;
254
+ if (n.alias) s += `: ${n.alias}`;
255
+ if (n.defaultValue) s += ` = ${this.expr(n.defaultValue)}`;
256
+ return s;
257
+ });
258
+ const open = node.pattern === 'object' ? '{ ' : '[ ';
259
+ const close = node.pattern === 'object' ? ' }' : ' ]';
260
+ this.emit(`${keyword} ${open}${parts.join(', ')}${close} = ${this.expr(node.value)};`);
261
+ }
262
+
263
+ visitClassDecl(node) {
264
+ const ext = node.parent ? ` extends ${node.parent}` : '';
265
+ this.emit(`class ${node.name}${ext} {`);
266
+ this.indent++;
267
+ if (node.init) {
268
+ const params = node.init.params.map(p => p.name).join(', ');
269
+ this.emit(`constructor(${params}) {`);
270
+ this.indent++;
271
+ if (node.parent) this.emit('super();');
272
+ for (const stmt of node.init.body) this.visitStatement(stmt);
273
+ this.indent--;
274
+ this.emit('}');
275
+ }
276
+ for (const field of node.fields) {
277
+ if (field.defaultValue) {
278
+ this.emit(`${field.name} = ${this.expr(field.defaultValue)};`);
279
+ }
280
+ }
281
+ for (const method of node.methods) {
282
+ const async = method.isAsync ? 'async ' : '';
283
+ const params = method.params.map(p => p.name).join(', ');
284
+ this.emit(`${async}${method.name}(${params}) {`);
285
+ this.indent++;
286
+ for (const stmt of method.body) this.visitStatement(stmt);
287
+ this.indent--;
288
+ this.emit('}');
289
+ }
290
+ this.indent--;
291
+ this.emit('}');
292
+ }
293
+
247
294
  visitAssignment(node) {
248
295
  const target = this.expr(node.target);
249
296
  const value = this.expr(node.value);
@@ -2464,6 +2511,13 @@ export class Generator {
2464
2511
  case 'now': return `Date.now()`;
2465
2512
  case 'time': return `new Date().toISOString()`;
2466
2513
  case 'chunk': return `Array.from({length: Math.ceil(${args[0]}.length / ${args[1]})}, (_, i) => ${args[0]}.slice(i * ${args[1]}, (i + 1) * ${args[1]}))`;
2514
+ case 'map': return `${args[0]}.map(${args[1]})`;
2515
+ case 'filter': return `${args[0]}.filter(${args[1]})`;
2516
+ case 'reduce': return args[2] ? `${args[0]}.reduce(${args[1]}, ${args[2]})` : `${args[0]}.reduce(${args[1]})`;
2517
+ case 'find': return `${args[0]}.find(${args[1]})`;
2518
+ case 'every': return `${args[0]}.every(${args[1]})`;
2519
+ case 'some': return `${args[0]}.some(${args[1]})`;
2520
+ case 'foreach': return `${args[0]}.forEach(${args[1]})`;
2467
2521
  default: return null;
2468
2522
  }
2469
2523
  }
package/src/parser.js CHANGED
@@ -468,6 +468,12 @@ export class Parser {
468
468
 
469
469
  parseTypedVariable() {
470
470
  const varType = this.advance().value;
471
+ if (this.at(T.LBRACE)) {
472
+ return this.parseDestructure(varType, false);
473
+ }
474
+ if (this.at(T.LBRACKET)) {
475
+ return this.parseDestructure(varType, false);
476
+ }
471
477
  const name = this.expectIdentLike();
472
478
  this.expect(T.ASSIGN);
473
479
  const value = this.parseExpression();
@@ -481,12 +487,47 @@ export class Parser {
481
487
  node.isMut = true;
482
488
  return node;
483
489
  }
490
+ if (this.at(T.LBRACE) || this.at(T.LBRACKET)) {
491
+ return this.parseDestructure(null, true);
492
+ }
484
493
  const name = this.expectIdentLike();
485
494
  this.expect(T.ASSIGN);
486
495
  const value = this.parseExpression();
487
496
  return new ASTNode('TypedVar', { varType: null, name, value, isMut: true });
488
497
  }
489
498
 
499
+ parseDestructure(varType, isMut) {
500
+ const pattern = this.at(T.LBRACE) ? 'object' : 'array';
501
+ this.advance(); // { or [
502
+ const names = [];
503
+ const endToken = pattern === 'object' ? T.RBRACE : T.RBRACKET;
504
+ while (!this.at(endToken) && !this.at(T.EOF)) {
505
+ this.skipWhitespace();
506
+ if (this.at(endToken)) break;
507
+ if (this.match(T.SPREAD)) {
508
+ const name = this.expectIdentLike();
509
+ names.push({ name, rest: true });
510
+ } else {
511
+ const name = this.expectIdentLike();
512
+ let alias = null;
513
+ if (this.match(T.COLON)) {
514
+ alias = this.expectIdentLike();
515
+ }
516
+ let defaultValue = null;
517
+ if (this.match(T.ASSIGN)) {
518
+ defaultValue = this.parseExpression();
519
+ }
520
+ names.push({ name, alias, defaultValue, rest: false });
521
+ }
522
+ this.match(T.COMMA);
523
+ this.skipWhitespace();
524
+ }
525
+ this.expect(endToken);
526
+ this.expect(T.ASSIGN);
527
+ const value = this.parseExpression();
528
+ return new ASTNode('Destructure', { pattern, names, value, varType, isMut });
529
+ }
530
+
490
531
  parseIf() {
491
532
  this.advance(); // if
492
533
  const condition = this.parseExpression();
@@ -1394,19 +1435,40 @@ export class Parser {
1394
1435
  parseSchema() {
1395
1436
  this.advance(); // schema
1396
1437
  const name = this.expect(T.IDENT).value;
1438
+ let parent = null;
1439
+ if (this.match(T.EXTENDS)) {
1440
+ parent = this.expect(T.IDENT).value;
1441
+ }
1397
1442
  this.expect(T.COLON);
1398
1443
  this.skipNewlines();
1399
1444
  this.expect(T.INDENT);
1400
1445
 
1401
1446
  const fields = [];
1447
+ const methods = [];
1448
+ let init = null;
1402
1449
  this.skipNewlines();
1403
1450
 
1404
1451
  while (!this.at(T.DEDENT) && !this.at(T.EOF)) {
1405
- fields.push(this.parseSchemaField());
1452
+ if (this.at(T.FN) || this.at(T.FN_ASYNC)) {
1453
+ methods.push(this.parseFunction(this.at(T.FN_ASYNC), false));
1454
+ } else if (this.at(T.IDENT) && this.peek().value === 'init' && this.peek(1).type === T.LPAREN) {
1455
+ this.advance(); // init
1456
+ this.expect(T.LPAREN);
1457
+ const params = this.parseFnParams();
1458
+ this.expect(T.RPAREN);
1459
+ this.expect(T.COLON);
1460
+ const body = this.parseBlock();
1461
+ init = { params, body };
1462
+ } else {
1463
+ fields.push(this.parseSchemaField());
1464
+ }
1406
1465
  this.skipNewlines();
1407
1466
  }
1408
1467
  this.match(T.DEDENT);
1409
1468
 
1469
+ if (parent || methods.length > 0 || init) {
1470
+ return new ASTNode('ClassDecl', { name, parent, fields, methods, init });
1471
+ }
1410
1472
  return new ASTNode('SchemaDecl', { name, fields });
1411
1473
  }
1412
1474
 
package/src/tokens.js CHANGED
@@ -114,6 +114,10 @@ export const T = {
114
114
  SWAP: 'SWAP',
115
115
  IS: 'IS',
116
116
  ISNT: 'ISNT',
117
+ EXTENDS: 'EXTENDS',
118
+
119
+ // Types (auto)
120
+ TYPE_AUTO: 'TYPE_AUTO',
117
121
 
118
122
  // Operators
119
123
  ASSIGN: 'ASSIGN',
@@ -256,7 +260,9 @@ export const KEYWORDS = {
256
260
  'swap': T.SWAP,
257
261
  'is': T.IS,
258
262
  'isnt': T.ISNT,
263
+ 'extends': T.EXTENDS,
259
264
  'print': T.LOG,
265
+ 'auto': T.TYPE_AUTO,
260
266
  'true': T.BOOL,
261
267
  'false': T.BOOL,
262
268
  'null': T.NULL,
@@ -274,4 +280,5 @@ export const KEYWORDS = {
274
280
  export const TYPE_TOKENS = new Set([
275
281
  T.TYPE_STR, T.TYPE_INT, T.TYPE_NUM, T.TYPE_BOOL,
276
282
  T.TYPE_LIST, T.TYPE_MAP, T.TYPE_ANY, T.TYPE_JSON, T.TYPE_VOID,
283
+ T.TYPE_AUTO,
277
284
  ]);
@@ -79,7 +79,7 @@
79
79
  "name": "storage.modifier.naide"
80
80
  },
81
81
  "types": {
82
- "match": "\\b(str|int|num|bool|list|map|any|json|void)\\b",
82
+ "match": "\\b(str|int|num|bool|list|map|any|json|void|auto)\\b",
83
83
  "name": "support.type.naide"
84
84
  },
85
85
  "keywords-control": {
@@ -91,7 +91,7 @@
91
91
  "name": "keyword.other.naide"
92
92
  },
93
93
  "keywords-declaration": {
94
- "match": "\\b(fn|fn\\.async|use|from|as|model|extends|schema|env|db|every|watch|test|assert|queue|job|new|enum|swap)\\b",
94
+ "match": "\\b(fn|fn\\.async|use|from|as|model|extends|schema|env|db|every|watch|test|assert|queue|job|new|enum|swap|init)\\b",
95
95
  "name": "keyword.declaration.naide"
96
96
  },
97
97
  "keywords-operator": {
@@ -129,7 +129,7 @@
129
129
  "name": "support.function.naide"
130
130
  },
131
131
  {
132
- "match": "\\b(ask|sleep|exit|read|write|fetch_json|random|sort|reverse|unique|len|upper|lower|trim|split|join|contains|replace|keys|values|entries|range|abs|round|ceil|floor|sqrt|pow|sum|flat|zip|chunk|str|int|float|json_parse|json_str|now|time|print)\\b(?=\\s*[\\(])",
132
+ "match": "\\b(ask|sleep|exit|read|write|fetch_json|random|sort|reverse|unique|len|upper|lower|trim|split|join|contains|replace|keys|values|entries|range|abs|round|ceil|floor|sqrt|pow|sum|flat|zip|chunk|str|int|float|json_parse|json_str|now|time|print|map|filter|reduce|find|every|some|foreach)\\b(?=\\s*[\\(])",
133
133
  "name": "support.function.naide"
134
134
  }
135
135
  ]