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.
- package/CHANGELOG.md +133 -0
- package/README.md +14 -3
- package/docs/dist/rip.browser.js +25 -17
- package/docs/dist/rip.browser.min.js +5 -5
- package/docs/dist/rip.browser.min.js.br +0 -0
- package/package.json +2 -2
- package/src/codegen.js +13 -4
- package/src/grammar/grammar.rip +1 -0
- package/src/grammar/solar.rip +53 -12
- package/src/lexer.js +2 -1
- package/src/parser.js +12 -11
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rip-lang",
|
|
3
|
-
"version": "1.3.
|
|
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
|
|
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
|
-
//
|
|
3149
|
-
const
|
|
3150
|
-
const
|
|
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 (!
|
|
3161
|
+
if (!isBlockStatement || !generated.endsWith('}')) {
|
|
3153
3162
|
return generated + ';';
|
|
3154
3163
|
}
|
|
3155
3164
|
}
|
package/src/grammar/grammar.rip
CHANGED
|
@@ -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)
|
package/src/grammar/solar.rip
CHANGED
|
@@ -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.
|
|
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
|
-
|
|
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'
|
|
889
|
-
when '-v', '--version'
|
|
890
|
-
when '-i', '--info'
|
|
891
|
-
when '-s', '--sexpr'
|
|
892
|
-
when '-
|
|
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
|
|
897
|
-
if options.version
|
|
898
|
-
if not grammarFile
|
|
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
|
-
|
|
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 = /^(?:[-=]
|
|
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
|