module MOO.Compiler ( compile, evaluate ) where
import Control.Applicative ((<$>), (<*>))
import Control.Monad (forM_, when, unless, void, join, (<=<))
import Control.Monad.Cont (callCC)
import Control.Monad.Reader (asks, local)
import Control.Monad.State (gets)
import Data.Monoid ((<>))
import qualified Data.HashMap.Lazy as HM
import MOO.AST
import MOO.Builtins
import MOO.Object
import MOO.Task
import MOO.Types
import qualified MOO.List as Lst
import qualified MOO.String as Str
compile :: Program -> MOO Value
compile (Program stmts) = callCC $ compileStatements stmts
compileStatements :: [Statement] -> (Value -> MOO Value) -> MOO Value
compileStatements (statement:rest) yield = case statement of
Expression lineNo expr ->
setLineNumber lineNo >> evaluate expr >> compile' rest
If lineNo cond (Then thens) elseIfs (Else elses) -> runTick >> do
compileIf ((lineNo, cond, thens) : map elseIf elseIfs) elses
compile' rest
where elseIf :: ElseIf -> (LineNo, Expr, [Statement])
elseIf (ElseIf lineNo cond thens) = (lineNo, cond, thens)
compileIf :: [(LineNo, Expr, [Statement])] -> [Statement] -> MOO Value
compileIf ((lineNo,cond,thens):conds) elses = do
setLineNumber lineNo
cond' <- evaluate cond
if truthOf cond' then compile' thens
else compileIf conds elses
compileIf [] elses = compile' elses
ForList lineNo var expr body -> do
handleDebug $ do
setLineNumber lineNo
elts <- getList =<< evaluate expr
callCC $ \break -> do
pushLoopContext (Just var) (Continuation break)
loop var elts (compile' body)
popContext
return zero
compile' rest
where loop :: Id -> [Value] -> MOO a -> MOO ()
loop var elts body = forM_ elts $ \elt -> runTick >> do
storeVariable var elt
callCC $ \k -> setLoopContinue (Continuation k) >> void body
ForRange lineNo var (start, end) body -> do
handleDebug $ do
setLineNumber lineNo
start' <- evaluate start
end' <- evaluate end
(ty, s, e) <- case (start', end') of
(Int s, Int e) -> return (Int . fromInteger, toInteger s, toInteger e)
(Obj s, Obj e) -> return (Obj . fromInteger, toInteger s, toInteger e)
(_ , _ ) -> raise E_TYPE
callCC $ \break -> do
pushLoopContext (Just var) (Continuation break)
loop var ty s e (compile' body)
popContext
return zero
compile' rest
where loop :: Id -> (Integer -> Value) -> Integer -> Integer -> MOO a ->
MOO ()
loop var ty start end body = forM_ [start..end] $ \i -> runTick >> do
storeVariable var (ty i)
callCC $ \k -> setLoopContinue (Continuation k) >> void body
While lineNo var expr body -> do
callCC $ \break -> do
pushLoopContext var (Continuation break)
loop lineNo var (evaluate expr) (compile' body)
popContext
compile' rest
where loop :: LineNo -> Maybe Id -> MOO Value -> MOO a -> MOO ()
loop lineNo var expr body = runTick >> do
setLineNumber lineNo
expr' <- expr
maybe return storeVariable var expr'
when (truthOf expr') $ do
callCC $ \k -> setLoopContinue (Continuation k) >> void body
loop lineNo var expr body
Fork lineNo var delay body -> runTick >> do
handleDebug $ do
setLineNumber lineNo
usecs <- getDelay =<< evaluate delay
checkQueuedTaskLimit
world <- getWorld
gen <- newRandomGen
let taskId = newTaskId world gen
maybe return storeVariable var (Int $ fromIntegral taskId)
forkTask taskId usecs (compileStatements body return)
return zero
compile' rest
Break name -> breakLoop name
Continue name -> continueLoop name
Return _ Nothing -> runTick >> yield zero
Return lineNo (Just expr) -> runTick >> do
setLineNumber lineNo
yield =<< evaluate expr
TryExcept body excepts -> runTick >> do
excepts' <- mapM compileExcept excepts
compile' body `catchException` dispatch excepts'
compile' rest
where compileExcept :: Except -> MOO (Maybe [Value], Maybe Id, MOO Value)
compileExcept (Except lineNo var codes handler) = do
codes' <- case codes of
ANY -> return Nothing
Codes args -> setLineNumber lineNo >> Just <$> expand args
return (codes', var, compile' handler)
dispatch :: [(Maybe [Value], Maybe Id, MOO Value)] -> Exception ->
MOO Value
dispatch ((codes, var, handler):next) except@Exception {
exceptionCode = code
, exceptionMessage = message
, exceptionValue = value
, exceptionCallStack = Stack errorFrames
}
| maybe True (code `elem`) codes = 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 ->
popContext >> finally' >> passException except
popContext
finally'
compile' rest
where compile' :: [Statement] -> MOO Value
compile' ss = compileStatements ss yield
compileStatements [] _ = return zero
evaluate :: Expr -> MOO Value
evaluate (Literal value) = return value
evaluate expr@Variable{} = handleDebug $ fetch (lValue expr)
evaluate expr = runTick >>= \_ -> handleDebug $ case expr of
List args -> fromList <$> expand args
PropertyRef{} -> fetch (lValue expr)
Assign what expr -> evaluate expr >>= store (lValue what)
Scatter items expr -> evaluate expr >>= usingList (scatterAssign items)
VerbCall target vname args -> do
target' <- evaluate target
vname' <- evaluate vname
args' <- expand args
(oid, name) <- case (target', vname') of
(Obj oid, Str name) -> return (oid, name)
(_ , _ ) -> raise E_TYPE
callVerb oid oid name args'
BuiltinFunc func args -> expand args >>= callBuiltin func
x `Plus` y -> binary plus x y
x `Minus` y -> binary minus x y
x `Times` y -> binary times x y
x `Divide` y -> binary divide x y
x `Remain` y -> binary remain x y
x `Power` y -> binary power x y
Negate x -> evaluate x >>= \x' -> case x' of
Int n -> return (Int $ negate n)
Flt n -> return (Flt $ negate n)
_ -> raise E_TYPE
Conditional cond x y ->
evaluate cond >>= \cond' -> evaluate $ if truthOf cond' then x else y
x `And` y -> evaluate x >>= \v -> if truthOf v then evaluate y else return v
x `Or` y -> evaluate x >>= \v -> if truthOf v then return v else evaluate y
Not x -> truthValue . not . truthOf <$> evaluate x
x `CompareEQ` y -> equality (==) x y
x `CompareNE` y -> equality (/=) x y
x `CompareLT` y -> comparison (<) x y
x `CompareLE` y -> comparison (<=) x y
x `CompareGT` y -> comparison (>) x y
x `CompareGE` y -> comparison (>=) x y
Index{} -> fetch (lValue expr)
Range{} -> fetch (lValue expr)
Length -> join (asks indexLength)
item `In` list -> do
elt <- evaluate item
evaluate list >>= usingList
(return . Int . maybe 0 (fromIntegral . succ) . Lst.elemIndex elt)
Catch expr codes (Default dv) -> do
codes' <- case codes of
ANY -> return Nothing
Codes args -> Just <$> expand args
let handler except@Exception { exceptionCode = code }
| maybe True (code `elem`) codes' = maybe (return code) evaluate dv
| otherwise = passException except
evaluate expr `catchException` handler
where binary :: (Value -> Value -> MOO Value) -> Expr -> Expr -> MOO Value
binary op x y = evaluate x >>= \x' -> evaluate y >>= \y' -> x' `op` y'
equality :: (Value -> Value -> Bool) -> Expr -> Expr -> MOO Value
equality op = binary test
where test x y = return $ truthValue (x `op` y)
comparison :: (Value -> Value -> Bool) -> Expr -> Expr -> MOO Value
comparison op = binary test
where test x y | comparable x y = return $ truthValue (x `op` y)
| otherwise = raise E_TYPE
fetchVariable :: Id -> MOO Value
fetchVariable var =
maybe (raise E_VARNF) return . HM.lookup var =<< frame variables
storeVariable :: Id -> Value -> MOO Value
storeVariable var value = do
modifyFrame $ \frame ->
frame { variables = HM.insert var value (variables frame) }
return value
fetchProperty :: (ObjT, StrT) -> MOO Value
fetchProperty (oid, name) = do
obj <- maybe (raise E_INVIND) return =<< getObject oid
maybe (search False obj) (handleBuiltin obj) $ builtinProperty name
where search :: Bool -> Object -> MOO Value
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 " ++
Str.toString name) (search True) parentObj
handleBuiltin :: Object -> (Object -> Value) -> MOO Value
handleBuiltin obj prop = checkProtectedProperty (toId name) >>
return (prop obj)
storeProperty :: (ObjT, StrT) -> Value -> MOO Value
storeProperty (oid, name) value = do
obj <- maybe (raise E_INVIND) return =<< getObject oid
if isBuiltinProperty name
then checkProtectedProperty (toId name) >>
setBuiltinProperty (oid, obj) name value
else modifyProperty obj name $ \prop -> do
unless (propertyPermW prop) $ checkPermission (propertyOwner prop)
return prop { propertyValue = Just value }
return value
withIndexLength :: Value -> MOO a -> MOO a
withIndexLength value = local $ \env -> env { indexLength = valueLength }
where valueLength :: MOO Value
valueLength = Int . fromIntegral <$> case value of
Lst v -> return (Lst.length v)
Str t -> return (Str.length t)
_ -> raise E_TYPE
usingList :: (LstT -> MOO a) -> Value -> MOO a
usingList f (Lst v) = f v
usingList _ _ = raise E_TYPE
getList :: Value -> MOO [Value]
getList = usingList (return . Lst.toList)
getIndex :: Value -> MOO Int
getIndex (Int i) = return (fromIntegral i)
getIndex _ = raise E_TYPE
checkLstRange :: LstT -> Int -> MOO ()
checkLstRange v i = when (i < 1 || i > Lst.length v) $ raise E_RANGE
checkStrRange :: StrT -> Int -> MOO ()
checkStrRange t i = when (i < 1 || t `Str.compareLength` i == LT) $
raise E_RANGE
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 fetch = fetchVariable var
store = storeVariable var
change = fetch >>= \value -> return (value, store)
lValue (PropertyRef objExpr nameExpr) = LValue fetch store change
where fetch = getRefs >>= fetchProperty
store value = getRefs >>= flip storeProperty value
change = do
refs <- getRefs
value <- fetchProperty refs
return (value, storeProperty refs)
getRefs :: MOO (ObjT, StrT)
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 = fst <$> changeIndex
storeIndex newValue = do
(_, change) <- getLens
change newValue
return newValue
changeIndex :: MOO (Value, Value -> MOO Value)
changeIndex = getLens >>= \(maybeValue, setValue) -> case maybeValue of
Just value -> return (value, setValue)
Nothing -> raise E_RANGE
getLens :: MOO (Maybe Value, Value -> MOO Value)
getLens = do
(value, changeExpr) <- change (lValue expr)
index' <- withIndexLength value (evaluate index)
case index' of
Int i -> getIntLens value (fromIntegral i) changeExpr
Str k -> getStrLens value k changeExpr
_ -> raise E_TYPE
getIntLens :: Value -> Int -> (Value -> MOO Value) ->
MOO (Maybe Value, Value -> MOO Value)
getIntLens value index' changeExpr = do
let i = index' 1 :: Int
value' <- case value of
Lst v -> checkLstRange v index' >> return (v Lst.! i)
Str t -> checkStrRange t index' >> return (Str $ Str.singleton $
t `Str.index` i)
_ -> raise E_TYPE
return (Just value', changeValue value i changeExpr)
where changeValue :: Value -> Int -> (Value -> MOO Value) ->
Value -> MOO Value
changeValue (Lst v) i changeExpr newValue =
changeExpr $ Lst $ Lst.set v newValue i
changeValue (Str t) i changeExpr (Str c) = do
when (c `Str.compareLength` 1 /= EQ) $ raise E_INVARG
let (s, r) = Str.splitAt i t :: (StrT, StrT)
changeExpr $ Str $ Str.concat [s, c, Str.tail r]
changeValue _ _ _ _ = raise E_TYPE
getStrLens :: Value -> StrT -> (Value -> MOO Value) ->
MOO (Maybe Value, Value -> MOO Value)
getStrLens (Lst lst) key changeExpr = case Lst.assocLens key lst of
Just (maybeValue, changeList) ->
return (maybeValue, changeExpr . Lst . changeList . Just)
Nothing -> raise E_INVIND
getStrLens _ _ _ = 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 emptyList
Str{} -> return emptyString
_ -> raise E_TYPE
else let len = end' start' + 1 in case value of
Lst v -> do checkLstRange v start' >> checkLstRange v end'
return $ Lst $ Lst.slice (start' 1) len v
Str t -> do checkStrRange t start' >> checkStrRange t end'
return $ Str $ Str.take len $ Str.drop (start' 1) t
_ -> raise E_TYPE
storeRange newValue = do
(value, changeExpr) <- change (lValue expr)
startEnd <- getIndices value
changeValue value startEnd changeExpr newValue
return newValue
changeRange = error "Illegal Range as lvalue subexpression"
getIndices :: Value -> MOO (Int, Int)
getIndices value = withIndexLength value $
(,) <$> (evaluate start >>= getIndex)
<*> (evaluate end >>= getIndex)
changeValue :: Value -> (Int, Int) -> (Value -> MOO a) -> Value -> MOO a
changeValue (Lst v) (start, end) changeExpr (Lst r) = do
let len = Lst.length v :: Int
when (end < 0 || start > len + 1) $ raise E_RANGE
let pre = sublist v 1 (start 1) :: LstT
post = sublist v (end + 1) len :: LstT
sublist :: LstT -> Int -> Int -> LstT
sublist v s e
| e < s = Lst.empty
| otherwise = Lst.slice (s 1) (e s + 1) v
changeExpr $ Lst $ Lst.concat [pre, r, post]
changeValue (Str t) (start, end) changeExpr (Str r) = do
when (end < 0 || t `Str.compareLength` (start 1) == LT) $
raise E_RANGE
let pre = substr t 1 (start 1) :: StrT
post = substr t (end + 1) (Str.length t) :: StrT
substr :: StrT -> Int -> Int -> StrT
substr t s e
| e < s = Str.empty
| otherwise = Str.take (e s + 1) $ Str.drop (s 1) t
changeExpr $ Str $ Str.concat [pre, r, post]
changeValue _ _ _ _ = raise E_TYPE
lValue expr = LValue fetch store change
where fetch = evaluate expr
store _ = error "Unmodifiable LValue"
change = fetch >>= \value -> return (value, store)
scatterAssign :: [ScatterItem] -> 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 = Lst.length args :: Int
nreqs = count required items :: Int
nopts = count optional items :: Int
ntarg = nreqs + nopts :: Int
nrest = if haveRest && nargs >= ntarg then nargs ntarg else 0 :: Int
count :: (a -> Bool) -> [a] -> Int
count p = length . filter p
haveRest = any rest items :: Bool
required, optional, rest :: ScatterItem -> Bool
required ScatRequired{} = True
required _ = False
optional ScatOptional{} = True
optional _ = False
rest ScatRest{} = True
rest _ = False
walk :: [ScatterItem] -> LstT -> Int -> MOO ()
walk (item:items) args noptAvail = case item of
ScatRequired var -> do
storeVariable var (Lst.head args)
walk items (Lst.tail args) noptAvail
ScatOptional var opt
| noptAvail > 0 -> do
storeVariable var (Lst.head args)
walk items (Lst.tail args) (pred noptAvail)
| otherwise -> do
maybe (return zero) (storeVariable var <=< evaluate) opt
walk items args noptAvail
ScatRest var -> do
let (s, r) = Lst.splitAt nrest args :: (LstT, LstT)
storeVariable var (Lst s)
walk items r noptAvail
walk [] _ _ = return ()
expand :: [Argument] -> MOO [Value]
expand (x:xs) = case x of
ArgNormal expr -> (:) <$> evaluate expr <*> expand xs
ArgSplice expr -> (++) <$> (evaluate expr >>= getList) <*> expand xs
expand [] = return []
plus :: Value -> Value -> MOO Value
Int x `plus` Int y = return $ Int (x + y)
Flt x `plus` Flt y = checkFloat (x + y)
Str x `plus` Str y = return $ Str (x <> y)
_ `plus` _ = raise E_TYPE
minus :: Value -> Value -> MOO Value
Int x `minus` Int y = return $ Int (x y)
Flt x `minus` Flt y = checkFloat (x y)
_ `minus` _ = raise E_TYPE
times :: Value -> Value -> MOO Value
Int x `times` Int y = return $ Int (x * y)
Flt x `times` Flt y = checkFloat (x * y)
_ `times` _ = raise E_TYPE
divide :: Value -> Value -> MOO Value
Int _ `divide` Int 0 = raise E_DIV
Int x `divide` Int (1)
| x == minBound = return $ Int x
Int x `divide` Int y = return $ Int (x `quot` y)
Flt _ `divide` Flt 0 = raise E_DIV
Flt x `divide` Flt y = checkFloat (x / y)
_ `divide` _ = raise E_TYPE
remain :: Value -> Value -> MOO Value
Int _ `remain` Int 0 = raise E_DIV
Int x `remain` Int y = return $ Int (x `rem` y)
Flt _ `remain` Flt 0 = raise E_DIV
Flt x `remain` Flt y = checkFloat (x `fmod` y)
_ `remain` _ = raise E_TYPE
fmod :: FltT -> FltT -> FltT
x `fmod` y = x y * fromInteger (truncate $ x / y)
power :: Value -> Value -> MOO Value
Flt x `power` Flt y = checkFloat (x ** y)
Flt x `power` Int y = checkFloat (x ^^ y)
Int x `power` Int y | y >= 0 = return $ Int (x ^ y)
Int 0 `power` Int _ = raise E_DIV
Int 1 `power` Int _ = return $ Int 1
Int (1) `power` Int y | even y = return $ Int 1
| otherwise = return $ Int (1)
Int _ `power` Int _ = return $ Int 0
_ `power` _ = raise E_TYPE