rip-lang 2.5.1 → 2.7.1

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/src/repl.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Rip REPL - Interactive Read-Eval-Print Loop
2
+ * Rip REPL - Interactive Read-Eval-Print Loop (Bun-native version)
3
3
  *
4
4
  * Features:
5
5
  * - Multi-line input detection
@@ -7,14 +7,15 @@
7
7
  * - Special commands (.help, .clear, .vars, etc.)
8
8
  * - Colored output
9
9
  * - Persistent context
10
+ * - Full dynamic import() support (Bun-native)
10
11
  */
11
12
 
12
13
  import * as readline from 'readline';
13
14
  import { inspect } from 'util';
14
- import * as vm from 'vm';
15
15
  import * as fs from 'fs';
16
16
  import * as path from 'path';
17
17
  import * as os from 'os';
18
+ import { pathToFileURL } from 'url';
18
19
  import { Compiler } from './compiler.js';
19
20
  import packageJson from '../package.json' with { type: 'json' };
20
21
 
@@ -34,6 +35,13 @@ const colors = {
34
35
  gray: '\x1b[90m'
35
36
  };
36
37
 
38
+ // Use globalThis for persistent context (works across async evaluations)
39
+ globalThis.__ripRepl = globalThis.__ripRepl || {
40
+ vars: {},
41
+ tempCounter: 0,
42
+ cwd: process.cwd()
43
+ };
44
+
37
45
  export class RipREPL {
38
46
  constructor() {
39
47
  this.buffer = ''; // Multi-line input buffer
@@ -41,19 +49,7 @@ export class RipREPL {
41
49
  this.historyFile = path.join(os.homedir(), '.rip_history');
42
50
  this.reactiveVars = new Set(); // Track reactive variables across lines
43
51
 
44
- // Create persistent VM context (like a sandboxed global scope)
45
- this.context = vm.createContext({
46
- console,
47
- process,
48
- Buffer,
49
- setTimeout,
50
- setInterval,
51
- clearTimeout,
52
- clearInterval,
53
- // Add any other globals you want available
54
- });
55
-
56
- // Inject reactive runtime once into context (so all reactive code shares same tracking)
52
+ // Inject reactive runtime into global context
57
53
  this.injectReactiveRuntime();
58
54
 
59
55
  this.rl = readline.createInterface({
@@ -68,6 +64,14 @@ export class RipREPL {
68
64
  this.loadHistory();
69
65
  }
70
66
 
67
+ get context() {
68
+ return globalThis.__ripRepl.vars;
69
+ }
70
+
71
+ set context(val) {
72
+ globalThis.__ripRepl.vars = val;
73
+ }
74
+
71
75
  getPrompt() {
72
76
  if (this.buffer) {
73
77
  return `${colors.dim}....>${colors.reset} `; // Continuation prompt
@@ -78,11 +82,12 @@ export class RipREPL {
78
82
  start() {
79
83
  this.printWelcome();
80
84
 
81
- this.rl.on('line', (line) => {
82
- this.handleLine(line);
85
+ this.rl.on('line', async (line) => {
86
+ await this.handleLine(line);
83
87
  });
84
88
 
85
89
  this.rl.on('close', () => {
90
+ this.cleanup();
86
91
  this.saveHistory();
87
92
  console.log(`\n${colors.gray}Goodbye!${colors.reset}`);
88
93
  process.exit(0);
@@ -92,89 +97,95 @@ export class RipREPL {
92
97
  }
93
98
 
94
99
  printWelcome() {
95
- console.log(`${colors.bright}Rip ${VERSION}${colors.reset} - Interactive REPL`);
100
+ console.log(`${colors.bright}Rip ${VERSION}${colors.reset} - Interactive REPL ${colors.dim}(Bun-native)${colors.reset}`);
96
101
  console.log(`${colors.gray}Type ${colors.cyan}.help${colors.gray} for commands, ${colors.cyan}Ctrl+C${colors.gray} to exit${colors.reset}\n`);
97
102
  }
98
103
 
99
104
  injectReactiveRuntime() {
100
- // Inject reactive runtime into REPL context once
101
- // This ensures all reactive code shares the same __currentEffect, etc.
102
- const runtimeCode = `
103
- let __currentEffect = null, __pendingEffects = new Set();
104
- function __signal(v) {
105
- const subs = new Set();
106
- let notifying = false, locked = false, dead = false;
107
- const s = {
108
- get value() { if (dead) return v; if (__currentEffect) { subs.add(__currentEffect); __currentEffect.dependencies.add(subs); } return v; },
109
- set value(n) {
110
- if (dead || locked || n === v || notifying) return;
111
- v = n;
112
- notifying = true;
113
- for (const sub of subs) if (sub.markDirty) sub.markDirty();
114
- for (const sub of subs) if (!sub.markDirty) __pendingEffects.add(sub);
115
- const fx = [...__pendingEffects]; __pendingEffects.clear();
116
- for (const e of fx) e.run();
117
- notifying = false;
118
- },
119
- read() { return v; },
120
- lock() { locked = true; return s; },
121
- free() { subs.clear(); return s; },
122
- kill() { dead = true; subs.clear(); return v; },
123
- valueOf() { return this.value; },
124
- toString() { return String(this.value); },
125
- [Symbol.toPrimitive](hint) { return hint === 'string' ? this.toString() : this.valueOf(); }
126
- };
127
- return s;
128
- }
129
- function __computed(fn) {
130
- let v, dirty = true, locked = false, dead = false;
131
- const subs = new Set();
132
- const c = {
133
- dependencies: new Set(),
134
- markDirty() {
135
- if (dead || locked || !dirty) { if (!dead && !locked && !dirty) { dirty = true; for (const s of subs) if (s.markDirty) s.markDirty(); for (const s of subs) if (!s.markDirty) __pendingEffects.add(s); } }
136
- },
137
- get value() {
138
- if (dead) return v;
139
- if (__currentEffect) { subs.add(__currentEffect); __currentEffect.dependencies.add(subs); }
140
- if (dirty && !locked) {
141
- for (const d of c.dependencies) d.delete(c); c.dependencies.clear();
142
- const prev = __currentEffect; __currentEffect = c;
143
- try { v = fn(); } finally { __currentEffect = prev; }
144
- dirty = false;
145
- }
146
- return v;
147
- },
148
- read() { return dead ? v : c.value; },
149
- lock() { locked = true; c.value; return c; },
150
- free() { for (const d of c.dependencies) d.delete(c); c.dependencies.clear(); subs.clear(); return c; },
151
- kill() { dead = true; const result = v; c.free(); return result; },
152
- valueOf() { return this.value; },
153
- toString() { return String(this.value); },
154
- [Symbol.toPrimitive](hint) { return hint === 'string' ? this.toString() : this.valueOf(); }
155
- };
156
- return c;
157
- }
158
- function __effect(fn) {
159
- const e = {
160
- dependencies: new Set(),
161
- run() {
162
- for (const d of e.dependencies) d.delete(e); e.dependencies.clear();
163
- const prev = __currentEffect; __currentEffect = e;
164
- try { fn(); } finally { __currentEffect = prev; }
165
- },
166
- free() { for (const d of e.dependencies) d.delete(e); e.dependencies.clear(); }
167
- };
168
- e.run();
169
- return () => e.free();
170
- }
171
- function __batch(fn) { fn(); }
172
- function __readonly(v) { return Object.freeze({ value: v }); }
173
- `;
174
- vm.runInContext(runtimeCode, this.context);
105
+ // Inject reactive runtime into global context once
106
+ if (globalThis.__ripRepl.runtimeInjected) return;
107
+
108
+ // Define reactive primitives on globalThis so they're available in all evaluations
109
+ globalThis.__currentEffect = null;
110
+ globalThis.__pendingEffects = new Set();
111
+
112
+ globalThis.__state = function(v) {
113
+ const subs = new Set();
114
+ let notifying = false, locked = false, dead = false;
115
+ const s = {
116
+ get value() { if (dead) return v; if (__currentEffect) { subs.add(__currentEffect); __currentEffect.dependencies.add(subs); } return v; },
117
+ set value(n) {
118
+ if (dead || locked || n === v || notifying) return;
119
+ v = n;
120
+ notifying = true;
121
+ for (const sub of subs) if (sub.markDirty) sub.markDirty();
122
+ for (const sub of subs) if (!sub.markDirty) __pendingEffects.add(sub);
123
+ const fx = [...__pendingEffects]; __pendingEffects.clear();
124
+ for (const e of fx) e.run();
125
+ notifying = false;
126
+ },
127
+ read() { return v; },
128
+ lock() { locked = true; return s; },
129
+ free() { subs.clear(); return s; },
130
+ kill() { dead = true; subs.clear(); return v; },
131
+ valueOf() { return this.value; },
132
+ toString() { return String(this.value); },
133
+ [Symbol.toPrimitive](hint) { return hint === 'string' ? this.toString() : this.valueOf(); }
134
+ };
135
+ return s;
136
+ };
137
+
138
+ globalThis.__computed = function(fn) {
139
+ let v, dirty = true, locked = false, dead = false;
140
+ const subs = new Set();
141
+ const c = {
142
+ dependencies: new Set(),
143
+ markDirty() {
144
+ if (dead || locked || !dirty) { if (!dead && !locked && !dirty) { dirty = true; for (const s of subs) if (s.markDirty) s.markDirty(); for (const s of subs) if (!s.markDirty) __pendingEffects.add(s); } }
145
+ },
146
+ get value() {
147
+ if (dead) return v;
148
+ if (__currentEffect) { subs.add(__currentEffect); __currentEffect.dependencies.add(subs); }
149
+ if (dirty && !locked) {
150
+ for (const d of c.dependencies) d.delete(c); c.dependencies.clear();
151
+ const prev = __currentEffect; __currentEffect = c;
152
+ try { v = fn(); } finally { __currentEffect = prev; }
153
+ dirty = false;
154
+ }
155
+ return v;
156
+ },
157
+ read() { return dead ? v : c.value; },
158
+ lock() { locked = true; c.value; return c; },
159
+ free() { for (const d of c.dependencies) d.delete(c); c.dependencies.clear(); subs.clear(); return c; },
160
+ kill() { dead = true; const result = v; c.free(); return result; },
161
+ valueOf() { return this.value; },
162
+ toString() { return String(this.value); },
163
+ [Symbol.toPrimitive](hint) { return hint === 'string' ? this.toString() : this.valueOf(); }
164
+ };
165
+ return c;
166
+ };
167
+
168
+ globalThis.__effect = function(fn) {
169
+ const e = {
170
+ dependencies: new Set(),
171
+ run() {
172
+ for (const d of e.dependencies) d.delete(e); e.dependencies.clear();
173
+ const prev = __currentEffect; __currentEffect = e;
174
+ try { fn(); } finally { __currentEffect = prev; }
175
+ },
176
+ free() { for (const d of e.dependencies) d.delete(e); e.dependencies.clear(); }
177
+ };
178
+ e.run();
179
+ return () => e.free();
180
+ };
181
+
182
+ globalThis.__batch = function(fn) { fn(); };
183
+ globalThis.__readonly = function(v) { return Object.freeze({ value: v }); };
184
+
185
+ globalThis.__ripRepl.runtimeInjected = true;
175
186
  }
176
187
 
177
- handleLine(line) {
188
+ async handleLine(line) {
178
189
  // Handle special commands
179
190
  if (line.startsWith('.')) {
180
191
  this.handleCommand(line);
@@ -192,7 +203,7 @@ function __readonly(v) { return Object.freeze({ value: v }); }
192
203
 
193
204
  // Check if input is complete
194
205
  if (this.isComplete(this.buffer)) {
195
- this.evaluate(this.buffer);
206
+ await this.evaluate(this.buffer);
196
207
  this.buffer = '';
197
208
  }
198
209
 
@@ -289,14 +300,12 @@ function __readonly(v) { return Object.freeze({ value: v }); }
289
300
  return true;
290
301
  }
291
302
 
292
- evaluate(code) {
303
+ async evaluate(code) {
293
304
  try {
294
305
  // Add to history
295
306
  this.history.push(code);
296
307
 
297
- // Compile Rip to JavaScript with debug options
298
- // skipReactiveRuntime: runtime is already injected into context
299
- // reactiveVars: track reactive variables across REPL lines
308
+ // Compile Rip to JavaScript
300
309
  const compiler = new Compiler({
301
310
  showTokens: this.showTokens,
302
311
  showSExpr: this.showSExp,
@@ -312,7 +321,7 @@ function __readonly(v) { return Object.freeze({ value: v }); }
312
321
  }
313
322
  }
314
323
 
315
- const js = result.code;
324
+ let js = result.code;
316
325
 
317
326
  // Show compiled JS if enabled
318
327
  if (this.showJS) {
@@ -320,18 +329,8 @@ function __readonly(v) { return Object.freeze({ value: v }); }
320
329
  console.log(`${colors.dim}${js}${colors.reset}\n`);
321
330
  }
322
331
 
323
- // Convert let/const to var for REPL persistence in vm context
324
- // In vm.runInContext, var declarations persist while let/const are scoped
325
- let processedJs = js;
326
-
327
- // Replace 'let x, y, z;' with 'var x, y, z;'
328
- processedJs = processedJs.replace(/^let\s+/m, 'var ');
329
-
330
- // Replace 'const x =' with 'var x ='
331
- processedJs = processedJs.replace(/^const\s+/gm, 'var ');
332
-
333
- // Evaluate in persistent context
334
- const evalResult = vm.runInContext(processedJs, this.context);
332
+ // Execute using Bun's native async evaluation with import support
333
+ const evalResult = await this.bunEval(js);
335
334
 
336
335
  // Store result in _ for convenience
337
336
  if (evalResult !== undefined) {
@@ -343,6 +342,92 @@ function __readonly(v) { return Object.freeze({ value: v }); }
343
342
  }
344
343
  }
345
344
 
345
+ async bunEval(js) {
346
+ // Extract variable declarations from the compiled JS
347
+ const varMatches = [...js.matchAll(/^let\s+(\w+)(?:,\s*(\w+))*;$/gm)];
348
+ const declaredVars = new Set();
349
+ for (const match of varMatches) {
350
+ for (let i = 1; i < match.length; i++) {
351
+ if (match[i]) declaredVars.add(match[i]);
352
+ }
353
+ }
354
+
355
+ // Also check for let x = ... pattern
356
+ const assignMatches = [...js.matchAll(/^let\s+(\w+)\s*=/gm)];
357
+ for (const match of assignMatches) {
358
+ declaredVars.add(match[1]);
359
+ }
360
+
361
+ // Build code that restores context
362
+ const ctx = this.context;
363
+ const existingVars = Object.keys(ctx);
364
+
365
+ // Remove let declarations for vars we're restoring from context
366
+ let cleanJs = js;
367
+ for (const v of existingVars) {
368
+ cleanJs = cleanJs.replace(new RegExp(`^let ${v};\\n`, 'm'), '');
369
+ cleanJs = cleanJs.replace(new RegExp(`^let ${v}(\\s*=)`, 'm'), `${v}$1`);
370
+ }
371
+
372
+ // Restore existing variables
373
+ const restoreCtx = existingVars
374
+ .filter(k => k !== '_')
375
+ .map(k => `let ${k} = globalThis.__ripRepl.vars["${k}"];`)
376
+ .join('\n');
377
+
378
+ // Find the last expression to capture as result
379
+ const lines = cleanJs.trim().split('\n');
380
+ let lastLine = lines[lines.length - 1];
381
+
382
+ // If it's a simple expression (ends with ;), capture it
383
+ if (lastLine && lastLine.endsWith(';') &&
384
+ !lastLine.startsWith('let ') &&
385
+ !lastLine.startsWith('const ') &&
386
+ !lastLine.startsWith('var ') &&
387
+ !lastLine.includes('import ') &&
388
+ !lastLine.includes('export ')) {
389
+ lines[lines.length - 1] = `globalThis.__ripRepl_result = ${lastLine.slice(0, -1)};`;
390
+ }
391
+
392
+ // Save variables back to context
393
+ const allVars = [...new Set([...existingVars, ...declaredVars])].filter(k => k !== '_');
394
+ const saveCtx = allVars.map(v =>
395
+ `if (typeof ${v} !== 'undefined') globalThis.__ripRepl.vars["${v}"] = ${v};`
396
+ ).join('\n');
397
+
398
+ const wrapped = `
399
+ ${restoreCtx}
400
+ ${lines.join('\n')}
401
+ ${saveCtx}
402
+ `;
403
+
404
+ // Write to temp file and import it (enables dynamic imports)
405
+ const tempFile = path.join(globalThis.__ripRepl.cwd, `.rip-repl-${globalThis.__ripRepl.tempCounter++}.mjs`);
406
+
407
+ try {
408
+ fs.writeFileSync(tempFile, wrapped);
409
+ await import(pathToFileURL(tempFile).href + `?t=${Date.now()}`);
410
+ const result = globalThis.__ripRepl_result;
411
+ delete globalThis.__ripRepl_result;
412
+ return result;
413
+ } finally {
414
+ try { fs.unlinkSync(tempFile); } catch {}
415
+ }
416
+ }
417
+
418
+ cleanup() {
419
+ // Clean up any leftover temp files
420
+ const cwd = globalThis.__ripRepl.cwd;
421
+ try {
422
+ const files = fs.readdirSync(cwd);
423
+ for (const f of files) {
424
+ if (f.startsWith('.rip-repl-') && f.endsWith('.mjs')) {
425
+ fs.unlinkSync(path.join(cwd, f));
426
+ }
427
+ }
428
+ } catch {}
429
+ }
430
+
346
431
  printResult(value) {
347
432
  // Pretty print the result
348
433
  const formatted = inspect(value, {
@@ -370,18 +455,8 @@ function __readonly(v) { return Object.freeze({ value: v }); }
370
455
  break;
371
456
 
372
457
  case '.clear':
373
- // Recreate the context to clear all variables
374
- this.context = vm.createContext({
375
- console,
376
- process,
377
- Buffer,
378
- setTimeout,
379
- setInterval,
380
- clearTimeout,
381
- clearInterval,
382
- });
383
- // Re-inject reactive runtime and clear reactive vars
384
- this.injectReactiveRuntime();
458
+ // Clear all variables
459
+ globalThis.__ripRepl.vars = {};
385
460
  this.reactiveVars = new Set();
386
461
  this.buffer = '';
387
462
  console.log(`${colors.green}Context cleared${colors.reset}`);
@@ -438,6 +513,7 @@ ${colors.cyan}Debug Toggles:${colors.reset}
438
513
 
439
514
  ${colors.cyan}Tips:${colors.reset}
440
515
  - Multi-line input is supported (press Enter mid-expression)
516
+ - Full import() support: { x } = await import('./file.js')
441
517
  - Use Tab for history navigation
442
518
  - Previous results stored in _ variable
443
519
  - Use Ctrl+C to cancel multi-line input or exit
@@ -445,10 +521,8 @@ ${colors.cyan}Tips:${colors.reset}
445
521
  }
446
522
 
447
523
  printVars() {
448
- // Get all variables from the context (excluding built-in globals)
449
- const builtins = new Set(['console', 'process', 'Buffer', 'setTimeout', 'setInterval', 'clearTimeout', 'clearInterval', '_']);
450
- const allKeys = Object.keys(this.context);
451
- const userVars = allKeys.filter(k => !builtins.has(k) && !k.startsWith('_'));
524
+ // Get all variables from the context
525
+ const userVars = Object.keys(this.context).filter(k => k !== '_');
452
526
 
453
527
  if (userVars.length === 0) {
454
528
  console.log(`${colors.gray}No variables defined${colors.reset}`);
package/src/tags.js DELETED
@@ -1,62 +0,0 @@
1
- // ============================================================================
2
- // Shared HTML/SVG Tag Definitions
3
- // ============================================================================
4
- // Single source of truth for tag recognition in templates.
5
- // Used by lexer and codegen at compile time.
6
-
7
- export const HTML_TAGS = new Set([
8
- // Document metadata
9
- 'html', 'head', 'title', 'base', 'link', 'meta', 'style',
10
- // Sectioning
11
- 'body', 'address', 'article', 'aside', 'footer', 'header',
12
- 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'main', 'nav', 'section',
13
- // Grouping
14
- 'blockquote', 'dd', 'div', 'dl', 'dt', 'figcaption', 'figure',
15
- 'hr', 'li', 'ol', 'p', 'pre', 'ul',
16
- // Text-level
17
- 'a', 'abbr', 'b', 'bdi', 'bdo', 'br', 'cite', 'code', 'data',
18
- 'dfn', 'em', 'i', 'kbd', 'mark', 'q', 'rp', 'rt', 'ruby', 's',
19
- 'samp', 'small', 'span', 'strong', 'sub', 'sup', 'time', 'u', 'var', 'wbr',
20
- // Embedded
21
- 'area', 'audio', 'img', 'map', 'track', 'video',
22
- 'embed', 'iframe', 'object', 'param', 'picture', 'portal', 'source',
23
- // SVG/Math
24
- 'svg', 'math', 'canvas',
25
- // Scripting
26
- 'noscript', 'script',
27
- // Edits
28
- 'del', 'ins',
29
- // Tables
30
- 'caption', 'col', 'colgroup', 'table', 'tbody', 'td', 'tfoot', 'th', 'thead', 'tr',
31
- // Forms
32
- 'button', 'datalist', 'fieldset', 'form', 'input', 'label', 'legend',
33
- 'meter', 'optgroup', 'option', 'output', 'progress', 'select', 'textarea',
34
- // Interactive
35
- 'details', 'dialog', 'menu', 'summary',
36
- // Web components
37
- 'slot', 'template'
38
- ]);
39
-
40
- export const SVG_TAGS = new Set([
41
- // Container elements
42
- 'svg', 'g', 'defs', 'symbol', 'use', 'marker', 'clipPath', 'mask', 'pattern',
43
- // Shape elements
44
- 'circle', 'ellipse', 'line', 'path', 'polygon', 'polyline', 'rect',
45
- // Text elements
46
- 'text', 'textPath', 'tspan',
47
- // Gradient elements
48
- 'linearGradient', 'radialGradient', 'stop',
49
- // Filter elements
50
- 'filter', 'feBlend', 'feColorMatrix', 'feComponentTransfer', 'feComposite',
51
- 'feConvolveMatrix', 'feDiffuseLighting', 'feDisplacementMap', 'feDistantLight',
52
- 'feDropShadow', 'feFlood', 'feFuncA', 'feFuncB', 'feFuncG', 'feFuncR',
53
- 'feGaussianBlur', 'feImage', 'feMerge', 'feMergeNode', 'feMorphology',
54
- 'feOffset', 'fePointLight', 'feSpecularLighting', 'feSpotLight', 'feTile', 'feTurbulence',
55
- // Animation elements
56
- 'animate', 'animateMotion', 'animateTransform', 'set', 'mpath',
57
- // Other elements
58
- 'desc', 'foreignObject', 'image', 'metadata', 'switch', 'title', 'view'
59
- ]);
60
-
61
- // Combined set for template element detection (HTML + common SVG)
62
- export const TEMPLATE_TAGS = new Set([...HTML_TAGS, ...SVG_TAGS]);