123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225(* 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 Scheme_step.mli *)typestep={before:string;redex:int*int;after:string;contractum:int*int}exceptionErrorofstringletfailfmt=Printf.ksprintf(funmsg->raise(Errormsg))fmt(*****************************************************************************)(* Terms *)(*****************************************************************************)(* a Beginning Student expression, as text is: no environment, values
written into it by substitution *)typeterm=|VofScheme.t|Idofstring|Callofstring*termlist|Ifofterm*term*term|Condof(termoption*term)list(* None: else *)|Andoftermlist|Oroftermlist|Markofterm(* the redex, or what replaced it: highlighted *)typeform=Defofstring*term|Exprofterm(* what the definitions made: constants' values, functions, structures *)typedef=ConstofScheme.t|Funofstringlist*term|Struct_opofScheme.procletrecterm(x:Sexpr.t):term=matchx.datumwith|Sym"true"->V(Booltrue)|Sym"false"->V(Boolfalse)|Sym"empty"->VNil|Syms->Ids|Int_|Float_|Str_|Char_|Bool_|Vector_->V(Scheme_syntax.datumx)|List([{datum=Sym"quote";_};d],None)->V(Scheme_syntax.datumd)|List([{datum=Sym"if";_};c;a;b],None)->If(termc,terma,termb)|List({datum=Sym"cond";_}::clauses,None)->Cond(List.map(fun(c:Sexpr.t)->matchc.datumwith|List([{datum=Sym"else";_};a],None)->(None,terma)|List([q;a],None)->(Some(termq),terma)|_->fail"cond: expected a question and an answer in %s"(Sexpr.to_stringc))clauses)|List({datum=Sym"and";_}::xs,None)->And(List.maptermxs)|List({datum=Sym"or";_}::xs,None)->Or(List.maptermxs)|List({datum=Sym(("lambda"|"λ"|"local"|"let"|"let*"|"letrec"|"set!"|"begin"|"define"|"define-struct"|"big-bang")asf);_}::_,None)->fail"the stepper knows Beginning Student only, and %s is not in it"f|List({datum=Symf;_}::args,None)->Call(f,List.maptermargs)|_->fail"not a Beginning Student expression: %s"(Sexpr.to_stringx)(*****************************************************************************)(* Printing, and where the mark is *)(*****************************************************************************)letto_text(f:form):string*(int*int)=letb=Buffer.create64andmark=ref(0,0)inletadd=Buffer.add_stringbinletrecgot=matchtwith|Vv->add(Scheme.printConstructorv)|Idx->addx|Call(f,args)->add"(";addf;List.iter(funa->add" ";goa)args;add")"|If(c,a,e)->add"(if ";goc;add" ";goa;add" ";goe;add")"|Condclauses->add"(cond";List.iter(fun(q,a)->add" [";(matchqwithSomeq->goq|None->add"else");add" ";goa;add"]")clauses;add")"|Andxs->add"(and";List.iter(funx->add" ";gox)xs;add")"|Orxs->add"(or";List.iter(funx->add" ";gox)xs;add")"|Markt->letstart=Buffer.lengthbingot;mark:=(start,Buffer.lengthb)in(matchfwithDef(x,t)->add("(define "^x^" ");got;add")"|Exprt->got);(Buffer.contentsb,!mark)(*****************************************************************************)(* A step *)(*****************************************************************************)letrecsubst(x:string)(v:Scheme.t)(t:term):term=lets=substxvinmatchtwith|Idywheny=x->Vv|V_|Id_->t|Call(f,args)->Call(f,List.mapsargs)|If(c,a,b)->If(sc,sa,sb)|Condcl->Cond(List.map(fun(q,a)->(Option.mapsq,sa))cl)|Andxs->And(List.mapsxs)|Orxs->Or(List.mapsxs)|Markt->Mark(st)(* a value, and in Beginning Student a constructor called on values is
one too: (make-posn 1 2) is not reduced, it is the posn *)letrecvalue(defs:(string*def)list)(t:term):Scheme.toption=matchtwith|Vv->Somev|Call(f,args)->(matchList.assoc_optfdefswith|Some(Struct_op(Make(name,n)))whenList.lengthargs=n->letvs=List.filter_map(valuedefs)argsinifList.lengthvs=nthenSome(Struct(name,vs))elseNone|_->None)|_->Noneletboolean(what:string)(v:Scheme.t):bool=matchvwithBoolb->b|_->fail"%s: question result is not true or false: %s"what(Scheme.printConstructorv)(* a call whose arguments are all values *)letcall(defs:(string*def)list)(f:string)(args:Scheme.tlist):term=matchList.assoc_optfdefswith|Some(Fun(params,body))->ifList.lengthparams<>List.lengthargsthenfail"%s: expects %d arguments, given %d"f(List.lengthparams)(List.lengthargs);List.fold_left2(funbodypa->substpabody)bodyparamsargs|Some(Struct_op(Make(name,n)))->ifList.lengthargs<>nthenfail"make-%s: expects %d arguments, given %d"namen(List.lengthargs)elseV(Struct(name,args))|Some(Struct_op(Get(name,i,field)))->(matchargswith[Struct(s,fields)]whens=name->V(List.nthfieldsi)|_->fail"%s-%s: expects argument of type <struct:%s>"namefieldname)|Some(Struct_op(Isname))->V(Bool(matchargswith[Struct(s,_)]->s=name|_->false))|Some_->fail"%s: this is a value, not a function"f|None->(matchScheme_prims.applyfargswithv->Vv|exceptionScheme_prims.Errormsg->fail"%s"msg|exceptionNot_found->fail"%s: this function is not defined"f)(* [reduce defs t]: the term with its redex marked, and with what
replaced it marked; None when [t] is a value *)letrecreduce(defs:(string*def)list)(t:term):(term*term)option=letcontractt'=Some(Markt,Markt')inmatchtwith|V_|Mark_->None|Idx->(matchList.assoc_optxdefswith|Some(Constv)->contract(Vv)|_->ifx="pi"thencontract(V(RealFloat.pi))elsefail"%s: this variable is not defined"x)|Call(f,args)->(matchinsidedefsargswith|Some(b,a)->Some(Call(f,b),Call(f,a))|None->ifvaluedefst<>NonethenNoneelsecontract(calldefsf(List.filter_map(valuedefs)args)))|If(c,a,e)->(matchreducedefscwith|Some(b,a')->Some(If(b,a,e),If(a',a,e))|None->contract(ifboolean"if"(Option.get(valuedefsc))thenaelsee))|Cond[]->fail"cond: all question results were false"|Cond((None,a)::_)->contracta|Cond((Someq,a)::rest)->(matchreducedefsqwith|Some(b,a')->Some(Cond((Someb,a)::rest),Cond((Somea',a)::rest))|None->contract(ifboolean"cond"(Option.get(valuedefsq))thenaelseCondrest))|And[]->contract(V(Booltrue))|Or[]->contract(V(Boolfalse))|And(x::rest)->logicdefs"and"xrest(funb->ifbthenAndrestelseV(Boolfalse))(funxs->Andxs)|Or(x::rest)->logicdefs"or"xrest(funb->ifbthenV(Booltrue)elseOrrest)(funxs->Orxs)(* the first argument not a value, reduced *)andinsidedefs(args:termlist):(termlist*termlist)option=matchargswith|[]->None|x::rest->(matchreducedefsxwith|Some(b,a)->Some(b::rest,a::rest)|None->Option.map(fun(b,a)->(x::b,x::a))(insidedefsrest))(* and, or: the first operand to a value, then the form shortened or
done *)andlogicdefs(what:string)xrest(next:bool->term)(rebuild:termlist->term)=matchreducedefsxwith|Some(b,a)->Some(rebuild(b::rest),rebuild(a::rest))|None->Some(Mark(rebuild(x::rest)),Mark(next(booleanwhat(Option.get(valuedefsx)))))letrecunmark(t:term):term=matchtwith|Markt->unmarkt|V_|Id_->t|Call(f,args)->Call(f,List.mapunmarkargs)|If(c,a,b)->If(unmarkc,unmarka,unmarkb)|Condcl->Cond(List.map(fun(q,a)->(Option.mapunmarkq,unmarka))cl)|Andxs->And(List.mapunmarkxs)|Orxs->Or(List.mapunmarkxs)(*****************************************************************************)(* The program *)(*****************************************************************************)letsteps?(max=1000)(text:string):steplist*stringoption=letacc=ref[]in(* a form reduced to a value, each step recorded *)letrecrundefs(make:term->form)(t:term):Scheme.t=ifList.length!acc>=maxthenfail"the stepper stopped after %d steps"max;matchreducedefstwith|None->Option.get(valuedefst)|Some(b,a)->letbefore,redex=to_text(makeb)andafter,contractum=to_text(makea)inacc:={before;redex;after;contractum}::!acc;rundefsmake(unmarka)inletformdefs(x:Sexpr.t):(string*def)list=matchx.datumwith|List([{datum=Sym"define";_};{datum=List(({datum=Symf;_})::params,None);_};body],None)->letparam(p:Sexpr.t)=matchp.datumwithSyms->s|_->fail"define: expected a variable, found %s"(Sexpr.to_stringp)in(f,Fun(List.mapparamparams,termbody))::defs|List([{datum=Sym"define";_};{datum=Symc;_};e],None)->(c,Const(rundefs(funt->Def(c,t))(terme)))::defs|List([{datum=Sym"define-struct";_};{datum=Symname;_};{datum=List(fields,None);_}],None)->letfields=List.map(fun(f:Sexpr.t)->matchf.datumwithSyms->s|_->fail"define-struct: expected a field name")fieldsin(("make-"^name,Struct_op(Make(name,List.lengthfields)))::(name^"?",Struct_op(Isname))::List.mapi(funif->(name^"-"^f,Struct_op(Get(name,i,f))))fields)@defs|List({datum=Sym("define"|"define-struct");_}::_,None)->fail"define: not a Beginning Student definition: %s"(Sexpr.to_stringx)(* a world runs in time, not in steps: left out *)|List({datum=Sym"big-bang";_}::_,None)->defs|_->ignore(rundefs(funt->Exprt)(termx));defsinmatchList.fold_leftform[](Sexpr_read.read_allSchemetext)with|_->(List.rev!acc,None)|exceptionErrormsg->(List.rev!acc,Somemsg)|exceptionSexpr_read.Error(msg,_)->(List.rev!acc,Some("read: "^msg))