Print.hs 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222
  1. {-# LANGUAGE RecordWildCards #-}
  2. {-# LANGUAGE OverloadedStrings #-}
  3. {-# LANGUAGE ScopedTypeVariables #-}
  4. module Data.SCargot.Print
  5. ( -- * Pretty-Printing
  6. encodeOne
  7. , encode
  8. -- * Pretty-Printing Control
  9. , SExprPrinter
  10. , Indent(..)
  11. , setFromCarrier
  12. , setMaxWidth
  13. , removeMaxWidth
  14. , setIndentAmount
  15. , setIndentStrategy
  16. -- * Default Printing Strategies
  17. , basicPrint
  18. , flatPrint
  19. ) where
  20. import Data.Monoid ((<>))
  21. import Data.Text (Text)
  22. import qualified Data.Text as T
  23. import Data.SCargot.Repr
  24. -- | The 'Indent' type is used to determine how to indent subsequent
  25. -- s-expressions in a list, after printing the head of the list.
  26. data Indent
  27. = Swing -- ^ A 'Swing' indent will indent subsequent exprs some fixed
  28. -- amount more than the current line.
  29. --
  30. -- > (foo
  31. -- > bar
  32. -- > baz
  33. -- > quux)
  34. | SwingAfter Int -- ^ A 'SwingAfter' @n@ indent will try to print the
  35. -- first @n@ expressions after the head on the same
  36. -- line as the head, and all after will be swung.
  37. -- 'SwingAfter' @0@ is equivalent to 'Swing'.
  38. --
  39. -- > (foo bar
  40. -- > baz
  41. -- > quux)
  42. | Align -- ^ An 'Align' indent will print the first expression after
  43. -- the head on the same line, and subsequent expressions will
  44. -- be aligned with that one.
  45. --
  46. -- > (foo bar
  47. -- > baz
  48. -- > quux)
  49. deriving (Eq, Show)
  50. -- | A 'SExprPrinter' value describes how to print a given value as an
  51. -- s-expression. The @carrier@ type parameter indicates the value
  52. -- that will be printed, and the @atom@ parameter indicates the type
  53. -- that will represent tokens in an s-expression structure.
  54. data SExprPrinter atom carrier = SExprPrinter
  55. { atomPrinter :: atom -> Text
  56. -- ^ How to serialize a given atom to 'Text'.
  57. , fromCarrier :: carrier -> SExpr atom
  58. -- ^ How to turn a carrier type back into a 'Sexpr'.
  59. , swingIndent :: SExpr atom -> Indent
  60. -- ^ How to indent subsequent expressions, as determined by
  61. -- the head of the list.
  62. , indentAmount :: Int
  63. -- ^ How much to indent after a swung indentation.
  64. , maxWidth :: Maybe Int
  65. -- ^ The maximum width (if any) If this is 'None' then
  66. -- the resulting s-expression will always be printed
  67. -- on a single line.
  68. }
  69. -- | A default 'LayoutOptions' struct that will always print a 'SExpr'
  70. -- as a single line.
  71. flatPrint :: (atom -> Text) -> SExprPrinter atom (SExpr atom)
  72. flatPrint printer = SExprPrinter
  73. { atomPrinter = printer
  74. , fromCarrier = id
  75. , swingIndent = const Swing
  76. , indentAmount = 2
  77. , maxWidth = Nothing
  78. }
  79. -- | A default 'LayoutOptions' struct that will always swing subsequent
  80. -- expressions onto later lines if they're too long, indenting them
  81. -- by two spaces.
  82. basicPrint :: (atom -> Text) -> SExprPrinter atom (SExpr atom)
  83. basicPrint printer = SExprPrinter
  84. { atomPrinter = printer
  85. , fromCarrier = id
  86. , swingIndent = const Swing
  87. , indentAmount = 2
  88. , maxWidth = Just 80
  89. }
  90. -- | Modify the carrier type of a 'SExprPrinter' by describing how
  91. -- to convert the new type back to the previous type. For example,
  92. -- to pretty-print a well-formed s-expression, we can modify the
  93. -- 'SExprPrinter' value as follows:
  94. --
  95. -- >>> let printer = setFromCarrier fromWellFormed (basicPrint id)
  96. -- >>> encodeOne printer (WFSList [WFSAtom "ele", WFSAtom "phant"])
  97. -- "(ele phant)"
  98. setFromCarrier :: (c -> b) -> SExprPrinter a b -> SExprPrinter a c
  99. setFromCarrier fc pr = pr { fromCarrier = fromCarrier pr . fc }
  100. -- | Dictate a maximum width for pretty-printed s-expressions.
  101. --
  102. -- >>> let printer = setMaxWidth 8 (basicPrint id)
  103. -- >>> encodeOne printer (L [A "one", A "two", A "three"])
  104. -- "(one \n two\n three)"
  105. setMaxWidth :: Int -> SExprPrinter atom carrier -> SExprPrinter atom carrier
  106. setMaxWidth n pr = pr { maxWidth = Just n }
  107. -- | Allow the serialized s-expression to be arbitrarily wide. This
  108. -- makes all pretty-printing happen on a single line.
  109. --
  110. -- >>> let printer = removeMaxWidth (basicPrint id)
  111. -- >>> encodeOne printer (L [A "one", A "two", A "three"])
  112. -- "(one two three)"
  113. removeMaxWidth :: SExprPrinter atom carrier -> SExprPrinter atom carrier
  114. removeMaxWidth pr = pr { maxWidth = Nothing }
  115. -- | Set the number of spaces that a subsequent line will be indented
  116. -- after a swing indentation.
  117. --
  118. -- >>> let printer = setMaxWidth 12 (basicPrint id)
  119. -- >>> encodeOne printer (L [A "elephant", A "pachyderm"])
  120. -- "(elephant \n pachyderm)"
  121. -- >>> encodeOne (setIndentAmount 4) (L [A "elephant", A "pachyderm"])
  122. -- "(elephant \n pachyderm)"
  123. setIndentAmount :: Int -> SExprPrinter atom carrier -> SExprPrinter atom carrier
  124. setIndentAmount n pr = pr { indentAmount = n }
  125. -- | Dictate how to indent subsequent lines based on the leading
  126. -- subexpression in an s-expression. For details on how this works,
  127. -- consult the documentation of the 'Indent' type.
  128. --
  129. -- >>> let indent (A "def") = SwingAfter 1; indent _ = Swing
  130. -- >>> let printer = setIndentStrategy indent (setMaxWidth 8 (basicPrint id))
  131. -- >>> encodeOne printer (L [ A "def", L [ A "func", A "arg" ], A "body" ])
  132. -- "(def (func arg)\n body)"
  133. -- >>> encodeOne printer (L [ A "elephant", A "among", A "pachyderms" ])
  134. -- "(elephant \n among\n pachyderms)"
  135. setIndentStrategy :: (SExpr atom -> Indent) -> SExprPrinter atom carrier -> SExprPrinter atom carrier
  136. setIndentStrategy st pr = pr { swingIndent = st }
  137. -- Sort of like 'unlines' but without the trailing newline
  138. joinLines :: [Text] -> Text
  139. joinLines = T.intercalate "\n"
  140. -- Indents a line by n spaces
  141. indent :: Int -> Text -> Text
  142. indent n ts = T.replicate n " " <> ts
  143. -- Indents every line n spaces, and adds a newline to the beginning
  144. -- used in swung indents
  145. indentAll :: Int -> [Text] -> Text
  146. indentAll n = ("\n" <>) . joinLines . map (indent n)
  147. -- Indents every line but the first by some amount
  148. -- used in aligned indents
  149. indentSubsequent :: Int -> [Text] -> Text
  150. indentSubsequent _ [] = ""
  151. indentSubsequent _ [t] = t
  152. indentSubsequent n (t:ts) = joinLines (t : go ts)
  153. where go = map (indent n)
  154. -- oh god this code is so disgusting
  155. -- i'm sorry to everyone i let down by writing this
  156. -- i swear i'll do better in the future i promise i have to
  157. -- for my sake and for everyone's
  158. -- | Pretty-print a 'SExpr' according to the options in a
  159. -- 'LayoutOptions' value.
  160. prettyPrintSExpr :: SExprPrinter a (SExpr a) -> SExpr a -> Text
  161. prettyPrintSExpr SExprPrinter { .. } = pHead 0
  162. where pHead _ SNil = "()"
  163. pHead _ (SAtom a) = atomPrinter a
  164. pHead ind (SCons x xs) = gather ind x xs id
  165. gather ind h (SCons x xs) k = gather ind h xs (k . (x:))
  166. gather ind h end k = "(" <> hd <> body <> tl <> ")"
  167. where tl = case end of
  168. SNil -> ""
  169. SAtom a -> " . " <> atomPrinter a
  170. SCons _ _ -> error "[unreachable]"
  171. hd = indentSubsequent ind [pHead (ind+1) h]
  172. lst = k []
  173. flat = T.unwords (map (pHead (ind+1)) lst)
  174. headWidth = T.length hd + 1
  175. indented =
  176. case swingIndent h of
  177. SwingAfter n ->
  178. let (l, ls) = splitAt n lst
  179. t = T.unwords (map (pHead (ind+1)) l)
  180. ts = indentAll (ind + indentAmount)
  181. (map (pHead (ind + indentAmount)) ls)
  182. in t <> ts
  183. Swing ->
  184. indentAll (ind + indentAmount)
  185. (map (pHead (ind + indentAmount)) lst)
  186. Align ->
  187. indentSubsequent (ind + headWidth + 1)
  188. (map (pHead (ind + headWidth + 1)) lst)
  189. body
  190. | length lst == 0 = ""
  191. | Just maxAmt <- maxWidth
  192. , T.length flat + ind > maxAmt = " " <> indented
  193. | otherwise = " " <> flat
  194. -- | Turn a single s-expression into a string according to a given
  195. -- 'SExprPrinter'.
  196. encodeOne :: SExprPrinter atom carrier -> carrier -> Text
  197. encodeOne s@(SExprPrinter { .. }) =
  198. prettyPrintSExpr (s { fromCarrier = id }) . fromCarrier
  199. -- | Turn a list of s-expressions into a single string according to
  200. -- a given 'SExprPrinter'.
  201. encode :: SExprPrinter atom carrier -> [carrier] -> Text
  202. encode spec = T.intercalate "\n\n" . map (encodeOne spec)