{-# LANGUAGE CPP, DeriveDataTypeable #-}{-| Position information for syntax. Crucial for giving good error messages.
-}moduleAgda.Syntax.Position(-- * PositionsPosition(..),positionInvariant,startPos,movePos,movePosByString,backupPos-- * Intervals,Interval(..),intervalInvariant,takeI,dropI-- * Ranges,Range(..),rangeInvariant,noRange,posToRange,rStart,rEnd,rangeToInterval,continuous,continuousPerLine,HasRange(..),SetRange(..),KillRange(..),killRange1,killRange2,killRange3,killRange4,killRange5,killRange6,killRange7,withRangeOf,fuseRange,fuseRanges,beginningOf-- * Tests,tests)whereimportData.Generics(Typeable,Data)importData.ListimportData.FunctionimportData.Set(Set,(\\))importqualifiedData.SetasSetimportData.IntimportAgda.Utils.QuickCheckimportControl.ApplicativeimportControl.MonadimportAgda.Utils.FileNamehiding(tests)importAgda.Utils.TestHelpers#include "../undefined.h"importAgda.Utils.Impossible{--------------------------------------------------------------------------
Types and classes
--------------------------------------------------------------------------}-- | Represents a point in the input.---- If two positions have the same 'srcFile' and 'posPos' components,-- then the final two components should be the same as well, but since-- this can be hard to enforce the program should not rely too much on-- the last two components; they are mainly there to improve error-- messages for the user.---- Note the invariant which positions have to satisfy: 'positionInvariant'.dataPosition=Pn{srcFile::MaybeAbsolutePath-- ^ File.,posPos::!Int32-- ^ Position.,posLine::!Int32-- ^ Line number, counting from 1.,posCol::!Int32-- ^ Column number, counting from 1.}deriving(Typeable,Data)positionInvariant::Position->BoolpositionInvariantp=posPosp>0&&posLinep>0&&posColp>0importantPartp=(srcFilep,posPosp)instanceEqPositionwhere(==)=(==)`on`importantPartinstanceOrdPositionwherecompare=compare`on`importantPart-- | An interval. The @iEnd@ position is not included in the interval.---- Note the invariant which intervals have to satisfy: 'intervalInvariant'.dataInterval=Interval{iStart,iEnd::!Position}deriving(Typeable,Data,Eq,Ord)intervalInvariant::Interval->BoolintervalInvarianti=allpositionInvariant[iStarti,iEndi]&&iStarti<=iEndi-- | The length of an interval, assuming that the start and end-- positions are in the same file.iLength::Interval->Int32iLengthi=posPos(iEndi)-posPos(iStarti)-- | A range is a list of intervals. The intervals should be-- consecutive and separated.---- Note the invariant which ranges have to satisfy: 'rangeInvariant'.newtypeRange=Range[Interval]deriving(Typeable,Data,Eq,Ord)rangeInvariant::Range->BoolrangeInvariant(Range[])=TruerangeInvariant(Rangeis)=allintervalInvariantis&&and(zipWith(<)(mapiEnd$initis)(mapiStart$tailis))-- | Things that have a range are instances of this class.classHasRangetwheregetRange::t->RangeinstanceHasRangeIntervalwheregetRangei=Range[i]instanceHasRangeRangewheregetRange=idinstanceHasRangea=>HasRange[a]wheregetRange=foldrfuseRangenoRangeinstance(HasRangea,HasRangeb)=>HasRange(a,b)wheregetRange=uncurryfuseRangeinstance(HasRangea,HasRangeb,HasRangec)=>HasRange(a,b,c)wheregetRange(x,y,z)=getRange(x,(y,z))instance(HasRangea,HasRangeb,HasRangec,HasRanged)=>HasRange(a,b,c,d)wheregetRange(x,y,z,w)=getRange(x,(y,(z,w)))instanceHasRangea=>HasRange(Maybea)wheregetRangeNothing=noRangegetRange(Justa)=getRangea-- | If it is also possible to set the range, this is the class.---- Instances should satisfy @'getRange' ('setRange' r x) == r@.classHasRanget=>SetRangetwheresetRange::Range->t->tinstanceSetRangeRangewheresetRange=const-- | Killing the range of an object sets all range information to 'noRange'.classKillRangeawherekillRange::a->akillRange1fa=f(killRangea)killRange2fa=killRange1(f$killRangea)killRange3fa=killRange2(f$killRangea)killRange4fa=killRange3(f$killRangea)killRange5fa=killRange4(f$killRangea)killRange6fa=killRange5(f$killRangea)killRange7fa=killRange6(f$killRangea)instanceKillRangeRangewherekillRange_=noRangeinstanceKillRangea=>KillRange[a]wherekillRange=mapkillRangeinstance(KillRangea,KillRangeb)=>KillRange(a,b)wherekillRange(x,y)=(killRangex,killRangey)instanceKillRangea=>KillRange(Maybea)wherekillRange=fmapkillRangeinstance(KillRangea,KillRangeb)=>KillRange(Eitherab)wherekillRange(Leftx)=Left$killRangexkillRange(Rightx)=Right$killRangex{--------------------------------------------------------------------------
Pretty printing
--------------------------------------------------------------------------}instanceShowPositionwhereshow(PnNothing_lc)=showl++","++showcshow(Pn(Justf)_lc)=filePathf++":"++showl++","++showcinstanceShowIntervalwhereshow(Intervalse)=file++start++"-"++endwheref=srcFilessl=posLinesel=posLineesc=posColsec=posColefile=casefofNothing->""Justf->filePathf++":"start=showsl++","++showscend|sl==el=showec|otherwise=showel++","++showecinstanceShowRangewhereshowr=caserangeToIntervalrofNothing->""Justi->showi{--------------------------------------------------------------------------
Functions on postitions and ranges
--------------------------------------------------------------------------}-- | The first position in a file: position 1, line 1, column 1.startPos::MaybeAbsolutePath->PositionstartPosf=Pn{srcFile=f,posPos=1,posLine=1,posCol=1}-- | Ranges between two unknown positionsnoRange::RangenoRange=Range[]-- | Advance the position by one character.-- A newline character (@'\n'@) moves the position to the first-- character in the next line. Any other character moves the-- position to the next column.movePos::Position->Char->PositionmovePos(Pnfplc)'\n'=Pnf(p+1)(l+1)1movePos(Pnfplc)_=Pnf(p+1)l(c+1)-- | Advance the position by a string.---- > movePosByString = foldl' movePosmovePosByString::Position->String->PositionmovePosByString=foldl'movePos-- | Backup the position by one character.---- Precondition: The character must not be @'\n'@.backupPos::Position->PositionbackupPos(Pnfplc)=Pnf(p-1)l(c-1)-- | Extracts the interval corresponding to the given string, assuming-- that the string starts at the beginning of the given interval.---- Precondition: The string must not be too long for the interval.takeI::String->Interval->IntervaltakeIsi|genericLengths>iLengthi=__IMPOSSIBLE__|otherwise=i{iEnd=movePosByString(iStarti)s}-- | Removes the interval corresponding to the given string from the-- given interval, assuming that the string starts at the beginning of-- the interval.---- Precondition: The string must not be too long for the interval.dropI::String->Interval->IntervaldropIsi|genericLengths>iLengthi=__IMPOSSIBLE__|otherwise=i{iStart=movePosByString(iStarti)s}-- | Converts two positions to a range.posToRange::Position->Position->RangeposToRangep1p2|p1<p2=Range[Intervalp1p2]|otherwise=Range[Intervalp2p1]-- | Converts a range to an interval, if possible.rangeToInterval::Range->MaybeIntervalrangeToInterval(Range[])=NothingrangeToInterval(Rangeis)=Just$Interval{iStart=iStart(headis),iEnd=iEnd(lastis)}-- | Returns the shortest continuous range containing the given one.continuous::Range->Rangecontinuousr=caserangeToIntervalrofNothing->Range[]Justi->Range[i]-- | Removes gaps between intervals on the same line.continuousPerLine::Range->RangecontinuousPerLine(Range[])=Range[]continuousPerLine(Range(i:is))=Range$fusei$sortBy(compare`on`iStart)iswherefusei[]=[i]fusei(j:is)|sameLineij=fuse(fuseIntervalsij)is|otherwise=i:fusejissameLineij=posLine(iEndi)==posLine(iStartj)-- | The initial position in the range, if any.rStart::Range->MaybePositionrStartr=iStart<$>rangeToIntervalr-- | The position after the final position in the range, if any.rEnd::Range->MaybePositionrEndr=iEnd<$>rangeToIntervalr-- | Finds the least interval which covers the arguments.fuseIntervals::Interval->Interval->IntervalfuseIntervalsxy=Interval{iStart=headps,iEnd=lastps}whereps=sort[iStartx,iStarty,iEndx,iEndy]-- | Finds a range which covers the arguments.fuseRanges::Range->Range->RangefuseRanges(Rangeis)(Rangejs)=Range(helperisjs)wherehelper[]js=jshelperis[]=ishelper(i:is)(j:js)|iEndi<iStartj=i:helperis(j:js)|iEndj<iStarti=j:helper(i:is)js|iEndi<iEndj=helperis(fuseIntervalsij:js)|otherwise=helper(fuseIntervalsij:is)jsfuseRange::(HasRangeu,HasRanget)=>u->t->RangefuseRangexy=fuseRanges(getRangex)(getRangey)-- | @beginningOf r@ is an empty range (a single, empty interval)-- positioned at the beginning of @r@. If @r@ does not have a-- beginning, then 'noRange' is returned.beginningOf::Range->RangebeginningOfr=caserStartrofJustpos->Range[Interval{iStart=pos,iEnd=pos}]Nothing->noRange-- | @x `withRangeOf` y@ sets the range of @x@ to the range of @y@.withRangeOf::(SetRanget,HasRangeu)=>t->u->tx`withRangeOf`y=setRange(getRangey)x-------------------------------------------------------------------------- Test suite-- | The positions corresponding to the interval, /including/ the-- end-point. This function assumes that the two end points belong to-- the same file. Note that the 'Arbitrary' instance for 'Position's-- uses a single, hard-wired file name.iPositions::Interval->SetInt32iPositionsi=Set.fromList[posPos(iStarti)..posPos(iEndi)]-- | The positions corresponding to the range, including the-- end-points. All ranges are assumed to belong to a single file.rPositions::Range->SetInt32rPositions(Rangeis)=Set.unions(mapiPositionsis)-- | Constructs the least interval containing all the elements in the-- set.makeInterval::SetInt32->SetInt32makeIntervals|Set.nulls=Set.empty|otherwise=Set.fromList[Set.findMins..Set.findMaxs]prop_iLengthi=iLengthi>=0prop_startPos=positionInvariant.startPosprop_noRange=rangeInvariantnoRangeprop_takeI_dropIi=forAll(choose(0,toInteger$iLengthi))$\n->lets=genericReplicaten' 't=takeIsid=dropIsiinintervalInvariantt&&intervalInvariantd&&fuseIntervalstd==iprop_rangeToInterval(Range[])=Trueprop_rangeToIntervalr=intervalInvarianti&&iPositionsi==makeInterval(rPositionsr)whereJusti=rangeToIntervalrprop_continuousr=rangeInvariantcr&&rPositionscr==makeInterval(rPositionsr)wherecr=continuousrprop_fuseIntervalsi1=forAll(intervalInSameFileAsi1)$\i2->leti=fuseIntervalsi1i2inintervalInvarianti&&iPositionsi==makeInterval(Set.union(iPositionsi1)(iPositionsi2))prop_fuseRanges::Range->Range->Boolprop_fuseRangesr1r2=rangeInvariantr&&rPositionsr==Set.union(rPositionsr1)(rPositionsr2)wherer=fuseRangesr1r2prop_beginningOfr=rangeInvariant(beginningOfr)instanceArbitraryPositionwherearbitrary=dosrcFile<-arbitraryNonZero(NonNegativepos')<-arbitraryletpos=fromIntegerpos'line=predpos`div`10+1col=predpos`mod`10+1return(Pn{srcFile=srcFile,posPos=pos,posLine=line,posCol=col})-- | Sets the 'srcFile' components of the interval.setFile::MaybeAbsolutePath->Interval->IntervalsetFilef(Intervalp1p2)=Interval(p1{srcFile=f})(p2{srcFile=f})-- | Generates an interval located in the same file as the given-- interval.intervalInSameFileAsi=setFile(srcFile$iStarti)<$>arbitraryprop_intervalInSameFileAsi=forAll(intervalInSameFileAsi)$\i'->intervalInvarianti'&&srcFile(iStarti)==srcFile(iStarti')instanceArbitraryIntervalwherearbitrary=do(p1,p2)<-liftM2(,)arbitraryarbitrarylet[p1',p2']=sort[p1,p2{srcFile=srcFilep1}]return(Intervalp1'p2')instanceArbitraryRangewherearbitrary=Range.fuse.sort.fixFiles<$>arbitrarywherefixFiles[]=[]fixFiles(i:is)=i:map(setFile$srcFile$iStarti)isfuse(i1:i2:is)|iEndi1>=iStarti2=fuse(fuseIntervalsi1i2:is)|otherwise=i1:fuse(i2:is)fuseis=is-- | Test suite.tests::IOBooltests=runTests"Agda.Syntax.Position"[quickCheck'positionInvariant,quickCheck'intervalInvariant,quickCheck'rangeInvariant,quickCheck'prop_iLength,quickCheck'prop_startPos,quickCheck'prop_noRange,quickCheck'prop_takeI_dropI,quickCheck'prop_rangeToInterval,quickCheck'prop_continuous,quickCheck'prop_fuseIntervals,quickCheck'prop_fuseRanges,quickCheck'prop_beginningOf,quickCheck'prop_intervalInSameFileAs]