rip-lang 2.8.6 → 2.8.7
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 +5 -3
- package/docs/dist/rip.browser.js +2 -2
- package/docs/dist/rip.browser.min.js +1 -1
- package/docs/dist/rip.browser.min.js.br +0 -0
- package/docs/repl.html +161 -12
- package/package.json +1 -1
- package/src/repl.js +68 -14
package/README.md
CHANGED
|
@@ -9,9 +9,9 @@
|
|
|
9
9
|
</p>
|
|
10
10
|
|
|
11
11
|
<p align="center">
|
|
12
|
-
<a href="CHANGELOG.md"><img src="https://img.shields.io/badge/version-2.8.
|
|
12
|
+
<a href="CHANGELOG.md"><img src="https://img.shields.io/badge/version-2.8.7-blue.svg" alt="Version"></a>
|
|
13
13
|
<a href="#zero-dependencies"><img src="https://img.shields.io/badge/dependencies-ZERO-brightgreen.svg" alt="Dependencies"></a>
|
|
14
|
-
<a href="#"><img src="https://img.shields.io/badge/tests-
|
|
14
|
+
<a href="#"><img src="https://img.shields.io/badge/tests-1098%2F1098-brightgreen.svg" alt="Tests"></a>
|
|
15
15
|
<a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-green.svg" alt="License"></a>
|
|
16
16
|
</p>
|
|
17
17
|
|
|
@@ -122,6 +122,8 @@ State, computed values, and effects as language operators:
|
|
|
122
122
|
| `=~` | `str =~ /Hello, (\w+)/` | Match (captures in `_`) |
|
|
123
123
|
| `[//, n]` | `str[/Hello, (\w+)/, 1]` | Extract capture n |
|
|
124
124
|
| `.new()` | `Dog.new()` | Ruby-style constructor |
|
|
125
|
+
| `or return` | `x = get() or return err` | Guard clause (Ruby-style) |
|
|
126
|
+
| `?? throw` | `x = get() ?? throw err` | Nullish guard (null/undefined only) |
|
|
125
127
|
|
|
126
128
|
**Optional chaining** — Both CoffeeScript and ES6 styles are supported:
|
|
127
129
|
|
|
@@ -261,7 +263,7 @@ rip file.rip # Run
|
|
|
261
263
|
rip -c file.rip # Compile
|
|
262
264
|
rip -t file.rip # Tokens
|
|
263
265
|
rip -s file.rip # S-expressions
|
|
264
|
-
bun run test #
|
|
266
|
+
bun run test # 1098 tests
|
|
265
267
|
bun run parser # Rebuild parser
|
|
266
268
|
bun run browser # Build browser bundle
|
|
267
269
|
```
|
package/docs/dist/rip.browser.js
CHANGED
|
@@ -7627,8 +7627,8 @@ function compileToJS(source, options = {}) {
|
|
|
7627
7627
|
return new Compiler(options).compileToJS(source);
|
|
7628
7628
|
}
|
|
7629
7629
|
// src/browser.js
|
|
7630
|
-
var VERSION = "2.8.
|
|
7631
|
-
var BUILD_DATE = "2026-02-04@11:
|
|
7630
|
+
var VERSION = "2.8.7";
|
|
7631
|
+
var BUILD_DATE = "2026-02-04@11:45:06GMT";
|
|
7632
7632
|
var dedent = (s) => {
|
|
7633
7633
|
const m = s.match(/^[ \t]*(?=\S)/gm);
|
|
7634
7634
|
const i = Math.min(...(m || []).map((x) => x.length));
|
|
@@ -530,4 +530,4 @@ function __catchErrors(fn) {
|
|
|
530
530
|
`),X=E.findIndex((G)=>G==="__DATA__");if(X!==-1){let G=E.slice(X+1);_=G.length>0?G.join(`
|
|
531
531
|
`)+`
|
|
532
532
|
`:"",$=E.slice(0,X).join(`
|
|
533
|
-
`)}let A=new m1().tokenize($);if(this.options.showTokens)A.forEach((G)=>console.log(`${G[0].padEnd(12)} ${JSON.stringify(G[1])}`)),console.log();$1.lexer={tokens:A,pos:0,setInput:function(){},lex:function(){if(this.pos>=this.tokens.length)return 1;let G=this.tokens[this.pos++];return this.yytext=G[1],this.yylloc=G[2],G[0]}};let U;try{U=$1.parse($)}catch(G){if(/\?\s*\([^)]*\?[^)]*:[^)]*\)\s*:/.test($)||/\?\s+\w+\s+\?\s+/.test($))throw Error("Nested ternary operators are not supported. Use if/else statements instead.");throw G}if(this.options.showSExpr)console.log(o(U,0,!0)),console.log();let Y=new c({dataSection:_,skipReactiveRuntime:this.options.skipReactiveRuntime,reactiveVars:this.options.reactiveVars}),F=Y.compile(U);return{tokens:A,sexpr:U,code:F,data:_,reactiveVars:Y.reactiveVars}}compileToJS($){return this.compile($).code}compileToSExpr($){return this.compile($).sexpr}}function U3($,_={}){return new O1(_).compile($)}function S1($,_={}){return new O1(_).compileToJS($)}var Q3="2.8.
|
|
533
|
+
`)}let A=new m1().tokenize($);if(this.options.showTokens)A.forEach((G)=>console.log(`${G[0].padEnd(12)} ${JSON.stringify(G[1])}`)),console.log();$1.lexer={tokens:A,pos:0,setInput:function(){},lex:function(){if(this.pos>=this.tokens.length)return 1;let G=this.tokens[this.pos++];return this.yytext=G[1],this.yylloc=G[2],G[0]}};let U;try{U=$1.parse($)}catch(G){if(/\?\s*\([^)]*\?[^)]*:[^)]*\)\s*:/.test($)||/\?\s+\w+\s+\?\s+/.test($))throw Error("Nested ternary operators are not supported. Use if/else statements instead.");throw G}if(this.options.showSExpr)console.log(o(U,0,!0)),console.log();let Y=new c({dataSection:_,skipReactiveRuntime:this.options.skipReactiveRuntime,reactiveVars:this.options.reactiveVars}),F=Y.compile(U);return{tokens:A,sexpr:U,code:F,data:_,reactiveVars:Y.reactiveVars}}compileToJS($){return this.compile($).code}compileToSExpr($){return this.compile($).sexpr}}function U3($,_={}){return new O1(_).compile($)}function S1($,_={}){return new O1(_).compileToJS($)}var Q3="2.8.7",W3="2026-02-04@11:45:06GMT",$3=($)=>{let _=$.match(/^[ \t]*(?=\S)/gm),E=Math.min(...(_||[]).map((X)=>X.length));return $.replace(RegExp(`^[ ]{${E}}`,"gm"),"").trim()};async function k2(){let $=document.querySelectorAll('script[type="text/rip"]');for(let _ of $){if(_.hasAttribute("data-rip-processed"))continue;try{let E=$3(_.textContent),X=S1(E);(0,eval)(X),_.setAttribute("data-rip-processed","true")}catch(E){console.error("Error compiling Rip script:",E),console.error("Script content:",_.textContent)}}}if(typeof document<"u")if(document.readyState==="loading")document.addEventListener("DOMContentLoaded",k2);else k2();function X3($){try{let E=S1($).replace(/^let\s+[^;]+;\s*\n\s*/m,"");E=E.replace(/^const\s+/gm,"var ");let X=(0,eval)(E);if(X!==void 0)globalThis._=X;return X}catch(_){console.error("Rip compilation error:",_.message);return}}if(typeof globalThis<"u")globalThis.rip=X3;export{X3 as rip,k2 as processRipScripts,$1 as parser,o as formatSExpr,S1 as compileToJS,U3 as compile,Q3 as VERSION,m1 as Lexer,O1 as Compiler,c as CodeGenerator,W3 as BUILD_DATE};
|
|
Binary file
|
package/docs/repl.html
CHANGED
|
@@ -659,6 +659,7 @@ console.log "Domain:", domain</textarea>
|
|
|
659
659
|
let replHistory = [];
|
|
660
660
|
let historyIndex = -1;
|
|
661
661
|
let replBuffer = '';
|
|
662
|
+
let reactiveVars = new Set(); // Track reactive variables across evaluations
|
|
662
663
|
|
|
663
664
|
// Create isolated iframe context for REPL (like vm.createContext in Node)
|
|
664
665
|
const iframe = document.createElement('iframe');
|
|
@@ -670,6 +671,90 @@ console.log "Domain:", domain</textarea>
|
|
|
670
671
|
replContext.console = console;
|
|
671
672
|
replContext.showSexp = false;
|
|
672
673
|
replContext.showTokens = false;
|
|
674
|
+
replContext.__reactiveVars = {}; // Store reactive objects persistently
|
|
675
|
+
|
|
676
|
+
// Inject reactive runtime into iframe context
|
|
677
|
+
(function injectReactiveRuntime() {
|
|
678
|
+
const ctx = replContext;
|
|
679
|
+
ctx.__currentEffect = null;
|
|
680
|
+
ctx.__pendingEffects = new Set();
|
|
681
|
+
|
|
682
|
+
ctx.__state = function(v) {
|
|
683
|
+
const subs = new Set();
|
|
684
|
+
let notifying = false, locked = false, dead = false;
|
|
685
|
+
const s = {
|
|
686
|
+
get value() { if (dead) return v; if (ctx.__currentEffect) { subs.add(ctx.__currentEffect); ctx.__currentEffect.dependencies.add(subs); } return v; },
|
|
687
|
+
set value(n) {
|
|
688
|
+
if (dead || locked || n === v || notifying) return;
|
|
689
|
+
v = n;
|
|
690
|
+
notifying = true;
|
|
691
|
+
for (const sub of subs) if (sub.markDirty) sub.markDirty();
|
|
692
|
+
for (const sub of subs) if (!sub.markDirty) ctx.__pendingEffects.add(sub);
|
|
693
|
+
const fx = [...ctx.__pendingEffects]; ctx.__pendingEffects.clear();
|
|
694
|
+
for (const e of fx) e.run();
|
|
695
|
+
notifying = false;
|
|
696
|
+
},
|
|
697
|
+
read() { return v; },
|
|
698
|
+
lock() { locked = true; return s; },
|
|
699
|
+
free() { subs.clear(); return s; },
|
|
700
|
+
kill() { dead = true; subs.clear(); return v; },
|
|
701
|
+
valueOf() { return this.value; },
|
|
702
|
+
toString() { return String(this.value); },
|
|
703
|
+
[Symbol.toPrimitive](hint) { return hint === 'string' ? this.toString() : this.valueOf(); }
|
|
704
|
+
};
|
|
705
|
+
return s;
|
|
706
|
+
};
|
|
707
|
+
|
|
708
|
+
ctx.__computed = function(fn) {
|
|
709
|
+
let v, dirty = true, locked = false, dead = false;
|
|
710
|
+
const subs = new Set();
|
|
711
|
+
const c = {
|
|
712
|
+
dependencies: new Set(),
|
|
713
|
+
markDirty() {
|
|
714
|
+
if (dead || locked || dirty) return;
|
|
715
|
+
dirty = true;
|
|
716
|
+
for (const s of subs) if (s.markDirty) s.markDirty();
|
|
717
|
+
for (const s of subs) if (!s.markDirty) ctx.__pendingEffects.add(s);
|
|
718
|
+
},
|
|
719
|
+
get value() {
|
|
720
|
+
if (dead) return v;
|
|
721
|
+
if (ctx.__currentEffect) { subs.add(ctx.__currentEffect); ctx.__currentEffect.dependencies.add(subs); }
|
|
722
|
+
if (dirty && !locked) {
|
|
723
|
+
for (const d of c.dependencies) d.delete(c); c.dependencies.clear();
|
|
724
|
+
const prev = ctx.__currentEffect; ctx.__currentEffect = c;
|
|
725
|
+
try { v = fn(); } finally { ctx.__currentEffect = prev; }
|
|
726
|
+
dirty = false;
|
|
727
|
+
}
|
|
728
|
+
return v;
|
|
729
|
+
},
|
|
730
|
+
read() { return dead ? v : c.value; },
|
|
731
|
+
lock() { locked = true; c.value; return c; },
|
|
732
|
+
free() { for (const d of c.dependencies) d.delete(c); c.dependencies.clear(); subs.clear(); return c; },
|
|
733
|
+
kill() { dead = true; const result = v; c.free(); return result; },
|
|
734
|
+
valueOf() { return this.value; },
|
|
735
|
+
toString() { return String(this.value); },
|
|
736
|
+
[Symbol.toPrimitive](hint) { return hint === 'string' ? this.toString() : this.valueOf(); }
|
|
737
|
+
};
|
|
738
|
+
return c;
|
|
739
|
+
};
|
|
740
|
+
|
|
741
|
+
ctx.__effect = function(fn) {
|
|
742
|
+
const e = {
|
|
743
|
+
dependencies: new Set(),
|
|
744
|
+
run() {
|
|
745
|
+
for (const d of e.dependencies) d.delete(e); e.dependencies.clear();
|
|
746
|
+
const prev = ctx.__currentEffect; ctx.__currentEffect = e;
|
|
747
|
+
try { fn(); } finally { ctx.__currentEffect = prev; }
|
|
748
|
+
},
|
|
749
|
+
free() { for (const d of e.dependencies) d.delete(e); e.dependencies.clear(); }
|
|
750
|
+
};
|
|
751
|
+
e.run();
|
|
752
|
+
return () => e.free();
|
|
753
|
+
};
|
|
754
|
+
|
|
755
|
+
ctx.__batch = function(fn) { fn(); };
|
|
756
|
+
ctx.__readonly = function(v) { return Object.freeze({ value: v }); };
|
|
757
|
+
})();
|
|
673
758
|
|
|
674
759
|
function addOutput(content, className = '') {
|
|
675
760
|
const line = document.createElement('div');
|
|
@@ -681,9 +766,17 @@ console.log "Domain:", domain</textarea>
|
|
|
681
766
|
|
|
682
767
|
function evaluateRip(code) {
|
|
683
768
|
try {
|
|
684
|
-
|
|
769
|
+
// Pass reactiveVars to compiler so it knows which vars need .value access
|
|
770
|
+
const result = compile(code, { reactiveVars, skipReactiveRuntime: true });
|
|
685
771
|
let js = result.code;
|
|
686
772
|
|
|
773
|
+
// Track new reactive variables
|
|
774
|
+
if (result.reactiveVars) {
|
|
775
|
+
for (const v of result.reactiveVars) {
|
|
776
|
+
reactiveVars.add(v);
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
|
|
687
780
|
// REPL strategy: Strip let/const declarations entirely
|
|
688
781
|
// Assignments will create properties on iframe's window (global scope)
|
|
689
782
|
// This allows variables to persist between eval() calls
|
|
@@ -691,13 +784,22 @@ console.log "Domain:", domain</textarea>
|
|
|
691
784
|
// Remove: let x, y, z;\n
|
|
692
785
|
js = js.replace(/^let\s+[^;]+;\s*\n+/m, '');
|
|
693
786
|
|
|
694
|
-
//
|
|
787
|
+
// Transform reactive declarations to persist in __reactiveVars
|
|
788
|
+
// const x = __state(...) → x = __reactiveVars.x ?? (__reactiveVars.x = __state(...))
|
|
789
|
+
js = js.replace(
|
|
790
|
+
/^const\s+(\w+)\s*=\s*((?:__state|__computed|__effect)\(.+\));?$/gm,
|
|
791
|
+
(match, varName, rhs) => {
|
|
792
|
+
return `${varName} = __reactiveVars['${varName}'] ?? (__reactiveVars['${varName}'] = ${rhs});`;
|
|
793
|
+
}
|
|
794
|
+
);
|
|
795
|
+
|
|
796
|
+
// Remove remaining const (but keep the assignment) for non-reactive
|
|
695
797
|
js = js.replace(/^const\s+(\w+)\s*=/gm, '$1 =');
|
|
696
798
|
|
|
697
799
|
// Evaluate in iframe context - assignments create globals
|
|
698
800
|
const evalResult = replContext.eval(js);
|
|
699
801
|
|
|
700
|
-
// Store in _
|
|
802
|
+
// Store in _ (unwrap reactive values)
|
|
701
803
|
if (evalResult !== undefined) {
|
|
702
804
|
replContext._ = evalResult;
|
|
703
805
|
}
|
|
@@ -730,13 +832,27 @@ console.log "Domain:", domain</textarea>
|
|
|
730
832
|
|
|
731
833
|
case '.clear':
|
|
732
834
|
replOutput.innerHTML = '';
|
|
733
|
-
|
|
835
|
+
// Reset reactive tracking
|
|
836
|
+
reactiveVars.clear();
|
|
837
|
+
replContext.__reactiveVars = {};
|
|
838
|
+
// Clear all user-defined globals in iframe
|
|
839
|
+
const builtinsToKeep = ['console', 'showSexp', 'showTokens', 'eval', 'window', 'document',
|
|
840
|
+
'location', 'navigator', 'self', 'top', 'parent', 'frames', '__state', '__computed',
|
|
841
|
+
'__effect', '__batch', '__readonly', '__currentEffect', '__pendingEffects', '__reactiveVars'];
|
|
842
|
+
for (const key of Object.keys(replContext)) {
|
|
843
|
+
if (!builtinsToKeep.includes(key) && !key.startsWith('__')) {
|
|
844
|
+
try { delete replContext[key]; } catch {}
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
addOutput('<div class="welcome">Output and context cleared.</div>');
|
|
734
848
|
break;
|
|
735
849
|
|
|
736
850
|
case '.vars':
|
|
737
|
-
const builtins = ['console', 'showSexp', 'showTokens', 'eval', 'window', 'document',
|
|
851
|
+
const builtins = ['console', 'showSexp', 'showTokens', 'eval', 'window', 'document',
|
|
852
|
+
'location', 'navigator', 'self', 'top', 'parent', 'frames', '__state', '__computed',
|
|
853
|
+
'__effect', '__batch', '__readonly', '__currentEffect', '__pendingEffects', '__reactiveVars'];
|
|
738
854
|
const vars = Object.keys(replContext).filter(k =>
|
|
739
|
-
!builtins.includes(k) && !k.startsWith('
|
|
855
|
+
!builtins.includes(k) && !k.startsWith('__') || k === '_'
|
|
740
856
|
);
|
|
741
857
|
if (vars.length === 0 || (vars.length === 1 && vars[0] === '_' && replContext._ === undefined)) {
|
|
742
858
|
addOutput('<span class="help-text">No variables defined</span>', 'command-output');
|
|
@@ -744,8 +860,21 @@ console.log "Domain:", domain</textarea>
|
|
|
744
860
|
let output = '<span class="help-text">Variables:</span>\\n';
|
|
745
861
|
vars.forEach(v => {
|
|
746
862
|
try {
|
|
747
|
-
|
|
748
|
-
|
|
863
|
+
let val = replContext[v];
|
|
864
|
+
let typeIndicator = '=';
|
|
865
|
+
// Check if it's a reactive value
|
|
866
|
+
if (val && typeof val === 'object' && 'value' in val) {
|
|
867
|
+
if (typeof val.markDirty === 'function') {
|
|
868
|
+
typeIndicator = '<span style="color:#c586c0">~=</span>'; // computed
|
|
869
|
+
} else if (typeof val === 'function') {
|
|
870
|
+
typeIndicator = '<span style="color:#c586c0">~></span>'; // effect
|
|
871
|
+
} else {
|
|
872
|
+
typeIndicator = '<span style="color:#c586c0">:=</span>'; // state
|
|
873
|
+
}
|
|
874
|
+
val = val.value;
|
|
875
|
+
}
|
|
876
|
+
const formatted = formatValue(val);
|
|
877
|
+
output += ` <span class="var-name">${v}</span> ${typeIndicator} <span class="var-value">${escapeHtml(formatted)}</span>\\n`;
|
|
749
878
|
} catch {
|
|
750
879
|
output += ` <span class="var-name">${v}</span> = <span class="var-value">[object]</span>\\n`;
|
|
751
880
|
}
|
|
@@ -809,11 +938,14 @@ console.log "Domain:", domain</textarea>
|
|
|
809
938
|
addOutput(`${formatted}`, 'command-output');
|
|
810
939
|
}
|
|
811
940
|
|
|
812
|
-
// Show result
|
|
941
|
+
// Show result (unwrap reactive values)
|
|
813
942
|
if (evalResult.value !== undefined) {
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
943
|
+
let displayValue = evalResult.value;
|
|
944
|
+
// Unwrap reactive objects to show their .value
|
|
945
|
+
if (displayValue && typeof displayValue === 'object' && 'value' in displayValue) {
|
|
946
|
+
displayValue = displayValue.value;
|
|
947
|
+
}
|
|
948
|
+
const formatted = formatValue(displayValue);
|
|
817
949
|
addOutput(`<span class="result">→ ${escapeHtml(formatted)}</span>`);
|
|
818
950
|
}
|
|
819
951
|
|
|
@@ -838,6 +970,23 @@ console.log "Domain:", domain</textarea>
|
|
|
838
970
|
.replace(/"/g, '"');
|
|
839
971
|
}
|
|
840
972
|
|
|
973
|
+
// Format values for display (like Node's util.inspect)
|
|
974
|
+
function formatValue(val) {
|
|
975
|
+
if (val === null) return 'null';
|
|
976
|
+
if (val === undefined) return 'undefined';
|
|
977
|
+
if (val instanceof RegExp) return val.toString();
|
|
978
|
+
if (typeof val === 'function') return val.toString();
|
|
979
|
+
if (typeof val === 'string') return JSON.stringify(val);
|
|
980
|
+
if (typeof val === 'number' || typeof val === 'boolean') return String(val);
|
|
981
|
+
if (Array.isArray(val)) {
|
|
982
|
+
try { return JSON.stringify(val); } catch { return '[Array]'; }
|
|
983
|
+
}
|
|
984
|
+
if (typeof val === 'object') {
|
|
985
|
+
try { return JSON.stringify(val, null, 2); } catch { return '[Object]'; }
|
|
986
|
+
}
|
|
987
|
+
return String(val);
|
|
988
|
+
}
|
|
989
|
+
|
|
841
990
|
// REPL input handling
|
|
842
991
|
replInput.addEventListener('keydown', (e) => {
|
|
843
992
|
if (e.key === 'Enter') {
|
package/package.json
CHANGED
package/src/repl.js
CHANGED
|
@@ -43,13 +43,13 @@ export class RipREPL {
|
|
|
43
43
|
this.historyFile = path.join(os.homedir(), '.rip_history');
|
|
44
44
|
this.reactiveVars = new Set(); // Track reactive variables across lines
|
|
45
45
|
this.cwd = process.cwd();
|
|
46
|
-
|
|
46
|
+
|
|
47
47
|
// Persisted variables across evaluations
|
|
48
48
|
this.vars = {};
|
|
49
|
-
|
|
49
|
+
|
|
50
50
|
// Module cache for linked modules
|
|
51
51
|
this.moduleCache = new Map();
|
|
52
|
-
|
|
52
|
+
|
|
53
53
|
// VM context with necessary globals
|
|
54
54
|
this.vmContext = vm.createContext({
|
|
55
55
|
console,
|
|
@@ -116,7 +116,7 @@ export class RipREPL {
|
|
|
116
116
|
injectReactiveRuntime() {
|
|
117
117
|
// Define reactive primitives in the VM context
|
|
118
118
|
const ctx = this.vmContext;
|
|
119
|
-
|
|
119
|
+
|
|
120
120
|
ctx.__currentEffect = null;
|
|
121
121
|
ctx.__pendingEffects = new Set();
|
|
122
122
|
|
|
@@ -367,11 +367,26 @@ export class RipREPL {
|
|
|
367
367
|
|
|
368
368
|
// Evaluate using vm.SourceTextModule (no temp files!)
|
|
369
369
|
async moduleEval(js) {
|
|
370
|
-
// Extract declared variables
|
|
370
|
+
// Extract declared variables (both let and const)
|
|
371
371
|
const declaredVars = new Set();
|
|
372
372
|
for (const match of js.matchAll(/^let\s+(\w+)/gm)) {
|
|
373
373
|
declaredVars.add(match[1]);
|
|
374
374
|
}
|
|
375
|
+
for (const match of js.matchAll(/^const\s+(\w+)\s*=/gm)) {
|
|
376
|
+
declaredVars.add(match[1]);
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
// Transform reactive declarations to persist in __vars
|
|
380
|
+
// const x = __state(...) → const x = __vars.x ?? (__vars.x = __state(...)); x;
|
|
381
|
+
// const x = __computed(...) → const x = __vars.x ?? (__vars.x = __computed(...)); x;
|
|
382
|
+
// const x = __effect(...) → const x = __vars.x ?? (__vars.x = __effect(...)); x;
|
|
383
|
+
// The trailing `x;` ensures the value is captured and displayed
|
|
384
|
+
js = js.replace(
|
|
385
|
+
/^const\s+(\w+)\s*=\s*((?:__state|__computed|__effect)\(.+\));?$/gm,
|
|
386
|
+
(match, varName, rhs) => {
|
|
387
|
+
return `const ${varName} = __vars['${varName}'] ?? (__vars['${varName}'] = ${rhs});\n${varName};`;
|
|
388
|
+
}
|
|
389
|
+
);
|
|
375
390
|
|
|
376
391
|
// Transform await import() to static imports (workaround for Bun bug #24217)
|
|
377
392
|
const dynamicImports = [];
|
|
@@ -388,20 +403,33 @@ export class RipREPL {
|
|
|
388
403
|
|
|
389
404
|
// Restore existing variables and remove duplicate declarations
|
|
390
405
|
const existingVars = Object.keys(this.vars);
|
|
391
|
-
|
|
406
|
+
const existingNonReactive = existingVars.filter(v => !this.reactiveVars.has(v));
|
|
407
|
+
|
|
408
|
+
for (const v of existingNonReactive) {
|
|
392
409
|
js = js.replace(new RegExp(`^let ${v};\\n?`, 'm'), '');
|
|
393
410
|
js = js.replace(new RegExp(`^let ${v}(\\s*=)`, 'm'), `${v}$1`);
|
|
394
411
|
}
|
|
395
412
|
|
|
396
|
-
// Build restore code
|
|
397
|
-
|
|
413
|
+
// Build restore code
|
|
414
|
+
// Non-reactive vars: let x = __vars['x'];
|
|
415
|
+
// Reactive vars: const x = __vars['x']; (they're already stored)
|
|
416
|
+
const nonReactiveRestore = existingNonReactive
|
|
398
417
|
.filter(k => k !== '_')
|
|
399
418
|
.map(v => `let ${v} = __vars['${v}'];`)
|
|
400
419
|
.join('\n');
|
|
401
420
|
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
421
|
+
const reactiveRestore = [...this.reactiveVars]
|
|
422
|
+
.filter(v => existingVars.includes(v))
|
|
423
|
+
.map(v => `const ${v} = __vars['${v}'];`)
|
|
424
|
+
.join('\n');
|
|
425
|
+
|
|
426
|
+
const restoreCode = [nonReactiveRestore, reactiveRestore].filter(Boolean).join('\n');
|
|
427
|
+
|
|
428
|
+
// Build save code (save non-reactive vars back to __vars)
|
|
429
|
+
// Reactive vars are already saved via the const transformation
|
|
430
|
+
const nonReactiveVars = [...new Set([...existingNonReactive, ...declaredVars])]
|
|
431
|
+
.filter(k => k !== '_' && !this.reactiveVars.has(k));
|
|
432
|
+
const saveCode = nonReactiveVars
|
|
405
433
|
.map(v => `if (typeof ${v} !== 'undefined') __vars['${v}'] = ${v};`)
|
|
406
434
|
.join('\n');
|
|
407
435
|
|
|
@@ -437,7 +465,14 @@ export { __result };
|
|
|
437
465
|
}
|
|
438
466
|
|
|
439
467
|
printResult(value) {
|
|
440
|
-
|
|
468
|
+
// For reactive values, show the actual value
|
|
469
|
+
let displayValue = value;
|
|
470
|
+
if (value && typeof value === 'object' && 'value' in value &&
|
|
471
|
+
(typeof value.valueOf === 'function' || typeof value.markDirty === 'function')) {
|
|
472
|
+
displayValue = value.value;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
const formatted = inspect(displayValue, {
|
|
441
476
|
colors: true,
|
|
442
477
|
depth: 3,
|
|
443
478
|
maxArrayLength: 100
|
|
@@ -539,8 +574,27 @@ ${colors.cyan}Tips:${colors.reset}
|
|
|
539
574
|
console.log(`${colors.bright}Defined variables:${colors.reset}`);
|
|
540
575
|
userVars.forEach(key => {
|
|
541
576
|
const value = this.vars[key];
|
|
542
|
-
const
|
|
543
|
-
|
|
577
|
+
const isReactive = this.reactiveVars.has(key);
|
|
578
|
+
|
|
579
|
+
// For reactive vars, display the .value and indicate the type
|
|
580
|
+
let displayValue, typeIndicator;
|
|
581
|
+
if (isReactive && value && typeof value === 'object' && 'value' in value) {
|
|
582
|
+
displayValue = value.value;
|
|
583
|
+
// Determine reactive type
|
|
584
|
+
if (typeof value.markDirty === 'function') {
|
|
585
|
+
typeIndicator = `${colors.magenta}~=${colors.reset}`; // computed
|
|
586
|
+
} else if (typeof value.run === 'function' || typeof value === 'function') {
|
|
587
|
+
typeIndicator = `${colors.magenta}~>${colors.reset}`; // effect
|
|
588
|
+
} else {
|
|
589
|
+
typeIndicator = `${colors.magenta}:=${colors.reset}`; // state
|
|
590
|
+
}
|
|
591
|
+
} else {
|
|
592
|
+
displayValue = value;
|
|
593
|
+
typeIndicator = `${colors.gray}=${colors.reset}`;
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
const preview = inspect(displayValue, { colors: true, depth: 0, maxArrayLength: 3 });
|
|
597
|
+
console.log(` ${colors.cyan}${key}${colors.reset} ${typeIndicator} ${preview}`);
|
|
544
598
|
});
|
|
545
599
|
|
|
546
600
|
if (this.vars._ !== undefined) {
|