tricu

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

inline-io-probe.mjs (9648B)


      1 import './source-probe-helpers.mjs';
      2 import {terminalHost} from './terminal-host.mjs';
      3 const assert=(condition,message)=>{if(!condition)throw Error(message);};
      4 const el=id=>document.getElementById(id),pause=ms=>new Promise(r=>setTimeout(r,ms));
      5 const input=(id,value)=>{if(id==='source-editor'){globalThis.__sourceEditor.replaceSource(value,true);return;}el(id).value=value;el(id).dispatchEvent(new Event('input'));};
      6 const change=(id,value)=>{el(id).value=value;el(id).dispatchEvent(new Event('change'));};
      7 async function wait(check,label){for(let i=0;i<3600;i++){if(check())return;await pause(25);}throw Error('Timeout '+label+' / '+el('status').textContent+' / '+el('operation-diagnostic').textContent);}
      8 const status=text=>wait(()=>el('status').textContent===text,text);
      9 export async function run(control,backend='haskell-wasm'){
     10  await status('Ready');
     11  assert(!el('expression-editor')&&!el('run')&&!document.querySelector('.expression-dock'),'No separate evaluation component');
     12  const base=new URL('../',document.querySelector('script[type=module][src]').src);
     13  const api=await import(new URL('dist/probe-api.js',base));
     14  const {createRuntimeSession}=api.runtimeSession,{Terminal}=api.ioTerminal;
     15  const releases=api.packages.packageIndex(await(await fetch(new URL('packages.json',base))).json());
     16  const lib=name=>api.packages.packageReleases(releases,'tricu-lib-'+name)[0].manifest;
     17  const ioDefinition='io = IO.io (IO.require IO.stdin readAction)';
     18  const fixture=`!import ${lib('io')} as IO
     19 !import ${lib('io-text')} as Text
     20 !export answer
     21 answer = 42
     22 readAction reader = IO.require (Text.readLine (Text.lineCursor reader) 256) (reply : case reply of
     23   [["line" value _] _] = IO.pure value
     24   [["eof"] _] = IO.pure ""
     25   _ = IO.fail "Invalid line reply")
     26 ${ioDefinition}
     27 wrong = t "other" (t 2 (t 0 t))
     28 unsupported = t "tricuIO" (t 3 (t 0 t))
     29 retired = t "tricuIO" (t 1 (t 11 t))
     30 loop = (x : x x) (x : x x)
     31 latent = t "tricuIO" (t 2 loop)
     32 write = IO.io (IO.require IO.stdout (writer : IO.require (Text.writeText writer "Explicit output") (_ : IO.pure t)))
     33 abort = IO.io (IO.fail loop)`;
     34  const session=await createRuntimeSession(backend,true);
     35  try{
     36   assert((await session.compile(Array.from({length:64},(_,i)=>`local${i} = ${i}`).join('\n'),'empty')).length===64,'Source exploration is not capped at 16 definitions');
     37   await session.compile(fixture,'empty');
     38   for(const name of ['io','write','latent','abort'])assert(await session.probeIO(name),'Supported IO envelope '+name);
     39   for(const name of ['answer','wrong','unsupported','retired'])assert(!await session.probeIO(name),'Non-IO or retired framing '+name);
     40   assert((await session.inspectDefinition('answer','number')).value==='42','Probes preserve compiled scope');
     41   assert((await session.runDefinitionIO('io','string',terminalHost(Terminal,'ฮป้›ช๐Ÿ™‚'))).value==='ฮป้›ช๐Ÿ™‚','Private IO definition runs directly');
     42   const output=terminalHost(Terminal,null);
     43   await session.runDefinitionIO('write','tricu',output);assert(output.terminalText==='Explicit output','Explicit IO executes once');
     44   let aborted=false;try{await session.runDefinitionIO('abort','tricu',terminalHost(Terminal,null));}catch(error){aborted=/IO failed/.test(String(error));}assert(aborted,'Failure does not force divergent diagnostic');
     45   assert((await session.inspectDefinition('answer','number')).value==='42','Failure preserves definitions');
     46  }finally{session.dispose();}
     47  document.querySelector('[data-compiler-example=identity]').click();input('source-editor',fixture);await status('Compiled');
     48  const button=name=>document.querySelector(`[aria-label="Inspect compiled definition ${name}"]`);
     49  const card=()=>document.querySelector('.definition-card'),view=()=>card().querySelector('pre');
     50  const form=()=>el('terminal-io').querySelector('form:has(input)');
     51  const output=()=>el('terminal-io').querySelector('[data-stream=output]').textContent;
     52  const open=async name=>{button(name).click();await pause(0);await wait(()=>!card().hidden&&!card().querySelector('select').disabled,'inspect '+name);};
     53  const advance=async check=>{for(let i=0;i<3600;i++){if(check())return;if(el('status').textContent==='Paused before action')el('next').click();await pause(25);}throw Error('Stepped IO did not progress');};
     54  await open('answer');assert(view().textContent==='42','Pure Auto inspection');
     55  assert(el('run-io').closest('section').hidden,'Pure binding has no IO run surface');
     56  assert(!card().querySelector('.definition-actions button').disabled,'Pure result can be copied/downloaded');button('answer').click();
     57  await open('latent');assert(card().textContent.includes('IO program ยท authenticated tricuIO envelope'),'Detection never forces divergent action');
     58  assert(el('terminal-io').textContent===''&&!el('input-form'),'Opening IO never executes');button('latent').click();
     59  // Structured Stop is not a hard interruption of divergent native reduction.
     60  // Exercise acknowledged cancellation below at a real pending terminal read.
     61  await open('write');await pause(100);assert(el('terminal-io').textContent==='','Detection emits nothing');el('run-io').click();await status('Finished');
     62  assert(output()==='Explicit output','Explicit inline output');el('run-io').click();await status('Finished');assert(output()==='Explicit output','Rerun clears terminal');button('write').click();
     63  await open('io');el('io-return-format').value='string';el('step-mode').checked=true;el('run-io').click();await status('Paused before action');
     64  assert(!form(),'Step stops before input admission');await advance(()=>!!form());
     65  assert(button('io').getAttribute('aria-disabled')==='true'&&button('io').getAttribute('aria-expanded')==='true','Active IO inspection stays highlighted');
     66  form().querySelector('input').value='a'.repeat(257);form().requestSubmit();assert(form()&&!form().hidden,'Invalid input remains retryable');
     67  form().querySelector('input').value='ฮป้›ช๐Ÿ™‚';form().requestSubmit();await advance(()=>el('status').textContent==='Finished');assert(view().textContent==='ฮป้›ช๐Ÿ™‚','Unicode return uses selected interpretation');
     68  el('step-mode').checked=false;el('run-io').click();await wait(()=>form(),'pending read');el('stop-io').click();await status('Stopped');
     69  assert(!form()&&!el('run-io').disabled,'Acknowledged Stop retires input and permits rerun');
     70  el('run-io').click();await wait(()=>form(),'reuse after Stop');card().querySelector('[aria-label="Close definition inspection"]').click();await status('Stopped');assert(card().hidden,'Closing active IO cancels and settles');
     71  await open('answer');assert(el('run-io').closest('section').hidden,'No stale IO controls');button('answer').click();
     72  await open('io');input('source-editor',fixture.replace(ioDefinition,'io = 7'));await status('Compiled');
     73  await wait(()=>view().textContent==='7','revision replaces IO card');assert(el('run-io').closest('section').hidden,'Edits invalidate IO recognition');
     74  const catalogue=await(await fetch(new URL('catalogue.json',base))).json(),names=catalogue.find(item=>item.id==='names-values');
     75  change('program-picker','hash');input('bundle-hash',names.hash);el('program-hash').requestSubmit();await status('Ready');
     76  assert(el('source-editor').hidden&&!el('package-definitions').hidden,'Source-free definition browser');
     77  [...el('package-definition-list').querySelectorAll('button')].find(b=>b.textContent==='answer').click();await wait(()=>view().textContent==='42'&&!card().querySelector('select').disabled,'source-free inspection');
     78  assert(card().parentElement.id==='package-definition-view','Source-free card remains visible');
     79  const conversation=catalogue.find(item=>item.id==='greet');input('bundle-hash',conversation.hash);el('program-hash').requestSubmit();await status('Ready');
     80  [...el('package-definition-list').querySelectorAll('button')].find(b=>b.textContent==='main').click();await wait(()=>!el('run-io').disabled,'source-free IO detection');
     81  const sendLine=async value=>{await wait(()=>form(),'line input');form().querySelector('input').value=value;form().requestSubmit();await status('Finished');};
     82  el('io-return-format').value='string';el('run-io').click();await sendLine('Source-free');assert(output()==='What is your name?\nHello, Source-free!\n','Source-free IO executes');
     83  change('program-picker','demo:greet');await status('Ready');await open('main');assert(el('terminal-io').textContent===''&&!el('input-form'),'Published IO is opt-in');
     84  el('io-return-format').value='string';el('run-io').click();await sendLine('Inline tester');assert(output().includes('Inline tester'),'Published conversation');
     85  el('run-io').click();await sendLine('');assert(output()==='What is your name?\nHello, !\n','Empty line is data');
     86  el('run-io').click();await wait(()=>form(),'EOF prompt');form().querySelector('button[type=button]').click();await status('Finished');assert(output()==='What is your name?\nEnd of input.\n'&&view().textContent==='','Explicit EOF is distinct');
     87  el('run-io').click();await wait(()=>form(),'cancel prompt');const stale=form();el('stop-io').click();await status('Stopped');
     88  assert(!form(),'Stop retires input');el('run-io').click();await wait(()=>form(),'rerun without compilation');stale.requestSubmit();await pause(50);assert(form(),'Retired form cannot consume new input');await sendLine('Still compiled');
     89  assert(output().includes('Still compiled'),'Stop preserves definitions');
     90  return {userAgent:navigator.userAgent,backend,checks:['v2-only framing; opaque latent action/failure; explicit private and public IO; scheduler stepping; Unicode and retry; EOF; rerun; acknowledged Stop and stale-form isolation; source revisions; source-free and pure inspection']};
     91 }