aboutsummaryrefslogtreecommitdiff
path: root/docs
diff options
context:
space:
mode:
authorAndrey Popp <8mayday@gmail.com>2022-03-13 23:26:51 +0300
committerAndrey Popp <8mayday@gmail.com>2022-03-13 23:26:51 +0300
commite31ed99a5df67af7e80c8694c1d9ad5170c328f2 (patch)
tree417cb22f9abe3e97110339f57aba0464a161bc6a /docs
parentf036d93326170963e69cba2f652811d6dcab81dd (diff)
Add repl preview
Repl now has an additional mode of evaluation where observable (visible outside of the evaluated expression) side effects are not allowed. More specifically all assignments and reassignments are *prohibited* unless they fall in the following categories: - Assignments and reassignments within the top level environment - Assignments and reassignments for the environments created during evaluation of the expression being previewed Examples: let repl = BQN.makerepl() repl.preview('42') // ok, pure repl.preview('x ← 42 ⋄ x + 1') // ok, x is from the top env repl('f ← {x ← 0 ⋄ F ← {x+↩𝕩} ⋄ f}') repl.preview('F 42') // fail! mutates x How it's implemented: - A global `preview` flag determines if we are in "preview" mode - During compilation of bytecode to JS we inject code which marks environments with `inpreview` flag - `true` means they've been created during "preview". - Top level environment always has `inpreview = true` as we have this handled by restoring it to the values before the "preview" eval started. - While setting variables we check if are in "preview" and if the target environment has `inpreview` flag, otherwise we fail.
Diffstat (limited to 'docs')
-rw-r--r--docs/bqn.js67
1 files changed, 53 insertions, 14 deletions
diff --git a/docs/bqn.js b/docs/bqn.js
index ea539d43..4bc4aead 100644
--- a/docs/bqn.js
+++ b/docs/bqn.js
@@ -46,6 +46,15 @@ let listkeys = x => {
let getv= (a,i) => { let v=a[i]; if (v===null) throw Error("Runtime: Variable referenced before definition"); return v; }
let get = x => x.e ? getv(x.e,x.p) : arr(x.map(c=>get(c)), x.sh);
+let preview = false;
+let inpreview = () => preview;
+
+let setc = (d, id, v) => {
+ if (preview && !id.e.inpreview)
+ throw {kind: 'previewError', message: 'side effects are not allowed'};
+ return set(d, id, v);
+}
+
let set = (d, id, v) => {
let eq = (a,b) => a.length===b.length && a.every((e,i)=>e===b[i]);
if (id.e) {
@@ -78,6 +87,7 @@ let chkM = (v,m) => { if (m.m!==v) throw Error("Runtime: Only a "+v+"-modifier c
let genjs = (B, p, L) => { // Bytecode -> Javascript compiler
let rD = 0;
let r = L?"let l=0;try{":"";
+ let set = L?"setc":"set"
let fin = L?"}catch(er){let s=L.map(p=>p[l]);s.sh=[1,2];let m=[s,er.message];m.loc=1;m.src=e.vid.src;m.sh=[2];er.message=m;throw er;}":"";
let szM = 1;
let rV = n => { szM=Math.max(szM,n+1); return 'v'+n; };
@@ -108,10 +118,10 @@ let genjs = (B, p, L) => { // Bytecode -> Javascript compiler
case 43: { let m=rG(); r+=rP("{match:1,v:"+m+"}"); break; }
case 44: { r+=rP("{match:1}"); break; }
case 47: { let i=rG(), v=rG(); r+="try{set(1,"+i+","+v+");}catch(e){break;}"; break; }
- case 48: { let i=rG(), v=rG(); r+=rP("set(1,"+i+","+v +")"); break; }
- case 49: { let i=rG(), v=rG(); r+=rP("set(0,"+i+","+v +")"); break; }
- case 50: { let i=rG(),f=rG(),x=rG(); r+=rP("set(0,"+i+",call("+f+","+x+",get("+i+")))"); break; }
- case 51: { let i=rG(),f=rG() ; r+=rP("set(0,"+i+",call("+f+ ",get("+i+")))"); break; }
+ case 48: { let i=rG(), v=rG(); r+=rP(set+"(1,"+i+","+v +")"); break; }
+ case 49: { let i=rG(), v=rG(); r+=rP(set+"(0,"+i+","+v +")"); break; }
+ case 50: { let i=rG(),f=rG(),x=rG(); r+=rP(set+"(0,"+i+",call("+f+","+x+",get("+i+")))"); break; }
+ case 51: { let i=rG(),f=rG() ; r+=rP(set+"(0,"+i+",call("+f+ ",get("+i+")))"); break; }
case 66: { let m=rG(); r+=rP("{vid:e.vid,m:"+m+",a:"+num()+"}"); break; }
case 64: { let v=rG(); r+=rP("readns("+v+",e.vid,"+num()+")"); break; }
}
@@ -135,20 +145,21 @@ let run = (B,O,F,S,L,T,src,env) => { // Bytecode, Objects, Blocks, Bodies, Locat
return [genjs(B, pos, L), vid];
}
+ let ginpreview = e => L ? (e + ".inpreview=inpreview()") : "";
let c,vid,def;
if (isnum(ind)) {
[c,vid] = gen(ind);
c="do {"+c+"} while (0);\nthrow Error('No matching case');\n";
if (useenv) { c = "const e=env;"+c; env.vid=vid; }
- else if (imm) c = "const e=[...e2];e.vid=vid;e.p=oe;"+c;
- else c = "const fn=(x, w)=>{const e=[...e2];e.vid=vid;e.p=oe;e[0]=fn;e[1]=x;e[2]=w;"+c+"};"+repdf[type]+"return fn;";
+ else if (imm) c = "const e=[...e2];"+ginpreview('e')+";e.vid=vid;e.p=oe;"+c;
+ else c = "const fn=(x, w)=>{const e=[...e2];"+ginpreview('e')+";e.vid=vid;e.p=oe;e[0]=fn;e[1]=x;e[2]=w;"+c+"};"+repdf[type]+"return fn;";
def = useenv ? "env" : ("new Array("+vid.length+").fill(null)");
} else {
if (imm !== +(ind.length<2)) throw "Internal error: malformed block info";
let cache=[]; // Avoid generating a shared case twice
vid=[]; let g = j => {
let [c,v] = cache[j] || (cache[j] = gen(j));
- c = "const e=[...e1];e.vid=vid["+vid.length+"];e.p=oe;e.length="+v.length+";e.fill(null,"+sp+");"+c;
+ c = "const e=[...e1];"+ginpreview('e')+";e.vid=vid["+vid.length+"];e.p=oe;e.length="+v.length+";e.fill(null,"+sp+");"+c;
vid.push(v);
return "do {"+c+"} while (0);\n"
}
@@ -157,11 +168,11 @@ let run = (B,O,F,S,L,T,src,env) => { // Bytecode, Objects, Blocks, Bodies, Locat
let e = js.length?"No matching case":"Left argument "+(i?"not allowed":"required");
return js.map(g).concat(["throw Error('"+e+"');\n"]).join("");
});
- let fn = b => "(x, w)=>{const e1=[...e2];e1[0]=fn;e1[1]=x;e1[2]=w;\n"+b+"\n};";
+ let fn = b => "(x, w)=>{const e1=[...e2];"+ginpreview('e1')+";e1[0]=fn;e1[1]=x;e1[2]=w;\n"+b+"\n};";
let combine = ([mon,dy]) =>
fn("if (w===undefined) {\n"+mon+"} else {\n"+dy+"}");
def = "new Array("+sp+").fill(null)";
- if (imm) c = "const e1=[...e2];"+cases[0];
+ if (imm) c = "const e1=[...e2];"+ginpreview('e1')+";"+cases[0];
else {
c = "const fn="+combine(cases)+repdf[type];
if (cases.length > 2) {
@@ -172,12 +183,12 @@ let run = (B,O,F,S,L,T,src,env) => { // Bytecode, Objects, Blocks, Bodies, Locat
}
}
- let de2 = "let e2="+def+";"
+ let de2 = "let e2="+def+";"+ginpreview('e2')+";"
if (type===0) c = de2+c;
if (type===1) c = "const mod=(f ) => {"+de2+" e2["+I+"]=mod;e2["+(I+1)+"]=f;" +c+"}; mod.m=1;return mod;";
if (type===2) c = "const mod=(f,g) => {"+de2+" e2["+I+"]=mod;e2["+(I+1)+"]=f;e2["+(I+2)+"]=g;"+c+"}; mod.m=2;return mod;";
- return Function("'use strict'; return (chkM,has,call,getv,get,set,llst,train2,train3,readns,O,L,env,vid) => D => oe => {"+c+"};")()
- (chkM,has,call,getv,get,set,llst,train2,train3,readns,O,L,env,vid);
+ return Function("'use strict'; return (chkM,has,call,getv,get,set,setc,llst,train2,train3,readns,O,L,env,vid,inpreview) => D => oe => {"+c+"};")()
+ (chkM,has,call,getv,get,set,setc,llst,train2,train3,readns,O,L,env,vid,inpreview);
});
D.forEach((d,i) => {D[i]=d(D)});
return D[0]([]);
@@ -581,9 +592,20 @@ let addprimitives = (state, p) => {
}
let rerepl = (repl, cmp, state) => {
let rd = repl>1 ? 0 : -1;
+ let vars0, names0, redef0;
let vars = [], names = [], redef = [];
+ vars.inpreview = true;
state.addrt = [names,redef];
- return (x,w) => {
+ let copyarr = (to,src) => {
+ to.length = src.length;
+ for (let i=0;i<to.length;i++) to[i] = src[i];
+ }
+ let f = (x,w) => {
+ if (preview) {
+ vars0 = vars.slice(0);
+ names0 = names.slice(0);
+ redef0 = redef.slice(0);
+ }
names.sh=redef.sh=[names.length];
let c = cmp(x,w);
let pnames = c[5][2][0];
@@ -591,8 +613,25 @@ let rerepl = (repl, cmp, state) => {
names.push(...newv.map(i=>pnames[i]));
redef.push(...newv.map(i=>rd));
vars .push(...newv.map(i=>null));
- return run(...c, vars);
+ try {
+ return run(...c, vars);
+ } finally {
+ if (preview) {
+ copyarr(vars, vars0);
+ copyarr(names, names0);
+ copyarr(redef, redef0);
+ }
+ }
}
+ f.preview = (x,w) => {
+ preview = true;
+ try {
+ return f(x,w);
+ } finally {
+ preview = false;
+ }
+ };
+ return f;
}
let primitives = dynsys(state => {
let gl=state.glyphs.flat(), rt=state.runtime;