rip-lang 3.10.6 → 3.10.7

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rip-lang",
3
- "version": "3.10.6",
3
+ "version": "3.10.7",
4
4
  "description": "A modern language that compiles to JavaScript",
5
5
  "type": "module",
6
6
  "main": "src/compiler.js",
package/src/compiler.js CHANGED
@@ -2980,6 +2980,16 @@ function __state(initialValue) {
2980
2980
  },
2981
2981
 
2982
2982
  read() { return value; },
2983
+ touch() {
2984
+ if (dead || notifying) return;
2985
+ notifying = true;
2986
+ for (const sub of subscribers) {
2987
+ if (sub.markDirty) sub.markDirty();
2988
+ else __pendingEffects.add(sub);
2989
+ }
2990
+ if (!__batching) __flushEffects();
2991
+ notifying = false;
2992
+ },
2983
2993
  lock() { locked = true; return state; },
2984
2994
  free() { subscribers.clear(); return state; },
2985
2995
  kill() { dead = true; subscribers.clear(); return value; },
package/src/components.js CHANGED
@@ -7,7 +7,7 @@
7
7
  //
8
8
  // Naming: All render-tree generators use generate* (consistent with compiler).
9
9
 
10
- import { TEMPLATE_TAGS } from './tags.js';
10
+ import { TEMPLATE_TAGS, SVG_TAGS } from './tags.js';
11
11
 
12
12
  // ============================================================================
13
13
  // Constants
@@ -24,6 +24,8 @@ const BOOLEAN_ATTRS = new Set([
24
24
  'allowfullscreen', 'inert',
25
25
  ]);
26
26
 
27
+ const SVG_NS = 'http://www.w3.org/2000/svg';
28
+
27
29
  // ============================================================================
28
30
  // Standalone Utilities
29
31
  // ============================================================================
