rip-lang 1.3.0 → 1.3.3

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.
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rip-lang",
3
- "version": "1.3.0",
3
+ "version": "1.3.3",
4
4
  "description": "A lightweight scripting language that compiles to modern JavaScript",
5
5
  "type": "module",
6
6
  "main": "src/compiler.js",
@@ -34,7 +34,7 @@
34
34
  "test": "bun test/runner.js",
35
35
  "version": "./bin/rip -v",
36
36
  "pack": "npm pack --dry-run",
37
- "prepublishOnly": "bun run test && bun run browser"
37
+ "prepublishOnly": "bun run test"
38
38
  },
39
39
  "keywords": [
40
40
  "compiler",
package/src/codegen.js CHANGED
@@ -413,6 +413,7 @@ export class CodeGenerator {
413
413
  case '&&':
414
414
  case '||':
415
415
  case '??':
416
+ case '!?':
416
417
  case '&':
417
418
  case '|':
418
419
  case '^':
@@ -430,6 +431,14 @@ export class CodeGenerator {
430
431
  // Binary operation (all operators)
431
432
  const [left, right] = rest;
432
433
 
434
+ // Special case: Otherwise operator (!?) - undefined-only coalescing
435
+ // Pattern: a !? b → (a !== undefined ? a : b)
436
+ if (head === '!?') {
437
+ const leftCode = this.generate(left, 'value');
438
+ const rightCode = this.generate(right, 'value');
439
+ return `(${leftCode} !== undefined ? ${leftCode} : ${rightCode})`;
440
+ }
441
+
433
442
  // Always use strict equality (CoffeeScript compatibility)
434
443
  // == → ===, != → !==, === → ===, !== → !==
435
444
  let op = head;
@@ -3145,11 +3154,11 @@ export class CodeGenerator {
3145
3154
  if (generated && !generated.endsWith(';')) {
3146
3155
  // Check if this statement type needs a semicolon
3147
3156
  const head = Array.isArray(stmt) ? stmt[0] : null;
3148
- // Control flow statements (if, for, while, switch, try) ending with } don't need semicolons
3149
- const controlFlowStatements = ['if', 'unless', 'for-in', 'for-of', 'while', 'until', 'loop', 'switch', 'try'];
3150
- const isControlFlow = controlFlowStatements.includes(head);
3157
+ // Block statements ending with } don't need semicolons
3158
+ const blockStatements = ['def', 'class', 'if', 'unless', 'for-in', 'for-of', 'for-from', 'while', 'until', 'loop', 'switch', 'try'];
3159
+ const isBlockStatement = blockStatements.includes(head);
3151
3160
 
3152
- if (!isControlFlow || !generated.endsWith('}')) {
3161
+ if (!isBlockStatement || !generated.endsWith('}')) {
3153
3162
  return generated + ';';
3154
3163
  }
3155
3164
  }
@@ -751,6 +751,7 @@ grammar =
751
751
  o 'Expression && Expression' , '["&&", 1, 3]'
752
752
  o 'Expression || Expression' , '["||", 1, 3]'
753
753
  o 'Expression ?? Expression' , '["??", 1, 3]' # Nullish coalescing
754
+ o 'Expression !? Expression' , '["!?", 1, 3]' # Otherwise operator (undefined-only coalescing)
754
755
  o 'Expression RELATION Expression', '[2, 1, 3]' # in, of, instanceof
755
756
 
756
757
  # Ternary operator (uses SPACE? token = ? with space before it)
@@ -14,7 +14,7 @@ import fs from 'fs'
14
14
  import path from 'path'
15
15
  import { fileURLToPath, pathToFileURL } from 'url'
16
16
 
17
- VERSION = '1.3.0-rip'
17
+ VERSION = '1.3.1'
18
18
 
19
19
  # Token: A terminal symbol that cannot be broken down further
20
20
  class Token
@@ -84,6 +84,7 @@ class Generator
84
84
  @rules = []
85
85
  @operators = {}
86
86
  @conflicts = 0
87
+ @conflictDetails = [] # Track conflict details for debugging
87
88
 
88
89
  # Initialize symbol table with special symbols
89
90
  @symbolTable = new Map
@@ -561,7 +562,34 @@ class Generator
561
562
  solution = @_resolveConflict item.rule, op, [REDUCE, item.rule.id], which
562
563
 
563
564
  if solution.bydefault
564
- @conflicts++
565
+ # Categorize conflict type
566
+ isEmpty = item.rule.symbols.length is 0 or (item.rule.symbols.length is 1 and item.rule.symbols[0] is '')
567
+ isPassthrough = item.rule.symbols.length is 1 and types[item.rule.symbols[0]] # Single nonterminal
568
+ hasPrecedence = op and item.rule.precedence > 0
569
+ isReduceReduce = which[0] is REDUCE
570
+
571
+ # Determine category
572
+ category = 'empty-optional' if isEmpty
573
+ category = 'passthrough' if !isEmpty and isPassthrough
574
+ category = 'precedence' if !isEmpty and !isPassthrough and hasPrecedence
575
+ category = 'reduce-reduce' if !isEmpty and !isPassthrough and !hasPrecedence and isReduceReduce
576
+ category = 'ambiguous' if !isEmpty and !isPassthrough and !hasPrecedence and !isReduceReduce
577
+
578
+ # Only count and track problematic conflicts (exclude benign ones)
579
+ if category is 'reduce-reduce' or category is 'ambiguous'
580
+ @conflicts++
581
+ @conflictDetails.push {
582
+ state: k,
583
+ lookahead: stackSymbol,
584
+ lookaheadName: @tokenNames[@symbolIds[stackSymbol]] or stackSymbol,
585
+ rule: item.rule.id,
586
+ ruleType: item.rule.type,
587
+ ruleSymbols: item.rule.symbols.join(' '),
588
+ shift: if which[0] is SHIFT then which[1] else null,
589
+ reduce: item.rule.id,
590
+ resolution: if which[0] is SHIFT then 'shift' else 'reduce',
591
+ category: category
592
+ }
565
593
  else
566
594
  action = solution.action
567
595
  else
@@ -851,11 +879,13 @@ if isRunAsScript
851
879
  -v, --version Show version
852
880
  -i, --info Show grammar information
853
881
  -s, --sexpr Show grammar as s-expression
882
+ -c, --conflicts Show conflict details (use with --info)
854
883
  -o, --output <file> Output file (default: parser.js)
855
884
 
856
885
  Examples:
857
886
  solar grammar.js
858
887
  solar --info grammar.js
888
+ solar --info --conflicts grammar.js
859
889
  solar --sexpr grammar.js
860
890
  solar -o parser.js grammar.js
861
891
  """
@@ -877,25 +907,35 @@ if isRunAsScript
877
907
  • Conflicts: #{conflicts}
878
908
  """
879
909
 
910
+ # Show conflict details if requested
911
+ if options.conflicts and generator.conflictDetails?.length
912
+ console.log "\n🔧 Conflict Details (first 30):"
913
+ for conflict, i in generator.conflictDetails.slice(0, 30)
914
+ console.log "\n #{i + 1}. State #{conflict.state}, lookahead '#{conflict.lookaheadName}':"
915
+ console.log " Rule #{conflict.rule}: #{conflict.ruleType} → #{conflict.ruleSymbols}"
916
+ console.log " Resolution: #{conflict.resolution} (by default)"
917
+
880
918
  # Parse command line
881
- options = {help: false, version: false, info: false, sexpr: false, output: 'parser.js'}
919
+ options = {help: false, version: false, info: false, sexpr: false, conflicts: false, output: 'parser.js'}
882
920
  grammarFile = null
883
921
  i = 0
884
922
 
885
923
  while i < process.argv.length - 2
886
924
  arg = process.argv[i + 2]
887
925
  switch arg
888
- when '-h', '--help' then options.help = true
889
- when '-v', '--version' then options.version = true
890
- when '-i', '--info' then options.info = true
891
- when '-s', '--sexpr' then options.sexpr = true
892
- when '-o', '--output' then options.output = process.argv[++i + 2]
926
+ when '-h', '--help' then options.help = true
927
+ when '-v', '--version' then options.version = true
928
+ when '-i', '--info' then options.info = true
929
+ when '-s', '--sexpr' then options.sexpr = true
930
+ when '-c', '--conflicts' then options.conflicts = true
931
+ when '-o', '--output' then options.output = process.argv[++i + 2]
893
932
  else grammarFile = arg unless arg.startsWith('-')
894
933
  i++
895
934
 
896
- if options.help then showHelp() ; process.exit 0
897
- if options.version then showVersion() ; process.exit 0
898
- if not grammarFile then showHelp() ; process.exit 0
935
+ if options.help then showHelp() ; process.exit 0
936
+ if options.version then showVersion() ; process.exit 0
937
+ if not grammarFile then showHelp() ; process.exit 0
938
+ if options.conflicts then options.info = true # --conflicts implies --info
899
939
 
900
940
  try
901
941
  unless fs.existsSync grammarFile
@@ -926,7 +966,8 @@ if isRunAsScript
926
966
  action ?= 1 # Default to 1 if not provided
927
967
  actionStr = if typeof action is 'string' then action else JSON.stringify(action)
928
968
  optsStr = if opts then " #{JSON.stringify(opts)}" else ''
929
- parts.push " (#{pattern || ''} #{actionStr}#{optsStr})"
969
+ patternStr = if pattern is '' then '""' else pattern
970
+ parts.push " (#{patternStr} #{actionStr}#{optsStr})"
930
971
  parts.push " )"
931
972
  parts.push ' )'
932
973
 
package/src/lexer.js CHANGED
@@ -1756,8 +1756,9 @@ NUMBER = /^0b[01](?:_?[01])*n?|^0o[0-7](?:_?[0-7])*n?|^0x[\da-f](?:_?[\da-f])*n?
1756
1756
  // decimal without support for numeric literal separators for reference:
1757
1757
  // \d*\.?\d+ (?:e[+-]?\d+)?
1758
1758
 
1759
- OPERATOR = /^(?:[-=]>|===|!==|\?\?|=~|[-+*\/%<>&|^!?=]=|>>>=?|([-+:])\1|([&|<>*\/%])\2=?|\?(\.|::)|\.{2,3})/; // function
1759
+ OPERATOR = /^(?:[-=]>|===|!==|!\?|\?\?|=~|[-+*\/%<>&|^!?=]=|>>>=?|([-+:])\1|([&|<>*\/%])\2=?|\?(\.|::)|\.{2,3})/; // function
1760
1760
  // Added === and !== for explicit strict equality (compiles same as == and !=)
1761
+ // !? (otherwise operator) must come before ?? and before !=
1761
1762
  // ?? must come before single ? to match correctly
1762
1763
  // regex match operator
1763
1764
  // compound assign / compare / strict equality