拿Haskell写的Interpreter For JavaScript

时间:2021-09-13 16:38:28

 

At first, it’s better not to meet you

So we cannot fall in love

Then it’s better not to know you

So I don’t need to become lovesick

时隔一年,偶尔看到Haskell虽然知道曾经拥有但是总会忘记,就像从未遇见那般。

遂决定收拾归纳一波以前学过的若干code,借以告慰即将逝去的Master时光~

***************************************

使用Haskell完成一个简易版本的JavaScript的Interpreter,支持赋值,判断以及循环语句等。

主要是锻炼Monad的实际使用

 

自己记录之余为了方便与诸君共同交流学习,详细的功能都有备注。(因为cnblog没有支持haskell,如下代码的插入采用Scale替代)

如下是主体部分的code,完整repository计划在毕业后push到github上,届时将更新并附上链接。

 

Note:需转载请务必通知作者,否则法律责任后果自负

 

 

module SubsInterpreter
       (
         Value(..)
       , runExpr
       )
       where

import SubsAst

import Control.Monad
import qualified Data.Map as Map
import Data.Map(Map)
import Data.Foldable

{-
   A value is either an integer, the special constant undefined,
   true, false, a string, or an array of values.
   Expressions are evaluated to values.
-}
data Value = IntVal Int
           | UndefinedVal
           | TrueVal | FalseVal
           | StringVal String
           | ArrayVal [Value]
           deriving (Eq, Show)

type Error = String
type Env = Map Ident Value
type Primitive = [Value] -> Either Error Value
type PEnv = Map FunName Primitive
type Context = (Env, PEnv)

{-
  Takes as input the Context type, that is, the Map for Value types
  and the Map for functions/operators. More specifically, the function
  returns an emptry context, that is, emptry Map of each.
  Specifically the binding is:
  Context: (Map String Value, Map String ([Value] -> Either Error Value))
  So it returns the Either monad
-}
initialContext :: Context
initialContext = (Map.empty, initialPEnv)
  where initialPEnv =
          Map.fromList [ ("===", equalOp)
                       , ("<", lowerOp)
                       , ("+", plusOp)
                       , ("*", multOp)
                       , ("-", minusOp)
                       , ("%", moduloOp)
                       , ("Array", mkArray)
                       ]

{-
  The data type for the Monad, which takes a single type constructor
  and has a single field. Inside the field is the function runSubsM,
  the accessor function of the monad.
-}
newtype SubsM a = SubsM {runSubsM :: Context -> Either Error (a, Env)}

{-
  The Functor instance of SubsM. It contains the
  default implementation for fmap, which binds the
  return of the f a invocation to the state.
-}
instance Functor SubsM where
  fmap f m = m >>= \a -> return (f a)

{-
  The Appplicative instance of SubsM.
  Contains simply some placeholders for pure and
  (<*>) functions
-}
instance Applicative SubsM where
  pure = return
  (<*>) = ap

{-
  The Monad instance of SubsM. The return function returns
  The new result a is kept in the unchanged state env.
  The bind operator is done with a monad m and a function f.
  Here, we run the accessor function runSubsM with the monad m
  and state x. Then, we pattern match whether it returns Right
  or Left from the Either monad. For Right, we update the
  primitive environment and the new value that return from (f a)
-}
instance Monad SubsM where
  return a = SubsM (\(env, _) -> Right (a, env))
  m >>= f = SubsM (\x -> case runSubsM m x of
                    Right (a, env) -> let (_, penv) = x
                                      in runSubsM (f a) (env, penv)
                    Left err -> Left err
                )
  fail s = SubsM (\_ -> Left s)

{-
  Compares for structural equality without type coercions
  Comparison of number and string will always yield false
  The type binding alias is: [Value] -> Either Error Value
-}
equalOp :: Primitive
equalOp vals =
  case vals of
    [StringVal x, StringVal y] -> if x == y then return TrueVal
                            else return FalseVal
    [IntVal x, IntVal y] -> if x == y then return TrueVal
                            else return FalseVal
    [IntVal _, StringVal _] -> return FalseVal
    [StringVal _, IntVal _] -> return FalseVal
    [UndefinedVal, UndefinedVal] -> return UndefinedVal
    [TrueVal, FalseVal] -> return FalseVal
    [FalseVal, TrueVal] -> return FalseVal
    [FalseVal, FalseVal] -> return TrueVal
    [TrueVal, TrueVal] -> return TrueVal
    [ArrayVal a, ArrayVal b] -> if a == b then return TrueVal
                                else return FalseVal
    _ -> fail "Invalid equal operation"

