{-# LANGUAGE RankNTypes #-} ----------------------------------------------------------------------------- -- 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) -- ----------------------------------------------------------------------------- module Ideas.Encoding.Encoder ( -- * Converter type class getExercise, getOptions, getRequest , withExercise, getBaseUrl, getQCGen, getScript , (//), withJSONTerm, withOpenMath -- * JSON support , hasJSONView, addJSONView, jsonEncoding , termToJSON, jsonToTerm -- * Latex support , hasLatexEncoding, latexPrinter, latexPrinterContext , latexEncoding, latexEncodingWith -- * Encoder datatype , EncoderX, TypedEncoder , (), encodeTyped -- * Decoder datatype , DecoderX, TypedDecoder ) where import Control.Monad.Reader import Data.Maybe import Ideas.Common.Library import Ideas.Encoding.Options import Ideas.Encoding.Request import Ideas.Service.FeedbackScript.Parser (Script) import Ideas.Service.Types import Ideas.Text.JSON hiding (String) import Ideas.Text.Latex import Ideas.Utils.Decoding import Test.QuickCheck.Random import qualified Ideas.Common.Rewriting.Term as Term import qualified Ideas.Text.JSON as JSON ------------------------------------------------------------------- -- Converter type class getExercise :: DecoderX a s (Exercise a) getExercise = reader fst getOptions :: DecoderX a s Options getOptions = reader snd getRequest :: DecoderX a s Request getRequest = request <$> getOptions withExercise :: (Exercise a -> DecoderX a s t) -> DecoderX a s t withExercise = (getExercise >>=) getBaseUrl :: DecoderX a s String getBaseUrl = fromMaybe "http://ideas.cs.uu.nl/" . baseUrl <$> getOptions getQCGen :: DecoderX a s QCGen getQCGen = fromMaybe (mkQCGen 0) . qcGen <$> getOptions getScript :: DecoderX a s Script getScript = script <$> getOptions withOpenMath :: (Bool -> DecoderX a s t) -> DecoderX a s t withOpenMath = (fmap useOpenMath getRequest >>=) withJSONTerm :: (Bool -> DecoderX a s t) -> DecoderX a s t withJSONTerm = (fmap useJSONTerm getRequest >>=) (//) :: Decoder env s a -> s -> Decoder env s2 a p // a = do env <- ask runDecoder p env a ------------------------------------------------------------------- -- JSON terms jsonProperty :: Id jsonProperty = describe "Support for JSON encoding" $ newId "json" hasJSONView :: Exercise a -> Maybe (View JSON a) hasJSONView = getPropertyF jsonProperty addJSONView :: View JSON a -> Exercise a -> Exercise a addJSONView = setPropertyF jsonProperty jsonEncoding :: InJSON a => Exercise a -> Exercise a jsonEncoding = addJSONView (makeView fromJSON toJSON) termToJSON :: Term -> JSON termToJSON term = case term of TVar s -> JSON.String s TCon s [] | s == trueSymbol -> Boolean True | s == falseSymbol -> Boolean False | s == nullSymbol -> Null TCon s ts | s == objectSymbol -> Object (f ts) | otherwise -> Object [("_apply", Array (JSON.String (show s):map termToJSON ts))] TList xs -> Array (map termToJSON xs) TNum n -> Number (I n) TFloat d -> Number (D d) TMeta n -> Object [("_meta", Number (I (toInteger n)))] where f [] = [] f (TVar s:x:xs) = (s, termToJSON x) : f xs f _ = error "termToJSON" jsonToTerm :: JSON -> Term jsonToTerm json = case json of Number (I n) -> TNum n Number (D d) -> TFloat d JSON.String s -> TVar s Boolean b -> Term.symbol (if b then trueSymbol else falseSymbol) Array xs -> TList (map jsonToTerm xs) Object [("_meta", Number (I n))] -> TMeta (fromInteger n) Object [("_apply", Array (JSON.String s:xs))] -> TCon (newSymbol s) (map jsonToTerm xs) Object xs -> TCon objectSymbol (concatMap f xs) Null -> Term.symbol nullSymbol where f (s, x) = [TVar s, jsonToTerm x] nullSymbol, objectSymbol :: Symbol nullSymbol = newSymbol "null" objectSymbol = newSymbol "object" ------------------------------------------------------------------- -- Latex support latexProperty :: Id latexProperty = describe "Support for LaTeX encoding" $ newId "latex" newtype F a = F { unF :: a -> Latex } getF :: Exercise a -> Maybe (F a) getF = getPropertyF latexProperty hasLatexEncoding :: Exercise a -> Bool hasLatexEncoding = isJust . getF -- | Uses exercise pretty-printer in case latex encoding is missing. latexPrinter :: Exercise a -> a -> Latex latexPrinter ex = maybe (toLatex . prettyPrinter ex) unF (getF ex) -- | Uses exercise pretty-printer in case latex encoding is missing. latexPrinterContext :: Exercise a -> Context a -> Latex latexPrinterContext ex ctx = let def = toLatex (prettyPrinterContext ex ctx) in fromMaybe def (unF <$> getF ex <*> fromContext ctx) latexEncoding :: ToLatex a => Exercise a -> Exercise a latexEncoding = latexEncodingWith toLatex latexEncodingWith :: (a -> Latex) -> Exercise a -> Exercise a latexEncodingWith = setPropertyF latexProperty . F ------------------------------------------------------------------- -- Encoder datatype type EncoderX a = Encoder (Exercise a, Options) type TypedEncoder a b = TypedValue (Type a) -> EncoderX a b infixr 5 () :: (t -> EncoderX a b, Type a t) -> TypedEncoder a b -> TypedEncoder a b ((p, t) q) tv@(val ::: tp) = case equal tp t of Just f -> p (f val) Nothing -> q tv encodeTyped :: (t -> EncoderX a b) -> Type a t -> TypedEncoder a b encodeTyped p t = (p, t) fail "Types do not match" ------------------------------------------------------------------- -- Decoder datatype type DecoderX a = Decoder (Exercise a, Options) type TypedDecoder a s = forall t . Type a t -> Decoder (Exercise a, Options) s t