@@ -792,6 +794,7 @@ export function installComponentSupport(CodeGenerator, Lexer) {
792
794
  this._createLines = [];
793
795
  this._setupLines = [];
794
796
  this._blockFactories = [];
797
+ this._loopVarStack = [];
795
798
 
796
799
  const statements = this.is(body, 'block') ? body.slice(1) : [body];
797
800
 
@@ -855,7 +858,12 @@ export function installComponentSupport(CodeGenerator, Lexer) {
855
858
  // Static tag without content (possibly with #id)
856
859
  const [tagStr, idStr] = str.split('#');
857
860
  const elVar = this.newElementVar();
858
- this._createLines.push(`${elVar} = document.createElement('${tagStr || 'div'}');`);
861
+ const actualTag = tagStr || 'div';
862
+ if (SVG_TAGS.has(actualTag) || this._svgDepth > 0) {
863
+ this._createLines.push(`${elVar} = document.createElementNS('${SVG_NS}', '${actualTag}');`);
864
+ } else {
865
+ this._createLines.push(`${elVar} = document.createElement('${actualTag}');`);
866
+ }
859
867
  if (idStr) this._createLines.push(`${elVar}.id = '${idStr}';`);
860
868
  return elVar;
861
869
  }
@@ -1011,16 +1019,27 @@ export function installComponentSupport(CodeGenerator, Lexer) {
1011
1019
 
1012
1020
  proto.generateTag = function(tag, classes, args, id) {
1013
1021
  const elVar = this.newElementVar();
1014
- this._createLines.push(`${elVar} = document.createElement('${tag}');`);
1022
+ const isSvg = SVG_TAGS.has(tag) || this._svgDepth > 0;
1023
+ if (isSvg) {
1024
+ this._createLines.push(`${elVar} = document.createElementNS('${SVG_NS}', '${tag}');`);
1025
+ } else {
1026
+ this._createLines.push(`${elVar} = document.createElement('${tag}');`);
1027
+ }
1015
1028
 
1016
1029
  if (id) {
1017
1030
  this._createLines.push(`${elVar}.id = '${id}';`);
1018
1031
  }
1019
1032
  if (classes.length > 0) {
1020
- this._createLines.push(`${elVar}.className = '${classes.join(' ')}';`);
1033
+ if (isSvg) {
1034
+ this._createLines.push(`${elVar}.setAttribute('class', '${classes.join(' ')}');`);
1035
+ } else {
1036
+ this._createLines.push(`${elVar}.className = '${classes.join(' ')}';`);
1037
+ }
1021
1038
  }
1022
1039
 
1040
+ if (tag === 'svg') this._svgDepth = (this._svgDepth || 0) + 1;
1023
1041
  this.appendChildren(elVar, args);
1042
+ if (tag === 'svg') this._svgDepth--;
1024
1043
  return elVar;
1025
1044
  };
1026
1045
 
@@ -1030,7 +1049,11 @@ export function installComponentSupport(CodeGenerator, Lexer) {
1030
1049
 
1031
1050
  proto.generateDynamicTag = function(tag, classExprs, children) {
1032
1051
  const elVar = this.newElementVar();
1033
- this._createLines.push(`${elVar} = document.createElement('${tag}');`);
1052
+ if (SVG_TAGS.has(tag) || this._svgDepth > 0) {
1053
+ this._createLines.push(`${elVar} = document.createElementNS('${SVG_NS}', '${tag}');`);
1054
+ } else {
1055
+ this._createLines.push(`${elVar} = document.createElement('${tag}');`);
1056
+ }
1034
1057
 
1035
1058
  // Defer className emission so class: attributes can merge with .() classes
1036
1059
  const classArgs = classExprs.map(e => this.generateInComponent(e, 'value'));
@@ -1039,11 +1062,18 @@ export function installComponentSupport(CodeGenerator, Lexer) {
1039
1062
  this._pendingClassArgs = classArgs;
1040
1063
  this._pendingClassEl = elVar;
1041
1064
 
1065
+ if (tag === 'svg') this._svgDepth = (this._svgDepth || 0) + 1;
1042
1066
  this.appendChildren(elVar, children);
1067
+ if (tag === 'svg') this._svgDepth--;
1043
1068
 
1044
1069
  if (this._pendingClassArgs.length > 0) {
1045
1070
  const combined = this._pendingClassArgs.join(', ');
1046
- this._setupLines.push(`__effect(() => { ${elVar}.className = __clsx(${combined}); });`);
1071
+ const isSvg = SVG_TAGS.has(tag) || this._svgDepth > 0;
1072
+ if (isSvg) {
1073
+ this._setupLines.push(`__effect(() => { ${elVar}.setAttribute('class', __clsx(${combined})); });`);
1074
+ } else {
1075
+ this._setupLines.push(`__effect(() => { ${elVar}.className = __clsx(${combined}); });`);
1076
+ }
1047
1077
  }
1048
1078
  this._pendingClassArgs = prevClassArgs;
1049
1079
  this._pendingClassEl = prevClassEl;
@@ -1087,9 +1117,17 @@ export function installComponentSupport(CodeGenerator, Lexer) {
1087
1117
  if (this._pendingClassArgs && this._pendingClassEl === elVar) {
1088
1118
  this._pendingClassArgs.push(valueCode);
1089
1119
  } else if (this.hasReactiveDeps(value)) {
1090
- this._setupLines.push(`__effect(() => { ${elVar}.className = __clsx(${valueCode}); });`);
1120
+ if (this._svgDepth > 0) {
1121
+ this._setupLines.push(`__effect(() => { ${elVar}.setAttribute('class', __clsx(${valueCode})); });`);
1122
+ } else {
1123
+ this._setupLines.push(`__effect(() => { ${elVar}.className = __clsx(${valueCode}); });`);
1124
+ }
1091
1125
  } else {
1092
- this._createLines.push(`${elVar}.className = ${valueCode};`);
1126
+ if (this._svgDepth > 0) {
1127
+ this._createLines.push(`${elVar}.setAttribute('class', ${valueCode});`);
1128
+ } else {
1129
+ this._createLines.push(`${elVar}.className = ${valueCode};`);
1130
+ }
1093
1131
  }
1094
1132
  continue;
1095
1133
  }
@@ -1117,7 +1155,12 @@ export function installComponentSupport(CodeGenerator, Lexer) {
1117
1155
  }
1118
1156
 
1119
1157
  this._setupLines.push(`__effect(() => { ${elVar}.${prop} = ${valueCode}; });`);
1120
- this._createLines.push(`${elVar}.addEventListener('${event}', (e) => ${valueCode} = ${valueAccessor});`);
1158
+ let assignCode = `${valueCode} = ${valueAccessor}`;
1159
+ const rootMember = !this.isSimpleAssignable(value) && this.findRootReactiveMember(value);
1160
+ if (rootMember) {
1161
+ assignCode += `; this.${rootMember}.touch?.()`;
1162
+ }
1163
+ this._createLines.push(`${elVar}.addEventListener('${event}', (e) => { ${assignCode}; });`);
1121
1164
  continue;
1122
1165
  }
1123
1166
 
@@ -1126,15 +1169,19 @@ export function installComponentSupport(CodeGenerator, Lexer) {
1126
1169
  // Smart two-way binding for value/checked when bound to reactive state
1127
1170
  if ((key === 'value' || key === 'checked') && this.hasReactiveDeps(value)) {
1128
1171
  this._setupLines.push(`__effect(() => { ${elVar}.${key} = ${valueCode}; });`);
1129
- // Only generate reverse binding when the value is a simple assignable
1130
- // target (plain reactive member or @prop), not a complex expression
1131
- // like selected.includes(opt) which can't be assigned to.
1132
- if (this.isSimpleAssignable(value)) {
1172
+ // Generate reverse binding for simple assignable targets or nested
1173
+ // reactive paths (with touch() for Svelte-style invalidation)
1174
+ const rootMemberImplicit = !this.isSimpleAssignable(value) && this.findRootReactiveMember(value);
1175
+ if (this.isSimpleAssignable(value) || rootMemberImplicit) {
1133
1176
  const event = key === 'checked' ? 'change' : 'input';
1134
1177
  const accessor = key === 'checked' ? 'e.target.checked'
1135
1178
  : (inputType === 'number' || inputType === 'range') ? 'e.target.valueAsNumber'
1136
1179
  : 'e.target.value';
1137
- this._createLines.push(`${elVar}.addEventListener('${event}', (e) => { ${valueCode} = ${accessor}; });`);
1180
+ let assignCode = `${valueCode} = ${accessor}`;
1181
+ if (rootMemberImplicit) {
1182
+ assignCode += `; this.${rootMemberImplicit}.touch?.()`;
1183
+ }
1184
+ this._createLines.push(`${elVar}.addEventListener('${event}', (e) => { ${assignCode}; });`);
1138
1185
  }
1139
1186
  continue;
1140
1187
  }
@@ -1194,6 +1241,10 @@ export function installComponentSupport(CodeGenerator, Lexer) {
1194
1241
 
1195
1242
  const condCode = this.generateInComponent(condition, 'value');
1196
1243
 
1244
+ // Collect loop variables from enclosing for-loops
1245
+ const loopParams = this._loopVarStack.map(v => `${v.itemVar}, ${v.indexVar}`).join(', ');
1246
+ const extraArgs = loopParams ? `, ${loopParams}` : '';
1247
+
1197
1248
  const thenBlockName = this.newBlockVar();
1198
1249
  this.generateConditionBranch(thenBlockName, thenBlock);
1199
1250
 
@@ -1221,17 +1272,17 @@ export function installComponentSupport(CodeGenerator, Lexer) {
1221
1272
  setupLines.push(` showing = want;`);
1222
1273
  setupLines.push(``);
1223
1274
  setupLines.push(` if (want === 'then') {`);
1224
- setupLines.push(` currentBlock = ${thenBlockName}(this);`);
1275
+ setupLines.push(` currentBlock = ${thenBlockName}(this${extraArgs});`);
1225
1276
  setupLines.push(` currentBlock.c();`);
1226
1277
  setupLines.push(` currentBlock.m(anchor.parentNode, anchor.nextSibling);`);
1227
- setupLines.push(` currentBlock.p(this);`);
1278
+ setupLines.push(` currentBlock.p(this${extraArgs});`);
1228
1279
  setupLines.push(` }`);
1229
1280
  if (elseBlock) {
1230
1281
  setupLines.push(` if (want === 'else') {`);
1231
- setupLines.push(` currentBlock = ${elseBlockName}(this);`);
1282
+ setupLines.push(` currentBlock = ${elseBlockName}(this${extraArgs});`);
1232
1283
  setupLines.push(` currentBlock.c();`);
1233
1284
  setupLines.push(` currentBlock.m(anchor.parentNode, anchor.nextSibling);`);
1234
- setupLines.push(` currentBlock.p(this);`);
1285
+ setupLines.push(` currentBlock.p(this${extraArgs});`);
1235
1286
  setupLines.push(` }`);
1236
1287
  }
1237
1288
  setupLines.push(` });`);
@@ -1262,8 +1313,12 @@ export function installComponentSupport(CodeGenerator, Lexer) {
1262
1313
 
1263
1314
  const localizeVar = (line) => this.localizeVar(line);
1264
1315
 
1316
+ // Include enclosing loop variables in the factory signature
1317
+ const loopParams = this._loopVarStack.map(v => `${v.itemVar}, ${v.indexVar}`).join(', ');
1318
+ const extraParams = loopParams ? `, ${loopParams}` : '';
1319
+
1265
1320
  const factoryLines = [];
1266
- factoryLines.push(`function ${blockName}(ctx) {`);
1321
+ factoryLines.push(`function ${blockName}(ctx${extraParams}) {`);
1267
1322
 
1268
1323
  // Declare local variables
1269
1324
  const localVars = new Set();
@@ -1295,7 +1350,7 @@ export function installComponentSupport(CodeGenerator, Lexer) {
1295
1350
  factoryLines.push(` },`);
1296
1351
 
1297
1352
  // p() - update/patch
1298
- factoryLines.push(` p(ctx) {`);
1353
+ factoryLines.push(` p(ctx${extraParams}) {`);
1299
1354
  if (hasEffects) {
1300
1355
  factoryLines.push(` disposers.forEach(d => d());`);
1301
1356
  factoryLines.push(` disposers = [];`);
@@ -1379,7 +1434,9 @@ export function installComponentSupport(CodeGenerator, Lexer) {
1379
1434
  this._createLines = [];
1380
1435
  this._setupLines = [];
1381
1436
 
1437
+ this._loopVarStack.push({ itemVar, indexVar });
1382
1438
  const itemNode = this.generateTemplateBlock(body);
1439
+ this._loopVarStack.pop();
1383
1440
  const itemCreateLines = this._createLines;
1384
1441
  const itemSetupLines = this._setupLines;
1385
1442
 
@@ -1469,32 +1526,32 @@ export function installComponentSupport(CodeGenerator, Lexer) {
1469
1526
  const setupLines = [];
1470
1527
  setupLines.push(`// Loop: ${blockName}`);
1471
1528
  setupLines.push(`{`);
1472
- setupLines.push(` const anchor = ${anchorVar};`);
1473
- setupLines.push(` const map = new Map();`);
1529
+ setupLines.push(` const __anchor = ${anchorVar};`);
1530
+ setupLines.push(` const __map = new Map();`);
1474
1531
  setupLines.push(` __effect(() => {`);
1475
- setupLines.push(` const items = ${collectionCode};`);
1476
- setupLines.push(` const parent = anchor.parentNode;`);
1477
- setupLines.push(` const newMap = new Map();`);
1532
+ setupLines.push(` const __items = ${collectionCode};`);
1533
+ setupLines.push(` const __parent = __anchor.parentNode;`);
1534
+ setupLines.push(` const __newMap = new Map();`);
1478
1535
  setupLines.push(``);
1479
- setupLines.push(` for (let ${indexVar} = 0; ${indexVar} < items.length; ${indexVar}++) {`);
1480
- setupLines.push(` const ${itemVar} = items[${indexVar}];`);
1481
- setupLines.push(` const key = ${keyExpr};`);
1482
- setupLines.push(` let block = map.get(key);`);
1483
- setupLines.push(` if (!block) {`);
1484
- setupLines.push(` block = ${blockName}(this, ${itemVar}, ${indexVar});`);
1485
- setupLines.push(` block.c();`);
1536
+ setupLines.push(` for (let ${indexVar} = 0; ${indexVar} < __items.length; ${indexVar}++) {`);
1537
+ setupLines.push(` const ${itemVar} = __items[${indexVar}];`);
1538
+ setupLines.push(` const __key = ${keyExpr};`);
1539
+ setupLines.push(` let __block = __map.get(__key);`);
1540
+ setupLines.push(` if (!__block) {`);
1541
+ setupLines.push(` __block = ${blockName}(this, ${itemVar}, ${indexVar});`);
1542
+ setupLines.push(` __block.c();`);
1486
1543
  setupLines.push(` }`);
1487
- setupLines.push(` block.m(parent, anchor);`);
1488
- setupLines.push(` block.p(this, ${itemVar}, ${indexVar});`);
1489
- setupLines.push(` newMap.set(key, block);`);
1544
+ setupLines.push(` __block.m(__parent, __anchor);`);
1545
+ setupLines.push(` __block.p(this, ${itemVar}, ${indexVar});`);
1546
+ setupLines.push(` __newMap.set(__key, __block);`);
1490
1547
  setupLines.push(` }`);
1491
1548
  setupLines.push(``);
1492
- setupLines.push(` for (const [key, block] of map) {`);
1493
- setupLines.push(` if (!newMap.has(key)) block.d(true);`);
1549
+ setupLines.push(` for (const [__k, __b] of __map) {`);
1550
+ setupLines.push(` if (!__newMap.has(__k)) __b.d(true);`);
1494
1551
  setupLines.push(` }`);
1495
1552
  setupLines.push(``);
1496
- setupLines.push(` map.clear();`);
1497
- setupLines.push(` for (const [k, v] of newMap) map.set(k, v);`);
1553
+ setupLines.push(` __map.clear();`);
1554
+ setupLines.push(` for (const [__k, __v] of __newMap) __map.set(__k, __v);`);
1498
1555
  setupLines.push(` });`);
1499
1556
  setupLines.push(`}`);
1500
1557
 
@@ -1510,7 +1567,7 @@ export function installComponentSupport(CodeGenerator, Lexer) {
1510
1567
  proto.generateChildComponent = function(componentName, args) {
1511
1568
  const instVar = this.newElementVar('inst');
1512
1569
  const elVar = this.newElementVar('el');
1513
- const { propsCode, childrenSetupLines } = this.buildComponentProps(args);
1570
+ const { propsCode, reactiveProps, childrenSetupLines } = this.buildComponentProps(args);
1514
1571
 
1515
1572
  this._createLines.push(`${instVar} = new ${componentName}(${propsCode});`);
1516
1573
  this._createLines.push(`${elVar} = ${instVar}._create();`);
@@ -1518,6 +1575,10 @@ export function installComponentSupport(CodeGenerator, Lexer) {
1518
1575
 
1519
1576
  this._setupLines.push(`if (${instVar}._setup) ${instVar}._setup();`);
1520
1577
 
1578
+ for (const { key, valueCode } of reactiveProps) {
1579
+ this._setupLines.push(`__effect(() => { if (${instVar}.${key}) ${instVar}.${key}.value = ${valueCode}; });`);
1580
+ }
1581
+
1521
1582
  for (const line of childrenSetupLines) {
1522
1583
  this._setupLines.push(line);
1523
1584
  }
@@ -1531,6 +1592,7 @@ export function installComponentSupport(CodeGenerator, Lexer) {
1531
1592
 
1532
1593
  proto.buildComponentProps = function(args) {
1533
1594
  const props = [];
1595
+ const reactiveProps = [];
1534
1596
  let childrenVar = null;
1535
1597
  const childrenSetupLines = [];
1536
1598
 
@@ -1539,13 +1601,22 @@ export function installComponentSupport(CodeGenerator, Lexer) {
1539
1601
  for (let i = 1; i < arg.length; i++) {
1540
1602
  const [key, value] = arg[i];
1541
1603
  if (typeof key === 'string') {
1542
- // Pass reactive members as signals (not values) for reactive prop binding.
1543
- // Child's __state passthrough returns the signal as-is shared reactivity.
1544
- const prevReactive = this.reactiveMembers;
1545
- this.reactiveMembers = new Set();
1546
- const valueCode = this.generateInComponent(value, 'value');
1547
- this.reactiveMembers = prevReactive;
1548
- props.push(`${key}: ${valueCode}`);
1604
+ // Simple reactive identifier pass signal directly for shared reactivity.
1605
+ // Complex expressions use normal .value unwrapping to compute the value.
1606
+ const isSimpleReactive = this.reactiveMembers && (
1607
+ (typeof value === 'string' && this.reactiveMembers.has(value)) ||
1608
+ (Array.isArray(value) && value[0] === '.' && value[1] === 'this' && typeof value[2] === 'string' && this.reactiveMembers.has(value[2]))
1609
+ );
1610
+ if (isSimpleReactive) {
1611
+ const member = typeof value === 'string' ? value : value[2];
1612
+ props.push(`${key}: this.${member}`);
1613
+ } else {
1614
+ const valueCode = this.generateInComponent(value, 'value');
1615
+ props.push(`${key}: ${valueCode}`);
1616
+ if (this.hasReactiveDeps(value)) {
1617
+ reactiveProps.push({ key, valueCode });
1618
+ }
1619
+ }
1549
1620
  }
1550
1621
  }
1551
1622
  } else if (Array.isArray(arg) && (arg[0] === '->' || arg[0] === '=>')) {
@@ -1559,11 +1630,20 @@ export function installComponentSupport(CodeGenerator, Lexer) {
1559
1630
  for (let i = 1; i < child.length; i++) {
1560
1631
  const [key, value] = child[i];
1561
1632
  if (typeof key === 'string') {
1562
- const prevReactive = this.reactiveMembers;
1563
- this.reactiveMembers = new Set();
1564
- const valueCode = this.generateInComponent(value, 'value');
1565
- this.reactiveMembers = prevReactive;
1566
- props.push(`${key}: ${valueCode}`);
1633
+ const isSimpleReactive = this.reactiveMembers && (
1634
+ (typeof value === 'string' && this.reactiveMembers.has(value)) ||
1635
+ (Array.isArray(value) && value[0] === '.' && value[1] === 'this' && typeof value[2] === 'string' && this.reactiveMembers.has(value[2]))
1636
+ );
1637
+ if (isSimpleReactive) {
1638
+ const member = typeof value === 'string' ? value : value[2];
1639
+ props.push(`${key}: this.${member}`);
1640
+ } else {
1641
+ const valueCode = this.generateInComponent(value, 'value');
1642
+ props.push(`${key}: ${valueCode}`);
1643
+ if (this.hasReactiveDeps(value)) {
1644
+ reactiveProps.push({ key, valueCode });
1645
+ }
1646
+ }
1567
1647
  }
1568
1648
  }
1569
1649
  } else {
@@ -1598,7 +1678,7 @@ export function installComponentSupport(CodeGenerator, Lexer) {
1598
1678
  }
1599
1679
 
1600
1680
  const propsCode = props.length > 0 ? `{ ${props.join(', ')} }` : '{}';
1601
- return { propsCode, childrenSetupLines };
1681
+ return { propsCode, reactiveProps, childrenSetupLines };
1602
1682
  };
1603
1683
 
1604
1684
  // --------------------------------------------------------------------------
@@ -1646,6 +1726,24 @@ export function installComponentSupport(CodeGenerator, Lexer) {
1646
1726
  return false;
1647
1727
  };
1648
1728
 
1729
+ // findRootReactiveMember — walk a nested access chain to find the root reactive member
1730
+ // e.g. (. ([] history 0) triglycerides) → 'history'
1731
+ // --------------------------------------------------------------------------
1732
+
1733
+ proto.findRootReactiveMember = function(sexpr) {
1734
+ if (typeof sexpr === 'string') {
1735
+ return this.reactiveMembers?.has(sexpr) ? sexpr : null;
1736
+ }
1737
+ if (!Array.isArray(sexpr)) return null;
1738
+ if (sexpr[0] === '.' && sexpr[1] === 'this' && typeof sexpr[2] === 'string') {
1739
+ return this.reactiveMembers?.has(sexpr[2]) ? sexpr[2] : null;
1740
+ }
1741
+ if (sexpr[0] === '.' || sexpr[0] === '[]') {
1742
+ return this.findRootReactiveMember(sexpr[1]);
1743
+ }
1744
+ return null;
1745
+ };
1746
+
1649
1747
  // _rootsAtThis — check if a property-access chain is rooted at 'this'
1650
1748
  // --------------------------------------------------------------------------
1651
1749