hnix/tests/ParserTests.hs
John Wiegley a3c4bbfb76 Use NonEmpty for let bindings and attribute selector paths
Note that Nix allows inherit; and inherit (foo);, so we cannot make the list
of inherited names non-empty, even though the former constructions have no
meaning.
2018-04-17 12:46:41 -07:00

389 lines
15 KiB
Haskell

{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RankNTypes #-}
{-# OPTIONS_GHC -fno-warn-name-shadowing #-}
{-# OPTIONS_GHC -Wno-missing-signatures -Wno-orphans #-}
module ParserTests (tests) where
import Data.Fix
import Data.List.NonEmpty (NonEmpty(..))
import Data.Semigroup
import Data.String.Interpolate.IsString
import Data.Text (Text, unpack, pack)
import Nix.Atoms
import Nix.Expr
import Nix.Parser
import Nix.Pretty
import Test.Tasty
import Test.Tasty.HUnit
import Test.Tasty.TH
import Text.PrettyPrint.ANSI.Leijen hiding ((<$>), (<>))
case_constant_int = assertParseText "234" $ mkInt 234
case_constant_bool = do
assertParseText "true" $ mkBool True
assertParseText "false" $ mkBool False
case_constant_bool_respects_attributes = do
assertParseText "true-foo" $ mkSym "true-foo"
assertParseText "false-bar" $ mkSym "false-bar"
case_constant_path = do
assertParseText "./." $ mkPath False "./."
assertParseText "./+-_/cdef/09ad+-" $ mkPath False "./+-_/cdef/09ad+-"
assertParseText "/abc" $ mkPath False "/abc"
assertParseText "../abc" $ mkPath False "../abc"
assertParseText "<abc>" $ mkPath True "abc"
assertParseText "<../cdef>" $ mkPath True "../cdef"
assertParseText "a//b" $ mkOper2 NUpdate (mkSym "a") (mkSym "b")
assertParseText "rec+def/cdef" $ mkPath False "rec+def/cdef"
assertParseText "a/b//c/def//<g> < def/d" $ mkOper2 NLt
(mkOper2 NUpdate (mkPath False "a/b") $ mkOper2 NUpdate
(mkPath False "c/def") (mkPath True "g"))
(mkPath False "def/d")
assertParseText "a'b/c" $ Fix $ NBinary NApp (mkSym "a'b") (mkPath False "/c")
assertParseText "a/b" $ mkPath False "a/b"
assertParseText "4/2" $ mkPath False "4/2"
assertParseFail "."
assertParseFail ".."
assertParseFail "/"
assertParseFail "a/"
assertParseFail "a/def/"
case_constant_uri = do
assertParseText "a:a" $ mkUri "a:a"
assertParseText "http://foo.bar" $ mkUri "http://foo.bar"
assertParseText "a+de+.adA+-:%%%ads%5asdk&/" $ mkUri "a+de+.adA+-:%%%ads%5asdk&/"
assertParseText "rec+def:c" $ mkUri "rec+def:c"
assertParseText "f.foo:bar" $ mkUri "f.foo:bar"
assertParseFail "http://foo${\"bar\"}"
assertParseFail ":bcdef"
assertParseFail "a%20:asda"
assertParseFail ".:adasd"
assertParseFail "+:acdcd"
case_simple_set = do
assertParseText "{ a = 23; b = 4; }" $ Fix $ NSet
[ NamedVar (mkSelector "a") $ mkInt 23
, NamedVar (mkSelector "b") $ mkInt 4
]
assertParseFail "{ a = 23 }"
case_set_inherit = do
assertParseText "{ e = 3; inherit a b; }" $ Fix $ NSet
[ NamedVar (mkSelector "e") $ mkInt 3
, Inherit Nothing $ flip StaticKey Nothing <$> ["a", "b"]
]
assertParseText "{ inherit; }" $ Fix $ NSet [ Inherit Nothing [] ]
case_set_scoped_inherit = assertParseText "{ inherit (a) b c; e = 4; inherit(a)b c; }" $ Fix $ NSet
[ Inherit (Just (mkSym "a")) $ flip StaticKey Nothing <$> ["b", "c"]
, NamedVar (mkSelector "e") $ mkInt 4
, Inherit (Just (mkSym "a")) $ flip StaticKey Nothing <$> ["b", "c"]
]
case_set_rec = assertParseText "rec { a = 3; b = a; }" $ Fix $ NRecSet
[ NamedVar (mkSelector "a") $ mkInt 3
, NamedVar (mkSelector "b") $ mkSym "a"
]
case_set_complex_keynames = do
assertParseText "{ \"\" = null; }" $ Fix $ NSet
[ NamedVar (DynamicKey (Plain "") :| []) mkNull ]
assertParseText "{ a.b = 3; a.c = 4; }" $ Fix $ NSet
[ NamedVar (StaticKey "a" Nothing :| [StaticKey "b" Nothing]) $ mkInt 3
, NamedVar (StaticKey "a" Nothing :| [StaticKey "c" Nothing]) $ mkInt 4
]
assertParseText "{ ${let a = \"b\"; in a} = 4; }" $ Fix $ NSet
[ NamedVar (DynamicKey (Antiquoted letExpr) :| []) $ mkInt 4 ]
assertParseText "{ \"a${let a = \"b\"; in a}c\".e = 4; }" $ Fix $ NSet
[ NamedVar (DynamicKey (Plain str) :| [StaticKey "e" Nothing]) $ mkInt 4 ]
where
letExpr = Fix $ NLet (NamedVar (mkSelector "a") (mkStr "b") :| []) (mkSym "a")
str = DoubleQuoted [Plain "a", Antiquoted letExpr, Plain "c"]
case_set_inherit_direct = assertParseText "{ inherit ({a = 3;}); }" $ Fix $ NSet
[ flip Inherit [] $ Just $ Fix $ NSet [NamedVar (mkSelector "a") $ mkInt 3]
]
case_inherit_selector = do
assertParseText "{ inherit \"a\"; }" $ Fix $ NSet
[Inherit Nothing [DynamicKey (Plain "a")]]
assertParseFail "{ inherit a.x; }"
case_int_list = assertParseText "[1 2 3]" $ Fix $ NList
[ mkInt i | i <- [1,2,3] ]
case_int_null_list = assertParseText "[1 2 3 null 4]" $ Fix (NList (map (Fix . NConstant) [NInt 1, NInt 2, NInt 3, NNull, NInt 4]))
case_mixed_list = do
assertParseText "[{a = 3;}.a (if true then null else false) null false 4 [] c.d or null]" $ Fix $ NList
[ Fix (NSelect (Fix (NSet [NamedVar (mkSelector "a") (mkInt 3)])) (mkSelector "a") Nothing)
, Fix (NIf (mkBool True) mkNull (mkBool False))
, mkNull, mkBool False, mkInt 4, Fix (NList [])
, Fix (NSelect (mkSym "c") (mkSelector "d") (Just mkNull))
]
assertParseFail "[if true then null else null]"
assertParseFail "[a ? b]"
assertParseFail "[a : a]"
assertParseFail "[${\"test\")]"
case_simple_lambda = assertParseText "a: a" $ Fix $ NAbs (Param "a") (mkSym "a")
case_lambda_or_uri = do
assertParseText "a :b" $ Fix $ NAbs (Param "a") (mkSym "b")
assertParseText "a c:def" $ Fix $ NBinary NApp (mkSym "a") (mkUri "c:def")
assertParseText "c:def: c" $ Fix $ NBinary NApp (mkUri "c:def:") (mkSym "c")
assertParseText "a:{}" $ Fix $ NAbs (Param "a") $ Fix $ NSet []
assertParseText "a:[a]" $ Fix $ NAbs (Param "a") $ Fix $ NList [mkSym "a"]
assertParseFail "def:"
case_lambda_pattern = do
assertParseText "{b, c ? 1}: b" $
Fix $ NAbs (fixed args Nothing) (mkSym "b")
assertParseText "{ b ? x: x }: b" $
Fix $ NAbs (fixed args2 Nothing) (mkSym "b")
assertParseText "a@{b,c ? 1}: b" $
Fix $ NAbs (fixed args (Just "a")) (mkSym "b")
assertParseText "{b,c?1}@a: c" $
Fix $ NAbs (fixed args (Just "a")) (mkSym "c")
assertParseText "{b,c?1,...}@a: c" $
Fix $ NAbs (variadic vargs (Just "a")) (mkSym "c")
assertParseText "{...}: 1" $
Fix $ NAbs (variadic mempty Nothing) (mkInt 1)
assertParseFail "a@b: a"
assertParseFail "{a}@{b}: a"
where
fixed args = ParamSet args False
variadic args = ParamSet args True
args = [("b", Nothing), ("c", Just $ mkInt 1)]
vargs = [("b", Nothing), ("c", Just $ mkInt 1)]
args2 = [("b", Just lam)]
lam = Fix $ NAbs (Param "x") (mkSym "x")
case_lambda_app_int = assertParseText "(a: a) 3" $ Fix (NBinary NApp lam int) where
int = mkInt 3
lam = Fix (NAbs (Param "a") asym)
asym = mkSym "a"
case_simple_let = do
assertParseText "let a = 4; in a" $ Fix (NLet binds $ mkSym "a")
assertParseFail "let a = 4 in a"
where
binds = (NamedVar (mkSelector "a") $ mkInt 4) :| []
case_let_body = assertParseText "let { body = 1; }" letBody
where
letBody = Fix $ NSelect aset (mkSelector "body") Nothing
aset = Fix $ NRecSet [NamedVar (mkSelector "body") (mkInt 1)]
case_nested_let = do
assertParseText "let a = 4; in let b = 5; in a" $ Fix $ NLet
(NamedVar (mkSelector "a") (mkInt 4) :| [])
(Fix $ NLet (NamedVar (mkSelector "b") (mkInt 5) :| []) $ mkSym "a")
assertParseFail "let a = 4; let b = 3; in b"
case_let_scoped_inherit = do
assertParseText "let a = null; inherit (b) c; in c" $ Fix $ NLet
(NamedVar (mkSelector "a") mkNull
:| [Inherit (Just $ mkSym "b") [StaticKey "c" Nothing]])
(mkSym "c")
assertParseFail "let inherit (b) c in c"
case_if = do
assertParseText "if true then true else false" $ Fix $ NIf (mkBool True) (mkBool True) (mkBool False)
assertParseFail "if true then false"
assertParseFail "else"
assertParseFail "if true then false else"
assertParseFail "if true then false else false else"
assertParseFail "1 + 2 then"
case_identifier_special_chars = do
assertParseText "_a" $ mkSym "_a"
assertParseText "a_b" $ mkSym "a_b"
assertParseText "a'b" $ mkSym "a'b"
assertParseText "a''b" $ mkSym "a''b"
assertParseText "a-b" $ mkSym "a-b"
assertParseText "a--b" $ mkSym "a--b"
assertParseText "a12a" $ mkSym "a12a"
assertParseFail ".a"
assertParseFail "'a"
case_identifier_keyword_prefix = do
assertParseText "true-name" $ mkSym "true-name"
assertParseText "trueName" $ mkSym "trueName"
assertParseText "null-name" $ mkSym "null-name"
assertParseText "nullName" $ mkSym "nullName"
assertParseText "[ null-name ]" $ mkList [ mkSym "null-name" ]
makeTextParseTest str = assertParseText ("\"" <> str <> "\"") $ mkStr str
case_simple_string = mapM_ makeTextParseTest ["abcdef", "a", "A", " a a ", ""]
case_string_dollar = mapM_ makeTextParseTest ["a$b", "a$$b", "$cdef", "gh$i"]
case_string_escape = do
assertParseText "\"\\$\\n\\t\\r\\\\\"" $ mkStr "$\n\t\r\\"
assertParseText "\" \\\" \\' \"" $ mkStr " \" ' "
case_string_antiquote = do
assertParseText "\"abc${ if true then \"def\" else \"abc\" } g\"" $
Fix $ NStr $ DoubleQuoted
[ Plain "abc"
, Antiquoted $ Fix $ NIf (mkBool True) (mkStr "def") (mkStr "abc")
, Plain " g"
]
assertParseText "\"\\${a}\"" $ mkStr "${a}"
assertParseFail "\"a"
assertParseFail "${true}"
assertParseFail "\"${true\""
case_select = do
assertParseText "a . e .di. f" $ Fix $ NSelect (mkSym "a")
(StaticKey "e" Nothing :| [StaticKey "di" Nothing, StaticKey "f" Nothing])
Nothing
assertParseText "a.e . d or null" $ Fix $ NSelect (mkSym "a")
(StaticKey "e" Nothing :| [StaticKey "d" Nothing])
(Just mkNull)
assertParseText "{}.\"\"or null" $ Fix $ NSelect (Fix (NSet []))
(DynamicKey (Plain "") :| []) (Just mkNull)
assertParseText "{ a = [1]; }.a or [2] ++ [3]" $ Fix $ NBinary NConcat
(Fix (NSelect
(Fix (NSet [NamedVar (StaticKey "a" Nothing :| [])
(Fix (NList [Fix (NConstant (NInt 1))]))]))
(StaticKey "a" Nothing :| [])
(Just (Fix (NList [Fix (NConstant (NInt 2))])))))
(Fix (NList [Fix (NConstant (NInt 3))]))
case_select_path = do
assertParseText "f ./." $ Fix $ NBinary NApp (mkSym "f") (mkPath False "./.")
assertParseText "f.b ../a" $ Fix $ NBinary NApp select (mkPath False "../a")
assertParseText "{}./def" $ Fix $ NBinary NApp (Fix (NSet [])) (mkPath False "./def")
assertParseText "{}.\"\"./def" $ Fix $ NBinary NApp
(Fix $ NSelect (Fix (NSet [])) (DynamicKey (Plain "") :| []) Nothing)
(mkPath False "./def")
where select = Fix $ NSelect (mkSym "f") (mkSelector "b") Nothing
case_fun_app = do
assertParseText "f a b" $ Fix $ NBinary NApp (Fix $ NBinary NApp (mkSym "f") (mkSym "a")) (mkSym "b")
assertParseText "f a.x or null" $ Fix $ NBinary NApp (mkSym "f") $ Fix $
NSelect (mkSym "a") (mkSelector "x") (Just mkNull)
assertParseFail "f if true then null else null"
case_indented_string = do
assertParseText "''a''" $ mkIndentedStr 0 "a"
assertParseText "''\n foo\n bar''" $ mkIndentedStr 2 "foo\nbar"
assertParseText "'' ''" $ mkIndentedStr 0 ""
assertParseText "'''''''" $ mkIndentedStr 0 "''"
assertParseText "'' ${null}\n a${null}''" $ Fix $ NStr $ Indented 3
[ Antiquoted mkNull
, Plain "\na"
, Antiquoted mkNull
]
assertParseFail "'''''"
assertParseFail "'' '"
case_indented_string_escape = assertParseText
"'' ''\\n ''\\t ''\\\\ ''${ \\ \\n ' ''' ''" $
mkIndentedStr 1 "\n \t \\ ${ \\ \\n ' '' "
case_operator_fun_app = do
assertParseText "a ++ b" $ mkOper2 NConcat (mkSym "a") (mkSym "b")
assertParseText "a ++ f b" $ mkOper2 NConcat (mkSym "a") $ Fix $ NBinary NApp
(mkSym "f") (mkSym "b")
case_operators = do
assertParseText "1 + 2 - 3" $ mkOper2 NMinus
(mkOper2 NPlus (mkInt 1) (mkInt 2)) (mkInt 3)
assertParseFail "1 + if true then 1 else 2"
assertParseText "1 + (if true then 2 else 3)" $ mkOper2 NPlus (mkInt 1) $ Fix $ NIf
(mkBool True) (mkInt 2) (mkInt 3)
assertParseText "{ a = 3; } // rec { b = 4; }" $ mkOper2 NUpdate
(Fix $ NSet [NamedVar (mkSelector "a") (mkInt 3)])
(Fix $ NRecSet [NamedVar (mkSelector "b") (mkInt 4)])
assertParseText "--a" $ mkOper NNeg $ mkOper NNeg $ mkSym "a"
assertParseText "a - b - c" $ mkOper2 NMinus
(mkOper2 NMinus (mkSym "a") (mkSym "b")) $
mkSym "c"
assertParseText "foo<bar" $ mkOper2 NLt (mkSym "foo") (mkSym "bar")
assertParseFail "+ 3"
assertParseFail "foo +"
case_comments = do
Success expected <- parseNixFile "data/let.nix"
assertParseFile "let-comments-multiline.nix" expected
assertParseFile "let-comments.nix" expected
case_select_or_precedence =
assertParsePrint [i|let
matchDef = def: matcher:
v: let
case = builtins.head (builtins.attrNames v);
in (matcher.case or def case) (v.case);
in null|] [i|let
matchDef = def: matcher:
v: let
case = builtins.head (builtins.attrNames v);
in (matcher.case or def) case (v.case);
in null|]
case_select_or_precedence2 =
assertParsePrint [i|let
matchDef = def: matcher:
v: let
case = builtins.head (builtins.attrNames v);
in (matcher.case or null.foo) (v.case);
in null|] [i|let
matchDef = def: matcher:
v: let
case = builtins.head (builtins.attrNames v);
in (matcher.case or null).foo (v.case);
in null|]
tests :: TestTree
tests = $testGroupGenerator
--------------------------------------------------------------------------------
assertParseText :: Text -> NExpr -> Assertion
assertParseText str expected = case parseNixText str of
Success actual ->
assertEqual ("When parsing " ++ unpack str)
(stripPositionInfo expected) (stripPositionInfo actual)
Failure err ->
assertFailure $ "Unexpected error parsing `" ++ unpack str ++ "':\n" ++ show err
assertParseFile :: FilePath -> NExpr -> Assertion
assertParseFile file expected = do
res <- parseNixFile $ "data/" ++ file
case res of
Success actual -> assertEqual ("Parsing data file " ++ file)
(stripPositionInfo expected) (stripPositionInfo actual)
Failure err ->
assertFailure $ "Unexpected error parsing data file `"
++ file ++ "':\n" ++ show err
assertParseFail :: Text -> Assertion
assertParseFail str = case parseNixText str of
Failure _ -> return ()
Success r ->
assertFailure $ "Unexpected success parsing `"
++ unpack str ++ ":\nParsed value: " ++ show r
-- assertRoundTrip :: Text -> Assertion
-- assertRoundTrip src = assertParsePrint src src
assertParsePrint :: Text -> Text -> Assertion
assertParsePrint src expect =
let Success expr = parseNixTextLoc src
result = displayS
. renderPretty 0.4 80
. prettyNix
. stripAnnotation
$ expr
in assertEqual "" expect (pack (result ""))