rip-lang 3.16.0 → 3.16.2

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.
Files changed (57) hide show
  1. package/README.md +3 -4
  2. package/bin/rip +201 -18
  3. package/bin/rip-schema +175 -0
  4. package/docs/AGENTS.md +1 -1
  5. package/docs/RIP-APP.md +200 -19
  6. package/docs/RIP-DUCKDB.md +64 -1
  7. package/docs/RIP-INTRO.md +4 -4
  8. package/docs/RIP-LANG.md +36 -38
  9. package/docs/RIP-SCHEMA.md +1204 -364
  10. package/docs/RIP-TYPES.md +74 -103
  11. package/docs/demo/README.md +4 -3
  12. package/docs/dist/rip.js +4195 -966
  13. package/docs/dist/rip.min.js +1161 -284
  14. package/docs/dist/rip.min.js.br +0 -0
  15. package/docs/example/index.json +7 -7
  16. package/docs/example/index.json.br +0 -0
  17. package/docs/extensions/vscode/print/print-1.0.14.vsix +0 -0
  18. package/docs/extensions/vscode/print/print-latest.vsix +0 -0
  19. package/docs/extensions/vscode/rip/index.html +2 -1
  20. package/docs/extensions/vscode/rip/rip-0.5.15.vsix +0 -0
  21. package/docs/extensions/vscode/rip/rip-latest.vsix +0 -0
  22. package/docs/extensions/vscode/rip/vscode-rip-0.6.0.vsix +0 -0
  23. package/docs/index.html +1 -1
  24. package/docs/ui/bundle.json +55 -55
  25. package/docs/ui/bundle.json.br +0 -0
  26. package/docs/ui/hljs-rip.js +1 -1
  27. package/docs/ui/index.html +1 -1
  28. package/package.json +15 -7
  29. package/rip-loader.js +59 -2
  30. package/src/AGENTS.md +43 -12
  31. package/src/browser.js +52 -11
  32. package/src/compiler.js +538 -80
  33. package/src/components.js +488 -48
  34. package/src/dts.js +80 -50
  35. package/src/grammar/README.md +29 -170
  36. package/src/grammar/grammar.rip +17 -12
  37. package/src/grammar/solar.rip +4 -17
  38. package/src/lexer.js +82 -32
  39. package/src/parser.js +229 -229
  40. package/src/schema/dts.js +328 -54
  41. package/src/schema/loader-server.js +2 -1
  42. package/src/schema/runtime-browser-stubs.js +20 -9
  43. package/src/schema/runtime-ddl.js +161 -44
  44. package/src/schema/runtime-migrate.js +681 -0
  45. package/src/schema/runtime-orm.js +698 -54
  46. package/src/schema/runtime-validate.js +808 -24
  47. package/src/schema/runtime.generated.js +2395 -135
  48. package/src/schema/schema.js +1054 -94
  49. package/src/typecheck.js +1610 -127
  50. package/src/types.js +90 -6
  51. package/src/grammar/lunar.rip +0 -2412
  52. /package/docs/demo/{components → routes}/_layout.rip +0 -0
  53. /package/docs/demo/{components → routes}/about.rip +0 -0
  54. /package/docs/demo/{components → routes}/card.rip +0 -0
  55. /package/docs/demo/{components → routes}/counter.rip +0 -0
  56. /package/docs/demo/{components → routes}/index.rip +0 -0
  57. /package/docs/demo/{components → routes}/todos.rip +0 -0
@@ -10,7 +10,6 @@
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'
14
13
 
15
14
  VERSION = '1.5.0'
16
15
 
@@ -90,11 +89,9 @@ class Generator
90
89
  # Build parser
91
90
  @timing '💥 Total time', =>
92
91
  @timing 'processGrammar' , => @processGrammar grammar # Process grammar rules
93
- unless @options.rd
94
- @timing 'buildLRAutomaton' , => @buildLRAutomaton() # Build LR(0) automaton
92
+ @timing 'buildLRAutomaton' , => @buildLRAutomaton() # Build LR(0) automaton
95
93
  @timing 'processLookaheads', => @processLookaheads() # Compute FIRST/FOLLOW and assign lookaheads
