rip-lang 3.7.3 → 3.8.8
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 +111 -0
- package/README.md +42 -34
- package/docs/RIP-INTERNALS.md +2 -4
- package/docs/RIP-LANG.md +150 -3
- package/docs/RIP-TYPES.md +1 -2
- package/docs/demo.html +342 -0
- package/docs/dist/rip-ui.min.js +516 -0
- package/docs/dist/rip-ui.min.js.br +0 -0
- package/docs/dist/rip.browser.js +379 -461
- package/docs/dist/rip.browser.min.js +204 -220
- package/docs/dist/rip.browser.min.js.br +0 -0
- package/docs/dist/ui.js +956 -0
- package/docs/dist/ui.min.js +2 -0
- package/docs/dist/ui.min.js.br +0 -0
- package/docs/dist/ui.rip +957 -0
- package/docs/dist/ui.rip.br +0 -0
- package/docs/examples.rip +180 -0
- package/docs/index.html +3 -1599
- package/docs/playground-app.html +1022 -0
- package/docs/playground-js.html +1645 -0
- package/docs/playground-rip-ui.html +1419 -0
- package/docs/playground-rip.html +1450 -0
- package/docs/rip-fav.svg +5 -0
- package/package.json +3 -3
- package/scripts/serve.js +3 -2
- package/src/browser.js +38 -16
- package/src/compiler.js +165 -226
- package/src/components.js +153 -140
- package/src/grammar/README.md +234 -0
- package/src/grammar/lunar.rip +2412 -0
- package/src/grammar/solar.rip +18 -4
- package/src/lexer.js +82 -30
- package/src/parser-rd.js +3242 -0
- package/src/parser.js +6 -5
- package/src/repl.js +24 -5
- package/docs/NOTES.md +0 -93
- package/docs/RIP-GUIDE.md +0 -698
- package/docs/RIP-REACTIVITY.md +0 -311
package/src/grammar/solar.rip
CHANGED
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
import * as fs from 'fs'
|
|
11
11
|
import * as path from 'path'
|
|
12
12
|
import { fileURLToPath, pathToFileURL } from 'url'
|
|
13
|
+
import { install as installLunar } from './lunar.rip'
|
|
13
14
|
|
|
14
15
|
VERSION = '1.5.0'
|
|
15
16
|
|
|
@@ -89,9 +90,11 @@ class Generator
|
|
|
89
90
|
# Build parser
|
|
90
91
|
@timing '💥 Total time', =>
|
|
91
92
|
@timing 'processGrammar' , => @processGrammar grammar # Process grammar rules
|
|
92
|
-
|
|
93
|
+
unless @options.rd
|
|
94
|
+
@timing 'buildLRAutomaton' , => @buildLRAutomaton() # Build LR(0) automaton
|
|
93
95
|
@timing 'processLookaheads', => @processLookaheads() # Compute FIRST/FOLLOW and assign lookaheads
|
|
94
|
-
|
|
96
|
+
unless @options.rd
|
|
97
|
+
@timing 'buildParseTable' , => @buildParseTable() # Build parse table with default actions
|
|
95
98
|
|
|
96
99
|
# ============================================================================
|
|
97
100
|
# Helper Functions
|
|
@@ -470,6 +473,7 @@ class Generator
|
|
|
470
473
|
|
|
471
474
|
# Assign FOLLOW sets to reduction items
|
|
472
475
|
_assignItemLookaheads!: ->
|
|
476
|
+
return unless @states
|
|
473
477
|
for state in @states
|
|
474
478
|
for item as state.reductions
|
|
475
479
|
follows = @types[item.rule.type]?.follows
|
|
@@ -653,6 +657,8 @@ class Generator
|
|
|
653
657
|
# Decoder reconstructs [{key: val}, ...] at runtime
|
|
654
658
|
"(()=>{let d=[#{data.join ','}],t=[],p=0,n,o,k,a;while(p<d.length){n=d[p++];o={};k=0;a=[];while(n--)k+=d[p++],a.push(k);for(k of a)o[k]=d[p++];t.push(o)}return t})()"
|
|
655
659
|
|
|
660
|
+
# Recursive descent generation — installed by Lunar (see lunar.rip)
|
|
661
|
+
|
|
656
662
|
# ============================================================================
|
|
657
663
|
# Runtime Parser
|
|
658
664
|
# ============================================================================
|
|
@@ -767,6 +773,8 @@ class Generator
|
|
|
767
773
|
# Exports
|
|
768
774
|
# ==============================================================================
|
|
769
775
|
|
|
776
|
+
installLunar Generator
|
|
777
|
+
|
|
770
778
|
export { Generator }
|
|
771
779
|
|
|
772
780
|
# ==============================================================================
|
|
@@ -798,6 +806,7 @@ if isRunAsScript
|
|
|
798
806
|
-v, --version Show version
|
|
799
807
|
-i, --info Show grammar information
|
|
800
808
|
-s, --sexpr Show grammar as s-expression
|
|
809
|
+
-r, --rd Generate recursive descent parser (parser-rd.js)
|
|
801
810
|
-c, --conflicts Show conflict details (use with --info)
|
|
802
811
|
-o, --output <file> Output file (default: parser.js)
|
|
803
812
|
|
|
@@ -835,7 +844,7 @@ if isRunAsScript
|
|
|
835
844
|
console.log " Resolution: #{conflict.resolution} (by default)"
|
|
836
845
|
|
|
837
846
|
# Parse command line
|
|
838
|
-
options = {help: false, version: false, info: false, sexpr: false, conflicts: false, output: 'parser.js'}
|
|
847
|
+
options = {help: false, version: false, info: false, sexpr: false, conflicts: false, rd: false, output: 'parser.js'}
|
|
839
848
|
grammarFile = null
|
|
840
849
|
i = 0
|
|
841
850
|
|
|
@@ -846,6 +855,7 @@ if isRunAsScript
|
|
|
846
855
|
when '-v', '--version' then options.version = true
|
|
847
856
|
when '-i', '--info' then options.info = true
|
|
848
857
|
when '-s', '--sexpr' then options.sexpr = true
|
|
858
|
+
when '-r', '--rd' then options.rd = true
|
|
849
859
|
when '-c', '--conflicts' then options.conflicts = true
|
|
850
860
|
when '-o', '--output' then options.output = process.argv[++i + 2]
|
|
851
861
|
else grammarFile = arg unless arg.startsWith('-')
|
|
@@ -906,7 +916,11 @@ if isRunAsScript
|
|
|
906
916
|
if options.info
|
|
907
917
|
showStats generator
|
|
908
918
|
else
|
|
909
|
-
|
|
919
|
+
if options.rd
|
|
920
|
+
options.output = 'parser-rd.js' if options.output is 'parser.js'
|
|
921
|
+
parserCode = generator.generateRD()
|
|
922
|
+
else
|
|
923
|
+
parserCode = generator.generate()
|
|
910
924
|
fs.writeFileSync options.output, parserCode
|
|
911
925
|
console.log "\nParser generated: #{options.output}"
|
|
912
926
|
|
package/src/lexer.js
CHANGED
|
@@ -220,7 +220,7 @@ let NEWLINE_RE = /^(?:\n[^\n\S]*)+/;
|
|
|
220
220
|
let COMMENT_RE = /^(\s*)###([^#][\s\S]*?)(?:###([^\n\S]*)|###$)|^((?:\s*#(?!##[^#]).*)+)/;
|
|
221
221
|
let CODE_RE = /^[-=]>/;
|
|
222
222
|
let REACTIVE_RE = /^(?:~[=>]|=!)/;
|
|
223
|
-
let STRING_START_RE = /^(?:'''|"""|'|")/;
|
|
223
|
+
let STRING_START_RE = /^(?:'''\\|"""\\|'''|"""|'|")/;
|
|
224
224
|
let STRING_SINGLE_RE = /^(?:[^\\']|\\[\s\S])*/;
|
|
225
225
|
let STRING_DOUBLE_RE = /^(?:[^\\"#$]|\\[\s\S]|\#(?!\{)|\$(?!\{))*/;
|
|
226
226
|
let HEREDOC_SINGLE_RE = /^(?:[^\\']|\\[\s\S]|'(?!''))*/;
|
|
@@ -627,10 +627,19 @@ export class Lexer {
|
|
|
627
627
|
// --------------------------------------------------------------------------
|
|
628
628
|
|
|
629
629
|
commentToken() {
|
|
630
|
+
// In render blocks, #word is an element ID, not a comment
|
|
631
|
+
if (this.inRenderBlock) {
|
|
632
|
+
if (/^#[a-zA-Z_]/.test(this.chunk)) {
|
|
633
|
+
let prev = this.prev();
|
|
634
|
+
if (prev && (prev[0] === 'IDENTIFIER' || prev[0] === 'PROPERTY'))
|
|
635
|
+
return 0; // after a tag (div#main) → let # become a token for rewriter
|
|
636
|
+
let m = /^#([a-zA-Z_][\w-]*)/.exec(this.chunk);
|
|
637
|
+
if (m) { this.emit('IDENTIFIER', 'div#' + m[1]); return m[0].length; }
|
|
638
|
+
}
|
|
639
|
+
if (/^\s+#[a-zA-Z_]/.test(this.chunk)) return 0; // let lineToken handle indentation first
|
|
640
|
+
}
|
|
630
641
|
let match = COMMENT_RE.exec(this.chunk);
|
|
631
642
|
if (!match) return 0;
|
|
632
|
-
// For now, consume the comment and discard it
|
|
633
|
-
// TODO: attach comments to adjacent tokens for source map support
|
|
634
643
|
return match[0].length;
|
|
635
644
|
}
|
|
636
645
|
|
|
@@ -779,6 +788,8 @@ export class Lexer {
|
|
|
779
788
|
if (!m) return 0;
|
|
780
789
|
|
|
781
790
|
let quote = m[0];
|
|
791
|
+
let raw = quote.length > 1 && quote.endsWith('\\');
|
|
792
|
+
let baseQuote = raw ? quote.slice(0, -1) : quote;
|
|
782
793
|
let prev = this.prev();
|
|
783
794
|
|
|
784
795
|
// Tag 'from' in import/export context
|
|
@@ -787,24 +798,24 @@ export class Lexer {
|
|
|
787
798
|
}
|
|
788
799
|
|
|
789
800
|
let regex;
|
|
790
|
-
switch (
|
|
801
|
+
switch (baseQuote) {
|
|
791
802
|
case "'": regex = STRING_SINGLE_RE; break;
|
|
792
803
|
case '"': regex = STRING_DOUBLE_RE; break;
|
|
793
804
|
case "'''": regex = HEREDOC_SINGLE_RE; break;
|
|
794
805
|
case '"""': regex = HEREDOC_DOUBLE_RE; break;
|
|
795
806
|
}
|
|
796
807
|
|
|
797
|
-
let {tokens: parts, index: end} = this.matchWithInterpolations(regex, quote);
|
|
798
|
-
let heredoc =
|
|
808
|
+
let {tokens: parts, index: end} = this.matchWithInterpolations(regex, quote, baseQuote);
|
|
809
|
+
let heredoc = baseQuote.length === 3;
|
|
799
810
|
|
|
800
811
|
// Heredoc indent processing
|
|
801
812
|
let indent = null;
|
|
802
813
|
if (heredoc) {
|
|
803
|
-
indent = this.processHeredocIndent(end,
|
|
814
|
+
indent = this.processHeredocIndent(end, baseQuote, parts);
|
|
804
815
|
}
|
|
805
816
|
|
|
806
817
|
// Merge interpolation tokens into the stream
|
|
807
|
-
this.mergeInterpolationTokens(parts, {quote, indent, endOffset: end});
|
|
818
|
+
this.mergeInterpolationTokens(parts, {quote: baseQuote, indent, endOffset: end, raw});
|
|
808
819
|
|
|
809
820
|
return end;
|
|
810
821
|
}
|
|
@@ -910,7 +921,7 @@ export class Lexer {
|
|
|
910
921
|
}
|
|
911
922
|
|
|
912
923
|
// Merge NEOSTRING/TOKENS into the real token stream
|
|
913
|
-
mergeInterpolationTokens(tokens, {quote, indent, endOffset}) {
|
|
924
|
+
mergeInterpolationTokens(tokens, {quote, indent, endOffset, raw}) {
|
|
914
925
|
if (tokens.length > 1) {
|
|
915
926
|
this.emit('STRING_START', '(', {len: quote?.length || 0, data: {quote}});
|
|
916
927
|
}
|
|
@@ -939,6 +950,12 @@ export class Lexer {
|
|
|
939
950
|
processed = processed.replace(/\n[^\S\n]*$/, '');
|
|
940
951
|
}
|
|
941
952
|
|
|
953
|
+
// Raw heredocs ('''\, """\): escape only recognized JS escape sequences
|
|
954
|
+
// so \n \t \u etc. stay literal, but \s \w \d pass through unchanged
|
|
955
|
+
if (raw) {
|
|
956
|
+
processed = processed.replace(/\\([nrtbfv0\\'"`xu])/g, '\\\\$1');
|
|
957
|
+
}
|
|
958
|
+
|
|
942
959
|
this.emit('STRING', `"${processed}"`, {len: val.length, data: {quote}});
|
|
943
960
|
}
|
|
944
961
|
}
|
|
@@ -996,8 +1013,8 @@ export class Lexer {
|
|
|
996
1013
|
let end = index + flags.length;
|
|
997
1014
|
|
|
998
1015
|
if (parts.length === 1 || !parts.some(p => p[0] === 'TOKENS')) {
|
|
999
|
-
// Simple heregex (no interpolations)
|
|
1000
|
-
let body = parts[0]?.[1] || '';
|
|
1016
|
+
// Simple heregex (no interpolations) — escape unescaped / for regex literal
|
|
1017
|
+
let body = (parts[0]?.[1] || '').replace(/(?<!\\)\//g, '\\/');
|
|
1001
1018
|
this.emit('REGEX', `/${body}/${flags}`, {len: end, data: {delimiter: '///', heregex: {flags}}});
|
|
1002
1019
|
} else {
|
|
1003
1020
|
// Complex heregex with interpolations
|
|
@@ -1133,10 +1150,23 @@ export class Lexer {
|
|
|
1133
1150
|
else if (val === '~>') tag = 'REACT_ASSIGN';
|
|
1134
1151
|
else if (val === '=!') tag = 'READONLY_ASSIGN';
|
|
1135
1152
|
// Merge assignment: *config = {a: 1} → Object.assign(config, {a: 1})
|
|
1153
|
+
// Also supports *@ = props → Object.assign(this, props)
|
|
1136
1154
|
else if (val === '*' && (!prev || prev[0] === 'TERMINATOR' || prev[0] === 'INDENT' || prev[0] === 'OUTDENT') &&
|
|
1137
|
-
/^[a-zA-Z_$]/.test(this.chunk[1] || '')) {
|
|
1138
|
-
// Scan ahead to find "IDENTIFIER =" pattern
|
|
1155
|
+
(/^[a-zA-Z_$]/.test(this.chunk[1] || '') || this.chunk[1] === '@')) {
|
|
1139
1156
|
let rest = this.chunk.slice(1);
|
|
1157
|
+
// Handle *@ = ... → Object.assign(@, ...)
|
|
1158
|
+
let mAt = /^@(\s*)=(?!=)/.exec(rest);
|
|
1159
|
+
if (mAt) {
|
|
1160
|
+
let space = mAt[1];
|
|
1161
|
+
this.emit('IDENTIFIER', 'Object');
|
|
1162
|
+
this.emit('.', '.');
|
|
1163
|
+
let t = this.emit('PROPERTY', 'assign');
|
|
1164
|
+
t.spaced = true; // trigger implicit call detection
|
|
1165
|
+
this.emit('@', '@');
|
|
1166
|
+
this.emit(',', ',');
|
|
1167
|
+
return 1 + 1 + space.length + 1; // consume *@ =, value tokens become args
|
|
1168
|
+
}
|
|
1169
|
+
// Scan ahead to find "IDENTIFIER =" pattern
|
|
1140
1170
|
let m = /^((?:(?!\s)[$\w\x7f-\uffff])+(?:\.[a-zA-Z_$][\w]*)*)(\s*)=(?!=)/.exec(rest);
|
|
1141
1171
|
if (m) {
|
|
1142
1172
|
let target = m[1], space = m[2];
|
|
@@ -1361,7 +1391,7 @@ export class Lexer {
|
|
|
1361
1391
|
// - Combine #id selectors: div # main → div#main
|
|
1362
1392
|
// - Two-way binding: value <=> username → __bind_value__: username
|
|
1363
1393
|
// - Event modifiers: @click.prevent: → [@click.prevent]:
|
|
1364
|
-
// - Dynamic classes: div.('card', x && 'active') → div.
|
|
1394
|
+
// - Dynamic classes: div.('card', x && 'active') → div.__clsx(...)
|
|
1365
1395
|
// - Implicit nesting: inject -> before INDENT for template elements
|
|
1366
1396
|
// - Hyphenated attributes: data-foo: "x" → "data-foo": "x"
|
|
1367
1397
|
// =========================================================================
|
|
@@ -1385,7 +1415,7 @@ export class Lexer {
|
|
|
1385
1415
|
return isHtmlTag(name) || isComponent(name);
|
|
1386
1416
|
};
|
|
1387
1417
|
|
|
1388
|
-
let
|
|
1418
|
+
let startsWithTag = (tokens, i) => {
|
|
1389
1419
|
let j = i;
|
|
1390
1420
|
while (j > 0) {
|
|
1391
1421
|
let pt = tokens[j - 1][0];
|
|
@@ -1394,7 +1424,7 @@ export class Lexer {
|
|
|
1394
1424
|
}
|
|
1395
1425
|
j--;
|
|
1396
1426
|
}
|
|
1397
|
-
return tokens[j] && tokens[j][0] === 'IDENTIFIER' &&
|
|
1427
|
+
return tokens[j] && tokens[j][0] === 'IDENTIFIER' && isTemplateTag(tokens[j][1]);
|
|
1398
1428
|
};
|
|
1399
1429
|
|
|
1400
1430
|
this.scanTokens(function(token, i, tokens) {
|
|
@@ -1486,7 +1516,7 @@ export class Lexer {
|
|
|
1486
1516
|
if (tag === 'IDENTIFIER' || tag === 'PROPERTY') {
|
|
1487
1517
|
let next = tokens[i + 1];
|
|
1488
1518
|
let nextNext = tokens[i + 2];
|
|
1489
|
-
if (next && next[0] === '#' && nextNext && nextNext[0] === 'PROPERTY') {
|
|
1519
|
+
if (next && next[0] === '#' && nextNext && (nextNext[0] === 'PROPERTY' || nextNext[0] === 'IDENTIFIER')) {
|
|
1490
1520
|
token[1] = token[1] + '#' + nextNext[1];
|
|
1491
1521
|
if (nextNext.spaced) token.spaced = true;
|
|
1492
1522
|
tokens.splice(i + 1, 2);
|
|
@@ -1533,15 +1563,15 @@ export class Lexer {
|
|
|
1533
1563
|
|
|
1534
1564
|
// ─────────────────────────────────────────────────────────────────────
|
|
1535
1565
|
// Dynamic classes
|
|
1536
|
-
// div.('card', x && 'active') → div.
|
|
1537
|
-
// .('card') → div.
|
|
1566
|
+
// div.('card', x && 'active') → div.__clsx('card', x && 'active')
|
|
1567
|
+
// .('card') → div.__clsx('card')
|
|
1538
1568
|
// ─────────────────────────────────────────────────────────────────────
|
|
1539
1569
|
if (tag === '.' && nextToken && nextToken[0] === '(') {
|
|
1540
1570
|
let prevToken = i > 0 ? tokens[i - 1] : null;
|
|
1541
1571
|
let prevTag = prevToken ? prevToken[0] : null;
|
|
1542
1572
|
let atLineStart = prevTag === 'INDENT' || prevTag === 'TERMINATOR';
|
|
1543
1573
|
|
|
1544
|
-
let cxToken = gen('PROPERTY', '
|
|
1574
|
+
let cxToken = gen('PROPERTY', '__clsx', token);
|
|
1545
1575
|
nextToken[0] = 'CALL_START';
|
|
1546
1576
|
let depth = 1;
|
|
1547
1577
|
for (let j = i + 2; j < tokens.length && depth > 0; j++) {
|
|
@@ -1572,14 +1602,16 @@ export class Lexer {
|
|
|
1572
1602
|
}
|
|
1573
1603
|
|
|
1574
1604
|
let isTemplateElement = false;
|
|
1605
|
+
let prevTag = i > 0 ? tokens[i - 1][0] : null;
|
|
1606
|
+
let isAfterControlFlow = prevTag === 'IF' || prevTag === 'UNLESS' || prevTag === 'WHILE' || prevTag === 'UNTIL' || prevTag === 'WHEN';
|
|
1575
1607
|
|
|
1576
|
-
if (tag === 'IDENTIFIER' && isTemplateTag(token[1])) {
|
|
1608
|
+
if (tag === 'IDENTIFIER' && isTemplateTag(token[1]) && !isAfterControlFlow) {
|
|
1577
1609
|
isTemplateElement = true;
|
|
1578
1610
|
} else if (tag === 'PROPERTY' || tag === 'STRING' || tag === 'CALL_END' || tag === ')') {
|
|
1579
|
-
isTemplateElement =
|
|
1611
|
+
isTemplateElement = startsWithTag(tokens, i);
|
|
1580
1612
|
}
|
|
1581
1613
|
else if (tag === 'IDENTIFIER' && i > 1 && tokens[i - 1][0] === '...') {
|
|
1582
|
-
if (
|
|
1614
|
+
if (startsWithTag(tokens, i)) {
|
|
1583
1615
|
let commaToken = gen(',', ',', token);
|
|
1584
1616
|
let arrowToken = gen('->', '->', token);
|
|
1585
1617
|
arrowToken.newLine = true;
|
|
@@ -1589,16 +1621,36 @@ export class Lexer {
|
|
|
1589
1621
|
}
|
|
1590
1622
|
|
|
1591
1623
|
if (isTemplateElement) {
|
|
1592
|
-
let
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1624
|
+
let isClassOrIdTail = tag === 'PROPERTY' && i > 0 && (tokens[i - 1][0] === '.' || tokens[i - 1][0] === '#');
|
|
1625
|
+
if ((tag === 'IDENTIFIER' && isTemplateTag(token[1])) || isClassOrIdTail) {
|
|
1626
|
+
// Bare tag or tag.class/tag#id (no other args): inject CALL_START -> and manage CALL_END
|
|
1627
|
+
let callStartToken = gen('CALL_START', '(', token);
|
|
1628
|
+
let arrowToken = gen('->', '->', token);
|
|
1629
|
+
arrowToken.newLine = true;
|
|
1630
|
+
tokens.splice(i + 1, 0, callStartToken, arrowToken);
|
|
1631
|
+
pendingCallEnds.push(currentIndent + 1);
|
|
1632
|
+
return 3;
|
|
1633
|
+
} else {
|
|
1634
|
+
// Tag with args: inject , -> (call wrapping handled by addImplicitBracesAndParens)
|
|
1635
|
+
let commaToken = gen(',', ',', token);
|
|
1636
|
+
let arrowToken = gen('->', '->', token);
|
|
1637
|
+
arrowToken.newLine = true;
|
|
1638
|
+
tokens.splice(i + 1, 0, commaToken, arrowToken);
|
|
1639
|
+
return 3;
|
|
1640
|
+
}
|
|
1599
1641
|
}
|
|
1600
1642
|
}
|
|
1601
1643
|
|
|
1644
|
+
// ─────────────────────────────────────────────────────────────────────
|
|
1645
|
+
// Bare component reference (PascalCase, no children, no args)
|
|
1646
|
+
// Counter → Counter() so it gets treated as a component instantiation
|
|
1647
|
+
// ─────────────────────────────────────────────────────────────────────
|
|
1648
|
+
if (tag === 'IDENTIFIER' && isComponent(token[1]) &&
|
|
1649
|
+
nextToken && (nextToken[0] === 'OUTDENT' || nextToken[0] === 'TERMINATOR')) {
|
|
1650
|
+
tokens.splice(i + 1, 0, gen('CALL_START', '(', token), gen('CALL_END', ')', token));
|
|
1651
|
+
return 3;
|
|
1652
|
+
}
|
|
1653
|
+
|
|
1602
1654
|
return 1;
|
|
1603
1655
|
});
|
|
1604
1656
|
}
|