Reputation: 365
Edit: For clarification, all arithmetic expressions in the form of integer op integer
work as they should but variable op integer
results in the confusing result.
In the simple language I am writing, I am at a point in which I am trying to implement user defined variables. I am following the 'Write Yourself a Scheme in 48 Hours' a guide to structure my development process. I have successfully implement variable instantiation such that the behaviour of my expression x:=4
behaves like WYS48's (define x 4)
in that they both return 4.
When I try to use x
in an arithmetic expression however, I get an unusual result.
Henry > x:=4
4
Henry > <x+4>
76
At first I assumed it was adding the ASCII value of x to 4 but it isn't because the ASCII value for x is 120 and it's Hex is 78, so I know it's not that. I don't see where the mistake is however in the program. I suspect it may be in my function str2Int
mainly due to the subtraction of 48 in it. Below is my code to evaluate arithmetic, expressions, and the functions that I used to evaluate my x
. Subtracting 71 when the expression is x:=0
seems to work for that case an arithmetic is fine for that instantiation of x
but it's not a particularly optimal fix.
eval :: Env -> HenryVal -> IOThrowsError HenryVal
eval env val@(Atom _) = return val
eval env val@(ABinOp _) = return val
eval env (Assign var val) = eval env val >>= defineVar env var
eval env (ABinary op x y) = return $ evalABinOp env x op y
evalABinOp :: Env -> HenryVal -> ABinOp -> HenryVal -> HenryVal
evalABinOp env (Atom a) Add (Integer b) = Integer ((toInteger (str2Int'(str2HenryStr (a)))) + b )
str2Int' :: HenryVal -> Integer
str2Int' n = toInteger $ (ord (show n !! 1)) - 48
str2HenryStr :: String -> HenryVal
str2HenryStr s = String $ s
I'm not sure of it's relevancy but below is the code that I used to implement variable assignment
type Env = IORef [(String, IORef HenryVal)]
type ThrowsError = Either HenryError
type IOThrowsError = ExceptT HenryError IO
nullEnv :: IO Env
nullEnv = newIORef []
liftThrows :: ThrowsError a -> IOThrowsError a
liftThrows (Left err) = throwError err
liftThrows (Right val) = return val
runIOThrows :: IOThrowsError String -> IO String
runIOThrows action = runExceptT (trapError action) >>= return . extractValue
isBound :: Env -> String -> IO Bool
isBound envRef var = readIORef envRef >>= return . maybe False (const True) . lookup var
getVar :: Env -> String -> IOThrowsError HenryVal
getVar envRef var = do env <- liftIO $ readIORef envRef
maybe (throwError $ UnboundVar "Getting an unbound variable" var)
(liftIO . readIORef)
(lookup var env)
setVar :: Env -> String -> HenryVal -> IOThrowsError HenryVal
setVar envRef var value = do env <- liftIO $ readIORef envRef
maybe (throwError $ UnboundVar "Setting an unbound variable" var)
(liftIO . (flip writeIORef value))
(lookup var env)
return value
defineVar :: Env -> String -> HenryVal -> IOThrowsError HenryVal
defineVar envRef var value = do
alreadyDefined <- liftIO $ isBound envRef var
if alreadyDefined
then setVar envRef var value >> return value
else liftIO $ do
valueRef <- newIORef value
env <- readIORef envRef
writeIORef envRef ((var, valueRef) : env)
return value
bindVars :: Env -> [(String, HenryVal)] -> IO Env
bindVars envRef bindings = readIORef envRef >>= extendEnv bindings >>= newIORef
where extendEnv bindings env = liftM (++ env) (mapM addBinding bindings)
addBinding (var, value) = do ref <- newIORef value
return (var, ref)
Upvotes: 1
Views: 147
Reputation: 105935
Unless you have any other implementation of eval
, any variable will not get evaluated as its value for binary operations (aside from assignment). Let's have a look at eval
and evalABinOp
:
eval :: Env -> HenryVal -> IOThrowsError HenryVal
eval env val@(Atom _) = return val
eval env val@(ABinOp _) = return val
eval env (Assign var val) = eval env val >>= defineVar env var -- defineVar returns val
eval env (ABinary op x y) = return $ evalABinOp env x op y -- evalABinOp uses env?
evalABinOp :: Env -> HenryVal -> ABinOp -> HenryVal -> HenryVal
evalABinOp env (Atom a) Add (Integer b) = Integer ((toInteger (str2Int'(str2HenryStr (a)))) + b )
-- env is not used on the right hand side!
As you never use env
on the right hand side, we can be 100% sure that the Atom a
cannot be interpreted as a variable, but must be interpreted as a number or string (or as undefined variable). Nowhere in your code do you actually look up a
's value in the current environment. Instead, you transform x
into an integer:
str2Int' :: HenryVal -> Integer
str2Int' n = toInteger $ (ord (show n !! 1)) - 48
str2HenryStr :: String -> HenryVal
str2HenryStr s = String $ s
I neither have HenryVal
's definition nor its Show
instance at hand, so I can only assume that show (String "x")
results in "\"x\""
and thus "\"x\"" !! == 'x'
. ord 'x' - 48
is 120 - 48 = 72
. Add 4
, and you end up with 76
. The arithmetic works as intended. You just don't use the assigned value at all. Instead, you don't even interpret the Atom
as a variable, but as an ASCII digit of a single-digit number.
That being said, how would a solution look like? Well, something like that:
evalABinOp :: Env -> HenryVal -> ABinOp -> HenryVal -> IOThrowsError HenryVal
evalABinOp env (Integer a) Add (Integer b) = return $ Integer $ a + b
evalABinOp env (Atom a) op b@(Integer _) = getVar env a >>= (\c -> evalABinOp env c op b)
Instead of using a
immediately, we first lookup the value. Note that this will only work if your parser interprets a number as Integer
in your HenryVal
language. Note that there is no str2Int
magic going on: all of that should get handled long before we use x
, unless we want to allow addition of Strings.
Upvotes: 2