------------------------------------------------------------------------------- |-- Module : Distribution.Version-- Copyright : Isaac Jones, Simon Marlow 2003-2004-- Duncan Coutts 2008---- Maintainer : cabal-devel@haskell.org-- Portability : portable---- Exports the 'Version' type along with a parser and pretty printer. A version-- is something like @\"1.3.3\"@. It also defines the 'VersionRange' data-- types. Version ranges are like @\">= 1.2 && < 2\"@.{- Copyright (c) 2003-2004, Isaac Jones
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following
disclaimer in the documentation and/or other materials provided
with the distribution.
* Neither the name of Isaac Jones nor the names of other
contributors may be used to endorse or promote products derived
from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -}moduleDistribution.Version(-- * Package versionsVersion(..),-- * Version rangesVersionRange(..),-- ** ConstructinganyVersion,noVersion,thisVersion,notThisVersion,laterVersion,earlierVersion,orLaterVersion,orEarlierVersion,unionVersionRanges,intersectVersionRanges,withinVersion,betweenVersionsInclusive,-- ** InspectionwithinRange,isAnyVersion,isNoVersion,isSpecificVersion,simplifyVersionRange,foldVersionRange,foldVersionRange',-- * Version intervals viewasVersionIntervals,VersionInterval,LowerBound(..),UpperBound(..),Bound(..),-- ** 'VersionIntervals' abstract type-- | The 'VersionIntervals' type and the accompanying functions are exposed-- primarily for completeness and testing purposes. In practice -- 'asVersionIntervals' is the main function to use to-- view a 'VersionRange' as a bunch of 'VersionInterval's.--VersionIntervals,toVersionIntervals,fromVersionIntervals,withinIntervals,versionIntervals,mkVersionIntervals,unionVersionIntervals,intersectVersionIntervals,)whereimportData.Version(Version(..))importDistribution.Text(Text(..))importqualifiedDistribution.Compat.ReadPasParseimportDistribution.Compat.ReadP((+++))importqualifiedText.PrettyPrintasDispimportText.PrettyPrint((<>),(<+>))importqualifiedData.CharasChar(isDigit)importControl.Exception(assert)-- ------------------------------------------------------------------------------- Version ranges-- Todo: maybe move this to Distribution.Package.Version?-- (package-specific versioning scheme).dataVersionRange=AnyVersion|ThisVersionVersion-- = version|LaterVersionVersion-- > version (NB. not >=)|EarlierVersionVersion-- < version|WildcardVersionVersion-- == ver.* (same as >= ver && < ver+1)|UnionVersionRangesVersionRangeVersionRange|IntersectVersionRangesVersionRangeVersionRangederiving(Show,Read,Eq){-# DEPRECATED AnyVersion "Use 'anyVersion', 'foldVersionRange' or 'asVersionIntervals'" #-}{-# DEPRECATED ThisVersion "use 'thisVersion', 'foldVersionRange' or 'asVersionIntervals'" #-}{-# DEPRECATED LaterVersion "use 'laterVersion', 'foldVersionRange' or 'asVersionIntervals'" #-}{-# DEPRECATED EarlierVersion "use 'earlierVersion', 'foldVersionRange' or 'asVersionIntervals'" #-}{-# DEPRECATED WildcardVersion "use 'anyVersion', 'foldVersionRange' or 'asVersionIntervals'" #-}{-# DEPRECATED UnionVersionRanges "use 'unionVersionRanges', 'foldVersionRange' or 'asVersionIntervals'" #-}{-# DEPRECATED IntersectVersionRanges "use 'intersectVersionRanges', 'foldVersionRange' or 'asVersionIntervals'" #-}-- | The version range @-any@. That is, a version range containing all-- versions.---- > withinRange v anyVersion = True--anyVersion::VersionRangeanyVersion=AnyVersion-- | The empty version range, that is a version range containing no versions.---- This can be constructed using any unsatisfiable version range expression,-- for example @> 1 && < 1@.---- > withinRange v anyVersion = False--noVersion::VersionRangenoVersion=IntersectVersionRanges(LaterVersionv)(EarlierVersionv)wherev=Version[1][]-- | The version range @== v@---- > withinRange v' (thisVersion v) = v' == v--thisVersion::Version->VersionRangethisVersion=ThisVersion-- | The version range @< v || > v@---- > withinRange v' (notThisVersion v) = v' /= v--notThisVersion::Version->VersionRangenotThisVersionv=UnionVersionRanges(EarlierVersionv)(LaterVersionv)-- | The version range @> v@---- > withinRange v' (laterVersion v) = v' > v--laterVersion::Version->VersionRangelaterVersion=LaterVersion-- | The version range @>= v@---- > withinRange v' (orLaterVersion v) = v' >= v--orLaterVersion::Version->VersionRangeorLaterVersionv=UnionVersionRanges(ThisVersionv)(LaterVersionv)-- | The version range @< v@---- > withinRange v' (earlierVersion v) = v' < v--earlierVersion::Version->VersionRangeearlierVersion=EarlierVersion-- | The version range @<= v@---- > withinRange v' (orEarlierVersion v) = v' <= v--orEarlierVersion::Version->VersionRangeorEarlierVersionv=UnionVersionRanges(ThisVersionv)(EarlierVersionv)-- | The version range @vr1 || vr2@---- > withinRange v' (unionVersionRanges vr1 vr2)-- > = withinRange v' vr1 || withinRange v' vr2--unionVersionRanges::VersionRange->VersionRange->VersionRangeunionVersionRanges=UnionVersionRanges-- | The version range @vr1 && vr2@---- > withinRange v' (intersectVersionRanges vr1 vr2)-- > = withinRange v' vr1 && withinRange v' vr2--intersectVersionRanges::VersionRange->VersionRange->VersionRangeintersectVersionRanges=IntersectVersionRanges-- | The version range @== v.*@.---- For example, for version @1.2@, the version range @== 1.2.*@ is the same as-- @>= 1.2 && < 1.3@---- > withinRange v' (laterVersion v) = v' >= v && v' < upper v-- > where-- > upper (Version lower t) = Version (init lower ++ [last lower + 1]) t--withinVersion::Version->VersionRangewithinVersion=WildcardVersion-- | The version range @>= v1 && <= v2@.---- In practice this is not very useful because we normally use inclusive lower-- bounds and exclusive upper bounds.---- > withinRange v' (laterVersion v) = v' > v--betweenVersionsInclusive::Version->Version->VersionRangebetweenVersionsInclusivev1v2=IntersectVersionRanges(orLaterVersionv1)(orEarlierVersionv2){-# DEPRECATED betweenVersionsInclusive
"In practice this is not very useful because we normally use inclusive lower bounds and exclusive upper bounds"
#-}-- | Fold over the basic syntactic structure of a 'VersionRange'.---- This provides a syntacic view of the expression defining the version range.-- The syntactic sugar @\">= v\"@, @\"<= v\"@ and @\"== v.*\"@ is presented-- in terms of the other basic syntax.---- For a semantic view use 'asVersionIntervals'.--foldVersionRange::a-- ^ @\"-any\"@ version->(Version->a)-- ^ @\"== v\"@->(Version->a)-- ^ @\"> v\"@->(Version->a)-- ^ @\"< v\"@->(a->a->a)-- ^ @\"_ || _\"@ union->(a->a->a)-- ^ @\"_ && _\"@ intersection->VersionRange->afoldVersionRangeanyvthislaterearlierunionintersect=foldwherefoldAnyVersion=anyvfold(ThisVersionv)=thisvfold(LaterVersionv)=latervfold(EarlierVersionv)=earliervfold(WildcardVersionv)=fold(wildcardv)fold(UnionVersionRangesv1v2)=union(foldv1)(foldv2)fold(IntersectVersionRangesv1v2)=intersect(foldv1)(foldv2)wildcardv=intersectVersionRanges(orLaterVersionv)(earlierVersion(wildcardUpperBoundv))-- | An extended variant of 'foldVersionRange' that also provides a view of-- in which the syntactic sugar @\">= v\"@, @\"<= v\"@ and @\"== v.*\"@ is presented-- explicitly rather than in terms of the other basic syntax.--foldVersionRange'::a-- ^ @\"-any\"@ version->(Version->a)-- ^ @\"== v\"@->(Version->a)-- ^ @\"> v\"@->(Version->a)-- ^ @\"< v\"@->(Version->a)-- ^ @\">= v\"@->(Version->a)-- ^ @\"<= v\"@->(Version->Version->a)-- ^ @\"== v.*\"@ wildcard. The-- function is passed the-- inclusive lower bound and the-- exclusive upper bounds of the-- range defined by the wildcard.->(a->a->a)-- ^ @\"_ || _\"@ union->(a->a->a)-- ^ @\"_ && _\"@ intersection->VersionRange->afoldVersionRange'anyvthislaterearlierorLaterorEarlierwildcardunionintersect=foldwherefoldAnyVersion=anyvfold(ThisVersionv)=thisvfold(LaterVersionv)=latervfold(EarlierVersionv)=earliervfold(UnionVersionRanges(ThisVersionv)(LaterVersionv'))|v==v'=orLatervfold(UnionVersionRanges(LaterVersionv)(ThisVersionv'))|v==v'=orLatervfold(UnionVersionRanges(ThisVersionv)(EarlierVersionv'))|v==v'=orEarliervfold(UnionVersionRanges(EarlierVersionv)(ThisVersionv'))|v==v'=orEarliervfold(WildcardVersionv)=wildcardv(wildcardUpperBoundv)fold(UnionVersionRangesv1v2)=union(foldv1)(foldv2)fold(IntersectVersionRangesv1v2)=intersect(foldv1)(foldv2)-- | Does this version fall within the given range?---- This is the evaluation function for the 'VersionRange' type.--withinRange::Version->VersionRange->BoolwithinRangev=foldVersionRangeTrue(\v'->versionBranchv==versionBranchv')(\v'->versionBranchv>versionBranchv')(\v'->versionBranchv<versionBranchv')(||)(&&)-- | View a 'VersionRange' as a union of intervals.---- This provides a canonical view of the semantics of a 'VersionRange' as-- opposed to the syntax of the expression used to define it. For the syntactic-- view use 'foldVersionRange'.---- Each interval is non-empty. The sequence is in increasing order and no-- intervals overlap or touch. Therefore only the first and last can be-- unbounded. The sequence can be empty if the range is empty-- (e.g. a range expression like @< 1 && > 2@).---- Other checks are trivial to implement using this view. For example:---- > isNoVersion vr | [] <- asVersionIntervals vr = True-- > | otherwise = False---- > isSpecificVersion vr-- > | [(LowerBound v InclusiveBound-- > ,UpperBound v' InclusiveBound)] <- asVersionIntervals vr-- > , v == v' = Just v-- > | otherwise = Nothing--asVersionIntervals::VersionRange->[VersionInterval]asVersionIntervals=versionIntervals.toVersionIntervals-- | Does this 'VersionRange' place any restriction on the 'Version' or is it-- in fact equivalent to 'AnyVersion'.---- Note this is a semantic check, not simply a syntactic check. So for example-- the following is @True@ (for all @v@).---- > isAnyVersion (EarlierVersion v `UnionVersionRanges` orLaterVersion v)--isAnyVersion::VersionRange->BoolisAnyVersionvr=caseasVersionIntervalsvrof[(LowerBoundvInclusiveBound,NoUpperBound)]|isVersion0v->True_->False-- | This is the converse of 'isAnyVersion'. It check if the version range is-- empty, if there is no possible version that satisfies the version range.---- For example this is @True@ (for all @v@):---- > isNoVersion (EarlierVersion v `IntersectVersionRanges` LaterVersion v)--isNoVersion::VersionRange->BoolisNoVersionvr=caseasVersionIntervalsvrof[]->True_->False-- | Is this version range in fact just a specific version?---- For example the version range @\">= 3 && <= 3\"@ contains only the version-- @3@.--isSpecificVersion::VersionRange->MaybeVersionisSpecificVersionvr=caseasVersionIntervalsvrof[(LowerBoundvInclusiveBound,UpperBoundv'InclusiveBound)]|v==v'->Justv_->Nothing-- | Simplify a 'VersionRange' expression. For non-empty version ranges-- this produces a canonical form. Empty or inconsistent version ranges-- are left as-is because that provides more information.---- If you need a canonical form use-- @fromVersionIntervals . toVersionIntervals@---- It satisfies the following properties:---- > withinRange v (simplifyVersionRange r) = withinRange v r---- > withinRange v r = withinRange v r'-- > ==> simplifyVersionRange r = simplifyVersionRange r'-- > || isNoVersion r-- > || isNoVersion r'--simplifyVersionRange::VersionRange->VersionRangesimplifyVersionRangevr-- If the version range is inconsistent then we just return the-- original since that has more information than ">1 && < 1", which-- is the canonical inconsistent version range.|null(versionIntervalsvi)=vr|otherwise=fromVersionIntervalsviwherevi=toVersionIntervalsvr------------------------------ Wildcard range utilities--wildcardUpperBound::Version->VersionwildcardUpperBound(VersionlowerBoundts)=(VersionupperBoundts)whereupperBound=initlowerBound++[lastlowerBound+1]isWildcardRange::Version->Version->BoolisWildcardRange(Versionbranch1_)(Versionbranch2_)=checkbranch1branch2wherecheck(n:[])(m:[])|n+1==m=Truecheck(n:ns)(m:ms)|n==m=checknsmscheck__=False-------------------- Intervals view---- | A complementary representation of a 'VersionRange'. Instead of a boolean-- version predicate it uses an increasing sequence of non-overlapping,-- non-empty intervals.---- The key point is that this representation gives a canonical representation-- for the semantics of 'VersionRange's. This makes it easier to check things-- like whether a version range is empty, covers all versions, or requires a-- certain minimum or maximum version. It also makes it easy to check equality-- or containment. It also makes it easier to identify \'simple\' version-- predicates for translation into foreign packaging systems that do not-- support complex version range expressions.--newtypeVersionIntervals=VersionIntervals[VersionInterval]deriving(Eq,Show)-- | Inspect the list of version intervals.--versionIntervals::VersionIntervals->[VersionInterval]versionIntervals(VersionIntervalsis)=istypeVersionInterval=(LowerBound,UpperBound)dataLowerBound=LowerBoundVersion!Boundderiving(Eq,Show)dataUpperBound=NoUpperBound|UpperBoundVersion!Boundderiving(Eq,Show)dataBound=ExclusiveBound|InclusiveBoundderiving(Eq,Show)minLowerBound::LowerBoundminLowerBound=LowerBound(Version[0][])InclusiveBoundisVersion0::Version->BoolisVersion0(Version[0]_)=TrueisVersion0_=FalseinstanceOrdLowerBoundwhereLowerBoundverbound<=LowerBoundver'bound'=casecompareverver'ofLT->TrueEQ->not(bound==ExclusiveBound&&bound'==InclusiveBound)GT->FalseinstanceOrdUpperBoundwhere_<=NoUpperBound=TrueNoUpperBound<=UpperBound__=FalseUpperBoundverbound<=UpperBoundver'bound'=casecompareverver'ofLT->TrueEQ->not(bound==InclusiveBound&&bound'==ExclusiveBound)GT->Falseinvariant::VersionIntervals->Boolinvariant(VersionIntervalsintervals)=allvalidIntervalintervals&&alldoesNotTouch'adjacentIntervalswheredoesNotTouch'::(VersionInterval,VersionInterval)->BooldoesNotTouch'((_,u),(l',_))=doesNotTouchul'adjacentIntervals::[(VersionInterval,VersionInterval)]adjacentIntervals|nullintervals=[]|otherwise=zipintervals(tailintervals)checkInvariant::VersionIntervals->VersionIntervalscheckInvariantis=assert(invariantis)is-- | Directly construct a 'VersionIntervals' from a list of intervals.---- Each interval must be non-empty. The sequence must be in increasing order-- and no invervals may overlap or touch. If any of these conditions are not-- satisfied the function returns @Nothing@.--mkVersionIntervals::[VersionInterval]->MaybeVersionIntervalsmkVersionIntervalsintervals|invariant(VersionIntervalsintervals)=Just(VersionIntervalsintervals)|otherwise=NothingvalidVersion::Version->BoolvalidVersion(Version[]_)=FalsevalidVersion(Versionvs_)=all(>=0)vsvalidInterval::(LowerBound,UpperBound)->BoolvalidIntervali@(l,u)=validLowerl&&validUpperu&&nonEmptyiwherevalidLower(LowerBoundv_)=validVersionvvalidUpperNoUpperBound=TruevalidUpper(UpperBoundv_)=validVersionv-- Check an interval is non-empty--nonEmpty::VersionInterval->BoolnonEmpty(_,NoUpperBound)=TruenonEmpty(LowerBoundllb,UpperBounduub)=(l<u)||(l==u&&lb==InclusiveBound&&ub==InclusiveBound)-- Check an upper bound does not intersect, or even touch a lower bound:---- ---| or ---) but not ---] or ---) or ---]-- |--- (--- (--- [--- [-----doesNotTouch::UpperBound->LowerBound->BooldoesNotTouchNoUpperBound_=FalsedoesNotTouch(UpperBounduub)(LowerBoundllb)=u<l||(u==l&&ub==ExclusiveBound&&lb==ExclusiveBound)-- | Check an upper bound does not intersect a lower bound:---- ---| or ---) or ---] or ---) but not ---]-- |--- (--- (--- [--- [-----doesNotIntersect::UpperBound->LowerBound->BooldoesNotIntersectNoUpperBound_=FalsedoesNotIntersect(UpperBounduub)(LowerBoundllb)=u<l||(u==l&&not(ub==InclusiveBound&&lb==InclusiveBound))-- | Test if a version falls within the version intervals.---- It exists mostly for completeness and testing. It satisfies the following-- properties:---- > withinIntervals v (toVersionIntervals vr) = withinRange v vr-- > withinIntervals v ivs = withinRange v (fromVersionIntervals ivs)--withinIntervals::Version->VersionIntervals->BoolwithinIntervalsv(VersionIntervalsintervals)=anywithinIntervalintervalswherewithinInterval(lowerBound,upperBound)=withinLowerlowerBound&&withinUpperupperBoundwithinLower(LowerBoundv'ExclusiveBound)=v'<vwithinLower(LowerBoundv'InclusiveBound)=v'<=vwithinUpperNoUpperBound=TruewithinUpper(UpperBoundv'ExclusiveBound)=v'>vwithinUpper(UpperBoundv'InclusiveBound)=v'>=v-- | Convert a 'VersionRange' to a sequence of version intervals.--toVersionIntervals::VersionRange->VersionIntervalstoVersionIntervals=foldVersionRange(chkIvl(minLowerBound,NoUpperBound))(\v->chkIvl(LowerBoundvInclusiveBound,UpperBoundvInclusiveBound))(\v->chkIvl(LowerBoundvExclusiveBound,NoUpperBound))(\v->ifisVersion0vthenVersionIntervals[]elsechkIvl(minLowerBound,UpperBoundvExclusiveBound))unionVersionIntervalsintersectVersionIntervalswherechkIvlinterval=checkInvariant(VersionIntervals[interval])-- | Convert a 'VersionIntervals' value back into a 'VersionRange' expression-- representing the version intervals.--fromVersionIntervals::VersionIntervals->VersionRangefromVersionIntervals(VersionIntervals[])=noVersionfromVersionIntervals(VersionIntervalsintervals)=foldr1UnionVersionRanges[intervallu|(l,u)<-intervals]whereinterval(LowerBoundvInclusiveBound)(UpperBoundv'InclusiveBound)|v==v'=ThisVersionvinterval(LowerBoundvInclusiveBound)(UpperBoundv'ExclusiveBound)|isWildcardRangevv'=WildcardVersionvintervallu=lowerBoundl`intersectVersionRanges'`upperBoundulowerBound(LowerBoundvInclusiveBound)|isVersion0v=AnyVersion|otherwise=orLaterVersionvlowerBound(LowerBoundvExclusiveBound)=LaterVersionvupperBoundNoUpperBound=AnyVersionupperBound(UpperBoundvInclusiveBound)=orEarlierVersionvupperBound(UpperBoundvExclusiveBound)=EarlierVersionvintersectVersionRanges'vrAnyVersion=vrintersectVersionRanges'AnyVersionvr=vrintersectVersionRanges'vrvr'=IntersectVersionRangesvrvr'unionVersionIntervals::VersionIntervals->VersionIntervals->VersionIntervalsunionVersionIntervals(VersionIntervalsis0)(VersionIntervalsis'0)=checkInvariant(VersionIntervals(unionis0is'0))whereunionis[]=isunion[]is'=is'union(i:is)(i':is')=caseunionIntervalii'ofLeftNothing->i:unionis(i':is')Left(Justi'')->unionis(i'':is')RightNothing->i':union(i:is)is'Right(Justi'')->union(i'':is)is'unionInterval::VersionInterval->VersionInterval->Either(MaybeVersionInterval)(MaybeVersionInterval)unionInterval(lower,upper)(lower',upper')-- Non-intersecting intervals with the left interval ending first|upper`doesNotTouch`lower'=LeftNothing-- Non-intersecting intervals with the right interval first|upper'`doesNotTouch`lower=RightNothing-- Complete or partial overlap, with the left interval ending first|upper<=upper'=lowerBound`seq`Left(Just(lowerBound,upper'))-- Complete or partial overlap, with the left interval ending first|otherwise=lowerBound`seq`Right(Just(lowerBound,upper))wherelowerBound=minlowerlower'intersectVersionIntervals::VersionIntervals->VersionIntervals->VersionIntervalsintersectVersionIntervals(VersionIntervalsis0)(VersionIntervalsis'0)=checkInvariant(VersionIntervals(intersectis0is'0))whereintersect_[]=[]intersect[]_=[]intersect(i:is)(i':is')=caseintersectIntervalii'ofLeftNothing->intersectis(i':is')Left(Justi'')->i'':intersectis(i':is')RightNothing->intersect(i:is)is'Right(Justi'')->i'':intersect(i:is)is'intersectInterval::VersionInterval->VersionInterval->Either(MaybeVersionInterval)(MaybeVersionInterval)intersectInterval(lower,upper)(lower',upper')-- Non-intersecting intervals with the left interval ending first|upper`doesNotIntersect`lower'=LeftNothing-- Non-intersecting intervals with the right interval first|upper'`doesNotIntersect`lower=RightNothing-- Complete or partial overlap, with the left interval ending first|upper<=upper'=lowerBound`seq`Left(Just(lowerBound,upper))-- Complete or partial overlap, with the right interval ending first|otherwise=lowerBound`seq`Right(Just(lowerBound,upper'))wherelowerBound=maxlowerlower'--------------------------------- Parsing and pretty printing--instanceTextVersionRangewheredisp=fst.foldVersionRange'-- precedence:(Disp.text"-any",0::Int)(\v->(Disp.text"=="<>dispv,0))(\v->(Disp.char'>'<>dispv,0))(\v->(Disp.char'<'<>dispv,0))(\v->(Disp.text">="<>dispv,0))(\v->(Disp.text"<="<>dispv,0))(\v_->(Disp.text"=="<>dispWildv,0))(\(r1,p1)(r2,p2)->(punct2p1r1<+>Disp.text"||"<+>punct2p2r2,2))(\(r1,p1)(r2,p2)->(punct1p1r1<+>Disp.text"&&"<+>punct1p2r2,1))wheredispWild(Versionb_)=Disp.hcat(Disp.punctuate(Disp.char'.')(mapDisp.intb))<>Disp.text".*"punctpp'|p<p'=Disp.parens|otherwise=idparse=exprwhereexpr=doParse.skipSpacest<-termParse.skipSpaces(do_<-Parse.string"||"Parse.skipSpacese<-exprreturn(UnionVersionRangeste)+++returnt)term=dof<-factorParse.skipSpaces(do_<-Parse.string"&&"Parse.skipSpacest<-termreturn(IntersectVersionRangesft)+++returnf)factor=Parse.choice$parensexpr:parseAnyVersion:parseWildcardRange:mapparseRangeOprangeOpsparseAnyVersion=Parse.string"-any">>returnAnyVersionparseWildcardRange=do_<-Parse.string"=="Parse.skipSpacesbranch<-Parse.sepBy1digits(Parse.char'.')_<-Parse.char'.'_<-Parse.char'*'return(WildcardVersion(Versionbranch[]))parensp=Parse.between(Parse.char'('>>Parse.skipSpaces)(Parse.char')'>>Parse.skipSpaces)(doa<-pParse.skipSpacesreturna)digits=dofirst<-Parse.satisfyChar.isDigitiffirst=='0'thenreturn0elsedorest<-Parse.munchChar.isDigitreturn(read(first:rest))parseRangeOp(s,f)=Parse.strings>>Parse.skipSpaces>>fmapfparserangeOps=[("<",EarlierVersion),("<=",orEarlierVersion),(">",LaterVersion),(">=",orLaterVersion),("==",ThisVersion)]