ideas-1.8: src/Ideas/Encoding/Encoder.hs
{-# 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