packages feed

ideas-0.6: src/Service/ModeJSON.hs

{-# OPTIONS -XGADTs #-}
-----------------------------------------------------------------------------
-- Copyright 2010, Open Universiteit Nederland. This file is distributed 
-- under the terms of the GNU General Public License. For more information, 
-- see the file "LICENSE.txt", which is included in the distribution.
-----------------------------------------------------------------------------
-- |
-- Maintainer  :  bastiaan.heeren@ou.nl
-- Stability   :  provisional
-- Portability :  portable (depends on ghc)
--
-- Services using JSON notation
--
-----------------------------------------------------------------------------
module Service.ModeJSON (processJSON, jsonTuple) where

import Common.Context
import Common.Utils (Some(..), distinct, readM)
import Common.Exercise
import Common.Strategy (makePrefix)
import Common.Transformation hiding (ruleList, defaultArgument)
import Text.JSON
import Service.Request
import Service.Types (TypedValue(..), Evaluator(..), Type, encodeDefault, decodeDefault, Encoder(..), Decoder(..), decoderExercise)
import qualified Service.Types as Tp
import qualified Service.TypedAbstractService as TAS
import Service.Submit
import Service.ServiceList hiding (Service)
import Service.ExercisePackage 
import Service.DomainReasoner
import Control.Monad
import Data.Maybe
import Data.Char

-- TODO: Clean-up code
extractCode :: JSON -> ExerciseCode
extractCode = fromMaybe noCode . readCode . f
 where 
   f (String s) = s
   f (Array [String _, String _, a@(Array _)]) = f a
   f (Array (String s:tl)) | any (\c -> not (isAlphaNum c || isSpace c || c `elem` ".-")) s = f (Array tl)
   f (Array (hd:_)) = f hd
   f _ = ""

processJSON :: String -> DomainReasoner (Request, String, String)
processJSON input = do
   json <- parseJSON input
   req  <- jsonRequest json
   vers <- getVersion
   resp <- jsonRPC json myHandler
   let out = show $ (if null vers then id else addVersion vers) (toJSON resp)
   return (req, out, "application/json")

addVersion :: String -> JSON -> JSON
addVersion version json = 
   case json of
      Object xs -> Object (xs ++ [info])
      _         -> json
 where
   info = ("version", String version)

jsonRequest :: Monad m => JSON -> m Request
jsonRequest json = do
   srv  <- case lookupM "method" json of
              Just (String s) -> return s
              _               -> fail "Invalid method"
   code <- liftM (return . extractCode) (lookupM "params" json)
   enc  <- case lookupM "encoding" json of
              Nothing         -> return Nothing
              Just (String s) -> liftM Just (readEncoding s)
              _               -> fail "Invalid encoding"
   src  <- case lookupM "source" json of
              Nothing         -> return Nothing
              Just (String s) -> return (Just s)
              _               -> fail "Invalid source"
   return Request 
      { service    = srv
      , exerciseID = code
      , source     = src
      , dataformat = JSON
      , encoding   = enc
      }

myHandler :: JSON_RPC_Handler DomainReasoner
myHandler fun arg = do
   pkg <- if fun == "exerciselist" 
          then return (Some (package emptyExercise))
          else findPackage (extractCode arg)
   srv <- findService fun
   case jsonConverter pkg of
      Some conv -> do
         either fail return (evalService conv srv arg)

jsonConverter :: Some ExercisePackage -> Some (Evaluator (Either String) JSON JSON)
jsonConverter (Some pkg) =
   Some (Evaluator (jsonEncoder (exercise pkg)) (jsonDecoder pkg))

jsonEncoder :: Monad m => Exercise a -> Encoder m JSON a
jsonEncoder ex = Encoder
   { encodeType  = encode (jsonEncoder ex)
   , encodeTerm  = return . String . prettyPrinter ex
   , encodeTuple = jsonTuple
   }
 where
   encode :: Monad m => Encoder m JSON a -> Type a t -> t -> m JSON
   encode enc serviceType a
      | length xs > 1 =
           liftM jsonTuple (mapM (\(b ::: t) -> encode enc t b) xs)
      | otherwise = 
           case serviceType of
              Tp.List t    -> liftM Array (mapM (encode enc t) a)
              Tp.Tag s t   -> liftM (\b -> Object [(s, b)]) (encode enc t a)
              Tp.Int       -> return (toJSON a)
              Tp.Bool      -> return (toJSON a)
              Tp.String    -> return (toJSON a)
              Tp.State     -> encodeState (encodeTerm enc) a
              Tp.Result    -> encodeResult enc a
              _            -> encodeDefault enc serviceType a
    where
      xs = tupleList (a ::: serviceType)
    
   tupleList :: TypedValue a -> [TypedValue a]
   tupleList (a ::: Tp.Iso _ f t)   = tupleList (f a ::: t)
   tupleList (p ::: Tp.Pair t1 t2) = 
      tupleList (fst p ::: t1) ++ tupleList (snd p ::: t2)
   tupleList tv = [tv]
         
jsonDecoder :: MonadPlus m => ExercisePackage a -> Decoder m JSON a
jsonDecoder pkg = Decoder
   { decodeType     = decode (jsonDecoder pkg)
   , decodeTerm     = reader (exercise pkg)
   , decoderPackage = pkg
   }
 where
   reader :: Monad m => Exercise a -> JSON -> m a
   reader ex (String s) = either (fail . show) return (parser ex s)
   reader _  _          = fail "Expecting a string when reading a term"
 
   decode :: MonadPlus m => Decoder m JSON a -> Type a t -> JSON -> m (t, JSON) 
   decode dec serviceType =
      case serviceType of
         Tp.State    -> useFirst $ decodeState (decoderExercise dec) (decodeTerm dec)
         Tp.Location -> useFirst decodeLocation
         Tp.Term     -> useFirst $ decodeTerm dec
         Tp.Rule     -> useFirst $ \x -> fromJSON x >>= getRule (decoderExercise dec)
         Tp.Exercise -> \json -> case json of
                                    (Array (String _:rest)) -> return (decoderExercise dec, Array rest)
                                    _ -> return (decoderExercise dec, json)
         Tp.Int      -> useFirst $ \json -> case json of 
                                               Number (I n) -> return (fromIntegral n)
                                               _        -> fail "not an integer"
         Tp.String   -> useFirst $ \json -> case json of 
                                               String s -> return s
                                               _        -> fail "not a string"
         _           -> decodeDefault dec serviceType
   
   useFirst :: Monad m => (JSON -> m a) -> JSON -> m (a, JSON)
   useFirst f (Array (x:xs)) = do
      a <- f x
      return (a, Array xs)
   useFirst _ _ = fail "expecting an argument"

decodeLocation :: Monad m => JSON -> m [Int]
decodeLocation (String s) = readM s
decodeLocation _          = fail "expecting a string for a location"

--------------------------

encodeState :: Monad m => (a -> m JSON) -> TAS.State a -> m JSON
encodeState f st = do 
   theTerm <- f (TAS.term st)
   return $ Array
      [ String (show (exerciseCode (TAS.exercise st)))
      , String (maybe "NoPrefix" show (TAS.prefix st))
      , theTerm
      , encodeContext (getEnvironment (TAS.context st))
      ]

encodeContext :: Environment -> JSON
encodeContext env = Object (map f (keysEnv env))
 where
   f k = (k, String $ fromMaybe "" $ lookupEnv k env)

decodeState :: Monad m => Exercise a -> (JSON -> m a) -> JSON -> m (TAS.State a)
decodeState ex f (Array [a]) = decodeState ex f a
decodeState ex f (Array [String _code, String p, ce, jsonContext]) = do
   a    <- f ce 
   env  <- decodeContext jsonContext
   return TAS.State 
      { TAS.exercise = ex
      , TAS.prefix   = readM p >>= (`makePrefix` strategy ex)
      , TAS.context  = makeContext ex env a
      }
decodeState _ _ s = fail $ "invalid state" ++ show s

decodeContext :: Monad m => JSON -> m Environment
decodeContext (String "") = decodeContext (Object []) -- Being backwards compatible (for now)
decodeContext (Object xs) = foldM add emptyEnv xs
 where 
   add env (k, String s) = return (storeEnv k s env)       
   add _ _ = fail "invalid item in context"
decodeContext json = fail $ "invalid context: " ++ show json
   
encodeResult :: Monad m => Encoder m JSON a -> Result a -> m JSON
encodeResult enc result =
   case result of
      -- TAS.SyntaxError _ -> [("result", String "SyntaxError")]
      Buggy rs      -> return $ Object [("result", String "Buggy"), ("rules", Array $ map (String . name) rs)]
      NotEquivalent -> return $ Object [("result", String "NotEquivalent")]   
      Ok rs st      -> do
         json <- encodeType enc Tp.State st
         return $ Object [("result", String "Ok"), ("rules", Array $ map (String . name) rs), ("state", json)]
      Detour rs st  -> do
         json <- encodeType enc Tp.State st
         return $ Object [("result", String "Detour"), ("rules", Array $ map (String . name) rs), ("state", json)]
      Unknown st    -> do
         json <- encodeType enc Tp.State st
         return $ Object [("result", String "Unknown"), ("state", json)]

jsonTuple :: [JSON] -> JSON
jsonTuple xs = 
   case mapM f xs of 
      Just xs | distinct (map fst xs) -> Object xs
      _ -> Array xs
 where
   f (Object [p]) = Just p
   f _ = Nothing