tricu

A lazy functional language based on Tree Calculus
Log | Files | Refs | README | LICENSE

io-public-probe.mjs (9712B)


      1 import {wait,status,change,inspect,el,pause,input} from './source-probe-helpers.mjs';
      2 export async function run(_control,backend){
      3  const assert=(ok,message)=>{if(!ok)throw Error(message);},checks=[];
      4  const base=new URL('../',document.querySelector('script[type=module][src]').src);
      5  const {runtimeSession:{createRuntimeSession},ioTerminalView:{terminalView}}=await import(new URL('dist/probe-api.js',base));
      6  const fixture=await(await fetch('/io-source.json')).json(),s=await createRuntimeSession(backend,false);
      7  const legacy={before:async()=>{},after(){},read(){throw Error('legacy read invoked');},write(){throw Error('legacy write invoked');},running(){}};
      8  const parent=document.createElement('div');document.body.append(parent);
      9  let widget,send,closed,steps=0,submitted=false,guardTask;
     10  function host(mode='normal'){
     11   parent.replaceChildren();closed=false;submitted=false;
     12   return {...legacy,before:async()=>{steps++;},structured:{stepping:mode==='step',
     13    open(run,command){send=command;widget=terminalView(parent,run,reply=>command({run,kind:'reply',reply}),()=>command({run,kind:'stop'}));},
     14    event(event){widget.handle(event);if(event.kind==='read'&&mode==='stop'){
     15      guardTask=(async()=>{try{
     16       for(const operation of [()=>s.inspectDefinition('main','tree'),()=>s.compile('changed = 9','empty'),()=>s.runExpression('42','number','pure',legacy)]){
     17        let blocked=false;try{await operation();}catch(error){blocked=/pending|busy/.test(error.message);}assert(blocked,'unrelated work entered active v2 run');
     18       }
     19      }finally{send({run:event.run,kind:'stop'});}})();
     20     }
     21     if(event.kind==='read'&&mode==='input'){
     22      const form=parent.querySelector('form');if(form&&!form.hidden){if(!submitted){submitted=true;form.querySelector('input').value='位馃榾';form.requestSubmit();}else form.querySelector('button[type=button]').click();}
     23     }
     24    },
     25    close(_run,settled){assert(settled,'unacknowledged runtime loss');widget.finish();closed=true;},
     26   }};
     27  }
     28  try{
     29   await s.load(new Uint8Array(fixture.bytes),fixture.hash);
     30   assert(await s.probeIO('main'),'v2 inert recognition');assert(!await s.probeIO('direct'),'bare computation is not an envelope');
     31   for(const name of ['main','publicHandled','publicCancellation','publicBinary']){
     32    const out=await s.runDefinitionIO(name,'number',host());assert(out.value==='42'&&closed,'public compiled outcome '+name);
     33    const bytes=widget.snapshot().output.bytes,expected=name==='publicBinary'?[92,120,102,102,255,240,159]:[...new TextEncoder().encode(name==='publicCancellation'?'ready':'位馃榾e\u0301\r\n')];
     34    assert(JSON.stringify(bytes)===JSON.stringify(expected),'public exact output '+name);
     35   }
     36   assert((await s.runDefinitionIO('publicRoundTrip','number',host('input'))).value==='42','line and EOF outcome');
     37   assert(JSON.stringify(widget.snapshot().output.bytes)===JSON.stringify([...new TextEncoder().encode('位馃榾\n')]),'line/EOF byte preservation');
     38   checks.push('compiled direct/handled/cancellation/binary programs through public RuntimeSession; explicit line/EOF');
     39   const cases=[['string',{type:'string',value:'位馃榾\0'},'位馃榾\0'],['number',{type:'number',value:'184467440737095516160'},'184467440737095516160'],['bool',{type:'bool',value:true},true],['bytes',{type:'bytes',value:[0,255,240]},[0,255,240]]];
     40   for(const [type,literal,expected]of cases){const out=await s.run('returnValue',[literal],type,'io',host());assert(JSON.stringify(out.value)===JSON.stringify(expected),'full codec '+type);}
     41   for(const type of ['tree','list','tricu','triangle','dag','ternary']){
     42    const literal=type==='list'?{type:'list',value:[{type:'number',value:'42'}]}:{type:'tree',value:'200'};
     43    const observed=await s.run('returnValue',[literal],type,'io',host());assert(observed.type===type&&observed.value!==null,'full graph codec '+type);
     44    if(['tricu','triangle','dag','ternary'].includes(type)){const expected=await s.runExpression('t t t',type,'pure',legacy);// DAG binders reflect physical node numbering, not semantic identities.
     45     const alpha=text=>{const names=new Map();return text.replace(/:[fn][0-9]+/g,name=>{if(!names.has(name))names.set(name,':v'+names.size);return names.get(name);});};
     46     assert((type==='dag'?alpha(expected.value)===alpha(observed.value):expected.value===observed.value),'complete graph result differs from independent pure result '+type);}
     47   }
     48   const values=await s.run('returnValue',[{type:'list',value:[{type:'number',value:'42'},{type:'string',value:'hello'}]}],'values','io',host());
     49   assert(JSON.stringify(values.value)===JSON.stringify([{type:'number',value:'42'},{type:'string',value:'hello'}]),'values codec');
     50   checks.push('all public codecs including >64-bit number, full trees and decoded values');
     51   let rejected=false;try{await s.runDefinitionIO('publicRoundTrip','number',host('stop'));}catch(error){rejected=/cancelled/.test(error.message);}assert(rejected&&closed,'public structured cancellation');await guardTask;
     52   let badCodec=false;try{await s.run('returnValue',[{type:'tree',value:'10'}],'number','io',host());}catch{badCodec=true;}assert(badCodec&&closed,'codec failure after cleanup');
     53   assert((await s.runExpression('returnValue "expression result"','string','io',host())).value==='expression result','IO expression result');
     54   assert((await s.run('returnValue',[{type:'number',value:'42'}],'number','io',host('step'))).value==='42'&&steps>0,'public scheduler stepping');
     55   checks.push('structured Stop drains read, definitions remain usable; expression and stepping paths');
     56  }finally{s.dispose();parent.remove();}
     57  const doomed=await createRuntimeSession(backend,false);let abandoned=false;
     58  try{
     59   await doomed.load(new Uint8Array(fixture.bytes),fixture.hash);
     60   let rejected=false;try{await doomed.runDefinitionIO('publicRoundTrip','number',{...legacy,structured:{open(){},event(event){if(event.kind==='read')doomed.dispose();},close(_run,settled){abandoned=!settled;}}});}
     61   catch(error){rejected=/disposed|terminated/i.test(error.message);}assert(rejected&&abandoned,'emergency dispose must reject without claiming cleanup');
     62  }finally{doomed.dispose();}
     63  checks.push('emergency runtime disposal rejects idle read and explicitly does not acknowledge cleanup');
     64  // Real mounted editor + outer worker + nested native worker, not a mock host.
     65  await status('Ready');
     66  const index=await(await fetch(new URL('packages.json',base))).json();
     67  const io=index.releases.find(r=>r.title==='lib/io.tri'&&r.version==='0.4.0');assert(io,'authenticated complete library');
     68  const source=`!import ${io.manifest} as IO\n!export answer\n!export binary\n!export waiting\n!export caps\n!export classification\n!export divergentValue\ndivergentValue = IO.io (IO.pure ((x : x x) (x : x x)))\nbackendOf rows = case rows of\n  [["backend" value] | _] = value\n  [_ | rest] = backendOf rest\n  _ = "missing"\ncaps = IO.io (IO.require IO.capabilities (rows : IO.pure (backendOf rows)))\nloop = (x : x x) (x : x x)\nclassification = IO.io (IO.require (IO.inspect (IO.request "file" 1 "open" loop)) IO.pure)\nanswer = 42\nbinary = IO.io (do IO.require\n  writer <- IO.stdout\n  IO.writeAll writer [92 120 102 102 255 240 159]\n  IO.pure "returned text")\nwaiting = IO.io (do IO.require\n  reader <- IO.stdin\n  IO.readSome reader 4\n  IO.pure 42)`;
     69  document.querySelector('[data-compiler-example=identity]').click();input('source-editor',source);await status('Compiled');
     70  await inspect('caps');el('io-return-format').value='string';el('run-io').click();await status('Finished');assert(document.querySelector('.definition-card').querySelector('pre').textContent===backend,'actual native capabilities');
     71  await inspect('classification');el('io-return-format').value='values';el('run-io').click();await status('Finished');assert(document.querySelector('.definition-card').querySelector('pre').textContent.includes('unsupported'),'inert inspection leaves divergent arguments opaque');
     72  await inspect('binary');el('io-return-format').value='string';el('run-io').click();await status('Finished');
     73  assert(el('terminal-io').querySelector('[data-hex=output]').textContent==='5c 78 66 66 ff f0 9f','actual page lossless output');
     74  assert(document.querySelector('.definition-card').querySelector('pre').textContent==='returned text','actual page full returned text');
     75  await inspect('waiting');el('io-return-format').value='number';el('run-io').click();await wait(()=>el('terminal-io').querySelector('form input'),'v2 input');
     76  const old=el('terminal-io').querySelector('form');el('stop').click();assert(!el('stop').hidden,'Stop waits rather than disposing worker');
     77  await status('Stopped');assert(!el('terminal-io').querySelector('form'),'input physically retired');
     78  old.dispatchEvent(new Event('submit',{cancelable:true}));assert((await inspect('answer','number'))==='42','compiled state survives structured Stop');
     79  // Stop also bypasses a suspended scheduler turn.
     80  await inspect('waiting');el('step-mode').checked=true;el('run-io').click();await status('Paused before action');el('stop').click();await status('Stopped');el('step-mode').checked=false;
     81  assert((await inspect('answer','number'))==='42','compiled state survives paused Stop');
     82  await inspect('divergentValue');el('io-return-format').value='number';el('run-io').click();await status('IO settled; interpreting returned value');el('stop').click();await status('Stopped');
     83  el('compile').click();await status('Compiled');assert((await inspect('answer','number'))==='42','hard Stop after acknowledged IO settlement can rebuild');
     84  await pause(10);checks.push('actual page exact bytes/full return, pending-input and paused-step Stop, stale DOM isolation and reuse without recompilation');
     85  return {backend,checks};
     86 }