EtaMOO-0.1.0.0: src/MOO/Compiler.hs
{-# LANGUAGE OverloadedStrings #-}
-- | Compiling abstract syntax trees into 'MOO' computations
module MOO.Compiler ( compile, evaluate ) where
import Control.Monad (when, unless, void, liftM)
import Control.Monad.Cont (callCC)
import Control.Monad.Reader (asks, local)
import Control.Monad.State (gets)
import qualified Data.Map as M
import qualified Data.Text as T
import qualified Data.Vector as V
import MOO.Types
import MOO.AST
import MOO.Task
import MOO.Builtins
import MOO.Object
-- | Compile a complete MOO program into a computation in the 'MOO' monad that
-- returns whatever the MOO program returns.
compile :: Program -> MOO Value
compile (Program stmts) = callCC $ compileStatements stmts
compileStatements :: [Statement] -> (Value -> MOO Value) -> MOO Value
compileStatements (statement:rest) yield = catchDebug $ case statement of
Expression lineNumber expr -> do
setLineNumber lineNumber
evaluate expr
compile' rest
If lineNumber cond (Then thens) elseIfs (Else elses) -> runTick >> do
compileIf ((lineNumber, cond, thens) : map elseIf elseIfs) elses
compile' rest
where elseIf (ElseIf lineNumber cond thens) = (lineNumber, cond, thens)
compileIf ((lineNumber,cond,thens):conds) elses = do
setLineNumber lineNumber
cond' <- truthOf `liftM` evaluate cond
if cond' then compile' thens
else compileIf conds elses
compileIf [] elses = compile' elses
ForList lineNumber var expr body -> do
setLineNumber lineNumber
expr' <- evaluate expr
elts <- case expr' of
Lst elts -> return $ V.toList elts
_ -> raise E_TYPE
callCC $ \break -> do
pushLoopContext (Just var') (Continuation break)
loop var' elts (compile' body)
popContext
compile' rest
where var' = T.toCaseFold var
loop var (elt:elts) body = runTick >> do
storeVariable var elt
callCC $ \continue -> do
setLoopContinue (Continuation continue)
body
loop var elts body
loop _ [] _ = return nothing
ForRange lineNumber var (start, end) body -> do
setLineNumber lineNumber
start' <- evaluate start
end' <- evaluate end
(ty, s, e) <- case (start', end') of
(Int s, Int e) -> return (Int . fromIntegral, toInteger s, toInteger e)
(Obj s, Obj e) -> return (Obj . fromIntegral, toInteger s, toInteger e)
(_ , _ ) -> raise E_TYPE
callCC $ \break -> do
pushLoopContext (Just var') (Continuation break)
loop var' ty s e (compile' body)
popContext
compile' rest
where var' = T.toCaseFold var
loop var ty i end body
| i > end = return nothing
| otherwise = runTick >> do
storeVariable var (ty i)
callCC $ \continue -> do
setLoopContinue (Continuation continue)
body
loop var ty (succ i) end body
While lineNumber var expr body -> do
callCC $ \break -> do
pushLoopContext var' (Continuation break)
loop lineNumber var' (evaluate expr) (compile' body)
return nothing
popContext
compile' rest
where var' = fmap T.toCaseFold var
loop lineNumber var expr body = runTick >> do
setLineNumber lineNumber
expr' <- expr
maybe return storeVariable var expr'
when (truthOf expr') $ do
callCC $ \continue -> do
setLoopContinue (Continuation continue)
body
loop lineNumber var expr body
Fork lineNumber var delay body -> runTick >> do
setLineNumber lineNumber
delay' <- evaluate delay
usecs <- case delay' of
Int secs
| secs < 0 -> raise E_INVARG
| otherwise -> return (fromIntegral secs * 1000000)
Flt secs
| secs < 0 -> raise E_INVARG
| otherwise -> return (ceiling $ secs * 1000000)
_ -> raise E_TYPE
world <- getWorld
gen <- newRandomGen
taskId <- liftSTM $ newTaskId world gen
case var of
Just ident -> void $ storeVariable ident (Int $ fromIntegral taskId)
Nothing -> return ()
forkTask taskId usecs (compileStatements body return)
compile' rest
Break name -> breakLoop (fmap T.toCaseFold name)
Continue name -> continueLoop (fmap T.toCaseFold name)
Return _ Nothing -> runTick >> yield nothing
Return lineNumber (Just expr) -> runTick >> do
setLineNumber lineNumber
yield =<< evaluate expr
TryExcept body excepts -> runTick >> do
excepts' <- mapM compileExcepts excepts
compile' body `catchException` dispatch excepts'
compile' rest
where compileExcepts (Except lineNumber var codes handler) = do
codes' <- case codes of
ANY -> return Nothing
Codes args -> setLineNumber lineNumber >> Just `liftM` expand args
return (codes', var, compile' handler)
dispatch ((codes, var, handler):next)
except@(Exception code message value)
| maybe True (code `elem`) codes = \(Stack errorFrames) -> do
Stack currentFrames <- gets stack
let traceback = formatFrames True $ take stackLen errorFrames
stackLen = length errorFrames - length currentFrames + 1
errorInfo = fromList [code, Str message, value, traceback]
maybe return storeVariable var errorInfo
handler
| otherwise = dispatch next except
dispatch [] except = passException except
TryFinally body (Finally finally) -> runTick >> do
let finally' = compile' finally
pushTryFinallyContext finally'
compile' body `catchException` \except callStack -> do
popContext
finally'
passException except callStack
popContext
finally'
compile' rest
where compile' ss = compileStatements ss yield
compileStatements [] _ = return nothing
catchDebug :: MOO Value -> MOO Value
catchDebug action =
action `catchException` \except@(Exception code _ _) callStack -> do
debug <- frame debugBit
if debug then passException except callStack else return code
-- | Compile a MOO expression into a computation in the 'MOO' monad. If a MOO
-- exception is raised and the current verb frame's debug bit is not set,
-- return the error code as a MOO value rather than propagating the exception.
evaluate :: Expr -> MOO Value
evaluate (Literal value) = return value
evaluate expr@Variable{} = catchDebug $ fetch (lValue expr)
evaluate expr = runTick >>= \_ -> catchDebug $ case expr of
List args -> fromList `liftM` expand args
PropRef{} -> fetch (lValue expr)
Assign what expr -> store (lValue what) =<< evaluate expr
ScatterAssign items expr -> do
expr' <- evaluate expr
case expr' of
Lst v -> scatterAssign items v
_ -> raise E_TYPE
VerbCall target vname args -> do
target' <- evaluate target
oid <- case target' of
Obj oid -> return oid
_ -> raise E_TYPE
vname' <- evaluate vname
name <- case vname' of
Str name -> return name
_ -> raise E_TYPE
callVerb oid oid name =<< expand args
BuiltinFunc func args -> callBuiltin (T.toCaseFold func) =<< expand args
a `Plus` b -> binary plus a b
a `Minus` b -> binary minus a b
a `Times` b -> binary times a b
a `Divide` b -> binary divide a b
a `Remain` b -> binary remain a b
a `Power` b -> binary power a b
Negate a -> do a' <- evaluate a
case a' of
Int x -> return (Int $ negate x)
Flt x -> return (Flt $ negate x)
_ -> raise E_TYPE
Conditional c x y -> do c' <- evaluate c
evaluate $ if truthOf c' then x else y
x `And` y -> do x' <- evaluate x
if truthOf x' then evaluate y else return x'
x `Or` y -> do x' <- evaluate x
if truthOf x' then return x' else evaluate y
Not x -> (truthValue . not . truthOf) `liftM` evaluate x
x `Equal` y -> equality (==) x y
x `NotEqual` y -> equality (/=) x y
x `LessThan` y -> comparison (<) x y
x `LessEqual` y -> comparison (<=) x y
x `GreaterThan` y -> comparison (>) x y
x `GreaterEqual` y -> comparison (>=) x y
Index{} -> fetch (lValue expr)
Range{} -> fetch (lValue expr)
Length -> liftM (Int . fromIntegral) =<< asks indexLength
item `In` list -> do
item' <- evaluate item
list' <- evaluate list
case list' of
Lst v -> return $ Int $ maybe 0 (fromIntegral . succ) $
V.elemIndex item' v
_ -> raise E_TYPE
Catch expr codes (Default dv) -> do
codes' <- case codes of
ANY -> return Nothing
Codes args -> Just `liftM` expand args
evaluate expr `catchException` \except@(Exception code _ _) callStack ->
if maybe True (code `elem`) codes'
then maybe (return code) evaluate dv
else passException except callStack
where binary op a b = do
a' <- evaluate a
b' <- evaluate b
a' `op` b'
equality op = binary test
where test a b = return $ truthValue (a `op` b)
comparison op = binary test
where test a b = do when (typeOf a /= typeOf b) $ raise E_TYPE
case a of
Lst{} -> raise E_TYPE
_ -> return $ truthValue (a `op` b)
fetchVariable :: Id -> MOO Value
fetchVariable var =
maybe (raise E_VARNF) return . M.lookup var =<< frame variables
storeVariable :: Id -> Value -> MOO Value
storeVariable var value = do
modifyFrame $ \frame ->
frame { variables = M.insert var value (variables frame) }
return value
fetchProperty :: (ObjT, StrT) -> MOO Value
fetchProperty (oid, name) = do
obj <- getObject oid >>= maybe (raise E_INVIND) return
maybe (search False obj) (return . ($ obj)) $ builtinProperty name'
where name' = T.toCaseFold name
search skipPermCheck obj = do
prop <- getProperty obj name'
unless (skipPermCheck || propertyPermR prop) $
checkPermission (propertyOwner prop)
case propertyValue prop of
Just value -> return value
Nothing -> do
parentObj <- maybe (return Nothing) getObject (objectParent obj)
maybe (error $ "No inherited value for property " ++
T.unpack name) (search True) parentObj
storeProperty :: (ObjT, StrT) -> Value -> MOO Value
storeProperty (oid, name) value = do
obj <- getObject oid >>= maybe (raise E_INVIND) return
if isBuiltinProperty name'
then setBuiltinProperty (oid, obj) name' value
else modifyProperty obj name' $ \prop -> do
unless (propertyPermW prop) $ checkPermission (propertyOwner prop)
return prop { propertyValue = Just value }
return value
where name' = T.toCaseFold name
withIndexLength :: Value -> MOO a -> MOO a
withIndexLength expr =
local $ \env -> env { indexLength = case expr of
Lst v -> return (V.length v)
Str t -> return (T.length t)
_ -> raise E_TYPE
}
checkLstRange :: LstT -> Int -> MOO ()
checkLstRange v i = when (i < 1 || i > V.length v) $ raise E_RANGE
checkStrRange :: StrT -> Int -> MOO ()
checkStrRange t i = when (i < 1 || t `T.compareLength` i == LT) $ raise E_RANGE
checkIndex :: Value -> MOO Int
checkIndex (Int i) = return (fromIntegral i)
checkIndex _ = raise E_TYPE
data LValue = LValue {
fetch :: MOO Value
, store :: Value -> MOO Value
, change :: MOO (Value, Value -> MOO Value)
}
lValue :: Expr -> LValue
lValue (Variable var) = LValue fetch store change
where var' = T.toCaseFold var
fetch = fetchVariable var'
store = storeVariable var'
change = do
value <- fetch
return (value, store)
lValue (PropRef objExpr nameExpr) = LValue fetch store change
where fetch = getRefs >>= fetchProperty
store value = getRefs >>= flip storeProperty value
change = do
value <- fetch
return (value, store)
-- XXX improve this to avoid redundant checks?
getRefs = do
objRef <- evaluate objExpr
nameRef <- evaluate nameExpr
case (objRef, nameRef) of
(Obj oid, Str name) -> return (oid, name)
_ -> raise E_TYPE
lValue (expr `Index` index) = LValue fetchIndex storeIndex changeIndex
where fetchIndex = do
(value, _) <- changeIndex
return value
storeIndex newValue = do
(_, change) <- changeIndex
change newValue
return newValue
changeIndex = do
(value, changeExpr) <- change (lValue expr)
index' <- checkIndex =<< withIndexLength value (evaluate index)
value' <- case value of
Lst v -> checkLstRange v index' >> return (v V.! (index' - 1))
Str t -> checkStrRange t index' >>
return (Str $ T.singleton $ t `T.index` (index' - 1))
_ -> raise E_TYPE
return (value', changeValue value index' changeExpr)
changeValue (Lst v) index changeExpr newValue =
changeExpr $ Lst $ listSet v index newValue
changeValue (Str t) index changeExpr (Str c) = do
when (c `T.compareLength` 1 /= EQ) $ raise E_INVARG
let (s, r) = T.splitAt (index - 1) t
changeExpr $ Str $ T.concat [s, c, T.tail r]
changeValue _ _ _ _ = raise E_TYPE
lValue (expr `Range` (start, end)) = LValue fetchRange storeRange changeRange
where fetchRange = do
value <- fetch (lValue expr)
(start', end') <- getIndices value
if start' > end'
then case value of
Lst{} -> return $ Lst V.empty
Str{} -> return $ Str T.empty
_ -> raise E_TYPE
else let len = end' - start' + 1 in case value of
Lst v -> do checkLstRange v start' >> checkLstRange v end'
return $ Lst $ V.slice (start' - 1) len v
Str t -> do checkStrRange t start' >> checkStrRange t end'
return $ Str $ T.take len $ T.drop (start' - 1) t
_ -> raise E_TYPE
getIndices value = withIndexLength value $ do
start' <- checkIndex =<< evaluate start
end' <- checkIndex =<< evaluate end
return (start', end')
storeRange newValue = do
(value, changeExpr) <- change (lValue expr)
(start', end') <- getIndices value
changeValue value start' end' changeExpr newValue
return newValue
changeValue (Lst v) start end changeExpr (Lst r) = do
let len = V.length v
when (end < 0 || start > len + 1) $ raise E_RANGE
let pre = sublist v 1 (start - 1)
post = sublist v (end + 1) len
sublist v s e
| e < s = V.empty
| otherwise = V.slice (s - 1) (e - s + 1) v
changeExpr $ Lst $ V.concat [pre, r, post]
changeValue (Str t) start end changeExpr (Str r) = do
when (end < 0 ||
t `T.compareLength` (start - 1) == LT) $ raise E_RANGE
let pre = substr t 1 (start - 1)
post = substr t (end + 1) (T.length t)
substr t s e
| e < s = T.empty
| otherwise = T.take (e - s + 1) $ T.drop (s - 1) t
changeExpr $ Str $ T.concat [pre, r, post]
changeValue _ _ _ _ _ = raise E_TYPE
changeRange = error "Illegal Range as lvalue subexpression"
lValue expr = LValue fetch store change
where fetch = evaluate expr
store _ = error "Unmodifiable LValue"
change = do
value <- fetch
return (value, store)
scatterAssign :: [ScatItem] -> LstT -> MOO Value
scatterAssign items args = do
when (nargs < nreqs || (not haveRest && nargs > ntarg)) $ raise E_ARGS
walk items args (nargs - nreqs)
return (Lst args)
where nargs = V.length args
nreqs = count required items
nopts = count optional items
ntarg = nreqs + nopts
nrest = if haveRest && nargs >= ntarg then nargs - ntarg else 0
haveRest = any rest items
count p = length . filter p
required ScatRequired{} = True
required _ = False
optional ScatOptional{} = True
optional _ = False
rest ScatRest{} = True
rest _ = False
walk (item:items) args noptAvail =
case item of
ScatRequired var -> do
assign var (V.head args)
walk items (V.tail args) noptAvail
ScatOptional var opt
| noptAvail > 0 -> do assign var (V.head args)
walk items (V.tail args) (noptAvail - 1)
| otherwise -> do
case opt of Nothing -> return ()
Just expr -> void $ assign var =<< evaluate expr
walk items args noptAvail
ScatRest var -> do
let (s, r) = V.splitAt nrest args
assign var (Lst s)
walk items r noptAvail
walk [] _ _ = return ()
assign var = storeVariable (T.toCaseFold var)
expand :: [Arg] -> MOO [Value]
expand (a:as) = case a of
ArgNormal expr -> do a' <- evaluate expr
(a' :) `liftM` expand as
ArgSplice expr -> do a' <- evaluate expr
case a' of
Lst v -> (V.toList v ++) `liftM` expand as
_ -> raise E_TYPE
expand [] = return []
plus :: Value -> Value -> MOO Value
(Int a) `plus` (Int b) = return $ Int (a + b)
(Flt a) `plus` (Flt b) = checkFloat (a + b)
(Str a) `plus` (Str b) = return $ Str (T.append a b)
_ `plus` _ = raise E_TYPE
minus :: Value -> Value -> MOO Value
(Int a) `minus` (Int b) = return $ Int (a - b)
(Flt a) `minus` (Flt b) = checkFloat (a - b)
_ `minus` _ = raise E_TYPE
times :: Value -> Value -> MOO Value
(Int a) `times` (Int b) = return $ Int (a * b)
(Flt a) `times` (Flt b) = checkFloat (a * b)
_ `times` _ = raise E_TYPE
divide :: Value -> Value -> MOO Value
(Int a) `divide` (Int b) | b == 0 = raise E_DIV
| otherwise = return $ Int (a `quot` b)
(Flt a) `divide` (Flt b) | b == 0 = raise E_DIV
| otherwise = checkFloat (a / b)
_ `divide` _ = raise E_TYPE
remain :: Value -> Value -> MOO Value
(Int a) `remain` (Int b) | b == 0 = raise E_DIV
| otherwise = return $ Int (a `rem` b)
(Flt a) `remain` (Flt b) | b == 0 = raise E_DIV
| otherwise = checkFloat (a `fmod` b)
_ `remain` _ = raise E_TYPE
fmod :: FltT -> FltT -> FltT
x `fmod` y = x - fromIntegral n * y
where n = roundZero (x / y)
roundZero :: FltT -> Integer
roundZero q | q > 0 = floor q
| q < 0 = ceiling q
| otherwise = round q
power :: Value -> Value -> MOO Value
(Int a) `power` (Int b)
| b >= 0 = return $ Int (a ^ b)
| otherwise = case a of
-1 | even b -> return $ Int 1
| otherwise -> return $ Int (-1)
0 -> raise E_DIV
1 -> return (Int 1)
_ -> return (Int 0)
(Flt a) `power` (Int b) | b >= 0 = checkFloat (a ^ b)
| otherwise = checkFloat (a ** fromIntegral b)
(Flt a) `power` (Flt b) = checkFloat (a ** b)
_ `power` _ = raise E_TYPE