1module Parser (mkParser) where23import Data.Either (isLeft)4import qualified Mach.Parser as P5import qualified Mach.Types as T6import Test.Tasty7import Test.Tasty.HUnit8import qualified Text.ParserCombinators.Parsec as Parsec910assignTests :: TestTree11assignTests =12 testGroup13 "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 rvalue49 ]50 where51 parse :: String -> Either Parsec.ParseError T.Assign52 parse = Parsec.parse P.assign ""5354 parseErr :: String -> Bool55 parseErr = isLeft . parse5657 assign :: String -> T.Flavor -> T.Token -> Either Parsec.ParseError T.Assign58 assign n f t = Right $ T.Assign (T.Seq [T.Lit n]) f t5960ruleTests :: TestTree61ruleTests =62 testGroup63 "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 where80 parse :: String -> Either Parsec.ParseError T.TgtRule81 parse = Parsec.parse P.targetRule ""8283 rule :: [T.Token] -> [T.Token] -> [T.Token] -> Either Parsec.ParseError T.TgtRule84 rule t p c = Right $ T.TgtRule t p c8586includeTests :: TestTree87includeTests =88 testGroup89 "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 where100 parse :: String -> Either Parsec.ParseError [T.Token]101 parse = Parsec.parse P.include ""102103 mkPaths :: [T.Token] -> Either Parsec.ParseError [T.Token]104 mkPaths = Right105106mkTests :: TestTree107mkTests =108 testGroup109 "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 where121 parse :: String -> Either Parsec.ParseError [T.MkStat]122 parse = Parsec.parse P.mkFile ""123124mkParser :: TestTree125mkParser =126 testGroup127 "Tests for the Makefile parser"128 [assignTests, ruleTests, includeTests, mkTests]