123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168(******************************************************************************)(* This file is part of the Dose library http://www.irill.org/software/dose *)(* *)(* Copyright (C) 2011 Ralf Treinen <ralf.treinen@pps.jussieu.fr> *)(* *)(* All rights reserved. This file is distributed under the terms of the *)(* GNU Lesser General Public License version 2.1, with the special *)(* exception on linking described in the file LICENSE. *)(* *)(* Work developed with the support of the Mancoosi Project *)(* http://www.mancoosi.org *)(* *)(******************************************************************************)letis_digit=function|'0'..'9'->true|_->false;;(* [skip_while_from i f w m] yields the index of the leftmost character
* in the string [s], starting from [i], end ending at [m], that does
* not satisfy the predicate [f], or [length w] if no such index exists. *)letskip_while_fromifwm=letrecloopi=ifi=mthenielseiffw.[i]thenloop(i+1)elseiinloopi;;(* splits a version into (epoch,rest), without the separating ':'. The
* epoch is delimited by the leftmost occurrence of ':' in x, and is ""
* in case there is no ':' in x. *)letextract_epochx=tryletci=String.indexx':'inletepoch=String.subx0ciandrest=String.subx(ci+1)(String.lengthx-ci-1)in(epoch,rest)with|Not_found->("",x);;(* splits a version into (prefix,revision). The revision starts on the
* right-most occurrence of '-', or is empty in case the version does
* not contain '-'. *)letextract_revisionx=tryletdi=String.rindexx'-'inletbefore=String.subx0diinletafter=String.subx(di+1)(String.lengthx-di-1)in(before,after)with|Not_found->(x,"");;(* character comparison uses a modified character ordering: '~' first,
then letters, then anything else *)letcompare_charsc1c2=matchc1with|'~'->(matchc2with|'~'->0|_->-1)|'a'..'z'|'A'..'Z'->(matchc2with|'~'->1|'a'..'z'|'A'..'Z'->Char.comparec1c2|_->-1)|_->(matchc2with|'~'|'a'..'z'|'A'..'Z'->1|_->Char.comparec1c2);;(* return the first index of x, starting from xi, of a nun-null
* character in x. or (length x) in case x contains only 0's starting
* from xi on. *)letskip_zerosxxixl=skip_while_fromxi(func->c='0')xxl;;(* compare versions chunks, that is parts of version strings that are
* epoch, upstream version, or revisision. Alternates string comparison
* and numerical comparison. *)letcompare_chunksxy=(* x and y may be empty *)letxl=String.lengthxandyl=String.lengthyinletrecloop_lexicalxiyi=assert(xi<=xl&&yi<=yl);match(xi=xl,yi=yl)with(* which of x and y is exhausted? *)|true,true->0|true,false->(* if y continues numerically than we have to continue by
* comparing numerically. In this case the x part is
* interpreted as 0 (since empty). If the y part consists
* only of 0's then both parts are equal, otherwise the y
* part is larger. If y continues non-numerically then y is
* larger anyway, so we only have to skip 0's in the y part
* and check whether this exhausts the y part. *)letys=skip_zerosyyiylinifys=ylthen0elseify.[ys]='~'then1else-1|false,true->(* symmetric to the preceding case *)letxs=skip_zerosxxixlinifxs=xlthen0elseifx.[xs]='~'then-1else1|false,false->(* which of x and y continues numerically? *)match(is_digitx.[xi],is_digity.[yi])with|true,true->(* both continue numerically. Skip leading zeros in the
* remaining parts, and then continue by
* comparing numerically. *)compare_numerical(skip_zerosxxixl)(skip_zerosyyiyl)|true,false->(* '~' is smaller than any numeric part *)ify.[yi]='~'then1else-1|false,true->(* '~' is smaller than any numeric part *)ifx.[xi]='~'then-1else1|false,false->(* continue comparing lexically *)letcomp=compare_charsx.[xi]y.[yi]inifcomp=0thenloop_lexical(xi+1)(yi+1)elsecompandcompare_numericalxiyi=assert(xi=xl||(xi<xl&&x.[xi]<>'0'));(* leading zeros have been stripped *)assert(yi=yl||(yi<yl&&y.[yi]<>'0'));(* leading zeros have been stripped *)letxn=skip_while_fromxiis_digitxxl(* length of numerical part *)andyn=skip_while_fromyiis_digityyl(* length of numerical part *)inletcomp=compare(xn-xi)(yn-yi)inifcomp=0then(* both numerical parts have same length: compare digit by digit *)loop_numericalxiyiynelse(* if one numerical part is longer than the other we have found the
* answer since leading 0 have been striped when switching
* to numerical comparison. *)compandloop_numericalxiyiyn=assert(xi<=xl&&yi<=yn&&yn<=yl);(* invariant: the two numerical parts that remain to compare are
of the same length *)ifyi=ynthen(* both numerical parts are exhausted, we switch to lexical
comparison *)loop_lexicalxiyielse(* both numerical parts are not exhausted, we continue comparing
digit by digit *)letcomp=Char.comparex.[xi]y.[yi]inifcomp=0thenloop_numerical(xi+1)(yi+1)ynelsecompinloop_lexical00;;letcompare(x:string)(y:string)=letnormalize_comp_resultx=ifx=0then0elseifx<0then-1else1inifx=ythen0elselet(e1,rest1)=extract_epochxand(e2,rest2)=extract_epochyinlete_comp=compare_chunkse1e2inife_comp<>0thennormalize_comp_resulte_compelselet(u1,r1)=extract_revisionrest1and(u2,r2)=extract_revisionrest2inletu_comp=compare_chunksu1u2inifu_comp<>0thennormalize_comp_resultu_compelsenormalize_comp_result(compare_chunksr1r2);;letequal(x:string)(y:string)=ifx=ythentrueelse(comparexy)=0;;