packages feed

Elm-0.9: compiler/Parse/Binop.hs

module Parse.Binop (binops, infixStmt, OpTable) where

import Control.Applicative ((<$>))
import Control.Monad.Error
import Data.List (intercalate)
import qualified Data.Map as Map

import SourceSyntax.Location (merge)
import SourceSyntax.Expression (LExpr, Expr(Binop))
import SourceSyntax.Declaration (Assoc(..))
import Text.Parsec
import Parse.Helpers

type OpTable = [(Int, Assoc, String)]

preludeTable :: OpTable
preludeTable =
  [ (9, R, ".")
  , (8, R, "^")
  , (7, L, "*"), (7, L, "/"), (7, L, "mod"), (7, L, "div"), (7, L, "rem")
  , (6, L, "+"), (6, L, "-")
  , (5, R, "::"), (5, R, "++")
  , (4, N, "<="), (4, N, ">="), (4, N, "<")
  , (4, N, "=="), (4, N, "/="), (4, N, ">")
  , (4, L, "~"), (4, L, "<~")
  , (3, R, "&&")
  , (2, R, "||")
  , (0, R, "<|"), (0, L, "|>")
  ]

opLevel :: OpTable -> String -> Int
opLevel table op = Map.findWithDefault 9 op dict
    where dict = Map.fromList (map (\(lvl,_,op) -> (op,lvl)) table)

opAssoc :: OpTable -> String -> Assoc
opAssoc table op = Map.findWithDefault R op dict
    where dict = Map.fromList (map (\(_,assoc,op) -> (op,assoc)) table)

hasLevel :: OpTable -> Int -> (String, LExpr t v) -> Bool
hasLevel table n (op,_) = opLevel table op == n

binops :: OpTable
       -> IParser (LExpr t v)
       -> IParser (LExpr t v)
       -> IParser String
       -> IParser (LExpr t v)
binops table term last anyOp =
    do e <- term
       split (table ++ preludeTable) 0 e =<< nextOps
    where
      nextOps = choice [ commitIf (whitespace >> anyOp) $ do
                           whitespace ; op <- anyOp ; whitespace
                           expr <- Left <$> try term <|> Right <$> last
                           case expr of
                             Left t -> (:) (op,t) <$> nextOps
                             Right e -> return [(op,e)]
                       , return [] ]

split :: OpTable
      -> Int
      -> LExpr t v
      -> [(String, LExpr t v)]
      -> IParser (LExpr t v)
split _ _ e []  = return e
split table n e eops = do
  assoc <- getAssoc table n eops
  es <- sequence (splitLevel table n e eops)
  let ops = map fst (filter (hasLevel table n) eops)
  case assoc of R -> joinR es ops
                _ -> joinL es ops

splitLevel :: OpTable -> Int -> LExpr t v -> [(String, LExpr t v)]
           -> [IParser (LExpr t v)]
splitLevel table n e eops =
    case break (hasLevel table n) eops of
      (lops, (op,e'):rops) ->
          split table (n+1) e lops : splitLevel table n e' rops
      (lops, []) -> [ split table (n+1) e lops ]

joinL :: [LExpr t v] -> [String] -> IParser (LExpr t v)
joinL [e] [] = return e
joinL (a:b:es) (op:ops) = joinL (merge a b (Binop op a b) : es) ops
joinL _ _ = fail "Ill-formed binary expression. Report a compiler bug."

joinR :: [LExpr t v] -> [String] -> IParser (LExpr t v)
joinR [e] [] = return e
joinR (a:b:es) (op:ops) = do e <- joinR (b:es) ops
                             return (merge a e (Binop op a e))
joinR _ _ = fail "Ill-formed binary expression. Report a compiler bug."

getAssoc :: OpTable -> Int -> [(String,LExpr t v)] -> IParser Assoc
getAssoc table n eops
    | all (==L) assocs = return L
    | all (==R) assocs = return R 
    | all (==N) assocs = case assocs of [_] -> return N
                                        _   -> fail msg
  where levelOps = filter (hasLevel table n) eops
        assocs = map (opAssoc table . fst) levelOps
        msg = concat [ "Conflicting precedence for binary operators ("
                     , intercalate ", " (map fst eops), "). "
                     , "Consider adding parentheses to disambiguate." ]

infixStmt :: IParser (Int, Assoc, String)
infixStmt =
  let infx str assoc = try (reserved ("infix" ++ str) >> return assoc) in
  do assoc <- choice [ infx "l" L, infx "r" R, infx "" N ]
     whitespace
     prec <- do n <- digit ; return (read [n] :: Int)
     whitespace
     op <- anyOp
     return (prec, assoc, op)