rip-lang 3.3.0 → 3.4.0

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/README.md CHANGED
@@ -82,7 +82,7 @@ dog = Dog.new("Buddy") # Ruby-style constructor
82
82
  ```coffee
83
83
  "Hello, #{name}!" # CoffeeScript-style
84
84
  "Hello, ${name}!" # JavaScript-style
85
- "#{a} + #{b} = #{a + b}" # Expressions work in both
85
+ "#{a} + #{b} = #{a + b}" # Expressions work in both
86
86
  ```
87
87
 
88
88
  Both `#{}` and `${}` compile to JavaScript template literals. Use whichever you prefer.
package/bin/rip CHANGED
@@ -26,6 +26,7 @@ Usage:
26
26
  Options:
27
27
  -c, --compile Show compiled JavaScript output
28
28
  -d, --dts Generate .d.ts type declaration file
29
+ -m, --map Generate .js.map source map file
29
30
  -h, --help Show this help message
30
31
  -o, --output <file> Write JavaScript to file
31
32
  -q, --quiet Suppress headers
@@ -46,6 +47,7 @@ Examples:
46
47
  rip -s -c example.rip # Show s-expressions AND JavaScript
47
48
  rip -s -t -c example.rip # Show everything (full debug mode)
48
49
  rip -d example.rip # Generate example.d.ts
50
+ rip -m example.rip # Generate example.js.map
49
51
  rip -cd example.rip # Compile JS and generate .d.ts
50
52
  rip -q -c example.rip # Just the JS, no headers (for piping)
51
53
  rip -w # Launch browser REPL (auto-opens)
