rip-lang 3.7.4 → 3.8.8

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.
@@ -0,0 +1,5 @@
1
+ <svg width="420" height="420" viewBox="0 0 420 420" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <circle cx="210" cy="210" r="210" fill="white"/>
3
+ <path d="M114.5 263C79.772 263 45.7872 275.051 34.271 283.579C33.4994 284.151 34.012 285.229 34.9656 285.117C73.1916 280.629 115.309 292.74 146.5 304C178.5 315.552 221 336 269 336C314.651 336 372.074 310.499 386.401 293.944C387.038 293.208 386.301 292.353 385.435 292.799C376.614 297.341 364.243 306.018 316.073 306.948C265.273 307.928 159 263 114.5 263Z" fill="#0389FF"/>
4
+ <path d="M223.46 84C239.53 84 253.592 86.9253 265.645 92.7754C277.697 98.6254 287.071 107.048 293.767 118.043C300.462 129.038 303.811 142.219 303.811 157.584C303.811 173.09 300.357 186.165 293.449 196.808C287.068 206.742 278.259 214.402 267.026 219.792L309.603 297.958C297.885 297.851 283.094 295.413 266.52 291.62C257.58 289.574 248.214 287.157 238.645 284.547L209.127 229.054H188.782V270.333C176.996 266.968 165.515 263.788 154.823 261.15C146.038 258.984 137.665 257.152 130 255.885V84H223.46ZM188.782 183.381H209.505C216.412 183.381 222.297 182.535 227.16 180.844C232.094 179.082 235.865 176.297 238.473 172.491C241.151 168.685 242.49 163.716 242.49 157.584C242.49 151.382 241.151 146.342 238.473 142.466C235.865 138.519 232.094 135.628 227.16 133.796C222.297 131.893 216.412 130.941 209.505 130.941H188.782V183.381Z" fill="#BB0000"/>
5
+ </svg>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rip-lang",
3
- "version": "3.7.4",
3
+ "version": "3.8.8",
4
4
  "description": "A modern language that compiles to JavaScript",
5
5
  "type": "module",
6
6
  "main": "src/compiler.js",
@@ -67,7 +67,7 @@
67
67
  "author": "Steve Shreeve <steve.shreeve@gmail.com>",
68
68
  "license": "MIT",
69
69
  "devDependencies": {
70
- "@rip-lang/api": "1.1.5",
71
- "@rip-lang/ui": "0.2.0"
70
+ "@rip-lang/api": "workspace:*",
71
+ "@rip-lang/ui": "workspace:*"
72
72
  }
73
73
  }
package/scripts/serve.js CHANGED
@@ -23,7 +23,8 @@ const MIME_TYPES = {
23
23
  '.png': 'image/png',
24
24
  '.jpg': 'image/jpeg',
25
25
  '.svg': 'image/svg+xml',
26
- '.ico': 'image/x-icon'
26
+ '.ico': 'image/x-icon',
27
+ '.rip': 'text/plain; charset=utf-8'
27
28
  };
28
29
 
29
30
  // Request handler for serving files
@@ -53,7 +54,7 @@ function handleRequest(req) {
53
54
  headers: {
54
55
  'Content-Type': MIME_TYPES[ext] || 'application/octet-stream',
55
56
  'Content-Encoding': 'br',
56
- 'Cache-Control': 'public, max-age=31536000'
57
+ 'Cache-Control': 'no-cache'
57
58
  }
58
59
  });
