1module Parser (mkParser) where
2
3import Data.Either (isLeft)
4import qualified Mach.Parser as P
5import qualified Mach.Types as T
6import Test.Tasty
7import Test.Tasty.HUnit
8import qualified Text.ParserCombinators.Parsec as Parsec
9
10assignTests :: TestTree
11assignTests =
12 testGroup
13 "assignments"
14 [ testCase "Simple assignment of macro to string" $
15 let rvalue = T.Seq [T.Lit "bar"]
16 in parse "foo = bar" @?= assign "foo" T.Delayed rvalue,
17 testCase "Assignment with multiple blanks" $
18 let rvalue = T.Seq [T.Lit "bar"]
19 in parse "foo = bar" @?= assign "foo" T.Delayed rvalue,
20 testCase "Assignment with tab character" $
21 assertBool "can only use blanks" $
22 parseErr "foo \t= \tbar",
23 testCase "Assignment without blanks" $
24 assertBool "missing blanks" $
25 parseErr "foo=bar",
26 testCase "Simple macro expansion" $
27 let rvalue = T.Seq [T.Exp $ T.Lit "BAR"]
28 in parse "m_exp = ${BAR}" @?= assign "m_exp" T.Delayed rvalue,
29 testCase "Nested macro expansion" $
30 let rvalue = T.Seq [T.Exp $ T.Exp (T.Lit "FOO_BAR")]
31 in parse "nested ::= ${${FOO_BAR}}" @?= assign "nested" T.Immediate rvalue,
32 testCase "Multi-token assignment" $
33 let rvalue = T.Seq [T.Lit "a", T.Exp (T.Lit "b"), T.Lit "c"]
34 in parse "_ ?= a${b}c" @?= assign "_" T.Cond rvalue,
35 testCase "Assignment with escaped dollar" $
36 let rvalue = T.Seq [T.Lit "$", T.Lit "foo", T.Lit "$", T.Lit "bar"]
37 in parse "a = $$foo$$bar" @?= assign "a" T.Delayed rvalue,
38 testCase "Assignment with escaped newline" $
39 let rvalue = T.Seq [T.Lit "foo", T.Lit " ", T.Lit "bar"]
40 in parse "a = foo\\\nbar" @?= assign "a" T.Delayed rvalue,
41 testCase "Invalid macro expansion" $
42 assertBool "closing brackets are not valid macro names" $
43 parseErr "foo = ${}}",
44 testCase "Macro expansion with substitution" $
45 let rvalue = T.Seq [T.ExpSub (T.Exp $ T.Lit "string1") "subst1" "subst2"]
46 in parse "a = ${string1:subst1=subst2}" @?= assign "a" T.Delayed rvalue,
47 testCase "Single character macro expansion" $
48 let rvalue = T.Seq [T.Exp (T.Lit "<")] in parse "_ ?= $<" @?= assign "_" T.Cond rvalue
49 ]
50 where
51 parse :: String -> Either Parsec.ParseError T.Assign
52 parse = Parsec.parse P.assign ""
53
54 parseErr :: String -> Bool
55 parseErr = isLeft . parse
56
57 assign :: String -> T.Flavor -> T.Token -> Either Parsec.ParseError T.Assign
58 assign n f t = Right $ T.Assign (T.Seq [T.Lit n]) f t
59
60ruleTests :: TestTree
61ruleTests =
62 testGroup
63 "target rules"
64 [ testCase "Single target, single prerequisite, single command" $
65 parse "foo: bar\n\tbaz\n" @?= rule [T.Lit "foo"] [T.Lit "bar"] [T.Seq [T.Lit "baz"]],
66 testCase "No prerequisite" $
67 parse "foo:\n\tbar\n" @?= rule [T.Lit "foo"] [] [T.Seq [T.Lit "bar"]],
68 testCase "No prerequisite, no commands" $
69 parse "foo:\n" @?= rule [T.Lit "foo"] [] [],
70 testCase "Multiple targets" $
71 parse "foo bar baz:\n\tcommand\n" @?= rule [T.Lit "foo", T.Lit "bar", T.Lit "baz"] [] [T.Seq [T.Lit "command"]],
72 testCase "Command on same line" $
73 parse "foo: bar baz;echo foo\n\techo bar\n" @?= rule [T.Lit "foo"] [T.Lit "bar", T.Lit "baz"] [T.Seq [T.Lit "echo foo"], T.Seq [T.Lit "echo bar"]],
74 testCase "Target with macro expansion" $
75 parse "$(MAIN):\n" @?= rule [T.Exp (T.Lit "MAIN")] [] [],
76 testCase "Command with macro expansion" $
77 parse "foo:\n\techo ${BAR}\n" @?= rule [T.Lit "foo"] [] [T.Seq [T.Lit "echo ", T.Exp (T.Lit "BAR")]]
78 ]
79 where
80 parse :: String -> Either Parsec.ParseError T.TgtRule
81 parse = Parsec.parse P.targetRule ""
82
83 rule :: [T.Token] -> [T.Token] -> [T.Token] -> Either Parsec.ParseError T.TgtRule
84 rule t p c = Right $ T.TgtRule t p c
85
86includeTests :: TestTree
87includeTests =
88 testGroup
89 "include lines"
90 [ testCase "single file path" $
91 parse "include foo" @?= mkPaths [T.Lit "foo"],
92 testCase "multiple paths" $
93 parse "include foo bar baz" @?= mkPaths [T.Lit "foo", T.Lit "bar", T.Lit "baz"],
94 testCase "path with macro expansion" $
95 parse "include ${FILE}" @?= mkPaths [T.Exp (T.Lit "FILE")],
96 testCase "include prefixed with minus" $
97 parse "-include foo" @?= mkPaths [T.Lit "foo"]
98 ]
99 where
100 parse :: String -> Either Parsec.ParseError [T.Token]
101 parse = Parsec.parse P.include ""
102
103 mkPaths :: [T.Token] -> Either Parsec.ParseError [T.Token]
104 mkPaths = Right
105
106mkTests :: TestTree
107mkTests =
108 testGroup
109 "makefile parser"
110 [ testCase "assignment" $
111 let assign = T.Assign (T.Seq [T.Lit "foo"]) T.Delayed (T.Seq [T.Lit "bar"])
112 in parse "foo = bar\n" @?= Right [T.MkAssign assign],
113 testCase "rule" $
114 let rule = T.TgtRule [T.Lit "foo"] [] [T.Seq [T.Lit "bar"]]
115 in parse "foo:\n\tbar\n" @?= Right [T.MkTgtRule rule],
116 testCase "include" $
117 let paths = [T.Lit "foo", T.Lit "bar"]
118 in parse "include foo bar\n" @?= Right [T.MkInclude paths]
119 ]
120 where
121 parse :: String -> Either Parsec.ParseError [T.MkStat]
122 parse = Parsec.parse P.mkFile ""
123
124mkParser :: TestTree
125mkParser =
126 testGroup
127 "Tests for the Makefile parser"
128 [assignTests, ruleTests, includeTests, mkTests]