123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142(* Merlin representation of a textual source code *)openStdlet{Logger.log}=Logger.for_section"Msource"typet={text:string}moduleDigest=structtypet=Digest.tletmake{text}=Digest.stringtextletequal=Digest.equalendletdumpt=`Assoc[("text",`Stringt.text)]letprint_position()=function|`Start->"start"|`Offseto->string_of_into|`Logical(l,c)->string_of_intl^":"^string_of_intc|`End->"end"letmaketext={text}(* Position management *)typeposition=[`Start|`Offsetofint|`Logicalofint*int|`End]exceptionFoundofintletfind_lineline{text}=ifline<=0thenPrintf.ksprintfinvalid_arg"Msource.find_line: invalid line number %d. Numbering starts from 1"line;ifline=1then0elseletline'=reflineintryfori=0toString.lengthtext-1doiftext.[i]='\n'thenbegindecrline';if!line'=1thenraise(Foundi)enddone;log~title:"find_line""line %d out of bounds (max = %d)"line(line-!line');String.lengthtextwithFoundn->n+1letfind_offset({text}ast)linecol=assert(col>=0);letoffset=find_linelinetinifcol=0thenoffsetelsetryfori=offsettomin(offset+col)(String.lengthtext)-1doiftext.[i]='\n'thenbeginlog~title:"find_offset""%d:%d out of line bounds, line %d only has %d columns"linecolline(i-offset);raise(Foundi)enddone;ifoffset+col>String.lengthtextthenbeginlog~title:"find_offset""%d:%d out of file bounds"linecolend;offset+colwithFoundoff->offletget_offsett=function|`Start->`Offset0|`Offsetx->assert(x>=0);ifx<=String.lengtht.textthen`Offsetxelsebeginlog~title:"get_offset""offset %d out of bounds (size is %d)"x(String.lengtht.text);`Offset(String.lengtht.text)end|`End->`Offset(String.lengtht.text)|`Logical(line,col)->`Offset(find_offsettlinecol)letget_logical{text}=function|`Start->`Logical(1,0)|`Logical_asp->p|(`Offset_|`End)asr->letlen=String.lengthtextinletoffset=matchrwith|`Offsetxwhenx>len->log~title:"get_logical""offset %d out of bounds (size is %d)"xlen;len|`Offsetx->assert(x>=0);x|`End->leninletline=ref1inletcnum=ref0infori=0tooffset-1doiftext.[i]='\n'thenbeginincrline;cnum:=i+1enddone;`Logical(!line,offset-!cnum)letget_lexing_post~filenamepos=let(`Offseto)=get_offsettposinlet(`Logical(line,col))=get_logicaltposin{Lexing.pos_fname=filename;pos_lnum=line;pos_bol=o-col;pos_cnum=o}letsubstitutetstartingendingtext=letlen=String.lengtht.textinlet(`Offsetstarting)=get_offsettstartinginlet(`Offsetending)=matchendingwith|`End->`Offsetlen|`Lengthl->ifstarting+l<=lenthen`Offset(starting+l)elsebeginlog~title:"substitute""offset %d + length %d out of bounds (size is %d)"startingllen;`Offsetlenend|#positionasp->get_offsettpinifending<startingtheninvalid_arg"Source.substitute: ending < starting";lettext=String.subt.text~pos:0~len:starting^text^String.subt.text~pos:ending~len:(len-ending)in{text}(* Accessing content *)lettextt=t.text