{-
  Arguments for < should either be of the same type,
  e.g. both strings or both int, and for strings they should be
  compared in lexicographical order.
-}
lowerOp :: Primitive
lowerOp vals =
  case vals of
    [StringVal x, StringVal y] -> if x < y then return TrueVal
                            else return FalseVal
    [IntVal x, IntVal y] -> if x < y then return TrueVal
                            else return FalseVal
    [UndefinedVal, UndefinedVal] -> return UndefinedVal
    [TrueVal, FalseVal] -> return FalseVal
    [FalseVal, TrueVal] -> return TrueVal
    [FalseVal, FalseVal] -> return FalseVal
    [TrueVal, TrueVal] -> return FalseVal
    _ -> fail "Invalid lower than operation"

{-
  Somewhat strongly typed, no addition of boolean and integer for example.
  Except for addition: ok to add two strings or a string
  and a number in any order. Remember the conversation first.
  For two strings it is string concatenation.
-}
plusOp :: Primitive
plusOp vals =
  case vals of
    [StringVal x, StringVal y] -> return $ StringVal (x++y)
    [IntVal x, IntVal y] -> return $ IntVal (x+y)
    [StringVal x, IntVal y] -> return $ StringVal (x ++ show y)
    [IntVal x, StringVal y] -> return $ StringVal (show x ++ y)
    [ArrayVal x, ArrayVal y] -> return $ ArrayVal (x++y)
    [TrueVal, FalseVal] -> return FalseVal
    [FalseVal, TrueVal] -> return FalseVal
    [UndefinedVal, UndefinedVal] -> return UndefinedVal
    _ -> fail "Invalid plus operation"

{-
  Covering the multiplication operator for two integers
  and also for two Arrays. However, for the array as input
  we recursively evaluated the deconstructed types to IntVal
  and then execute the operation.
-}
multOp :: Primitive
multOp vals = case vals of
                [IntVal x, IntVal y] -> return $ IntVal (x*y)
                [ArrayVal x, ArrayVal y] -> do
                                               a <- multOp x
                                               b <- multOp y
                                               Right (ArrayVal (a : [b]))
                _ -> fail "Illegal multiplication operation.\
                          \Can only be two integers"

{-
  Covering the subtraction operator for two integers
  and also for two Arrays. However, for the array as input
  we recursively evaluate the deconstructed types to IntVal
  and then execute the operation.
-}
minusOp :: Primitive
minusOp vals = case vals of
                 [IntVal x, IntVal y] -> return $ IntVal (x-y)
                 [ArrayVal x, ArrayVal y] -> do
                                                a <- minusOp x
                                                b <- minusOp y
                                                Right (ArrayVal (a : [b]))
                 _ -> fail "Illegal minus operations. Can only be two integers"

{-
  Covering the modulo operator for two integers
  and also for two Arrays. However, for the array as input
  we recursively evaluate the deconstructed types to IntVal
  and then execute the operation.
-}
moduloOp :: Primitive
moduloOp vals = case vals of
                  [IntVal x, IntVal y] -> return $ IntVal (x `mod` y)
                  [ArrayVal x, ArrayVal y] -> do
                                                 a <- moduloOp x
                                                 b <- moduloOp y
                                                 Right (ArrayVal (a : [b]))
                  _ -> fail "Illegal modulo operation. Can only be two integers"

{-
  Function for making an array of undefined values
  given the non negative integer n
-}
mkArray :: Primitive
mkArray [IntVal n] | n >= 0 = return $ ArrayVal (replicate n UndefinedVal)
mkArray _ = Left "Array() called with wrong number or type of arguments"

{-
  A function that given a function f
  updates the environment/state
-}
modifyEnv :: (Env -> Env) -> SubsM ()
modifyEnv f = SubsM (\(env, _) -> Right ((), f env))

{-
  Given an identifier and a corresponding value
  We insert these in the Map in the monad, thus,
  we modify the environment/state
-}
putVar :: Ident -> Value -> SubsM ()
putVar ident val = modifyEnv (Map.insert ident val)

