1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
-------------------------------------------------------------------------
--- A library containing some data types to describe makefiles.
---
--- @author Michael Hanus
--- @version December 2020
-------------------------------------------------------------------------

module MakeFile ( MakeFile, MakeElement(..), SpecialTarget(..)
                , showMakeFile ) where

import Data.List ( intercalate )

-------------------------------------------------------------------------

--- A make file consists of a list of make elements.
type MakeFile = [MakeElement]

--- A data type for the description of an element of a make file.
data MakeElement = DefineVariable String [String]
                 | Rule [SpecialTarget] String [String] [String]
                 | Comment String
                 | Empty

--- A data type to describe special targets.
data SpecialTarget = PHONY | NOTPARALLEL
  deriving Show

--- Shows a `MakeFile` as a string in standard makefile syntax.
showMakeFile :: MakeFile -> String
showMakeFile = unlines . map showMakeElement

showMakeElement :: MakeElement -> String
showMakeElement Empty = ""
showMakeElement (Comment s) = "# " ++ s
showMakeElement (DefineVariable name values) =
  name ++ " = " ++ intercalate " \\\n\t  " (unwordsUpTo 70 values)
showMakeElement (Rule specials target prereqs actions) = unlines $
  map (\s -> showSpecialTarget s ++ ": " ++ target) specials ++
  [target ++ ": " ++ intercalate " \\\n\t  " (unwordsUpTo 70 prereqs) ] ++
  map ("\t"++) actions

showSpecialTarget :: SpecialTarget -> String
showSpecialTarget st = '.' : show st

--- Fill all words into lines up to the given maximal length.
unwordsUpTo :: Int -> [String] -> [String]
unwordsUpTo max words = fillWords 0 "" words
 where
  fillWords _ s [] = [s]
  fillWords n s (w:ws) =
    let nw = n + length w
    in if nw < max || n==0 -- no line break if we are at the line begin
         then fillWords (nw+1) (s ++ (if n==0 then "" else " ") ++ w) ws
         else s : fillWords 0 "" (w:ws)