59
60
  } catch (e) {
package/src/browser.js CHANGED
@@ -9,8 +9,8 @@ export { CodeGenerator, Compiler, compile, compileToJS, formatSExpr, getReactive
9
9
  export const VERSION = "0.0.0";
10
10
  export const BUILD_DATE = "0000-00-00@00:00:00GMT";
11
11
 
12
- // Import compileToJS for use in rip() function
13
- import { compileToJS, getReactiveRuntime } from './compiler.js';
12
+ // Import compiler functions for use in rip() function and globalThis registration
13
+ import { compile, compileToJS, formatSExpr, getReactiveRuntime, getComponentRuntime } from './compiler.js';
14
14
 
15
15
  // Eagerly register Rip's reactive primitives on globalThis so that
16
16
  // framework code (ui.rip) can use them directly without the compiler
@@ -26,6 +26,7 @@ const dedent = s => {
26
26
  }
27
27
 
28
28
  // Browser runtime for executing <script type="text/rip"> tags
29
+ // Supports both inline scripts and external files via src attribute
29
30
  async function processRipScripts() {
30
31
  const scripts = document.querySelectorAll('script[type="text/rip"]');
31
32
 
@@ -33,7 +34,18 @@ async function processRipScripts() {
33
34
  if (script.hasAttribute('data-rip-processed')) continue;
34
35
 
35
36
  try {
36
- const ripCode = dedent(script.textContent);
37
+ let ripCode;
38
+ if (script.src) {
39
+ const response = await fetch(script.src);
40
+ if (!response.ok) {
41
+ console.error(`Rip: failed to fetch ${script.src} (${response.status})`);
42
+ continue;
43
+ }
44
+ ripCode = await response.text();
45
+ } else {
46
+ ripCode = dedent(script.textContent);
47
+ }
48
+
37
49
  let jsCode;
38
50
  try {
39
51
  jsCode = compileToJS(ripCode);
@@ -113,13 +125,17 @@ if (typeof globalThis !== 'undefined') {
113
125
  globalThis.rip = rip;
114
126
  globalThis.importRip = importRip;
115
127
  globalThis.compileToJS = compileToJS;
128
+ globalThis.__ripExports = { compile, compileToJS, formatSExpr, VERSION, BUILD_DATE, getReactiveRuntime, getComponentRuntime };
116
129
  }
117
130
 
118
131
  // Auto-process scripts when this module loads
132
+ // Expose __ripScriptsReady promise so other modules can await completion
119
133
  if (typeof document !== 'undefined') {
120
134
  if (document.readyState === 'loading') {
121
- document.addEventListener('DOMContentLoaded', processRipScripts);
135
+ globalThis.__ripScriptsReady = new Promise(resolve => {
136
+ document.addEventListener('DOMContentLoaded', () => processRipScripts().then(resolve));
137
+ });
122
138
  } else {
123
- processRipScripts();
139
+ globalThis.__ripScriptsReady = processRipScripts();
124
140
  }
125
141
  }
package/src/compiler.js CHANGED
@@ -201,9 +201,7 @@ export class CodeGenerator {
201
201
 
202
202
  // Control flow — simple
203
203
  'break': 'generateBreak',
204
- 'break-if': 'generateBreakIf',
205
204
  'continue': 'generateContinue',
206
- 'continue-if': 'generateContinueIf',
207
205
  '?': 'generateExistential',
208
206
  '?:': 'generateTernary',
209
207
  '|>': 'generatePipe',
@@ -311,14 +309,13 @@ export class CodeGenerator {
311
309
  collect(sexpr);
312
310
 
313
311
  // Match output lines to collected statement locations in parallel.
314
- // Skip generated lines that have no source correspondence.
312
+ // Skip lines with no source correspondence (preamble, braces, etc.).
315
313
  let lines = code.split('\n');
316
314
  let locIdx = 0;
317
315
  for (let outLine = 0; outLine < lines.length; outLine++) {
318
316
  let line = lines[outLine];
319
317
  let trimmed = line.trim();
320
318
 
321
- // Skip lines with no source correspondence
322
319
  if (!trimmed || trimmed === '}' || trimmed === '});') continue;
323
320
  if (trimmed.startsWith('let ') || trimmed.startsWith('var ')) continue;
324
321
  if (trimmed.startsWith('const slice') || trimmed.startsWith('const modulo') || trimmed.startsWith('const toSearchable')) continue;
@@ -359,6 +356,7 @@ export class CodeGenerator {
359
356
  }
360
357
 
361
358
  if (head === 'readonly') return;
359
+ if (head === 'component') return; // Component body has its own scope
362
360
 
363
361
  if (CodeGenerator.ASSIGNMENT_OPS.has(head)) {
364
362
  let [target, value] = rest;
@@ -929,11 +927,17 @@ export class CodeGenerator {
929
927
 
930
928
  generatePropertyAccess(head, rest, context, sexpr) {
931
929
  let [obj, prop] = rest;
930
+ // In subclass constructors, rewrite @param refs (this.x) to _x for super() safety
931
+ if (this._atParamMap && obj === 'this') {
932
+ let mapped = this._atParamMap.get(str(prop));
933
+ if (mapped) return mapped;
934
+ }
932
935
  this.suppressReactiveUnwrap = true;
933
936
  let objCode = this.generate(obj, 'value');
934
937
  this.suppressReactiveUnwrap = false;
935
938
  let needsParens = CodeGenerator.NUMBER_LITERAL_RE.test(objCode) ||
936
- ((this.is(obj, 'object') || this.is(obj, 'await') || this.is(obj, 'yield')));
939
+ objCode.startsWith('await ') ||
940
+ ((this.is(obj, 'object') || this.is(obj, 'yield')));
937
941
  let base = needsParens ? `(${objCode})` : objCode;
938
942
  if (meta(prop, 'await') === true) return `await ${base}.${str(prop)}()`;
939
943
  if (meta(prop, 'predicate')) return `(${base}.${str(prop)} != null)`;
@@ -1134,9 +1138,7 @@ export class CodeGenerator {
1134
1138
  // ---------------------------------------------------------------------------
1135
1139
 
1136
1140
  generateBreak() { return 'break'; }
1137
- generateBreakIf(head, rest) { return `if (${this.generate(rest[0], 'value')}) break`; }
1138
1141
  generateContinue() { return 'continue'; }
1139
- generateContinueIf(head, rest) { return `if (${this.generate(rest[0], 'value')}) continue`; }
1140
1142
 
1141
1143
  generateExistential(head, rest) {
1142
1144
  return `(${this.generate(rest[0], 'value')} != null)`;
@@ -1574,7 +1576,11 @@ export class CodeGenerator {
1574
1576
  let keyCode;
1575
1577
  if (this.is(key, 'dynamicKey')) keyCode = `[${this.generate(key[1], 'value')}]`;
1576
1578
  else if (this.is(key, 'str')) keyCode = `[${this.generate(key, 'value')}]`;
1577
- else keyCode = this.generate(key, 'value');
1579
+ else {
1580
+ this.suppressReactiveUnwrap = true;
1581
+ keyCode = this.generate(key, 'value');
1582
+ this.suppressReactiveUnwrap = false;
1583
+ }
1578
1584
  let valCode = this.generate(value, 'value');
1579
1585
  if (operator === '=') return `${keyCode} = ${valCode}`;
1580
1586
  if (operator === ':') return `${keyCode}: ${valCode}`;
@@ -1729,6 +1735,65 @@ export class CodeGenerator {
1729
1735
  // Comprehensions
1730
1736
  // ---------------------------------------------------------------------------
1731
1737
 
1738
+ // Shared: parse a for-in iterator and return { header, setup }.
1739
+ // header: the for(...) clause (no trailing brace)
1740
+ // setup: any `const x = arr[i]` preamble line, or null
1741
+ _forInHeader(vars, iterable, step) {
1742
+ let va = Array.isArray(vars) ? vars : [vars];
1743
+ let noVar = va.length === 0;
1744
+ let [itemVar, indexVar] = noVar ? ['_i', null] : va;
1745
+ let ivp = (this.is(itemVar, 'array') || this.is(itemVar, 'object'))
1746
+ ? this.generateDestructuringPattern(itemVar) : itemVar;
1747
+
1748
+ if (step && step !== null) {
1749
+ let ih = Array.isArray(iterable) && iterable[0];
1750
+ if (ih instanceof String) ih = str(ih);
1751
+ let isRange = ih === '..' || ih === '...';
1752
+ if (isRange) {
1753
+ let isExcl = ih === '...';
1754
+ let [s, e] = iterable.slice(1);
1755
+ let sc = this.generate(s, 'value'), ec = this.generate(e, 'value'), stc = this.generate(step, 'value');
1756
+ return { header: `for (let ${ivp} = ${sc}; ${ivp} ${isExcl ? '<' : '<='} ${ec}; ${ivp} += ${stc})`, setup: null };
1757
+ }
1758
+ let ic = this.generate(iterable, 'value'), idxN = indexVar || '_i', stc = this.generate(step, 'value');
1759
+ let isNeg = this.is(step, '-', 1);
1760
+ let isMinus1 = isNeg && (step[1] === '1' || step[1] === 1 || str(step[1]) === '1');
1761
+ let isPlus1 = !isNeg && (step === '1' || step === 1 || str(step) === '1');
1762
+ let update = isMinus1 ? `${idxN}--` : isPlus1 ? `${idxN}++` : `${idxN} += ${stc}`;
1763
+ let header = isNeg
1764
+ ? `for (let ${idxN} = ${ic}.length - 1; ${idxN} >= 0; ${update})`
1765
+ : `for (let ${idxN} = 0; ${idxN} < ${ic}.length; ${update})`;
1766
+ return { header, setup: noVar ? null : `const ${ivp} = ${ic}[${idxN}];` };
1767
+ }
1768
+ if (indexVar) {
1769
+ let ic = this.generate(iterable, 'value');
1770
+ return {
1771
+ header: `for (let ${indexVar} = 0; ${indexVar} < ${ic}.length; ${indexVar}++)`,
1772
+ setup: `const ${ivp} = ${ic}[${indexVar}];`,
1773
+ };
1774
+ }
1775
+ return { header: `for (const ${ivp} of ${this.generate(iterable, 'value')})`, setup: null };
1776
+ }
1777
+
1778
+ // Shared: parse a for-of (object) iterator and return { header, own, vv, oc, kvp }.
1779
+ _forOfHeader(vars, iterable, own) {
1780
+ let va = Array.isArray(vars) ? vars : [vars];
1781
+ let [kv, vv] = va;
1782
+ let kvp = (this.is(kv, 'array') || this.is(kv, 'object'))
1783
+ ? this.generateDestructuringPattern(kv) : kv;
1784
+ let oc = this.generate(iterable, 'value');
1785
+ return { header: `for (const ${kvp} in ${oc})`, own, vv, oc, kvp };
1786
+ }
1787
+
1788
+ // Shared: parse a for-as (iterator) spec and return { header }.
1789
+ _forAsHeader(vars, iterable, isAwait) {
1790
+ let va = Array.isArray(vars) ? vars : [vars];
1791
+ let [fv] = va;
1792
+ let ivp = (this.is(fv, 'array') || this.is(fv, 'object'))
1793
+ ? this.generateDestructuringPattern(fv) : fv;
1794
+ return { header: `for ${isAwait ? 'await ' : ''}(const ${ivp} of ${this.generate(iterable, 'value')})` };
1795
+ }
1796
+
1732
1797
  generateComprehension(head, rest, context) {
1733
1798
  let [expr, iterators, guards] = rest;
1734
1799
  if (context === 'statement') return this.generateComprehensionAsLoop(expr, iterators, guards);
@@ -1743,59 +1808,19 @@ export class CodeGenerator {
1743
1808
  for (let iter of iterators) {
1744
1809
  let [iterType, vars, iterable, stepOrOwn] = iter;
1745
1810
  if (iterType === 'for-in') {
1746
- let step = stepOrOwn;
1747
- let va = Array.isArray(vars) ? vars : [vars];
1748
- let noVar = va.length === 0;
1749
- let [itemVar, indexVar] = noVar ? ['_i', null] : va;
1750
- let ivp = ((this.is(itemVar, 'array') || this.is(itemVar, 'object')))
1751
- ? this.generateDestructuringPattern(itemVar) : itemVar;
1752
-
1753
- if (step && step !== null) {
1754
- let ih = Array.isArray(iterable) && iterable[0];
1755
- if (ih instanceof String) ih = str(ih);
1756
- let isRange = ih === '..' || ih === '...';
1757
- if (isRange) {
1758
- let isExcl = ih === '...';
1759
- let [s, e] = iterable.slice(1);
1760
- let sc = this.generate(s, 'value'), ec = this.generate(e, 'value'), stc = this.generate(step, 'value');
1761
- code += this.indent() + `for (let ${ivp} = ${sc}; ${ivp} ${isExcl ? '<' : '<='} ${ec}; ${ivp} += ${stc}) {\n`;
1762
- this.indentLevel++;
1763
- } else {
1764
- let ic = this.generate(iterable, 'value'), idxN = indexVar || '_i', stc = this.generate(step, 'value');
1765
- let isNeg = this.is(step, '-', 1);
1766
- code += isNeg
1767
- ? this.indent() + `for (let ${idxN} = ${ic}.length - 1; ${idxN} >= 0; ${idxN} += ${stc}) {\n`
1768
- : this.indent() + `for (let ${idxN} = 0; ${idxN} < ${ic}.length; ${idxN} += ${stc}) {\n`;
1769
- this.indentLevel++;
1770
- if (!noVar) code += this.indent() + `const ${ivp} = ${ic}[${idxN}];\n`;
1771
- }
1772
- } else if (indexVar) {
1773
- let ic = this.generate(iterable, 'value');
1774
- code += this.indent() + `for (let ${indexVar} = 0; ${indexVar} < ${ic}.length; ${indexVar}++) {\n`;
1775
- this.indentLevel++;
1776
- code += this.indent() + `const ${ivp} = ${ic}[${indexVar}];\n`;
1777
- } else {
1778
- code += this.indent() + `for (const ${ivp} of ${this.generate(iterable, 'value')}) {\n`;
1779
- this.indentLevel++;
1780
- }
1811
+ let { header, setup } = this._forInHeader(vars, iterable, stepOrOwn);
1812
+ code += this.indent() + header + ' {\n';
1813
+ this.indentLevel++;
1814
+ if (setup) code += this.indent() + setup + '\n';
1781
1815
  } else if (iterType === 'for-of') {
1782
- let own = stepOrOwn;
1783
- let va = Array.isArray(vars) ? vars : [vars];
1784
- let [kv, vv] = va;
1785
- let kvp = ((this.is(kv, 'array') || this.is(kv, 'object')))
1786
- ? this.generateDestructuringPattern(kv) : kv;
1787
- let oc = this.generate(iterable, 'value');
1788
- code += this.indent() + `for (const ${kvp} in ${oc}) {\n`;
1816
+ let { header, own, vv, oc, kvp } = this._forOfHeader(vars, iterable, stepOrOwn);
1817
+ code += this.indent() + header + ' {\n';
1789
1818
  this.indentLevel++;
1790
1819
  if (own) code += this.indent() + `if (!Object.hasOwn(${oc}, ${kvp})) continue;\n`;
1791
1820
  if (vv) code += this.indent() + `const ${vv} = ${oc}[${kvp}];\n`;
1792
1821
  } else if (iterType === 'for-as') {
1793
- let isAwait = iter[3];
1794
- let va = Array.isArray(vars) ? vars : [vars];
1795
- let [fv] = va;
1796
- let ivp = ((this.is(fv, 'array') || this.is(fv, 'object')))
1797
- ? this.generateDestructuringPattern(fv) : fv;
1798
- code += this.indent() + `for ${isAwait ? 'await ' : ''}(const ${ivp} of ${this.generate(iterable, 'value')}) {\n`;
1822
+ let { header } = this._forAsHeader(vars, iterable, iter[3]);
1823
+ code += this.indent() + header + ' {\n';
1799
1824
  this.indentLevel++;
1800
1825
  }
1801
1826
  }
@@ -1808,7 +1833,7 @@ export class CodeGenerator {
1808
1833
  let hasCtrl = (node) => {
1809
1834
  if (typeof node === 'string' && (node === 'break' || node === 'continue')) return true;
1810
1835
  if (!Array.isArray(node)) return false;
1811
- if (['break', 'continue', 'break-if', 'continue-if', 'return', 'throw'].includes(node[0])) return true;
1836
+ if (['break', 'continue', 'return', 'throw'].includes(node[0])) return true;
1812
1837
  if (node[0] === 'if' || node[0] === 'unless') return node.slice(1).some(hasCtrl);
1813
1838
  return node.some(hasCtrl);
1814
1839
  };
@@ -1909,17 +1934,36 @@ export class CodeGenerator {
1909
1934
  let hasAwait = this.containsAwait(body), hasYield = this.containsYield(body);
1910
1935
  let cleanParams = params, autoAssign = [];
1911
1936
  if (mName === 'constructor') {
1937
+ let isSubclass = !!parentClass;
1938
+ let atParamMap = isSubclass ? new Map() : null;
1912
1939
  cleanParams = params.map(p => {
1913
- if (this.is(p, '.') && p[1] === 'this') { autoAssign.push(`this.${p[2]} = ${p[2]}`); return p[2]; }
1940
+ // Handle @param: ['.', 'this', 'name']
1941
+ if (this.is(p, '.') && p[1] === 'this') {
1942
+ let name = p[2];
1943
+ let param = isSubclass ? `_${name}` : name;
1944
+ autoAssign.push(`this.${name} = ${param}`);
1945
+ if (isSubclass) atParamMap.set(name, param);
1946
+ return param;
1947
+ }
1948
+ // Handle @param with default: ['default', ['.', 'this', 'name'], value]
1949
+ if (this.is(p, 'default') && this.is(p[1], '.') && p[1][1] === 'this') {
1950
+ let name = p[1][2];
1951
+ let param = isSubclass ? `_${name}` : name;
1952
+ autoAssign.push(`this.${name} = ${param}`);
1953
+ if (isSubclass) atParamMap.set(name, param);
1954
+ return ['default', param, p[2]];
1955
+ }
1914
1956
  return p;
1915
1957
  });
1916
1958
  for (let bm of boundMethods) autoAssign.unshift(`this.${bm} = this.${bm}.bind(this)`);
1959
+ if (atParamMap?.size > 0) this._atParamMap = atParamMap;
1917
1960
  }
1918
1961
  let pList = this.generateParamList(cleanParams);
1919
1962
  let prefix = (isStatic ? 'static ' : '') + (hasAwait ? 'async ' : '') + (hasYield ? '*' : '');
1920
1963
  code += this.indent() + `${prefix}${mName}(${pList}) `;
1921
1964
  if (!isComputed) this.currentMethodName = mName;
1922
1965
  code += this.generateMethodBody(body, autoAssign, mName === 'constructor', cleanParams);
1966
+ this._atParamMap = null;
1923
1967
  this.currentMethodName = null;
1924
1968
  code += '\n';
1925
1969
  } else if (isStatic) {
@@ -2299,7 +2343,13 @@ export class CodeGenerator {
2299
2343
  // Single expression
2300
2344
  this.sideEffectOnly = prevSEO;
2301
2345
  let result;
2302
- if (isConstructor || this.hasExplicitControlFlow(body)) result = `{ ${this.generate(body, 'statement')}; }`;
2346
+ if (isConstructor && autoAssignments.length > 0) {
2347
+ // Constructor with @params as a single expression — need to emit autoAssignments
2348
+ let isSuper = Array.isArray(body) && body[0] === 'super';
2349
+ let bodyCode = this.generate(body, 'statement');
2350
+ let assigns = autoAssignments.map(a => `${a};`).join(' ');
2351
+ result = isSuper ? `{ ${bodyCode}; ${assigns} }` : `{ ${assigns} ${bodyCode}; }`;
2352
+ } else if (isConstructor || this.hasExplicitControlFlow(body)) result = `{ ${this.generate(body, 'statement')}; }`;
2303
2353
  else if (Array.isArray(body) && (noRetStmts.includes(body[0]) || loopStmts.includes(body[0]))) result = `{ ${this.generate(body, 'statement')}; }`;
2304
2354
  else if (sideEffectOnly) result = `{ ${this.generate(body, 'statement')}; return; }`;
2305
2355
  else result = `{ return ${this.generate(body, 'value')}; }`;
@@ -2379,34 +2429,10 @@ export class CodeGenerator {
2379
2429
  if (iterators.length === 1) {
2380
2430
  let [iterType, vars, iterable, stepOrOwn] = iterators[0];
2381
2431
  if (iterType === 'for-in') {
2382
- let step = stepOrOwn;
2383
- let va = Array.isArray(vars) ? vars : [vars];
2384
- let noVar = va.length === 0;
2385
- let [itemVar, indexVar] = noVar ? ['_i', null] : va;
2386
- let ivp = ((this.is(itemVar, 'array') || this.is(itemVar, 'object')))
2387
- ? this.generateDestructuringPattern(itemVar) : itemVar;
2388
-
2389
- if (step && step !== null) {
2390
- let ih = Array.isArray(iterable) && iterable[0];
2391
- if (ih instanceof String) ih = str(ih);
2392
- let isRange = ih === '..' || ih === '...';
2393
- if (isRange) {
2394
- let isExcl = ih === '...';
2395
- let [s, e] = iterable.slice(1);
2396
- code += this.indent() + `for (let ${ivp} = ${this.generate(s, 'value')}; ${ivp} ${isExcl ? '<' : '<='} ${this.generate(e, 'value')}; ${ivp} += ${this.generate(step, 'value')}) {\n`;
2397
- } else {
2398
- let ic = this.generate(iterable, 'value'), idxN = indexVar || '_i', stc = this.generate(step, 'value');
2399
- let isNeg = this.is(step, '-', 1);
2400
- code += isNeg
2401
- ? this.indent() + `for (let ${idxN} = ${ic}.length - 1; ${idxN} >= 0; ${idxN} += ${stc}) {\n`
2402
- : this.indent() + `for (let ${idxN} = 0; ${idxN} < ${ic}.length; ${idxN} += ${stc}) {\n`;
2403
- this.indentLevel++;
2404
- if (!noVar) code += this.indent() + `const ${ivp} = ${ic}[${idxN}];\n`;
2405
- }
2406
- } else {
2407
- code += this.indent() + `for (const ${ivp} of ${this.generate(iterable, 'value')}) {\n`;
2408
- }
2432
+ let { header, setup } = this._forInHeader(vars, iterable, stepOrOwn);
2433
+ code += this.indent() + header + ' {\n';
2409
2434
  this.indentLevel++;
2435
+ if (setup) code += this.indent() + setup + '\n';
2410
2436
  if (guards && guards.length > 0) {
2411
2437
  code += this.indent() + `if (${guards.map(g => this.generate(g, 'value')).join(' && ')}) {\n`;
2412
2438
  this.indentLevel++;
@@ -2423,155 +2449,51 @@ export class CodeGenerator {
2423
2449
 
2424
2450
  generateComprehensionAsLoop(expr, iterators, guards) {
2425
2451
  let code = '';
2452
+ let guardCond = guards?.length ? guards.map(g => this.generate(g, 'value')).join(' && ') : null;
2453
+
2454
+ // Helper: emit the loop body with optional guard wrapping
2455
+ let emitBody = () => {
2456
+ if (guardCond) {
2457
+ code += this.indent() + `if (${guardCond}) {\n`;
2458
+ this.indentLevel++;
2459
+ code += this.indent() + this.generate(expr, 'statement') + ';\n';
2460
+ this.indentLevel--; code += this.indent() + '}\n';
2461
+ } else {
2462
+ code += this.indent() + this.generate(expr, 'statement') + ';\n';
2463
+ }
2464
+ };
2465
+
2426
2466
  if (iterators.length === 1) {
2427
2467
  let [iterType, vars, iterable, stepOrOwn] = iterators[0];
2428
2468
 
2429
2469
  if (iterType === 'for-in') {
2430
- let step = stepOrOwn;
2431
- let va = Array.isArray(vars) ? vars : [vars];
2432
- let noVar = va.length === 0;
2433
- let [itemVar, indexVar] = noVar ? ['_i', null] : va;
2434
- let ivp = ((this.is(itemVar, 'array') || this.is(itemVar, 'object')))
2435
- ? this.generateDestructuringPattern(itemVar) : itemVar;
2436
-
2437
- if (step && step !== null) {
2438
- let ih = Array.isArray(iterable) && iterable[0];
2439
- if (ih instanceof String) ih = str(ih);
2440
- let isRange = ih === '..' || ih === '...';
2441
- if (isRange) {
2442
- let isExcl = ih === '...';
2443
- let [s, e] = iterable.slice(1);
2444
- code += `for (let ${ivp} = ${this.generate(s, 'value')}; ${ivp} ${isExcl ? '<' : '<='} ${this.generate(e, 'value')}; ${ivp} += ${this.generate(step, 'value')}) `;
2445
- } else {
2446
- let ic = this.generate(iterable, 'value'), idxN = indexVar || '_i', stc = this.generate(step, 'value');
2447
- let isNeg = this.is(step, '-', 1);
2448
- let isMinus1 = isNeg && (step[1] === '1' || step[1] === 1 || str(step[1]) === '1');
2449
- let isPlus1 = !isNeg && (step === '1' || step === 1 || str(step) === '1');
2450
- if (isMinus1) code += `for (let ${idxN} = ${ic}.length - 1; ${idxN} >= 0; ${idxN}--) `;
2451
- else if (isPlus1) code += `for (let ${idxN} = 0; ${idxN} < ${ic}.length; ${idxN}++) `;
2452
- else if (isNeg) code += `for (let ${idxN} = ${ic}.length - 1; ${idxN} >= 0; ${idxN} += ${stc}) `;
2453
- else code += `for (let ${idxN} = 0; ${idxN} < ${ic}.length; ${idxN} += ${stc}) `;
2454
- code += '{\n';
2455
- this.indentLevel++;
2456
- if (!noVar) code += this.indent() + `const ${ivp} = ${ic}[${idxN}];\n`;
2457
- }
2458
- if (guards?.length) {
2459
- if (!isRange) code += this.indent();
2460
- code += '{\n'; this.indentLevel++;
2461
- code += this.indent() + `if (${guards.map(g => this.generate(g, 'value')).join(' && ')}) {\n`;
2462
- this.indentLevel++;
2463
- code += this.indent() + this.generate(expr, 'statement') + ';\n';
2464
- this.indentLevel--; code += this.indent() + '}\n';
2465
- this.indentLevel--; code += this.indent() + '}';
2466
- } else {
2467
- if (!isRange) code += this.indent();
2468
- code += '{\n'; this.indentLevel++;
2469
- code += this.indent() + this.generate(expr, 'statement') + ';\n';
2470
- this.indentLevel--; code += this.indent() + '}';
2471
- }
2472
- if (!isRange) { this.indentLevel--; code += '\n' + this.indent() + '}'; }
2473
- return code;
2474
- }
2475
-
2476
- if (indexVar) {
2477
- let ic = this.generate(iterable, 'value');
2478
- code += `for (let ${indexVar} = 0; ${indexVar} < ${ic}.length; ${indexVar}++) `;
2479
- code += '{\n'; this.indentLevel++;
2480
- code += this.indent() + `const ${ivp} = ${ic}[${indexVar}];\n`;
2481
- } else {
2482
- code += `for (const ${ivp} of ${this.generate(iterable, 'value')}) `;
2483
- if (guards?.length) {
2484
- code += '{\n'; this.indentLevel++;
2485
- code += this.indent() + `if (${guards.map(g => this.generate(g, 'value')).join(' && ')}) {\n`;
2486
- this.indentLevel++;
2487
- code += this.indent() + this.generate(expr, 'statement') + ';\n';
2488
- this.indentLevel--; code += this.indent() + '}\n';
2489
- this.indentLevel--; code += this.indent() + '}';
2490
- } else {
2491
- code += '{\n'; this.indentLevel++;
2492
- code += this.indent() + this.generate(expr, 'statement') + ';\n';
2493
- this.indentLevel--; code += this.indent() + '}';
2494
- }
2495
- return code;
2496
- }
2497
-
2498
- // Fall through for indexVar case
2499
- if (guards?.length) {
2500
- code += this.indent() + `if (${guards.map(g => this.generate(g, 'value')).join(' && ')}) {\n`;
2501
- this.indentLevel++;
2502
- code += this.indent() + this.generate(expr, 'statement') + ';\n';
2503
- this.indentLevel--; code += this.indent() + '}\n';
2504
- } else {
2505
- code += this.indent() + this.generate(expr, 'statement') + ';\n';
2506
- }
2470
+ let { header, setup } = this._forInHeader(vars, iterable, stepOrOwn);
2471
+ code += header + ' {\n';
2472
+ this.indentLevel++;
2473
+ if (setup) code += this.indent() + setup + '\n';
2474
+ emitBody();
2507
2475
  this.indentLevel--;
2508
2476
  code += this.indent() + '}';
2509
2477
  return code;
2510
2478
  }
2511
2479
 
2512
2480
  if (iterType === 'for-as') {
2513
- let va = Array.isArray(vars) ? vars : [vars];
2514
- let [fv] = va;
2515
- let ivp = ((this.is(fv, 'array') || this.is(fv, 'object')))
2516
- ? this.generateDestructuringPattern(fv) : fv;
2517
- code += `for (const ${ivp} of ${this.generate(iterable, 'value')}) `;
2518
- if (guards?.length) {
2519
- code += '{\n'; this.indentLevel++;
2520
- code += this.indent() + `if (${guards.map(g => this.generate(g, 'value')).join(' && ')}) {\n`;
2521
- this.indentLevel++;
2522
- code += this.indent() + this.generate(expr, 'statement') + ';\n';
2523
- this.indentLevel--; code += this.indent() + '}\n';
2524
- this.indentLevel--; code += this.indent() + '}';
2525
- } else {
2526
- code += '{\n'; this.indentLevel++;
2527
- code += this.indent() + this.generate(expr, 'statement') + ';\n';
2528
- this.indentLevel--; code += this.indent() + '}';
2529
- }
2481
+ let { header } = this._forAsHeader(vars, iterable, stepOrOwn);
2482
+ code += header + ' {\n';
2483
+ this.indentLevel++;
2484
+ emitBody();
2485
+ this.indentLevel--;
2486
+ code += this.indent() + '}';
2530
2487
  return code;
2531
2488
  }
2532
2489
 
2533
2490
  if (iterType === 'for-of') {
2534
- let va = Array.isArray(vars) ? vars : [vars];
2535
- let [kv, vv] = va;
2536
- let own = stepOrOwn;
2537
- let oc = this.generate(iterable, 'value');
2538
- code += `for (const ${kv} in ${oc}) {\n`;
2491
+ let { header, own, vv, oc, kvp } = this._forOfHeader(vars, iterable, stepOrOwn);
2492
+ code += header + ' {\n';
2539
2493
  this.indentLevel++;
2540
- if (own && !vv && !guards?.length) {
2541
- code += this.indent() + `if (!Object.hasOwn(${oc}, ${kv})) continue;\n`;
2542
- code += this.indent() + this.generate(expr, 'statement') + ';\n';
2543
- } else if (own && vv && guards?.length) {
2544
- code += this.indent() + `if (Object.hasOwn(${oc}, ${kv})) {\n`;
2545
- this.indentLevel++;
2546
- code += this.indent() + `const ${vv} = ${oc}[${kv}];\n`;
2547
- code += this.indent() + `if (${guards.map(g => this.generate(g, 'value')).join(' && ')}) {\n`;
2548
- this.indentLevel++;
2549
- code += this.indent() + this.generate(expr, 'statement') + ';\n';
2550
- this.indentLevel--; code += this.indent() + '}\n';
2551
- this.indentLevel--; code += this.indent() + '}\n';
2552
- } else if (own && vv) {
2553
- code += this.indent() + `if (Object.hasOwn(${oc}, ${kv})) {\n`;
2554
- this.indentLevel++;
2555
- code += this.indent() + `const ${vv} = ${oc}[${kv}];\n`;
2556
- code += this.indent() + this.generate(expr, 'statement') + ';\n';
2557
- this.indentLevel--; code += this.indent() + '}\n';
2558
- } else if (vv && guards?.length) {
2559
- code += this.indent() + `const ${vv} = ${oc}[${kv}];\n`;
2560
- code += this.indent() + `if (${guards.map(g => this.generate(g, 'value')).join(' && ')}) {\n`;
2561
- this.indentLevel++;
2562
- code += this.indent() + this.generate(expr, 'statement') + ';\n';
2563
- this.indentLevel--; code += this.indent() + '}\n';
2564
- } else if (vv) {
2565
- code += this.indent() + `const ${vv} = ${oc}[${kv}];\n`;
2566
- code += this.indent() + this.generate(expr, 'statement') + ';\n';
2567
- } else if (guards?.length) {
2568
- code += this.indent() + `if (${guards.map(g => this.generate(g, 'value')).join(' && ')}) {\n`;
2569
- this.indentLevel++;
2570
- code += this.indent() + this.generate(expr, 'statement') + ';\n';
2571
- this.indentLevel--; code += this.indent() + '}\n';
2572
- } else {
2573
- code += this.indent() + this.generate(expr, 'statement') + ';\n';
2574
- }
2494
+ if (own) code += this.indent() + `if (!Object.hasOwn(${oc}, ${kvp})) continue;\n`;
2495
+ if (vv) code += this.indent() + `const ${vv} = ${oc}[${kvp}];\n`;
2496
+ emitBody();
2575
2497
  this.indentLevel--;
2576
2498
  code += this.indent() + '}';
2577
2499
  return code;
@@ -3154,14 +3076,19 @@ function __effect(fn) {
3154
3076
  dependencies: new Set(),
3155
3077
 
3156
3078
  run() {
3079
+ if (effect._cleanup) { effect._cleanup(); effect._cleanup = null; }
3157
3080
  for (const dep of effect.dependencies) dep.delete(effect);
3158
3081
  effect.dependencies.clear();
3159
3082
  const prev = __currentEffect;
3160
3083
  __currentEffect = effect;
3161
- try { fn(); } finally { __currentEffect = prev; }
3084
+ try {
3085
+ const result = fn();
3086
+ if (typeof result === 'function') effect._cleanup = result;
3087
+ } finally { __currentEffect = prev; }
3162
3088
  },
3163
3089
 
3164
3090
  dispose() {
3091
+ if (effect._cleanup) { effect._cleanup(); effect._cleanup = null; }
3165
3092
  for (const dep of effect.dependencies) dep.delete(effect);
3166
3093
  effect.dependencies.clear();
3167
3094
  }