96
- unless @options.rd
97
- @timing 'buildParseTable' , => @buildParseTable() # Build parse table with default actions
94
+ @timing 'buildParseTable' , => @buildParseTable() # Build parse table with default actions
98
95
 
99
96
  # ============================================================================
100
97
  # Helper Functions
@@ -656,8 +653,6 @@ class Generator
656
653
  # Decoder reconstructs [{key: val}, ...] at runtime
657
654
  "(()=>{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})()"
658
655
 
659
- # Recursive descent generation — installed by Lunar (see lunar.rip)
660
-
661
656
  # ============================================================================
662
657
  # Runtime Parser
663
658
  # ============================================================================
@@ -772,8 +767,6 @@ class Generator
772
767
  # Exports
773
768
  # ==============================================================================
774
769
 
775
- installLunar Generator
776
-
777
770
  export { Generator }
778
771
 
779
772
  # ==============================================================================
@@ -805,7 +798,6 @@ if isRunAsScript
805
798
  -v, --version Show version
806
799
  -i, --info Show grammar information
807
800
  -s, --sexpr Show grammar as s-expression
808
- -r, --rd Generate recursive descent parser (parser-rd.js)
809
801
  -c, --conflicts Show conflict details (use with --info)
810
802
  -o, --output <file> Output file (default: parser.js)
811
803
 
@@ -843,7 +835,7 @@ if isRunAsScript
843
835
  console.log " Resolution: #{conflict.resolution} (by default)"
844
836
 
845
837
  # Parse command line
846
- options = {help: false, version: false, info: false, sexpr: false, conflicts: false, rd: false, output: 'parser.js'}
838
+ options = {help: false, version: false, info: false, sexpr: false, conflicts: false, output: 'parser.js'}
847
839
  grammarFile = null
848
840
  i = 0
849
841
 
@@ -854,7 +846,6 @@ if isRunAsScript
854
846
  when '-v', '--version' then options.version = true
855
847
  when '-i', '--info' then options.info = true
856
848
  when '-s', '--sexpr' then options.sexpr = true
857
- when '-r', '--rd' then options.rd = true
858
849
  when '-c', '--conflicts' then options.conflicts = true
859
850
  when '-o', '--output' then options.output = process.argv[++i + 2]
860
851
  else grammarFile = arg unless arg.startsWith('-')
@@ -915,11 +906,7 @@ if isRunAsScript
915
906
  if options.info
916
907
  showStats generator
917
908
  else
918
- if options.rd
919
- options.output = 'parser-rd.js' if options.output is 'parser.js'
920
- parserCode = generator.generateRD()
921
- else
922
- parserCode = generator.generate()
909
+ parserCode = generator.generate()
923
910
  fs.writeFileSync options.output, parserCode
924
911
  console.log "\nParser generated: #{options.output}"
925
912
 
package/src/lexer.js CHANGED
@@ -7,7 +7,7 @@
7
7
  //
8
8
  // Design principles:
9
9
  // - Every token carries .pre (whitespace count before it)
10
- // - Every token carries .data (metadata: await, predicate, quote, etc.)
10
+ // - Every token carries .data (metadata: bang, optional, quote, etc.)
11
11
  // - Every token carries .loc (location: row, col, len)
12
12
  // - Indentation is derived from .pre, not tracked during lexing
13
13
  // - Token categories use Sets for O(1) membership tests
@@ -23,8 +23,8 @@
23
23
  // token.newLine — true if preceded by a newline
24
24
  //
25
25
  // Identifier suffixes:
26
- // ! — dammit operator: fetch!() → await fetch()
27
- // ? — predicate: empty? → isEmpty (returns boolean convention)
26
+ // ! — bang: fetch!() → await fetch() (call site) | foo! = -> → void (definition)
27
+ // ? — optional: empty? → (empty != null) (existence check / optional marker)
28
28
  //
29
29
  // The 9 tokenizer methods (in priority order):
30
30
  // 1. identifier — variables, keywords, properties, ! and ? suffixes
@@ -129,7 +129,7 @@ let IMPLICIT_UNSPACED_CALL = new Set(['+', '-']);
129
129
  // Tokens that end an implicit call
