rip-lang 3.7.4 → 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.
@@ -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
- @timing 'buildLRAutomaton' , => @buildLRAutomaton() # Build LR(0) automaton
93
+ unless @options.rd
94
+ @timing 'buildLRAutomaton' , => @buildLRAutomaton() # Build LR(0) automaton
93
95
  @timing 'processLookaheads', => @processLookaheads() # Compute FIRST/FOLLOW and assign lookaheads
94
- @timing 'buildParseTable' , => @buildParseTable() # Build parse table with default actions
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
- parserCode = generator.generate()
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 (quote) {
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 = quote.length === 3;
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, quote, parts);
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
@@ -1398,7 +1415,7 @@ export class Lexer {
1398
1415
  return isHtmlTag(name) || isComponent(name);
1399
1416
  };
1400
1417
 
1401
- let startsWithHtmlTag = (tokens, i) => {
1418
+ let startsWithTag = (tokens, i) => {
1402
1419
  let j = i;
1403
1420
  while (j > 0) {
1404
1421
  let pt = tokens[j - 1][0];
@@ -1407,7 +1424,7 @@ export class Lexer {
1407
1424
  }
1408
1425
  j--;
1409
1426
  }
1410
- return tokens[j] && tokens[j][0] === 'IDENTIFIER' && isHtmlTag(tokens[j][1]);
1427
+ return tokens[j] && tokens[j][0] === 'IDENTIFIER' && isTemplateTag(tokens[j][1]);
1411
1428
  };
1412
1429
 
1413
1430
  this.scanTokens(function(token, i, tokens) {
@@ -1499,7 +1516,7 @@ export class Lexer {
1499
1516
  if (tag === 'IDENTIFIER' || tag === 'PROPERTY') {
1500
1517
  let next = tokens[i + 1];
1501
1518
  let nextNext = tokens[i + 2];
1502
- if (next && next[0] === '#' && nextNext && nextNext[0] === 'PROPERTY') {
1519
+ if (next && next[0] === '#' && nextNext && (nextNext[0] === 'PROPERTY' || nextNext[0] === 'IDENTIFIER')) {
1503
1520
  token[1] = token[1] + '#' + nextNext[1];
1504
1521
  if (nextNext.spaced) token.spaced = true;
1505
1522
  tokens.splice(i + 1, 2);
@@ -1585,14 +1602,16 @@ export class Lexer {
1585
1602
  }
1586
1603
 
1587
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';
1588
1607
 
1589
- if (tag === 'IDENTIFIER' && isTemplateTag(token[1])) {
1608
+ if (tag === 'IDENTIFIER' && isTemplateTag(token[1]) && !isAfterControlFlow) {
1590
1609
  isTemplateElement = true;
1591
1610
  } else if (tag === 'PROPERTY' || tag === 'STRING' || tag === 'CALL_END' || tag === ')') {
1592
- isTemplateElement = startsWithHtmlTag(tokens, i);
1611
+ isTemplateElement = startsWithTag(tokens, i);
1593
1612
  }
1594
1613
  else if (tag === 'IDENTIFIER' && i > 1 && tokens[i - 1][0] === '...') {
1595
- if (startsWithHtmlTag(tokens, i)) {
1614
+ if (startsWithTag(tokens, i)) {
1596
1615
  let commaToken = gen(',', ',', token);
1597
1616
  let arrowToken = gen('->', '->', token);
1598
1617
  arrowToken.newLine = true;
@@ -1602,13 +1621,23 @@ export class Lexer {
1602
1621
  }
1603
1622
 
1604
1623
  if (isTemplateElement) {
1605
- let callStartToken = gen('CALL_START', '(', token);
1606
- let arrowToken = gen('->', '->', token);
1607
- arrowToken.newLine = true;
1608
-
1609
- tokens.splice(i + 1, 0, callStartToken, arrowToken);
1610
- pendingCallEnds.push(currentIndent + 1);
1611
- return 3;
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
+ }
1612
1641
  }
1613
1642
  }
1614
1643