heterocephalus-1.0.1.1: src/Text/Heterocephalus/Parse.hs
{-# LANGUAGE CPP #-}
{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE TemplateHaskell #-}
module Text.Heterocephalus.Parse where
#if MIN_VERSION_base(4,9,0)
#else
import Control.Applicative ((<$>))
#endif
import Control.Monad
import Data.Char (isUpper)
import Data.Data
import Text.Parsec.Prim (Parsec)
import Text.ParserCombinators.Parsec hiding (Line)
import Text.Shakespeare.Base
import Text.Hamlet.Parse
data Control
= ControlForall Deref Binding
| ControlEndForall
| ControlIf Deref
| ControlElse
| ControlEndIf
| NoControl Content
deriving (Show, Eq, Read, Data, Typeable)
data Doc = DocForall Deref Binding [Doc]
| DocCond [(Deref, [Doc])] (Maybe [Doc])
| DocContent Content
deriving (Show, Eq, Read, Data, Typeable)
data Content = ContentRaw String
| ContentVar Deref
deriving (Show, Eq, Read, Data, Typeable)
type UserParser a = Parsec String a
docFromString :: String -> [Doc]
docFromString s =
case parseDoc s of
Error s' -> error s'
Ok d -> d
parseDoc :: String -> Result [Doc]
parseDoc s = do
controls <- parseLineControl s
return $ controlsToDocs controls
controlsToDocs :: [Control] -> [Doc]
controlsToDocs [] = []
controlsToDocs (ControlForall d b:cs) =
let (inner, rest) = parseReps 0 cs
in (DocForall d b $ controlsToDocs inner) : controlsToDocs rest
controlsToDocs (ControlIf d:cs) =
let (inner, el, rest) = parseConds 0 cs
in DocCond [(d, controlsToDocs inner)] (fmap controlsToDocs el) :
controlsToDocs rest
controlsToDocs (NoControl c:cs) = DocContent c : controlsToDocs cs
controlsToDocs cs = error $ "Parse error: " ++ show cs
-- TODO parse elseif
parseConds :: Int -> [Control] -> ([Control], Maybe [Control], [Control])
parseConds _ [] = error "No endif found"
parseConds depth (x:xs')
| depth < 0 =
error "A `endif` keyword without any corresponding `if` was found."
| depth == 0 && isEndIf x = ([], Nothing, xs')
| depth == 0 && isElse x =
let (ys, may, zs) = parseConds depth xs'
in case may of
Nothing -> ([], Just ys, zs)
Just _ ->
error "A `if` clause can not have more than one `else` keyword."
| isEndIf x =
let (ys, may, zs) = parseConds (depth - 1) xs'
in (x : ys, may, zs)
| isIf x =
let (ys, may, zs) = parseConds (depth + 1) xs'
in (x : ys, may, zs)
| otherwise =
let (ys, may, zs) = parseConds depth xs'
in (x : ys, may, zs)
where
isIf (ControlIf _) = True
isIf _ = False
isEndIf ControlEndIf = True
isEndIf _ = False
isElse ControlElse = True
isElse _ = False
parseReps :: Int -> [Control] -> ([Control], [Control])
parseReps _ [] = error "No endforall found"
parseReps depth (x:xs')
| depth < 0 =
error "A `endforall` keyword without any corresponding `for` was found."
| depth == 0 && isEndForall x = ([], xs')
| isEndForall x =
let (ys, zs) = parseReps (depth - 1) xs'
in (x : ys, zs)
| isForall x =
let (ys, zs) = parseReps (depth + 1) xs'
in (x : ys, zs)
| otherwise =
let (ys, zs) = parseReps depth xs'
in (x : ys, zs)
where
isEndForall ControlEndForall = True
isEndForall _ = False
isForall (ControlForall _ _) = True
isForall _ = False
parseLineControl :: String -> Result [Control]
parseLineControl s =
case parse lineControl s s of
Left e -> Error $ show e
Right x -> Ok x
lineControl :: UserParser () [Control]
lineControl = manyTill control $ try eof >> return ()
control :: UserParser () Control
control = controlHash <|> controlPercent <|> controlReg
where
controlPercent = do
x <- parsePercent
case x of
Left str -> return (NoControl $ ContentRaw str)
Right ctrl -> return ctrl
controlHash = do
x <- parseHash
return . NoControl $
case x of
Left str -> ContentRaw str
Right deref -> ContentVar deref
controlReg = (NoControl . ContentRaw) <$> many (noneOf "#%")
parsePercent :: UserParser () (Either String Control)
parsePercent = do
a <- parseControl '%'
optional eol
return a
where
eol = (char '\n' >> return ()) <|> (string "\r\n" >> return ())
parseControl :: Char -> UserParser () (Either String Control)
parseControl c = do
_ <- char c
(char '\\' >> return (Left [c])) <|>
(do ctrl <-
between (char '{') (char '}') $ do
spaces
x <- parseControl'
spaces
return x
return $ Right ctrl) <|>
return (Left [c])
parseControl' :: UserParser () Control
parseControl' =
try parseForall <|> try parseEndForall <|> try parseIf <|> try parseElse <|>
try parseEndIf
where
parseForall = do
_ <- try $ string "forall"
spaces
(x, y) <- binding
return $ ControlForall x y
parseEndForall = do
_ <- try $ string "endforall"
return $ ControlEndForall
parseIf = do
_ <- try $ string "if"
spaces
x <- parseDeref
return $ ControlIf x
parseElse = do
_ <- try $ string "else"
return $ ControlElse
parseEndIf = do
_ <- try $ string "endif"
return $ ControlEndIf
binding = do
y <- identPattern
spaces
_ <- string "<-"
spaces
x <- parseDeref
_ <- spaceTabs
return (x, y)
spaceTabs :: Parser String
spaceTabs = many $ oneOf " \t"
ident :: Parser Ident
ident =
do i <- many1 (alphaNum <|> char '_' <|> char '\'')
white
return (Ident i) <?> "identifier"
parens = between (char '(' >> white) (char ')' >> white)
brackets = between (char '[' >> white) (char ']' >> white)
braces = between (char '{' >> white) (char '}' >> white)
comma = char ',' >> white
atsign = char '@' >> white
equals = char '=' >> white
white = skipMany $ char ' '
wildDots = string ".." >> white
isVariable (Ident (x:_)) = not (isUpper x)
isVariable (Ident []) = error "isVariable: bad identifier"
isConstructor (Ident (x:_)) = isUpper x
isConstructor (Ident []) = error "isConstructor: bad identifier"
identPattern :: Parser Binding
identPattern = gcon True <|> apat
where
apat = choice [varpat, gcon False, parens tuplepat, brackets listpat]
varpat =
do v <-
try $ do
v <- ident
guard (isVariable v)
return v
option (BindVar v) $ do
atsign
b <- apat
return (BindAs v b) <?> "variable"
gcon :: Bool -> Parser Binding
gcon allowArgs =
do c <-
try $ do
c <- dataConstr
return c
choice
[ record c
, fmap (BindConstr c) (guard allowArgs >> many apat)
, return (BindConstr c [])
] <?> "constructor"
dataConstr = do
p <- dcPiece
ps <- many dcPieces
return $ toDataConstr p ps
dcPiece = do
x@(Ident y) <- ident
guard $ isConstructor x
return y
dcPieces = do
_ <- char '.'
dcPiece
toDataConstr x [] = DCUnqualified $ Ident x
toDataConstr x (y:ys) = go (x :) y ys
where
go front next [] = DCQualified (Module $ front []) (Ident next)
go front next (rest:rests) = go (front . (next :)) rest rests
record c =
braces $ do
(fields, wild) <- option ([], False) $ go
return (BindRecord c fields wild)
where
go =
(wildDots >> return ([], True)) <|>
(do x <- recordField
(xs, wild) <- option ([], False) (comma >> go)
return (x : xs, wild))
recordField = do
field <- ident
p <-
option
(BindVar field) -- support punning
(equals >> identPattern)
return (field, p)
tuplepat = do
xs <- identPattern `sepBy` comma
return $
case xs of
[x] -> x
_ -> BindTuple xs
listpat = BindList <$> identPattern `sepBy` comma