-
Notifications
You must be signed in to change notification settings - Fork 5
LineRange and StringDiff invariants static checks
#34
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
ninioArtillero
wants to merge
2
commits into
master
Choose a base branch
from
xg/output-checks
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+86
−27
Open
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,3 +1,14 @@ | ||
| -- With PLE (Proof by Logical Evaluation) reflected predicates like | ||
| -- '_coherentDiff' are automatically unfolded when verifying refinement types. | ||
| -- Without it, LiquidHaskell cannot prove obligations like @_coherentDiff (Both x x) = True@ | ||
| -- because the reflected function's definition is not instantiated in the SMT context. | ||
| -- For example, the following check would fail: | ||
| -- | ||
| -- {-@ good :: StringDiff @-} | ||
| -- good = Both ["hello"] ["hello"] | ||
| -- | ||
| -- without PLE. | ||
| {-@ LIQUID "--ple" @-} | ||
| ----------------------------------------------------------------------------- | ||
| -- | | ||
| -- Module : Data.Algorithm.DiffOutput | ||
|
|
@@ -16,51 +27,76 @@ import Text.PrettyPrint hiding ((<>)) | |
| import Data.Char | ||
| import Data.List | ||
|
|
||
| -- Non-empty lists as refinements of regular lists. | ||
| {-@ type NonEmpty a = {xs : [a] | len xs >0}@-} | ||
|
|
||
| {-@ reflect _coherentDiff @-} | ||
| -- | This functions checks that both contents match whenever we have a 'Both' value. | ||
| -- Used in LiquidHaskell logic as a predicate. | ||
| _coherentDiff :: Eq c => Diff c -> Bool | ||
| _coherentDiff (Both x y) = x == y | ||
| _coherentDiff (First _) = True | ||
| _coherentDiff (Second _) = True | ||
|
|
||
| -- This refinement type synonym encodes the invariants on the 'Diff' type | ||
| -- that we expect throughout this module. Namely: | ||
| -- | ||
| -- * They have non-empty contents | ||
| -- * 'Both' values have equal arguments | ||
| {-@ type StringDiff = {d : Diff (NonEmpty String) | _coherentDiff d} @-} | ||
|
|
||
| -- | Converts 'Diff's to 'DiffOperation's. 'First' and 'Second' | ||
| -- ocurrances are converted to 'Addition' and 'Deletion', respectively, while | ||
| -- consecutive ocurrances of them are replaced by a 'Change'. | ||
| {-@ diffToLineRanges :: [StringDiff] -> [DiffOperation LineRange] @-} | ||
| diffToLineRanges :: [Diff [String]] -> [DiffOperation LineRange] | ||
| diffToLineRanges = toLineRange 1 1 | ||
| where | ||
| -- | In @toLineRange x y ds@, @x@ is the index of the current string in the | ||
| -- left input of the diff @ds@, and @y@ is the index of the corresponding | ||
| -- string in the right input of the diff @ds@. | ||
| {-@ toLineRange :: Int -> Int -> diffs : [Diff[String]] -> [DiffOperation LineRange] / [len diffs, 0] @-} | ||
| {-@ toLineRange :: {l : Int | l >= 1} | ||
| -> {r : Int | r >= 1} | ||
| -> diffs : [StringDiff] | ||
| -> [DiffOperation LineRange] / [len diffs, 0] @-} | ||
| toLineRange :: Int -> Int -> [Diff [String]] -> [DiffOperation LineRange] | ||
| toLineRange _ _ []=[] | ||
| -- If the lines are the same, we just move forward. | ||
| toLineRange leftLine rightLine (Both ls _:rs)= | ||
| let lins=length ls | ||
| in toLineRange (leftLine+lins) (rightLine+lins) rs | ||
| -- A 'Change' is introduced when an addition is followed by a deletion, or vice versa. | ||
| toLineRange leftLine rightLine (Second lsS:First lsF:rs)= | ||
| toLineRange leftLine rightLine (Second lsS : First lsF : rs) = | ||
| toChange leftLine rightLine lsF lsS rs | ||
| toLineRange leftLine rightLine (First lsF:Second lsS:rs)= | ||
| toLineRange leftLine rightLine (First lsF : Second lsS : rs) = | ||
| toChange leftLine rightLine lsF lsS rs | ||
| -- Introduce 'Addition's. | ||
| toLineRange leftLine rightLine (Second lsS:rs)= | ||
| let linesS=length lsS | ||
| diff=Addition (LineRange (rightLine,rightLine+linesS-1) lsS) (leftLine-1) | ||
| in diff : toLineRange leftLine (rightLine+linesS) rs | ||
| toLineRange leftLine rightLine (Second lsS : rs) = | ||
| let diff = Addition (mkLineRange rightLine lsS) (leftLine-1) | ||
| in diff : toLineRange leftLine (rightLine + length lsS) rs | ||
| -- Introduce 'Deletion's. | ||
| toLineRange leftLine rightLine (First lsF:rs)= | ||
| let linesF=length lsF | ||
| diff=Deletion (LineRange (leftLine,leftLine+linesF-1) lsF) (rightLine-1) | ||
| in diff: toLineRange(leftLine+linesF) rightLine rs | ||
| toLineRange leftLine rightLine (First lsF : rs)= | ||
| let diff = Deletion (mkLineRange leftLine lsF) (rightLine-1) | ||
| in diff : toLineRange (leftLine + length lsF) rightLine rs | ||
| -- | Build 'Change's from adjacent additions and deletions. | ||
| {-@ toChange :: Int -> Int -> [String] -> [String] -> diffs : [Diff [String]] -> [DiffOperation LineRange] / [len diffs, 1] @-} | ||
| {-@ toChange :: {l : Int | l >= 1} | ||
| -> {r : Int | r >= 1} | ||
| -> {lf : [String] | len lf > 0} | ||
| -> {ls : [String] | len ls > 0} | ||
| -> diffs : [StringDiff] | ||
| -> [DiffOperation LineRange] | ||
| / [len diffs, 1] @-} | ||
| toChange :: Int -- ^ Current left line number. | ||
| -> Int -- ^ Current right line number. | ||
| -> [String] -- ^ Lines from the 'First' list (corresponding to deletions). | ||
| -> [String] -- ^ Lines from the 'Second' list (corresponding to additions). | ||
| -> [Diff [String]] -- ^ Remaining 'Diff's. | ||
| -> [DiffOperation LineRange] | ||
| toChange leftLine rightLine lsF lsS rs= | ||
| let linesS=length lsS | ||
| linesF=length lsF | ||
| in Change (LineRange (leftLine,leftLine+linesF-1) lsF) (LineRange (rightLine,rightLine+linesS-1) lsS) | ||
| : toLineRange (leftLine+linesF) (rightLine+linesS) rs | ||
| Change (mkLineRange leftLine lsF) (mkLineRange rightLine lsS) | ||
| : toLineRange (leftLine + length lsF) (rightLine + length lsS) rs | ||
|
|
||
| {-@ ppDiff :: [StringDiff] -> String @-} | ||
| -- | Pretty print the differences. The output is similar to the output of the @diff@ utility. | ||
| -- | ||
| -- > > putStr (ppDiff (getGroupedDiff ["a","b","c","d","e"] ["a","c","d","f"])) | ||
|
|
@@ -114,6 +150,7 @@ parsePrettyDiffs = reverse . doParse [] . lines | |
| Just nd -> doParse (nd:acc) r | ||
| _ -> doParse acc r | ||
|
|
||
| {-@ parseDiff :: s:{[String] | len s > 0} -> {v:(Maybe (DiffOperation LineRange), [String]) | len (snd v) < len s} @-} | ||
| parseDiff :: [String] -> (Maybe (DiffOperation LineRange), [String]) | ||
| parseDiff [] = (Nothing,[]) | ||
| parseDiff (h:rs) = let | ||
|
|
@@ -128,46 +165,56 @@ parsePrettyDiffs = reverse . doParse [] . lines | |
| ('c':hrs2) -> parseChange r1 hrs2 rs | ||
| _ -> (Nothing,rs) | ||
|
|
||
| {-@ parseDel :: (Nat, Nat) -> String -> rs:[String] -> {v:(Maybe (DiffOperation LineRange), [String]) | len (snd v) <= len rs} @-} | ||
| parseDel :: (LineNo, LineNo) -> String -> [String] -> (Maybe (DiffOperation LineRange), [String]) | ||
| parseDel r1 hrs2 rs = let | ||
| -- NOTE: the wildcard should correspond to the end of line, | ||
| -- but is ignored for simplicity. | ||
| (r2,_) = parseRange hrs2 | ||
| (ls,rs2) = span (isPrefixOf "<") rs | ||
| in (Just $ Deletion (LineRange r1 (map (drop 2) ls)) (fst r2), rs2) | ||
| contents = map (drop 2) ls | ||
| in case contents of | ||
| (_:_) -> (Just $ Deletion (mkLineRange (fst r1) contents) (fst r2), rs2) | ||
| _ -> (Nothing, rs2) | ||
|
|
||
| {-@ parseAdd :: (Nat, Nat) -> String -> rs:[String] -> {v:(Maybe (DiffOperation LineRange), [String]) | len (snd v) <= len rs} @-} | ||
| parseAdd :: (LineNo, LineNo) -> String -> [String] -> (Maybe (DiffOperation LineRange), [String]) | ||
| parseAdd r1 hrs2 rs = let | ||
| -- NOTE: the wildcard should correspond to the end of line, | ||
| -- but is ignored for simplicity. | ||
| (r2,_) = parseRange hrs2 | ||
| (ls,rs2) = span (isPrefixOf ">") rs | ||
| in (Just $ Addition (LineRange r2 (map (drop 2) ls)) (fst r1), rs2) | ||
| contents = map (drop 2) ls | ||
| in case contents of | ||
| (_:_) -> (Just $ Addition (mkLineRange (fst r2) contents) (fst r1), rs2) | ||
| _ -> (Nothing, rs2) | ||
|
|
||
| {-@ parseChange :: (Nat, Nat) -> String -> rs:[String] -> {v:(Maybe (DiffOperation LineRange), [String]) | len (snd v) <= len rs} @-} | ||
| parseChange :: (LineNo, LineNo) -> String -> [String] -> (Maybe (DiffOperation LineRange), [String]) | ||
| parseChange r1 hrs2 rs = let | ||
| -- NOTE: the wildcard should correspond to the end of line, | ||
| -- but is ignored for simplicity. | ||
| (r2,_) = parseRange hrs2 | ||
| (ls1,rs2) = span (isPrefixOf "<") rs | ||
| in case rs2 of | ||
| -- The left and right diff of a 'Change' are separated by a "---" line. | ||
| ("---":rs3) -> let | ||
| (ls2,rs4) = span (isPrefixOf ">") rs3 | ||
| in (Just $ Change (LineRange r1 (map (drop 2) ls1)) (LineRange r2 (map (drop 2) ls2)), rs4) | ||
| contents1 = map (drop 2) ls1 | ||
| contents2 = map (drop 2) ls2 | ||
| in case (contents1, contents2) of | ||
| (_:_, _:_) -> (Just $ Change (mkLineRange (fst r1) contents1) (mkLineRange (fst r2) contents2), rs4) | ||
| _ -> (Nothing, rs4) | ||
| _ -> (Nothing,rs2) | ||
|
|
||
| parseRange :: String -> ((LineNo, LineNo),String) | ||
| {-@ parseRange :: String -> {v : ((Nat, Nat), String) | fst (fst v) <= snd (fst v)} @-} | ||
| parseRange :: String -> ((LineNo, LineNo), String) | ||
| parseRange l = let | ||
| (fstLine,rs) = span isDigit l | ||
| a = max 0 (read fstLine) | ||
| (sndLine,rs3) = case rs of | ||
| -- The comma is used to separate | ||
| -- the start and end line numbers in a range, | ||
| -- but is omitted if they are the same. | ||
| -- i.e. the range is a single line. | ||
| (',':rs2) -> span isDigit rs2 | ||
| _ -> (fstLine,rs) | ||
| in ((read fstLine,read sndLine),rs3) | ||
| b = max a (read sndLine) | ||
| in ((a, b), rs3) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Looks like this is changing behavior of Before After |
||
|
|
||
| -- | Line number alias. Always non-negative. | ||
| type LineNo = Int | ||
|
|
@@ -180,11 +227,23 @@ type LineNo = Int | |
| -- > snd lrNumbers - fst lrNumbers + 1 == length lrContents | ||
| -- | ||
| -- which imply @lrContents@ cannot be empty. | ||
| {-@ | ||
| data LineRange = LineRange { lrNumbers :: {range : (Nat, Nat) | fst range <= snd range} | ||
| , lrContents :: {contents : [String] | snd lrNumbers - fst lrNumbers = len contents - 1} | ||
| } | ||
| @-} | ||
| data LineRange = LineRange { lrNumbers :: (LineNo, LineNo) | ||
| , lrContents :: [String] | ||
| } | ||
| deriving (Show, Read, Eq, Ord) | ||
|
|
||
| -- | Smart constructor for 'LineRange' that computes the end line from the | ||
| -- start line and the content length, guaranteeing that its content length and | ||
| -- range match. | ||
| {-@ mkLineRange :: start:Nat -> contents:{[String] | len contents > 0} -> LineRange @-} | ||
| mkLineRange :: Int -> [String] -> LineRange | ||
| mkLineRange start contents = LineRange (start, start + length contents - 1) contents | ||
|
|
||
| -- | Diff operation representing changes to apply. | ||
| data DiffOperation a | ||
| = Deletion a LineNo -- ^ Element deleted on the left input, line number | ||
|
|
||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is changing behavior of
parseDel.Before
parseDel (a, b) "1,2" [] == (Just (Deletion (LineRange (a, a - 1) []) 1), []).After
parseDel (a, b) "1,2" [] == (Nothing, "").The other parsing functions are modified in a similar way.