Updated TeLML to a parser-library-less, nearly dependency-less implementation
Getty Ritter
10 years ago
| 1 | import Data.Char | |
| 2 | ||
| 3 | type Document = [Chunk] | |
| 4 | data Chunk | |
| 5 | = Text String | |
| 6 | | Tag String [Document] | |
| 7 | deriving (Eq, Show) | |
| 8 | ||
| 9 | isSpecial c = c `elem` "\\{}|" | |
| 10 | ||
| 11 | ||
| 12 | pText :: String -> (String, Chunk) | |
| 13 | pText = fmap Text . go | |
| 14 | where go ('\\':x:xs) | |
| 15 | | isSpecial x = fmap (x:) (go xs) | |
| 16 | go i@(x:xs) | |
| 17 | | isSpecial x = (i, "") | |
| 18 | | otherwise = fmap (x:) (go xs) | |
| 19 | go "" = ("", "") | |
| 20 | ||
| 21 | pTagName :: String -> (String, String) | |
| 22 | pTagName i@(x:xs) | |
| 23 | | isAlpha x = fmap (x:) (pTagName xs) | |
| 24 | | elem x "-_" = fmap (x:) (pTagName xs) | |
| 25 | | otherwise = (i, "") | |
| 26 | ||
| 27 | skipSpace :: String -> (String, ()) | |
| 28 | skipSpace i@(x:xs) | |
| 29 | | isSpace x = skipSpace xs | |
| 30 | | otherwise = (i, ()) | |
| 31 | ||
| 32 | pTag :: String -> (String, Chunk) | |
| 33 | pTag i = | |
| 34 | let (i', name) = pTagName i | |
| 35 | in case skipSpace i' of | |
| 36 | ('{':i'') -> fmap (Tag name) (pArgs i'') | |
| 37 | otherwise -> error "expected start of block" | |
| 38 | ||
| 39 | pArgs :: String -> (String, [Document]) | |
| 40 | pArgs ('|') | |
| 41 | ||
| 42 | pChunk :: String -> (String, Chunk) | |
| 43 | pChunk = undefined |
| 1 |
{-# LANGUAGE |
|
| 1 | {-# LANGUAGE LambdaCase #-} | |
| 2 | 2 | |
| 3 |
module Data.TeLML.Parser (Fragment(..), |
|
| 3 | module Data.TeLML.Parser (Fragment(..), Document, parse) where | |
| 4 | 4 | |
| 5 | import Control.Applicative | |
| 6 | import Data.Attoparsec.Text | |
| 7 | import Data.Char (isAlphaNum) | |
| 8 | import Data.Text (Text) | |
| 9 | import qualified Data.Text as T | |
| 10 | import Prelude hiding (takeWhile) | |
| 5 | import Data.Char (isAlpha, isSpace) | |
| 6 | import Data.TeLML.Type | |
| 11 | 7 | |
| 12 | data Fragment | |
| 13 | = Chunk Text | |
| 14 | | Tag Text [Fragment] | |
| 15 | deriving (Eq, Show) | |
| 8 | type Result a = Either String (String, a) | |
| 9 | type Parse a = String -> Result a | |
| 16 | 10 | |
| 17 | 11 | isSpecial :: Char -> Bool |
| 18 | isSpecial '\\' = True | |
| 19 | isSpecial '{' = True | |
| 20 | isSpecial '}' = True | |
| 21 | isSpecial '|' = True | |
| 22 |
isSpecial |
|
| 12 | isSpecial c = c `elem` "\\{}|" | |
| 23 | 13 | |
| 24 | isTagChar :: Char -> Bool | |
| 25 | isTagChar c = isAlphaNum c | |
| 14 | throw :: a -> Either a b | |
| 15 | throw = Left | |
| 26 | 16 | |
| 27 | parseFg :: Parser Fragment | |
| 28 | parseFg = (char '\\' *> parseBk) | |
| 29 | <|> (char '{' *> parseFg) | |
| 30 | <|> (Chunk <$> takeWhile1 (not . isSpecial)) | |
| 17 | over :: (a -> b) -> Result a -> Result b | |
| 18 | over _ (Left err) = Left err | |
| 19 | over f (Right (s, x)) = Right (s, f x) | |
| 31 | 20 | |
| 32 | parseBk :: Parser Fragment | |
| 33 | parseBk = (char '\\' *> pure (Chunk "\\")) | |
| 34 | <|> (char '|' *> pure (Chunk "|")) | |
| 35 | <|> parseTag | |
| 21 | bind :: Result a -> ((String, a) -> Result b) -> Result b | |
| 22 | bind (Left err) _ = Left err | |
| 23 | bind (Right a) f = f a | |
| 36 | 24 | |
| 37 | parseTag :: Parser Fragment | |
| 38 | parseTag = | |
| 39 | Tag <$> (takeWhile isTagChar <* many space) | |
| 40 | <*> (char '{' *> sepBy parseFg (char '|') <* char '}') | |
| 25 | pText :: Parse Fragment | |
| 26 | pText = over Text . go | |
| 27 | where go ('\\':x:xs) | |
| 28 | | isSpecial x = (x:) `over` go xs | |
| 29 | go i@(x:xs) | |
| 30 | | isSpecial x = return (i, "") | |
| 31 | | otherwise = (x:) `over` go xs | |
| 32 | go "" = return ("", "") | |
| 41 | 33 | |
| 42 | parseDocument :: Text -> Either String [Fragment] | |
| 43 | parseDocument = parseOnly (many parseFg <* endOfInput) | |
| 34 | pTagName :: Parse String | |
| 35 | pTagName i@(x:xs) | |
| 36 | | isAlpha x = (x:) `over` pTagName xs | |
| 37 | | elem x "-_" = (x:) `over` pTagName xs | |
| 38 | | otherwise = return (i, "") | |
| 39 | ||
| 40 | skipSpace :: Parse () | |
| 41 | skipSpace i@(x:xs) | |
| 42 | | isSpace x = skipSpace xs | |
| 43 | | otherwise = return (i, ()) | |
| 44 | ||
| 45 | pTag :: Parse Fragment | |
| 46 | pTag i = | |
| 47 | bind (pTagName i) $ \ (i', name) -> | |
| 48 | bind (skipSpace i') $ \case | |
| 49 | ('{':i'', ()) -> Tag name `over` pArgs i'' | |
| 50 | _ -> throw "expected start of block" | |
| 51 | ||
| 52 | pArgs :: Parse [Document] | |
| 53 | pArgs ('}':xs) = return (xs, []) | |
| 54 | pArgs s = bind (pFragments s) $ \case | |
| 55 | ('|':xs, cs) -> (cs:) `over` pArgs xs | |
| 56 | ('}':xs, cs) -> return (xs, [cs]) | |
| 57 | _ -> throw "[unreachable]" | |
| 58 | ||
| 59 | pFragment :: Parse Fragment | |
| 60 | pFragment s@('\\':c:_) | |
| 61 | | isSpecial c = pText s | |
| 62 | pFragment ('\\':xs) = pTag xs | |
| 63 | pFragment s = pText s | |
| 64 | ||
| 65 | pFragments :: Parse Document | |
| 66 | pFragments "" = return ("", []) | |
| 67 | pFragments s@(x:_) | |
| 68 | | x `elem` "}|" = return (s, []) | |
| 69 | | otherwise = | |
| 70 | bind (pFragment s) $ \case | |
| 71 | (s', c) -> (c:) `over` pFragments s' | |
| 72 | ||
| 73 | parse :: String -> Either String Document | |
| 74 | parse s = case pFragments s of | |
| 75 | Right ("", r) -> return r | |
| 76 | Right (s, _) -> throw ("expected end of document but found " ++ show s) | |
| 77 | Left err -> throw err |
| 1 | {-# LANGUAGE DeriveDataTypeable #-} | |
| 2 | ||
| 3 | module Data.TeLML.Type (Document, Fragment(..)) where | |
| 4 | ||
| 5 | import Control.DeepSeq (NFData(..)) | |
| 6 | import Data.Data (Data) | |
| 7 | import Data.Typeable (Typeable) | |
| 8 | import Data.String (IsString(..)) | |
| 9 | ||
| 10 | type Document = [Fragment] | |
| 11 | data Fragment | |
| 12 | = Text String | |
| 13 | | Tag String [Document] | |
| 14 | deriving (Eq, Show, Typeable, Data) | |
| 15 | ||
| 16 | instance IsString Fragment where | |
| 17 | fromString = Text | |
| 18 | ||
| 19 | instance NFData Fragment where | |
| 20 | rnf (Text s) = rnf s | |
| 21 | rnf (Tag s l) = rnf s `seq` rnf l |
| 1 | 1 | module Data.TeLML(parse, Document, Fragment(..)) where |
| 2 | 2 | |
| 3 | 3 | import Data.TeLML.Parser |
| 4 | import Data.Text (Text) | |
| 5 | ||
| 6 | type Document = [Fragment] | |
| 7 | ||
| 8 | parse :: Text -> Maybe Document | |
| 9 | parse t = | |
| 10 | case parseDocument t of | |
| 11 | Left _ -> Nothing | |
| 12 | Right d -> Just d |
| 4 | 4 | |
| 5 | 5 | import Data.Aeson (Value(..), encode, object, (.=)) |
| 6 | 6 | import qualified Data.ByteString.Lazy.Char8 as BS |
| 7 | import Data.Maybe (fromJust) | |
| 8 | 7 | import Data.TeLML |
| 9 | 8 | import Data.Vector (fromList) |
| 10 | 9 | import qualified Data.Text as T |
| 11 | import qualified Data.Text.IO as T | |
| 12 | 10 | |
| 13 | 11 | telmlToValue :: Fragment -> Value |
| 14 |
telmlToValue ( |
|
| 12 | telmlToValue (Text t) = String (T.pack t) | |
| 15 | 13 | telmlToValue (Tag t ts) = object |
| 16 | [ "name" .= String t | |
| 17 | , "contents" .= Array (fromList (map telmlToValue ts)) | |
| 14 | [ "name" .= String (T.pack t) | |
| 15 | , "contents" .= arr (map (arr . map telmlToValue) ts) | |
| 18 | 16 | ] |
| 17 | where arr = Array . fromList | |
| 18 | ||
| 19 | fromRight (Right x) = x | |
| 20 | fromRight _ = undefined | |
| 19 | 21 | |
| 20 | 22 | main = do |
| 21 | contents <- T.getContents | |
| 22 | BS.putStrLn . encode . map telmlToValue . fromJust . parse $ contents | |
| 23 | r <- fmap parse getContents | |
| 24 | case r of | |
| 25 | Right x -> BS.putStrLn . encode . map telmlToValue $ x | |
| 26 | Left err -> putStrLn err |
| 13 | 13 | |
| 14 | 14 | library |
| 15 | 15 | exposed-modules: Data.TeLML |
| 16 | other-modules: Data.TeLML.Parser | |
| 17 | build-depends: base >=4.7 && <4.8, text, attoparsec | |
| 16 | other-modules: Data.TeLML.Parser, Data.TeLML.Type | |
| 17 | build-depends: base >=4.7 && <4.9, deepseq | |
| 18 | 18 | default-language: Haskell2010 |
| 19 | 19 | |
| 20 | 20 | executable from-telml |