123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514(* 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.
*)(* See Ps_machine.mli *)moduleG=Ps_graphicstypevalue=|Intofint|Realoffloat|Boolofbool|Nameofstring|Literalofstring|Stringofstring|Arrayofarray_|Dictof(string,value)Hashtbl.t|Operatorofstring|Fontoffloat|Mark|Nullandarray_={items:valuearray;spans:(int*int)array;exec:bool}typepaint={lines:((float*float)list*bool)list;rgb:float*float*float;how:how}andhow=Fill|Strokeoffloattypehost={glyph:char->(float*float)listlist*float}typestatus=Running|Done|Failedofstring(*****************************************************************************)(* The machine *)(*****************************************************************************)(* the graphics state: what gsave saves and grestore brings back *)typegstate={ctm:G.matrix;path:G.segmentlist;(* reversed *)current:(float*float)option;(* in device space *)start:(float*float)option;(* the subpath's first point *)rgb:float*float*float;line_width:float;font:float;}(* what is being run: the program's text from a place, a procedure
from an item, or a loop and what is left of it *)typeframe=|Sourceofint|Runofarray_*int|Repeatofint*array_|Foroffloat*float*float*bool*array_(* the value, the step, the limit, integers? *)|Loopofarray_|Forallofvaluelistlist*array_(* what each turn pushes *)typet={text:string;host:host;operands:valuelist;(* top first *)exec:framelist;dicts:(string,value)Hashtbl.tlist;(* userdict, then systemdict, at the bottom *)gs:gstate;saved:gstatelist;painted:paintlist;(* reversed *)finished:paintlistlist;(* reversed *)printed:stringlist;(* reversed *)state:status;last:(int*int)option;count:int;}(* an error's name, as the Red Book names them; [Offending] adds the
command that raised it *)exceptionErrorofstringexceptionOffendingofstring*stringletinitial_gs={ctm=G.identity;path=[];current=None;start=None;rgb=(0.,0.,0.);line_width=1.;font=0.}(*****************************************************************************)(* Values *)(*****************************************************************************)letnumber_textf=lets=Printf.sprintf"%g"finifString.containss'.'||String.containss'e'||String.containss'n'||String.containss'i'thenselses^".0"letrecshowv=matchvwith|Intn->string_of_intn|Realf->number_textf|Boolb->string_of_boolb|Namen->n|Literaln->"/"^n|Strings->"("^s^")"|Arraya->letinside=String.concat" "(Array.to_list(Array.mapshowa.items))inifa.execthen"{"^inside^"}"else"["^inside^"]"|Dict_->"-dict-"|Operatorn->"--"^n^"--"|Font_->"-font-"|Mark->"-mark-"|Null->"null"(* what = prints: a string or a name as its characters *)lettext_ofv=matchvwithStrings->s|Namen|Literaln->n|v->showvletpushvm={mwithoperands=v::m.operands}letpopm=matchm.operandswithv::rest->(v,{mwithoperands=rest})|[]->raise(Error"stackunderflow")letnum=functionIntn->float_of_intn|Realf->f|_->raise(Error"typecheck")letpop_numm=letv,m=popmin(numv,m)letpop_intm=matchpopmwithIntn,m->(n,m)|_->raise(Error"typecheck")letpop_boolm=matchpopmwithBoolb,m->(b,m)|_->raise(Error"typecheck")letpop_procm=matchpopmwithArraya,m->(a,m)|_->raise(Error"typecheck")letpop_keym=matchpopmwith(Literalk|Stringk|Namek),m->(k,m)|_->raise(Error"typecheck")letreclookupdictskey=matchdictswith[]->None|d::rest->(matchHashtbl.find_optdkeywithSomev->Somev|None->lookuprestkey)letrun_frameam={mwithexec=Run(a,0)::m.exec}letoutputsm={mwithprinted=s::m.printed}(*****************************************************************************)(* Operators *)(*****************************************************************************)(* integers stay integers when both are, as the Red Book's add does *)letarithfiffm=letb,m=popminleta,m=popminmatch(a,b)withIntx,Inty->push(Int(fixy))m|_->push(Real(ff(numa)(numb)))mletreal1fm=letx,m=pop_numminpush(Real(fx))mletdegr=r*.180./.Float.piletradd=d*.Float.pi/.180.letrecequalab=match(a,b)with|(Int_|Real_),(Int_|Real_)->numa=numb|(Namex|Literalx|Stringx),(Namey|Literaly|Stringy)->x=y|Arrayx,Arrayy->x.items==y.items|Dictx,Dicty->x==y|_->a=b&&equal_simpleaandequal_simple=functionBool_|Mark|Null|Operator_|Font_->true|_->falseletcompare_optestm=letb,m=popminleta,m=popminletc=match(a,b)withStringx,Stringy->comparexy|_->compare(numa)(numb)inpush(Bool(testc))mletlogicfbfim=letb,m=popminleta,m=popminmatch(a,b)withBoolx,Booly->push(Bool(fbxy))m|Intx,Inty->push(Int(fixy))m|_->raise(Error"typecheck")(* the top [n] operands, top first, and the rest *)letrectakenl=ifn=0then([],l)elsematchlwithx::rest->leta,b=take(n-1)restin(x::a,b)|[]->raise(Error"stackunderflow")(* what forall pushes at each turn: an item, a character's code, or a
key and its value *)letturns_ofv=matchvwith|Arraya->List.map(funx->[x])(Array.to_lista.items)|Strings->List.init(String.lengths)(funi->[Int(Char.codes.[i])])|Dictd->Hashtbl.fold(funkvacc->[Literalk;v]::acc)d[]|_->raise(Error"typecheck")letgsfm={mwithgs=fm.gs}letcurrentm=matchm.gs.currentwithSomep->p|None->raise(Error"nocurrentpoint")letadd_segmentsm=gs(fung->{gwithpath=s::g.path})mletmove_topm=gs(fung->{gwithpath=G.Movep::g.path;current=Somep;start=Somep})mletline_topm=ignore(currentm);gs(fung->{gwithpath=G.Linep::g.path;current=Somep})mletpainthowm=letlines=G.flatten(List.revm.gs.path)inletm=iflines=[]thenmelse{mwithpainted={lines;rgb=m.gs.rgb;how}::m.painted}ings(fung->{gwithpath=[];current=None;start=None})mlethsbhsb=leti=Float.to_int(Float.floor(h*.6.))mod6andf=(h*.6.)-.Float.floor(h*.6.)inletp=b*.(1.-.s)andq=b*.(1.-.(s*.f))andt=b*.(1.-.(s*.(1.-.f)))inmatchiwith0->(b,t,p)|1->(q,b,p)|2->(p,b,t)|3->(p,q,b)|4->(t,p,b)|_->(b,p,q)(* a string's letters, stroked from the current point *)letshow_textsm=letsize=m.gs.fontinifsize=0.thenraise(Error"invalidfont");letux,uy=G.transform(G.invertm.gs.ctm)(currentm)inletstrokes,x=String.fold_left(fun(acc,x)ch->letlines,advance=m.host.glyphchinletplaced=List.map(funl->(List.map(fun(gx,gy)->G.transformm.gs.ctm(x+.(size*.gx),uy+.(size*.gy)))l,false))linesin(placed@acc,x+.(size*.advance)))([],ux)sinletm=ifstrokes=[]thenmelse{mwithpainted={lines=strokes;rgb=m.gs.rgb;how=Stroke(size*.0.07*.G.scale_ofm.gs.ctm)}::m.painted}ings(fung->{gwithcurrent=Some(G.transformg.ctm(x,uy))})mletwidth_ofsm=String.fold_left(funwch->w+.(m.gs.font*.snd(m.host.glyphch)))0.sletapply(op:string)(m:t):t=matchopwith(* the stack *)|"pop"->snd(popm)|"exch"->letb,m=popminleta,m=popminpusha(pushbm)|"dup"->leta,m=popminpusha(pusham)|"copy"->letn,m=pop_intminlettop,_=takenm.operandsin{mwithoperands=top@m.operands}|"index"->letn,m=pop_intmin(matchList.nth_optm.operandsnwithSomevwhenn>=0->pushvm|_->raise(Error"rangecheck"))|"roll"->letj,m=pop_intminletn,m=pop_intminifn=0thenmelselettop,rest=takenm.operandsin(* bottom first, rotated j places toward the top *)letbottom_first=List.revtopinletj=((jmodn)+n)modninleta,b=take(n-j)bottom_firstin{mwithoperands=List.rev(b@a)@rest}|"clear"->{mwithoperands=[]}|"count"->push(Int(List.lengthm.operands))m|"mark"|"["->pushMarkm|"cleartomark"->letrecdrop=functionMark::rest->rest|_::rest->droprest|[]->raise(Error"unmatchedmark")in{mwithoperands=dropm.operands}|"counttomark"->letrecni=functionMark::_->i|_::rest->n(i+1)rest|[]->raise(Error"unmatchedmark")inpush(Int(n0m.operands))m|"]"->letrecgatheracc=functionMark::rest->(acc,rest)|v::rest->gather(v::acc)rest|[]->raise(Error"unmatchedmark")inletitems,rest=gather[]m.operandsinpush(Array{items=Array.of_listitems;spans=[||];exec=false}){mwithoperands=rest}(* arithmetic *)|"add"->arith(+)(+.)m|"sub"->arith(-)(-.)m|"mul"->arith(*)(*.)m|"div"->letb,m=pop_numminleta,m=pop_numminifb=0.thenraise(Error"undefinedresult")elsepush(Real(a/.b))m|"idiv"->letb,m=pop_intminleta,m=pop_intminifb=0thenraise(Error"undefinedresult")elsepush(Int(a/b))m|"mod"->letb,m=pop_intminleta,m=pop_intminifb=0thenraise(Error"undefinedresult")elsepush(Int(amodb))m|"neg"->(matchpopmwithIntn,m->push(Int(-n))m|v,m->push(Real(-.numv))m)|"abs"->(matchpopmwithIntn,m->push(Int(absn))m|v,m->push(Real(Float.abs(numv)))m)|"sqrt"->real1Float.sqrtm|"sin"->real1(fund->Float.sin(radd))m|"cos"->real1(fund->Float.cos(radd))m|"atan"->letden,m=pop_numminletn,m=pop_numminleta=deg(Float.atan2nden)inpush(Real(ifa<0.thena+.360.elsea))m|"exp"->lete,m=pop_numminletb,m=pop_numminpush(Real(b**e))m|"ln"->real1Float.logm|"log"->real1Float.log10m|"round"|"floor"|"ceiling"|"truncate"->(letf=matchopwith"round"->Float.round|"floor"->Float.floor|"ceiling"->Float.ceil|_->Float.truncinmatchpopmwithIntn,m->push(Intn)m|v,m->push(Real(f(numv)))m)|"cvi"->letx,m=pop_numminpush(Int(Float.to_intx))m|"cvr"->letx,m=pop_numminpush(Realx)m(* comparison and logic *)|"eq"->letb,m=popminleta,m=popminpush(Bool(equalab))m|"ne"->letb,m=popminleta,m=popminpush(Bool(not(equalab)))m|"gt"->compare_op(func->c>0)m|"ge"->compare_op(func->c>=0)m|"lt"->compare_op(func->c<0)m|"le"->compare_op(func->c<=0)m|"and"->logic(&&)(land)m|"or"->logic(||)(lor)m|"xor"->logic(<>)(lxor)m|"not"->(matchpopmwithBoolb,m->push(Bool(notb))m|Intn,m->push(Int(lnotn))m|_->raise(Error"typecheck"))(* control: operators that take procedures *)|"exec"->(matchpopmwithArraya,mwhena.exec->run_frameam|v,m->pushvm)|"if"->letp,m=pop_procminletb,m=pop_boolminifbthenrun_framepmelsem|"ifelse"->letq,m=pop_procminletp,m=pop_procminletb,m=pop_boolminrun_frame(ifbthenpelseq)m|"repeat"->letp,m=pop_procminletn,m=pop_intmin{mwithexec=Repeat(n,p)::m.exec}|"for"->letp,m=pop_procminletlimit,m=pop_numminletinc,m=popminletinit,m=popminletints=match(init,inc)withInt_,Int_->true|_->falsein{mwithexec=For(numinit,numinc,limit,ints,p)::m.exec}|"loop"->letp,m=pop_procmin{mwithexec=Loopp::m.exec}|"forall"->letp,m=pop_procminletv,m=popmin{mwithexec=Forall(turns_ofv,p)::m.exec}|"exit"->letrecout=function|(Repeat_|For_|Loop_|Forall_)::rest->rest|Run_::rest->outrest|_->raise(Error"invalidexit")in{mwithexec=outm.exec}|"quit"->{mwithexec=[];state=Done}(* dictionaries *)|"def"->letv,m=popminletk,m=pop_keyminHashtbl.replace(List.hdm.dicts)kv;m|"dict"->let_,m=pop_intminpush(Dict(Hashtbl.create16))m|"begin"->(matchpopmwithDictd,m->{mwithdicts=d::m.dicts}|_->raise(Error"typecheck"))|"end"->ifList.lengthm.dicts<=2thenraise(Error"dictstackunderflow")else{mwithdicts=List.tlm.dicts}|"currentdict"->push(Dict(List.hdm.dicts))m|"load"->letk,m=pop_keymin(matchlookupm.dictskwithSomev->pushvm|None->raise(Error"undefined"))|"store"->letv,m=popminletk,m=pop_keyminletd=matchList.find_opt(fund->Hashtbl.memdk)m.dictswithSomed->d|None->List.hdm.dictsinHashtbl.replacedkv;m|"known"->letk,m=pop_keymin(matchpopmwithDictd,m->push(Bool(Hashtbl.memdk))m|_->raise(Error"typecheck"))(* arrays and strings *)|"array"->letn,m=pop_intminpush(Array{items=Array.make(max0n)Null;spans=[||];exec=false})m|"string"->letn,m=pop_intminpush(String(String.make(max0n)' '))m|"length"->(matchpopmwith|Arraya,m->push(Int(Array.lengtha.items))m|Strings,m->push(Int(String.lengths))m|Dictd,m->push(Int(Hashtbl.lengthd))m|(Names|Literals),m->push(Int(String.lengths))m|_->raise(Error"typecheck"))|"get"->(letkey,m=popminmatch(popm,key)with|(Arraya,m),Inti->ifi<0||i>=Array.lengtha.itemsthenraise(Error"rangecheck")elsepusha.items.(i)m|(Strings,m),Inti->ifi<0||i>=String.lengthsthenraise(Error"rangecheck")elsepush(Int(Char.codes.[i]))m|(Dictd,m),(Literalk|Namek|Stringk)->(matchHashtbl.find_optdkwithSomev->pushvm|None->raise(Error"undefined"))|_->raise(Error"typecheck"))|"put"->(letv,m=popminletkey,m=popminmatch(popm,key)with|(Arraya,m),Inti->ifi<0||i>=Array.lengtha.itemsthenraise(Error"rangecheck")else(a.items.(i)<-v;m)|(Dictd,m),(Literalk|Namek|Stringk)->Hashtbl.replacedkv;m|_->raise(Error"typecheck"))|"aload"->(matchpopmwithArraya,m->push(Arraya)(Array.fold_left(funmv->pushvm)ma.items)|_->raise(Error"typecheck"))|"cvx"->(matchpopmwithLiteraln,m->push(Namen)m|Arraya,m->push(Array{awithexec=true})m|v,m->pushvm)|"cvlit"->(matchpopmwithNamen,m->push(Literaln)m|Arraya,m->push(Array{awithexec=false})m|v,m->pushvm)|"cvn"->letk,m=pop_keyminpush(Literalk)m|"cvs"->let_,m=popminletv,m=popminpush(String(text_ofv))m(* printing, to the transcript *)|"="->letv,m=popminoutput(text_ofv)m|"=="->letv,m=popminoutput(showv)m|"print"->(matchpopmwithStrings,m->outputsm|_->raise(Error"typecheck"))|"pstack"->List.fold_left(funmv->output(showv)m)mm.operands|"stack"->List.fold_left(funmv->output(text_ofv)m)mm.operands(* the path *)|"newpath"->gs(fung->{gwithpath=[];current=None;start=None})m|"moveto"->lety,m=pop_numminletx,m=pop_numminmove_to(G.transformm.gs.ctm(x,y))m|"rmoveto"->letdy,m=pop_numminletdx,m=pop_numminletcx,cy=currentmandddx,ddy=G.dtransformm.gs.ctm(dx,dy)inmove_to(cx+.ddx,cy+.ddy)m|"lineto"->lety,m=pop_numminletx,m=pop_numminline_to(G.transformm.gs.ctm(x,y))m|"rlineto"->letdy,m=pop_numminletdx,m=pop_numminletcx,cy=currentmandddx,ddy=G.dtransformm.gs.ctm(dx,dy)inline_to(cx+.ddx,cy+.ddy)m|"curveto"|"rcurveto"->letys,m=List.fold_left(fun(acc,m)_->letv,m=pop_nummin(v::acc,m))([],m)[1;2;3;4;5;6]inletcx,cy=currentminletpointi=letx=List.nthys(2*i)andy=List.nthys((2*i)+1)inifop="curveto"thenG.transformm.gs.ctm(x,y)elseletdx,dy=G.dtransformm.gs.ctm(x,y)in(cx+.dx,cy+.dy)inletp3=point2ings(fung->{gwithpath=G.Curve(point0,point1,p3)::g.path;current=Somep3})m|"arc"|"arcn"->leta2,m=pop_numminleta1,m=pop_numminletr,m=pop_numminlety,m=pop_numminletx,m=pop_numminletp0,curves=G.arc(x,y)ra1a2~clockwise:(op="arcn")inlett=G.transformm.gs.ctminletm=matchm.gs.currentwithSome_->line_to(tp0)m|None->move_to(tp0)minList.fold_left(funm(c1,c2,p)->gs(fung->{gwithpath=G.Curve(tc1,tc2,tp)::g.path;current=Some(tp)})m)mcurves|"closepath"->(matchm.gs.currentwithNone->m|Some_->gs(fung->{gwithcurrent=g.start})(add_segmentG.Closem))|"currentpoint"->letx,y=G.transform(G.invertm.gs.ctm)(currentm)inpush(Realy)(push(Realx)m)(* painting *)|"fill"|"eofill"->paintFillm|"stroke"->paint(Stroke(m.gs.line_width*.G.scale_ofm.gs.ctm))m|"setlinewidth"->letw,m=pop_nummings(fung->{gwithline_width=w})m|"setgray"->letv,m=pop_nummings(fung->{gwithrgb=(v,v,v)})m|"setrgbcolor"->letb,m=pop_numminletg',m=pop_numminletr,m=pop_nummings(fung->{gwithrgb=(r,g',b)})m|"sethsbcolor"->letb,m=pop_numminlets,m=pop_numminleth,m=pop_nummings(fung->{gwithrgb=hsbhsb})m|"gsave"->{mwithsaved=m.gs::m.saved}|"grestore"->(matchm.savedwithg::rest->{mwithgs=g;saved=rest}|[]->m)(* the coordinates *)|"translate"->lety,m=pop_numminletx,m=pop_nummings(fung->{gwithctm=G.concat(G.translationxy)g.ctm})m|"scale"->lety,m=pop_numminletx,m=pop_nummings(fung->{gwithctm=G.concat(G.scalingxy)g.ctm})m|"rotate"->leta,m=pop_nummings(fung->{gwithctm=G.concat(G.rotationa)g.ctm})m|"showpage"->{mwithfinished=List.revm.painted::m.finished;painted=[];gs=initial_gs;saved=[]}(* text *)|"findfont"->let_,m=pop_keyminpush(Font1.)m|"scalefont"->letn,m=pop_nummin(matchpopmwithFonts,m->push(Font(s*.n))m|_->raise(Error"typecheck"))|"setfont"->(matchpopmwithFonts,m->gs(fung->{gwithfont=s})m|_->raise(Error"typecheck"))|"show"->(matchpopmwithStrings,m->show_textsm|_->raise(Error"typecheck"))|"stringwidth"->(matchpopmwithStrings,m->push(Real0.)(push(Real(width_ofsm))m)|_->raise(Error"typecheck"))|_->raise(Error"undefined")letoperators=["pop";"exch";"dup";"copy";"index";"roll";"clear";"count";"mark";"[";"]";"cleartomark";"counttomark";"add";"sub";"mul";"div";"idiv";"mod";"neg";"abs";"sqrt";"sin";"cos";"atan";"exp";"ln";"log";"round";"floor";"ceiling";"truncate";"cvi";"cvr";"eq";"ne";"gt";"ge";"lt";"le";"and";"or";"xor";"not";"exec";"if";"ifelse";"repeat";"for";"loop";"forall";"exit";"quit";"def";"dict";"begin";"end";"currentdict";"load";"store";"known";"array";"string";"length";"get";"put";"aload";"cvx";"cvlit";"cvn";"cvs";"=";"==";"print";"pstack";"stack";"newpath";"moveto";"rmoveto";"lineto";"rlineto";"curveto";"rcurveto";"arc";"arcn";"closepath";"currentpoint";"fill";"eofill";"stroke";"setlinewidth";"setgray";"setrgbcolor";"sethsbcolor";"gsave";"grestore";"translate";"scale";"rotate";"showpage";"findfont";"scalefont";"setfont";"show";"stringwidth"](*****************************************************************************)(* Running *)(*****************************************************************************)(* a box for each letter, when no host gives the letters *)letboxes={glyph=(fun_->([[(0.1,0.);(0.5,0.);(0.5,0.7);(0.1,0.7);(0.1,0.)]],0.6))}letstart?(host=boxes)text=letsystemdict=Hashtbl.create128inList.iter(funop->Hashtbl.replacesystemdictop(Operatorop))operators;Hashtbl.replacesystemdict"true"(Booltrue);Hashtbl.replacesystemdict"false"(Boolfalse);Hashtbl.replacesystemdict"null"Null;{text;host;operands=[];exec=[Source0];dicts=[Hashtbl.create64;systemdict];gs=initial_gs;saved=[];painted=[];finished=[];printed=[];state=Running;last=None;count=0;}letvalue_of(k:Ps_lexer.kind):value=matchkwith|Intn->Intn|Realf->Realf|Namen->Namen|Literaln->Literaln|Strings->Strings|Open_brace|Close_brace->Null(* a procedure's body, from after its { to its }: kept, not run *)letrecread_proctextpos:array_*int=letrecgopositems=matchPs_lexer.nexttextposwith|None->raise(Offending("syntaxerror","{"))|Some(Error_)->raise(Offending("syntaxerror","("))|Some(Ok{kind=Close_brace;stop;_})->letitems=List.revitemsin({items=Array.of_list(List.mapfstitems);spans=Array.of_list(List.mapsnditems);exec=true},stop)|Some(Ok{kind=Open_brace;start;_})->letinner,stop=read_proctext(start+1)ingostop((Arrayinner,(start,stop))::items)|Some(Okt)->got.stop((value_oft.kind,(t.start,t.stop))::items)ingopos[](* an executable name: an operator runs, a procedure is entered, any
other value is pushed *)letrecexecute_namenm=matchlookupm.dictsnwith|None->raise(Offending("undefined",n))|Some(Operatorop)->(tryapplyopmwithErrore->raise(Offending(e,op)))|Some(Arraya)whena.exec->run_frameam|Some(Nameother)whenother<>n->execute_nameotherm|Somev->pushvm(* an object met in the text or in a procedure's body: a name is
executed, anything else -- a procedure too -- is data *)letexecute_directvm=matchvwithNamen->execute_namenm|v->pushvmletstepm=ifm.state<>Runningthenmelseletm={mwithcount=m.count+1}intrymatchm.execwith|[]->{mwithstate=Done}|frame::rest->(matchframewith|Sourcepos->(matchPs_lexer.nextm.textposwith|None->{mwithexec=rest}|Some(Error_)->raise(Offending("syntaxerror","("))|Some(Ok{kind=Close_brace;_})->raise(Offending("syntaxerror","}"))|Some(Ok{kind=Open_brace;start;_})->letproc,stop=read_procm.text(start+1)inpush(Arrayproc){mwithexec=Sourcestop::rest;last=Some(start,stop)}|Some(Okt)->execute_direct(value_oft.kind){mwithexec=Sourcet.stop::rest;last=Some(t.start,t.stop)})|Run(a,i)->letn=Array.lengtha.itemsinifi>=nthen{mwithexec=rest}else(* the last item leaves the frame first: a procedure's
last call is a jump, and recursion keeps no frame *)letexec=ifi=n-1thenrestelseRun(a,i+1)::restinletlast=ifi<Array.lengtha.spansthenSomea.spans.(i)elsem.lastinexecute_directa.items.(i){mwithexec;last}|Repeat(k,p)->ifk<=0then{mwithexec=rest}elserun_framep{mwithexec=Repeat(k-1,p)::rest}|For(v,inc,limit,ints,p)->if(inc>=0.&&v>limit)||(inc<0.&&v<limit)then{mwithexec=rest}elseletm=push(ifintsthenInt(Float.to_intv)elseRealv){mwithexec=For(v+.inc,inc,limit,ints,p)::rest}inrun_framepm|Loopp->run_framepm|Forall([],_)->{mwithexec=rest}|Forall(turn::more,p)->run_framep(List.fold_left(funmv->pushvm){mwithexec=Forall(more,p)::rest}turn))with|Offending(e,cmd)->{mwithstate=Failed(Printf.sprintf"%%%%[ Error: %s; OffendingCommand: %s ]%%%%"ecmd)}|Errore->{mwithstate=Failed(Printf.sprintf"%%%%[ Error: %s ]%%%%"e)}letrun?(budget=max_int)m=letrecgomk=ifk>=budget||m.state<>Runningthenmelsego(stepm)(k+1)ingom0letstatusm=m.stateletpagem=List.revm.paintedletpagesm=List.revm.finishedletstackm=List.mapshowm.operandsletoutputm=List.revm.printedletspanm=m.lastletstepsm=m.count