tricu

An interpreted language for exploring Tree Calculus
Log | Files | Refs | README | LICENSE

browser-bridge.mjs (5639B)


      1 // Shared site's boundary validators/codecs; no JS evaluator executes programs.
      2 import {decodeBundle,sha256,bundleHash} from './site/bundle.js';
      3 import {literals,makeLiteral,outputType} from './site/codecs.js';
      4 import {exportTree,treeFormat} from './site/tree-export.js';
      5 const utf8=new TextEncoder();
      6 export function frame(fields){
      7   const values=fields.map(x=>typeof x==='string'?utf8.encode(x):x);
      8   const size=values.reduce((sum,x)=>sum+4+x.byteLength,0);
      9   if(size>16777216)throw Error('Input byte limit exceeded');
     10   const bytes=new Uint8Array(size),view=new DataView(bytes.buffer);let at=0;
     11   for(const value of values){view.setUint32(at,value.byteLength,true);at+=4;bytes.set(value,at);at+=value.byteLength;}
     12   return bytes;
     13 }
     14 export function dagBytes(nodes){
     15   if(!nodes.length||nodes.length>1000000)throw Error('DAG node limit exceeded');
     16   const bytes=new Uint8Array(8+nodes.length*9),view=new DataView(bytes.buffer);
     17   view.setUint32(0,nodes.length,true);view.setUint32(4,0,true);
     18   for(const [i,node] of nodes.entries()){
     19     const at=8+i*9;
     20     bytes[at]=node.kind==='leaf'?0:node.kind==='stem'?1:2;
     21     view.setUint32(at+1,node.kind==='stem'?node.child:node.kind==='fork'?node.left:0,true);
     22     view.setUint32(at+5,node.kind==='fork'?node.right:0,true);
     23   }
     24   return bytes;
     25 }
     26 export async function authenticatedBundle(buffer,hash){
     27   bundleHash(hash);
     28   if(!(buffer instanceof ArrayBuffer)||buffer.byteLength>16777216)throw Error('Bundle byte limit exceeded');
     29   const bytes=new Uint8Array(buffer.slice(0));
     30   if(await sha256(bytes)!==hash)throw Error('Bundle SHA-256 mismatch');
     31   return decodeBundle(bytes);
     32 }
     33 export function loadFields(bundle){return ['load',dagBytes(bundle.nodes),...bundle.exports.flatMap(e=>[e.name,String(e.root)])];}
     34 class DataGraph {
     35   constructor(){this.nodes=[{kind:'leaf'}];this.leaf=0;}
     36   add(node){if(this.nodes.length>=1000000)throw Error('Input DAG limit exceeded');this.nodes.push(node);return this.nodes.length-1;}
     37   stem(child){return this.add({kind:'stem',child});}
     38   fork(left,right){return this.add({kind:'fork',left,right});}
     39   fromTernary(text){
     40     const pending=[];let root;
     41     for(const c of text){
     42       if(root!==undefined)throw Error('Trailing ternary data');
     43       if(c==='1'||c==='2'){pending.push({arity:Number(c),children:[]});continue;}
     44       if(c!=='0')throw Error('Invalid ternary digit');
     45       let value=this.leaf;
     46       for(;;){
     47         if(!pending.length){root=value;break;}
     48         const frame=pending.at(-1);frame.children.push(value);
     49         if(frame.children.length<frame.arity)break;
     50         pending.pop();value=frame.arity===1?this.stem(frame.children[0]):this.fork(...frame.children);
     51       }
     52     }
     53     if(root===undefined||pending.length)throw Error('Truncated tree');
     54     return root;
     55   }
     56 }
     57 export function application(name,args=[]){
     58   if(typeof name!=='string'||!name||name.length>4096)throw Error('Invalid export name');
     59   const graph=new DataGraph(),roots=literals(args).map(value=>makeLiteral(graph,value));
     60   return [name,dagBytes(graph.nodes),...roots.map(String)];
     61 }
     62 export function evaluation(name,args,codec){const [n,...rest]=application(name,args);return ['evaluate',n,outputType(codec),...rest];}
     63 export function snapshotFields(name,args,limit,full=false){
     64   if(!Number.isInteger(limit)||limit<1||limit>(full?100000:256))throw Error('Invalid snapshot budget');
     65   const [n,...rest]=application(name,args);return [full?'snapshot':'preview',n,String(limit),...rest];
     66 }
     67 // Convert a COMPLETE runtime snapshot to the site's immutable DAG export input.
     68 // IDs are local to this snapshot; no Haskell handle or address is retained.
     69 export function snapshotBundle(snapshot,name){
     70   if(snapshot.truncated)throw Error('Snapshot limit exceeded; refusing partial export');
     71   const aliases=new Map(snapshot.aliases.map(a=>[a.from,a.to])),byId=new Map(snapshot.nodes.map(n=>[n.id,n]));
     72   const resolve=id=>{const seen=new Set();while(aliases.has(id)){if(seen.has(id))throw Error('Snapshot alias cycle');seen.add(id);id=aliases.get(id);}return id;};
     73   const nodes=[],mapped=new Map(),active=new Set(),root=resolve(snapshot.root),stack=[[root,false]];
     74   while(stack.length){
     75     const [raw,leave]=stack.pop(),id=resolve(raw);
     76     if(mapped.has(id))continue;
     77     const node=byId.get(id);if(!node)throw Error('Incomplete snapshot');
     78     if(!leave){
     79       if(active.has(id))throw Error('Cyclic tree cannot be exported as a DAG');
     80       active.add(id);stack.push([id,true]);for(const child of [...node.children].reverse())stack.push([child,false]);
     81     }else{
     82       active.delete(id);const cs=node.children.map(c=>mapped.get(resolve(c)));
     83       if(cs.some(c=>c===undefined))throw Error('Invalid snapshot order');
     84       mapped.set(id,nodes.length);nodes.push(node.kind==='leaf'?{kind:'leaf'}:node.kind==='stem'?{kind:'stem',child:cs[0]}:{kind:'fork',left:cs[0],right:cs[1]});
     85     }
     86   }
     87   return {nodes,roots:[mapped.get(root)],exports:[{name,root:mapped.get(root),kind:'term',abi:'arboricx.abi.tree.v1'}],metadata:{}};
     88 }
     89 export function exportedTree(bundle,name,format){return exportTree(bundle,name,treeFormat(format));}
     90 
     91 export function environmentFields(bundle,names){
     92   if(!Array.isArray(names)||names.length>4096||names.some(n=>typeof n!=='string'||!n||n.length>128)||new Set(names).size!==names.length)throw Error('Invalid environment bindings');
     93   const exports=new Map(bundle.exports.map(e=>[e.name,e.root]));
     94   return [dagBytes(bundle.nodes),...names.flatMap(name=>{if(!exports.has(name))throw Error('Missing environment binding '+name);return [name,String(exports.get(name))];})];
     95 }
     96 export function emptyEnvironment(){return [dagBytes([{kind:'leaf'}])];}