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.
- package/CHANGELOG.md +27 -1
- package/README.md +42 -17
- package/docs/NOTES.md +93 -0
- package/docs/RIP-GUIDE.md +39 -1
- package/docs/RIP-INTERNALS.md +9 -9
- package/docs/RIP-LANG.md +213 -2
- package/docs/RIP-TYPES.md +98 -0
- package/docs/dist/rip.browser.js +976 -508
- package/docs/dist/rip.browser.min.js +215 -215
- package/docs/dist/rip.browser.min.js.br +0 -0
- package/docs/index.html +52 -31
- package/package.json +3 -3
- package/src/compiler.js +238 -151
- package/src/components.js +167 -169
- package/src/grammar/grammar.rip +12 -2
- package/src/lexer.js +77 -5
- package/src/parser.js +123 -120
- package/src/repl.js +4 -128
- package/src/types.js +416 -35
package/src/grammar/grammar.rip
CHANGED
|
@@ -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'
|
|
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 = /^(?:<=>|::=|::|[-=]
|
|
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 [
|
|
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
|
-
|
|
491
|
-
|
|
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;
|