rip-lang 3.13.26 → 3.13.28

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.26",
3
+ "version": "3.13.28",
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
@@ -757,7 +757,7 @@ export createRenderer = (opts = {}) ->
757
757
  inst.mount wrapper
758
758
  layoutInstances.push inst
759
759
 
760
- slot = wrapper.querySelector('#content') or wrapper
760
+ slot = wrapper.querySelector('slot') or wrapper
761
761
  mp = slot
762
762
 
763
763
  currentLayouts = [...layoutFiles]
package/src/compiler.js CHANGED
@@ -205,6 +205,7 @@ export class CodeGenerator {
205
205
  'continue': 'generateContinue',
206
206
  '?': 'generateExistential',
207
207
  'defined': 'generateDefined',
208
+ 'presence': 'generatePresence',
208
209
  '?:': 'generateTernary',
209
210
  '|>': 'generatePipe',
210
211
  'loop': 'generateLoop',
@@ -941,17 +942,38 @@ export class CodeGenerator {
941
942
  let isIncl = index[0] === '..';
942
943
  let arrCode = this.generate(arr, 'value');
943
944
  let [start, end] = index.slice(1);
945
+
946
+ // Detect compile-time numeric literals (positive, negative, String objects)
947
+ let numericLiteral = (node) => {
948
+ if (node === null) return null;
949
+ let v = str(node) ?? node;
950
+ if (typeof v === 'number') return v;
951
+ if ((typeof v === 'string') && /^\d+$/.test(v)) return +v;
952
+ if (Array.isArray(node) && node[0] === '-' && node.length === 2) {
953
+ let inner = str(node[1]) ?? node[1];
954
+ if (typeof inner === 'number') return -inner;
955
+ if ((typeof inner === 'string') && /^\d+$/.test(inner)) return -inner;
956
+ }
957
+ return null;
958
+ };
959
+
960
+ let inclEnd = (s, e, endNode) => {
961
+ let n = numericLiteral(endNode);
962
+ if (n !== null && n !== -1) return `${arrCode}.slice(${s}, ${n + 1})`;
963
+ return `${arrCode}.slice(${s}, +${e} + 1 || 9e9)`;
964
+ };
965
+
944
966
  if (start === null && end === null) return `${arrCode}.slice()`;
945
967
  if (start === null) {
946
968
  if (isIncl && this.is(end, '-', 1) && (str(end[1]) ?? end[1]) == 1) return `${arrCode}.slice(0)`;
947
969
  let e = this.generate(end, 'value');
948
- return isIncl ? `${arrCode}.slice(0, +${e} + 1 || 9e9)` : `${arrCode}.slice(0, ${e})`;
970
+ return isIncl ? inclEnd('0', e, end) : `${arrCode}.slice(0, ${e})`;
949
971
  }
950
972
  if (end === null) return `${arrCode}.slice(${this.generate(start, 'value')})`;
951
973
  let s = this.generate(start, 'value');
952
974
  if (isIncl && this.is(end, '-', 1) && (str(end[1]) ?? end[1]) == 1) return `${arrCode}.slice(${s})`;
953
975
  let e = this.generate(end, 'value');
954
- return isIncl ? `${arrCode}.slice(${s}, +${e} + 1 || 9e9)` : `${arrCode}.slice(${s}, ${e})`;
976
+ return isIncl ? inclEnd(s, e, end) : `${arrCode}.slice(${s}, ${e})`;
955
977
  }
956
978
  // Negative literal index: arr[-1] → arr.at(-1)
957
979
  if (this.is(index, '-', 1)) {
@@ -1124,6 +1146,10 @@ export class CodeGenerator {
1124
1146
  return `(${this.generate(rest[0], 'value')} !== undefined)`;
1125
1147
  }
1126
1148
 
1149
+ generatePresence(head, rest) {
1150
+ return `(${this.generate(rest[0], 'value')} ? true : undefined)`;
1151
+ }
1152
+
1127
1153
  generateTernary(head, rest, context) {
1128
1154
  let [cond, then_, else_] = rest;
1129
1155
 
package/src/components.js CHANGED
@@ -289,9 +289,14 @@ export function installComponentSupport(CodeGenerator, Lexer) {
289
289
  let prevTag = prevToken ? prevToken[0] : null;
290
290
  if (prevTag === 'INDENT' || prevTag === 'TERMINATOR') {
291
291
  if (nextToken && nextToken[0] === 'PROPERTY') {
292
- let divToken = gen('IDENTIFIER', 'div', token);
293
- tokens.splice(i, 0, divToken);
294
- return 2;
292
+ // Check if property is followed by : — if so, it's an attribute
293
+ // (. foo: bar → div foo: bar), not a class (. foo → div.foo)
294
+ let nextNext = i + 2 < tokens.length ? tokens[i + 2] : null;
295
+ if (!nextNext || nextNext[0] !== ':') {
296
+ let divToken = gen('IDENTIFIER', 'div', token);
297
+ tokens.splice(i, 0, divToken);
298
+ return 2;
299
+ }
295
300
  }
296
301
  // Skip .('classes') — handled by dynamic classes handler below
297
302
  if (!nextToken || nextToken[0] !== '(') {
@@ -1041,19 +1046,26 @@ export function installComponentSupport(CodeGenerator, Lexer) {
1041
1046
  this.generateAttributes(elVar, arg);
1042
1047
  }
1043
1048
  else if (typeof arg === 'string' || arg instanceof String) {
1044
- const textVar = this.newTextVar();
1045
1049
  const val = arg.valueOf();
1046
- if (val.startsWith('"') || val.startsWith("'") || val.startsWith('`')) {
1047
- this._createLines.push(`${textVar} = document.createTextNode(${val});`);
1048
- } else if (this.reactiveMembers && this.reactiveMembers.has(val)) {
1049
- this._createLines.push(`${textVar} = document.createTextNode('');`);
1050
- this._pushEffect(`${textVar}.data = ${this._self}.${val}.value;`);
1051
- } else if (this.componentMembers && this.componentMembers.has(val)) {
1052
- this._createLines.push(`${textVar} = document.createTextNode(String(${this._self}.${val}));`);
1050
+ // Template tag appearing as a string arg (e.g., slot after multi-line attrs)
1051
+ const [tagPart, idPart] = val.split('#');
1052
+ if (this.isHtmlTag(tagPart || 'div') || this.isComponent(val)) {
1053
+ const childVar = this.generateNode(arg);
1054
+ this._createLines.push(`${elVar}.appendChild(${childVar});`);
1053
1055
  } else {
1054
- this._createLines.push(`${textVar} = document.createTextNode(${this.generateInComponent(arg, 'value')});`);
1056
+ const textVar = this.newTextVar();
1057
+ if (val.startsWith('"') || val.startsWith("'") || val.startsWith('`')) {
1058
+ this._createLines.push(`${textVar} = document.createTextNode(${val});`);
1059
+ } else if (this.reactiveMembers && this.reactiveMembers.has(val)) {
1060
+ this._createLines.push(`${textVar} = document.createTextNode('');`);
1061
+ this._pushEffect(`${textVar}.data = ${this._self}.${val}.value;`);
1062
+ } else if (this.componentMembers && this.componentMembers.has(val)) {
1063
+ this._createLines.push(`${textVar} = document.createTextNode(String(${this._self}.${val}));`);
1064
+ } else {
1065
+ this._createLines.push(`${textVar} = document.createTextNode(${this.generateInComponent(arg, 'value')});`);
1066
+ }
1067
+ this._createLines.push(`${elVar}.appendChild(${textVar});`);
1055
1068
  }
1056
- this._createLines.push(`${elVar}.appendChild(${textVar});`);
1057
1069
  }
1058
1070
  else if (arg) {
1059
1071
  const childVar = this.generateNode(arg);
@@ -853,6 +853,9 @@ grammar =
853
853
  # Postfix defined check: expr!? → (expr !== undefined)
854
854
  o 'Value DEFINED' , '["defined", 1]'
855
855
 
856
+ # Postfix presence check: expr?! → (expr ? true : undefined) — Houdini operator
857
+ o 'Value PRESENCE' , '["presence", 1]'
858
+
856
859
  # Await
857
860
  o 'AWAIT Expression' , '["await", 2]'
858
861
  o 'AWAIT INDENT Object OUTDENT' , '["await", 3]'
@@ -895,7 +898,7 @@ grammar =
895
898
  o 'Expression RELATION Expression', '[2, 1, 3]' # in, of, instanceof
896
899
 
897
900
  # Ternary
898
- o 'Expression SPACE? Expression : Expression', '["?:", 1, 3, 5]'
901
+ o 'Expression TERNARY Expression : Expression', '["?:", 1, 3, 5]'
899
902
 
900
903
  # Compound assignment
901
904
  o 'SimpleAssignable COMPOUND_ASSIGN Expression' , '[2, 1, 3]'
@@ -916,7 +919,7 @@ operators = """
916
919
  right DO_IIFE
917
920
  left . ?.
918
921
  left CALL_START CALL_END
919
- nonassoc ++ -- ? DEFINED
922
+ nonassoc ++ -- ? DEFINED PRESENCE
920
923
  right UNARY DO
921
924
  right AWAIT
922
925
  right **
@@ -932,7 +935,7 @@ operators = """
932
935
  left &&
933
936
  left ||
934
937
  left PIPE
935
- right SPACE?
938
+ right TERNARY
936
939
  nonassoc INDENT OUTDENT
937
940
  right YIELD
938
941
  right = : COMPOUND_ASSIGN RETURN THROW EXTENDS
package/src/lexer.js CHANGED
@@ -180,7 +180,7 @@ let CALL_CLOSERS = new Set(['.', '?.']);
180
180
  let UNFINISHED = new Set([
181
181
  '\\', '.', '?.', 'UNARY', 'DO', 'DO_IIFE',
182
182
  'MATH', 'UNARY_MATH', '+', '-', '**', 'SHIFT', 'RELATION',
183
- 'COMPARE', '&', '^', '|', '&&', '||', 'SPACE?', 'EXTENDS',
183
+ 'COMPARE', '&', '^', '|', '&&', '||', 'TERNARY', 'EXTENDS',
184
184
  ]);
185
185
 
186
186
  // Tokens that are not followed by regex (division context)
@@ -209,12 +209,12 @@ let UNARY_MATH = new Set(['!', '~']);
209
209
  // ==========================================================================
210
210
 
211
211
  // Identifier: word chars + optional trailing ! (await) or ? (predicate)
212
- // The ? suffix is only captured when NOT followed by . ? [ ( to avoid
213
- // conflict with ?. (optional chaining), ?? (nullish), ?.( and ?.[
212
+ // The ? suffix is only captured when NOT followed by . ? ! [ ( to avoid
213
+ // conflict with ?. (optional chaining), ?? (nullish), ?! (presence), ?.( and ?.[
214
214
  // The ! suffix is NOT captured when followed by ? to preserve !? as operator
215
- let IDENTIFIER_RE = /^(?!\d)((?:(?!\s)[$\w\x7f-\uffff])+(?:!(?!\?)|[?](?![.?[(]))?)([^\n\S]*:(?![=:]))?/;
215
+ let IDENTIFIER_RE = /^(?!\d)((?:(?!\s)[$\w\x7f-\uffff])+(?:!(?!\?)|[?](?![.?![(]))?)([^\n\S]*:(?![=:]))?/;
216
216
  let NUMBER_RE = /^0b[01](?:_?[01])*n?|^0o[0-7](?:_?[0-7])*n?|^0x[\da-f](?:_?[\da-f])*n?|^\d+(?:_\d+)*n|^(?:\d+(?:_\d+)*)?\.?\d+(?:_\d+)*(?:e[+-]?\d+(?:_\d+)*)?/i;
217
- let OPERATOR_RE = /^(?:<=>|::=|::|[-=]>|~>|~=|:=|=!|===|!==|!\?|\?\?|=~|\|>|[-+*\/%<>&|^!?=]=|>>>=?|([-+:])\1|([&|<>*\/%])\2=?|\?\.?|\.{2,3})/;
217
+ let OPERATOR_RE = /^(?:<=>|::=|::|[-=]>|~>|~=|:=|=!|===|!==|!\?|\?\!|\?\?|=~|\|>|[-+*\/%<>&|^!?=]=|>>>=?|([-+:])\1|([&|<>*\/%])\2=?|\?\.?|\.{2,3})/;
218
218
  let WHITESPACE_RE = /^[^\n\S]+/;
219
219
  let NEWLINE_RE = /^(?:\n[^\n\S]*)+/;
220
220
  let COMMENT_RE = /^(\s*)###([^#][\s\S]*?)(?:###([^\n\S]*)|###$)|^((?:\s*#(?!##[^#]).*)+)/;
@@ -500,7 +500,7 @@ export class Lexer {
500
500
  let prev = this.prev();
501
501
 
502
502
  // Don't treat colon as property when in ternary context
503
- if (colon && prev && prev[0] === 'SPACE?') colon = null;
503
+ if (colon && prev && prev[0] === 'TERNARY') colon = null;
504
504
 
505
505
  // Property vs identifier
506
506
  if (colon || (prev && (prev[0] === '.' || prev[0] === '?.' || (!prev.spaced && prev[0] === '@')))) {
@@ -634,7 +634,10 @@ export class Lexer {
634
634
  if (prev && (prev[0] === 'IDENTIFIER' || prev[0] === 'PROPERTY'))
635
635
  return 0; // after a tag (div#main) → let # become a token for rewriter
636
636
  let m = /^#([a-zA-Z_][\w-]*)/.exec(this.chunk);
637
- if (m) { this.emit('IDENTIFIER', 'div#' + m[1]); return m[0].length; }
637
+ if (m) {
638
+ this.emit('IDENTIFIER', m[1] === 'content' ? 'slot' : 'div#' + m[1]);
639
+ return m[0].length;
640
+ }
638
641
  }
639
642
  if (/^\s+#[a-zA-Z_]/.test(this.chunk)) return 0; // let lineToken handle indentation first
640
643
  }
@@ -1225,10 +1228,12 @@ export class Lexer {
1225
1228
  else if (COMPOUND_ASSIGN.has(val)) tag = 'COMPOUND_ASSIGN';
1226
1229
  else if (UNARY_MATH.has(val)) tag = 'UNARY_MATH';
1227
1230
  else if (SHIFT.has(val)) tag = 'SHIFT';
1228
- // Spaced ? → SPACE? (ternary)
1229
- else if (val === '?' && prev?.spaced) tag = 'SPACE?';
1231
+ // Spaced ? → TERNARY (ternary)
1232
+ else if (val === '?' && prev?.spaced) tag = 'TERNARY';
1230
1233
  // Unspaced !? → DEFINED (postfix defined check: v!? → v !== undefined)
1231
1234
  else if (val === '!?' && prev && !prev.spaced) tag = 'DEFINED';
1235
+ // Unspaced ?! → PRESENCE (Houdini: v?! → v ? true : undefined)
1236
+ else if (val === '?!' && prev && !prev.spaced) tag = 'PRESENCE';
1232
1237
  // ?[ and ?( without dot → treat as optional chaining (?.)
1233
1238
  else if (val === '?' && (this.chunk[1] === '[' || this.chunk[1] === '(')) tag = '?.';
1234
1239
  // Call/index context (ES6 optional chaining only)
@@ -1529,7 +1534,7 @@ export class Lexer {
1529
1534
  }
1530
1535
 
1531
1536
  // Track ternary
1532
- if (tag === 'SPACE?') inTernary = true;
1537
+ if (tag === 'TERNARY') inTernary = true;
1533
1538
 
1534
1539
  // Implicit objects start at ':'
1535
1540
  if (tag === ':') {