diff options
| author | Andrey Popp <8mayday@gmail.com> | 2022-03-13 23:26:51 +0300 |
|---|---|---|
| committer | Andrey Popp <8mayday@gmail.com> | 2022-03-13 23:26:51 +0300 |
| commit | e31ed99a5df67af7e80c8694c1d9ad5170c328f2 (patch) | |
| tree | 417cb22f9abe3e97110339f57aba0464a161bc6a /docs | |
| parent | f036d93326170963e69cba2f652811d6dcab81dd (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.js | 67 |
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; |
