Print.hs 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452
  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 Control.Applicative
  21. import Data.Monoid ((<>))
  22. import Data.Text (Text)
  23. import qualified Data.Text as T
  24. import qualified Data.Text.Lazy as TL
  25. import qualified Data.Text.Lazy.Builder as B
  26. import Data.SCargot.Repr
  27. -- | The 'Indent' type is used to determine how to indent subsequent
  28. -- s-expressions in a list, after printing the head of the list.
  29. data Indent
  30. = Swing -- ^ A 'Swing' indent will indent subsequent exprs some fixed
  31. -- amount more than the current line.
  32. --
  33. -- > (foo
  34. -- > bar
  35. -- > baz
  36. -- > quux)
  37. | SwingAfter Int -- ^ A 'SwingAfter' @n@ indent will try to print the
  38. -- first @n@ expressions after the head on the same
  39. -- line as the head, and all after will be swung.
  40. -- 'SwingAfter' @0@ is equivalent to 'Swing'.
  41. --
  42. -- > (foo bar
  43. -- > baz
  44. -- > quux)
  45. | Align -- ^ An 'Align' indent will print the first expression after
  46. -- the head on the same line, and subsequent expressions will
  47. -- be aligned with that one.
  48. --
  49. -- > (foo bar
  50. -- > baz
  51. -- > quux)
  52. deriving (Eq, Show)
  53. -- | A 'SExprPrinter' value describes how to print a given value as an
  54. -- s-expression. The @carrier@ type parameter indicates the value
  55. -- that will be printed, and the @atom@ parameter indicates the type
  56. -- that will represent tokens in an s-expression structure.
  57. data SExprPrinter atom carrier = SExprPrinter
  58. { atomPrinter :: atom -> Text
  59. -- ^ How to serialize a given atom to 'Text'.
  60. , fromCarrier :: carrier -> SExpr atom
  61. -- ^ How to turn a carrier type back into a 'Sexpr'.
  62. , swingIndent :: SExpr atom -> Indent
  63. -- ^ How to indent subsequent expressions, as determined by
  64. -- the head of the list.
  65. , indentAmount :: Int
  66. -- ^ How much to indent after a swung indentation.
  67. , maxWidth :: Maybe Int
  68. -- ^ The maximum width (if any) If this is 'None' then
  69. -- the resulting s-expression will always be printed
  70. -- on a single line.
  71. }
  72. -- | A default 'LayoutOptions' struct that will always print a 'SExpr'
  73. -- as a single line.
  74. flatPrint :: (atom -> Text) -> SExprPrinter atom (SExpr atom)
  75. flatPrint printer = SExprPrinter
  76. { atomPrinter = printer
  77. , fromCarrier = id
  78. , swingIndent = const Swing
  79. , indentAmount = 2
  80. , maxWidth = Nothing
  81. }
  82. -- | A default 'LayoutOptions' struct that will always swing subsequent
  83. -- expressions onto later lines if they're too long, indenting them
  84. -- by two spaces.
  85. basicPrint :: (atom -> Text) -> SExprPrinter atom (SExpr atom)
  86. basicPrint printer = SExprPrinter
  87. { atomPrinter = printer
  88. , fromCarrier = id
  89. , swingIndent = const Swing
  90. , indentAmount = 2
  91. , maxWidth = Just 80
  92. }
  93. -- | Modify the carrier type of a 'SExprPrinter' by describing how
  94. -- to convert the new type back to the previous type. For example,
  95. -- to pretty-print a well-formed s-expression, we can modify the
  96. -- 'SExprPrinter' value as follows:
  97. --
  98. -- >>> let printer = setFromCarrier fromWellFormed (basicPrint id)
  99. -- >>> encodeOne printer (WFSList [WFSAtom "ele", WFSAtom "phant"])
  100. -- "(ele phant)"
  101. setFromCarrier :: (c -> b) -> SExprPrinter a b -> SExprPrinter a c
  102. setFromCarrier fc pr = pr { fromCarrier = fromCarrier pr . fc }
  103. -- | Dictate a maximum width for pretty-printed s-expressions.
  104. --
  105. -- >>> let printer = setMaxWidth 8 (basicPrint id)
  106. -- >>> encodeOne printer (L [A "one", A "two", A "three"])
  107. -- "(one \n two\n three)"
  108. setMaxWidth :: Int -> SExprPrinter atom carrier -> SExprPrinter atom carrier
  109. setMaxWidth n pr = pr { maxWidth = Just n }
  110. -- | Allow the serialized s-expression to be arbitrarily wide. This
  111. -- makes all pretty-printing happen on a single line.
  112. --
  113. -- >>> let printer = removeMaxWidth (basicPrint id)
  114. -- >>> encodeOne printer (L [A "one", A "two", A "three"])
  115. -- "(one two three)"
  116. removeMaxWidth :: SExprPrinter atom carrier -> SExprPrinter atom carrier
  117. removeMaxWidth pr = pr { maxWidth = Nothing }
  118. -- | Set the number of spaces that a subsequent line will be indented
  119. -- after a swing indentation.
  120. --
  121. -- >>> let printer = setMaxWidth 12 (basicPrint id)
  122. -- >>> encodeOne printer (L [A "elephant", A "pachyderm"])
  123. -- "(elephant \n pachyderm)"
  124. -- >>> encodeOne (setIndentAmount 4) (L [A "elephant", A "pachyderm"])
  125. -- "(elephant \n pachyderm)"
  126. setIndentAmount :: Int -> SExprPrinter atom carrier -> SExprPrinter atom carrier
  127. setIndentAmount n pr = pr { indentAmount = n }
  128. -- | Dictate how to indent subsequent lines based on the leading
  129. -- subexpression in an s-expression. For details on how this works,
  130. -- consult the documentation of the 'Indent' type.
  131. --
  132. -- >>> let indent (A "def") = SwingAfter 1; indent _ = Swing
  133. -- >>> let printer = setIndentStrategy indent (setMaxWidth 8 (basicPrint id))
  134. -- >>> encodeOne printer (L [ A "def", L [ A "func", A "arg" ], A "body" ])
  135. -- "(def (func arg)\n body)"
  136. -- >>> encodeOne printer (L [ A "elephant", A "among", A "pachyderms" ])
  137. -- "(elephant \n among\n pachyderms)"
  138. setIndentStrategy :: (SExpr atom -> Indent) -> SExprPrinter atom carrier -> SExprPrinter atom carrier
  139. setIndentStrategy st pr = pr { swingIndent = st }
  140. -- Sort of like 'unlines' but without the trailing newline
  141. joinLines :: [Text] -> Text
  142. joinLines = T.intercalate "\n"
  143. -- Indents a line by n spaces
  144. indent :: Int -> Text -> Text
  145. indent n ts = T.replicate n " " <> ts
  146. -- Indents every line n spaces, and adds a newline to the beginning
  147. -- used in swung indents
  148. indentAll :: Int -> [Text] -> Text
  149. indentAll n = ("\n" <>) . joinLines . map (indent n)
  150. -- Indents every line but the first by some amount
  151. -- used in aligned indents
  152. indentSubsequent :: Int -> [Text] -> Text
  153. indentSubsequent _ [] = ""
  154. indentSubsequent _ [t] = t
  155. indentSubsequent n (t:ts) = joinLines (t : go ts)
  156. where go = map (indent n)
  157. -- oh god this code is so disgusting
  158. -- i'm sorry to everyone i let down by writing this
  159. -- i swear i'll do better in the future i promise i have to
  160. -- for my sake and for everyone's
  161. -- | Pretty-print a 'SExpr' according to the options in a
  162. -- 'LayoutOptions' value.
  163. prettyPrintSExpr :: SExprPrinter a (SExpr a) -> SExpr a -> Text
  164. prettyPrintSExpr pr@SExprPrinter { .. } expr = case maxWidth of
  165. Nothing -> flatPrintSExpr (fmap atomPrinter (fromCarrier expr))
  166. Just w -> indentPrintSExpr2 pr w expr
  167. indentPrintSExpr :: SExprPrinter a (SExpr a) -> Int -> SExpr a -> Text
  168. indentPrintSExpr SExprPrinter { .. } _ = pHead 0
  169. where
  170. pHead _ SNil = "()"
  171. pHead _ (SAtom a) = atomPrinter a
  172. pHead ind (SCons x xs) = gather ind x xs id
  173. gather ind h (SCons x xs) k = gather ind h xs (k . (x:))
  174. gather ind h end k = "(" <> hd <> body <> tl <> ")"
  175. where tl = case end of
  176. SNil -> ""
  177. SAtom a -> " . " <> atomPrinter a
  178. SCons _ _ -> error "[unreachable]"
  179. hd = indentSubsequent ind [pHead (ind+1) h]
  180. lst = k []
  181. flat = T.unwords (map (pHead (ind+1)) lst)
  182. headWidth = T.length hd + 1
  183. indented =
  184. case swingIndent h of
  185. SwingAfter n ->
  186. let (l, ls) = splitAt n lst
  187. t = T.unwords (map (pHead (ind+1)) l)
  188. ts = indentAll (ind + indentAmount)
  189. (map (pHead (ind + indentAmount)) ls)
  190. in t <> ts
  191. Swing ->
  192. indentAll (ind + indentAmount)
  193. (map (pHead (ind + indentAmount)) lst)
  194. Align ->
  195. indentSubsequent (ind + headWidth + 1)
  196. (map (pHead (ind + headWidth + 1)) lst)
  197. body
  198. | length lst == 0 = ""
  199. | Just maxAmt <- maxWidth
  200. , T.length flat + ind > maxAmt = " " <> indented
  201. | otherwise = " " <> flat
  202. -- | Pretty-printing for S-Expressions. The general strategy is that
  203. -- an SCons tail should either all fit on the current line, or else
  204. -- each tail item should be placed on its own line with indenting.
  205. -- Note that a line must print something, so while subsequent elements
  206. -- will be placed on following lines, it is possible that the first
  207. -- thing on a line (plus its indentation) may exceed the maxwidth.
  208. type IndentSpec = Int
  209. type Indenting = Maybe IndentSpec
  210. data PPS = PPS { indentWc :: IndentSpec
  211. , remWidth :: Int
  212. , numClose :: Int
  213. }
  214. deriving Show
  215. data SElem = SText Int T.Text
  216. | SPair Int SElem SElem
  217. | SDecl Int SElem [SElem]
  218. | SJoin Int [SElem]
  219. deriving (Show, Eq)
  220. sElemSize :: SElem -> Int
  221. sElemSize (SText n _) = n
  222. sElemSize (SPair n _ _) = n
  223. sElemSize (SDecl n _ _) = n
  224. sElemSize (SJoin n _) = n
  225. indentPrintSExpr2 :: SExprPrinter a (SExpr a) -> Int -> SExpr a -> Text
  226. indentPrintSExpr2 SExprPrinter { .. } maxW sexpr =
  227. let atomTextTree = selems sexpr
  228. pretty = fmap addIndent $ fst $ pHead (PPS 0 maxW 0) atomTextTree
  229. -- prettyWithDebug = pretty <> ["", (T.pack $ show atomTextTree)]
  230. in T.unlines pretty
  231. where
  232. -- selems converts the (SExpr a) into an SElem, converting
  233. -- individual atoms to their text format but not concerned with
  234. -- other text formatting. The resulting SElem tree will be
  235. -- iterated over to determine the wrapping strategy to apply.
  236. selems SNil = SText 2 "()"
  237. selems (SAtom a) = let p = atomPrinter a in SText (T.length p) p
  238. selems (SCons l r) =
  239. let l' = selems l
  240. lsz = sElemSize l'
  241. r' = selems r
  242. rsz = sElemSize r'
  243. bsz = lsz + rsz
  244. in case r of
  245. SNil -> SJoin lsz [l']
  246. SAtom _ -> SPair bsz l' r'
  247. _ -> case l of
  248. SAtom _ -> case r' of
  249. SJoin _ rl' -> SDecl bsz l' rl'
  250. SDecl _ d dl -> SDecl bsz l' (d:dl)
  251. _ -> SDecl bsz l' [r']
  252. _ -> SJoin bsz $ prefixOnto l' r'
  253. prefixOnto e (SJoin _ l) = e:l
  254. prefixOnto e (SDecl _ l r) = e:l:r
  255. prefixOnto e r = [e,r]
  256. addIndent (Nothing, t) = t
  257. addIndent (Just n, t) = indent n t
  258. nextIndent = incrIndent indentAmount
  259. incrIndent v n = n + v
  260. pHead :: PPS -> SElem -> ( [(Indenting, Text)], PPS )
  261. pHead pps (SText _ t) = ( [(Nothing, t)]
  262. , pps { remWidth = remWidth pps - T.length t})
  263. pHead pps (SPair _ e1 e2) =
  264. let (t1,pps1) = pHead pps e1
  265. (t2,pps2) = pTail ppsNextLine e2
  266. (t3,pps3) = pTail ppsSameLine e2 -- same line
  267. ppsNextLine = pps { remWidth = remWidth pps - T.length sep }
  268. ppsSameLine = pps1 { remWidth = remWidth pps1 - T.length sep }
  269. sep = " . "
  270. t1h = head t1
  271. wrapJoin i l rs = wrapT i (snd l <> sep) rs
  272. sameLine l r p = (wrapJoin (indentWc pps) l r, p)
  273. separateLines l r p = (wrapTWith False "(" "" (indentWc pps) "" l ++
  274. wrapTWith True sep ")" (indentWc pps) "" r, p)
  275. in if length t1 > 1 || remWidth pps3 < numClose pps + 5
  276. then separateLines t1 t2 pps2
  277. else sameLine t1h t3 pps3
  278. -- An SJoin is a sequence of elements at the same rank. They are
  279. -- either all placed on a single line, or one on each line.
  280. pHead pps (SJoin _ []) = ( [], pps )
  281. pHead pps (SJoin els others) =
  282. let (t1,_) = pHead pps $ head others
  283. (t3,pps3) = foldl pTail' ([], pps) others
  284. pTail' :: ([(Indenting, Text)], PPS)
  285. -> SElem
  286. -> ([(Indenting, Text)], PPS)
  287. pTail' (rl,pp) ne = let (rt,pr) = pTail pp ne
  288. hrl = head rl
  289. hrt = head rt
  290. in if length rt == 1
  291. then case length rl of
  292. 0 -> (rt, pr)
  293. 1 -> ((fst hrl, snd hrl <> " " <> snd hrt):[], pr)
  294. _ -> (rl <> rt, pr)
  295. else (rl <> rt, pr)
  296. sameLine parts pEnd = (wrapT (indentWc ppsSame) "" parts, pEnd)
  297. ppsNext = pps { indentWc = nextIndent (indentWc pps)
  298. , remWidth = remWidth pps - indentAmount
  299. }
  300. ppsSame = pps { indentWc = nextIndent (indentWc pps)
  301. , remWidth = remWidth pps - indentAmount
  302. }
  303. ppsMulti = pps { indentWc = nextIndent (indentWc pps)
  304. , remWidth = remWidth pps - indentAmount
  305. }
  306. pps3' = pps3
  307. separateLines elems pEnd =
  308. let lr = concatMap (fst . pTail pEnd) elems
  309. in (wrapTWith False "(" ")" (indentWc ppsNext) "" lr, pEnd)
  310. in if els > remWidth pps3 || length t1 > 1 || remWidth pps3 < numClose pps + 5
  311. then separateLines others ppsMulti
  312. else sameLine t3 pps3'
  313. -- For an SDecl, always put the first element on the line. If
  314. -- *all* other elements fit on the same line, do that, otherwise
  315. -- all other elements should appear on subsequent lines with
  316. -- indentation. This will produce left-biased wrapping: wrapping
  317. -- will occur near the root of the SExp tree more than at the
  318. -- leaves.
  319. pHead pps (SDecl els first others) =
  320. let (t1,pps1) = pHead pp2 first
  321. (to1,_) = pTail pps1 (head others)
  322. firstPlusFits = sElemSize first + sElemSize (head others) < (remWidth pps - 4)
  323. allFits = els < (remWidth pps - length others - 3)
  324. tryFirstArgSameLine = case swingIndent (SCons SNil (SCons SNil SNil)) of
  325. Align -> True
  326. _ -> False
  327. pp2 = pps { indentWc = nextIndent (indentWc pps)
  328. , remWidth = remWidth pps - 1 - indentAmount
  329. , numClose = numClose pps + 1
  330. }
  331. pp2next = pp2
  332. pp2solo = pp2
  333. t1h = head t1
  334. pps1' = pps1 { indentWc = incrIndent (T.length (snd t1h) + 1)
  335. (indentWc pps1)
  336. , remWidth = remWidth pps1 - T.length (snd t1h) - 1
  337. }
  338. tothers = concatMap (fst . pTail pp2next) others -- multiline
  339. tothers' = concatMap (fst . pTail pps1') $ tail others -- multiline from 2nd
  340. (others', ppone) = foldl foldPTail ([],pps1) others -- oneline
  341. (others'', ppone') = foldl foldPTail ([],pps1') $ tail others -- multiline from 2nd
  342. foldPTail (tf,ppf) o = let (ot,opp) = pTail ppf o
  343. tf1 = head tf
  344. tr = if length ot == 1
  345. then case length tf of
  346. 0 -> ot
  347. 1 -> [(fst tf1, snd tf1 <> " " <> snd (head ot))]
  348. _ -> tf ++ ot
  349. else tf ++ ot
  350. in (tr, opp)
  351. separateLines l r p =
  352. let wr = if null r then []
  353. else wrapTWith True "" ")" (indentWc p) "" r
  354. cl = if null r then ")" else ""
  355. in (wrapTWith False "(" cl (indentWc pps) "" l <> wr, pp2)
  356. maybeSameLine l (r1,p1) (rn,p2) =
  357. if length r1 <= 1 && remWidth p1 > numClose p1
  358. then (wrapT (indentWc pps) (snd l <> " ") r1, p1)
  359. else separateLines [l] rn p2
  360. in if allFits && length t1 < 2
  361. then maybeSameLine t1h (others',ppone) (tothers,pp2solo)
  362. else if (tryFirstArgSameLine && firstPlusFits &&
  363. length t1 < 2 &&
  364. length to1 < 2 &&
  365. not (null to1) && not (null others))
  366. then maybeSameLine (fst t1h,
  367. snd t1h <> " " <> snd (head to1)) (others'',ppone') (tothers',pps1')
  368. else separateLines t1 tothers pp2
  369. pTail = pHead
  370. wrapTWith :: Bool -> T.Text -> T.Text -> IndentSpec
  371. -> T.Text
  372. -> [(Indenting, T.Text)]
  373. -> [(Indenting, T.Text)]
  374. wrapTWith isContinued st en ind hstart ts =
  375. let th = head ts
  376. tt = last ts
  377. tb = init $ tail ts
  378. tp l = (fst l <|> Just ind, snd l)
  379. fi = if isContinued then Just ind else Nothing
  380. in if length ts > 1
  381. then (((fi, st <> hstart <> snd th) : map tp tb) ++
  382. [ tp $ (fst tt, snd tt <> en) ])
  383. else [(fi, st <> hstart <> snd th <> en)]
  384. wrapT :: IndentSpec -> T.Text -> [(Indenting, T.Text)] -> [(Indenting, T.Text)]
  385. wrapT = wrapTWith False "(" ")"
  386. -- if we don't indent anything, then we can ignore a bunch of the
  387. -- details above
  388. flatPrintSExpr :: SExpr Text -> Text
  389. flatPrintSExpr = TL.toStrict . B.toLazyText . pHead
  390. where
  391. pHead (SCons x xs) =
  392. B.fromString "(" <> pHead x <> pTail xs
  393. pHead (SAtom t) =
  394. B.fromText t
  395. pHead SNil =
  396. B.fromString "()"
  397. pTail e@(SCons _ (SAtom _)) =
  398. B.fromString " " <> pHead e <> B.fromString ")"
  399. pTail (SCons x xs) =
  400. B.fromString " " <> pHead x <> pTail xs
  401. pTail (SAtom t) =
  402. B.fromString " . " <> B.fromText t <> B.fromString ")"
  403. pTail SNil =
  404. B.fromString ")"
  405. -- | Turn a single s-expression into a string according to a given
  406. -- 'SExprPrinter'.
  407. encodeOne :: SExprPrinter atom carrier -> carrier -> Text
  408. encodeOne s@(SExprPrinter { .. }) =
  409. prettyPrintSExpr (s { fromCarrier = id }) . fromCarrier
  410. -- | Turn a list of s-expressions into a single string according to
  411. -- a given 'SExprPrinter'.
  412. encode :: SExprPrinter atom carrier -> [carrier] -> Text
  413. encode spec = T.intercalate "\n\n" . map (encodeOne spec)