1 {-# LANGUAGE FlexibleInstances #-}
2 {-# LANGUAGE OverloadedStrings #-}
3 {-# LANGUAGE StrictData #-}
4 {-# OPTIONS_GHC -fno-warn-orphans #-}
5 module Hdoc.TCT.Write.Plain where
7 import Control.Applicative (liftA2)
8 import Control.Monad (Monad(..))
10 import Data.Char (Char)
11 import Data.Default.Class (Default(..))
12 import Data.Eq (Eq(..))
13 import Data.Foldable (Foldable(..))
14 import Data.Function (($))
15 import Data.Functor ((<$>))
17 import Data.List.NonEmpty (NonEmpty(..))
18 import Data.Maybe (Maybe(..), maybe)
19 import Data.Monoid (Monoid(..))
20 import Data.Ord (Ord(..), Ordering(..))
21 import Data.Semigroup (Semigroup(..))
22 import Data.Sequence (ViewL(..))
23 import Data.String (String, IsString(..))
24 import Prelude (Num(..), error)
25 import Text.Show (Show(..))
26 import qualified Control.Monad.Trans.State as S
27 import qualified Data.List as List
28 import qualified Data.Sequence as Seq
29 import qualified Data.Text.Lazy as TL
30 import qualified Data.Text.Lazy.Builder as TLB
31 import qualified Language.Symantic.XML as XML
32 import qualified Text.Megaparsec as P
36 -- import Hdoc.TCT.Debug
38 writePlain :: Roots -> TL.Text
39 writePlain doc = text (setStart doc def) doc
42 type Plain = S.State State TLB.Builder
43 -- NOTE: To get maximum performance when building lazy Text values using a builder,
44 -- associate mappend calls to the right.
45 -- NOTE: (Semigroup.<>) associates to the right.
46 instance IsString Plain where
48 instance Semigroup Plain where
50 instance Monoid Plain where
54 runPlain :: Plain -> State -> TL.Text
55 runPlain p s = TLB.toLazyText $ S.evalState p s
57 text :: Plainify a => State -> a -> TL.Text
58 text s a = runPlain (plainify a) s
63 { state_escape :: Bool
64 , state_pos :: LineColumn
65 -- ^ current position,
66 -- always in sync with annotated 'LineColumn' of the input,
67 -- not with the output (whose colmuns may be shifted left by 'state_unindent')
68 , state_indent :: TL.Text
69 -- ^ indentation, which contain horizontal spaces,
70 -- but also any repeated prefix introduced by 'HeaderBar' or 'HeaderGreat'
71 , state_unindent :: Int
72 -- ^ initial 'colNum' set by 'setStart',
73 -- useful to shift everything to the left
75 instance Default State where
83 -- | Set the starting 'LineColumn' of given 'State'
84 -- by using the first 'cell_begin'.
85 setStart :: Roots -> State -> State
88 , state_unindent = colInt pos
93 Tree (Sourced (FileRange{fileRange_begin}:|_) _) _ :< _ -> fileRange_begin
96 class Plainify a where
97 plainify :: a -> Plain
98 instance Plainify () where
100 instance Plainify Char where
103 S.modify' $ \s@State{state_pos=LineColumn line _col, state_indent, state_unindent} ->
104 s{state_pos=LineColumn (line <> P.pos1) $ P.mkPos $ state_unindent + int (TL.length state_indent)}
106 return $ TLB.singleton '\n' <> TLB.fromLazyText state_indent
108 S.modify' $ \s@State{state_pos=LineColumn line col} ->
109 s{state_pos=LineColumn line (col <> P.pos1)}
110 return $ TLB.singleton c
111 instance Plainify String where
112 plainify = foldMap plainify
113 instance Plainify TL.Text where
117 let (h,ts) = TL.span (/='\n') t in
120 S.modify' $ \s@State{state_pos=LineColumn line col} ->
121 s{state_pos=LineColumn line $ col <> P.mkPos (int (TL.length h))}
122 return $ TLB.fromLazyText h
124 return (TLB.fromLazyText h) <>
125 -- NOTE: useless to increment the 'colNum' for h,
126 -- since the following '\n' will reset the 'colNum'.
129 instance Plainify LineColumn where
136 let lineOld = lineInt old
137 let colOld = colInt old
138 S.modify' $ \s -> s{state_pos=new}
139 return $ TLB.fromLazyText $
140 case lineNew`compare`lineOld of
141 GT -> lines <> state_indent <> hspaces
143 lines = TL.replicate (int64 $ lineNew - lineOld) "\n"
144 hspaces = TL.replicate (int64 (colNew - state_unindent) - TL.length state_indent) " "
145 EQ | colNew >= colOld ->
146 TL.replicate (int64 $ colNew - colOld) " "
147 _ -> error $ "plainify: non-ascending LineColumn:"
148 <> "\n old: " <> show old
149 <> "\n new: " <> show new
151 lineNew = lineInt new
153 instance Plainify Roots where
154 plainify = foldMap plainify
155 instance Plainify Root where
156 plainify (Tree (Sourced (FileRange{fileRange_begin=bp}:|_) nod) ts) =
159 ----------------------
160 NodeLower n as -> "<" <> plainify n <> plainify as <> plainify ts
161 ----------------------
164 HeaderGreat{} -> plainHeaderRepeated
165 HeaderBar{} -> plainHeaderRepeated
166 HeaderDotSlash{} -> plainify hdr
167 _ -> plainify hdr <> plainify ts
169 plainHeaderRepeated = do
172 S.modify' $ \s -> s{state_indent =
174 TL.replicate (int64 (colInt bp - state_unindent) - TL.length state_indent) " " <>
178 S.modify' $ \s -> s{state_indent}
180 ----------------------
183 S.modify' $ \s -> s{state_indent =
185 TL.replicate (int64 (colInt bp - state_unindent) - TL.length state_indent) " "
188 S.modify' $ \s -> s{state_indent}
190 ----------------------
193 S.modify' $ \s -> s{state_indent =
195 TL.replicate (int64 (colInt bp - state_unindent) - TL.length state_indent) " "
198 S.modify' $ \s -> s{state_indent}
200 ----------------------
201 NodeToken t -> plainify t <> plainify ts
202 ----------------------
204 plainify o <> plainify ts <> plainify c
205 where (o,c) = pairBorders p ts
206 instance Plainify Header where
209 HeaderColon n wh -> plainify n <> plainify wh <> ":"
210 HeaderGreat n wh -> plainify n <> plainify wh <> ">"
211 HeaderEqual n wh -> plainify n <> plainify wh <> "="
212 HeaderBar n wh -> plainify n <> plainify wh <> "|"
213 HeaderDot n -> plainify n <> "."
214 HeaderBrackets n -> "[" <> plainify n <> "]"
216 HeaderDashDash -> "-- "
217 HeaderSection lvl -> plainify (List.replicate lvl '#')
218 HeaderDotSlash n -> plainify n
219 instance Plainify Token where
221 TokenText t -> plainify t
222 TokenAt b r -> (if b then plainify '~' else mempty) <> plainify '@' <> plainify r
223 TokenTag b r -> (if b then plainify '~' else mempty) <> plainify '#' <> plainify r
224 TokenLink l -> plainify l
226 esc <- S.gets state_escape
228 then plainify ['\\', c]
230 instance Plainify ElemName where
231 plainify (XML.NCName n) = plainify n
232 instance Plainify (Maybe ElemName) where
233 plainify = maybe mempty plainify
234 instance Plainify ElemAttrs where
235 plainify = foldMap plainify
236 instance Plainify (White,ElemAttr) where
237 plainify (elemAttr_white,ElemAttr{..}) =
238 mconcat $ plainify <$>
240 , XML.unNCName elemAttr_name
247 -- * Class 'RackUpLeft'
248 class RackUpLeft a where
249 rackUpLeft :: a -> S.State (Maybe LineColumn) a
250 instance RackUpLeft LineColumn where
251 rackUpLeft pos@LineColumn{..} = do
253 Nothing -> return pos
254 Just (LineColumn l0 c0) ->
256 { lineNum = lineNum - l0 + 1
257 , colNum = colNum - c0 + 1
259 instance RackUpLeft (Sourced a) where
260 rackUpLeft (Sourced bp ep a) = do
268 instance RackUpLeft a => RackUpLeft (Seq a) where
269 rackUpLeft = mapM rackUpLeft
270 instance RackUpLeft a => RackUpLeft (Tree a) where
271 rackUpLeft (Tree n ts) =