{-
  Given an identifier we get the retrieved
  value, provided it exists, in the monad
-}
getVar :: Ident -> SubsM Value
getVar name = do
  s <- SubsM (\(env, _) -> Right (env, env))
  case Map.lookup name s of
    Just a  -> return a
    Nothing -> fail "Variable name not in scope"

{-
  Given a function name, we look it up the
  in the Map, return the Map with the monad type
  SubsM (Either monad)
-}
getFunction :: FunName -> SubsM Primitive
getFunction name = do
  s <- SubsM (\(env, penv) -> Right (penv, env))
  case Map.lookup name s of
    Just n  -> return n
    Nothing -> fail "Function name not in scope"

{-
  evalExpr evaluates the various expressions to their corresponding
  Value in the monad.
-}
evalExpr :: Expr -> SubsM Value
evalExpr (Number x) = return (IntVal x)
evalExpr (String s) = return (StringVal s)
evalExpr (Array []) = return (ArrayVal [])
evalExpr (Array (x:xs)) = do
                             x' <- evalExpr x
                             xs' <- evalExpr (Array xs)
                             case xs' of
                               ArrayVal y -> return (ArrayVal (x':y))
                               _ -> fail "Evaluating array expected an ArrayVal\
                                         \,but was not given"
evalExpr TrueConst = return TrueVal
evalExpr FalseConst = return FalseVal
evalExpr Undefined = return UndefinedVal
evalExpr (Var x) = getVar x
evalExpr (Assign ident expr) =  do
                                   xpeval <- evalExpr expr
                                   putVar ident xpeval
                                   return xpeval
evalExpr (Call name expr) = do
                               fn <- getFunction name
                               values <- mapM evalExpr expr
                               case fn values of
                                 Right y -> return y
                                 Left _ -> fail "Invalid function call"
evalExpr (Comma expr1 expr2) = do
                                  _ <- evalExpr expr1
                                  evalExpr expr2
evalExpr (Compr compr) = do
                            x <- evalCompr compr
                            return (ArrayVal x)

{-
  evaluating the comprehension mechanism
  taking as an input the type and returning
  a list of Value in the SubsM monad. We have patterns
  for the three types and here the ACFor is the most complex.
  In case of an array evaluate each value in it and then concatenate
  In case of a string we do similarly, however, we get for each string
  an additional list layer
-}
evalCompr :: ArrayCompr -> SubsM [Value]
evalCompr arrcompr =
  case arrcompr of
    ACBody expr -> do
                      x <- evalExpr expr
                      return [ArrayVal [x]]
    ACFor ident expr arrcomp ->
      do
         xpeval <- evalExpr expr
         var <- getVarMaybe ident
         retVal <- case xpeval of
                     ArrayVal vals -> do
                                         a <- mapM (helpFun arrcomp ident) vals
                                         _ <- evalCompr arrcomp
                                         return (concatMap f a)
                     StringVal s -> do
                                      _ <- putVar ident (StringVal s)
                                      _ <- evalCompr arrcomp
                                      let x = ArrayVal [StringVal s]
                                      return (concatMap f [[x]])
                     _ -> fail "Only Arrays and Strings allowed\
                               \in for comprehensions"
         Data.Foldable.forM_ var (putVar ident)
         -- when (isJust var) (putVar ident (fromJust var))
         return retVal
           where f [ArrayVal x] = x
                 f x = x
                 helpFun arrc idier val = do
                           _ <- putVar idier val
                           evalCompr arrc
    ACIf expr arrcomp ->
      do
         xpeval <- evalExpr expr
         case xpeval of
           TrueVal -> evalCompr arrcomp
           FalseVal -> return [ArrayVal []]
           _ -> fail "If stmt not working"

{-
  Function used to invoke whether variable exists in
  the Map or not. getVar can't be used, as it would yield
  an error and not resume execution.
-}
getVarMaybe :: Ident -> SubsM (Maybe Value)
getVarMaybe name = do
  s <- SubsM (\(env, _) -> Right (env, env))
  case Map.lookup name s of
    Just a  -> return (Just a)
    Nothing -> return Nothing

{-
  Running an expression, also running the initialContext function
  with empty Maps, eventually returning the Either monad of either
  Error or Value.
-}
runExpr :: Expr -> Either Error Value
runExpr expr =
  case runSubsM (evalExpr expr) initialContext of
    Right (a, _) -> Right a
    Left err -> fail err