rip-lang 3.13.55 → 3.13.56

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.
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rip-lang",
3
- "version": "3.13.55",
3
+ "version": "3.13.56",
4
4
  "description": "A modern language that compiles to JavaScript",
5
5
  "type": "module",
6
6
  "main": "src/compiler.js",
package/src/app.rip CHANGED
@@ -98,7 +98,7 @@ makeProxy = (target) ->
98
98
  deleteProperty: (target, prop) ->
99
99
  delete target[prop]
100
100
  sig = target[SIGNALS]?.get(prop)
101
- sig.value = undefined if sig
101
+ sig?.value = undefined
102
102
  keysSignal(target).value = ++_keysVersion
103
103
  true
104
104
 
package/src/browser.js CHANGED
@@ -72,14 +72,15 @@ async function processRipScripts() {
72
72
  for (const s of sources) {
73
73
  if (!s.code) continue;
74
74
  try {
75
- compiled.push(compileToJS(s.code, opts));
75
+ const js = compileToJS(s.code, opts);
76
+ compiled.push({ js, url: s.url || 'inline' });
76
77
  } catch (e) {
77
- console.error('Rip compile error:', e.message);
78
+ console.error(`Rip compile error in ${s.url || 'inline'}:`, e.message);
78
79
  }
79
80
  }
80
81
 
81
82
  if (compiled.length > 0) {
82
- let js = compiled.join('\n');
83
+ let js = compiled.map(c => c.js).join('\n');
83
84
 
84
85
  // Step 4: Append data-mount call inside the shared IIFE
85
86
  const mount = runtimeTag?.getAttribute('data-mount');
@@ -91,7 +92,15 @@ async function processRipScripts() {
91
92
  try {
92
93
  await (0, eval)(`(async()=>{\n${js}\n})()`);
93
94
  } catch (e) {
94
- console.error('Rip runtime error:', e);
95
+ if (e instanceof SyntaxError) {
96
+ console.error(`Rip syntax error in combined output: ${e.message}`);
97
+ for (const c of compiled) {
98
+ try { new Function(`(async()=>{\n${c.js}\n})()`); }
99
+ catch (e2) { console.error(` → source: ${c.url}`, e2.message); }
100
+ }
101
+ } else {
102
+ console.error('Rip runtime error:', e);
103
+ }
95
104
  }
96
105
  }
97
106
  }
package/src/compiler.js CHANGED
@@ -588,26 +588,25 @@ export class CodeGenerator {
588
588
 
589
589
  generateProgram(head, statements, context, sexpr) {
590
590
  let code = '';
591
- let imports = [], exports = [], other = [];
591
+ let imports = [], body = [];
592
592
 
593
593
  for (let stmt of statements) {
594
- if (!Array.isArray(stmt)) { other.push(stmt); continue; }
594
+ if (!Array.isArray(stmt)) { body.push(stmt); continue; }
595
595
  let h = stmt[0];
596
596
  if (h === 'import') imports.push(stmt);
597
- else if (h === 'export' || h === 'export-default' || h === 'export-all' || h === 'export-from') exports.push(stmt);
598
- else other.push(stmt);
597
+ else body.push(stmt);
599
598
  }
600
599
 
601
600
  // Generate body first to detect needed helpers
602
601
  let blockStmts = ['def', 'class', 'if', 'for-in', 'for-of', 'for-as', 'while', 'loop', 'switch', 'try'];
603
- let stmtEntries = other.map((stmt, index) => {
604
- let isSingle = other.length === 1 && imports.length === 0 && exports.length === 0;
602
+ let stmtEntries = body.map((stmt, index) => {
603
+ let isSingle = body.length === 1 && imports.length === 0;
605
604
  let isObj = this.is(stmt, 'object');
606
605
  let isObjComp = isObj && stmt.length === 2 && Array.isArray(stmt[1]) && Array.isArray(stmt[1][1]) && stmt[1][1][0] === 'comprehension';
607
606
  let isAlreadyExpr = (this.is(stmt, 'comprehension') || this.is(stmt, 'object-comprehension') || this.is(stmt, 'do-iife'));
608
607
  let hasNoVars = this.programVars.size === 0;
609
608
  let needsParens = isSingle && isObj && hasNoVars && !isAlreadyExpr && !isObjComp;
610
- let isLast = index === other.length - 1;
609
+ let isLast = index === body.length - 1;
611
610
  let isLastComp = isLast && isAlreadyExpr;
612
611
 
613
612
  let generated;
@@ -672,12 +671,6 @@ export class CodeGenerator {
672
671
  }
673
672
  }
674
673
 
675
- // Generate exports code early so component/reactivity flags are set before runtime checks
676
- let exportsCode = '';
677
- if (exports.length > 0) {
678
- exportsCode = '\n' + exports.map(s => this.addSemicolon(s, this.generate(s, 'statement'))).join('\n');
679
- }
680
-
681
674
  if (this.usesReactivity && !skip) {
682
675
  if (skipRT) {
683
676
  code += 'var { __state, __computed, __effect, __batch, __readonly, __setErrorHandler, __handleError, __catchErrors } = globalThis.__rip;\n';
@@ -709,7 +702,6 @@ export class CodeGenerator {
709
702
  this._stmtEntries = stmtEntries;
710
703
  this._preambleLines = code.length === 0 ? 0 : code.split('\n').length - 1;
711
704
  code += statementsCode;
712
- code += exportsCode;
713
705
 
714
706
  if (this.dataSection !== null && this.dataSection !== undefined) {
715
707
  code += `\n\nfunction _setDataSection() {\n DATA = ${JSON.stringify(this.dataSection)};\n}`;
@@ -786,6 +778,18 @@ export class CodeGenerator {
786
778
  let [target, value] = rest;
787
779
  let op = head === '?=' ? '??=' : head;
788
780
 
781
+ // Optional chain assignment: x?.prop = val → if (x != null) x.prop = val
782
+ let optInfo = this._findOptionalInTarget(target);
783
+ if (optInfo) {
784
+ let guardCode = this.generate(optInfo.guard, 'value');
785
+ let targetCode = this.generate(optInfo.rewritten, 'value');
786
+ let valueCode = this.generate(value, 'value');
787
+ if (context === 'value') {
788
+ return `(${guardCode} != null ? (${targetCode} ${op} ${valueCode}) : undefined)`;
789
+ }
790
+ return `if (${guardCode} != null) ${targetCode} ${op} ${valueCode}`;
791
+ }
792
+
789
793
  // Validate: no sigils in assignment targets (except void function syntax)
790
794
  let isFnValue = (this.is(value, '->') || this.is(value, '=>') || this.is(value, 'def'));
791
795
  if (target instanceof String && meta(target, 'await') !== undefined && !isFnValue) {
@@ -2754,6 +2758,17 @@ export class CodeGenerator {
2754
2758
  return code;
2755
2759
  }
2756
2760
 
2761
+ _findOptionalInTarget(node) {
2762
+ if (!Array.isArray(node)) return null;
2763
+ if (node[0] === '?.') return { guard: node[1], rewritten: ['.', node[1], node[2]] };
2764
+ if (node[0] === 'optindex') return { guard: node[1], rewritten: ['[]', node[1], node[2]] };
2765
+ if (node[0] === '.' || node[0] === '[]') {
2766
+ let inner = this._findOptionalInTarget(node[1]);
2767
+ if (inner) return { guard: inner.guard, rewritten: [node[0], inner.rewritten, node[2]] };
2768
+ }
2769
+ return null;
2770
+ }
2771
+
2757
2772
  unwrapLogical(code) {
2758
2773
  if (typeof code !== 'string') return code;
2759
2774
  while (code.startsWith('(') && code.endsWith(')')) {
package/src/components.js CHANGED
@@ -141,6 +141,7 @@ export function installComponentSupport(CodeGenerator, Lexer) {
141
141
  // - Event modifiers: @click.prevent: → [@click.prevent]:
142
142
  // - Dynamic classes: div.('card', x && 'active') → div.__clsx(...)
143
143
  // - Implicit nesting: inject -> before INDENT for template elements
144
+ // - Data attribute sigil: $open: true → "data-open": true
144
145
  // - Hyphenated attributes: data-foo: "x" → "data-foo": "x"
145
146
  // ==========================================================================
146
147
 
@@ -272,9 +273,20 @@ export function installComponentSupport(CodeGenerator, Lexer) {
272
273
  return 1;
273
274
  }
274
275
 
276
+ // ─────────────────────────────────────────────────────────────────────
277
+ // Data attribute sigil
278
+ // $open: true → "data-open": true
279
+ // ─────────────────────────────────────────────────────────────────────
280
+ if (tag === 'PROPERTY' && token[1][0] === '$' && token[1].length > 1) {
281
+ token[0] = 'STRING';
282
+ token[1] = `"data-${token[1].slice(1)}"`;
283
+ return 1;
284
+ }
285
+
275
286
  // ─────────────────────────────────────────────────────────────────────
276
287
  // Hyphenated attributes
277
288
  // data-lucide: "search" → "data-lucide": "search"
289
+ // $my-thing: "x" → "data-my-thing": "x"
278
290
  // ─────────────────────────────────────────────────────────────────────
279
291
  if (tag === 'IDENTIFIER' && !token.spaced) {
280
292
  let parts = [token[1]];
@@ -292,8 +304,10 @@ export function installComponentSupport(CodeGenerator, Lexer) {
292
304
  }
293
305
  }
294
306
  if (parts.length > 1 && j > i + 1 && tokens[j - 1][0] === 'PROPERTY') {
307
+ let joined = parts.join('-');
308
+ if (joined[0] === '$') joined = 'data-' + joined.slice(1);
295
309
  token[0] = 'STRING';
296
- token[1] = `"${parts.join('-')}"`;
310
+ token[1] = `"${joined}"`;
297
311
  tokens.splice(i + 1, j - i - 1);
298
312
  return 1;
299
313
  }
@@ -1385,21 +1399,9 @@ export function installComponentSupport(CodeGenerator, Lexer) {
1385
1399
 
1386
1400
  const valueCode = this.generateInComponent(value, 'value');
1387
1401
 
1388
- // Smart two-way binding for value/checked when bound to reactive state
1402
+ // value/checked with reactive deps: one-way push (use <=> for two-way)
1389
1403
  if ((key === 'value' || key === 'checked') && this.hasReactiveDeps(value)) {
1390
1404
  this._pushEffect(`${elVar}.${key} = ${valueCode};`);
1391
- const rootMemberImplicit = !this.isSimpleAssignable(value) && this.findRootReactiveMember(value);
1392
- if (this.isSimpleAssignable(value) || rootMemberImplicit) {
1393
- const event = key === 'checked' ? 'change' : 'input';
1394
- const accessor = key === 'checked' ? 'e.target.checked'
1395
- : (inputType === 'number' || inputType === 'range') ? 'e.target.valueAsNumber'
1396
- : 'e.target.value';
1397
- let assignCode = `${valueCode} = ${accessor}`;
1398
- if (rootMemberImplicit) {
1399
- assignCode += `; ${this._self}.${rootMemberImplicit}.touch?.()`;
1400
- }
1401
- this._createLines.push(`${elVar}.addEventListener('${event}', (e) => { ${assignCode}; });`);
1402
- }
1403
1405
  continue;
1404
1406
  }
1405
1407
 
@@ -1641,7 +1643,15 @@ export function installComponentSupport(CodeGenerator, Lexer) {
1641
1643
 
1642
1644
  const varNames = Array.isArray(vars) ? vars : [vars];
1643
1645
  const itemVar = varNames[0];
1644
- const indexVar = varNames[1] || 'i';
1646
+ let indexVar = varNames[1] || null;
1647
+ if (!indexVar) {
1648
+ const usedNames = new Set(this._loopVarStack.flatMap(v => [v.itemVar, v.indexVar]));
1649
+ usedNames.add(itemVar);
1650
+ for (const candidate of ['i', 'j', 'k', 'l', 'm', 'n']) {
1651
+ if (!usedNames.has(candidate)) { indexVar = candidate; break; }
1652
+ }
1653
+ indexVar = indexVar || `_i${this._loopVarStack.length}`;
1654
+ }
1645
1655
 
1646
1656
  const collectionCode = this.generateInComponent(collection, 'value');
1647
1657