gdritter repos telml / c3f00e4
Added a lot of comments Getty Ritter 8 years ago
2 changed file(s) with 34 addition(s) and 0 deletion(s). Collapse all Expand all
88 type Result a = Either String (String, a)
99 type Parse a = String -> Result a
1010
11 -- All of these characters are the ones which need escaping if used
12 -- inside a document.
1113 isSpecial :: Char -> Bool
1214 isSpecial c = c `elem` "\\{}|"
1315
16 -- This is here for, uh, aesthetic reasons.
1417 throw :: a -> Either a b
1518 throw = Left
1619
20 -- This is 'fmap' named in such a way that it does not conflict with
21 -- 'fmap'.
1722 over :: (a -> b) -> Result a -> Result b
1823 over _ (Left err) = Left err
1924 over f (Right (s, x)) = Right (s, f x)
2025
26 {- And this is a monadic bind. You'll note that this basically has the
27 - same type as almost any other monadic parser combinator library, so
28 - you might be wondering why I'm not just using parsec. On the other
29 - hand, this is literally the only function I actually need there, so
30 - why have another dependency I need to keep up-to-date when I could
31 - just define this trivial function here?
32 -
33 - I'm also not defining this for `Monad` itself because then I'd have
34 - to deal with newtype wrappers and defining methods for the whole
35 - Functor=>Applicative=>Monad hierarchy, when all I really need is
36 - this.
37 -}
2138 bind :: Result a -> ((String, a) -> Result b) -> Result b
2239 bind (Left err) _ = Left err
2340 bind (Right a) f = f a
2441
42 -- Parse a text fragment, handling escapes. This will end as soon as it
43 -- sees any non-escaped special character.
2544 pText :: Parse Fragment
2645 pText = over Text . go
2746 where go ('\\':x:xs)
3150 | otherwise = (x:) `over` go xs
3251 go "" = return ("", "")
3352
53 -- Parse a tag name of length >= 0.
3454 pTagName :: Parse String
3555 pTagName s = go s `bind` ensureLen
3656 where go i@(x:xs)
4262 | length name > 0 = return (xs, name)
4363 | otherwise = throw "expected tag name after `\\'"
4464
65 -- Skip any space charaters, returning () for the first non-space
66 -- character (including EOF).
4567 skipSpace :: Parse ()
4668 skipSpace i@(x:xs)
4769 | isSpace x = skipSpace xs
4870 | otherwise = return (i, ())
4971 skipSpace _ = return ("", ())
5072
73 -- Parse a tag assuming that a backslash has already been encountered.
5174 pTag :: Parse Fragment
5275 pTag i =
5376 bind (pTagName i) $ \ (i', name) ->
5679 ("",_) -> throw "unexpected end-of-document while parsing tag"
5780 _ -> throw "expected start of block"
5881
82 -- Parse the vertical-bar-separated arguments to a tag, ending when a
83 -- right curly brace is encountered.
5984 pArgs :: Parse [Document]
6085 pArgs ('}':xs) = return (xs, [])
6186 pArgs s = bind (pFragments s) $ \case
6388 ('}':xs, cs) -> return (xs, [cs])
6489 _ -> throw "[unreachable]"
6590
91 -- Parse any fragment, deciding whether to parse it as a tag or a text chunk
6692 pFragment :: Parse Fragment
6793 pFragment s@('\\':c:_)
6894 | isSpecial c = pText s
6995 pFragment ('\\':xs) = pTag xs
7096 pFragment s = pText s
7197
98 -- Parse multiple fragments, ending when it encounters a }, or |, or end-of-file.
7299 pFragments :: Parse Document
73100 pFragments "" = return ("", [])
74101 pFragments ('{':s) = bind (pFragments s) $ \case
81108 bind (pFragment s) $ \case
82109 (s', c) -> (c:) `over` pFragments s'
83110
111 -- | Parse a string into a @TeLML@ 'Fragment'.
84112 parse :: String -> Either String Document
85113 parse str = case pFragments str of
86114 Right ("", r) -> return r
77 import Data.Typeable (Typeable)
88 import Data.String (IsString(..))
99
10 -- | A 'Document' is zero or more 'Fragment's.
1011 type Document = [Fragment]
12
13 -- | A 'Fragment' is either a snippet of text (as indicated by the
14 -- 'Text' constructor) or a tag (as indicated by the 'Tag'
15 -- constructor). The former is a raw string, and the latter consists
16 -- of a name followed by zero or more 'Document's.
1117 data Fragment
1218 = Text String
1319 | Tag String [Document]