130
130
  let IMPLICIT_END = new Set([
131
131
  'POST_IF', 'POST_UNLESS', 'FOR', 'WHILE', 'UNTIL',
132
- 'WHEN', 'BY', 'LOOP', 'TERMINATOR', '||', '&&', 'PIPE',
132
+ 'WHEN', 'BY', 'LOOP', 'TERMINATOR', '||', '&&',
133
133
  ]);
134
134
 
135
135
  // Tokens that trigger implicit comma insertion before arrows
@@ -246,12 +246,12 @@ let UNARY_MATH = new Set(['!', '~']);
246
246
  // Regex Patterns
247
247
  // ==========================================================================
248
248
 
249
- // Identifier: word chars + optional trailing ! (await) or ? (predicate)
249
+ // Identifier: word chars + optional trailing ! (bang) or ? (optional)
250
250
  // The ? suffix is only captured when NOT followed by . ? ! [ ( to avoid
251
251
  // conflict with ?. (optional chaining), ?? (nullish), ?! (presence), ?.( and ?.[
252
252
  let IDENTIFIER_RE = /^(?!\d)((?:(?!\s)[$\w\x7f-\uffff])+(?:!|[?](?![.?![(]))?)([^\n\S]*:(?![=:]))?/;
253
253
  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;
254
- let OPERATOR_RE = /^(?:<=>|::|\*>|[-=]>|~>|~=|:=|=!|===|!==|\?\!|\?\?|=~|\|>|[-+*\/%<>&|^!?=]=|>>>=?|([-+:])\1|([&|<>*\/%])\2=?|\?\.?|\.{2,3})/;
254
+ let OPERATOR_RE = /^(?:<=>|<~|::|\*>|[-=]>|~>|~=|:=|=!|===|!==|\?\!|\?\?|=~|[-+*\/%<>&|^!?=]=|>>>=?|([-+:])\1|([&|<>*\/%])\2=?|\?\.?|\.{2,3})/;
255
255
  let WHITESPACE_RE = /^[^\n\S]+/;
256
256
  let NEWLINE_RE = /^(?:\n[^\n\S]*)+/;
257
257
  let COMMENT_RE = /^(\s*)###([^#][\s\S]*?)(?:###([^\n\S]*)|###$)|^((?:\s*#(?!##[^#]).*)+)/;
@@ -519,8 +519,10 @@ export class Lexer {
519
519
  // Handles: variables, keywords, properties, aliases
520
520
  //
521
521
  // Suffix operators on identifiers:
522
- // ! → dammit operator (await): fetch!() await fetch()
523
- // ? → predicate (boolean): empty? isEmpty
522
+ // ! → bang: a neutral "trailing !" flag. Resolved by context downstream —
523
+ // dammit/await at a call site (fetch!() await fetch()), or the void
524
+ // marker at a function definition (foo! = -> / def foo! → no return).
525
+ // ? → optional (existence): empty? → (empty != null)
524
526
  //
525
527
  // The ? suffix is captured by IDENTIFIER_RE only when NOT followed by
526
528
  // . ? [ ( — so x?.y (optional chaining) and x?? (nullish coalescing)
@@ -655,16 +657,20 @@ export class Lexer {
655
657
  }
656
658
  }
657
659
 
658
- // --- Dammit operator: trailing ! → await ---
660
+ // --- Bang: trailing ! → context-resolved (await at call site / void at def) ---
659
661
  if (id.length > 1 && id.endsWith('!')) {
660
- data.await = true;
662
+ data.bang = true;
661
663
  id = id.slice(0, -1);
662
664
  }
663
665
 
664
- // --- Predicate operator: trailing ? → boolean convention ---
665
- // empty? isEmpty, active?isActive, valid? → isValid
666
+ // --- Optional marker: trailing ? ---
667
+ // Identifier-position: existence check (`empty?``(empty != null)`).
668
+ // Property-name position: optional prop / type field (`@label?:: T`,
669
+ // `{ x?:: T }`, `def f(x?:: T)`). The flag is consumed by the type/
670
+ // component/schema emitters; the runtime semantics for `name?` as an
671
+ // existence check are unchanged.
666
672
  if (id.length > 1 && id.endsWith('?')) {
667
- data.predicate = true;
673
+ data.optional = true;
668
674
  id = id.slice(0, -1);
669
675
  }
670
676
 
@@ -742,18 +748,6 @@ export class Lexer {
742
748
  }
743
749
  if (/^\s+#[a-zA-Z_]/.test(this.chunk)) return 0; // let lineToken handle indentation first
744
750
  }
745
- // Schema field modifier: `#` adjacent (unspaced) to an identifier acts
746
- // as the unique-marker inside schema bodies (e.g. `email!# email`).
747
- // Return 0 so literalToken emits a standalone `#` token; rewriteSchema
748
- // absorbs it. Outside schema bodies the `#` token is harmless because
749
- // nothing else in the grammar accepts `IDENTIFIER #` without a space.
750
- if (this.chunk[0] === '#') {
751
- let prev = this.prev();
752
- if (prev && !prev.spaced && !prev.newLine &&
753
- (prev[0] === 'IDENTIFIER' || prev[0] === 'PROPERTY')) {
754
- return 0;
755
- }
756
- }
757
751
  let match = COMMENT_RE.exec(this.chunk);
758
752
  if (!match) return 0;
759
753
  return match[0].length;
@@ -1382,8 +1376,6 @@ export class Lexer {
1382
1376
  this.inTypeAnnotation = false;
1383
1377
  tag = 'TERMINATOR';
1384
1378
  }
1385
- // Pipe operator
1386
- else if (val === '|>') tag = 'PIPE';
1387
1379
  // Type operators
1388
1380
  else if (val === '::' && /^[a-zA-Z_$]/.test(this.chunk[2] || '')) {
1389
1381
  // Prototype access: String::trim → String.prototype.trim
@@ -1397,6 +1389,7 @@ export class Lexer {
1397
1389
  else if (val === '~=') tag = 'COMPUTED_ASSIGN';
1398
1390
  else if (val === ':=') tag = 'REACTIVE_ASSIGN';
1399
1391
  else if (val === '<=>') tag = 'BIND';
1392
+ else if (val === '<~') { tag = 'GATE'; this.inTypeAnnotation = false; }
1400
1393
  else if (val === '~>') { tag = 'EFFECT'; this.inTypeAnnotation = false; }
1401
1394
  else if (val === '=!') { tag = 'READONLY_ASSIGN'; this.inTypeAnnotation = false; }
1402
1395
  // Merge assignment: *>config = {a: 1} → Object.assign(config, {a: 1})
@@ -1519,9 +1512,42 @@ export class Lexer {
1519
1512
 
1520
1513
  // Walk back to tag parameters for arrow functions
1521
1514
  tagParameters() {
1522
- if (this.prevTag() !== ')') return this.tagDoIife();
1515
+ let closeIdx = this.tokens.length - 1;
1516
+ if (this.tokens[closeIdx]?.[0] !== ')') {
1517
+ // Maybe a return-type annotation sits between `)` and the arrow:
1518
+ // `(x:: T):: R ->`. Scan backward over the type expression (balanced
1519
+ // brackets) looking for the trailing `TYPE_ANNOTATION` whose previous
1520
+ // token is `)`. If found, treat that `)` as the param-list close.
1521
+ let n = this.tokens.length;
1522
+ let depth = 0;
1523
+ let j = n - 1;
1524
+ let found = -1;
1525
+ while (j >= 0) {
1526
+ let tk = this.tokens[j];
1527
+ let tg = tk[0];
1528
+ if (tg === ')' || tg === ']' || tg === '}' || tg === 'CALL_END' ||
1529
+ tg === 'PARAM_END' || tg === 'INDEX_END' ||
1530
+ (tg === 'COMPARE' && tk[1] === '>')) {
1531
+ depth++;
1532
+ } else if (tg === '(' || tg === '[' || tg === '{' || tg === 'CALL_START' ||
1533
+ tg === 'PARAM_START' || tg === 'INDEX_START' ||
1534
+ (tg === 'COMPARE' && tk[1] === '<')) {
1535
+ depth--;
1536
+ } else if (depth === 0) {
1537
+ if (tg === 'TYPE_ANNOTATION') {
1538
+ if (j > 0 && this.tokens[j - 1][0] === ')') found = j - 1;
1539
+ break;
1540
+ }
1541
+ if (tg === 'TERMINATOR' || tg === 'INDENT' || tg === 'OUTDENT' ||
1542
+ tg === '=' || tg === '->' || tg === '=>') break;
1543
+ }
1544
+ j--;
1545
+ }
1546
+ if (found < 0) return this.tagDoIife();
1547
+ closeIdx = found;
1548
+ }
1523
1549
 
1524
- let i = this.tokens.length - 1;
1550
+ let i = closeIdx;
1525
1551
  let stack = [];
1526
1552
  this.tokens[i][0] = 'PARAM_END';
1527
1553
 
@@ -1536,7 +1562,7 @@ export class Lexer {
1536
1562
  tok[0] = 'PARAM_START';
1537
1563
  return this.tagDoIife(i - 1);
1538
1564
  } else {
1539
- this.tokens[this.tokens.length - 1][0] = 'CALL_END';
1565
+ this.tokens[closeIdx][0] = 'CALL_END';
1540
1566
  return;
1541
1567
  }
1542
1568
  }
@@ -1562,10 +1588,14 @@ export class Lexer {
1562
1588
  this.closeMergeAssignments();
1563
1589
  this.closeOpenCalls();
1564
1590
  this.closeOpenIndexes();
1591
+ // rewriteTypes must run BEFORE normalizeLines: otherwise a type-arrow
1592
+ // `=>` inside `(...) => T` gets treated as a single-liner function and
1593
+ // wrapped in spurious INDENT/OUTDENT, which then derails the type
1594
+ // collector (it slurps the whole assignment body as part of the type).
1595
+ this.rewriteTypes();
1565
1596
  this.normalizeLines();
1566
1597
  this.rewriteRender?.();
1567
1598
  this.rewriteSchema?.();
1568
- this.rewriteTypes();
1569
1599
  this.tagPostfixConditionals();
1570
1600
  this.rewriteTaggedTemplates();
1571
1601
  this.addImplicitBracesAndParens();
@@ -2020,12 +2050,32 @@ export class Lexer {
2020
2050
  } else if (t && t !== 'TERMINATOR' && t !== 'OUTDENT' && t !== ',') j++;
2021
2051
  logicalKeep = tokens[j]?.[0] === ',';
2022
2052
  }
2053
+ // A postfix conditional on the FIRST property line of a multiline
2054
+ // implicit object must bind to the property's value, not close the
2055
+ // object — otherwise `a: 1 if c` followed by more properties splits
2056
+ // the object into separate arguments. Later property lines already
2057
+ // bind to the value (the sameLine flag is false by then), so only
2058
+ // first-line conditionals need the lookahead: keep the object open
2059
+ // when the property list continues after this line.
2060
+ let objectContinues = (j) => {
2061
+ for (let d = 0; j < tokens.length; j++) {
2062
+ let t = tokens[j][0];
2063
+ if (t === '(' || t === '[' || t === '{' || t === 'CALL_START' || t === 'INDEX_START' || t === 'INDENT') d++;
2064
+ else if (t === ')' || t === ']' || t === '}' || t === 'CALL_END' || t === 'INDEX_END' || t === 'OUTDENT') {
2065
+ if (d === 0) return false;
2066
+ d--;
2067
+ }
2068
+ else if (t === 'TERMINATOR' && d === 0) return this.looksObjectish(j + 1);
2069
+ }
2070
+ return false;
2071
+ };
2023
2072
  if ((IMPLICIT_END.has(tag) && !logicalKeep) || (CALL_CLOSERS.has(tag) && newLine)) {
2024
2073
  while (isImplicit(stackTop())) {
2025
2074
  let [stackTag, , {sameLine, startsLine}] = stackTop();
2026
2075
  if (inImplicitCall() && prevTag !== ',') {
2027
2076
  endImplicitCall();
2028
- } else if (inImplicitObject() && !isLogicalOp && sameLine && tag !== 'TERMINATOR' && prevTag !== ':') {
2077
+ } else if (inImplicitObject() && !isLogicalOp && sameLine && tag !== 'TERMINATOR' && prevTag !== ':' &&
2078
+ !((tag === 'POST_IF' || tag === 'POST_UNLESS') && objectContinues(i + 1))) {
2029
2079
  endImplicitObject();
2030
2080
  } else if (inImplicitObject() && tag === 'TERMINATOR' && prevTag !== ',' && !(startsLine && this.looksObjectish(i + 1))) {
2031
2081
  endImplicitObject();