packages feed

fmark-0.1.1: src/Parser.hs

module Parser where

import Data.Char (isPunctuation, isSpace)

import Data.Document
import Data.Text
import Data.Token
import Utils


-- | 'isParagraph' @str@ decides whether @str@ is a paragraph or a
-- heading.
isParagraph :: String -> Bool
isParagraph str =
    isPunctuation c && (not $ c `elem` "[]\"")
    where c = last str


-- | 'section' @idns idn@ is the 'List' of 'BeginSection' and
-- 'EndSection' 'Token's issued according the indentation stack @idns@
-- and current indentation @idn@.
section :: [Int] -> Int -> [Token]
section [] _ = error "section: idns is empty"
section (idn1:idns) idn2 =
    case compare idn1 idn2 of
      EQ -> []
      LT -> [BeginSection]
      GT -> EndSection:section (dropWhile (> idn1) idns) idn2


-- | 'reduce' @idns ln@ is the 'List' containing the 'Text' 'Token'
-- holding @ln@ preceeded by the appropriate section 'Token's as
-- issued by 'section' according to the indentation stack @idns@.
reduce :: [Int] -> Int -> String -> [Token]
reduce idns n ln = section idns (indentation ln) ++ [Literal n $ trim ln]


-- | 'classify' @str@ is the 'List' of 'Token's of @str@.
classify :: String -> [Token]
classify str =
    classify' [0] $ zip [1..] $ lines str
        where classify' _ [] = []
              classify' _ [(_, ln)] | all isSpace ln = []
              classify' idns ((_, ln1):lns) | all isSpace ln1 = classify' idns lns
              classify' idns [(n, ln)] = reduce idns n ln
              classify' idns ((n1, ln1):(n2, ln2):lns)
                  | all isSpace ln2 = reduce idns n1 ln1 ++ classify' (push idn1 (dropWhile (> idn1) idns)) lns
                  | idn1 < idn2 = reduce idns n1 ln1 ++ classify' (push idn1 (dropWhile (> idn1) idns)) ((n2, ln2):lns)
                  | idn1 > idn2 = reduce idns n1 ln1 ++ classify' (push idn1 idns) ((n2, ln2):lns)
                  | otherwise = classify' idns ((n1, join ln1 ln2):lns)
                  where idn1 = indentation ln1
                        idn2 = indentation ln2


-- | 'reconstruct' @str@ produces the 'List' of 'Text' elements for
-- 'String' @str@.
reconstructLine :: String -> [Text]
reconstructLine = loop
    where loop [] = []
          loop ('[':str) =
              case span (/= ']') str of
                (hd, []) -> [Plain hd]
                (hd, _:tl) -> Footnote hd:loop tl
          loop str =
              Plain hd:loop tl
              where (hd, tl) = span (/= '[') str


-- | 'reconstructLines' @str@ produces the 'List' of 'Text' elements
-- for each line in @str@.
reconstructLines :: String -> [[Text]]
reconstructLines = map reconstructLine . lines


-- | 'docify' @tks@ parses the sequence of 'Token's @tks@ into a 'Document'.
docify :: [Token] -> Document
docify tks =
    docify' tks [[]]
    where docify' :: [Token] -> [[Document]] -> Document
          -- edit: this 'ensureDocument' is interfering with style weaving
          -- docify' [] [docs] = ensureDocument $ reverse docs
          docify' [] [docs] = Content $ reverse docs
          docify' [] st = docify' [EndSection] st

          docify' (Literal n str:tks) (top:st) =
              docify' tks ((doc:top):st)
              where doc | isParagraph str = Paragraph (n, str) $ reconstructLine $ replace ' ' str
                        | otherwise = Heading (n, str) $ reconstructLines str

          docify' (BeginSection:tks) st =
              docify' tks ([]:st)

          docify' (EndSection:tks) (top:bot:st) =
              docify' tks ((Section (ensureDocument $ reverse top):bot):st)

          docify' tks st =
              error $ "\n\n\tdocify: docify': unhandled case" ++
                      "\n\n\t tks = " ++ show tks ++
                      "\n\n\t st = " ++ show st ++ "\n\n"