Print.hs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290
  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 qualified Data.Text.Lazy as TL
  24. import qualified Data.Text.Lazy.Builder as B
  25. import Data.SCargot.Repr
  26. -- | The 'Indent' type is used to determine how to indent subsequent
  27. -- s-expressions in a list, after printing the head of the list.
  28. data Indent
  29. = Swing -- ^ A 'Swing' indent will indent subsequent exprs some fixed
  30. -- amount more than the current line.
  31. --
  32. -- > (foo
  33. -- > bar
  34. -- > baz
  35. -- > quux)
  36. | SwingAfter Int -- ^ A 'SwingAfter' @n@ indent will try to print the
  37. -- first @n@ expressions after the head on the same
  38. -- line as the head, and all after will be swung.
  39. -- 'SwingAfter' @0@ is equivalent to 'Swing'.
  40. --
  41. -- > (foo bar
  42. -- > baz
  43. -- > quux)
  44. | Align -- ^ An 'Align' indent will print the first expression after
  45. -- the head on the same line, and subsequent expressions will
  46. -- be aligned with that one.
  47. --
  48. -- > (foo bar
  49. -- > baz
  50. -- > quux)
  51. deriving (Eq, Show)
  52. -- | A 'SExprPrinter' value describes how to print a given value as an
  53. -- s-expression. The @carrier@ type parameter indicates the value
  54. -- that will be printed, and the @atom@ parameter indicates the type
  55. -- that will represent tokens in an s-expression structure.
  56. data SExprPrinter atom carrier = SExprPrinter
  57. { atomPrinter :: atom -> Text
  58. -- ^ How to serialize a given atom to 'Text'.
  59. , fromCarrier :: carrier -> SExpr atom
  60. -- ^ How to turn a carrier type back into a 'Sexpr'.
  61. , swingIndent :: SExpr atom -> Indent
  62. -- ^ How to indent subsequent expressions, as determined by
  63. -- the head of the list.
  64. , indentAmount :: Int
  65. -- ^ How much to indent after a swung indentation.
  66. , maxWidth :: Maybe Int
  67. -- ^ The maximum width (if any) If this is 'None' then
  68. -- the resulting s-expression will always be printed
  69. -- on a single line.
  70. }
  71. -- | A default 'LayoutOptions' struct that will always print a 'SExpr'
  72. -- as a single line.
  73. flatPrint :: (atom -> Text) -> SExprPrinter atom (SExpr atom)
  74. flatPrint printer = SExprPrinter
  75. { atomPrinter = printer
  76. , fromCarrier = id
  77. , swingIndent = const Swing
  78. , indentAmount = 2
  79. , maxWidth = Nothing
  80. }
  81. -- | A default 'LayoutOptions' struct that will always swing subsequent
  82. -- expressions onto later lines if they're too long, indenting them
  83. -- by two spaces.
  84. basicPrint :: (atom -> Text) -> SExprPrinter atom (SExpr atom)
  85. basicPrint printer = SExprPrinter
  86. { atomPrinter = printer
  87. , fromCarrier = id
  88. , swingIndent = const Swing
  89. , indentAmount = 2
  90. , maxWidth = Just 80
  91. }
  92. -- | Modify the carrier type of a 'SExprPrinter' by describing how
  93. -- to convert the new type back to the previous type. For example,
  94. -- to pretty-print a well-formed s-expression, we can modify the
  95. -- 'SExprPrinter' value as follows:
  96. --
  97. -- >>> let printer = setFromCarrier fromWellFormed (basicPrint id)
  98. -- >>> encodeOne printer (WFSList [WFSAtom "ele", WFSAtom "phant"])
  99. -- "(ele phant)"
  100. setFromCarrier :: (c -> b) -> SExprPrinter a b -> SExprPrinter a c
  101. setFromCarrier fc pr = pr { fromCarrier = fromCarrier pr . fc }
  102. -- | Dictate a maximum width for pretty-printed s-expressions.
  103. --
  104. -- >>> let printer = setMaxWidth 8 (basicPrint id)
  105. -- >>> encodeOne printer (L [A "one", A "two", A "three"])
  106. -- "(one \n two\n three)"
  107. setMaxWidth :: Int -> SExprPrinter atom carrier -> SExprPrinter atom carrier
  108. setMaxWidth n pr = pr { maxWidth = Just n }
  109. -- | Allow the serialized s-expression to be arbitrarily wide. This
  110. -- makes all pretty-printing happen on a single line.
  111. --
  112. -- >>> let printer = removeMaxWidth (basicPrint id)
  113. -- >>> encodeOne printer (L [A "one", A "two", A "three"])
  114. -- "(one two three)"
  115. removeMaxWidth :: SExprPrinter atom carrier -> SExprPrinter atom carrier
  116. removeMaxWidth pr = pr { maxWidth = Nothing }
  117. -- | Set the number of spaces that a subsequent line will be indented
  118. -- after a swing indentation.
  119. --
  120. -- >>> let printer = setMaxWidth 12 (basicPrint id)
  121. -- >>> encodeOne printer (L [A "elephant", A "pachyderm"])
  122. -- "(elephant \n pachyderm)"
  123. -- >>> encodeOne (setIndentAmount 4) (L [A "elephant", A "pachyderm"])
  124. -- "(elephant \n pachyderm)"
  125. setIndentAmount :: Int -> SExprPrinter atom carrier -> SExprPrinter atom carrier
  126. setIndentAmount n pr = pr { indentAmount = n }
  127. -- | Dictate how to indent subsequent lines based on the leading
  128. -- subexpression in an s-expression. For details on how this works,
  129. -- consult the documentation of the 'Indent' type.
  130. --
  131. -- >>> let indent (A "def") = SwingAfter 1; indent _ = Swing
  132. -- >>> let printer = setIndentStrategy indent (setMaxWidth 8 (basicPrint id))
  133. -- >>> encodeOne printer (L [ A "def", L [ A "func", A "arg" ], A "body" ])
  134. -- "(def (func arg)\n body)"
  135. -- >>> encodeOne printer (L [ A "elephant", A "among", A "pachyderms" ])
  136. -- "(elephant \n among\n pachyderms)"
  137. setIndentStrategy :: (SExpr atom -> Indent) -> SExprPrinter atom carrier -> SExprPrinter atom carrier
  138. setIndentStrategy st pr = pr { swingIndent = st }
  139. -- Sort of like 'unlines' but without the trailing newline
  140. joinLines :: [Text] -> Text
  141. joinLines = T.intercalate "\n"
  142. -- Indents a line by n spaces
  143. indent :: Int -> Text -> Text
  144. indent n ts = T.replicate n " " <> ts
  145. -- Indents every line n spaces, and adds a newline to the beginning
  146. -- used in swung indents
  147. indentAll :: Int -> [Text] -> Text
  148. indentAll n = ("\n" <>) . joinLines . map (indent n)
  149. -- Indents every line but the first by some amount
  150. -- used in aligned indents
  151. indentSubsequent :: Int -> [Text] -> Text
  152. indentSubsequent _ [] = ""
  153. indentSubsequent _ [t] = t
  154. indentSubsequent n (t:ts) = joinLines (t : go ts)
  155. where go = map (indent n)
  156. -- oh god this code is so disgusting
  157. -- i'm sorry to everyone i let down by writing this
  158. -- i swear i'll do better in the future i promise i have to
  159. -- for my sake and for everyone's
  160. -- | Pretty-print a 'SExpr' according to the options in a
  161. -- 'LayoutOptions' value.
  162. prettyPrintSExpr :: SExprPrinter a (SExpr a) -> SExpr a -> Text
  163. prettyPrintSExpr pr@SExprPrinter { .. } expr = case maxWidth of
  164. Nothing -> flatPrintSExpr (fmap atomPrinter (fromCarrier expr))
  165. Just _ -> indentPrintSExpr pr expr
  166. indentPrintSExpr :: SExprPrinter a (SExpr a) -> SExpr a -> Text
  167. indentPrintSExpr SExprPrinter { .. } = pHead 0
  168. where
  169. pHead _ SNil = "()"
  170. pHead _ (SAtom a) = atomPrinter a
  171. pHead ind (SCons x xs) = gather ind x xs id
  172. gather ind h (SCons x xs) k = gather ind h xs (k . (x:))
  173. gather ind h end k = "(" <> hd <> body <> tl <> ")"
  174. where tl = case end of
  175. SNil -> ""
  176. SAtom a -> " . " <> atomPrinter a
  177. SCons _ _ -> error "[unreachable]"
  178. hd = indentSubsequent ind [pHead (ind+1) h]
  179. lst = k []
  180. flat = T.unwords (map (pHead (ind+1)) lst)
  181. headWidth = T.length hd + 1
  182. indented =
  183. case swingIndent h of
  184. SwingAfter n ->
  185. let (l, ls) = splitAt n lst
  186. t = T.unwords (map (pHead (ind+1)) l)
  187. ts = indentAll (ind + indentAmount)
  188. (map (pHead (ind + indentAmount)) ls)
  189. in t <> ts
  190. Swing ->
  191. indentAll (ind + indentAmount)
  192. (map (pHead (ind + indentAmount)) lst)
  193. Align ->
  194. indentSubsequent (ind + headWidth + 1)
  195. (map (pHead (ind + headWidth + 1)) lst)
  196. body
  197. | length lst == 0 = ""
  198. | Just maxAmt <- maxWidth
  199. , T.length flat + ind > maxAmt = " " <> indented
  200. | otherwise = " " <> flat
  201. -- where
  202. -- -- this is the base-case that knows how to print empty lists and
  203. -- -- atoms
  204. -- pHead _ SNil = B.fromString "()"
  205. -- pHead _ (SAtom a) = B.fromText a
  206. -- pHead ind (SCons x xs) = gather ind x xs id 0
  207. -- -- otherwise, we trawl through the list grabbing every element...
  208. -- gather ind h (SCons x xs) k r = gather ind h xs (k . (x:)) (r + T.length x)
  209. -- gather ind h end k r = B.fromString "(" <> hd <> body <> tl <> B.fromString ")"
  210. -- where
  211. -- tl = case end of
  212. -- SNil -> mempty
  213. -- SAtom a -> B.fromString " . " <> B.fromText a
  214. -- SCons _ _ -> error "[unreachable]"
  215. -- hd = indentSubsequent ind [pHead (ind+1) h]
  216. -- lst = k []
  217. -- flat = T.unwords (map (pHead (ind+1)) lst)
  218. -- headWidth = T.length hd + 1
  219. -- indented =
  220. -- case swingIndent h of
  221. -- SwingAfter n ->
  222. -- let (l, ls) = splitAt n lst
  223. -- t = T.unwords (map (pHead (ind+1)) l)
  224. -- ts = indentAll (ind + indentAmount)
  225. -- (map (pHead (ind + indentAmount)) ls)
  226. -- in t <> ts
  227. -- Swing ->
  228. -- indentAll (ind + indentAmount)
  229. -- (map (pHead (ind + indentAmount)) lst)
  230. -- Align ->
  231. -- indentSubsequent (ind + headWidth + 1)
  232. -- (map (pHead (ind + headWidth + 1)) lst)
  233. -- body
  234. -- | length lst == 0 = B.fromString ""
  235. -- | Just maxAmt <- maxWidth
  236. -- , T.length flat + ind > maxAmt = B.fromString " " <> indented
  237. -- | otherwise = B.fromString " " <> flat
  238. -- if we don't indent anything, then we can ignore a bunch of the
  239. -- details above
  240. flatPrintSExpr :: SExpr Text -> Text
  241. flatPrintSExpr = TL.toStrict . B.toLazyText . pHead
  242. where
  243. pHead (SCons x xs) =
  244. B.fromString "(" <> pHead x <> pTail xs
  245. pHead (SAtom t) =
  246. B.fromText t
  247. pHead SNil =
  248. B.fromString "()"
  249. pTail e@(SCons _ (SAtom _)) =
  250. B.fromString " " <> pHead e <> B.fromString ")"
  251. pTail (SCons x xs) =
  252. B.fromString " " <> pHead x <> pTail xs
  253. pTail (SAtom t) =
  254. B.fromString " . " <> B.fromText t <> B.fromString ")"
  255. pTail SNil =
  256. B.fromString ")"
  257. -- | Turn a single s-expression into a string according to a given
  258. -- 'SExprPrinter'.
  259. encodeOne :: SExprPrinter atom carrier -> carrier -> Text
  260. encodeOne s@(SExprPrinter { .. }) =
  261. prettyPrintSExpr (s { fromCarrier = id }) . fromCarrier
  262. -- | Turn a list of s-expressions into a single string according to
  263. -- a given 'SExprPrinter'.
  264. encode :: SExprPrinter atom carrier -> [carrier] -> Text
  265. encode spec = T.intercalate "\n\n" . map (encodeOne spec)