packages feed

hs-logo-0.5: src/Logo/Evaluator.hs

module Logo.Evaluator where

import Logo.Types

import qualified Data.Map as M

import Control.Monad (replicateM)
import Control.Applicative ((<$>), (<|>))
import Control.Arrow ((&&&), (***))
import Control.Monad.Trans (lift)

import Text.Parsec.Prim (runParserT, tokenPrim, getState, putState, modifyState)
import Text.Parsec.Combinator (many1, choice, chainl1)
import Text.Parsec.Error (ParseError)

-- ----------------------------------------------------------------------

--  Expression Evaluation

-- ----------------------------------------------------------------------

--  Expression               := RelationalExpression
--  RelationalExpression     := AdditiveExpression [ ( '=' | '<' | '>' | '<=' | '>=' | '<>' ) AdditiveExpression ... ]
--  AdditiveExpression       := MultiplicativeExpression [ ( '+' | '-' ) MultiplicativeExpression ... ]
--  MultiplicativeExpression := PowerExpression [ ( '*' | '/' | '%' ) PowerExpression ... ]
--  PowerExpression          := UnaryExpression [ '^' UnaryExpression ]
--  UnaryExpression          := ( '-' ) UnaryExpression
--                            | FinalExpression
--  FinalExpression          := string-literal
--                            | number-literal
--                            | list
--                            | variable-reference
--                            | procedure-call
--                            | '(' Expression ')'

evaluateWithContext :: [LogoToken] -> LogoContext -> TurtleIO (Either ParseError ([LogoToken], LogoContext))
evaluateWithContext tokens ctx = runParserT expression ctx "(stream)" tokens

evaluateTokens :: [LogoToken] -> LogoEvaluator LogoToken
evaluateTokens [] = return $ StrLiteral ""
evaluateTokens tokens = do
  ctx <- getState

  (t,s) <- lift $ do
      res <- evaluateWithContext tokens ctx
      case res of
        Left  e -> error $ show e
        Right r -> return r
  putState s
  return $ LogoList t

satisfy ::  (LogoToken -> Bool) -> LogoEvaluator LogoToken
satisfy f =
  tokenPrim (\c -> show [c])
            (\pos _ _ ->  pos)
            (\c -> if f c then Just c else Nothing)

logoToken :: LogoToken -> LogoEvaluator LogoToken
logoToken x = satisfy (==x)

anyLogoToken :: LogoEvaluator LogoToken
anyLogoToken = satisfy (const True)

expression :: LogoEvaluator ([LogoToken], LogoContext)
expression =  do
  t <- many1 relationalExpression
  s <- getState
  return (t,s)

relationalExpression :: LogoEvaluator LogoToken
relationalExpression = parseWithOperators ["<", ">", "=", "<=", ">=", "<>"] additiveExpression

additiveExpression :: LogoEvaluator LogoToken
additiveExpression = parseWithOperators ["+", "-"] multiplicativeExpression

multiplicativeExpression :: LogoEvaluator LogoToken
multiplicativeExpression = parseWithOperators ["*", "/", "%"] powerExpression

powerExpression :: LogoEvaluator LogoToken
powerExpression = parseWithOperators ["^"] finalExpression

finalExpression :: LogoEvaluator LogoToken
finalExpression =  anyLogoToken >>= evalFinal

evalFinal, evalList, eval :: LogoToken -> LogoEvaluator LogoToken

evalFinal (Identifier s) = dispatchFn s

evalFinal (VarLiteral v) = lookupVar v

evalFinal (LogoExpr e) = do
  LogoList res <- evaluateTokens e
  return $ head res

evalFinal token = return token

evalList (LogoList l) = evaluateTokens l
evalList _ = undefined

-- Forces evaluation of a token, even if it is a list
eval token = case token of
  LogoList _ -> evalList token
  _          -> evalFinal token

parseWithOperators :: [String] -> LogoEvaluator LogoToken  -> LogoEvaluator LogoToken
parseWithOperators operators parser = parser `chainl1` func
 where
  func  = do op <- choice $ map (logoToken . OperLiteral) operators
             return $ evalBinOp op

evalBinOp :: LogoToken -> LogoToken -> LogoToken -> LogoToken

-- Arithmetic
evalBinOp (OperLiteral "+") (NumLiteral l) (NumLiteral r) = NumLiteral (l + r)
evalBinOp (OperLiteral "-") (NumLiteral l) (NumLiteral r) = NumLiteral (l - r)
evalBinOp (OperLiteral "*") (NumLiteral l) (NumLiteral r) = NumLiteral (l * r)
evalBinOp (OperLiteral "/") (NumLiteral l) (NumLiteral r) = NumLiteral (l / r)
evalBinOp (OperLiteral "%") (NumLiteral l) (NumLiteral r) = NumLiteral $ fromIntegral ((truncate l `rem` truncate r) :: Integer )
evalBinOp (OperLiteral "^") (NumLiteral l) (NumLiteral r) = NumLiteral (l ** r)

-- Logical
evalBinOp (OperLiteral "<")  (NumLiteral l) (NumLiteral r) = StrLiteral (if l < r then "TRUE" else "FALSE")
evalBinOp (OperLiteral ">")  (NumLiteral l) (NumLiteral r) = StrLiteral (if l > r then "TRUE" else "FALSE")
evalBinOp (OperLiteral "=")  (NumLiteral l) (NumLiteral r) = StrLiteral (if l == r then "TRUE" else "FALSE")
evalBinOp (OperLiteral "<>") (NumLiteral l) (NumLiteral r) = StrLiteral (if l /= r then "TRUE" else "FALSE")
evalBinOp (OperLiteral "<=") (NumLiteral l) (NumLiteral r) = StrLiteral (if l <= r then "TRUE" else "FALSE")
evalBinOp (OperLiteral ">=") (NumLiteral l) (NumLiteral r) = StrLiteral (if l >= r then "TRUE" else "FALSE")

-- Undefined
evalBinOp op a b  = error $ "Evaluation undefined for " ++ show [op, a, b]

setLocals :: LogoSymbolTable -> LogoEvaluator ()
setLocals l = modifyState $ \s -> s { locals = l }

getLocals :: LogoEvaluator LogoSymbolTable
getLocals = locals <$> getState

evaluateInLocalContext :: LogoSymbolTable -> LogoEvaluator a -> LogoEvaluator a
evaluateInLocalContext localVars computation = do
  old <- getLocals
  setLocals $ localVars `M.union` old
  res <- computation
  setLocals old
  return res

lookupVar :: String -> LogoEvaluator LogoToken
lookupVar v = do
 (l,g) <-  (M.lookup v *** M.lookup v) . (locals &&& globals) <$> getState
 case l <|> g of
   Just x -> return x
   _      -> error $ "variable " ++ v ++ " not in scope"

dispatchFn :: String -> LogoEvaluator LogoToken
dispatchFn fn = do
  -- get function definition
  ctx <- getState
  let fns = functions ctx
      f = case M.lookup fn fns of
        Just x -> x
        _      -> error ("Function undefined: " ++ fn)
  -- find arity
  let (LogoFunctionDef a func) =  f
  -- get number of tokens
  -- FIXME evaludate the token before getting a list of expressions
  arguments <- replicateM a relationalExpression
  -- call function and update context
  func arguments