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 }