Safe Haskell | None |
---|---|
Language | Haskell2010 |
FlexTask.Generic.Parse
Description
Generic Parsing interface for submission types.
Synopsis
- class Parse a where
- formParser :: Parser a
- parseInstanceSingleChoice :: (Bounded a, Enum a, Eq a) => Parser a
- parseInstanceMultiChoice :: (Bounded a, Enum a, Eq a) => Parser [a]
- parseInstanceSingleInputList :: Parser a -> Parser (SingleInputList a)
- escaped :: Parser a -> Parser a
- parseWithOrReport :: forall (m :: Type -> Type) o a. (Monad m, OutputCapable (ReportT o m)) => Parser a -> (String -> ParseError -> LangM (ReportT o m)) -> String -> LangM' (ReportT o m) a
- reportWithFieldNumber :: forall (m :: Type -> Type). OutputCapable m => String -> ParseError -> LangM m
- parseInfallibly :: Applicative m => Parser a -> String -> m a
- parseWithFallback :: forall (m :: Type -> Type) o a. (Monad m, OutputCapable (ReportT o m)) => Parser a -> (String -> Maybe ParseError -> ParseError -> LangM (ReportT o m)) -> Parser () -> String -> LangM' (ReportT o m) a
- displayInputAnd :: forall (m :: Type -> Type) a. OutputCapable m => (Maybe a -> ParseError -> LangM m) -> String -> Maybe a -> ParseError -> LangM m
- asSubmission :: [[String]] -> String
Documentation
Class for generic parsing of types. Bodyless instances can be declared for any type instancing Generic. Exception: Types with multiple constructors. Use utility functions for those or provide your own instance.
Minimal complete definition
Nothing
Methods
formParser :: Parser a Source #
A Parsec
parser for values of the instanced type.
Ready to use for parsing submissions to flex-tasks.
Examples
>>>
parseTest (formParser @Bool) $ asSubmission [["yes"]]
True
>>>
parseTest (formParser @[Double]) $ asSubmission [["2.0", "0.12e-12"]]
[2.0,1.2e-13]
>>>
parseTest (formParser @(String,Integer)) $ asSubmission [["Good day"],["-100"]]
("Good day",-100)
>>>
parseTest (formParser @Int) $ asSubmission [["Test"]]
parse error at (line 1, column 3): ...
Instances
Parse Helpers
parseInstanceSingleChoice :: (Bounded a, Enum a, Eq a) => Parser a Source #
Parser for single choice answer of Enum types. Use as implementation of formParser
for manual Parse
instances.
Intended for use with types such as
data MyType = One | Two | Three deriving (Bounded, Enum, Eq)
that can not use a bodyless Parse
instance.
Example
>>>
instance Parse MyType where formParser = parseInstanceSingleChoice
>>>
parseTest (formParser @MyType) $ asSubmission [["1"]]
One
parseInstanceMultiChoice :: (Bounded a, Enum a, Eq a) => Parser [a] Source #
Same as parseInstanceSingleChoice
, but for parsing a List of the given type, i.e. a multiple choice version.
Example
>>>
instance Parse [MyType] where formParser = parseInstanceMultiChoice
>>>
parseTest (formParser @[MyType]) $ asSubmission [["1","3"]]
[One,Three]
parseInstanceSingleInputList :: Parser a -> Parser (SingleInputList a) Source #
Parser for a list of values inside a single input field.
Takes a parser for individual list values.
Please note that it must not use the escape
function found in this module.
Use as implementation of formParser
for manual Parse
instances.
These instances are already provided for standard types as is.
Example
>>>
newtype CustomInt = CustomInt Int deriving Show
>>>
let myParser = CustomInt . read <$> many1 digit
>>>
instance Parse (SingleInputList CustomInt) where formParser = parseInstanceSingleInputList myParser
>>>
parseTest (formParser @(SingleInputList CustomInt)) $ asSubmission [["1, 2, 3"]]
SingleInputList {getList = [CustomInt 1,CustomInt 2,CustomInt 3]}
escaped :: Parser a -> Parser a Source #
Parses FlexTask answer escape characters enclosing the input.
Use this to wrap preexisting parsers that are used to parse a student solution.
Otherwise your parser will fail on the escape characters.
Parsers generated by Parse
already consider the escaping and must not be wrapped.
Examples
>>>
let myParser = read @Int <$> many1 digit
>>>
parseTest myParser $ asSubmission [["12345"]]
parse error at (line 1, column 1): ...
>>>
parseTest (escaped myParser) $ asSubmission [["12345"]]
12345
>>>
parseTest (escaped $ formParser @Int) $ asSubmission [["12345"]]
parse error at (line 1, column 3): ...
Embedding Functions
parseWithOrReport :: forall (m :: Type -> Type) o a. (Monad m, OutputCapable (ReportT o m)) => Parser a -> (String -> ParseError -> LangM (ReportT o m)) -> String -> LangM' (ReportT o m) a Source #
Parses a String with the given parser and embeds the result into the OutputCapable
interface.
No value will be embedded in case of a ParseError
. Instead, an error report is given then.
That report is built using the second function argument.
The report will automatically abort after displaying.
It is therefore not necessary to include a refuse
, but it is not harmful either.
Adding a refuse will display text and cut off any following output as usual.
This can be useful for giving better error messages.
Examples
>>>
import Control.OutputCapable.Blocks (Language(..))
>>>
import Control.OutputCapable.Blocks.Debug (run)
>>>
run English $ parseWithOrReport (formParser @Int) reportWithFieldNumber $ asSubmission [["1"]]
Just 1
>>>
run German $ parseWithOrReport (formParser @Int) reportWithFieldNumber $ asSubmission [["Hello"]]
Fehler in Eingabefeld 1: "Hello" an Position 1>>>> unexpected "H" expecting white space, "-", "+" or digit<<<< Nothing
reportWithFieldNumber :: forall (m :: Type -> Type). OutputCapable m => String -> ParseError -> LangM m Source #
Provide error report with positional information relative to an input form.
See parseWithOrReport
for a usage example.
parseInfallibly :: Applicative m => Parser a -> String -> m a Source #
Parses a String with the given parser and embeds the result into the OutputCapable
interface.
Use when you know that there will be no error (e.g., when the parser used is formParser
and
the input form is "infallible" since only constructed from String text fields, single, multiple choice).
Examples
>>>
import Control.OutputCapable.Blocks (Language(..))
>>>
import Control.OutputCapable.Blocks.Debug (run)
>>>
run German $ parseInfallibly (formParser @SingleChoiceSelection) $ asSubmission [["1"]]
Just (SingleChoiceSelection {getAnswer = Just 1})
>>>
run English $ parseInfallibly (formParser @(SingleInputList Double)) $ asSubmission [["Wrong input"]]
*** Exception: The impossible happened: (line 1, column 3): ...
Error Processing
Arguments
:: forall (m :: Type -> Type) o a. (Monad m, OutputCapable (ReportT o m)) | |
=> Parser a | Parser to use initially |
-> (String -> Maybe ParseError -> ParseError -> LangM (ReportT o m)) | How to produce an error report based on: ^ 1. The input string ^ 2. The possible parse error of the fallback parser ^ 3. The original parse error |
-> Parser () | The secondary parser to use in case of a parse error. ^ Only used for generating possible further errors, thus does not return a value. |
-> String | The input |
-> LangM' (ReportT o m) a | The finished error report or embedded value |
Parses a String with the given parser.
Allows for further processing of a possible parse error.
A second parser is used as a fallback in case of an error.
The result of both parsers is then used to construct the report.
Comments on refuse
's behaviour for parseWithOrReport
also apply for this function.
This can be useful for giving more specific error messages,
e.g. checking a term for bracket consistency even if the parser failed early on.
Examples
>>>
import Control.OutputCapable.Blocks (Language(..))
>>>
import Control.OutputCapable.Blocks.Debug (run)
>>>
let onDouble = text "Fractions are not allowed"
>>>
let onNoNumber = text "That's not a number"
>>>
let errorHandling mSecondError _ = maybe onDouble (const onNoNumber) mSecondError
>>>
let errorFeedback = displayInputAnd errorHandling
>>>
let intFirst = formParser @Int
>>>
let thenDouble = void $ formParser @Double
>>>
run English $ parseWithFallback intFirst errorFeedback thenDouble $ asSubmission [["2"]]
Just 2
>>>
run English $ parseWithFallback intFirst errorFeedback thenDouble $ asSubmission [["2.5"]]
Error in """2.5""" : >>>>Fractions are not allowed<<<< Nothing
>>>
run English $ parseWithFallback intFirst errorFeedback thenDouble $ asSubmission [["I'm a number"]]
Error in """I'm a number""" : >>>>That's not a number<<<< Nothing
displayInputAnd :: forall (m :: Type -> Type) a. OutputCapable m => (Maybe a -> ParseError -> LangM m) -> String -> Maybe a -> ParseError -> LangM m Source #
Provide error report displaying the entire input and additional, customizable messaging.
This is intended to be used with parseWithFallback
,
see its description for a usage example.
Debugging
asSubmission :: [[String]] -> String Source #
Turn a nested list of String values into the Autotool Flex-Tasks submission format.
Each inner list represents a group of inputs that will be interpreted as a list.
The result is parsable by a matching formParser
.
Used for debugging parsers.
Examples
>>>
parseTest (formParser @([Integer],String)) $ asSubmission [["20","34","-7"], ["Some Answer"]]
([20,34,-7],"Some Answer")