hermit-0.1.1.0: src/Language/HERMIT/Expr.hs
module Language.HERMIT.Expr
( -- * HERMIT Expressions
-- | This is the /untyped/ command and control language for HERMIT.
ExprH(..)
, StmtH(..)
, parseExprH
, parseStmtsH
, unparseExprH
, unparseStmtH
, unparseStmtsH
, numStmtsH
) where
import Control.Applicative ((<$>))
import Data.Char
import Data.List
---------------------------------------------
-- | A simple expression language AST, for things parsed from 'String' or JSON structures.
data ExprH
= SrcName String -- ^ Variable names (refers to source code).
| CmdName String -- ^ Commands (to be looked up in 'Language.HERMIT.Dictionary').
| AppH ExprH ExprH -- ^ Application.
deriving (Eq, Show)
-- | Nested lists to represent scoping structure.
data StmtH expr
= ExprH expr
| ScopeH [StmtH expr]
deriving Show
data Box e = InfixableExpr e | Box e deriving Show
---------------------------------------------
-- TODO: This is a quick hack that's better than just saying "N"; I have no idea how accurate this is.
-- | Count the total number of statements.
numStmtH :: StmtH expr -> Int
numStmtH (ExprH _) = 1
numStmtH (ScopeH ss) = numStmtsH ss
-- | Count the total number of statements.
numStmtsH :: [StmtH expr] -> Int
numStmtsH = sum . map numStmtH
---------------------------------------------
unparseExprH :: ExprH -> String
unparseExprH (SrcName nm) = "'" ++ nm
unparseExprH (CmdName nm)
| all isId nm = nm
| otherwise = show nm -- with quotes
unparseExprH (AppH (AppH (CmdName nm) e1) e2)
| all isInfixId nm
= unparseAtom e1 ++ " " ++ nm ++ " " ++ unparseAtom e2
unparseExprH (AppH e1 e2) = unparseExprH e1 ++ " " ++ unparseAtom e2
unparseAtom :: ExprH -> String
unparseAtom e@(AppH {}) = "(" ++ unparseExprH e ++ ")"
unparseAtom e = unparseExprH e
unparseStmtH :: StmtH ExprH -> String
unparseStmtH (ExprH expr) = unparseExprH expr
unparseStmtH (ScopeH stmts) = "{ " ++ unparseStmtsH stmts ++ "}"
unparseStmtsH :: [StmtH ExprH] -> String
unparseStmtsH stmts = intercalate " ; " (map unparseStmtH stmts)
---------------------------------------------
-- Cheap and cheerful parser. Pretty hacky for now
parse :: ReadS a -> String -> Either String a
parse p str = case p str of
(a,rest) : _ | all isSpace rest -> Right a
_ -> Left $ "User error, cannot parse: " ++ str
many :: ReadS a -> ReadS [a]
many p inp =
some p inp ++
[ ([], inp) ]
some :: ReadS a -> ReadS [a]
some p inp =
[ (x:xs,inp2)
| (x,inp1) <- p inp
, (xs,inp2) <- many p inp1
]
bind :: ReadS a -> (a -> ReadS b) -> ReadS b
bind m k inp =
[ (b,inp2)
| (a,inp1) <- m inp
, (b,inp2) <- k a inp1
]
---------------------------------------------
-- | Parse an expression.
parseExprH :: String -> Either String ExprH
parseExprH = parse parseExprH1
-- | Parse a list of statements, seperated by semicolons.
parseStmtsH :: String -> Either String [StmtH ExprH]
parseStmtsH = parse parseExprsH'
---------------------------------------------
parseExprsH' :: ReadS [StmtH ExprH]
parseExprsH' inp =
(many (item ";") `bind` \ _ -> -- another hack
(parseTopExprH1 `bind` (\ a ->
(many (item ";") `bind` \ _ -> -- complete hack, needed fixed with real parser
(parseExprsH' `bind` (\ as ->
(\ inp' -> [(a:as,inp')]))))))) inp ++ [ ([], inp) ]
parseTopExprH1 :: ReadS (StmtH ExprH)
parseTopExprH1 inp =
(parseExprH1 `bind` \ es inp1 -> [(ExprH es,inp1)]) inp ++
[ (ScopeH es,inp3)
| ("{",inp1) <- parseToken inp
, (es,inp2) <- parseExprsH' inp1
, ("}",inp3) <- parseToken inp2
]
parseExprH0 :: ReadS (Box ExprH)
parseExprH0 inp =
[ (Box (CmdName str),inp1)
| (str,inp1) <- parseToken inp
, isId (head str) || head str == ':' -- commands can start with :
, all isId (tail str)
] ++
[ (InfixableExpr (CmdName str),inp1)
| (str,inp1) <- parseToken inp
, all isInfixId str
] ++
[ (Box (SrcName str),inp2)
| ("'",inp1) <- parseToken inp
, (str,inp2) <- parseToken inp1
] ++
[ (Box (CmdName $ chomp '"' str),inp1)
| (str@('"':_),inp1) <- lex inp
] ++
[ (Box e,inp3)
| ("(",inp1) <- parseToken inp
, (e,inp2) <- parseExprH1 inp1
, (")",inp3) <- parseToken inp2
]
where chomp _ [] = []
chomp ch s@(c:cs) | c == ch && last cs == ch = init cs
| otherwise = s
parseExprH1 :: ReadS ExprH
parseExprH1 = some parseExprH0 `bind` \ es inp ->
case mkAppH es id [] of
Nothing -> []
Just r -> [(r,inp)]
-- TODO: Assoc to the right, want assoc to the left.
mkAppH :: [Box ExprH] -> (ExprH -> ExprH) -> [ExprH] -> Maybe ExprH
mkAppH (Box e:es) ops rs = mkAppH es ops (rs ++ [e])
mkAppH (InfixableExpr e:es) ops rs = maybe Nothing (\ lhs ->
mkAppH es (ops . AppH (AppH e lhs)) []
) (mkAppH' rs)
mkAppH [] ops rs = ops <$> mkAppH' rs
mkAppH' :: [ExprH] -> Maybe ExprH
mkAppH' (r:rs) = Just $ foldl AppH r rs
mkAppH' [] = Nothing
--mkAppH (e:es) = foldl AppH (f e) (map f es)
-- where f (InfixableExpr e) = e
-- f (Box e) = e
item :: String -> ReadS ()
item str inp =
[ ((),rest)
| (tok,rest) <- parseToken inp
, tok == str
]
parseToken :: ReadS String
parseToken [] = []
parseToken ('\n':cs) = [(";",cs)] -- yes, really
parseToken ('(' :cs) = [("(",cs)]
parseToken (')' :cs) = [(")",cs)]
parseToken ('{' :cs) = [("{",cs)]
parseToken ('}' :cs) = [("}",cs)]
parseToken (';' :cs) = [(";",cs)]
parseToken ('\'':cs) = [("'",cs)]
parseToken ('\"':cs) = [("\"",cs)]
parseToken (c :cs) | isSpace c = parseToken cs
| c == ':' = [let (a,b) = span isId cs in (c:a,b) ]
| isAlphaNum c = [span isId (c:cs)]
| isInfixId c = [span isInfixId (c:cs)]
parseToken _ = []
---------------------------------------------
isId :: Char -> Bool
isId c = isAlphaNum c || c `elem` "_-"
isInfixId :: Char -> Bool
isInfixId c = c `elem` "+._-:<>"
---------------------------------------------