packages feed

lima 0.2.0.0 → 0.2.1.0

raw patch · 16 files changed

+667/−601 lines, 16 files

Files

README.hs view
@@ -48,7 +48,6 @@ - The `lima` library provides a parser and a printer for each supported format. - A composition of a printer after a parser produces a converter. - Such a converter is usually invertible for two formatted documents.-  - `TeX` format requires special tags surrounding the `Haskell` code blocks for this property to hold.  ## Setup 
README.md view
@@ -47,7 +47,6 @@ - The `lima` library provides a parser and a printer for each supported format. - A composition of a printer after a parser produces a converter. - Such a converter is usually invertible for two formatted documents.-  - `TeX` format requires special tags surrounding the `Haskell` code blocks for this property to hold.  ## Setup 
lima.cabal view
@@ -5,7 +5,7 @@ -- see: https://github.com/sol/hpack  name:           lima-version:        0.2.0.0+version:        0.2.1.0 synopsis:       Convert between Haskell, Markdown, Literate Haskell, TeX description:    See the [README.md](https://github.com/deemp/lima#readme) category:       Productivity@@ -21,7 +21,6 @@     testdata/md/test.hs     testdata/md/test.lhs     testdata/md/test.md-    testdata/md/test.tex     testdata/md/tokens.hs     testdata/tex/test.hs     testdata/tex/test.lhs@@ -36,6 +35,7 @@ library   exposed-modules:       Converter+      Converter.Internal   other-modules:       Paths_lima   hs-source-dirs:
src/Converter.hs view
@@ -6,7 +6,7 @@ -- * @document block@ - consecutive lines of a document. -- * 'Token' - a representation of a document block as a @Haskell@ type. -- * 'Tokens' - a list of 'Token's.--- * @parser@ - a function that reads a document line by line and converts it to 'Token's. Example: 'hsToTokens'.+-- * @parser@ - a function that reads a document line by line and converts it to 'Tokens'. Example: 'hsToTokens'. -- * @printer@ - a function that converts 'Tokens' to a document. Example: 'hsFromTokens'. -- * @tag@ - a marker that affects how 'Tokens' are parsed. --@@ -56,8 +56,7 @@   Internal,   Config (..),   def,-  toInternalConfig,-  fromInternalConfig,+  toConfigInternal,    -- ** Lenses   disable,@@ -68,6 +67,8 @@   mdHaskellCodeEnd,   texHaskellCodeStart,   texHaskellCodeEnd,+  texSingleLineCommentStart,+  lhsSingleLineCommentStart,    -- * microlens   (&),@@ -99,29 +100,34 @@   texFromTokens',    -- * Parsers-  lhsToTokens,   hsToTokens,-  texToTokens,+  lhsToTokens,   mdToTokens,+  texToTokens, +  -- * Helpers+  mkFromTokens,+  mkToTokens,+  parseLineToToken,+  errorExpectedToken,+  errorNotEnoughTokens,+  pp,+   -- * Examples   exampleNonTexTokens',   exampleNonTexTokens,   exampleTexTokens,--  -- * Helpers-  stripEmpties,-  PrettyPrint (..), ) where +import Converter.Internal (Pretty (..), PrettyPrint (..), constructorName, countSpaces, dropEmpties, dropLen, dropLhsComment, dropTexComment, errorEmptyCommentAt, hsCommentClose, hsCommentCloseSpace, hsCommentOpen, hsCommentOpenSpace, indentN, isHsComment, isMdComment, lhsCommentSpace, lhsEscapeHash, lhsHsCodeSpace, lhsUnescapeHash, mdCommentClose, mdCommentCloseSpace, mdCommentOpenSpace, prependLhsComment, prependTexComment, startsWith, stripEmpties, stripHsComment, stripMdComment, stripSpaces, texCommentSpace) import Data.Char (isAlpha)-import Data.Data (Data (toConstr), showConstr)+import Data.Data (Data) import Data.Default (Default (def))-import Data.List (dropWhileEnd, intersperse)-import Data.List.NonEmpty (NonEmpty ((:|)), fromList, toList, (<|))+import Data.List (intersperse)+import Data.List.NonEmpty as NonEmpty (NonEmpty ((:|)), fromList, init, last, toList, (<|)) import Data.Text qualified as T import GHC.Generics (Generic)-import Lens.Micro (non, (&), (?~), (^.), (^?))+import Lens.Micro (non, to, (&), (?~), (^.)) import Lens.Micro.TH (makeLenses) import Text.Read (readMaybe) import Text.Show qualified as T@@ -138,59 +144,71 @@ type User = 'User  -- | Calculates the mode for data.-type family Mode a b where-  Mode User b = Maybe b-  Mode Internal b = b+type family Mode a where+  Mode User = Maybe String+  Mode Internal = T.Text  -- | Configuration of tag names. ----- Here are the default names.+-- The default values of @Config User@ are all 'Nothing's. --+-- Inside the library functions, @Config User@ is converted to @Config Internal@.+--+-- The below examples show the names from @Config Internal@.+-- -- >>> pp (def :: Config User) -- Config {---   _disable = Just "LIMA_DISABLE",---   _enable = Just "LIMA_ENABLE",---   _indent = Just "LIMA_INDENT",---   _dedent = Just "LIMA_DEDENT",---   _mdHaskellCodeStart = Just "```haskell",---   _mdHaskellCodeEnd = Just "```",---   _texHaskellCodeStart = Just "\\begin{code}",---   _texHaskellCodeEnd = Just "\\end{code}"+--   _disable = "LIMA_DISABLE",+--   _enable = "LIMA_ENABLE",+--   _indent = "LIMA_INDENT",+--   _dedent = "LIMA_DEDENT",+--   _mdHaskellCodeStart = "```haskell",+--   _mdHaskellCodeEnd = "```",+--   _texHaskellCodeStart = "\\begin{code}",+--   _texHaskellCodeEnd = "\\end{code}",+--   _texSingleLineCommentStart = "SINGLE_LINE ",+--   _lhsSingleLineCommentStart = "SINGLE_LINE " -- } -- -- It's possible to override these names. -- -- >>> pp ((def :: Config User) & disable ?~ "off" & enable ?~ "on" & indent ?~ "indent" & dedent ?~ "dedent") -- Config {---   _disable = Just "off",---   _enable = Just "on",---   _indent = Just "indent",---   _dedent = Just "dedent",---   _mdHaskellCodeStart = Just "```haskell",---   _mdHaskellCodeEnd = Just "```",---   _texHaskellCodeStart = Just "\\begin{code}",---   _texHaskellCodeEnd = Just "\\end{code}"+--   _disable = "off",+--   _enable = "on",+--   _indent = "indent",+--   _dedent = "dedent",+--   _mdHaskellCodeStart = "```haskell",+--   _mdHaskellCodeEnd = "```",+--   _texHaskellCodeStart = "\\begin{code}",+--   _texHaskellCodeEnd = "\\end{code}",+--   _texSingleLineCommentStart = "SINGLE_LINE ",+--   _lhsSingleLineCommentStart = "SINGLE_LINE " -- } data Config (a :: Mode') = Config-  { _disable :: Mode a T.Text+  { _disable :: Mode a   -- ^   -- Make parser ignore tags and just copy the following lines verbatim.   --   -- Set indentation to @0@.-  , _enable :: Mode a T.Text+  , _enable :: Mode a   -- ^ Stop parser from ignoring tags.-  , _indent :: Mode a T.Text+  , _indent :: Mode a   -- ^ Set code indentation to a given 'Int'.-  , _dedent :: Mode a T.Text+  , _dedent :: Mode a   -- ^ Set code indentation to @0@.-  , _mdHaskellCodeStart :: Mode a T.Text+  , _mdHaskellCodeStart :: Mode a   -- ^ Mark the start of a @Haskell@ code block in @Markdown@.-  , _mdHaskellCodeEnd :: Mode a T.Text+  , _mdHaskellCodeEnd :: Mode a   -- ^ Mark the end of a @Haskell@ code block in @Markdown@.-  , _texHaskellCodeStart :: Mode a T.Text+  , _texHaskellCodeStart :: Mode a   -- ^ Mark the start of a @Haskell@ code block in @TeX@.-  , _texHaskellCodeEnd :: Mode a T.Text+  , _texHaskellCodeEnd :: Mode a   -- ^ Mark the end of a @Haskell@ code block in @TeX@.+  , _texSingleLineCommentStart :: Mode a+  -- ^ Mark start of a comment that must be single-line.+  , _lhsSingleLineCommentStart :: Mode a+  -- ^ Mark start of a comment that must be single-line   }   deriving (Generic) @@ -200,32 +218,9 @@ deriving instance Eq (Config User) deriving instance Show (Config Internal) -newtype Pretty a = Pretty String--instance Show a => Show (Pretty a) where-  show :: Pretty a -> String-  show (Pretty s) = s---- >>> lhsFromTokens def ex--- "line 1\nline2\n\n% line 3\n\n% line 4\n"---- | A class for prettyprinting data on multiple lines in haddocks.------ It's not meant to be used outside of this library.-class Show a => PrettyPrint a where-  pp :: a -> Pretty String--instance PrettyPrint String where-  pp :: String -> Pretty String-  pp = Pretty . dropWhileEnd (== '\n')--instance PrettyPrint T.Text where-  pp :: T.Text -> Pretty String-  pp = pp . T.unpack- instance PrettyPrint (Config User) where   pp :: Config User -> Pretty String-  pp (fromInternalConfig . toInternalConfig -> config) =+  pp (toConfigInternal -> config) =     pp $       ( concatMap           ( \(a, b) ->@@ -250,38 +245,32 @@     _mdHaskellCodeEnd = "```"     _texHaskellCodeStart = "\\begin{code}"     _texHaskellCodeEnd = "\\end{code}"---- | Make a user 'Config' with default values from an internal 'Config'.-fromInternalConfig :: Config Internal -> Config User-fromInternalConfig conf = Config{..}- where-  _disable = conf ^? disable-  _enable = conf ^? enable-  _indent = conf ^? indent-  _dedent = conf ^? dedent-  _mdHaskellCodeStart = conf ^? mdHaskellCodeStart-  _mdHaskellCodeEnd = conf ^? mdHaskellCodeEnd-  _texHaskellCodeStart = conf ^? texHaskellCodeStart-  _texHaskellCodeEnd = conf ^? texHaskellCodeEnd+    _texSingleLineCommentStart = "SINGLE_LINE"+    _lhsSingleLineCommentStart = "SINGLE_LINE" -instance Default (Config User) where-  def :: Config User-  def = fromInternalConfig def+deriving instance Default (Config User)  -- | Convert a user 'Config' to an internal 'Config' with user-supplied values.-toInternalConfig :: Config User -> Config Internal-toInternalConfig conf =+--+-- It's important to do this conversion at a single entrypoint.+--+-- Otherwise, repeated conversions will accumulate changes such as appended spaces.+toConfigInternal :: Config User -> Config Internal+toConfigInternal conf =   Config-    { _disable = conf ^. disable . non _disable-    , _enable = conf ^. enable . non _enable-    , _indent = conf ^. indent . non _indent-    , _dedent = conf ^. dedent . non _dedent-    , _mdHaskellCodeStart = conf ^. mdHaskellCodeStart . non _mdHaskellCodeStart-    , _mdHaskellCodeEnd = conf ^. mdHaskellCodeEnd . non _mdHaskellCodeEnd-    , _texHaskellCodeStart = conf ^. texHaskellCodeStart . non _texHaskellCodeStart-    , _texHaskellCodeEnd = conf ^. texHaskellCodeEnd . non _texHaskellCodeEnd+    { _disable = l disable _disable+    , _enable = l enable _enable+    , _indent = l indent _indent+    , _dedent = l dedent _dedent+    , _mdHaskellCodeStart = l mdHaskellCodeStart _mdHaskellCodeStart+    , _mdHaskellCodeEnd = l mdHaskellCodeEnd _mdHaskellCodeEnd+    , _texHaskellCodeStart = l texHaskellCodeStart _texHaskellCodeStart+    , _texHaskellCodeEnd = l texHaskellCodeEnd _texHaskellCodeEnd+    , _texSingleLineCommentStart = (l texSingleLineCommentStart _texSingleLineCommentStart) <> " "+    , _lhsSingleLineCommentStart = (l lhsSingleLineCommentStart _lhsSingleLineCommentStart) <> " "     }  where+  l a b = conf ^. a . to (T.pack <$>) . non b   Config{..} = def @(Config Internal)  -- | A format of a document.@@ -294,30 +283,53 @@     Md   | -- | @TeX@     TeX+  deriving (Eq) +-- | Show a 'Format' as a file extension.+--+-- >>>showFormatExtension Lhs+-- "lhs"+showFormatExtension :: Format -> String+showFormatExtension = \case+  Hs -> "hs"+  Md -> "md"+  Lhs -> "lhs"+  TeX -> "tex"++-- | Show a 'Format' as a full name.+--+-- >>>showFormatName Lhs+-- "Literate Haskell"+showFormatName :: Format -> String+showFormatName = \case+  Hs -> "Haskell"+  Md -> "Markdown"+  Lhs -> "Literate Haskell"+  TeX -> "TeX"+ -- | Internal representation of a document. ----- A printer processes a list of 'Token's one by one.+-- A printer processes 'Tokens' one by one. -- -- A 'Token' can have: -- -- - Action - how this 'Token' affects the subsequent 'Tokens'.--- - Target - a type of 'Token's that are affected by this 'Token'.--- - Range - the nearest 'Token' until which this 'Token' affects the subsequent 'Token's.+-- - Target - a type of 'Tokens' that are affected by this 'Token'.+-- - Range - the nearest 'Token' until which this 'Token' affects the subsequent 'Tokens'. data Token   = -- |     -- - Action: set indentation to @n@.     --     -- - Target: 'HaskellCode'.     ---    -- - Range: until 'Indent', 'Dedent', or 'Disabled'.+    -- - Range: 'Indent', 'Dedent', or 'Disabled'.     Indent {n :: Int}   | -- |     -- - Action: set indentation to @0@.     --     -- - Target: 'HaskellCode'.     ---    -- - Range: until 'Indent', 'Dedent', or 'Disabled'.+    -- - Range: 'Indent', 'Dedent', or 'Disabled'.     Dedent   | -- | A block that should be invisible when rendered outside of @.hs@.     --@@ -325,7 +337,7 @@     --     -- - Target: 'HaskellCode'.     ---    -- - Range: until 'Indent', 'Dedent', or 'Disabled'.+    -- - Range: 'Indent', 'Dedent', or 'Disabled'.     Disabled {manyLines :: [T.Text]}   | -- | Lines copied verbatim while a parser was in a @Haskell@ code block.     HaskellCode {manyLines :: [T.Text]}@@ -333,6 +345,10 @@     Text {someLines :: NonEmpty T.Text}   | -- | Lines copied verbatim while a parser was in a comment block.     Comment {someLines :: NonEmpty T.Text}+  | -- | A line of a comment that must be kept on a single-line.+    --+    -- E.g., {- FOURMOLU_DISABLE -} from a @.hs@.+    CommentSingleLine {someLine :: T.Text}   deriving (Show, Data, Eq)  -- | A list of 'Token's.@@ -353,6 +369,66 @@       )         <> "\n]" +-- | Select a printer function based on a given format.+selectFromTokens :: Config User -> Format -> Tokens -> T.Text+selectFromTokens config format =+  ( case format of+      Hs -> hsFromTokens+      Lhs -> lhsFromTokens+      Md -> mdFromTokens+      TeX -> texFromTokens+  )+    config++-- | Select a parser function based on a given format.+selectToTokens :: Config User -> Format -> T.Text -> Tokens+selectToTokens config format =+  ( case format of+      Hs -> hsToTokens+      Lhs -> lhsToTokens+      Md -> mdToTokens+      TeX -> texToTokens+  )+    config++-- | Example non-@TeX@ 'Tokens'. See 'exampleTexTokens'.+--+-- When printed to a @TeX@ document, these 'Tokens' can't be correctly parsed.+-- This is because they don't have necessary tags surrounding @Haskell@ code blocks.+--+-- >>> pp $ exampleNonTexTokens'+-- [+--   Indent {n = 3},+--   Disabled {manyLines = ["-- What's the answer?"]},+--   Indent {n = 1},+--   Indent {n = 2},+--   Text {someLines = "- Intermediate results" :| []},+--   HaskellCode {manyLines = ["   b = a 4","   a = const 3"]},+--   Dedent,+--   HaskellCode {manyLines = ["answer = b * 14"]},+--   Comment {someLines = "Hello from comments," :| []},+--   Comment {someLines = "world!" :| []},+--   CommentSingleLine {someLine = "Comment on a single line."},+--   Text {someLines = "Hello from text," :| []},+--   Text {someLines = "world!" :| []}+-- ]+exampleNonTexTokens' :: Tokens+exampleNonTexTokens' =+  [ Indent 3+  , Disabled{manyLines = ["-- What's the answer?"]}+  , Indent 1+  , Indent 2+  , Text ("- Intermediate results" :| [])+  , HaskellCode ["   b = a 4", "   a = const 3"]+  , Dedent+  , HaskellCode ["answer = b * 14"]+  , Comment ("Hello from comments," :| [])+  , Comment ("world!" :| [])+  , CommentSingleLine ("Comment on a single line.")+  , Text ("Hello from text," :| [])+  , Text ("world!" :| [])+  ]+ -- | Merge specific consecutive 'Tokens'. -- -- >>> pp exampleNonTexTokens'@@ -367,8 +443,9 @@ --   HaskellCode {manyLines = ["answer = b * 14"]}, --   Comment {someLines = "Hello from comments," :| []}, --   Comment {someLines = "world!" :| []},---   Text {someLines = "world!" :| ["Hello from text,"]},---   Text {someLines = "here!" :| ["And from"]}+--   CommentSingleLine {someLine = "Comment on a single line."},+--   Text {someLines = "Hello from text," :| []},+--   Text {someLines = "world!" :| []} -- ] -- -- >>> pp $ mergeTokens exampleNonTexTokens'@@ -382,20 +459,22 @@ --   Dedent, --   HaskellCode {manyLines = ["answer = b * 14"]}, --   Comment {someLines = "world!" :| ["","Hello from comments,"]},---   Text {someLines = "here!" :| ["And from","","world!","Hello from text,"]}+--   CommentSingleLine {someLine = "Comment on a single line."},+--   Text {someLines = "world!" :| ["","Hello from text,"]} -- ] mergeTokens :: Tokens -> Tokens mergeTokens (t1@Text{} : t2@Text{} : ts) = mergeTokens $ Text{someLines = someLines t2 <> (T.empty <| someLines t1)} : ts-mergeTokens (t1@Comment{} : t2@Comment{} : ts) = mergeTokens $ Comment{someLines = someLines t2 <> (T.empty <| someLines t1)} : ts+mergeTokens (Comment{someLines = ls1} : Comment{someLines = ls2} : ts) =+  mergeTokens $ Comment{someLines = ls2 <> (T.empty <| ls1)} : ts mergeTokens (t : ts) = t : mergeTokens ts mergeTokens ts = ts --- | Example non-@TeX@ 'Tokens'. See 'exampleTexTokens'.+-- | Strip empty lines and leading spaces in 'Tokens'. ----- When printed to a @TeX@ document, these 'Tokens' can't be correctly parsed.--- This is because they don't have necessary tags surrounding @Haskell@ code blocks.+-- - Remove empty lines in 'Tokens'.+-- - Shift lines in 'HaskellCode' to the left by the minimal number of leading spaces in nonempty lines. ----- >>> pp $ exampleNonTexTokens'+-- >>> pp exampleNonTexTokens' -- [ --   Indent {n = 3}, --   Disabled {manyLines = ["-- What's the answer?"]},@@ -407,25 +486,59 @@ --   HaskellCode {manyLines = ["answer = b * 14"]}, --   Comment {someLines = "Hello from comments," :| []}, --   Comment {someLines = "world!" :| []},---   Text {someLines = "world!" :| ["Hello from text,"]},---   Text {someLines = "here!" :| ["And from"]}+--   CommentSingleLine {someLine = "Comment on a single line."},+--   Text {someLines = "Hello from text," :| []},+--   Text {someLines = "world!" :| []} -- ]-exampleNonTexTokens' :: Tokens-exampleNonTexTokens' =-  [ Indent 3-  , Disabled{manyLines = ["-- What's the answer?"]}-  , Indent 1-  , Indent 2-  , Text ("- Intermediate results" :| [])-  , HaskellCode ["   b = a 4", "   a = const 3"]-  , Dedent-  , HaskellCode ["answer = b * 14"]-  , Comment ("Hello from comments," :| [])-  , Comment ("world!" :| [])-  , Text ("world!" :| ["Hello from text,"])-  , Text ("here!" :| ["And from"])-  ]+--+-- >>> pp $ stripTokens exampleNonTexTokens'+-- [+--   Indent {n = 3},+--   Disabled {manyLines = ["-- What's the answer?"]},+--   Indent {n = 1},+--   Indent {n = 2},+--   Text {someLines = "- Intermediate results" :| []},+--   HaskellCode {manyLines = ["b = a 4","a = const 3"]},+--   Dedent,+--   HaskellCode {manyLines = ["answer = b * 14"]},+--   Comment {someLines = "Hello from comments," :| []},+--   Comment {someLines = "world!" :| []},+--   CommentSingleLine {someLine = "Comment on a single line."},+--   Text {someLines = "Hello from text," :| []},+--   Text {someLines = "world!" :| []}+-- ]+stripTokens :: Tokens -> Tokens+stripTokens xs =+  ( \case+      Disabled{..} -> Disabled{manyLines = stripEmpties manyLines}+      HaskellCode{..} ->+        let ls = stripEmpties manyLines+         in HaskellCode{manyLines = T.drop (minimum (countSpaces <$> filter (not . T.null) ls)) <$> ls}+      Text{..} -> Text{someLines = fromList $ stripEmpties (toList someLines)}+      Comment{..} -> Comment{someLines = fromList $ stripEmpties (toList someLines)}+      x -> x+  )+    <$> xs +-- | 'mergeTokens' and 'stripTokens'.+--+-- >>>pp $ normalizeTokens exampleNonTexTokens+-- [+--   Indent {n = 3},+--   Disabled {manyLines = ["-- What's the answer?"]},+--   Indent {n = 1},+--   Indent {n = 2},+--   Text {someLines = "- Intermediate results" :| []},+--   HaskellCode {manyLines = ["b = a 4","a = const 3"]},+--   Dedent,+--   HaskellCode {manyLines = ["answer = b * 14"]},+--   Comment {someLines = "world!" :| ["","Hello from comments,"]},+--   CommentSingleLine {someLine = "Comment on a single line."},+--   Text {someLines = "world!" :| ["","Hello from text,"]}+-- ]+normalizeTokens :: Tokens -> Tokens+normalizeTokens tokens = stripTokens $ mergeTokens $ tokens+ -- | Normalized 'exampleNonTexTokens''. -- -- >>>pp $ exampleNonTexTokens@@ -439,69 +552,54 @@ --   Dedent, --   HaskellCode {manyLines = ["answer = b * 14"]}, --   Comment {someLines = "world!" :| ["","Hello from comments,"]},---   Text {someLines = "here!" :| ["And from","","world!","Hello from text,"]}+--   CommentSingleLine {someLine = "Comment on a single line."},+--   Text {someLines = "world!" :| ["","Hello from text,"]} -- ] exampleNonTexTokens :: Tokens exampleNonTexTokens = normalizeTokens exampleNonTexTokens' --- | Select a printer function based on a given format.-selectFromTokens :: Config User -> Format -> Tokens -> T.Text-selectFromTokens config format =-  ( case format of-      Hs -> hsFromTokens-      Lhs -> lhsFromTokens-      Md -> mdFromTokens-      TeX -> texFromTokens-  )-    config---- | Select a parser function based on a given format.-selectToTokens :: Config User -> Format -> T.Text -> Tokens-selectToTokens config format =-  ( case format of-      Hs -> hsToTokens-      Lhs -> lhsToTokens-      Md -> mdToTokens-      TeX -> texToTokens-  )-    config+-- | same as 'exampleNonTexTokens', but with @TeX@-specific tags that make @Haskell@ code blocks correctly parsable.+--+-- >>> pp $ exampleTexTokens+-- [+--   Indent {n = 3},+--   Disabled {manyLines = ["-- What's the answer?"]},+--   Indent {n = 1},+--   Indent {n = 2},+--   Text {someLines = "\\begin{code}" :| ["","Intermediate results"]},+--   HaskellCode {manyLines = ["b = a 4","a = const 3"]},+--   Text {someLines = "\\end{code}" :| []},+--   Dedent,+--   Text {someLines = "\\begin{code}" :| []},+--   HaskellCode {manyLines = ["answer = b * 14"]},+--   Text {someLines = "\\end{code}" :| []},+--   Comment {someLines = "world!" :| ["","Hello from comments,"]},+--   CommentSingleLine {someLine = "Comment on a single line."}+-- ]+exampleTexTokens :: Tokens+exampleTexTokens =+  normalizeTokens $+    [ Indent 3+    , Disabled{manyLines = ["-- What's the answer?"]}+    , Indent 1+    , Indent 2+    , Text{someLines = "Intermediate results" :| []}+    , Text{someLines = "\\begin{code}" :| []}+    , HaskellCode ["   b = a 4", "   a = const 3"]+    , Text{someLines = "\\end{code}" :| []}+    , Dedent+    , Text{someLines = "\\begin{code}" :| []}+    , HaskellCode ["answer = b * 14"]+    , Text{someLines = "\\end{code}" :| []}+    , Comment ("Hello from comments," :| [])+    , Comment ("world!" :| [])+    , CommentSingleLine ("Comment on a single line.")+    ]  -- | Compose a function that converts a document in one 'Format' to a document in another 'Format'. convertTo :: Format -> Format -> Config User -> T.Text -> T.Text convertTo a b config src = selectFromTokens config b $ selectToTokens config a src --- | Escaped hash character-escapedHash :: T.Text-escapedHash = "\\#"---- | Hash character-hash :: T.Text-hash = "#"---- | Drop a prefix of a line with length of a given line-dropLen :: T.Text -> T.Text -> T.Text-dropLen x y = T.drop (T.length x) y---- | Check if a list starts with a given list-startsWith :: T.Text -> T.Text -> Bool-startsWith = flip T.isPrefixOf---- | Check if a list ends with a given list-endsWith :: T.Text -> T.Text -> Bool-endsWith = flip T.isSuffixOf---- | Drop leading spaces and drop at each end of a 'T.Text' the number of characters as in the supplied prefix and suffix.-stripEnds :: T.Text -> T.Text -> T.Text -> T.Text-stripEnds prefix suffix x = T.dropEnd (T.length suffix) (dropLen prefix (stripSpaces x))---- | Replace "\\#" with "#" in a 'T.Text' prefix.-lhsUnescapeHash :: T.Text -> T.Text-lhsUnescapeHash x = if x `startsWith` escapedHash then hash <> (dropLen escapedHash x) else x---- | Replace "#" with "\\#" in a 'T.Text' prefix.-lhsEscapeHash :: T.Text -> T.Text-lhsEscapeHash x = if x `startsWith` hash then escapedHash <> (dropLen hash x) else x- -- | State of a parser. -- -- Only one flag can be enabled when processing a line.@@ -525,39 +623,30 @@       , inComment = False       } --- | 'mergeTokens' and 'stripTokens'.------ >>>pp $ normalizeTokens exampleNonTexTokens--- [---   Indent {n = 3},---   Disabled {manyLines = ["-- What's the answer?"]},---   Indent {n = 1},---   Indent {n = 2},---   Text {someLines = "- Intermediate results" :| []},---   HaskellCode {manyLines = ["b = a 4","a = const 3"]},---   Dedent,---   HaskellCode {manyLines = ["answer = b * 14"]},---   Comment {someLines = "world!" :| ["","Hello from comments,"]},---   Text {someLines = "here!" :| ["And from","","world!","Hello from text,"]}--- ]-normalizeTokens :: Tokens -> Tokens-normalizeTokens tokens = stripTokens $ mergeTokens $ tokens+-- | Compose a function from 'Tokens' to a 'T.Text'.+mkFromTokens :: (Config User -> Tokens -> [T.Text]) -> Config User -> Tokens -> T.Text+mkFromTokens f' config = (<> "\n") . T.intercalate "\n" . f' config  -- | Compose a function from a 'T.Text' to 'Tokens'.-mkIntoTokens :: (State -> [(Int, T.Text)] -> [Token] -> [Token]) -> T.Text -> Tokens-mkIntoTokens toTokens xs = normalizeTokens (drop 1 $ reverse $ toTokens def (zip [1 ..] (T.lines xs)) [Dedent])+mkToTokens :: (State -> [(Int, T.Text)] -> [Token] -> [Token]) -> T.Text -> Tokens+mkToTokens toTokens xs = normalizeTokens (drop 1 $ reverse $ toTokens def (zip [1 ..] (T.lines xs)) [Dedent]) --- | Parse to a token contents of a multiline comment written on a single line.+-- | Parse contents of a single line to a token. ----- Merge consecutive 'Comment's-parseToken :: Config Internal -> Token -> T.Text -> Int -> Tokens-parseToken Config{..} prev l lineNumber+-- - Merge comments+parseLineToToken :: Config Internal -> Format -> Token -> T.Text -> Int -> Tokens+parseLineToToken Config{..} format prev l lineNumber   | l `startsWith` _indent =       maybe-        (error $ "Expected a number at line: " <> show lineNumber)+        (error $ "Expected a number after " <> T.unpack _indent <> " at line: " <> show lineNumber)         (\x -> [Indent (max 0 x), prev])         (readMaybe @Int (T.unpack (dropLen _indent l)))   | l == _dedent = [Dedent, prev]+  | format `elem` [Md, Hs] = [CommentSingleLine{someLine = l}, prev]+  | format == TeX && l `startsWith` _texSingleLineCommentStart =+      [CommentSingleLine{someLine = dropLen _texSingleLineCommentStart l}, prev]+  | format == Lhs && l `startsWith` _lhsSingleLineCommentStart =+      [CommentSingleLine{someLine = dropLen _lhsSingleLineCommentStart l}, prev]   | otherwise =       case prev of         Comment{..} -> [Comment{someLines = l <| someLines}]@@ -572,67 +661,27 @@       <> ("Expected last token: " <> constructorName expectedToken <> "\n\n")       <> ("Got last token: " <> show lastToken <> "\n\n") --- | Strip the given value from the beginning and the end of a list.-stripList :: Eq a => a -> [a] -> [a]-stripList x = dropWhileEnd (== x) . dropWhile (== x)---- | Pad a 'T.Text' with a given number of spaces-indentN :: Int -> T.Text -> T.Text-indentN x s = T.concat (replicate x " ") <> s---- | Compose a function from 'Tokens' to a 'T.Text'.-mkFromTokens :: (Config User -> Tokens -> [T.Text]) -> Config User -> Tokens -> T.Text-mkFromTokens f' config = (<> "\n") . T.intercalate "\n" . f' config---- | same as 'exampleNonTexTokens', but with @TeX@-specific tags that make @Haskell@ code blocks correctly parsable.------ >>> pp $ exampleTexTokens--- [---   Disabled {manyLines = ["-- What's the answer?"]},---   Indent {n = 1},---   Indent {n = 2},---   Text {someLines = "\\begin{code}" :| ["","Intermediate results"]},---   HaskellCode {manyLines = ["b = a 4","a = const 3"]},---   Text {someLines = "\\end{code}" :| []},---   Dedent,---   Text {someLines = "\\begin{code}" :| []},---   HaskellCode {manyLines = ["answer = b * 14"]},---   Text {someLines = "\\end{code}" :| []},---   Comment {someLines = "world!" :| ["","Hello from comments,"]},---   Text {someLines = "world!" :| ["Hello from text,"]}--- ]-exampleTexTokens :: Tokens-exampleTexTokens =-  normalizeTokens $-    [ Disabled{manyLines = ["-- What's the answer?"]}-    , Indent 1-    , Indent 2-    , Text{someLines = "Intermediate results" :| []}-    , Text{someLines = "\\begin{code}" :| []}-    , HaskellCode ["   b = a 4", "   a = const 3"]-    , Text{someLines = "\\end{code}" :| []}-    , Dedent-    , Text{someLines = "\\begin{code}" :| []}-    , HaskellCode ["answer = b * 14"]-    , Text{someLines = "\\end{code}" :| []}-    , Comment ("Hello from comments," :| [])-    , Comment ("world!" :| [])-    , Text{someLines = "world!" :| ["Hello from text,"]}-    ]+errorNotEnoughTokens :: Format -> a+errorNotEnoughTokens format = error $ "Got not enough tokens when converting 'Tokens' to " <> showFormatName format  -- | Convert 'Tokens' to @TeX@ code. -- -- __Rules__ -- -- - Certain [assumptions]("Converter#assumptions") must hold for inputs.--- - These are the relations between document blocks and tokens when the default 'Config' values are used.+-- - These are the relations between tokens and document blocks when the default 'Config' values are used. -----     - @'% LIMA_INDENT N'@ (@N@ is an 'Int') ~ 'Indent'---     - @'% LIMA_DEDENT'@ ~ 'Dedent'.---     - Lines between and including @'% LIMA_DISABLE'@ and @'% LIMA_ENABLE'@ ~ 'Disabled'.+--     - 'Indent' ~ @'% LIMA_INDENT N'@ (@N@ is an 'Int').+--     - 'Dedent' ~ @'% LIMA_DEDENT'@.+--     - 'Disabled' ~ @'% LIMA_DISABLE'@ and @'% LIMA_ENABLE'@ and lines between them.+--     - 'CommentSingleLine' ~ a line starting with @'% SINGLE_LINE '@. -----     - Consecutive lines, either empty or starting with @'% '@ ~ 'Comment'.+--         @+--         % SINGLE_LINE line+--         @ --+--     - 'Comment' ~ consecutive lines, either empty or starting with @'% '@.+-- --         @ --         % Hello, --         % world!@@ -641,15 +690,20 @@ --         % user! --         @ -----         - At least one line must have nonempty text after @'% '@+--         - At least one line must have nonempty text after @'% '@. -----     - Lines between possibly indented tags @'\\begin{code}'@ and @'\\end{code}'@ ~ 'HaskellCode'.+--     - 'HaskellCode' ~ lines between possibly indented tags @'\\begin{code}'@ and @'\\end{code}'@. -----     - Other lines ~ 'Text'.+--         - Inside a 'Token', code will be shifted to the left. See 'normalizeTokens'.+--         - When printing the 'Tokens', code will be indented according to previous 'Tokens'. --+--     - 'Text' ~ other lines.+-- -- === __Example__ -- -- >>> pp $ texFromTokens def exampleTexTokens+-- % LIMA_INDENT 3+-- <BLANKLINE> -- % LIMA_DISABLE -- <BLANKLINE> -- % -- What's the answer?@@ -677,26 +731,17 @@ -- <BLANKLINE> -- % world! -- <BLANKLINE>--- Hello from text,--- world!+-- % SINGLE_LINE Comment on a single line. texFromTokens :: Config User -> Tokens -> T.Text texFromTokens = mkFromTokens texFromTokens' --- | Start a @TeX@ comment.-texComment :: T.Text-texComment = "%"---- | Start a @TeX@ comment plus a space.-texCommentSpace :: T.Text-texCommentSpace = texComment <> " "- -- | Convert 'Tokens' to @TeX@ code. -- -- Each 'Token' becomes a 'T.Text' in a list. -- -- These 'T.Text's are concatenated in 'texFromTokens'. texFromTokens' :: Config User -> Tokens -> [T.Text]-texFromTokens' (toInternalConfig -> Config{..}) tokens =+texFromTokens' (toConfigInternal -> Config{..}) tokens =   dropEmpties $ reverse $ (T.intercalate "\n" . reverse <$> (fromTokens (Dedent : tokens) (0, [])))  where   fromTokens :: Tokens -> (Int, [[T.Text]]) -> [[T.Text]]@@ -724,32 +769,9 @@                   _ -> [] : rs               )       Comment{someLines = t :| ts} -> (curIndent, (prependTexComment <$> (t : ts)) : [] : rs)+      CommentSingleLine{someLine} -> (curIndent, [prependTexComment $ _texSingleLineCommentStart <> someLine] : [] : rs)   translate _ _ _ = errorNotEnoughTokens TeX -errorNotEnoughTokens :: Format -> a-errorNotEnoughTokens format = error $ "Got not enough tokens when converting 'Tokens' to " <> showFormatName format---- | Drop spaces at the start and the end of a 'T.Text'.-stripSpaces :: T.Text -> T.Text-stripSpaces = T.strip---- | Prepend start of a @TeX@ comment (@'% '@) to a 'T.Text'.-prependTexComment :: T.Text -> T.Text-prependTexComment l-  | l == T.empty = l-  | otherwise = texCommentSpace <> l---- | Drop start of a @TeX@ comment from a 'T.Text'.-dropTexComment :: Show a => T.Text -> a -> T.Text-dropTexComment l lineNumber-  | l `startsWith` texCommentSpace = dropLen texCommentSpace l-  | l == T.empty = l-  | otherwise =-      error $-        "Lines in a 'Disabled' block must either be empty or start with '% '\n\n"-          <> "Note that each 'Disabled' block must have at least one line starting with '% ' and having nonempty text after '% ' "-          <> ("The line " <> show lineNumber <> " must either be empty or start with '% '")- -- | Convert 'Tokens' to @TeX@ code. -- -- Inverse of 'texFromTokens'.@@ -757,9 +779,9 @@ -- >>> (texToTokens def $ texFromTokens def exampleTexTokens) == exampleTexTokens -- True texToTokens :: Config User -> T.Text -> Tokens-texToTokens (toInternalConfig -> conf@Config{..}) xs = tokens+texToTokens (toConfigInternal -> conf@Config{..}) xs = tokens  where-  tokens = mkIntoTokens toTokens xs+  tokens = mkToTokens toTokens xs   toTokens :: State -> [(Int, T.Text)] -> Tokens -> Tokens   toTokens State{..} ((lineNumber, l) : ls) result@(r : rs)     | inDisabled =@@ -789,7 +811,7 @@             : case r of               Text{..} -> Text{someLines = l <| someLines} : rs               _ -> Text{someLines = l :| []} : result-    | -- comment on a single line+    | -- Comment on a single line.       l `startsWith` texCommentSpace =         let l' = dropLen texCommentSpace l          in if@@ -798,7 +820,7 @@                     toTokens def{inDisabled = True} ls (Disabled [] : result)                 | otherwise ->                     toTokens def ls $-                      parseToken conf r l' lineNumber <> rs+                      parseLineToToken conf TeX r l' lineNumber <> rs     | inText =         toTokens def{inText} ls $           case r of@@ -824,15 +846,21 @@ -- -- - These are the relations between document blocks and tokens when the default 'Config' values are used. -----     - @'% LIMA_INDENT N'@ (@N@ is an 'Int') ~ 'Indent'.---     - @'% LIMA_DEDENT'@ ~ 'Dedent'.---     - Lines between and including @'% LIMA_DISABLE'@ and @'% LIMA_ENABLE'@ ~ 'Disabled'.+--     - 'Indent' ~ @'% LIMA_INDENT N'@ (@N@ is an 'Int').+--     - 'Dedent' ~ @'% LIMA_DEDENT'@.+--     - 'Disabled' ~ Lines between and including @'% LIMA_DISABLE'@ and @'% LIMA_ENABLE'@. -- --         - There must be at least one nonempty line between these tags. -----     - Consecutive lines, either empty or starting with @'% '@ ~ 'Comment'.+--     - 'CommentSingleLine' ~ a line starting with @'% SINGLE_LINE '@. -- --         @+--         % SINGLE_LINE line+--         @+--+--     - 'Comment' ~ consecutive lines, either empty or starting with @'% '@.+--+--         @ --         % Hello, --         % world! --@@ -842,15 +870,18 @@ -- --         - At least one line must have nonempty text after @'% '@ -----     - Consecutive lines starting with @'> '@ ~ 'HaskellCode'.+--     - 'HaskellCode' ~ consecutive lines starting with @'> '@. -- --         @ --         > a4 = 4 --         > a2 = 2 --         @ -----     - Other lines ~ 'Text'.+--         - Inside a 'Token', code is shifted to the left. See 'normalizeTokens'.+--         - During printing, code is indented according to previous 'Tokens'. --+--     - 'Text' ~ other lines.+-- -- === __Example__ -- -- >>> pp $ lhsFromTokens def exampleNonTexTokens@@ -878,43 +909,21 @@ -- <BLANKLINE> -- % world! -- <BLANKLINE>+-- % SINGLE_LINE Comment on a single line.+-- <BLANKLINE> -- Hello from text,--- world! -- <BLANKLINE>--- And from--- here!+-- world! lhsFromTokens :: Config User -> Tokens -> T.Text lhsFromTokens config tokens = mkFromTokens lhsFromTokens' config tokens --- | Start a @Literate Haskell@ comment.-lhsComment :: T.Text-lhsComment = "%"---- | Start a @Literate Haskell@ comment plus a space.-lhsCommentSpace :: T.Text-lhsCommentSpace = lhsComment <> " "---- | Start a @Literate Haskell@ line of @Haskell@ code.-lhsHsCode :: T.Text-lhsHsCode = ">"---- | Start a @Literate Haskell@ line of @Haskell@ code plus a space.-lhsHsCodeSpace :: T.Text-lhsHsCodeSpace = lhsHsCode <> " "---- | Prepend start of a @TeX@ comment (@'% '@) to a 'T.Text'.-prependLhsComment :: T.Text -> T.Text-prependLhsComment l-  | l == T.empty = l-  | otherwise = texCommentSpace <> l- -- | Convert 'Tokens' to @Literate Haskell@ code. -- -- Each 'Token' becomes a 'T.Text' in a list. -- -- These 'T.Text's are concatenated in 'lhsFromTokens'. lhsFromTokens' :: Config User -> Tokens -> [T.Text]-lhsFromTokens' (toInternalConfig -> Config{..}) blocks =+lhsFromTokens' (toConfigInternal -> Config{..}) blocks =   dropEmpties $ reverse (T.intercalate "\n" . reverse <$> (fromTokens (Dedent : blocks) (0, [])))  where   fromTokens :: Tokens -> (Int, [[T.Text]]) -> [[T.Text]]@@ -942,15 +951,9 @@                   _ -> [] : rs               )       Comment{someLines = t :| ts} -> (curIndent, (prependLhsComment <$> t : ts) : [] : rs)+      CommentSingleLine{someLine} -> (curIndent, [prependLhsComment $ _lhsSingleLineCommentStart <> someLine] : [] : rs)   translate _ _ _ = errorNotEnoughTokens Lhs --- | Drop start of a @TeX@ comment from a 'T.Text'.-dropLhsComment :: Show a => T.Text -> a -> T.Text-dropLhsComment l lineNumber-  | l `startsWith` lhsCommentSpace = dropLen lhsCommentSpace l-  | l == T.empty = l-  | otherwise = error $ "The line " <> show lineNumber <> " must either be empty or start with '% '"- -- | Convert 'Tokens' to @Markdown@ code. -- -- Inverse of 'lhsFromTokens'.@@ -958,9 +961,9 @@ -- >>> (lhsToTokens def $ lhsFromTokens def exampleNonTexTokens) == exampleNonTexTokens -- True lhsToTokens :: Config User -> T.Text -> Tokens-lhsToTokens (toInternalConfig -> conf@Config{..}) xs = tokens+lhsToTokens (toConfigInternal -> conf@Config{..}) xs = tokens  where-  tokens = mkIntoTokens toTokens xs+  tokens = mkToTokens toTokens xs   toTokens :: State -> [(Int, T.Text)] -> Tokens -> Tokens   toTokens State{..} ((lineNumber, lhsUnescapeHash -> l) : ls) result@(r : rs)     | inDisabled =@@ -974,7 +977,7 @@                   case r of                     Disabled{..} -> (r{manyLines = dropLhsComment l lineNumber : manyLines} : rs)                     _ -> errorExpected Disabled{}-    | -- comment on a single line+    | -- Comment on a single line.       l `startsWith` lhsCommentSpace =         let l' = dropLen lhsCommentSpace l          in if@@ -983,7 +986,7 @@                     toTokens def{inDisabled = True} ls (Disabled [] : result)                 | otherwise ->                     toTokens def ls $-                      parseToken conf r l' lineNumber <> rs+                      parseLineToToken conf Lhs r l' lineNumber <> rs     | -- start of a snippet       l `startsWith` lhsHsCodeSpace =         toTokens def{inHaskellCode = True} ls $@@ -1016,11 +1019,11 @@ -- -- - These are the relations between document blocks and tokens when the default 'Config' values are used. -----     - @'<!-- LIMA_INDENT N --\>'@ (@N@ is an 'Int') ~ 'Indent'---     - @'<!-- LIMA_DEDENT --\>'@ ~ 'Dedent'.---     - Multiline comment+--     - 'Indent' ~ @'<!-- LIMA_INDENT N --\>'@, where @N@ is an 'Int'.+--     - 'Dedent' ~ @'<!-- LIMA_DEDENT --\>'@.+--     - 'Disabled' ~ a multiline comment --       starting with @'<!-- LIMA_DISABLE\\n'@---       and ending with @'\\nLIMA_ENABLE --\>'@  ~ 'Disabled'.+--       and ending with @'\\nLIMA_ENABLE --\>'@. -- --         @ --         <!-- LIMA_DISABLE@@ -1029,9 +1032,15 @@ --         LIMA_ENABLE --\> --         @ -----     - Multiline comments starting with @'<!-- {text}'@ where @{text}@ is nonempty text ~ 'Comment'.+--     - 'CommentSingleLine' ~ a line starting with @'<!-- '@ and ending with @' -->'@. -- --         @+--         <!-- line -->+--         @+--+--     - 'Comment' ~ a multiline comment starting with @'<!-- {text}'@, where @{text}@ is nonempty text.+--+--         @ --         <!-- line 1 --         line 2 --         --\>@@ -1039,7 +1048,7 @@ -- --         - Consecutive 'Comment's are merged into a single 'Comment'. -----     - Possibly indented block starting with @\'```haskell\'@ and ending with @'```'@ ~ 'HaskellCode'.+--     - 'HaskellCode' ~ possibly indented block starting with @\'```haskell\'@ and ending with @'```'@. -- --         @ --           ```haskell@@ -1047,11 +1056,7 @@ --           ``` --         @ -----     - Other lines ~ 'Text'.------         @---         Hello, world!---         @+--     - 'Text' ~ other lines. -- -- === __Example__ --@@ -1086,41 +1091,21 @@ -- world! -- --> -- <BLANKLINE>+-- <!-- Comment on a single line. -->+-- <BLANKLINE> -- Hello from text,--- world! -- <BLANKLINE>--- And from--- here!+-- world! mdFromTokens :: Config User -> Tokens -> T.Text mdFromTokens = mkFromTokens mdFromTokens' --- | Open a @Markdown@ comment.-mdCommentOpen :: T.Text-mdCommentOpen = "<!--"---- | Close a @Markdown@ comment.-mdCommentClose :: T.Text-mdCommentClose = "-->"---- | Open a @Markdown@ comment plus a space.-mdCommentOpenSpace :: T.Text-mdCommentOpenSpace = mdCommentOpen <> " "---- | A space plus close a @Markdown@ comment.-mdCommentCloseSpace :: T.Text-mdCommentCloseSpace = " " <> mdCommentClose---- | Strip comment markers from a 'T.Text'.-stripMdComment :: T.Text -> T.Text-stripMdComment = stripEnds mdCommentOpenSpace mdCommentCloseSpace- -- | Convert 'Tokens' to @Haskell@ code. -- -- Each 'Token' becomes a 'T.Text' in a list. -- -- These 'T.Text's are concatenated in 'mdFromTokens'. mdFromTokens' :: Config User -> Tokens -> [T.Text]-mdFromTokens' (toInternalConfig -> Config{..}) blocks =+mdFromTokens' (toConfigInternal -> Config{..}) blocks =   intersperse T.empty . reverse $ T.intercalate "\n" . reverse <$> fromTokens 0 blocks []  where   fromTokens :: Int -> Tokens -> [[T.Text]] -> [[T.Text]]@@ -1132,81 +1117,12 @@       Disabled{..} -> fromTokens 0 bs ([[_enable <> mdCommentCloseSpace]] <> [manyLines] <> [[mdCommentOpenSpace <> _disable]] <> res)       HaskellCode{..} -> fromTokens curIndent bs ((indentN curIndent <$> ([_mdHaskellCodeEnd] <> manyLines <> [_mdHaskellCodeStart])) : res)       Text{..} -> fromTokens curIndent bs (toList someLines : res)-      Comment{someLines = t :| ts} ->-        let ts' = t : ts-         in fromTokens curIndent bs $ [mdCommentClose] <> init ts' <> [mdCommentOpenSpace <> last ts'] : res---- | Show the name of a constructor.-constructorName :: Data a => a -> String-constructorName x = showConstr (toConstr x)---- | Remove empty lines from the beginning and the end of a list.-stripEmpties :: [T.Text] -> [T.Text]-stripEmpties = stripList T.empty--dropEmpties :: [T.Text] -> [T.Text]-dropEmpties = dropWhile (== T.empty)---- | Check if a line without leading spaces is surrounded by the given 'T.Text's.-isEnclosedWith :: T.Text -> T.Text -> T.Text -> Bool-isEnclosedWith start end (stripSpaces -> x) = x `startsWith` start && x `endsWith` end---- | Check if a line is a @Markdown@ comment.-isMdComment :: T.Text -> Bool-isMdComment = isEnclosedWith mdCommentOpenSpace mdCommentCloseSpace---- | Count leading spaces in a 'T.Text'.-countSpaces :: T.Text -> Int-countSpaces x = T.length $ T.takeWhile (== ' ') x---- | Strip empty lines an leading spaces in 'Tokens'.------ - Remove empty lines in 'Tokens'.--- - Shift lines in 'HaskellCode' to the left by the minimal number of leading spaces in nonempty lines.------ >>> pp exampleNonTexTokens'--- [---   Indent {n = 3},---   Disabled {manyLines = ["-- What's the answer?"]},---   Indent {n = 1},---   Indent {n = 2},---   Text {someLines = "- Intermediate results" :| []},---   HaskellCode {manyLines = ["   b = a 4","   a = const 3"]},---   Dedent,---   HaskellCode {manyLines = ["answer = b * 14"]},---   Comment {someLines = "Hello from comments," :| []},---   Comment {someLines = "world!" :| []},---   Text {someLines = "world!" :| ["Hello from text,"]},---   Text {someLines = "here!" :| ["And from"]}--- ]------ >>> pp $ stripTokens exampleNonTexTokens'--- [---   Indent {n = 3},---   Disabled {manyLines = ["-- What's the answer?"]},---   Indent {n = 1},---   Indent {n = 2},---   Text {someLines = "- Intermediate results" :| []},---   HaskellCode {manyLines = ["b = a 4","a = const 3"]},---   Dedent,---   HaskellCode {manyLines = ["answer = b * 14"]},---   Comment {someLines = "Hello from comments," :| []},---   Comment {someLines = "world!" :| []},---   Text {someLines = "world!" :| ["Hello from text,"]},---   Text {someLines = "here!" :| ["And from"]}--- ]-stripTokens :: Tokens -> Tokens-stripTokens xs =-  ( \case-      Disabled{..} -> Disabled{manyLines = stripEmpties manyLines}-      HaskellCode{..} ->-        let ls = stripEmpties manyLines-         in HaskellCode{manyLines = T.drop (minimum (countSpaces <$> filter (not . T.null) ls)) <$> ls}-      Text{..} -> Text{someLines = fromList $ stripEmpties (toList someLines)}-      Comment{..} -> Comment{someLines = fromList $ stripEmpties (toList someLines)}-      x -> x-  )-    <$> xs+      Comment{someLines} ->+        fromTokens curIndent bs $+          [mdCommentClose] <> NonEmpty.init someLines <> [mdCommentOpenSpace <> NonEmpty.last someLines] : res+      CommentSingleLine{someLine} ->+        fromTokens curIndent bs $+          [mdCommentOpenSpace <> someLine <> mdCommentCloseSpace] : res  -- | Convert 'Tokens' to @Markdown@ code. --@@ -1215,9 +1131,9 @@ -- >>> (mdToTokens def $ mdFromTokens def exampleNonTexTokens) == exampleNonTexTokens -- True mdToTokens :: Config User -> T.Text -> Tokens-mdToTokens (toInternalConfig -> conf@Config{..}) xs = tokens+mdToTokens (toConfigInternal -> conf@Config{..}) xs = tokens  where-  tokens = mkIntoTokens toTokens xs+  tokens = mkToTokens toTokens xs   toTokens :: State -> [(Int, T.Text)] -> Tokens -> Tokens   toTokens State{..} ((lineNumber, l) : ls) res@(r : rs)     | inDisabled =@@ -1249,9 +1165,9 @@               _ -> errorExpected HaskellCode{}     -- Doesn't matter if in text -    | -- comment on a single line+    | -- Comment on a single line.       isMdComment l =-        toTokens def ls $ parseToken conf r (stripMdComment l) lineNumber <> rs+        toTokens def ls $ parseLineToToken conf Md r (stripMdComment l) lineNumber <> rs     | -- start of a comment on multiple lines       l `startsWith` mdCommentOpenSpace =         let l' = dropLen mdCommentOpenSpace l@@ -1289,15 +1205,23 @@ -- -- - Certain [assumptions]("Converter#assumptions") must hold for inputs. ----- - These are the relations between document blocks and tokens when the default 'Config' values are used.+-- - These are the relations between 'Tokens' and document blocks when the default 'Config' values are used. -----     - @'{- LIMA_INDENT N -}'@ (@N@ is an 'Int') ~ 'Indent'.---     - @'{- LIMA_DEDENT -}'@ ~ 'Dedent'.---     - Lines between and including @'{- LIMA_DISABLE -}'@ and @'{- LIMA_ENABLE -}'@ ~ 'Disabled'.+--     - 'Indent' ~ @'{- LIMA_INDENT N -}'@ where @N@ is an 'Int'.+--     - 'Dedent' ~ @'{- LIMA_DEDENT -}'@.+--     - 'Disabled' ~ @'{- LIMA_DISABLE -}'@ and @'{- LIMA_ENABLE -}'@ and lines between them. -----     - Multiline comment starting with @'{-\\n'@ ~ 'Text'.+--         @+--         {- LIMA_DISABLE -} --+--         disabled+--+--         {- LIMA_ENABLE -} --         @+--+--     - 'Text' ~ a multiline comment starting with @'{-\\n'@ and ending with @'\\n-}'@.+--+--         @ --         {- --         line 1 --         -}@@ -1306,9 +1230,15 @@ --         - Consecutive 'Text's are merged into a single 'Text'. --         - There must be at list one nonempty line inside this comment. -----     - Multiline comment starting with @'{- '@ where @<text>@ is nonempty text ~ 'Comment'.+--     - 'CommentSingleLine' ~ a multiline comment on a single line. -- --         @+--         {- line -}+--         @+--+--     - 'Comment' ~ a multiline comment starting with @'{- TEXT'@, where @TEXT@ is nonempty text, and ending with @\\n-}@+--+--         @ --         {- line 1 --         line 2 --         -}@@ -1316,11 +1246,7 @@ -- --         - Consecutive 'Comment's are merged into a single 'Comment'. -----     - Other lines ~ 'HaskellCode'.------         @---         a = 42---         @+--     - 'HaskellCode' ~ other lines. -- -- === __Example__ --@@ -1353,39 +1279,23 @@ -- world! -- -} -- <BLANKLINE>+-- {- Comment on a single line. -}+-- <BLANKLINE> -- {- -- Hello from text,--- world! -- <BLANKLINE>--- And from--- here!+-- world! -- -} hsFromTokens :: Config User -> Tokens -> T.Text hsFromTokens = mkFromTokens hsFromTokens' --- | Open a @Haskell@ multi-line comment.-hsCommentOpen :: T.Text-hsCommentOpen = "{-"---- | Open a @Haskell@ multi-line comment plus a space.-hsCommentOpenSpace :: T.Text-hsCommentOpenSpace = hsCommentOpen <> " "---- | Close a @Haskell@ multi-line comment.-hsCommentClose :: T.Text-hsCommentClose = "-}"---- | A space plus close a @Haskell@ multi-line comment.-hsCommentCloseSpace :: T.Text-hsCommentCloseSpace = " " <> hsCommentClose- -- | Convert 'Tokens' to @Haskell@ code. -- -- Each 'Token' becomes a 'T.Text' in a list. -- -- These 'T.Text's are concatenated in 'hsFromTokens'. hsFromTokens' :: Config User -> Tokens -> [T.Text]-hsFromTokens' (toInternalConfig -> Config{..}) blocks =+hsFromTokens' (toConfigInternal -> Config{..}) blocks =   intersperse T.empty . reverse $ T.intercalate "\n" . reverse <$> toHs blocks []  where   toHs :: Tokens -> [[T.Text]] -> [[T.Text]]@@ -1402,26 +1312,10 @@             <> res         HaskellCode{..} -> manyLines : res         Text{..} -> [hsCommentClose] <> toList someLines <> [hsCommentOpen] : res-        Comment{someLines = t :| ts} ->-          let ts' = t : ts-           in [hsCommentClose] <> init ts' <> [hsCommentOpenSpace <> last ts'] : res---- | Drop leading spaces and drop at each end of a 'T.Text' the number of characters as in the supplied prefix and suffix.-stripHsComment :: T.Text -> T.Text-stripHsComment = stripEnds hsCommentOpenSpace hsCommentCloseSpace---- | Check if a line without leading zeros is a multi-line @Haskell@ comment-isHsComment :: T.Text -> Bool-isHsComment = isEnclosedWith hsCommentOpenSpace hsCommentCloseSpace---- | Show error with line number for a token.--- errorEmptyCommentAt :: Int -> String-errorEmptyCommentAt :: Show a1 => a1 -> a2-errorEmptyCommentAt lineNumber =-  error $-    ("Expected a 'Comment' at line " <> show lineNumber <> ".\n\n")-      <> "However, there are no characters after '{- '.\n\n"-      <> "Please, write there something after '{- '."+        Comment{someLines} ->+          [hsCommentClose] <> NonEmpty.init someLines <> [hsCommentOpenSpace <> NonEmpty.last someLines] : res+        CommentSingleLine{someLine} ->+          [hsCommentOpenSpace <> someLine <> hsCommentCloseSpace] : res  -- | Convert 'Tokens' to @Haskell@ code. --@@ -1430,9 +1324,9 @@ -- >>> (hsToTokens def $ hsFromTokens def exampleNonTexTokens) == exampleNonTexTokens -- True hsToTokens :: Config User -> T.Text -> Tokens-hsToTokens (toInternalConfig -> conf@Config{..}) xs = tokens+hsToTokens (toConfigInternal -> conf@Config{..}) xs = tokens  where-  tokens = mkIntoTokens toTokens xs+  tokens = mkToTokens toTokens xs   toTokens :: State -> [(Int, T.Text)] -> Tokens -> Tokens   toTokens State{..} ((lineNumber, l) : ls) res@(r : rs)     | inText =@@ -1486,13 +1380,12 @@     | -- start of text       l == hsCommentOpen =         toTokens def{inText = True} ls (Text{someLines = T.empty :| []} : res)-    | -- comment on a single line+    | -- Comment on a single line.       isHsComment l =         let l' = stripHsComment l          in if                 | l' `startsWith` _disable -> toTokens def{inDisabled = True} ls (Disabled [] : res)-                -- \| null l' -> error-                | otherwise -> toTokens def ls $ parseToken conf r l' lineNumber <> rs+                | otherwise -> toTokens def ls $ parseLineToToken conf Hs r l' lineNumber <> rs     | -- start of a comment on multiple lines       l `startsWith` hsCommentOpenSpace =         let l' = dropLen hsCommentOpenSpace l@@ -1516,25 +1409,3 @@    where     errorExpected = error . errorExpectedToken lineNumber r   toTokens _ _ res = res---- | Show a 'Format' as a file extension.------ >>>showFormatExtension Lhs--- "lhs"-showFormatExtension :: Format -> String-showFormatExtension = \case-  Hs -> "hs"-  Md -> "md"-  Lhs -> "lhs"-  TeX -> "tex"---- | Show a 'Format' as a full name.------ >>>showFormatName Lhs--- "Literate Haskell"-showFormatName :: Format -> String-showFormatName = \case-  Hs -> "Haskell"-  Md -> "Markdown"-  Lhs -> "Literate Haskell"-  TeX -> "TeX"
+ src/Converter/Internal.hs view
@@ -0,0 +1,217 @@+module Converter.Internal where++import Data.Data (Data (toConstr), showConstr)+import Data.List (dropWhileEnd)+import Data.Text qualified as T++-- | A wrapper for prettyprinting strings+newtype Pretty a = Pretty String++instance Show a => Show (Pretty a) where+  show :: Pretty a -> String+  show (Pretty s) = s++-- | A class for prettyprinting data on multiple lines in haddocks.+--+-- It's not meant to be used outside of this library.+class Show a => PrettyPrint a where+  -- | A printing function+  --+  -- It's not meant to be used outside of this library.+  pp :: a -> Pretty String++instance PrettyPrint String where+  pp :: String -> Pretty String+  pp = Pretty . dropWhileEnd (== '\n')++instance PrettyPrint T.Text where+  pp :: T.Text -> Pretty String+  pp = pp . T.unpack++-- | Escaped hash character+escapedHash :: T.Text+escapedHash = "\\#"++-- | Hash character+hash :: T.Text+hash = "#"++-- | Drop a prefix of a line with length of a given line+dropLen :: T.Text -> T.Text -> T.Text+dropLen x y = T.drop (T.length x) y++-- | Check if a list starts with a given list+startsWith :: T.Text -> T.Text -> Bool+startsWith = flip T.isPrefixOf++-- | Check if a list ends with a given list+endsWith :: T.Text -> T.Text -> Bool+endsWith = flip T.isSuffixOf++-- | Drop leading spaces and drop at each end of a 'T.Text' the number of characters as in the supplied prefix and suffix.+stripEnds :: T.Text -> T.Text -> T.Text -> T.Text+stripEnds prefix suffix x = T.dropEnd (T.length suffix) (dropLen prefix (stripSpaces x))++-- | Drop spaces at the start and the end of a 'T.Text'.+stripSpaces :: T.Text -> T.Text+stripSpaces = T.strip++-- | Strip the given value from the beginning and the end of a list.+stripList :: Eq a => a -> [a] -> [a]+stripList x = dropWhileEnd (== x) . dropWhile (== x)++-- | Pad a 'T.Text' with a given number of spaces+indentN :: Int -> T.Text -> T.Text+indentN x s = T.concat (replicate x " ") <> s++-- | Show the name of a constructor.+constructorName :: Data a => a -> String+constructorName x = showConstr (toConstr x)++-- | Remove empty lines from the beginning and the end of a list.+stripEmpties :: [T.Text] -> [T.Text]+stripEmpties = stripList T.empty++-- | Drop leading empty strings+dropEmpties :: [T.Text] -> [T.Text]+dropEmpties = dropWhile (== T.empty)++-- | Check if a line without leading spaces is surrounded by the given 'T.Text's.+isEnclosedWith :: T.Text -> T.Text -> T.Text -> Bool+isEnclosedWith start end (stripSpaces -> x) = x `startsWith` start && x `endsWith` end++-- | Count leading spaces in a 'T.Text'.+countSpaces :: T.Text -> Int+countSpaces x = T.length $ T.takeWhile (== ' ') x++-- | Show error with line number for a token.+-- errorEmptyCommentAt :: Int -> String+errorEmptyCommentAt :: Show a1 => a1 -> a2+errorEmptyCommentAt lineNumber =+  error $+    ("Expected a 'Comment' at line " <> show lineNumber <> ".\n\n")+      <> "However, there are no characters after '{- '.\n\n"+      <> "Please, write there something after '{- '."+++------+-- TeX++-- | Prepend start of a @TeX@ comment (@'% '@) to a 'T.Text'.+prependTexComment :: T.Text -> T.Text+prependTexComment l+  | l == T.empty = l+  | otherwise = texCommentSpace <> l++-- | Drop start of a @TeX@ comment from a 'T.Text'.+dropTexComment :: Show a => T.Text -> a -> T.Text+dropTexComment l lineNumber+  | l `startsWith` texCommentSpace = dropLen texCommentSpace l+  | l == T.empty = l+  | otherwise =+      error $+        "Lines in a 'Disabled' block must either be empty or start with '% '\n\n"+          <> "Note that each 'Disabled' block must have at least one line starting with '% ' and having nonempty text after '% ' "+          <> ("The line " <> show lineNumber <> " must either be empty or start with '% '")++-- | Start a @TeX@ comment.+texComment :: T.Text+texComment = "%"++-- | Start a @TeX@ comment plus a space.+texCommentSpace :: T.Text+texCommentSpace = texComment <> " "++-------------------+-- Literate Haskell++-- | Start a @Literate Haskell@ comment.+lhsComment :: T.Text+lhsComment = "%"++-- | Start a @Literate Haskell@ comment plus a space.+lhsCommentSpace :: T.Text+lhsCommentSpace = lhsComment <> " "++-- | Start a @Literate Haskell@ line of @Haskell@ code.+lhsHsCode :: T.Text+lhsHsCode = ">"++-- | Start a @Literate Haskell@ line of @Haskell@ code plus a space.+lhsHsCodeSpace :: T.Text+lhsHsCodeSpace = lhsHsCode <> " "++-- | Prepend start of a @TeX@ comment (@'% '@) to a 'T.Text'.+prependLhsComment :: T.Text -> T.Text+prependLhsComment l+  | l == T.empty = l+  | otherwise = texCommentSpace <> l++-- | Drop start of a @TeX@ comment from a 'T.Text'.+dropLhsComment :: Show a => T.Text -> a -> T.Text+dropLhsComment l lineNumber+  | l `startsWith` lhsCommentSpace = dropLen lhsCommentSpace l+  | l == T.empty = l+  | otherwise = error $ "The line " <> show lineNumber <> " must either be empty or start with '% '"++-- | Replace "\\#" with "#" in a 'T.Text' prefix.+lhsUnescapeHash :: T.Text -> T.Text+lhsUnescapeHash x = if x `startsWith` escapedHash then hash <> (dropLen escapedHash x) else x++-- | Replace "#" with "\\#" in a 'T.Text' prefix.+lhsEscapeHash :: T.Text -> T.Text+lhsEscapeHash x = if x `startsWith` hash then escapedHash <> (dropLen hash x) else x++-----------+-- Markdown++-- | Open a @Markdown@ comment.+mdCommentOpen :: T.Text+mdCommentOpen = "<!--"++-- | Close a @Markdown@ comment.+mdCommentClose :: T.Text+mdCommentClose = "-->"++-- | Open a @Markdown@ comment plus a space.+mdCommentOpenSpace :: T.Text+mdCommentOpenSpace = mdCommentOpen <> " "++-- | A space plus close a @Markdown@ comment.+mdCommentCloseSpace :: T.Text+mdCommentCloseSpace = " " <> mdCommentClose++-- | Strip comment markers from a 'T.Text'.+stripMdComment :: T.Text -> T.Text+stripMdComment = stripEnds mdCommentOpenSpace mdCommentCloseSpace++-- | Check if a line is a @Markdown@ comment.+isMdComment :: T.Text -> Bool+isMdComment = isEnclosedWith mdCommentOpenSpace mdCommentCloseSpace++-----------+-- Haskell++-- | Open a @Haskell@ multi-line comment.+hsCommentOpen :: T.Text+hsCommentOpen = "{-"++-- | Open a @Haskell@ multi-line comment plus a space.+hsCommentOpenSpace :: T.Text+hsCommentOpenSpace = hsCommentOpen <> " "++-- | Close a @Haskell@ multi-line comment.+hsCommentClose :: T.Text+hsCommentClose = "-}"++-- | A space plus close a @Haskell@ multi-line comment.+hsCommentCloseSpace :: T.Text+hsCommentCloseSpace = " " <> hsCommentClose++-- | Drop leading spaces and drop at each end of a 'T.Text' the number of characters as in the supplied prefix and suffix.+stripHsComment :: T.Text -> T.Text+stripHsComment = stripEnds hsCommentOpenSpace hsCommentCloseSpace++-- | Check if a line without leading zeros is a multi-line @Haskell@ comment+isHsComment :: T.Text -> Bool+isHsComment = isEnclosedWith hsCommentOpenSpace hsCommentCloseSpace
test/Conversions/Main.hs view
@@ -1,11 +1,11 @@ {-# OPTIONS_GHC -fplugin Debug.Breakpoint #-} -import Converter (Config, Format (..), Token (..), Tokens, User, def, exampleNonTexTokens, exampleTexTokens, normalizeTokens, selectFromTokens, selectToTokens, showFormatExtension, showFormatName, stripEmpties, texHaskellCodeEnd, texHaskellCodeStart, toInternalConfig)+import Converter (Config, Format (..), Token (..), Tokens, User, def, exampleNonTexTokens, exampleTexTokens, normalizeTokens, selectFromTokens, selectToTokens, showFormatExtension, showFormatName, texHaskellCodeEnd, texHaskellCodeStart, toConfigInternal)+import Converter.Internal (stripEmpties) import Data.List.NonEmpty (NonEmpty (..)) import Data.String.Interpolate (i) import Data.Text qualified as T import Data.Text.IO qualified as T-import Data.Text.Lazy qualified as TL import Hedgehog (Gen, MonadGen, MonadTest, Property, property, tripping) import Hedgehog.Gen qualified as Gen import Hedgehog.Range qualified as Range@@ -15,7 +15,7 @@ import Test.Tasty (TestTree, defaultMain, testGroup, withResource) import Test.Tasty.HUnit (assertEqual, testCase) import Test.Tasty.Hedgehog (testProperty)-import Text.Pretty.Simple (CheckColorTty (..), OutputOptions (..), StringOutputStyle (Literal), defaultOutputOptionsDarkBg, pHPrintOpt, pShow, pShowNoColor, pStringNoColor)+import Text.Pretty.Simple (CheckColorTty (..), OutputOptions (..), StringOutputStyle (Literal), defaultOutputOptionsDarkBg, pHPrintOpt)  testDir :: String testDir = "testdata"@@ -31,6 +31,10 @@       , testTripping Md       ] +-- How many tokens to generate for tripping tests+_N_TOKENS :: Int+_N_TOKENS = 1000+ -- TODO test initial haskell code indentation is the same as after parsing -- so, generate lines with indentation relative to the previous indent token @@ -122,10 +126,15 @@   someLines <- genNonEmpty   pure Text{..} +genCommentSingleLine :: Gen Token+genCommentSingleLine = do+  someLine <- genLine+  pure CommentSingleLine{..}+ genNonDisabled :: (?genCode :: GenCode) => Gen Tokens genNonDisabled =   Gen.choice $-    [?genCode] <> (((: []) <$>) <$> [genComment, genText, genIndent, genDedent])+    [?genCode] <> (((: []) <$>) <$> [genComment, genCommentSingleLine, genText, genIndent, genDedent])  genDisabled :: Env => Gen Token genDisabled = do@@ -139,14 +148,14 @@  genTokens :: Env => Gen Tokens genTokens = do-  subLists <- Gen.list (Range.singleton 1000) genTokensSublist+  subLists <- Gen.list (Range.singleton _N_TOKENS) genTokensSublist   pure $ normalizeTokens $ concat subLists  -- Code tokens from tex files can only be recognized in a specific sequence -- That's why, they're generated in a list texGenCode :: (?config :: Config User) => Gen [Token] texGenCode = do-  let config = toInternalConfig ?config+  let config = toConfigInternal ?config   manyLines <- genLines   let manyLines' = if null (stripEmpties manyLines) then ["a"] else manyLines   pure $@@ -157,8 +166,8 @@  -- Code tokens from tex files can only be recognized in a specific sequence -- That's why, they're generated in a list-notTexGenCode :: Gen [Token]-notTexGenCode = do+nonTexGenCode :: Gen [Token]+nonTexGenCode = do   manyLines <- genLines   let manyLines' = if null (stripEmpties manyLines) then ["a"] else manyLines   pure [HaskellCode{manyLines = manyLines'}]@@ -181,7 +190,7 @@ selectGenCode :: Format -> ((?config :: Config User) => GenCode) selectGenCode = \case   TeX -> texGenCode-  _ -> notTexGenCode+  _ -> nonTexGenCode  testTripping :: Format -> TestTree testTripping format =
testdata/md/test.hs view
@@ -26,10 +26,10 @@ world! -} +{- Comment on a single line. -}+ {- Hello from text,-world! -And from-here!+world! -}
testdata/md/test.lhs view
@@ -22,8 +22,8 @@  % world! +% SINGLE_LINE Comment on a single line.+ Hello from text,-world! -And from-here!+world!
testdata/md/test.md view
@@ -28,8 +28,8 @@ world! --> +<!-- Comment on a single line. -->+ Hello from text,-world! -And from-here!+world!
− testdata/md/test.tex
@@ -1,33 +0,0 @@-% LIMA_INDENT 3--   a =-     f b--% LIMA_DEDENT--a =-  f b--% LIMA_INDENT 2--% LIMA_INDENT 5--     a =-       f b--% Hello,--% world!--Line 1-Line 2--Line 1-Line 2--% LIMA_DISABLE--% Line 1-% Line 2--% LIMA_ENABLE
testdata/md/tokens.hs view
@@ -23,11 +23,11 @@         , "Hello from comments,"         ]     }+, CommentSingleLine+    { someLine = "Comment on a single line." } , Text-    { someLines = "here!" :|-        [ "And from"-        , ""-        , "world!"+    { someLines = "world!" :|+        [ ""         , "Hello from text,"         ]     }
testdata/tex/test.hs view
@@ -1,3 +1,5 @@+{- LIMA_INDENT 3 -}+ {- LIMA_DISABLE -}  -- What's the answer?@@ -38,7 +40,4 @@ world! -} -{--Hello from text,-world!--}+{- Comment on a single line. -}
testdata/tex/test.lhs view
@@ -1,3 +1,5 @@+% LIMA_INDENT 3+ % LIMA_DISABLE  % -- What's the answer?@@ -25,5 +27,4 @@  % world! -Hello from text,-world!+% SINGLE_LINE Comment on a single line.
testdata/tex/test.md view
@@ -1,3 +1,5 @@+   <!-- LIMA_INDENT 3 -->+ <!-- LIMA_DISABLE  -- What's the answer?@@ -34,5 +36,4 @@ world! --> -Hello from text,-world!+<!-- Comment on a single line. -->
testdata/tex/test.tex view
@@ -1,3 +1,5 @@+% LIMA_INDENT 3+ % LIMA_DISABLE  % -- What's the answer?@@ -25,5 +27,4 @@  % world! -Hello from text,-world!+% SINGLE_LINE Comment on a single line.
testdata/tex/tokens.hs view
@@ -1,4 +1,6 @@-[ Disabled+[ Indent+    { n = 3 }+, Disabled     { manyLines = [ "-- What's the answer?" ] } , Indent     { n = 1 }@@ -31,6 +33,6 @@         , "Hello from comments,"         ]     }-, Text-    { someLines = "world!" :| [ "Hello from text," ] }+, CommentSingleLine+    { someLine = "Comment on a single line." } ]