packages feed

atomo-0.4: src/Atomo/Parser/Expr.hs

module Atomo.Parser.Expr where

import Control.Monad.State
import Data.Maybe (fromJust)
import Text.Parsec

import Atomo.Parser.Base
import Atomo.Pattern
import Atomo.Types hiding (keyword, option, particle, string)
import qualified Atomo.Types as T


-- | The default precedence for an operator (5).
defaultPrec :: Integer
defaultPrec = 5

-- | Parses any Atomo expression.
pExpr :: Parser Expr
pExpr = choice
    [ pOperator
    , pMacro
    , pForMacro
    , pDispatch
    ]
    <?> "expression"

-- | Parses any Atomo literal value.
pLiteral :: Parser Expr
pLiteral = choice
    [ pThis
    , pBlock
    , pMacroQuote
    , pList
    , pTuple
    , 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 (EPrimitive Nothing) primitive

-- | The @this@ keyword, i.e. the toplevel object literal.
pThis :: Parser Expr
pThis = tagged (reserved "this" >> return (ETop Nothing))
    <?> "this"

-- | An expression literal.
--
-- Example: @'1@, @'(2 + 2)@
pQuoted :: Parser Expr
pQuoted = tagged (do
    punctuation '\''
    e <- pSpacedExpr
    return (EPrimitive Nothing (Expression e)))
    <?> "quoted expression"

-- | 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
    punctuation '`'
    modifyState $ \ps -> ps { psInQuote = True }
    e <- pSpacedExpr
    modifyState $ \ps -> ps { psInQuote = False }
    return (EQuote Nothing e))
    <?> "quasiquoted expression"

-- | An unquote expression, used inside a quasiquote.
--
-- Examples: @~1@, @~(2 + 2)@
pUnquoted :: Parser Expr
pUnquoted = tagged (do
    punctuation '~'
    iq <- fmap psInQuote getState
    modifyState $ \ps -> ps { psInQuote = False }
    e <- pSpacedExpr
    modifyState $ \ps -> ps { psInQuote = iq }
    return (EUnquote Nothing e))
    <?> "unquoted expression"

-- | 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

-- | A single message sent to the toplevel object.
simpleDispatch :: Parser Expr
simpleDispatch = tagged $ do
    name <- identifier
    return (EDispatch Nothing (single name (ETop Nothing)))

-- | The for-macro "pragma."
--
-- Example: @for-macro 1 print@
pForMacro :: Parser Expr
pForMacro = tagged (do
    reserved "for-macro"
    e <- pExpr
    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 (liftM (fromJust . toMacroPattern) pExpr)
    e <- pExpr
    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 integer
            return (a, prec)
        , liftM ((,) ALeft) integer
        ]

    ops <- many operator

    forM_ ops $ \name ->
        modifyState $ \ps -> ps
            { psOperators =
                (name, info) : psOperators ps
            }

    return (uncurry (EOperator Nothing ops) info))
    <?> "operator declaration"

-- | A particle literal.
--
-- Examples: @\@foo@, @\@(bar: 2)@, @\@bar:@, @\@(foo: 2 bar: _)@
pParticle :: Parser Expr
pParticle = tagged (do
    msg <- choice
        [ do
            punctuation '@'
            liftM toMsg (cSingle <|> try cKeyword) <|> filledHead
        , liftM toMsg particle
        ]

    return (EParticle Nothing msg))
    <?> "particle"
  where
    filledHead = do
        p <- parens pDispatch
        case p of
            EDispatch { eMessage = Single i n t os } ->
                return (Single i n (toRole t) (map toOpt os))
            EDispatch { eMessage = Keyword i ns ts os } ->
                return (Keyword i ns (map toRole ts) (map toOpt os))
            _ -> fail "non-message in particle"

    toOpt (Option i n e) = Option i n (toRole e)

    toMsg (CSingle n os) =
        single' n Nothing (map toOpt os)
    toMsg (CKeyword ns es os) =
        keyword' ns (Nothing:map toRole es) (map toOpt os)

    toRole (EDispatch { eMessage = Single { mName = "_", mTarget = ETop {} } }) =
        Nothing
    toRole e = Just e

-- | 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 (blockOf pExpr)))
    <?> "list"

-- | A comma-separated list of zero or two or more expressions, surrounded by
-- parentheses.
--
-- Examples: @(1, $a)@
pTuple :: Parser Expr
pTuple = (tagged . liftM (ETuple Nothing) . try . parens $ choice
    [ do
        v <- pExpr
        end
        vs <- blockOf1 pExpr
        return (v:vs)
    , return []
    ])
    <?> "tuple"

-- | 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
    as <- option [] (try $ manyTill pSpacedExpr (punctuation '|' >> optional end))
    es <- blockOf pExpr
    return (EBlock Nothing (map (fromJust . toPattern) as) es)

pMacroQuote :: Parser Expr
pMacroQuote = tagged $ do
    (name, raw, flags) <- macroQuote
    return (EMacroQuote Nothing name raw flags)

-- | Parse an expression possibly up to an operator dispatch.
--
-- That is, this may be:
--  - an operator dispatch
--  - a keyword dispatch
--  - a single dispatch
--  - a literal or parenthesized expression
pDispatch :: Parser Expr
pDispatch = tagged (do
    s <- do
        h <- choice
            [ try (lookAhead (keyword <|> operator)) >> return (ETop Nothing)
            , pmSingle
            ]

        case h of
            EDispatch { eMessage = m@(Single { mOptionals = [] }) } -> do
                os <- pdOptionals
                return h { eMessage = m { mOptionals = os } }
            _ -> return h

    k <- followedBy keyword
    if k
        then keywordNext s
        else do

    o <- followedBy operator
    if o
        then operatorNext s
        else return s)
    <?> "dispatch"

