rip-lang 3.6.1 → 3.7.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.
@@ -564,6 +564,7 @@ grammar =
564
564
  o 'UnlessBlock'
565
565
  o 'Statement POST_IF Expression' , '["if", 3, [1]]'
566
566
  o 'Expression POST_IF Expression' , '["if", 3, [1]]'
567
+ o 'Expression POST_IF Expression ELSE INDENT Expression OUTDENT', '["?:", 3, 1, 6]'
567
568
  o 'Statement POST_UNLESS Expression' , '["unless", 3, [1]]'
568
569
  o 'Expression POST_UNLESS Expression', '["unless", 3, [1]]'
569
570
  ]
@@ -620,8 +621,9 @@ grammar =
620
621
  ]
621
622
 
622
623
  Loop: [
623
- o 'LOOP Block' , '["loop", 2]'
624
- o 'LOOP Expression', '["loop", [2]]'
624
+ o 'LOOP Block' , '["loop", 2]'
625
+ o 'LOOP Expression' , '["loop", [2]]'
626
+ o 'LOOP Expression Block' , '["loop-n", 2, 3]'
625
627
  ]
626
628
 
627
629
  # For loops
@@ -797,6 +799,10 @@ grammar =
797
799
  o 'EXPORT Identifier = Expression' , '["export", ["=", 2, 4]]'
798
800
  o 'EXPORT Identifier = TERMINATOR Expression' , '["export", ["=", 2, 5]]'
799
801
  o 'EXPORT Identifier = INDENT Expression OUTDENT' , '["export", ["=", 2, 5]]'
802
+ o 'EXPORT ReactiveAssign' , '["export", 2]'
803
+ o 'EXPORT ComputedAssign' , '["export", 2]'
804
+ o 'EXPORT ReadonlyAssign' , '["export", 2]'
805
+ o 'EXPORT ReactAssign' , '["export", 2]'
800
806
  o 'EXPORT DEFAULT Expression' , '["export-default", 3]'
801
807
  o 'EXPORT DEFAULT INDENT Object OUTDENT' , '["export-default", 4]'
802
808
  o 'EXPORT EXPORT_ALL FROM String' , '["export-all", 4]'
@@ -878,6 +884,9 @@ grammar =
878
884
  o 'Expression ?? Expression' , '["??", 1, 3]'
879
885
  o 'Expression !? Expression' , '["!?", 1, 3]' # Otherwise (undefined-only coalescing)
880
886
 
887
+ # Pipe
888
+ o 'Expression PIPE Expression' , '["|>", 1, 3]'
889
+
881
890
  # Relational
882
891
  o 'Expression RELATION Expression', '[2, 1, 3]' # in, of, instanceof
883
892
 
