123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279(* Claude Code
*
* Copyright (C) 2026 Yoann Padioleau
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public License
* (LGPL) as published by the Free Software Foundation; either version
* 2 of the License, or (at your option) any later version.
*)openPcode(* See Pmachine.mli *)typemachine={program:Pcode.program;store:intarray;(* the stack: every frame, and the expressions *)mutablepc:int;mutablesp:int;(* the first free word *)mutablemp:int;(* the current frame *)mutableline:stringoption;(* the input line being read, and where *)mutablecol:int;out:Buffer.t;mutableexecuted:int;(* instructions so far *)}(* what stops the machine for a while, or for good *)typestop=Halted|Paused|Slice_over|Need_line|Need_randomofint|Failedofint*stringexceptionRuntimeofint*string(* Turbo Pascal's 64 KB of stack, in words of 16 bits *)letstack_words=32768letwrap(n:int):int=((n+32768)land0xFFFF)-32768letpush(m:machine)(v:int):unit=ifm.sp>=Array.lengthm.storethenraise(Runtime(202,"Stack overflow"));m.store.(m.sp)<-v;m.sp<-m.sp+1letpop(m:machine):int=m.sp<-m.sp-1;m.store.(m.sp)lettop(m:machine):int=m.store.(m.sp-1)(* the frame [d] static links out from the current one *)letbase(m:machine)(d:int):int=letrecgobd=ifd=0thenbelsegom.store.(b+1)(d-1)ingom.mpdletpad(width:int)(s:string):string=ifString.lengths>=widththenselseString.make(width-String.lengths)' '^s(*****************************************************************************)(* Reading *)(*****************************************************************************)(* the input line, when there is one left to read from *)letrest(m:machine):stringoption=matchm.linewithSomel->Some(String.sublm.col(String.lengthl-m.col))|None->None(* read(i): blanks skipped, the ends of lines included, then the
integer; None when a line must be typed first *)letread_integer(m:machine):intoption=matchm.linewith|None->None|Somel->letn=String.lengthlinwhilem.col<n&&(l.[m.col]=' '||l.[m.col]='\t')dom.col<-m.col+1done;ifm.col>=nthenbeginm.line<-None;Noneendelsebeginletstart=m.colinifl.[m.col]='-'||l.[m.col]='+'thenm.col<-m.col+1;whilem.col<n&&l.[m.col]>='0'&&l.[m.col]<='9'dom.col<-m.col+1done;matchint_of_string_opt(String.sublstart(m.col-start))with|Somev->Some(wrapv)|None->raise(Runtime(106,"Invalid numeric format"))end(*****************************************************************************)(* Executing *)(*****************************************************************************)letcompare(m:machine)(f:int->int->bool):unit=letb=popminleta=popminpushm(iffabthen1else0)letarith(m:machine)(f:int->int->int):unit=letb=popminleta=popminpushm(wrap(fab))(* one instruction; what stops the machine, or None to go on *)letinstruction(m:machine):stopoption=leti=m.program.code.(m.pc)inm.pc<-m.pc+1;letagain()=m.pc<-m.pc-1inmatchiwith|Ldcn->pushmn;None|Lod(d,o)->pushmm.store.(basemd+o);None|Lda(d,o)->pushm(basemd+o);None|Str(d,o)->m.store.(basemd+o)<-popm;None|Indo->leta=popminpushmm.store.(a+o);None|Sto->letv=popminleta=popminm.store.(a)<-v;None|Ldmn->leta=popminfork=0ton-1dopushmm.store.(a+k)done;None|Stmn->m.sp<-m.sp-n;letwords=m.spinleta=popminArray.blitm.storewordsm.storean;None|Ixan->letk=popminleta=popminpushm(a+(k*n));None|Incn->pushm(wrap(popm+n));None|Chk(lo,hi)->letv=topminifv<lo||v>hithenraise(Runtime(201,"Range check error"))elseNone|Adi->arithm(+);None|Sbi->arithm(-);None|Mpi->arithm(*);None|Dvi|Mod->iftopm=0thenraise(Runtime(200,"Division by zero"));arithm(ifi=Dvithen(/)else(mod));None|Ngi->pushm(wrap(-popm));None|Abi->pushm(wrap(abs(popm)));None|Sqi->letv=popminpushm(wrap(v*v));None|Odd->pushm(popmland1);None|Equ->comparem(=);None|Neq->comparem(<>);None|Les->comparem(<);None|Leq->comparem(<=);None|Grt->comparem(>);None|Geq->comparem(>=);None|And->arithm(land);None|Ior->arithm(lor);None|Not->pushm(1-popm);None|Ujpl->m.pc<-l;None|Fjpl->ifpopm=0thenm.pc<-l;None|Mstd->letstatic=basemdinpushm0;pushmstatic;pushmm.mp;pushm0;None|Cup(n,l)->letframe=m.sp-n-Pcode.markinm.store.(frame+3)<-m.pc;m.mp<-frame;m.pc<-l;None|Entn->letstop=m.mp+ninifstop>=Array.lengthm.storethenraise(Runtime(202,"Stack overflow"));Array.fillm.storem.sp(stop-m.sp)0;m.sp<-stop;None|Retp|Retf->letframe=m.mpinm.pc<-m.store.(frame+3);m.mp<-m.store.(frame+2);(* a function's result, in its frame's first word, stays on top *)m.sp<-(ifi=Retfthenframe+1elseframe);None|Stp->SomeHalted|Cspc->(matchcwith|Wri->letw=popminBuffer.add_stringm.out(padw(string_of_int(popm)));None|Wrc->letw=popminBuffer.add_stringm.out(padw(String.make1(Char.chr(popmland255))));None|Wrb->letw=popminBuffer.add_stringm.out(padw(ifpopm<>0then"TRUE"else"FALSE"));None|Wrss->letw=popminBuffer.add_stringm.out(padws);None|Wln->Buffer.add_charm.out'\n';None|Rdi->(matchread_integermwith|Somev->leta=popminm.store.(a)<-v;None|None->again();SomeNeed_line)|Rdc->(matchm.linewith|None->again();SomeNeed_line|Somel->leta=popminifm.col>=String.lengthlthenbegin(* the end of a line reads as a space *)m.store.(a)<-Char.code' ';m.line<-Noneendelsebeginm.store.(a)<-Char.codel.[m.col];m.col<-m.col+1end;None)|Rln->(matchm.linewithNone->again();SomeNeed_line|Some_->m.line<-None;None)|Eol->(matchrestmwithNone->again();SomeNeed_line|Somer->pushm(ifr=""then1else0);None)|Rnd->letn=popminifn<=0thenraise(Runtime(201,"Range check error"))elseSome(Need_randomn))(*****************************************************************************)(* A machine to pause *)(*****************************************************************************)letstart(program:Pcode.program):machine={program;store=Array.makestack_words0;pc=0;sp=0;mp=0;line=None;col=0;out=Buffer.create256;executed=0}(* instructions until something stops the machine, [k] at most; [pause]
asked before each
claude: the loop a local function without the optional argument:
resume calling itself with ~pause was a tail call natively, but not
compiled to JavaScript (a call through the optional argument's
closure), and a frame's budget of instructions, a stack frame each,
overflowed the browser's stack -- Run froze TinyTurboPascal on the web
old: let rec resume ?(pause = ...) m k = ... resume ~pause m (k - 1) *)letresume?(pause=fun_->false)(m:machine)(k:int):stop=letrecgok=ifk=0thenSlice_overelseifpausemthenPausedelsematchinstructionmwith|None->m.executed<-m.executed+1;go(k-1)|Somestop->stop|exceptionRuntime(code,msg)->Failed(code,msg)|exceptionInvalid_argument_->Failed(204,"Invalid address")ingokletgive_line(m:machine)(l:string):unit=m.line<-Somel;m.col<-0letgive_random(m:machine)(k:int):unit=pushmkletoutput(m:machine):string=lets=Buffer.contentsm.outinBuffer.clearm.out;sletpc(m:machine)=m.pcletsp(m:machine)=m.spletmp(m:machine)=m.mpletword(m:machine)(a:int)=m.store.(a)letexecuted(m:machine)=m.executed(*****************************************************************************)(* A machine running on a teletype *)(*****************************************************************************)letrun(program:Pcode.program):unitTalk.talk=letm=startprogramin(* what was written, printed before the machine stops *)letflush(k:unit->unitTalk.talk):unitTalk.talk=lets=Buffer.contentsm.outinBuffer.clearm.out;ifs=""thenk()elseTalk.Print(s,k())inletrecgo()=matchresumem5000with|Paused->assertfalse(* no pause asked *)|Halted->flush(fun()->Talk.Done())|Slice_over->flush(fun()->Talk.Stepgo)|Need_line->flush(fun()->Talk.Read_line(funl->give_lineml;go()))|Need_randomn->flush(fun()->Talk.Random(n,funk->give_randommk;go()))|Failed(code,msg)->letline=ifm.pc>0thenprogram.lines.(m.pc-1)else0inflush(fun()->Talk.Print(Printf.sprintf"\nRuntime error %d at line %d: %s\n"codelinemsg,Talk.Done()))ingo()letexecute(source:string)(answers:stringlist):string=matchPascal_compile.compilesourcewith|Okp->Talk.run(runp)answers|Errore->Printf.sprintf"Error at %d:%d: %s\n"e.linee.cole.message