-- | Optional keyword arguments.
pdOptionals :: Parser [Option Expr]
pdOptionals = do
    os <- many (optionSegment prKeyword <|> optionFlag)
    return (map (uncurry T.option) os)

-- | Parse an expr up to a single dispatch.
--
-- That is, this may end up just being a literal or a parenthesized expression.
pmSingle :: Parser Expr
pmSingle = do
    target <- pSpacedExpr

    let restOf =
            case target of
                EDispatch {} -> many
                _ -> many1

    chain <- option [] (try $ restOf (cSingle <|> cKeyword))

    if null chain
        then return target
        else return (dispatches target chain)
  where
    dispatches = foldl sendTo

    sendTo t (CSingle n os) =
        EDispatch Nothing (single' n t os)
    sendTo t (CKeyword ns es os) =
        EDispatch Nothing (keyword' ns (t:es) os)

-- | Parse an expr up to a keyword dispatch.
--
-- That is, this may end up just being a single dispatch, or a literal, or
-- a parenthesized expression, but it will not parse the rest of an operator
-- dispatch.
pmKeyword :: Parser Expr
pmKeyword = do
    t <- choice
        [ try (lookAhead keyword) >> return (ETop Nothing)
        , pmSingle
        ]

    k <- followedBy keyword
    if not k
        then return t
        else do

    phKeyword t

-- | Headless operator dispatch
phOperator :: Parser Expr
phOperator = do
    n <- operator
    t <- prOperator
    os <- pdOptionals
    return (EDispatch Nothing (keyword' [n] [ETop Nothing, t] os))

-- | Headless keyword dispatch
phKeyword :: Expr -> Parser Expr
phKeyword t = do
    (ns, ts) <- liftM unzip $ many1 (keywordSegment prKeyword)
    os <- pdOptionals
    return $ EDispatch Nothing (keyword' ns (t:ts) os)

-- | Keyword (non-first) roles
prKeyword :: Parser Expr
prKeyword = phOperator <|> phKeyword (ETop Nothing) <|> pmSingle
    <?> "keyword role"

-- | Operator (non-first) roles
prOperator :: Parser Expr
prOperator = phOperator <|> phKeyword (ETop Nothing) <|> pmKeyword
    <?> "operand"

-- | Parse the rest of a keyword dispatch, possibly followed by an operator
-- dispatch, with a given first role.
keywordNext :: Expr -> Parser Expr
keywordNext t = do
    keywd <- phKeyword t

    o <- followedBy operator
    if o
        then operatorNext keywd
        else return keywd

-- | Parse the rest of an operator dispatch, with a given first role.
operatorNext :: Expr -> Parser Expr
operatorNext f = do
    (ns, ts, os) <- liftM unzip3 . many1 $ do
        n <- operator
        t <- prOperator
        os <- pdOptionals
        return (n, t, os)

    ops <- fmap psOperators getState
    return (EDispatch Nothing (opChain ops ns (f:ts) os))

-- | Chained single message
cSingle :: Parser Chained
cSingle = choice
    [ liftM (flip CSingle []) (anyReserved <|> identifier)
    , try . parens $ do
        n <- anyReserved <|> identifier
        os <- pdOptionals
        return (CSingle n os)
    ]

-- | Chained keyword message
cKeyword :: Parser Chained
cKeyword = parens $ do
    (ns, es) <- choice
        [ liftM unzip $ many1 (keywordSegment prKeyword)
        , do
            o <- operator
            e <- prOperator
            return ([o], [e])
        ]

    os <- pdOptionals

    return (CKeyword ns es os)

-- | Work out precadence, associativity, etc. for binary dispatch.
-- Takes the operator table, a list of operators, their operands, and their
-- options, and creates a message dispatch with proper associativity/precedence
-- worked out.
--
-- Operators taking optional values are treated with highest precedence
-- regardless of their settings in the operator table.
--
-- For example, @1 -> 2 &x: 3 * 5@ is @(1 -> 2 &x: 3) * 5@, rather than
-- @1 -> (2 * 5) &x: 3@
opChain :: Operators -> [String] -> [Expr] -> [[Option Expr]] -> Message Expr
opChain _ [] [EDispatch { eMessage = done }] [] = done
opChain _ [a] [w, x] [opts] = keyword' [a] [w, x] opts
opChain os (a:b:cs) (w:x:y:zs) (aopts:bopts:opts)
    | nextFirst =
        keyword' [a] [w, disp $ opChain os (b:cs) (x:y:zs) (bopts:opts)] aopts
    | otherwise =
        opChain os (b:cs) (disp (keyword' [a] [w, x] aopts):y:zs) (bopts:opts)
  where
    disp = EDispatch Nothing

    nextFirst =
        null aopts && (prec b > prec a || (assoc a == ARight && prec b == prec a))

    assoc o = maybe ALeft fst (lookup o os)
    prec o = maybe defaultPrec snd (lookup o os)
opChain _ ns ts oss = error $ "opChain: " ++ show (ns, ts, oss)

-- | Parse a block of expressions from a given input string.
parser :: Parser [Expr]
parser = do
    es <- blockOf pExpr
    endOfFile
    return es