Print.hs 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443
  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. , unboundIndentPrint
  20. ) where
  21. import qualified Data.Foldable as F
  22. import Data.Monoid ((<>))
  23. import qualified Data.Sequence as Seq
  24. import Data.Text (Text)
  25. import qualified Data.Text as T
  26. import qualified Data.Text.Lazy as TL
  27. import qualified Data.Text.Lazy.Builder as B
  28. import qualified Data.Traversable as T
  29. import Data.SCargot.Repr
  30. -- | The 'Indent' type is used to determine how to indent subsequent
  31. -- s-expressions in a list, after printing the head of the list.
  32. data Indent
  33. = Swing -- ^ A 'Swing' indent will indent subsequent exprs some fixed
  34. -- amount more than the current line.
  35. --
  36. -- > (foo
  37. -- > bar
  38. -- > baz
  39. -- > quux)
  40. | SwingAfter Int -- ^ A 'SwingAfter' @n@ indent will try to print the
  41. -- first @n@ expressions after the head on the same
  42. -- line as the head, and all after will be swung.
  43. -- 'SwingAfter' @0@ is equivalent to 'Swing'.
  44. --
  45. -- > (foo bar
  46. -- > baz
  47. -- > quux)
  48. | Align -- ^ An 'Align' indent will print the first expression after
  49. -- the head on the same line, and subsequent expressions will
  50. -- be aligned with that one.
  51. --
  52. -- > (foo bar
  53. -- > baz
  54. -- > quux)
  55. deriving (Eq, Show)
  56. -- | A 'SExprPrinter' value describes how to print a given value as an
  57. -- s-expression. The @carrier@ type parameter indicates the value
  58. -- that will be printed, and the @atom@ parameter indicates the type
  59. -- that will represent tokens in an s-expression structure.
  60. data SExprPrinter atom carrier = SExprPrinter
  61. { atomPrinter :: atom -> Text
  62. -- ^ How to serialize a given atom to 'Text'.
  63. , fromCarrier :: carrier -> SExpr atom
  64. -- ^ How to turn a carrier type back into a 'Sexpr'.
  65. , swingIndent :: SExpr atom -> Indent
  66. -- ^ How to indent subsequent expressions, as determined by
  67. -- the head of the list.
  68. , indentAmount :: Int
  69. -- ^ How much to indent after a swung indentation.
  70. , maxWidth :: Maybe Int
  71. -- ^ The maximum width (if any) If this is 'None' then the
  72. -- resulting s-expression might be printed on one line (if
  73. -- 'indentPrint' is 'False') and might be pretty-printed in
  74. -- the most naive way possible (if 'indentPrint' is 'True').
  75. , indentPrint :: Bool
  76. -- ^ Whether to indent or not. This has been retrofitted onto
  77. }
  78. -- | A default 'SExprPrinter' struct that will always print a 'SExpr'
  79. -- as a single line.
  80. flatPrint :: (atom -> Text) -> SExprPrinter atom (SExpr atom)
  81. flatPrint printer = SExprPrinter
  82. { atomPrinter = printer
  83. , fromCarrier = id
  84. , swingIndent = const Swing
  85. , indentAmount = 2
  86. , maxWidth = Nothing
  87. , indentPrint = False
  88. }
  89. -- | A default 'SExprPrinter' struct that will always swing subsequent
  90. -- expressions onto later lines if they're too long, indenting them
  91. -- by two spaces.
  92. basicPrint :: (atom -> Text) -> SExprPrinter atom (SExpr atom)
  93. basicPrint printer = SExprPrinter
  94. { atomPrinter = printer
  95. , fromCarrier = id
  96. , swingIndent = const Swing
  97. , indentAmount = 2
  98. , maxWidth = Just 80
  99. , indentPrint = True
  100. }
  101. unboundIndentPrint :: (atom -> Text) -> SExprPrinter atom (SExpr atom)
  102. unboundIndentPrint printer = SExprPrinter
  103. { atomPrinter = printer
  104. , fromCarrier = id
  105. , swingIndent = const Swing
  106. , indentAmount = 2
  107. , maxWidth = Nothing
  108. , indentPrint = True
  109. }
  110. -- | This is an intermediate representation which is like (but not
  111. -- identical to) a RichSExpr representation. In particular, it has a
  112. -- special case for empty lists, and it also keeps a single piece of
  113. -- indent information around for each list
  114. data Intermediate
  115. = IAtom Text
  116. | IList Indent (Seq.Seq Intermediate) (Maybe Text)
  117. | IEmpty
  118. toIntermediate :: SExprPrinter a (SExpr a) -> SExpr a -> Intermediate
  119. toIntermediate
  120. SExprPrinter { atomPrinter = printAtom
  121. , swingIndent = swing
  122. } = headOf
  123. where
  124. headOf (SAtom a) = IAtom (printAtom a)
  125. headOf SNil = IEmpty
  126. headOf (SCons x xs) =
  127. gather (swing x) (Seq.singleton (headOf x)) xs
  128. gather sw rs SNil =
  129. IList sw rs Nothing
  130. gather sw rs (SAtom a) =
  131. IList sw rs (Just (printAtom a))
  132. gather sw rs (SCons x xs) =
  133. gather sw (rs Seq.|> headOf x) xs
  134. unboundIndentPrintSExpr :: SExprPrinter a (SExpr a) -> SExpr a -> TL.Text
  135. unboundIndentPrintSExpr spec = finalize . go . toIntermediate spec
  136. where
  137. finalize = B.toLazyText . F.foldMap (<> B.fromString "\n")
  138. go :: Intermediate -> Seq.Seq B.Builder
  139. go (IAtom t) = Seq.singleton (B.fromText t)
  140. go IEmpty = Seq.singleton (B.fromString "()")
  141. -- this case should never be called with an empty argument to
  142. -- @values@, as that should have been translated to @IEmpty@
  143. -- instead.
  144. go (IList iv values rest)
  145. -- if we're looking at an s-expression that has no nested
  146. -- s-expressions, then we might as well consider it flat and let
  147. -- it take the whole line
  148. | Just strings <- T.traverse ppBasic values =
  149. Seq.singleton (B.fromString "(" <> buildUnwords strings <> pTail rest)
  150. -- it's not "flat", so we might want to swing after the first thing
  151. | Swing <- iv =
  152. -- if this match fails, then it means we've failed to
  153. -- convert to an Intermediate correctly!
  154. let x Seq.:< xs = Seq.viewl values
  155. butLast = insertParen (go x) <> fmap doIndent (F.foldMap go xs)
  156. in handleTail rest butLast
  157. -- ...or after several things
  158. | SwingAfter n <- iv =
  159. let (hs, xs) = Seq.splitAt n values
  160. hd = B.fromString "(" <> buildUnwords (F.foldMap go hs)
  161. butLast = hd Seq.<| fmap doIndent (F.foldMap go xs)
  162. in handleTail rest butLast
  163. -- the 'align' choice is clunkier because we need to know how
  164. -- deep to indent, so we have to force the first builder to grab its size
  165. | otherwise =
  166. let x Seq.:< xs = Seq.viewl values
  167. -- so we grab that and figure out its length plus two (for
  168. -- the leading paren and the following space). This uses a
  169. -- max because it's possible the first thing is itself a
  170. -- multi-line s-expression (in which case it seems like
  171. -- using the Align strategy is a terrible idea, but who am
  172. -- I to quarrel with the wild fruits upon the Tree of
  173. -- Life?)
  174. len = 2 + F.maximum (fmap (TL.length . B.toLazyText) (go x))
  175. in case Seq.viewl xs of
  176. -- if there's nothing after the head of the expression, then
  177. -- we simply close it
  178. Seq.EmptyL -> insertParen (insertCloseParen (go x))
  179. -- otherwise, we put the first two things on the same line
  180. -- with spaces and everything else gets indended the
  181. -- forementioned length
  182. y Seq.:< ys ->
  183. let hd = B.fromString "(" <> buildUnwords (F.foldMap go (Seq.fromList [x, y]))
  184. butLast = hd Seq.<| fmap (doIndentOf (fromIntegral len)) (F.foldMap go ys)
  185. in handleTail rest butLast
  186. -- B.fromString "(" <> buildUnwords (F.foldMap go (Seq.fromList [x, y]))
  187. -- Seq.<| fmap (doIndentOf (fromIntegral len)) (handleTail rest (F.foldMap go ys))
  188. doIndent :: B.Builder -> B.Builder
  189. doIndent = doIndentOf (indentAmount spec)
  190. doIndentOf :: Int -> B.Builder -> B.Builder
  191. doIndentOf n b = B.fromText (T.replicate n " ") <> b
  192. insertParen :: Seq.Seq B.Builder -> Seq.Seq B.Builder
  193. insertParen s = case Seq.viewl s of
  194. Seq.EmptyL -> s
  195. x Seq.:< xs -> (B.fromString "(" <> x) Seq.<| xs
  196. handleTail :: Maybe Text -> Seq.Seq B.Builder -> Seq.Seq B.Builder
  197. handleTail Nothing = insertCloseParen
  198. handleTail (Just t) =
  199. (Seq.|> (B.fromString "." <> B.fromText t <> B.fromString ")"))
  200. insertCloseParen :: Seq.Seq B.Builder -> Seq.Seq B.Builder
  201. insertCloseParen s = case Seq.viewr s of
  202. Seq.EmptyR -> Seq.singleton (B.fromString ")")
  203. xs Seq.:> x -> xs Seq.|> (x <> B.fromString ")")
  204. buildUnwords sq =
  205. case Seq.viewl sq of
  206. Seq.EmptyL -> mempty
  207. t Seq.:< ts -> t <> F.foldMap (\ x -> B.fromString " " <> x) ts
  208. pTail Nothing = B.fromString ")"
  209. pTail (Just t) = B.fromString ". " <> B.fromText t <> B.fromString ")"
  210. ppBasic (IAtom t) = Just (B.fromText t)
  211. ppBasic (IEmpty) = Just (B.fromString "()")
  212. ppBasic _ = Nothing
  213. -- | Modify the carrier type of a 'SExprPrinter' by describing how
  214. -- to convert the new type back to the previous type. For example,
  215. -- to pretty-print a well-formed s-expression, we can modify the
  216. -- 'SExprPrinter' value as follows:
  217. --
  218. -- >>> let printer = setFromCarrier fromWellFormed (basicPrint id)
  219. -- >>> encodeOne printer (WFSList [WFSAtom "ele", WFSAtom "phant"])
  220. -- "(ele phant)"
  221. setFromCarrier :: (c -> b) -> SExprPrinter a b -> SExprPrinter a c
  222. setFromCarrier fc pr = pr { fromCarrier = fromCarrier pr . fc }
  223. -- | Dictate a maximum width for pretty-printed s-expressions.
  224. --
  225. -- >>> let printer = setMaxWidth 8 (basicPrint id)
  226. -- >>> encodeOne printer (L [A "one", A "two", A "three"])
  227. -- "(one \n two\n three)"
  228. setMaxWidth :: Int -> SExprPrinter atom carrier -> SExprPrinter atom carrier
  229. setMaxWidth n pr = pr { maxWidth = Just n }
  230. -- | Allow the serialized s-expression to be arbitrarily wide. This
  231. -- makes all pretty-printing happen on a single line.
  232. --
  233. -- >>> let printer = removeMaxWidth (basicPrint id)
  234. -- >>> encodeOne printer (L [A "one", A "two", A "three"])
  235. -- "(one two three)"
  236. removeMaxWidth :: SExprPrinter atom carrier -> SExprPrinter atom carrier
  237. removeMaxWidth pr = pr { maxWidth = Nothing }
  238. -- | Set the number of spaces that a subsequent line will be indented
  239. -- after a swing indentation.
  240. --
  241. -- >>> let printer = setMaxWidth 12 (basicPrint id)
  242. -- >>> encodeOne printer (L [A "elephant", A "pachyderm"])
  243. -- "(elephant \n pachyderm)"
  244. -- >>> encodeOne (setIndentAmount 4) (L [A "elephant", A "pachyderm"])
  245. -- "(elephant \n pachyderm)"
  246. setIndentAmount :: Int -> SExprPrinter atom carrier -> SExprPrinter atom carrier
  247. setIndentAmount n pr = pr { indentAmount = n }
  248. -- | Dictate how to indent subsequent lines based on the leading
  249. -- subexpression in an s-expression. For details on how this works,
  250. -- consult the documentation of the 'Indent' type.
  251. --
  252. -- >>> let indent (A "def") = SwingAfter 1; indent _ = Swing
  253. -- >>> let printer = setIndentStrategy indent (setMaxWidth 8 (basicPrint id))
  254. -- >>> encodeOne printer (L [ A "def", L [ A "func", A "arg" ], A "body" ])
  255. -- "(def (func arg)\n body)"
  256. -- >>> encodeOne printer (L [ A "elephant", A "among", A "pachyderms" ])
  257. -- "(elephant \n among\n pachyderms)"
  258. setIndentStrategy :: (SExpr atom -> Indent) -> SExprPrinter atom carrier -> SExprPrinter atom carrier
  259. setIndentStrategy st pr = pr { swingIndent = st }
  260. -- Sort of like 'unlines' but without the trailing newline
  261. joinLines :: [Text] -> Text
  262. joinLines = T.intercalate "\n"
  263. -- Indents a line by n spaces
  264. indent :: Int -> Text -> Text
  265. indent n ts = T.replicate n " " <> ts
  266. -- Indents every line n spaces, and adds a newline to the beginning
  267. -- used in swung indents
  268. indentAll :: Int -> [Text] -> Text
  269. indentAll n = ("\n" <>) . joinLines . map (indent n)
  270. -- Indents every line but the first by some amount
  271. -- used in aligned indents
  272. indentSubsequent :: Int -> [Text] -> Text
  273. indentSubsequent _ [] = ""
  274. indentSubsequent _ [t] = t
  275. indentSubsequent n (t:ts) = joinLines (t : go ts)
  276. where go = map (indent n)
  277. -- oh god this code is so disgusting
  278. -- i'm sorry to everyone i let down by writing this
  279. -- i swear i'll do better in the future i promise i have to
  280. -- for my sake and for everyone's
  281. -- | Pretty-print a 'SExpr' according to the options in a
  282. -- 'LayoutOptions' value.
  283. prettyPrintSExpr :: SExprPrinter a (SExpr a) -> SExpr a -> Text
  284. prettyPrintSExpr pr@SExprPrinter { .. } expr = case maxWidth of
  285. Nothing
  286. | indentPrint -> TL.toStrict (unboundIndentPrintSExpr pr (fromCarrier expr))
  287. | otherwise -> flatPrintSExpr (fmap atomPrinter (fromCarrier expr))
  288. Just _ -> indentPrintSExpr pr expr
  289. indentPrintSExpr :: SExprPrinter a (SExpr a) -> SExpr a -> Text
  290. indentPrintSExpr SExprPrinter { .. } = pHead 0
  291. where
  292. pHead _ SNil = "()"
  293. pHead _ (SAtom a) = atomPrinter a
  294. pHead ind (SCons x xs) = gather ind x xs id
  295. gather ind h (SCons x xs) k = gather ind h xs (k . (x:))
  296. gather ind h end k = "(" <> hd <> body <> tl <> ")"
  297. where tl = case end of
  298. SNil -> ""
  299. SAtom a -> " . " <> atomPrinter a
  300. SCons _ _ -> error "[unreachable]"
  301. hd = indentSubsequent ind [pHead (ind+1) h]
  302. lst = k []
  303. flat = T.unwords (map (pHead (ind+1)) lst)
  304. headWidth = T.length hd + 1
  305. indented =
  306. case swingIndent h of
  307. SwingAfter n ->
  308. let (l, ls) = splitAt n lst
  309. t = T.unwords (map (pHead (ind+1)) l)
  310. ts = indentAll (ind + indentAmount)
  311. (map (pHead (ind + indentAmount)) ls)
  312. in t <> ts
  313. Swing ->
  314. indentAll (ind + indentAmount)
  315. (map (pHead (ind + indentAmount)) lst)
  316. Align ->
  317. indentSubsequent (ind + headWidth + 1)
  318. (map (pHead (ind + headWidth + 1)) lst)
  319. body
  320. | length lst == 0 = ""
  321. | Just maxAmt <- maxWidth
  322. , T.length flat + ind > maxAmt = " " <> indented
  323. | otherwise = " " <> flat
  324. -- where
  325. -- -- this is the base-case that knows how to print empty lists and
  326. -- -- atoms
  327. -- pHead _ SNil = B.fromString "()"
  328. -- pHead _ (SAtom a) = B.fromText a
  329. -- pHead ind (SCons x xs) = gather ind x xs id 0
  330. -- -- otherwise, we trawl through the list grabbing every element...
  331. -- gather ind h (SCons x xs) k r = gather ind h xs (k . (x:)) (r + T.length x)
  332. -- gather ind h end k r = B.fromString "(" <> hd <> body <> tl <> B.fromString ")"
  333. -- where
  334. -- tl = case end of
  335. -- SNil -> mempty
  336. -- SAtom a -> B.fromString " . " <> B.fromText a
  337. -- SCons _ _ -> error "[unreachable]"
  338. -- hd = indentSubsequent ind [pHead (ind+1) h]
  339. -- lst = k []
  340. -- flat = T.unwords (map (pHead (ind+1)) lst)
  341. -- headWidth = T.length hd + 1
  342. -- indented =
  343. -- case swingIndent h of
  344. -- SwingAfter n ->
  345. -- let (l, ls) = splitAt n lst
  346. -- t = T.unwords (map (pHead (ind+1)) l)
  347. -- ts = indentAll (ind + indentAmount)
  348. -- (map (pHead (ind + indentAmount)) ls)
  349. -- in t <> ts
  350. -- Swing ->
  351. -- indentAll (ind + indentAmount)
  352. -- (map (pHead (ind + indentAmount)) lst)
  353. -- Align ->
  354. -- indentSubsequent (ind + headWidth + 1)
  355. -- (map (pHead (ind + headWidth + 1)) lst)
  356. -- body
  357. -- | length lst == 0 = B.fromString ""
  358. -- | Just maxAmt <- maxWidth
  359. -- , T.length flat + ind > maxAmt = B.fromString " " <> indented
  360. -- | otherwise = B.fromString " " <> flat
  361. -- if we don't indent anything, then we can ignore a bunch of the
  362. -- details above
  363. flatPrintSExpr :: SExpr Text -> Text
  364. flatPrintSExpr = TL.toStrict . B.toLazyText . pHead
  365. where
  366. pHead (SCons x xs) =
  367. B.fromString "(" <> pHead x <> pTail xs
  368. pHead (SAtom t) =
  369. B.fromText t
  370. pHead SNil =
  371. B.fromString "()"
  372. pTail (SCons x xs) =
  373. B.fromString " " <> pHead x <> pTail xs
  374. pTail (SAtom t) =
  375. B.fromString " . " <> B.fromText t <> B.fromString ")"
  376. pTail SNil =
  377. B.fromString ")"
  378. -- | Turn a single s-expression into a string according to a given
  379. -- 'SExprPrinter'.
  380. encodeOne :: SExprPrinter atom carrier -> carrier -> Text
  381. encodeOne s@(SExprPrinter { .. }) =
  382. prettyPrintSExpr (s { fromCarrier = id }) . fromCarrier
  383. -- | Turn a list of s-expressions into a single string according to
  384. -- a given 'SExprPrinter'.
  385. encode :: SExprPrinter atom carrier -> [carrier] -> Text
  386. encode spec = T.intercalate "\n\n" . map (encodeOne spec)