atomo-0.2.2.1: src/Atomo/Parser/Expr.hs
module Atomo.Parser.Expr where
import Control.Arrow (first, second)
import "monads-fd" Control.Monad.State
import Data.Maybe (fromJust, isJust)
import Text.Parsec
import qualified "mtl" Control.Monad.Trans as MTL
import Atomo.Environment
import Atomo.Helpers (toPattern', toMacroPattern')
import Atomo.Method (addMethod)
import Atomo.Parser.Base
import Atomo.Parser.Expand
import Atomo.Parser.Primitive
import Atomo.Types hiding (keyword, string)
-- | The types of values in Dispatch syntax.
data Dispatch
= DParticle EParticle
| DNormal Expr
deriving Show
-- | The default precedence for an operator (5).
defaultPrec :: Integer
defaultPrec = 5
-- | Parses any Atomo expression.
pExpr :: Parser Expr
pExpr = choice
[ pOperator
, pMacro
, pForMacro
, try pDispatch
, pLiteral
, parens pExpr
]
<?> "expression"
-- | Parses any Atomo literal value.
pLiteral :: Parser Expr
pLiteral = choice
[ pThis
, pBlock
, pList
, pParticle
, pQuoted
, pQuasiQuoted
, pUnquoted
, pPrimitive
]
<?> "literal"
-- | Parses a primitive value.
--
-- Examples: @1@, @2.0@, @3\/4@, @$d@, @\"foo\"@, @True@, @False@
pPrimitive :: Parser Expr
pPrimitive = tagged $ liftM (Primitive Nothing) pPrim
-- | The @this@ keyword, i.e. the toplevel object literal.
pThis :: Parser Expr
pThis = tagged $ reserved "this" >> return (ETop Nothing)
-- | An expression literal.
--
-- Example: @'1@, @'(2 + 2)@
pQuoted :: Parser Expr
pQuoted = tagged $ do
char '\''
e <- pSpacedExpr
return (Primitive Nothing (Expression e))
-- | An expression literal that may contain "unquotes" - expressions to splice
-- in to yield a different expression.
--
-- Examples: @`a@, @`(1 + ~(2 + 2))@
pQuasiQuoted :: Parser Expr
pQuasiQuoted = tagged $ do
char '`'
modifyState $ \ps -> ps { psInQuote = True }
e <- pSpacedExpr
modifyState $ \ps -> ps { psInQuote = False }
return (EQuote Nothing e)
-- | An unquote expression, used inside a quasiquote.
--
-- Examples: @~1@, @~(2 + 2)@
pUnquoted :: Parser Expr
pUnquoted = tagged $ do
char '~'
iq <- fmap psInQuote getState
modifyState $ \ps -> ps { psInQuote = False }
e <- pSpacedExpr
modifyState $ \ps -> ps { psInQuote = iq }
return (EUnquote Nothing e)
-- | Any expression that fits into one lexical "space" - either a simple
-- literal value, a single dispatch to the toplevel object, or an expression in
-- parentheses.
--
-- Examples: @1@, @[1, 2]@, @a@, @(2 + 2)@
pSpacedExpr :: Parser Expr
pSpacedExpr = pLiteral <|> simpleDispatch <|> parens pExpr
where
simpleDispatch = tagged $ do
name <- ident
notFollowedBy (char ':')
spacing
return (Dispatch Nothing (esingle name (ETop Nothing)))
-- | The for-macro "pragma."
--
-- Example: @for-macro 1 print@
pForMacro :: Parser Expr
pForMacro = tagged (do
reserved "for-macro"
e <- pExpr
-- TODO: evaluate this with a specific toplevel, probably Lobby
macroExpand e >>= MTL.lift . eval
return (EForMacro Nothing e))
<?> "for-macro expression"
-- | A macro definition.
--
-- Example: @macro (n squared) `(~n * ~n)@
pMacro :: Parser Expr
pMacro = tagged (do
reserved "macro"
p <- parens (pExpr >>= MTL.lift . toMacroPattern')
whiteSpace
e <- pExpr
macroExpand e >>= addMacro p
return (EMacro Nothing p e))
<?> "macro definition"
-- | An operator "pragma" - tells the parser about precedence and associativity
-- for the given operator(s).
--
-- Examples: @operator right 0 ->@, @operator 7 * /@
pOperator :: Parser Expr
pOperator = tagged (do
reserved "operator"
info <- choice
[ do
a <- choice
[ symbol "right" >> return ARight
, symbol "left" >> return ALeft
]
prec <- option defaultPrec (try integer)
return (a, prec)
, liftM ((,) ALeft) integer
]
ops <- operator `sepBy1` spacing
forM_ ops $ \name ->
modifyState (\ps -> ps { psOperators = (name, info) : psOperators ps })
return (uncurry (Operator Nothing ops) info))
<?> "operator pragma"
-- | A particle literal.
--
-- Examples: @\@foo@, @\@(bar: 2)@, @\@bar:@, @\@(foo: 2 bar: _)@
pParticle :: Parser Expr
pParticle = tagged (do
char '@'
c <- choice
[ cKeyword True
, binary
, try (cSingle True)
, symbols
]
return (EParticle Nothing c))
<?> "particle"
where
binary = do
op <- operator
return $ EPMKeyword [op] [Nothing, Nothing]
symbols = do
names <- many1 (anyIdent >>= \n -> char ':' >> return n)
spacing
return $ EPMKeyword names (replicate (length names + 1) Nothing)
-- | Any dispatch, both single and keyword.
pDispatch :: Parser Expr
pDispatch = try pdKeys <|> pdChain
<?> "dispatch"
-- | A keyword dispatch.
--
-- Examples: @1 foo: 2@, @1 + 2@
pdKeys :: Parser Expr
pdKeys = do
pos <- getPosition
msg <- keywords ekeyword (ETop (Just pos)) (try pdChain <|> headless)
ops <- liftM psOperators getState
return $ Dispatch (Just pos) (toBinaryOps ops msg)
<?> "keyword dispatch"
where
headless = do
p <- getPosition
msg <- ckeywd p
ops <- liftM psOperators getState
return (Dispatch (Just p) (toBinaryOps ops msg))
ckeywd pos = do
ks <- wsMany1 $ keyword pdChain
let (ns, es) = unzip ks
return $ ekeyword ns (ETop (Just pos):es)
<?> "keyword segment"
-- | A chain of message sends, both single and chained keywords.
--
-- Example: @1 sqrt (* 2) floor@
pdChain :: Parser Expr
pdChain = do
pos <- getPosition
chain <- wsManyStart
(liftM DNormal (try pLiteral <|> pThis <|> parens pExpr) <|> chained)
chained
return $ dispatches pos chain
<?> "single dispatch"
where
chained = liftM DParticle $ choice
[ cKeyword False
, cSingle False
]
-- start off by dispatching on either a primitive or Top
dispatches :: SourcePos -> [Dispatch] -> Expr
dispatches p (DNormal e:ps) =
dispatches' p ps e
dispatches p (DParticle (EPMSingle n):ps) =
dispatches' p ps (Dispatch (Just p) $ esingle n (ETop (Just p)))
dispatches p (DParticle (EPMKeyword ns (Nothing:es)):ps) =
dispatches' p ps (Dispatch (Just p) $ ekeyword ns (ETop (Just p):map fromJust es))
dispatches _ ds = error $ "impossible: dispatches on " ++ show ds
-- roll a list of partial messages into a bunch of dispatches
dispatches' :: SourcePos -> [Dispatch] -> Expr -> Expr
dispatches' _ [] acc = acc
dispatches' p (DParticle (EPMKeyword ns (Nothing:es)):ps) acc =
dispatches' p ps (Dispatch (Just p) $ ekeyword ns (acc : map fromJust es))
dispatches' p (DParticle (EPMSingle n):ps) acc =
dispatches' p ps (Dispatch (Just p) $ esingle n acc)
dispatches' _ x y = error $ "impossible: dispatches' on " ++ show (x, y)
-- | A comma-separated list of zero or more expressions, surrounded by square
-- brackets.
--
-- Examples: @[]@, @[1, $a]@
pList :: Parser Expr
pList = (tagged . liftM (EList Nothing) $ brackets (wsDelim "," pExpr))
<?> "list"
-- | A block of expressions, surrounded by braces and optionally having
-- arguments.
--
-- Examples: @{ }@, @{ a b | a + b }@, @{ a = 1; a + 1 }@
pBlock :: Parser Expr
pBlock = tagged (braces $ do
arguments <- option [] . try $ do
ps <- many1 pSpacedExpr
whiteSpace
string "|"
whiteSpace1
mapM (MTL.lift . toPattern') ps
code <- wsBlock pExpr
return $ EBlock Nothing arguments code)
<?> "block"
-- | A general "single dispatch" form, without a target.
--
-- Used for both chaines and particles.
cSingle :: Bool -> Parser EParticle
cSingle p = do
n <- if p then anyIdent else ident
notFollowedBy colon
spacing
return (EPMSingle n)
<?> "single segment"
-- | A general "keyword dispatch" form, without a head.
--
-- Used for both chaines and particles.
cKeyword :: Bool -> Parser EParticle
cKeyword wc = do
ks <- parens $ many1 keyword'
let (ns, mvs) = second (Nothing:) $ unzip ks
if any isOperator (tail ns)
then toDispatch ns mvs
else return $ EPMKeyword ns mvs
<?> "keyword segment"
where
keywordVal
| wc = wildcard <|> value
| otherwise = value
keywordDispatch
| wc = wildcard <|> disp
| otherwise = disp
value = liftM Just pdChain
disp = liftM Just pDispatch
keyword' = do
name <- try (do
name <- ident
char ':'
return name) <|> operator
whiteSpace1
target <-
if isOperator name
then keywordDispatch
else keywordVal
return (name, target)
wildcard = symbol "_" >> return Nothing
toDispatch [] mvs = error $ "impossible: toDispatch on [] and " ++ show mvs
toDispatch (n:ns) mvs
| all isJust opVals = do
os <- getState
pos <- getPosition
let msg = toBinaryOps (psOperators os) $ ekeyword opers (map fromJust opVals)
return . EPMKeyword nonOpers $
partVals ++ [Just $ Dispatch (Just pos) msg]
| otherwise = fail "invalid particle; toplevel operator with wildcards as values"
where
(nonOpers, opers) = first (n:) $ break isOperator ns
(partVals, opVals) = splitAt (length nonOpers) mvs
-- | Work out precadence, associativity, etc. for a keyword dispatch.
--
-- The input is a keyword EMessage with a mix of operators and identifiers as
-- its name, e.g. @EKeyword { emNames = ["+", "*", "remainder"] }@.
toBinaryOps :: Operators -> EMessage -> EMessage
toBinaryOps _ done@(EKeyword _ [_] [_, _]) = done
toBinaryOps ops (EKeyword h (n:ns) (v:vs))
| nextFirst =
ekeyword [n]
[ v
, Dispatch (eLocation v)
(toBinaryOps ops (ekeyword ns vs))
]
| isOperator n =
toBinaryOps ops . ekeyword ns $
(Dispatch (eLocation v) (ekeyword [n] [v, head vs]):tail vs)
| nonOperators == ns = EKeyword h (n:ns) (v:vs)
| null nonOperators && length vs > 2 =
ekeyword [head ns]
[ Dispatch (eLocation v) $
ekeyword [n] [v, head vs]
, Dispatch (eLocation v) $
toBinaryOps ops (ekeyword (tail ns) (tail vs))
]
| otherwise =
toBinaryOps ops . ekeyword (drop numNonOps ns) $
(Dispatch (eLocation v) $
ekeyword (n : nonOperators)
(v : take (numNonOps + 1) vs)) :
drop (numNonOps + 1) vs
where
numNonOps = length nonOperators
nonOperators = takeWhile (not . isOperator) ns
nextFirst =
isOperator n && or
[ null ns
, prec next > prec n
, assoc n == ARight && prec next == prec n
]
where next = head ns
assoc n' =
case lookup n' ops of
Nothing -> ALeft
Just (a, _) -> a
prec n' =
case lookup n' ops of
Nothing -> defaultPrec
Just (_, p) -> p
toBinaryOps _ u = error $ "cannot toBinaryOps: " ++ show u
-- | Defines a macro, given its pattern and expression.
addMacro :: Pattern -> Expr -> Parser ()
addMacro p e =
case p of
PSingle {} ->
modifyState $ \ps -> ps
{ psMacros =
( addMethod (Macro p e) (fst (psMacros ps))
, snd (psMacros ps)
)
}
PKeyword {} ->
modifyState $ \ps -> ps
{ psMacros =
( fst (psMacros ps)
, addMethod (Macro p e) (snd (psMacros ps))
)
}
_ -> error $ "impossible: addMacro: p is " ++ show p
-- | Parse a block of expressions from a given input string.
parser :: Parser [Expr]
parser = do
whiteSpace
es <- wsBlock pExpr
whiteSpace
eof
return es
-- | Same as `parser', but ignores a shebang at the start of the source.
fileParser :: Parser [Expr]
fileParser = do
optional (string "#!" >> manyTill anyToken (eol <|> eof))
parser