lamb/interp.hs

340 lines
10 KiB
Haskell
Raw Normal View History

2013-10-21 00:48:02 +00:00
-- Interpreter for the Lamb programming language
-- Copyright (c) 2013 darkf
-- Licensed under the terms of the zlib license, see LICENSE for details
2013-10-20 22:55:30 +00:00
module Interp where
2013-10-20 23:30:39 +00:00
import Prelude hiding (lookup)
2013-10-18 01:58:41 +00:00
import qualified Data.Map as M
2013-10-24 03:50:55 +00:00
import qualified Data.ByteString.Char8 as BSC
2013-10-24 04:29:43 +00:00
import qualified Network.Socket as SO
2013-10-22 22:10:34 +00:00
import Data.List (intercalate)
import Control.Monad.Trans (lift)
import Control.Monad.Trans.State (StateT, runStateT, evalStateT, get, put)
2013-10-24 04:03:36 +00:00
import System.IO (Handle, hPutStr, hGetLine, hFlush, hClose, hIsEOF, openBinaryFile, IOMode(..), stdout, stdin)
import System.IO.Unsafe (unsafePerformIO)
import AST
import Parser (parseProgram)
2013-10-18 01:58:41 +00:00
-- for Show
newtype BIF = BIF (Value -> InterpState Value)
instance Show BIF where show _ = "<built-in>"
instance Eq BIF where a == b = False
data Value = IntV Integer
2013-10-18 01:58:41 +00:00
| StrV String
| UnitV
2013-10-23 21:41:44 +00:00
| BoolV Bool
2013-10-20 04:34:30 +00:00
| StreamV Int
2013-10-22 22:10:34 +00:00
| TupleV [Value]
2013-10-18 20:24:00 +00:00
| ListV [Value]
| Builtin BIF
| FnV Env [(Pattern, AST)] -- closure pattern->body bindings
2013-10-21 05:37:58 +00:00
deriving (Eq)
2013-10-18 01:58:41 +00:00
type Env = [M.Map String Value] -- lexical environment (linked list)
type InterpState = StateT ([Handle], Env) IO -- interpreter state (open handles, global env)
2013-10-18 01:58:41 +00:00
emptyEnv = [M.empty]
-- look up a binding from the bottom up
2013-10-20 23:30:39 +00:00
lookup :: Env -> String -> Maybe Value
lookup [] _ = Nothing
lookup (env:xs) name =
case M.lookup name env of
Nothing -> lookup xs name
Just x -> Just x
2013-10-20 23:30:39 +00:00
-- bind in the local environment
2013-10-20 23:32:23 +00:00
bind :: Env -> String -> Value -> Env
bind (env:xs) name value = (M.insert name value env):xs
2013-10-20 23:32:23 +00:00
2013-10-21 05:37:58 +00:00
instance Show Value where
show (IntV i) = show i
show (StrV s) = show s
show (BoolV b) = show b
2013-10-22 22:10:34 +00:00
show (TupleV v) = "(" ++ intercalate "," (map show v) ++ ")"
2013-10-21 05:37:58 +00:00
show (ListV v) = show v
show (FnV _ _) = "<fn>"
2013-10-21 05:37:58 +00:00
show (StreamV _) = "<stream>"
show (Builtin _) = "<built-in>"
show UnitV = "()"
2013-10-21 00:24:51 +00:00
-- value operators
2013-10-18 01:58:41 +00:00
(IntV l) +$ (IntV r) = IntV (l + r)
(StrV l) +$ (StrV r) = StrV (l ++ r)
2013-10-23 22:06:57 +00:00
(ListV l) +$ (ListV r) = ListV (l ++ r)
2013-10-18 01:58:41 +00:00
l +$ r = error $ "cannot + " ++ show l ++ " and " ++ show r
2013-10-21 00:24:51 +00:00
(IntV l) -$ (IntV r) = IntV (l - r)
l -$ r = error $ "cannot - " ++ show l ++ " and " ++ show r
(IntV l) *$ (IntV r) = IntV (l * r)
l *$ r = error $ "cannot * " ++ show l ++ " and " ++ show r
(IntV l) /$ (IntV r) = IntV (l `div` r)
l /$ r = error $ "cannot / " ++ show l ++ " and " ++ show r
(IntV l) <$ (IntV r) = BoolV (l < r)
l <$ r = error $ "cannot < " ++ show l ++ " and " ++ show r
(IntV l) >$ (IntV r) = BoolV (l > r)
l >$ r = error $ "cannot > " ++ show l ++ " and " ++ show r
l ==$ r = BoolV (l == r)
l !=$ r = BoolV (l /= r)
-- some built-in functions
_fputstr (TupleV [StreamV h, StrV str]) = do
(handles,_) <- get
let handle = handles !! h
io <- lift $ hPutStr handle str >> hFlush handle
return UnitV
_fgetline (StreamV h) = do
(handles,_) <- get
let handle = handles !! h
str <- lift $ hGetLine handle
2013-10-24 06:36:24 +00:00
if last str == '\r' then -- remove trailing CR
return . StrV $ init str
else return $ StrV str
2013-10-24 03:50:55 +00:00
_fread (TupleV [StreamV h, IntV n]) = do
(handles,_) <- get
let handle = handles !! h
str <- lift $ BSC.hGet handle (fromIntegral n :: Int)
return . StrV $ BSC.unpack str
_fopen (TupleV [StrV path, StrV mode]) = do
(handles,env) <- get
let mode' = case mode of
"r" -> ReadMode
"w" -> WriteMode
"rw" -> ReadWriteMode
handle <- lift $ openBinaryFile path mode'
put (handles ++ [handle], env)
2013-10-24 04:03:36 +00:00
return . StreamV $ length handles
_feof (StreamV h) = do
(handles,_) <- get
let handle = handles !! h
isEof <- lift $ hIsEOF handle
return $ BoolV isEof
2013-10-24 03:50:55 +00:00
_fclose handle@(StreamV h) = do
(handles,_) <- get
let handle = handles !! h
lift $ hClose handle
return UnitV
2013-10-24 04:29:43 +00:00
_sockopen (TupleV [StrV host, IntV port]) = do
(handles,env) <- get
handle <- lift $ SO.withSocketsDo $ do
addr:_ <- SO.getAddrInfo Nothing (Just host) (Just $ show port)
sock <- SO.socket (SO.addrFamily addr) SO.Stream SO.defaultProtocol
SO.connect sock (SO.addrAddress addr)
handle <- SO.socketToHandle sock ReadWriteMode
return handle
2013-10-24 04:29:43 +00:00
put (handles ++ [handle], env)
return . StreamV $ length handles
_putstr str@(StrV _) = _fputstr $ TupleV [StreamV 0, str]
_getline UnitV = _fgetline (StreamV 1)
_print v = _putstr $ StrV $ show v ++ "\n"
2013-10-24 07:35:45 +00:00
_repr v = return . StrV $ show v
2013-10-21 00:25:38 +00:00
_itos (IntV i) = return $ StrV $ show i
_itos v = error $ "itos: not an int: " ++ show v
2013-10-24 02:33:44 +00:00
_loop args@(TupleV [fn@(FnV _ _), arg]) = do
v <- apply fn arg
if v == BoolV True then
_loop args
else return UnitV
_loop fn@(FnV _ _) = _loop $ TupleV [fn, UnitV]
initialState = ([stdout, stdin],
[M.fromList [("id", FnV emptyEnv [(VarP "x", Var "x")]),
2013-10-24 02:33:44 +00:00
("loop", Builtin $ BIF _loop),
2013-10-24 07:35:45 +00:00
("repr", Builtin $ BIF _repr),
("stdout", StreamV 0),
("stdin", StreamV 1),
("print", Builtin $ BIF _print),
("putstr", Builtin $ BIF _putstr),
("putstrln", Builtin $ BIF (\x -> _putstr $ x +$ StrV "\n")),
("getline", Builtin $ BIF _getline),
("fgetline", Builtin $ BIF _fgetline),
("fputstr", Builtin $ BIF _fputstr),
2013-10-24 03:50:55 +00:00
("fread", Builtin $ BIF _fread),
2013-10-24 04:03:36 +00:00
("feof", Builtin $ BIF _feof),
("fclose", Builtin $ BIF _fclose),
2013-10-24 03:50:55 +00:00
("fopen", Builtin $ BIF _fopen),
2013-10-24 04:29:43 +00:00
("sockopen", Builtin $ BIF _sockopen),
("itos", Builtin $ BIF _itos)]])
2013-10-18 03:46:34 +00:00
2013-10-18 01:58:41 +00:00
eval :: AST -> InterpState Value
eval (IntConst i) = return $ IntV i
eval (StrConst s) = return $ StrV s
2013-10-23 21:41:44 +00:00
eval (BoolConst b) = return $ BoolV b
2013-10-18 01:58:41 +00:00
eval UnitConst = return UnitV
2013-10-20 23:04:56 +00:00
eval (Block body) = foldr1 (>>) $ map eval body
2013-10-21 05:27:27 +00:00
eval (Cons a b) = do
a' <- eval a
b' <- eval b
case b' of
ListV v' -> return $ ListV $ a':v'
_ -> error "cons: RHS must be a list"
2013-10-18 20:24:00 +00:00
eval (ListConst v) =
mapM eval v >>= \xs ->
return $ ListV xs
2013-10-22 22:10:34 +00:00
eval (TupleConst v) = mapM eval v >>= return . TupleV
2013-10-23 21:43:30 +00:00
eval (IfExpr c t e) = eval c >>= \cond ->
case cond of
BoolV True -> eval t
BoolV False -> eval e
_ -> error "if: condition must be a boolean"
2013-10-20 23:30:39 +00:00
eval (Var var) = get >>= \(_,env) ->
case lookup env var of
2013-10-18 01:58:41 +00:00
Just v -> return v
Nothing -> error $ "unbound variable " ++ var
eval (Defun name fn) = do
(s,env) <- get
case lookup env name of
Nothing -> -- bind new fn
eval fn >>= \fn' ->
put (s, bind env name fn') >> return fn'
Just oldfn -> -- add pattern to old fn
let FnV cls oldpats = oldfn
Lambda [(pat, body)] = fn
newfn = FnV cls (oldpats ++ [(pat, body)]) in
put (s, bind env name newfn) >> return newfn
2013-10-18 01:58:41 +00:00
eval (Def name v') = do
v <- eval v'
2013-10-20 23:32:23 +00:00
(s,env) <- get
put (s, bind env name v)
2013-10-18 01:58:41 +00:00
return v
2013-10-18 08:14:13 +00:00
eval (Lambda pats) =
get >>= \(_,env) ->
if length env == 1 then -- if in global env just use [], denoting the current global scope
return $ FnV [] pats
else return $ FnV env pats
2013-10-18 08:14:13 +00:00
2013-10-21 00:24:51 +00:00
eval (Add l r) = do { l <- eval l; r <- eval r; return $ l +$ r }
eval (Sub l r) = do { l <- eval l; r <- eval r; return $ l -$ r }
eval (Mul l r) = do { l <- eval l; r <- eval r; return $ l *$ r }
eval (Div l r) = do { l <- eval l; r <- eval r; return $ l /$ r }
2013-10-18 01:58:41 +00:00
eval (Equals l r) = do { l <- eval l; r <- eval r; return $ l ==$ r }
eval (NotEquals l r) = do { l <- eval l; r <- eval r; return $ l !=$ r }
eval (LessThan l r) = do { l <- eval l; r <- eval r; return $ l <$ r }
eval (GreaterThan l r) = do { l <- eval l; r <- eval r; return $ l >$ r }
eval (Call name arg) = get >>= \(h,env) ->
2013-10-20 23:30:39 +00:00
case lookup env name of
Just fn@(FnV cls _) -> do
arg' <- eval arg
let cls' = if cls == [] then [last env] else cls -- if [], use current global env
put (h,cls') -- enter closure env
2013-10-23 08:59:15 +00:00
v <- apply fn arg'
put (h,env) -- restore env
2013-10-23 08:59:15 +00:00
return v
Just fn@(Builtin _) -> eval arg >>= apply fn
2013-10-18 03:46:34 +00:00
Nothing -> error $ "call: name " ++ name ++ " doesn't exist or is not a function"
eval x = error $ "eval: unhandled: " ++ show x
patternBindings :: Pattern -> Value -> Maybe (M.Map String Value)
2013-10-18 03:46:34 +00:00
patternBindings (VarP n) v = Just $ M.fromList [(n, v)]
2013-10-18 20:49:33 +00:00
patternBindings (IntP n) (IntV v)
| v == n = Just M.empty
| otherwise = Nothing
patternBindings (IntP n) _ = Nothing
2013-10-18 03:46:34 +00:00
patternBindings UnitP UnitV = Just M.empty
patternBindings UnitP _ = Nothing
2013-10-23 22:31:37 +00:00
patternBindings (StrP x) (StrV y)
| x == y = Just M.empty
| otherwise = Nothing
patternBindings (StrP _) _ = Nothing
2013-10-23 22:15:34 +00:00
-- cons on strings
patternBindings (ConsP x (ListP [])) (StrV (y:[])) = patternBindings x (StrV [y])
patternBindings (ConsP xp xsp) (StrV (x:xs)) =
do
xe <- patternBindings xp (StrV [x])
xse <- patternBindings xsp $ StrV xs
Just $ M.union xe xse
-- cons on lists
2013-10-18 21:06:27 +00:00
patternBindings (ConsP x (ListP [])) (ListV (y:[])) = patternBindings x y
2013-10-18 20:49:33 +00:00
patternBindings (ConsP xp xsp) (ListV (x:xs)) =
do
xe <- patternBindings xp x
xse <- patternBindings xsp $ ListV xs
Just $ M.union xe xse
2013-10-19 09:21:04 +00:00
patternBindings (ConsP _ _) _ = Nothing
2013-10-18 20:49:33 +00:00
-- lists
2013-10-23 22:31:37 +00:00
patternBindings (ListP []) (ListV (x:_)) = Nothing -- not enough patterns
2013-10-18 21:06:27 +00:00
patternBindings (ListP (_:_)) (ListV []) = Nothing -- not enough values
patternBindings (ListP []) (ListV []) = Just M.empty -- base case
patternBindings (ListP (x:xs)) (ListV (y:ys)) =
do
env <- patternBindings x y
env' <- patternBindings (ListP xs) (ListV ys)
Just $ M.union env' env
2013-10-21 00:04:12 +00:00
patternBindings (ListP _) _ = Nothing -- not a list
2013-10-18 21:06:27 +00:00
-- tuples
patternBindings (TupleP []) (TupleV (x:_)) = Nothing -- not enough patterns
patternBindings (TupleP (_:_)) (TupleV []) = Nothing -- not enough values
patternBindings (TupleP []) (TupleV []) = Just M.empty -- base case
patternBindings (TupleP (x:xs)) (TupleV (y:ys)) =
do
env <- patternBindings x y
env' <- patternBindings (TupleP xs) (TupleV ys)
Just $ M.union env' env
patternBindings (TupleP _) _ = Nothing -- not a tuple
2013-10-18 08:14:13 +00:00
2013-10-18 03:46:34 +00:00
-- applies a function
2013-10-18 08:14:13 +00:00
apply :: Value -> Value -> InterpState Value
apply (FnV _ pats) arg =
2013-10-18 08:14:13 +00:00
apply' pats
2013-10-18 03:46:34 +00:00
where
apply' [] = error $ "argument " ++ show arg ++ " doesn't satisfy any patterns"
2013-10-18 08:14:13 +00:00
apply' ((pat, body):xs) =
2013-10-18 03:46:34 +00:00
case patternBindings pat arg of
Just bindings -> -- satisfies
2013-10-18 03:46:34 +00:00
do
2013-10-20 04:34:30 +00:00
(s,env) <- get
let newenv = bindings:env
put (s, newenv)
eval body
Nothing -> -- doesn't satisfy this pattern
apply' xs
2013-10-18 03:46:34 +00:00
apply (Builtin (BIF fn)) arg = fn arg
evalProgram :: [AST] -> IO Value -- fold the state from each node and return the result
evalProgram nodes = evalStateT (foldr1 (>>) $ map eval nodes) initialState
2013-10-18 01:58:41 +00:00
evalString :: String -> IO Value
2013-10-19 09:21:34 +00:00
evalString program =
case parseProgram program of
Left err -> error $ show err
Right prg -> evalProgram prg