packages feed

ideas-1.9.1: src/Ideas/Encoding/NewDecoderJSON.hs

{-# LANGUAGE GADTs #-}
-----------------------------------------------------------------------------
-- Copyright 2019, Ideas project team. This file is distributed under the
-- terms of the Apache License 2.0. For more information, see the files
-- "LICENSE.txt" and "NOTICE.txt", which are included in the distribution.
-----------------------------------------------------------------------------
-- |
-- Maintainer  :  bastiaan.heeren@ou.nl
-- Stability   :  provisional
-- Portability :  portable (depends on ghc)
--
-- Services using JSON notation
--
-----------------------------------------------------------------------------

module Ideas.Encoding.NewDecoderJSON
   ( JSONDecoder, jsonTypeDecoder
   ) where

import Control.Monad.State (get)
import Data.Char
import Ideas.Common.Library hiding (exerciseId, symbol)
import Ideas.Common.Traversal.Navigator
import Ideas.Encoding.Encoder
import Ideas.Encoding.Options
import Ideas.Service.State
import Ideas.Service.Types hiding (String)
import Ideas.Text.JSON
import qualified Ideas.Service.Types as Tp

type JSONDecoder a = GDecoderJSON (Exercise a, Options)

jsonTypeDecoder :: Type a t -> DecoderX a String JSON t
jsonTypeDecoder tp = do
    env  <- reader id
    json <- get
    case evalGDecoderJSON (jObject $ decodeType tp) env json of
       Left err -> throwError (show err)
       Right a  -> return a

decodeType :: Type a t -> JSONDecoder a t
decodeType tp =
   case tp of
      Tag s t ->
         jKey (map toLower s) $ decodeType t
      Iso p t -> from p <$> decodeType t
      Pair t1 t2 -> do
         a <- decodeType t1
         b <- decodeType t2
         return (a, b)
      t1 :|: t2 ->
         Left  <$> decodeType t1 <|>
         Right <$> decodeType t2
      Unit         -> return ()
      Const t -> decodeConst t
      _ -> errorStr $ "No support for argument type: " ++ show tp

decodeConst :: Const a t -> JSONDecoder a t
decodeConst tp =
   case tp of
      State       -> decodeState
      Context     -> decodeContext
      Exercise    -> getExercise
      Environment -> decodeEnvironment
      Location    -> decodeLocation
      Script      -> getScript
      Int         -> jInt
      Tp.String   -> jString
      Id          -> decodeId
      Rule        -> decodeRule
      QCGen       -> getQCGen
      _           -> errorStr $ "No support for argument type: " ++ show tp

decodeState :: JSONDecoder a (State a)
decodeState = jKey "state" $ jObject $ do
   ex  <- getExercise
   ps  <- decodePaths
   ctx <- decodeContext
   let prf = maybe noPrefix (\x -> replayPaths x (strategy ex) ctx) ps
   uid <- searchKey "userid"
   sid <- searchKey "sessionid"
   tid <- searchKey "taskid"
   return $ (makeState ex prf ctx)
      { stateUser      = uid
      , stateSession   = sid
      , stateStartTerm = tid
      }

searchKey :: String -> JSONDecoder a (Maybe String)
searchKey k = Just <$> jKey k jString <|> return Nothing

decodePaths :: JSONDecoder a (Maybe [Path])
decodePaths = f <$> jKey "prefix" jString
 where
   f s | s ~= "noprefix" = Nothing
       | otherwise       = readPaths s
   x ~= y = filter isAlphaNum (map toLower x) == y

decodeContext :: JSONDecoder a (Context a)
decodeContext = jKey "context" $ jObject $ do
   ex  <- getExercise
   val <- decodeValue
   env <- decodeEnvironment
   loc <- decodeLocation
   return $ navigateTowards loc $ deleteRef locRef $
      setEnvironment env $ inContext ex val

decodeValue :: JSONDecoder a a
decodeValue = jKey "term" $ decodeParser <|> decodeJSON
 where
   decodeParser = do
      ex <- getExercise
      s <- jString
      either errorStr return (parser ex s)

   decodeJSON = do
      ex <- getExercise
      case hasJSONView ex of 
         Just jv -> jNext $ maybe (Left "Could not decode term") Right . match jv
         Nothing -> errorStr "No JSON decoder for term"

decodeEnvironment :: JSONDecoder a Environment
decodeEnvironment = jKey "environment" $ foldr ($) mempty <$> jObjectWithKeys f
 where
   f k = g <$> jString <|> g . show <$> jInt <|> g . show <$> jDouble
    where
      g = insertRef (makeRef k)

decodeLocation :: JSONDecoder a Location
decodeLocation = jKey "location" $ 
   toLocation <$> jArrayOf jInt

decodeRule :: JSONDecoder a (Rule (Context a))
decodeRule = do
   ex <- getExercise
   jKey "rule" $ do 
      rid <- newId <$> jString
      case getRule ex rid of
         Just a  -> return a
         Nothing -> errorStr ("unknown rule " ++ show rid) 

decodeId :: JSONDecoder a Id -- fix me
decodeId = jKey "rule" $ newId <$> jString

locRef :: Ref String
locRef = makeRef "location"