summaryrefslogtreecommitdiff
path: root/src/Fragment.hs
blob: 64b04e5ac42b21b9397a014bf9e464bed07cd715 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
{- Copyright © 2015 Benjamin Barenblat

This program is free software: you can redistribute it and/or modify it under
the terms of the GNU General Public License as published by the Free Software
Foundation, either version 3 of the License, or (at your option) any later
version.

This program is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE.  See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License along with
this program.  If not, see <http://www.gnu.org/licenses/>. -}

{- Grammar:

S         → ε | TEXT | TEXT block S | block S
block     → << TEXT >>= code
code      → @ | TEXT @ | TEXT reference code | reference code
reference → << TEXT >>

TEXT -> any sequence of one or more Unicode code points -}

module Fragment ( Fragment(..)
                , CodeOrReference(..)
                , parseFragments) where

import Data.Data (Data)
import Data.Typeable (Typeable)
import GHC.Generics (Generic)

import Control.Applicative ((<$>), (<*>))
import Control.Monad (void)
import Text.Parsec
import Text.Parsec.String

data Fragment = Documentation String
              | BlockCode String [CodeOrReference]
              deriving (Eq, Show, Data, Typeable, Generic)

data CodeOrReference = Code String
                     | Reference String
                     deriving (Eq, Show, Data, Typeable, Generic)

parseFragments :: FilePath -> String -> Either String [Fragment]
parseFragments path input =
  case parse literateFile path input of
    Right result -> Right result
    Left err -> Left $ show err

literateFile :: Parser [Fragment]
literateFile = (:) <$> blockCode <*> literateFile
               <|> do body <- try $ manyTill anyChar (lookAhead blockCode)
                      block <- blockCode
                      rest <- literateFile
                      return $ Documentation body : block : rest
               <|> (:[]) . Documentation <$> manyTill anyChar eof

blockCode :: Parser Fragment
blockCode = do
  void $ try $ string "<<"
  name <- many1Till (noneOf "\r\n") (try $ string ">>=")
  body <- code
  return $ BlockCode name body


data CodeTerminator = AtSign
                    | BeginReference
                    deriving (Eq, Show, Data, Typeable, Generic)

atSign :: Parser CodeTerminator
atSign = char '@' >> return AtSign

beginReference :: Parser CodeTerminator
beginReference = lookAhead reference >> return BeginReference

code :: Parser [CodeOrReference]
code = (:) <$> reference <*> code
       <|> do (body, exitChar) <- manyTill' anyChar (atSign <|> beginReference)
              case exitChar of
                AtSign -> return [Code body]
                BeginReference -> do
                  ref <- option [] $ (:[]) <$> reference
                  rest <- code
                  return $ Code body : ref ++ rest

reference :: Parser CodeOrReference
reference = do
  void $ try $ string "<<"
  name <- many1Till anyChar (try $ string ">>")
  return $ Reference name

manyTill' :: Stream s m t
             => ParsecT s u m a -> ParsecT s u m end -> ParsecT s u m ([a], end)
manyTill' p end = scan
  where scan  = do exit <- end
                   return ([], exit)
                <|>
                do x <- p
                   (xs, exit) <- scan
                   return (x:xs, exit)

many1Till :: Stream s m t
             => ParsecT s u m a -> ParsecT s u m end -> ParsecT s u m [a]
many1Till p end = (:) <$> p <*> manyTill p end