mach

A work-in-progress implementation of make(1)

git clone https://git.8pit.net/mach.git

  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]