@@ -918,6 +927,7 @@ operators = """
918
927
  left |
919
928
  left &&
920
929
  left ||
930
+ left PIPE
921
931
  right SPACE?
922
932
  nonassoc INDENT OUTDENT
923
933
  right YIELD
package/src/lexer.js CHANGED
@@ -127,7 +127,7 @@ let IMPLICIT_UNSPACED_CALL = new Set(['+', '-']);
127
127
  // Tokens that end an implicit call
128
128
  let IMPLICIT_END = new Set([
129
129
  'POST_IF', 'POST_UNLESS', 'FOR', 'WHILE', 'UNTIL',
130
- 'WHEN', 'BY', 'LOOP', 'TERMINATOR', '||', '&&',
130
+ 'WHEN', 'BY', 'LOOP', 'TERMINATOR', '||', '&&', 'PIPE',
131
131
  ]);
132
132
 
133
133
  // Tokens that trigger implicit comma insertion before arrows
@@ -214,7 +214,7 @@ let UNARY_MATH = new Set(['!', '~']);
214
214
  // conflict with ?. (optional chaining), ?? (nullish), ?.( and ?.[
215
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*#(?!##[^#]).*)+)/;
@@ -441,7 +441,7 @@ export class Lexer {
441
441
  let match = IDENTIFIER_RE.exec(this.chunk);
442
442
  if (!match) return 0;
443
443
 
444
- let [input, id, colon] = match;
444
+ let [, id, colon] = match;
445
445
  let idLen = id.length;
446
446
  let data = {};
447
447
  let tag;
@@ -487,8 +487,8 @@ export class Lexer {
487
487
  }
488
488
 
489
489
  // 'do super' shorthand
490
- if (id === 'do' && /^(\s*super)(?!\(\))/.test(this.chunk.slice(3))) {
491
- let m = /^(\s*super)(?!\(\))/.exec(this.chunk.slice(3));
490
+ let m;
491
+ if (id === 'do' && (m = /^(\s*super)(?!\(\))/.exec(this.chunk.slice(3)))) {
492
492
  this.emit('SUPER', 'super');
493
493
  this.emit('CALL_START', '(');
494
494
  this.emit('CALL_END', ')');
@@ -1071,6 +1071,20 @@ export class Lexer {
1071
1071
  // Arrow functions → tag parameters
1072
1072
  if (CODE_RE.test(val)) this.tagParameters();
1073
1073
 
1074
+ // Method assignment: x .= trim() → x = x.trim()
1075
+ if (val === '=' && prev && prev[1] === '.' && !prev.spaced) {
1076
+ let target = this.tokens[this.tokens.length - 2];
1077
+ if (target && (target[0] === 'IDENTIFIER' || target[0] === 'PROPERTY' || target[0] === ')' || target[0] === ']')) {
1078
+ prev[0] = '=';
1079
+ prev[1] = '=';
1080
+ // Insert target clone + '.' after the '='
1081
+ let targetClone = tok(target[0], target[1], { pre: 0, row: target[2], col: target[3], len: target[4] });
1082
+ let dot = tok('.', '.', { pre: 0, row: this.row, col: this.col, len: 1 });
1083
+ this.tokens.push(targetClone, dot);
1084
+ return val.length;
1085
+ }
1086
+ }
1087
+
1074
1088
  // Compound assignment merging: ||= &&= ??=
1075
1089
  if (prev && (val === '=' || COMPOUND_ASSIGN.has(val))) {
1076
1090
  if (val === '=' && (prev[1] === '||' || prev[1] === '&&' || prev[1] === '??') && !prev.spaced) {
@@ -1094,8 +1108,17 @@ export class Lexer {
1094
1108
  this.seenFor = this.seenImport = this.seenExport = false;
1095
1109
  tag = 'TERMINATOR';
1096
1110
  }
1111
+ // Pipe operator
1112
+ else if (val === '|>') tag = 'PIPE';
1097
1113
  // Type operators
1098
1114
  else if (val === '::=') tag = 'TYPE_ALIAS';
1115
+ else if (val === '::' && /^[a-zA-Z_$]/.test(this.chunk[2] || '')) {
1116
+ // Prototype access: String::trim → String.prototype.trim
1117
+ this.emit('.', '.');
1118
+ this.emit('PROPERTY', 'prototype');
1119
+ this.emit('.', '.');
1120
+ return 2;
1121
+ }
1099
1122
  else if (val === '::') tag = 'TYPE_ANNOTATION';
1100
1123
  // Reactive operators
1101
1124
  else if (val === '~=') tag = 'COMPUTED_ASSIGN';
@@ -1103,6 +1126,31 @@ export class Lexer {
1103
1126
  else if (val === '<=>') tag = 'BIND';
1104
1127
  else if (val === '~>') tag = 'REACT_ASSIGN';
1105
1128
  else if (val === '=!') tag = 'READONLY_ASSIGN';
1129
+ // Merge assignment: *config = {a: 1} → Object.assign(config, {a: 1})
1130
+ else if (val === '*' && (!prev || prev[0] === 'TERMINATOR' || prev[0] === 'INDENT' || prev[0] === 'OUTDENT') &&
1131
+ /^[a-zA-Z_$]/.test(this.chunk[1] || '')) {
1132
+ // Scan ahead to find "IDENTIFIER =" pattern
1133
+ let rest = this.chunk.slice(1);
1134
+ let m = /^((?:(?!\s)[$\w\x7f-\uffff])+(?:\.[a-zA-Z_$][\w]*)*)(\s*)=(?!=)/.exec(rest);
1135
+ if (m) {
1136
+ let target = m[1], space = m[2];
1137
+ this.emit('IDENTIFIER', 'Object');
1138
+ this.emit('.', '.');
1139
+ this.emit('PROPERTY', 'assign');
1140
+ this.emit('CALL_START', '(');
1141
+ // Emit target — handle dotted paths like el.style
1142
+ let parts = target.split('.');
1143
+ this.emit('IDENTIFIER', parts[0]);
1144
+ for (let i = 1; i < parts.length; i++) {
1145
+ this.emit('.', '.');
1146
+ this.emit('PROPERTY', parts[i]);
1147
+ }
1148
+ this.emit(',', ',');
1149
+ let comma = this.prev();
1150
+ comma.mergeClose = true; // mark for rewriter to insert CALL_END
1151
+ return 1 + target.length + space.length + 1; // consume *target =
1152
+ }
1153
+ }
1106
1154
  // Export all
1107
1155
  else if (val === '*' && prev?.[0] === 'EXPORT') tag = 'EXPORT_ALL';
1108
1156
  // Operator classification
@@ -1185,6 +1233,7 @@ export class Lexer {
1185
1233
  this.tagPostfixConditionals();
1186
1234
  this.addImplicitBracesAndParens();
1187
1235
  this.addImplicitCallCommas();
1236
+ this.closeMergeAssignments();
1188
1237
  return this.tokens;
1189
1238
  }
1190
1239
 
@@ -1788,6 +1837,29 @@ export class Lexer {
1788
1837
  }
1789
1838
  }
1790
1839
 
1840
+ // Close merge assignments: find marked commas and insert CALL_END at expression end
1841
+ closeMergeAssignments() {
1842
+ let tokens = this.tokens;
1843
+ for (let i = 0; i < tokens.length; i++) {
1844
+ if (!tokens[i].mergeClose) continue;
1845
+ // Scan forward to find where the value expression ends
1846
+ let depth = 0;
1847
+ for (let j = i + 1; j < tokens.length; j++) {
1848
+ let tag = tokens[j][0];
1849
+ if (tag === '(' || tag === '[' || tag === '{' || tag === 'CALL_START' || tag === 'INDEX_START' || tag === 'INDENT') depth++;
1850
+ if (tag === ')' || tag === ']' || tag === '}' || tag === 'CALL_END' || tag === 'INDEX_END' || tag === 'OUTDENT') depth--;
1851
+ if (depth < 0 || (depth === 0 && tag === 'TERMINATOR')) {
1852
+ tokens.splice(j, 0, gen('CALL_END', ')', tokens[j - 1]));
1853
+ break;
1854
+ }
1855
+ if (j === tokens.length - 1) {
1856
+ tokens.splice(j + 1, 0, gen('CALL_END', ')', tokens[j]));
1857
+ break;
1858
+ }
1859
+ }
1860
+ }
1861
+ }
1862
+
1791
1863
  // Scan backward from comma to see if it's inside an implicit function call
1792
1864
  commaInImplicitCall(i) {
1793
1865
  let levels = 0;