packages feed

lambdabot-4.0: Plugin/Lambda.hs

--
-- | Lambda calculus interpreter
--
module Plugin.Lambda (theModule) where

import Plugin
import Plugin.Lambda.LMEngine (evaluate, define, resume, Environment)

import qualified Data.Map as M
import Lib.Util (mapMaybeMap)

import Data.Dynamic                     (Dynamic)

import qualified Data.ByteString.Char8 as P

PLUGIN Eval

initFuel, maxFuel :: Int
initFuel = 1000
maxFuel  = 500000 -- ~ 200M mem usage for "sum $ enumFrom 1"

maxPrivate :: Int
maxPrivate = 10

initEnv :: Environment
initEnv = M.empty

initDefns :: M.Map String String
initDefns = M.empty

outOfFuelMsg :: [Char]
outOfFuelMsg = "out of fuel - use @resume to continue"

type EvalGlobal = (Int, Environment, M.Map String String)
type EvalState = GlobalPrivate EvalGlobal Dynamic

instance Module EvalModule EvalState where
    moduleDefState _ = return $ mkGlobalPrivate (maxPrivate)
      (initFuel, initEnv, initDefns)

    moduleSerialize _ = Just $ Serial {
              serialize = Just . (\(fuel,_,defns) ->
                P.pack . unlines $ show fuel: map show (M.toList defns)) . global,
              deserialize = fmap (mkGlobalPrivate maxPrivate) .  loadDefinitions . P.unpack
           }

    moduleHelp _ s = case s of
        "lambda"         -> "lambda <expr>. Evaluate the lambda calculus expression, <expr>"
        "define"         -> "define <name> <expr>. Define name to be expr"
        "get-definition" -> "get-definition <name>. Get the expression defining name"
        "definitions"    -> "definitions <prefix>. Get the definitions starting with prefix"
        "del-definition" -> "del-definition <name>. Delete name"
        "set-fuel"       -> "set-fuel <ticks>. How many ticks before @lambda runs out of fuel"
        "resume"         -> "resume. Continue an expression that has run out of fuel"

    moduleCmds   _ = ["lambda","define","get-definition","definitions","resume"]
    modulePrivs  _ = ["set-fuel","del-definition"]

    process      _ _ target cmd rest = do
       let writeRes = writePS target
       (fuel, env, defns) <- readGS
       res <- readPS target
       case cmd of
        "lambda" -> do 
             let r_or_s = evaluate rest env fuel
             case r_or_s of
                Right s -> writeRes Nothing   >> return [s]
                Left nr -> writeRes (Just nr) >> return [outOfFuelMsg]

        "define" -> 
            let rslt = define defn
                (nm,defn) = break (' '==) rest 
            in case rslt of
                    Left s  -> return [s]
                    Right v -> do
                      withGS $ \(fuel', env', defns') writer -> do
                          writer (fuel', M.insert nm v env', 
                                         M.insert nm defn defns')
                      return [nm ++ " defined"]

        "definitions" -> 
            let nm = M.keys defns 
            in return . (:[]) $ if null rest 
                then unlines $ map show $
                                       groupBy (\(x:_) (y:_) -> x == y) $ nm
                else show [x | x <- sort nm, rest `isPrefixOf` x]

        "get-definition" -> 
            let defn = M.lookup rest defns 
                out = maybe (rest++" not defined") ((rest++" =")++) defn
            in return [out]

        "set-fuel" -> case readM rest of
            Nothing -> return ["Not a number"]
            Just x  -> if x > 0 && x <= maxFuel 
                        then do writeGS (x,env,defns)
                                return ["Fuel set to "++show x]
                        else return ["Can't set fuel above "++show maxFuel]

        "del-definition" -> case words rest of
            (d:_) -> do
                withGS $ \(fuel',env',defns') writer -> 
                    writer (fuel', M.delete d env', M.delete d defns')
                return [d++" removed"]
            _ -> return []

        "resume" -> case res of
            Nothing -> return []
            Just r -> case resume r fuel of
                    Left nr -> do
                        writeRes $ Just nr
                        return [outOfFuelMsg]
                    Right s -> do
                        writeRes Nothing
                        return [s]

--
-- this is so ugly (at least it's only init)
--
-- This stuff is slow
--
loadDefinitions :: String -> Maybe EvalGlobal
loadDefinitions s = do
  -- grab fuel val from definitions.
  fuel':rest <- return $ lines s
  fuel <- readM fuel'
  -- rest is a list of paris of ids and rhs defins
  let rests = mapMaybe readM rest
  -- parse the lot. :/
  let dMap = M.fromList $! rests
      eMap = mapMaybeMap ((const Nothing `either` \x -> Just $! x) . define) $ dMap

      keys = M.keys eMap
  
  fuel `seq` eMap `seq` dMap `seq` return
                (fuel,
                 eMap,
                 M.filterWithKey (\k _ -> k `elem` keys) dMap)