123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345(* 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.
*)openScheme(* See Scheme_prims.mli *)exceptionErrorofstringletfailfmt=Printf.ksprintf(funmsg->raise(Errormsg))fmt(* DrScheme v20x's message: "car: expects argument of type <pair>;
given 5" *)letexpects(name:string)(what:string)(v:Scheme.t)=fail"%s: expects argument of type <%s>; given %s"namewhat(printWritev)letarity(name:string)(n:int)(args:Scheme.tlist)=ifList.lengthargs<>nthenfail"%s: expects %d argument%s, given %d"namen(ifn=1then""else"s")(List.lengthargs)(*****************************************************************************)(* Numbers *)(*****************************************************************************)letnumnamev=matchvwithIntn->float_of_intn|Realf->f|_->expectsname"number"vletintnamev=matchvwithIntn->n|RealfwhenFloat.is_integerf->int_of_floatf|_->expectsname"integer"vletis_numv=matchvwithInt_|Real_->true|_->false(* an operation on two numbers: on integers if both are, else on reals *)letarithname(fi:int->int->int)(ff:float->float->float)(a:Scheme.t)(b:Scheme.t):Scheme.t=match(a,b)withIntx,Inty->Int(fixy)|_->Real(ff(numnamea)(numnameb))letfoldnamefiffunitargs=matchargswith|[]->unit|[a]->ifis_numathenaelseexpectsname"number"a|a::rest->List.fold_left(arithnamefiff)arestletdivide(a:Scheme.t)(b:Scheme.t):Scheme.t=match(a,b)with|_,(Int0|Real0.)->fail"/: division by zero"|Intx,Intywhenxmody=0->Int(x/y)|_->Real(num"/"a/.num"/"b)(* = < ...: each pair in turn, (< 1 2 3) *)letcompare_allname(ok:float->float->bool)args=letrecgo=functiona::(b::_asrest)->ok(numnamea)(numnameb)&&gorest|[a]->ignore(numnamea);true|[]->trueinifList.lengthargs<2thenfail"%s: expects at least 2 arguments, given %d"name(List.lengthargs);Bool(goargs)letreal_to_num(f:float):Scheme.t=ifFloat.is_integerf&&Float.absf<1e15thenInt(int_of_floatf)elseRealfletroundingname(f:float->float)args=arityname1args;matchargswith[Intn]->Intn|[v]->Real(f(numnamev))|_->assertfalseletinteger_opname(op:int->int->int)args=arityname2args;matchargswith[a;b]->ifintnameb=0thenfail"%s: undefined for 0"nameelseInt(op(intnamea)(intnameb))|_->assertfalse(* Scheme's modulo takes the divisor's sign, OCaml's mod the dividend's *)letmoduloab=letr=amodbinifr<>0&&(r<0)<>(b<0)thenr+belserletnumber_to_string(v:Scheme.t):string=matchvwithRealf->Scheme.printWrite(Realf)|_->Scheme.printWritev(*****************************************************************************)(* Lists, strings *)(*****************************************************************************)letpairnamev=matchvwithPair(a,d)->(a,d)|_->expectsname"pair"vletstrnamev=matchvwithStrs->s|_->expectsname"string"vletchrnamev=matchvwithCharc->c|_->expectsname"character"vletlstnamev=matchto_listvwithSomexs->xs|None->expectsname"list"v(* car, cadr, caddr...: the a's and d's read right to left *)letcxr(name:string)(path:string)args=arityname1args;letv=ref(List.hdargs)infori=String.lengthpath-1downto0doleta,d=pairname!vinv:=ifpath.[i]='a'thenaelseddone;!vletnthname(i:int)args=arityname1args;matchList.nth_opt(lstname(List.hdargs))iwithSomev->v|None->fail"%s: list contains too few elements"name(* member and its kin: the tail starting with [x], or #f *)letrecmember(eq:Scheme.t->Scheme.t->bool)(x:Scheme.t)(l:Scheme.t):Scheme.t=matchlwithPair(a,d)->ifeqxathenlelsemembereqxd|_->Boolfalseletrecassoc(eq:Scheme.t->Scheme.t->bool)(x:Scheme.t)(l:Scheme.t):Scheme.t=matchlwithPair((Pair(k,_)asentry),d)->ifeqxkthenentryelseassoceqxd|Pair(_,d)->assoceqxd|_->Boolfalseleteqvab=match(a,b)withProcp,Procq->p==q|(Pair_|Vector_|Struct_|Str_),_->a==b|_->a=b(* format's ~a (display) ~s (write) ~n and ~~ *)letformat(fmt:string)(args:Scheme.tlist):string=letb=Buffer.create32andargs=refargsinletnext()=match!argswithv::rest->args:=rest;v|[]->fail"format: not enough arguments for the format string"inleti=ref0inwhile!i<String.lengthfmtdo(iffmt.[!i]='~'&&!i+1<String.lengthfmtthenbegin(matchfmt.[!i+1]with|'a'|'A'->Buffer.add_stringb(display(next()))|'s'|'S'|'v'->Buffer.add_stringb(printWrite(next()))|'n'|'%'->Buffer.add_charb'\n'|c->Buffer.add_charbc);incriendelseBuffer.add_charbfmt.[!i]);incridone;Buffer.contentsb(*****************************************************************************)(* Images *)(*****************************************************************************)letlengthnamev=letf=numnameviniff<0.thenexpectsname"non-negative number"velsefletcolornamev=matchvwithStrs|Syms->String.lowercase_asciis|_->expectsname"color"vletmodenamev:Scheme_image.mode=matchvwithStr("solid"|"Solid")|Sym"solid"->Solid|Str("outline"|"Outline")|Sym"outline"->Outline|_->expectsname"mode (\"solid\" or \"outline\")"vletimgnamev=matchvwithImagei->i|_->expectsname"image"v(* beside, above, overlay: two images or more, folded *)letcombinename(f:Scheme_image.t->Scheme_image.t->Scheme_image.t)args=ifList.lengthargs<2thenfail"%s: expects at least 2 arguments, given %d"name(List.lengthargs);letimgs=List.map(imgname)argsinImage(List.fold_leftf(List.hdimgs)(List.tlimgs))letimagenameargs:Scheme.t=letopenScheme_imageinmatch(name,args)with|"circle",[r;m;c]->Image(Circle(lengthnamer,modenamem,colornamec))|"ellipse",[w;h;m;c]->Image(Ellipse(lengthnamew,lengthnameh,modenamem,colornamec))|"rectangle",[w;h;m;c]->Image(Rectangle(lengthnamew,lengthnameh,modenamem,colornamec))|"square",[s;m;c]->Image(Rectangle(lengthnames,lengthnames,modenamem,colornamec))|"triangle",[s;m;c]->Image(Triangle(lengthnames,modenamem,colornamec))|"text",[s;size;c]->Image(Text(strnames,lengthnamesize,colornamec))|"empty-scene",[w;h]->Image(Scene(lengthnamew,lengthnameh))|"beside",_->combinename(funab->Beside(a,b))args|"above",_->combinename(funab->Above(a,b))args|"overlay",_->combinename(funab->Overlay(a,b))args|"place-image",[i;x;y;scene]->Image(Place(imgnamei,numnamex,numnamey,imgnamescene))|"image-width",[i]->real_to_num(Float.round(width(imgnamei)))|"image-height",[i]->real_to_num(Float.round(height(imgnamei)))|"image?",[v]->Bool(matchvwithImage_->true|_->false)|_->fail"%s: wrong number of arguments (%d)"name(List.lengthargs)letimage_names=["circle";"ellipse";"rectangle";"square";"triangle";"text";"empty-scene";"beside";"above";"overlay";"place-image";"image-width";"image-height";"image?"](*****************************************************************************)(* The table *)(*****************************************************************************)letonenameargs=arityname1args;List.hdargslettwonameargs=arityname2args;matchargswith[a;b]->(a,b)|_->assertfalseletpredname(p:Scheme.t->bool)=(name,funargs->Bool(p(onenameargs)))(* sqrt of 4 is 2, of 2 is a real *)letfloat1name(f:float->float)=(name,funargs->letr=f(numname(onenameargs))inifFloat.is_integerrthenInt(int_of_floatr)elseRealr)lettable:(string*(Scheme.tlist->Scheme.t))list=[("+",fold"+"(+)(+.)(Int0));("*",fold"*"(*)(*.)(Int1));("-",funargs->matchargswith[a]->arith"-"(-)(-.)(Int0)a|[]->fail"-: expects at least 1 argument"|_->fold"-"(-)(-.)(Int0)args);("/",funargs->matchargswith[a]->divide(Int1)a|a::restwhenrest<>[]->List.fold_leftdividearest|_->fail"/: expects at least 1 argument");("=",compare_all"="(=));("<",compare_all"<"(<));(">",compare_all">"(>));("<=",compare_all"<="(<=));(">=",compare_all">="(>=));("quotient",integer_op"quotient"(/));("remainder",integer_op"remainder"(mod));("modulo",integer_op"modulo"modulo);("abs",funargs->matchone"abs"argswithIntn->Int(absn)|v->Real(Float.abs(num"abs"v)));("min",funargs->fold"min"minFloat.min(Int0)args);("max",funargs->fold"max"maxFloat.max(Int0)args);("add1",funargs->arith"add1"(+)(+.)(one"add1"args)(Int1));("sub1",funargs->arith"sub1"(-)(-.)(one"sub1"args)(Int1));("sqr",funargs->letv=one"sqr"argsinarith"sqr"(*)(*.)vv);pred"zero?"(funv->num"zero?"v=0.);pred"positive?"(funv->num"positive?"v>0.);pred"negative?"(funv->num"negative?"v<0.);pred"even?"(funv->int"even?"vmod2=0);pred"odd?"(funv->int"odd?"vmod2<>0);pred"number?"is_num;pred"integer?"(funv->matchvwithInt_->true|Realf->Float.is_integerf|_->false);pred"real?"is_num;pred"rational?"is_num;pred"exact?"(funv->matchvwithInt_->true|Real_->false|_->expects"exact?""number"v);pred"inexact?"(funv->matchvwithReal_->true|Int_->false|_->expects"inexact?""number"v);("exact->inexact",funargs->Real(num"exact->inexact"(one"exact->inexact"args)));("inexact->exact",funargs->matchone"inexact->exact"argswithRealf->Int(int_of_float(Float.roundf))|v->ignore(num"inexact->exact"v);v);("floor",rounding"floor"Float.floor);("ceiling",rounding"ceiling"Float.ceil);("round",rounding"round"Float.round);("truncate",rounding"truncate"Float.trunc);float1"sqrt"Float.sqrt;float1"exp"Float.exp;float1"log"Float.log;("sin",funargs->Real(sin(num"sin"(one"sin"args))));("cos",funargs->Real(cos(num"cos"(one"cos"args))));("tan",funargs->Real(tan(num"tan"(one"tan"args))));("atan",funargs->matchargswith[y;x]->Real(Float.atan2(num"atan"y)(num"atan"x))|_->Real(atan(num"atan"(one"atan"args))));("expt",funargs->matchtwo"expt"argswith|Intb,Intewhene>=0->letrecpowbe=ife=0then1elseb*powb(e-1)inInt(powbe)|b,e->Real(Float.pow(num"expt"b)(num"expt"e)));("gcd",funargs->letrecgcdab=ifb=0thenabsaelsegcdb(amodb)inInt(List.fold_left(fungv->gcdg(int"gcd"v))0args));("number->string",funargs->Str(number_to_string(letv=one"number->string"argsinignore(num"number->string"v);v)));("string->number",funargs->lets=str"string->number"(one"string->number"args)inmatchint_of_string_optswithSomen->Intn|None->(matchfloat_of_string_optswithSomefwhens<>""->Realf|_->Boolfalse));(* booleans, equality *)pred"not"(funv->v=Boolfalse);pred"boolean?"(funv->matchvwithBool_->true|_->false);("eq?",funargs->leta,b=two"eq?"argsinBool(eqvab));("eqv?",funargs->leta,b=two"eqv?"argsinBool(eqvab));("equal?",funargs->leta,b=two"equal?"argsinBool(equalab));("boolean=?",funargs->leta,b=two"boolean=?"argsinBool(a=b));(* symbols *)pred"symbol?"(funv->matchvwithSym_->true|_->false);("symbol->string",funargs->matchone"symbol->string"argswithSyms->Strs|v->expects"symbol->string""symbol"v);("string->symbol",funargs->Sym(str"string->symbol"(one"string->symbol"args)));("symbol=?",funargs->leta,b=two"symbol=?"argsinBool(a=b));(* strings *)pred"string?"(funv->matchvwithStr_->true|_->false);("string-length",funargs->Int(String.length(str"string-length"(one"string-length"args))));("string-append",funargs->Str(String.concat""(List.map(str"string-append")args)));("substring",funargs->matchargswith|s::i::rest->lets=str"substring"sandi=int"substring"iinletj=matchrestwith[j]->int"substring"j|_->String.lengthsinifi<0||j>String.lengths||i>jthenfail"substring: ending index %d out of range [%d, %d] for string %S"ji(String.lengths)selseStr(String.subsi(j-i))|_->fail"substring: expects 2 or 3 arguments");("string-ref",funargs->lets,i=two"string-ref"argsinlets=str"string-ref"sandi=int"string-ref"iinifi<0||i>=String.lengthsthenfail"string-ref: index %d out of range for %S"iselseChar(Char.codes.[i]));("string=?",funargs->leta,b=two"string=?"argsinBool(str"string=?"a=str"string=?"b));("string<?",funargs->leta,b=two"string<?"argsinBool(str"string<?"a<str"string<?"b));("string>?",funargs->leta,b=two"string>?"argsinBool(str"string>?"a>str"string>?"b));("string-upcase",funargs->Str(String.uppercase_ascii(str"string-upcase"(one"string-upcase"args))));("string-downcase",funargs->Str(String.lowercase_ascii(str"string-downcase"(one"string-downcase"args))));("string->list",funargs->list(List.map(func->Char(Char.codec))(List.of_seq(String.to_seq(str"string->list"(one"string->list"args))))));("list->string",funargs->Str(String.of_seq(List.to_seq(List.map(func->Char.chr(chr"list->string"cland255))(lst"list->string"(one"list->string"args))))));("string",funargs->Str(String.of_seq(List.to_seq(List.map(func->Char.chr(chr"string"cland255))args))));("format",funargs->matchargswithf::rest->Str(format(str"format"f)rest)|[]->fail"format: expects a format string");(* characters *)pred"char?"(funv->matchvwithChar_->true|_->false);("char->integer",funargs->Int(chr"char->integer"(one"char->integer"args)));("integer->char",funargs->Char(int"integer->char"(one"integer->char"args)));("char=?",funargs->leta,b=two"char=?"argsinBool(chr"char=?"a=chr"char=?"b));("char<?",funargs->leta,b=two"char<?"argsinBool(chr"char<?"a<chr"char<?"b));("char-upcase",funargs->Char(Char.code(Char.uppercase_ascii(Char.chr(chr"char-upcase"(one"char-upcase"args)land255)))));pred"char-alphabetic?"(funv->matchChar.chr(chr"char-alphabetic?"vland255)with'a'..'z'|'A'..'Z'->true|_->false);pred"char-numeric?"(funv->matchChar.chr(chr"char-numeric?"vland255)with'0'..'9'->true|_->false);pred"char-whitespace?"(funv->List.mem(chr"char-whitespace?"v)[32;9;10;13]);(* pairs and lists *)("cons",funargs->leta,d=two"cons"argsinPair(a,d));("car",cxr"car""a");("cdr",cxr"cdr""d");("caar",cxr"caar""aa");("cadr",cxr"cadr""ad");("cdar",cxr"cdar""da");("cddr",cxr"cddr""dd");("caddr",cxr"caddr""add");("cdddr",cxr"cdddr""ddd");("first",funargs->matchone"first"argswithPair(a,_)->a|v->expects"first""non-empty list"v);("rest",funargs->matchone"rest"argswithPair(_,d)->d|v->expects"rest""non-empty list"v);("second",nth"second"1);("third",nth"third"2);("fourth",nth"fourth"3);("last",funargs->matchList.rev(lst"last"(one"last"args))withv::_->v|[]->expects"last""non-empty list"Nil);pred"empty?"(funv->v=Nil);pred"null?"(funv->v=Nil);pred"cons?"(funv->matchvwithPair_->true|_->false);pred"pair?"(funv->matchvwithPair_->true|_->false);pred"list?"(funv->to_listv<>None);("list",funargs->listargs);("length",funargs->Int(List.length(lst"length"(one"length"args))));("append",funargs->matchList.revargswith|[]->Nil|last::firsts->List.fold_left(funaccl->List.fold_right(funxrest->Pair(x,rest))(lst"append"l)acc)lastfirsts);("reverse",funargs->list(List.rev(lst"reverse"(one"reverse"args))));("list-ref",funargs->letl,i=two"list-ref"argsinmatchList.nth_opt(lst"list-ref"l)(int"list-ref"i)withSomev->v|None->fail"list-ref: index %s too large for list"(printWritei));("list-tail",funargs->letl,k=two"list-tail"argsinletrecgolk=ifk=0thenlelsego(snd(pair"list-tail"l))(k-1)ingol(int"list-tail"k));("member",funargs->letx,l=two"member"argsinmemberequalxl);("memv",funargs->letx,l=two"memv"argsinmembereqvxl);("memq",funargs->letx,l=two"memq"argsinmembereqvxl);("assoc",funargs->letx,l=two"assoc"argsinassocequalxl);("assv",funargs->letx,l=two"assv"argsinassoceqvxl);("assq",funargs->letx,l=two"assq"argsinassoceqvxl);("remove",funargs->letx,l=two"remove"argsinletrecgo=function[]->[]|y::ys->ifequalxythenyselsey::goysinlist(go(lst"remove"l)));(* vectors *)pred"vector?"(funv->matchvwithVector_->true|_->false);("vector",funargs->Vector(Array.of_listargs));("make-vector",funargs->matchargswith[n]->Vector(Array.make(int"make-vector"n)(Int0))|[n;v]->Vector(Array.make(int"make-vector"n)v)|_->fail"make-vector: expects 1 or 2 arguments");("vector-length",funargs->matchone"vector-length"argswithVectorxs->Int(Array.lengthxs)|v->expects"vector-length""vector"v);("vector-ref",funargs->matchtwo"vector-ref"argswith|Vectorxs,i->leti=int"vector-ref"iinifi<0||i>=Array.lengthxsthenfail"vector-ref: index %d out of range"ielsexs.(i)|v,_->expects"vector-ref""vector"v);("vector->list",funargs->matchone"vector->list"argswithVectorxs->list(Array.to_listxs)|v->expects"vector->list""vector"v);("list->vector",funargs->Vector(Array.of_list(lst"list->vector"(one"list->vector"args))));(* the rest *)pred"procedure?"(funv->matchvwithProc_->true|_->false);("void",fun_->Void);("identity",funargs->one"identity"args);("error",funargs->matchargswith|Symwho::msg::rest->fail"%s: %s"who(String.concat" "(displaymsg::List.map(printWrite)rest))|msg::rest->fail"%s"(String.concat" "(displaymsg::List.map(printWrite)rest))|[]->fail"error");("cond-fell-through",fun_->fail"cond: all question results were false")]@List.map(funname->(name,imagename))image_namesletnames=List.mapfsttableletby_name:(string,Scheme.tlist->Scheme.t)Hashtbl.t=leth=Hashtbl.create200inList.iter(fun(name,f)->Hashtbl.replacehnamef)table;hletapply(name:string)(args:Scheme.tlist):Scheme.t=(Hashtbl.findby_namename)args