@@ -146,13 +148,16 @@ async function main() {
146
148
  const showSExpr = ripOptions.includes('-s') || ripOptions.includes('--sexpr');
147
149
  const showCompiled = ripOptions.includes('-c') || ripOptions.includes('--compile');
148
150
  const generateDts = ripOptions.includes('-d') || ripOptions.includes('--dts');
151
+ const generateMap = ripOptions.includes('-m') || ripOptions.includes('--map');
149
152
  const quiet = ripOptions.includes('-q') || ripOptions.includes('--quiet');
150
153
 
151
154
  const options = {
152
155
  showTokens,
153
156
  showSExpr,
154
157
  quiet,
155
- types: generateDts ? 'emit' : undefined
158
+ types: generateDts ? 'emit' : undefined,
159
+ sourceMap: generateMap ? true : undefined,
160
+ filename: null, // set below after determining input file
156
161
  };
157
162
 
158
163
  // Find input file and output file from ripOptions only
@@ -167,7 +172,7 @@ async function main() {
167
172
  }
168
173
 
169
174
  // If .rip file without compile flags → execute instead of compile
170
- const hasCompileFlag = showCompiled || showTokens || showSExpr || generateDts || outputFile;
175
+ const hasCompileFlag = showCompiled || showTokens || showSExpr || generateDts || generateMap || outputFile;
171
176
  if (inputFile && inputFile.endsWith('.rip') && !hasCompileFlag) {
172
177
  // Check if file exists
173
178
  if (!existsSync(inputFile)) {
@@ -238,6 +243,11 @@ async function main() {
238
243
  source = readFileSync(inputFile, 'utf-8');
239
244
  }
240
245
 
246
+ // Set filename for source map generation
247
+ if (inputFile) {
248
+ options.filename = inputFile.replace(/\.rip$/, '');
249
+ }
250
+
241
251
  // Compile
242
252
  const compiler = new Compiler(options);
243
253
  const result = compiler.compile(source);
@@ -277,6 +287,23 @@ async function main() {
277
287
  console.log(result.dts);
278
288
  }
279
289
  }
290
+
291
+ // Write .js.map file
292
+ if (generateMap && result.map) {
293
+ if (inputFile) {
294
+ let mapFile = inputFile.replace(/\.rip$/, '.js.map');
295
+ writeFileSync(mapFile, result.map, 'utf-8');
296
+ if (!options.quiet) {
297
+ console.log(`Generated ${mapFile}`);
298
+ }
299
+ } else {
300
+ // stdin — print source map to stdout
301
+ if (!options.quiet) {
302
+ console.log(`// == Source map == //\n`);
303
+ }
304
+ console.log(result.map);
305
+ }
306
+ }
280
307
  } catch (error) {
281
308
  console.error('Compilation Error:', error.message);
282
309
  if (error.stack) {
@@ -2966,7 +2966,7 @@ var parserInstance = {
2966
2966
  return this.trace(str);
2967
2967
  else {
2968
2968
  line = (hash.line || 0) + 1;
2969
- col = hash.loc?.first_column || 0;
2969
+ col = hash.loc?.c || 0;
2970
2970
  token = hash.token ? ` (token: ${hash.token})` : "";
2971
2971
  text = hash.text ? ` near '${hash.text}'` : "";
2972
2972
  location = `line ${line}, column ${col}`;
@@ -2977,7 +2977,7 @@ var parserInstance = {
2977
2977
  }
2978
2978
  },
2979
2979
  parse(input) {
2980
- let EOF, TERROR, action, errStr, expected, len, lex, lexer, locFirst, locLast, locs, newState, p, parseTable, preErrorSymbol, r, ranges, recovering, rv, sharedState, state, stk, symbol, tokenLen, tokenLine, tokenLoc, tokenText, vals;
2980
+ let EOF, TERROR, action, errStr, expected, len, lex, lexer, loc, locs, newState, p, parseTable, preErrorSymbol, r, recovering, rv, sharedState, state, stk, symbol, tokenLen, tokenLine, tokenLoc, tokenText, vals;
2981
2981
  [stk, vals, locs] = [[0], [null], []];
2982
2982
  [parseTable, tokenText, tokenLine, tokenLen, recovering] = [this.parseTable, "", 0, 0, 0];
2983
2983
  [TERROR, EOF] = [2, 1];
@@ -2994,7 +2994,6 @@ var parserInstance = {
2994
2994
  lexer.loc = {};
2995
2995
  tokenLoc = lexer.loc;
2996
2996
  locs.push(tokenLoc);
2997
- ranges = lexer.options?.ranges;
2998
2997
  this.parseError = typeof sharedState.ctx.parseError === "function" ? sharedState.ctx.parseError : Object.getPrototypeOf(this).parseError;
2999
2998
  lex = () => {
3000
2999
  let token;
@@ -3049,13 +3048,13 @@ Expecting ${expected.join(", ")}, got '${this.tokenNames[symbol] || symbol}'`;
3049
3048
  } else if (action < 0) {
3050
3049
  len = this.ruleTable[-action * 2 + 1];
3051
3050
  rv.$ = vals[vals.length - len];
3052
- [locFirst, locLast] = [locs[locs.length - (len || 1)], locs[locs.length - 1]];
3053
- rv._$ = { first_line: locFirst.first_line, last_line: locLast.last_line, first_column: locFirst.first_column, last_column: locLast.last_column };
3054
- if (ranges)
3055
- rv._$.range = [locFirst.range[0], locLast.range[1]];
3051
+ loc = locs[locs.length - (len || 1)];
3052
+ rv._$ = { r: loc.r, c: loc.c };
3056
3053
  r = this.ruleActions.call(rv, -action, vals, locs, sharedState.ctx);
3057
3054
  if (r != null)
3058
3055
  rv.$ = r;
3056
+ if (Array.isArray(rv.$))
3057
+ rv.$.loc = rv._$;
3059
3058
  if (len) {
3060
3059
  stk.length -= len * 2;
3061
3060
  vals.length -= len;
@@ -3984,6 +3983,92 @@ if (typeof globalThis !== 'undefined') {
3984
3983
  };
3985
3984
  }
3986
3985
 
3986
+ // src/sourcemap.js
3987
+ var B64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
3988
+ function vlqEncode(value) {
3989
+ let result = "";
3990
+ let vlq = value < 0 ? -value << 1 | 1 : value << 1;
3991
+ do {
3992
+ let digit = vlq & 31;
3993
+ vlq >>>= 5;
3994
+ if (vlq > 0)
3995
+ digit |= 32;
3996
+ result += B64[digit];
3997
+ } while (vlq > 0);
3998
+ return result;
3999
+ }
4000
+
4001
+ class SourceMapGenerator {
4002
+ constructor(file, source, sourceContent = null) {
4003
+ this.file = file;
4004
+ this.source = source;
4005
+ this.sourceContent = sourceContent;
4006
+ this.names = [];
4007
+ this.nameIndex = new Map;
4008
+ this.lines = [];
4009
+ this.mappings = [];
4010
+ this.prevGenCol = 0;
4011
+ this.prevOrigLine = 0;
4012
+ this.prevOrigCol = 0;
4013
+ this.prevNameIdx = 0;
4014
+ this.currentLine = -1;
4015
+ }
4016
+ ensureLine(line) {
4017
+ while (this.lines.length <= line)
4018
+ this.lines.push([]);
4019
+ }
4020
+ addName(name) {
4021
+ if (this.nameIndex.has(name))
4022
+ return this.nameIndex.get(name);
4023
+ let idx = this.names.length;
4024
+ this.names.push(name);
4025
+ this.nameIndex.set(name, idx);
4026
+ return idx;
4027
+ }
4028
+ addMapping(genLine, genCol, origLine, origCol, name) {
4029
+ this.ensureLine(genLine);
4030
+ if (this.currentLine !== genLine) {
4031
+ this.prevGenCol = 0;
4032
+ this.currentLine = genLine;
4033
+ }
4034
+ if (origLine == null) {
4035
+ this.lines[genLine].push(vlqEncode(genCol - this.prevGenCol));
4036
+ this.prevGenCol = genCol;
4037
+ return;
4038
+ }
4039
+ this.mappings.push({ genLine, genCol, origLine, origCol });
4040
+ let segment = vlqEncode(genCol - this.prevGenCol);
4041
+ this.prevGenCol = genCol;
4042
+ segment += vlqEncode(0);
4043
+ segment += vlqEncode(origLine - this.prevOrigLine);
4044
+ this.prevOrigLine = origLine;
4045
+ segment += vlqEncode(origCol - this.prevOrigCol);
4046
+ this.prevOrigCol = origCol;
4047
+ if (name != null) {
4048
+ let idx = this.addName(name);
4049
+ segment += vlqEncode(idx - this.prevNameIdx);
4050
+ this.prevNameIdx = idx;
4051
+ }
4052
+ this.lines[genLine].push(segment);
4053
+ }
4054
+ toReverseMap() {
4055
+ let reverse = new Map;
4056
+ for (let m of this.mappings) {
4057
+ if (!reverse.has(m.origLine)) {
4058
+ reverse.set(m.origLine, { genLine: m.genLine, genCol: m.genCol });
4059
+ }
4060
+ }
4061
+ return reverse;
4062
+ }
4063
+ toJSON() {
4064
+ let mappings = this.lines.map((segs) => segs.join(",")).join(";");
4065
+ let map = { version: 3, file: this.file, sources: [this.source], names: this.names, mappings };
4066
+ if (this.sourceContent != null)
4067
+ map.sourcesContent = [this.sourceContent];
4068
+ return JSON.stringify(map);
4069
+ }
4070
+ }
4071
+
3987
4072
  // src/compiler.js
3988
4073
  var meta = (node, key) => node instanceof String ? node[key] : undefined;
3989
4074
  var str = (node) => node instanceof String ? node.valueOf() : node;
@@ -4267,6 +4352,7 @@ class CodeGenerator {
4267
4352
  this.indentString = " ";
4268
4353
  this.comprehensionDepth = 0;
4269
4354
  this.dataSection = options.dataSection;
4355
+ this.sourceMap = options.sourceMap || null;
4270
4356
  if (options.reactiveVars) {
4271
4357
  this.reactiveVars = new Set(options.reactiveVars);
4272
4358
  }
@@ -4276,7 +4362,56 @@ class CodeGenerator {
4276
4362
  this.functionVars = new Map;
4277
4363
  this.helpers = new Set;
4278
4364
  this.collectProgramVariables(sexpr);
4279
- return this.generate(sexpr);
4365
+ let code = this.generate(sexpr);
4366
+ if (this.sourceMap)
4367
+ this.buildMappings(code, sexpr);
4368
+ return code;
4369
+ }
4370
+ buildMappings(code, sexpr) {
4371
+ if (!sexpr || sexpr[0] !== "program")
4372
+ return;
4373
+ let locs = [];
4374
+ let collect = (node) => {
4375
+ if (!Array.isArray(node))
4376
+ return;
4377
+ let head = node[0];
4378
+ if (head === "program" || head === "block") {
4379
+ for (let i = 1;i < node.length; i++) {
4380
+ let child = node[i];
4381
+ if (Array.isArray(child) && child.loc)
4382
+ locs.push(child.loc);
4383
+ collect(child);
4384
+ }
4385
+ } else {
4386
+ for (let i = 1;i < node.length; i++)
4387
+ collect(node[i]);
4388
+ }
4389
+ };
4390
+ collect(sexpr);
4391
+ let lines = code.split(`
4392
+ `);
4393
+ let locIdx = 0;
4394
+ for (let outLine = 0;outLine < lines.length; outLine++) {
4395
+ let line = lines[outLine];
4396
+ let trimmed = line.trim();
4397
+ if (!trimmed || trimmed === "}" || trimmed === "});")
4398
+ continue;
4399
+ if (trimmed.startsWith("let ") || trimmed.startsWith("var "))
4400
+ continue;
4401
+ if (trimmed.startsWith("const slice") || trimmed.startsWith("const modulo") || trimmed.startsWith("const toSearchable"))
4402
+ continue;
4403
+ if (trimmed.startsWith("const {") && trimmed.includes("__"))
4404
+ continue;
4405
+ if (trimmed.startsWith("} else"))
4406
+ continue;
4407
+ if (trimmed.startsWith("//# source"))
4408
+ continue;
4409
+ if (locIdx < locs.length) {
4410
+ let indent = line.length - trimmed.length;
4411
+ this.sourceMap.addMapping(outLine, indent, locs[locIdx].r, locs[locIdx].c);
4412
+ locIdx++;
4413
+ }
4414
+ }
4280
4415
  }
4281
4416
  collectProgramVariables(sexpr) {
4282
4417
  if (!Array.isArray(sexpr))
@@ -7353,14 +7488,30 @@ class Compiler {
7353
7488
  console.log(formatSExpr(sexpr, 0, true));
7354
7489
  console.log();
7355
7490
  }
7491
+ let sourceMap = null;
7492
+ if (this.options.sourceMap) {
7493
+ let file = (this.options.filename || "output") + ".js";
7494
+ let sourceFile = this.options.filename || "input.rip";
7495
+ sourceMap = new SourceMapGenerator(file, sourceFile, source);
7496
+ }
7356
7497
  let generator = new CodeGenerator({
7357
7498
  dataSection,
7358
7499
  skipReactiveRuntime: this.options.skipReactiveRuntime,
7359
7500
  skipComponentRuntime: this.options.skipComponentRuntime,
7360
- reactiveVars: this.options.reactiveVars
7501
+ reactiveVars: this.options.reactiveVars,
7502
+ sourceMap
7361
7503
  });
7362
7504
  let code = generator.compile(sexpr);
7363
- return { tokens, sexpr, code, dts, data: dataSection, reactiveVars: generator.reactiveVars };
7505
+ let map = sourceMap ? sourceMap.toJSON() : null;
7506
+ let reverseMap = sourceMap ? sourceMap.toReverseMap() : null;
7507
+ if (map && this.options.sourceMap === "inline") {
7508
+ code += `
7509
+ //# sourceMappingURL=data:application/json;base64,${btoa(map)}`;
7510
+ } else if (map && this.options.filename) {
7511
+ code += `
7512
+ //# sourceMappingURL=${this.options.filename}.js.map`;
7513
+ }
7514
+ return { tokens, sexpr, code, dts, map, reverseMap, data: dataSection, reactiveVars: generator.reactiveVars };
7364
7515
  }
7365
7516
  compileToJS(source) {
7366
7517
  return this.compile(source).code;
@@ -7384,8 +7535,8 @@ function getComponentRuntime() {
7384
7535
  return new CodeGenerator({}).getComponentRuntime();
7385
7536
  }
7386
7537
  // src/browser.js
7387
- var VERSION = "3.3.0";
7388
- var BUILD_DATE = "2026-02-09@04:28:55GMT";
7538
+ var VERSION = "3.4.0";
7539
+ var BUILD_DATE = "2026-02-09@07:03:58GMT";
7389
7540
  var dedent = (s) => {
7390
7541
  const m = s.match(/^[ \t]*(?=\S)/gm);
7391
7542
  const i = Math.min(...(m || []).map((x) => x.length));