2018-04-27 22:35:15 +02:00
|
|
|
{-# LANGUAGE DataKinds #-}
|
|
|
|
{-# LANGUAGE FlexibleContexts #-}
|
2018-05-07 07:13:30 +02:00
|
|
|
{-# LANGUAGE FlexibleInstances #-}
|
|
|
|
{-# LANGUAGE LambdaCase #-}
|
2018-04-27 22:35:15 +02:00
|
|
|
{-# LANGUAGE MonoLocalBinds #-}
|
2018-05-07 07:13:30 +02:00
|
|
|
{-# LANGUAGE NoMonomorphismRestriction #-}
|
|
|
|
{-# LANGUAGE OverloadedStrings #-}
|
|
|
|
{-# LANGUAGE UndecidableInstances #-}
|
|
|
|
|
2018-04-27 22:35:15 +02:00
|
|
|
{-# OPTIONS -Wno-orphans#-}
|
2018-05-07 07:13:30 +02:00
|
|
|
|
2019-03-17 22:47:38 +01:00
|
|
|
module PrettyParseTests where
|
2018-04-27 22:35:15 +02:00
|
|
|
|
2018-05-07 07:13:30 +02:00
|
|
|
import Data.Algorithm.Diff
|
|
|
|
import Data.Algorithm.DiffOutput
|
|
|
|
import Data.Char
|
|
|
|
import Data.Fix
|
2019-03-17 22:47:38 +01:00
|
|
|
import qualified Data.List.NonEmpty as NE
|
|
|
|
import Data.Text ( Text
|
|
|
|
, pack
|
|
|
|
)
|
2018-11-17 05:51:18 +01:00
|
|
|
import Data.Text.Prettyprint.Doc
|
2018-05-14 22:19:32 +02:00
|
|
|
import Hedgehog
|
2019-03-17 22:47:38 +01:00
|
|
|
import qualified Hedgehog.Gen as Gen
|
|
|
|
import qualified Hedgehog.Range as Range
|
2018-05-07 07:13:30 +02:00
|
|
|
import Nix.Atoms
|
|
|
|
import Nix.Expr
|
|
|
|
import Nix.Parser
|
|
|
|
import Nix.Pretty
|
|
|
|
import Test.Tasty
|
2018-05-14 22:19:32 +02:00
|
|
|
import Test.Tasty.Hedgehog
|
2019-03-17 22:47:38 +01:00
|
|
|
import Text.Megaparsec ( Pos
|
|
|
|
, SourcePos
|
|
|
|
, mkPos
|
|
|
|
)
|
|
|
|
import qualified Text.Show.Pretty as PS
|
2018-05-07 07:13:30 +02:00
|
|
|
|
2018-05-14 22:19:32 +02:00
|
|
|
asciiString :: MonadGen m => m String
|
|
|
|
asciiString = Gen.list (Range.linear 1 15) Gen.lower
|
2018-04-27 22:35:15 +02:00
|
|
|
|
|
|
|
asciiText :: Gen Text
|
|
|
|
asciiText = pack <$> asciiString
|
|
|
|
|
|
|
|
-- Might want to replace this instance with a constant value
|
2018-05-14 22:19:32 +02:00
|
|
|
genPos :: Gen Pos
|
|
|
|
genPos = mkPos <$> Gen.int (Range.linear 1 256)
|
|
|
|
|
|
|
|
genSourcePos :: Gen SourcePos
|
|
|
|
genSourcePos = SourcePos <$> asciiString <*> genPos <*> genPos
|
|
|
|
|
|
|
|
genKeyName :: Gen (NKeyName NExpr)
|
2019-03-17 22:47:38 +01:00
|
|
|
genKeyName =
|
|
|
|
Gen.choice [DynamicKey <$> genAntiquoted genString, StaticKey <$> asciiText]
|
2018-05-14 22:19:32 +02:00
|
|
|
|
|
|
|
genAntiquoted :: Gen a -> Gen (Antiquoted a NExpr)
|
2019-03-17 22:47:38 +01:00
|
|
|
genAntiquoted gen =
|
|
|
|
Gen.choice [Plain <$> gen, pure EscapedNewline, Antiquoted <$> genExpr]
|
2018-05-14 22:19:32 +02:00
|
|
|
|
|
|
|
genBinding :: Gen (Binding NExpr)
|
|
|
|
genBinding = Gen.choice
|
|
|
|
[ NamedVar <$> genAttrPath <*> genExpr <*> genSourcePos
|
2019-03-17 22:47:38 +01:00
|
|
|
, Inherit
|
|
|
|
<$> Gen.maybe genExpr
|
|
|
|
<*> Gen.list (Range.linear 0 5) genKeyName
|
|
|
|
<*> genSourcePos
|
2018-05-14 22:19:32 +02:00
|
|
|
]
|
|
|
|
|
|
|
|
genString :: Gen (NString NExpr)
|
|
|
|
genString = Gen.choice
|
|
|
|
[ DoubleQuoted <$> Gen.list (Range.linear 0 5) (genAntiquoted asciiText)
|
2019-03-17 22:47:38 +01:00
|
|
|
, Indented <$> Gen.int (Range.linear 0 10) <*> Gen.list
|
|
|
|
(Range.linear 0 5)
|
|
|
|
(genAntiquoted asciiText)
|
2018-05-14 22:19:32 +02:00
|
|
|
]
|
|
|
|
|
|
|
|
genAttrPath :: Gen (NAttrPath NExpr)
|
2019-03-17 22:47:38 +01:00
|
|
|
genAttrPath = (NE.:|) <$> genKeyName <*> Gen.list (Range.linear 0 4) genKeyName
|
2018-05-14 22:19:32 +02:00
|
|
|
|
|
|
|
genParams :: Gen (Params NExpr)
|
|
|
|
genParams = Gen.choice
|
2019-03-17 22:47:38 +01:00
|
|
|
[ Param <$> asciiText
|
|
|
|
, ParamSet
|
|
|
|
<$> Gen.list (Range.linear 0 10) ((,) <$> asciiText <*> Gen.maybe genExpr)
|
|
|
|
<*> Gen.bool
|
|
|
|
<*> Gen.choice [pure Nothing, Just <$> asciiText]
|
2018-05-14 22:19:32 +02:00
|
|
|
]
|
|
|
|
|
|
|
|
genAtom :: Gen NAtom
|
|
|
|
genAtom = Gen.choice
|
2019-03-17 22:47:38 +01:00
|
|
|
[ NInt <$> Gen.integral (Range.linear 0 1000)
|
2018-05-14 22:19:32 +02:00
|
|
|
, NFloat <$> Gen.float (Range.linearFrac 0.0 1000.0)
|
2019-03-17 22:47:38 +01:00
|
|
|
, NBool <$> Gen.bool
|
|
|
|
, pure NNull
|
|
|
|
]
|
2018-04-27 22:35:15 +02:00
|
|
|
|
2018-05-07 07:13:30 +02:00
|
|
|
-- This is written by hand so we can use `fairList` rather than the normal
|
|
|
|
-- list Arbitrary instance which makes the generator terminate. The
|
|
|
|
-- distribution is not scientifically chosen.
|
2018-05-14 22:19:32 +02:00
|
|
|
genExpr :: Gen NExpr
|
2019-03-17 22:47:38 +01:00
|
|
|
genExpr = Gen.sized $ \(Size n) -> Fix <$> if n < 2
|
|
|
|
then Gen.choice [genConstant, genStr, genSym, genLiteralPath, genEnvPath]
|
|
|
|
else Gen.frequency
|
|
|
|
[ (1 , genConstant)
|
|
|
|
, (1 , genSym)
|
|
|
|
, (4 , Gen.resize (Size (n `div` 3)) genIf)
|
|
|
|
, (10, genRecSet)
|
|
|
|
, (20, genSet)
|
|
|
|
, (5 , genList)
|
|
|
|
, (2 , genUnary)
|
|
|
|
, (2, Gen.resize (Size (n `div` 3)) genBinary)
|
|
|
|
, (3, Gen.resize (Size (n `div` 3)) genSelect)
|
|
|
|
, (20, Gen.resize (Size (n `div` 2)) genAbs)
|
|
|
|
, (2, Gen.resize (Size (n `div` 2)) genHasAttr)
|
|
|
|
, (10, Gen.resize (Size (n `div` 2)) genLet)
|
|
|
|
, (10, Gen.resize (Size (n `div` 2)) genWith)
|
|
|
|
, (1, Gen.resize (Size (n `div` 2)) genAssert)
|
|
|
|
]
|
2018-05-14 22:19:32 +02:00
|
|
|
where
|
2019-03-17 22:47:38 +01:00
|
|
|
genConstant = NConstant <$> genAtom
|
|
|
|
genStr = NStr <$> genString
|
|
|
|
genSym = NSym <$> asciiText
|
|
|
|
genList = NList <$> fairList genExpr
|
|
|
|
genSet = NSet <$> fairList genBinding
|
|
|
|
genRecSet = NRecSet <$> fairList genBinding
|
2018-05-14 22:19:32 +02:00
|
|
|
genLiteralPath = NLiteralPath . ("./" ++) <$> asciiString
|
2019-03-17 22:47:38 +01:00
|
|
|
genEnvPath = NEnvPath <$> asciiString
|
|
|
|
genUnary = NUnary <$> Gen.enumBounded <*> genExpr
|
|
|
|
genBinary = NBinary <$> Gen.enumBounded <*> genExpr <*> genExpr
|
|
|
|
genSelect = NSelect <$> genExpr <*> genAttrPath <*> Gen.maybe genExpr
|
|
|
|
genHasAttr = NHasAttr <$> genExpr <*> genAttrPath
|
|
|
|
genAbs = NAbs <$> genParams <*> genExpr
|
|
|
|
genLet = NLet <$> fairList genBinding <*> genExpr
|
|
|
|
genIf = NIf <$> genExpr <*> genExpr <*> genExpr
|
|
|
|
genWith = NWith <$> genExpr <*> genExpr
|
|
|
|
genAssert = NAssert <$> genExpr <*> genExpr
|
2018-05-07 07:13:30 +02:00
|
|
|
|
|
|
|
-- | Useful when there are recursive positions at each element of the list as
|
|
|
|
-- it divides the size by the length of the generated list.
|
2018-04-27 22:35:15 +02:00
|
|
|
fairList :: Gen a -> Gen [a]
|
2018-05-14 22:19:32 +02:00
|
|
|
fairList g = Gen.sized $ \s -> do
|
|
|
|
k <- Gen.int (Range.linear 0 (unSize s))
|
2018-04-27 22:35:15 +02:00
|
|
|
-- Use max here to avoid dividing by zero when there is the empty list
|
2018-05-14 22:19:32 +02:00
|
|
|
Gen.resize (Size (unSize s `div` max 1 k)) $ Gen.list (Range.singleton k) g
|
2018-05-07 07:13:30 +02:00
|
|
|
|
|
|
|
equivUpToNormalization :: NExpr -> NExpr -> Bool
|
|
|
|
equivUpToNormalization x y = normalize x == normalize y
|
|
|
|
|
|
|
|
normalize :: NExpr -> NExpr
|
|
|
|
normalize = cata $ \case
|
2019-03-17 22:47:38 +01:00
|
|
|
NConstant (NInt n) | n < 0 ->
|
|
|
|
Fix (NUnary NNeg (Fix (NConstant (NInt (negate n)))))
|
|
|
|
NConstant (NFloat n) | n < 0 ->
|
|
|
|
Fix (NUnary NNeg (Fix (NConstant (NFloat (negate n)))))
|
2018-05-07 07:13:30 +02:00
|
|
|
|
2019-03-17 22:47:38 +01:00
|
|
|
NSet binds -> Fix (NSet (map normBinding binds))
|
|
|
|
NRecSet binds -> Fix (NRecSet (map normBinding binds))
|
|
|
|
NLet binds r -> Fix (NLet (map normBinding binds) r)
|
2018-05-07 07:13:30 +02:00
|
|
|
|
2019-03-17 22:47:38 +01:00
|
|
|
NAbs params r -> Fix (NAbs (normParams params) r)
|
2018-05-07 07:13:30 +02:00
|
|
|
|
2019-03-17 22:47:38 +01:00
|
|
|
r -> Fix r
|
2018-05-07 07:13:30 +02:00
|
|
|
|
|
|
|
where
|
2019-03-17 22:47:38 +01:00
|
|
|
normBinding (NamedVar path r pos) = NamedVar (NE.map normKey path) r pos
|
|
|
|
normBinding (Inherit mr names pos) = Inherit mr (map normKey names) pos
|
2018-05-07 07:13:30 +02:00
|
|
|
|
|
|
|
normKey (DynamicKey quoted) = DynamicKey (normAntiquotedString quoted)
|
2019-03-17 22:47:38 +01:00
|
|
|
normKey (StaticKey name ) = StaticKey name
|
2018-05-07 07:13:30 +02:00
|
|
|
|
2019-03-17 22:47:38 +01:00
|
|
|
normAntiquotedString
|
|
|
|
:: Antiquoted (NString NExpr) NExpr -> Antiquoted (NString NExpr) NExpr
|
|
|
|
normAntiquotedString (Plain (DoubleQuoted [EscapedNewline])) = EscapedNewline
|
2018-05-07 07:13:30 +02:00
|
|
|
normAntiquotedString (Plain (DoubleQuoted strs)) =
|
2019-03-17 22:47:38 +01:00
|
|
|
let strs' = map normAntiquotedText strs
|
|
|
|
in if strs == strs'
|
|
|
|
then Plain (DoubleQuoted strs)
|
|
|
|
else normAntiquotedString (Plain (DoubleQuoted strs'))
|
2018-05-07 07:13:30 +02:00
|
|
|
normAntiquotedString r = r
|
|
|
|
|
|
|
|
normAntiquotedText :: Antiquoted Text NExpr -> Antiquoted Text NExpr
|
2019-03-17 22:47:38 +01:00
|
|
|
normAntiquotedText (Plain "\n" ) = EscapedNewline
|
2018-05-07 07:13:30 +02:00
|
|
|
normAntiquotedText (Plain "''\n") = EscapedNewline
|
2019-03-17 22:47:38 +01:00
|
|
|
normAntiquotedText r = r
|
2018-05-07 07:13:30 +02:00
|
|
|
|
|
|
|
normParams (ParamSet binds var (Just "")) = ParamSet binds var Nothing
|
2019-03-17 22:47:38 +01:00
|
|
|
normParams r = r
|
2018-04-27 22:35:15 +02:00
|
|
|
|
2018-05-07 07:13:30 +02:00
|
|
|
-- | Test that parse . pretty == id up to attribute position information.
|
2018-05-14 22:19:32 +02:00
|
|
|
prop_prettyparse :: Monad m => NExpr -> PropertyT m ()
|
|
|
|
prop_prettyparse p = do
|
2018-11-17 05:51:18 +01:00
|
|
|
let prog = show (prettyNix p)
|
2018-05-14 22:19:32 +02:00
|
|
|
case parse (pack prog) of
|
|
|
|
Failure s -> do
|
2019-03-17 22:47:38 +01:00
|
|
|
footnote $ show $ vsep
|
|
|
|
[fillSep ["Parse failed:", pretty (show s)], indent 2 (prettyNix p)]
|
|
|
|
discard
|
2018-05-07 07:13:30 +02:00
|
|
|
Success v
|
2019-03-17 22:47:38 +01:00
|
|
|
| equivUpToNormalization p v -> success
|
|
|
|
| otherwise -> do
|
|
|
|
let pp = normalise prog
|
|
|
|
pv = normalise (show (prettyNix v))
|
|
|
|
footnote
|
|
|
|
$ show
|
|
|
|
$ vsep
|
|
|
|
$ [ "----------------------------------------"
|
|
|
|
, vsep ["Expr before:", indent 2 (pretty (PS.ppShow p))]
|
|
|
|
, "----------------------------------------"
|
|
|
|
, vsep ["Expr after:", indent 2 (pretty (PS.ppShow v))]
|
|
|
|
, "----------------------------------------"
|
|
|
|
, vsep ["Pretty before:", indent 2 (pretty prog)]
|
|
|
|
, "----------------------------------------"
|
|
|
|
, vsep ["Pretty after:", indent 2 (prettyNix v)]
|
|
|
|
, "----------------------------------------"
|
|
|
|
, vsep ["Normalised before:", indent 2 (pretty pp)]
|
|
|
|
, "----------------------------------------"
|
|
|
|
, vsep ["Normalised after:", indent 2 (pretty pv)]
|
|
|
|
, "========================================"
|
|
|
|
, vsep ["Normalised diff:", pretty (ppDiff (diff pp pv))]
|
|
|
|
, "========================================"
|
|
|
|
]
|
|
|
|
assert (pp == pv)
|
|
|
|
where
|
|
|
|
parse = parseNixText
|
|
|
|
|
|
|
|
normalise = unlines . map (reverse . dropWhile isSpace . reverse) . lines
|
|
|
|
|
|
|
|
diff :: String -> String -> [Diff [String]]
|
|
|
|
diff s1 s2 = getDiff (map (: []) (lines s1)) (map (: []) (lines s2))
|
2018-04-27 22:35:15 +02:00
|
|
|
|
2018-05-14 22:19:32 +02:00
|
|
|
tests :: TestLimit -> TestTree
|
|
|
|
tests n = testProperty "Pretty/Parse Property" $ withTests n $ property $ do
|
2019-03-17 22:47:38 +01:00
|
|
|
x <- forAll genExpr
|
|
|
|
prop_prettyparse x
|