remarks 0.1.7 → 0.1.8
raw patch · 15 files changed
+338/−166 lines, 15 files
Files
- README.md +14/−3
- app/Main.hs +16/−10
- remarks.cabal +8/−4
- src/Ast.hs +15/−1
- src/Collector.hs +0/−9
- src/Collector/Html.hs +0/−108
- src/Export.hs +14/−0
- src/Export/CSV.hs +30/−0
- src/Export/Generic.hs +11/−0
- src/Export/Html.hs +101/−0
- src/Invalid.hs +18/−0
- src/Parser/Impl.hs +27/−1
- src/PointsChecker.hs +9/−19
- src/PrettyPrinter.hs +15/−11
- src/PropertyInterp.hs +60/−0
README.md view
@@ -89,8 +89,8 @@ make a judgement about this point. Structural remarks are good for listing things to look for. For instance, a-(template) judgement for an operating systems exercise may look something like-this:+(template) judgement for an operating systems exercise might look something+like this: ``` ## T1: /15@@ -109,12 +109,15 @@ * Avoids race conditions * Has a good degree of multiprogramming * Solves the problem++### Bonus: +0+ * Has an accompanying implementation / simulation ``` Once filled in by a teaching assistant, this may look something like this: ```-## T1: 7/15+## T1: 9/15 ### Formal requirements: 5/5 * Has code@@ -146,7 +149,15 @@ + It's a ticket system, so it could be okay. * Solves the problem + In a complicated way, but yes.++### Bonus: +2+ * Has an accompanying implementation / simulation+ + Yes! ```++Bonus-judgements _must_ have the title `Bonus`, but they don't have to be there+if you don't like them. The presence of bonus judgments also allows points to+sum up to more than the given maximum. This is regarded as a feature. ## Points and Sums
app/Main.hs view
@@ -3,10 +3,11 @@ import Ast import Parser import PointsChecker+import PropertyInterp import PrettyPrinter-import Collector+import Export -import Control.Monad ( void, filterM, liftM )+import Control.Monad ( void, filterM, liftM, (<=<) ) import Data.List ( sort ) import System.Directory ( doesFileExist, doesDirectoryExist, listDirectory )@@ -18,6 +19,10 @@ import Text.PrettyPrint.GenericPretty +splitBy delimiter = foldr f [[]] + where f c l@(x:xs) | c == delimiter = []:l+ | otherwise = (c:x):xs+ report :: String -> IO () report = hPutStrLn stderr @@ -112,26 +117,26 @@ if not hasDir then parseTopFile pathWithExt else do -- we now also have directory- (fjs, (Judgement (h, cs, js))) <- parseFileWithDir pathWithExt+ (fjs, (Judgement (h, p, cs, js))) <- parseFileWithDir pathWithExt dirJs <- parseDir pathWithoutExt- pure $ fjs ++ [Judgement (h, cs, js ++ dirJs)]+ pure $ fjs ++ [Judgement (h, p, cs, js ++ dirJs)] parsePaths :: [FilePath] -> IO [[Judgement]] parsePaths = mapM parsePath check :: [Judgement] -> IO () check js = do- case mapM checkPoints js of+ case mapM (interpProps <=< checkPoints) js of Right newJs -> printJs newJs Left e -> putStrLn $ show e printJs :: [Judgement] -> IO () printJs = putStrLn . ppJs -collect :: [Judgement] -> IO ()-collect js = do- case mapM checkPoints js of- Right newJs -> putStrLn $ collectHTML newJs+export :: [String] -> [Judgement] -> IO ()+export format js = do+ case mapM (interpProps <=< checkPoints) js of+ Right newJs -> putStrLn $ exportCSV ";" format newJs Left e -> putStrLn $ show e main :: IO ()@@ -142,5 +147,6 @@ ("parse" : paths) -> parsePaths paths >>= putStrLn . pretty ("check" : paths) -> parsePaths paths >>= mapM_ check ("show" : paths) -> parsePaths paths >>= mapM_ printJs- ("collect" : paths) -> parsePaths paths >>= mapM_ collect+ ("export" : "--format" : format : paths) -> parsePaths paths >>= mapM_ (export (splitBy ';' format))+ ("export" : paths) -> parsePaths paths >>= mapM_ (export ["Title", "Total", "MaxPoints"]) (c:args) -> invalidCommand c args
remarks.cabal view
@@ -1,5 +1,5 @@ name: remarks-version: 0.1.7+version: 0.1.8 synopsis: A DSL for marking student work description: A DSL for marking student work; see README.md for further details. homepage: https://github.com/oleks/remarks#readme@@ -16,13 +16,17 @@ library hs-source-dirs: src exposed-modules: Ast+ , Config+ , Export+ , Export.Html+ , Export.Generic+ , Export.CSV+ , Invalid , Parser , Parser.Impl , PointsChecker , PrettyPrinter- , Config- , Collector- , Collector.Html+ , PropertyInterp build-depends: base >= 4.7 && < 5 , GenericPretty >= 1.2.1 , pretty >= 1.1.3.3
src/Ast.hs view
@@ -32,8 +32,22 @@ instance Out Comment +newtype Property + = Property (String, PropertyExp)+ deriving (Eq, Show, Generic)++instance Out Property++data PropertyExp + = Lookup (Int, String)+ | Value String+ | Num Double+ deriving (Eq, Show, Generic)++instance Out PropertyExp+ data Judgement- = Judgement (Header, [Comment], [Judgement])+ = Judgement (Header, [Property], [Comment], [Judgement]) | Bonus (Double, [Comment]) deriving (Eq, Show, Generic)
− src/Collector.hs
@@ -1,9 +0,0 @@-module Collector (collectHTML) where--import Ast-import Collector.Html--import Text.PrettyPrint--collectHTML :: [Judgement] -> String-collectHTML = render . htmlRemarks
− src/Collector/Html.hs
@@ -1,108 +0,0 @@-module Collector.Html (htmlRemarks) where--import Ast--import Text.PrettyPrint--htmlRemarks :: [Judgement] -> Doc-htmlRemarks js = - doctype $ html $ - (head_ $ documentStyle $$ documentScript) $$ - (body . table $ htmlTableHead (head js) $+$ vcat (map htmlJudgement js))--tag :: String -> Doc -> Doc -> Doc-tag tagStr attr doc = (text "<" <> text tagStr <+> attr <> text ">") $$ nest 2 doc $$ text ("</" ++ tagStr ++ ">") --etag :: String -> Doc -> Doc-etag tagStr = tag tagStr empty--atag :: String -> String -> Doc -> Doc-atag tagStr attrStr = tag tagStr (text attrStr)--doctype = ($$) (text "<!DOCTYPE html>")-html = etag "html"-head_ = etag "head"-body = etag "body"-script = atag "script" "type=\"text/javascript\""-style_ = etag "style"-table = atag "table" "border=\"1\""-tr = etag "tr"-trhidden = atag "tr" "style=\"display: none;\""-th = etag "th"-td = etag "td"-toggle = atag "a" "href=\"#\" onclick=\"toggleRow(this);\""-ul = etag "ul"-li = etag "li"--liclass c = atag "li" $ "class=\"" ++ c ++ "\""-tdspan i = atag "td" $ "colspan=\"" ++ (show i) ++ "\""--br = text "<br>"--details d1 d2 = etag "details" ((etag "summary" d1) $$ d2)--documentStyle = style_ $ - text "details {padding-left: 16px;}" $$ - text "ul {list-style: none; padding-left: 16px; padding-top: 0px; padding-bottom: 0px; margin-top: 0px; margin-bottom: 0px;}" $$- text "li.plus:before {content: \"+\"; margin-right: 4px;}" $$- text "li.minus:before {content: \"-\"; margin-right: 4px;}" $$- text "li.quest:before {content: \"?\"; margin-right: 4px;}" $$- text "li.star:before {content: \"*\"; margin-right: 4px;}"--documentScript = script $ - nest 2 ((text "function toggleRow(e){") $$- nest 2 ((text "var subRow = e.parentNode.parentNode.nextElementSibling;") $$- text "subRow.style.display = subRow.style.display === 'none' ? 'table-row' : 'none';") $$- (text "}"))--isIntegral :: Double -> Bool-isIntegral x = x == fromInteger (round x)--pointsDoc :: Double -> Doc-pointsDoc v | isNaN v = empty-pointsDoc v | isIntegral v = integer (round v)-pointsDoc v = double v--htmlTableHead :: Judgement -> Doc-htmlTableHead (Judgement (_, _, js)) = - tr $ th (text "Title") $$ (vcat $ map maketd js) $$ th (text "Total")- where- maketd (Judgement (Header(title, _, maxPoint), _, _)) = th $ text (title ++ "/") <> pointsDoc maxPoint--htmlJudgement :: Judgement -> Doc-htmlJudgement (Judgement (Header(title, points, maxPoint), comments, judgements)) = - (tr $ (td . toggle $ text title) $$ vcat (map htmlSubJudgement judgements) $$ (td $ pointsDoc points)) $$- (trhidden $ tdspan (length judgements+2) $ (htmlDetailComments comments $$ htmlDetailJudgements judgements))--htmlSubJudgement :: Judgement -> Doc-htmlSubJudgement (Judgement (Header(title, points, maxPoint), comments, judgements)) = - (td $ pointsDoc points) --htmlDetailJudgements :: [Judgement] -> Doc -htmlDetailJudgements = vcat . (map htmlDetailJudgement) --htmlDetailJudgement :: Judgement -> Doc-htmlDetailJudgement (Judgement (Header(title, points, maxPoint), comments, judgements)) = - details - (text title <> parens (pointsDoc points <> text "/" <> pointsDoc maxPoint)) - (htmlDetailComments comments $$ htmlDetailJudgements judgements)--htmlDetailComments :: [Comment] -> Doc-htmlDetailComments [] = text ""-htmlDetailComments comments = - ul . vcat $ map htmlDetailComment comments--htmlDetailComment :: Comment -> Doc-htmlDetailComment (Comment (mood, commentParts)) =- liclass (htmlDetailMood mood) $ vcat $ map htmlDetailCommentPart commentParts--htmlDetailMood :: Mood -> String-htmlDetailMood Positive = "plus"-htmlDetailMood Negative = "minus"-htmlDetailMood Neutral = "star"-htmlDetailMood Impartial = "quest"--htmlDetailCommentPart :: CommentPart -> Doc-htmlDetailCommentPart (CommentStr string) = text string-htmlDetailCommentPart (CommentCmt comment) =- ul $ htmlDetailComment comment
+ src/Export.hs view
@@ -0,0 +1,14 @@+module Export (exportHTML, exportCSV) where++import Ast+import Export.Html+import Export.CSV++import Text.PrettyPrint++exportHTML :: [Judgement] -> String+exportHTML = render . htmlRemarks++exportCSV :: String -> [String] -> [Judgement] -> String+exportCSV delim ps js = render $ csvRemarks delim ps js+
+ src/Export/CSV.hs view
@@ -0,0 +1,30 @@+module Export.CSV (csvRemarks) where++import Ast+import Export.Generic++import Text.PrettyPrint+import Data.List (intersperse)++csvRemarks :: String -> [String] -> [Judgement] -> Doc+csvRemarks delimiter propertyList judgements = + (hcat $ (intersperse (text delimiter)) $ map text propertyList) $$+ (vcat $ map (formatJudgement delimiter propertyList) judgements)++formatJudgement :: String -> [String] -> Judgement -> Doc+formatJudgement delimiter properties judgement = + hcat $ (intersperse (text delimiter)) $ map (mapfun judgement) properties+ where+ mapfun = flip lookupProperty ++lookupProperty :: String -> Judgement -> Doc+lookupProperty name (Judgement (_, properties, _, _)) = + case (lookup name (map (\(Property (n,v)) -> (n,v)) properties)) of+ Nothing -> error "Property not found."+ Just(value) -> formatPropertyExp value++formatPropertyExp :: PropertyExp -> Doc+formatPropertyExp (Lookup (index, name)) =+ brackets $ int index <> text "." <> text name+formatPropertyExp (Value value) = text value+formatPropertyExp (Num value) = pointsDoc value
+ src/Export/Generic.hs view
@@ -0,0 +1,11 @@+module Export.Generic where++import Text.PrettyPrint++isIntegral :: Double -> Bool+isIntegral x = x == fromInteger (round x)++pointsDoc :: Double -> Doc+pointsDoc v | isNaN v = empty+pointsDoc v | isIntegral v = integer (round v)+pointsDoc v = double v
+ src/Export/Html.hs view
@@ -0,0 +1,101 @@+module Export.Html (htmlRemarks) where++import Ast++import Export.Generic+import Text.PrettyPrint++htmlRemarks :: [Judgement] -> Doc+htmlRemarks js = + doctype $ html $ + (head_ $ documentStyle $$ documentScript) $$ + (body . table $ htmlTableHead (head js) $+$ vcat (map htmlJudgement js))++tag :: String -> Doc -> Doc -> Doc+tag tagStr attr doc = (text "<" <> text tagStr <+> attr <> text ">") $$ nest 2 doc $$ text ("</" ++ tagStr ++ ">") ++etag :: String -> Doc -> Doc+etag tagStr = tag tagStr empty++atag :: String -> String -> Doc -> Doc+atag tagStr attrStr = tag tagStr (text attrStr)++doctype = ($$) (text "<!DOCTYPE html>")+html = etag "html"+head_ = etag "head"+body = etag "body"+script = atag "script" "type=\"text/javascript\""+style_ = etag "style"+table = atag "table" "border=\"1\""+tr = etag "tr"+trhidden = atag "tr" "style=\"display: none;\""+th = etag "th"+td = etag "td"+toggle = atag "a" "href=\"#\" onclick=\"toggleRow(this);\""+ul = etag "ul"+li = etag "li"++liclass c = atag "li" $ "class=\"" ++ c ++ "\""+tdspan i = atag "td" $ "colspan=\"" ++ (show i) ++ "\""++br = text "<br>"++details d1 d2 = etag "details" ((etag "summary" d1) $$ d2)++documentStyle = style_ $ + text "details {padding-left: 16px;}" $$ + text "ul {list-style: none; padding-left: 16px; padding-top: 0px; padding-bottom: 0px; margin-top: 0px; margin-bottom: 0px;}" $$+ text "li.plus:before {content: \"+\"; margin-right: 4px;}" $$+ text "li.minus:before {content: \"-\"; margin-right: 4px;}" $$+ text "li.quest:before {content: \"?\"; margin-right: 4px;}" $$+ text "li.star:before {content: \"*\"; margin-right: 4px;}"++documentScript = script $ + nest 2 ((text "function toggleRow(e){") $$+ nest 2 ((text "var subRow = e.parentNode.parentNode.nextElementSibling;") $$+ text "subRow.style.display = subRow.style.display === 'none' ? 'table-row' : 'none';") $$+ (text "}"))++htmlTableHead :: Judgement -> Doc+htmlTableHead (Judgement (_, _, _, js)) = + tr $ th (text "Title") $$ (vcat $ map maketd js) $$ th (text "Total")+ where+ maketd (Judgement (Header(title, _, maxPoint), _, _, _)) = th $ text (title ++ "/") <> pointsDoc maxPoint++htmlJudgement :: Judgement -> Doc+htmlJudgement (Judgement (Header(title, points, maxPoint), _, comments, judgements)) = + (tr $ (td . toggle $ text title) $$ vcat (map htmlSubJudgement judgements) $$ (td $ pointsDoc points)) $$+ (trhidden $ tdspan (length judgements+2) $ (htmlDetailComments comments $$ htmlDetailJudgements judgements))++htmlSubJudgement :: Judgement -> Doc+htmlSubJudgement (Judgement (Header(title, points, maxPoint), _, comments, judgements)) = + (td $ pointsDoc points) ++htmlDetailJudgements :: [Judgement] -> Doc +htmlDetailJudgements = vcat . (map htmlDetailJudgement) ++htmlDetailJudgement :: Judgement -> Doc+htmlDetailJudgement (Judgement (Header(title, points, maxPoint), _, comments, judgements)) = + details + (text title <> parens (pointsDoc points <> text "/" <> pointsDoc maxPoint)) + (htmlDetailComments comments $$ htmlDetailJudgements judgements)++htmlDetailComments :: [Comment] -> Doc+htmlDetailComments [] = text ""+htmlDetailComments comments = + ul . vcat $ map htmlDetailComment comments++htmlDetailComment :: Comment -> Doc+htmlDetailComment (Comment (mood, commentParts)) =+ liclass (htmlDetailMood mood) $ vcat $ map htmlDetailCommentPart commentParts++htmlDetailMood :: Mood -> String+htmlDetailMood Positive = "plus"+htmlDetailMood Negative = "minus"+htmlDetailMood Neutral = "star"+htmlDetailMood Impartial = "quest"++htmlDetailCommentPart :: CommentPart -> Doc+htmlDetailCommentPart (CommentStr string) = text string+htmlDetailCommentPart (CommentCmt comment) =+ ul $ htmlDetailComment comment
+ src/Invalid.hs view
@@ -0,0 +1,18 @@+{-# LANGUAGE DeriveGeneric #-}++module Invalid where++import Ast++import Text.PrettyPrint.GenericPretty++data Invalid+ = PointsExceedMaxPoints Header+ | BadSubJudgementPointsSum Judgement+ | BadSubJudgementMaxPointsSum Judgement+ | NoPointsInBottomJudgement Judgement+ | PropertyNotFound String+ deriving (Eq, Show, Generic)++instance Out Invalid+
src/Parser/Impl.hs view
@@ -59,6 +59,31 @@ comments <- many parseComment' pure $ Bonus (points, comments) +parsePropertyExp :: ReadP PropertyExp+parsePropertyExp = choice [lookupProp, value]+ where + lookupProp = do+ void $ char '['+ index <- parseIntegral+ void $ char '.'+ name <- lineToken $ munchTillExcl ']'+ void $ lineBreak+ case (maybeRead (index)) of+ Just i -> pure $ Lookup (i, name)+ _ -> pfail+ value = do+ v <- satisfy (/='[')+ value <- parseLine+ pure $ Value (v:value)++parseProperty :: ReadP Property+parseProperty = do+ void $ string " :"+ name <- lineToken $ munchTillExcl ':'+ void $ char ' '+ value <- parsePropertyExp+ pure $ Property (name, value)+ parseRegularJudgement :: Int -> String -> ReadP Judgement parseRegularJudgement depth title = do points <- (lineToken $ parsePoints) +++ (return $ 1/0)@@ -68,9 +93,10 @@ let header = Header (title, points, maxPoints) + properties <- many parseProperty comments <- many parseComment' subjs <- many $ parseJudgement (depth + 1)- pure $ Judgement (header, comments, subjs)+ pure $ Judgement (header, properties, comments, subjs) parseJudgement :: Int -> ReadP Judgement parseJudgement depth = skipSpaces *> do
src/PointsChecker.hs view
@@ -1,22 +1,12 @@ {-# LANGUAGE DeriveGeneric #-} -module PointsChecker ( checkPoints, Invalid(..) ) where+module PointsChecker ( checkPoints ) where import Ast+import Invalid import Control.Monad ( forM_ ) -import Text.PrettyPrint.GenericPretty--data Invalid- = PointsExceedMaxPoints Header- | BadSubJudgementPointsSum Judgement- | BadSubJudgementMaxPointsSum Judgement- | NoPointsInBottomJudgement Judgement- deriving (Eq, Show, Generic)--instance Out Invalid- try :: Bool -> Invalid -> Either Invalid () try True = \_ -> Right () try False = Left@@ -26,20 +16,20 @@ x ~= y = abs (x - y) <= 0.01 checkPointsSubJs :: Judgement -> Either Invalid Judgement-checkPointsSubJs (Judgement (h @ (Header (t, _, maxP)), cs, subJs)) = do+checkPointsSubJs (Judgement (h @ (Header (t, _, maxP)), prop, cs, subJs)) = do newSubJs <- mapM checkPoints subJs let newP = sum $ map points newSubJs- pure $ Judgement (Header (t, newP, maxP), cs, newSubJs)+ pure $ Judgement (Header (t, newP, maxP), prop, cs, newSubJs) checkPointsSubJs j = pure j checkPoints :: Judgement -> Either Invalid Judgement-checkPoints j @ (Judgement (h @ (Header (_, p, maxP)), _, [])) | isInfinite p = do+checkPoints j @ (Judgement (h @ (Header (_, p, maxP)), _, _, [])) | isInfinite p = do Left $ NoPointsInBottomJudgement j-checkPoints j @ (Judgement (h @ (Header (_, p, maxP)), _, subJs @ (_:_))) | isInfinite p = do+checkPoints j @ (Judgement (h @ (Header (_, p, maxP)), _, _, subJs @ (_:_))) | isInfinite p = do try ((sum $ map maxPoints subJs) ~= maxP) (BadSubJudgementMaxPointsSum j) checkPointsSubJs j-checkPoints j @ (Judgement (h @ (Header (_, p, maxP)), _, subJs)) = do+checkPoints j @ (Judgement (h @ (Header (_, p, maxP)), _, _, subJs)) = do try (p <= maxP) (PointsExceedMaxPoints h) case subJs of@@ -54,8 +44,8 @@ points :: Judgement -> Double points (Bonus (v, _)) = v-points (Judgement (Header (_, v, _), _, _)) = v+points (Judgement (Header (_, v, _), _, _, _)) = v maxPoints :: Judgement -> Double maxPoints (Bonus _) = 0.0-maxPoints (Judgement (Header (_, _, v), _, _)) = v+maxPoints (Judgement (Header (_, _, v), _, _, _)) = v
src/PrettyPrinter.hs view
@@ -1,6 +1,7 @@ module PrettyPrinter (ppJs) where import Ast+import Export.Generic import Text.PrettyPrint import Data.List (intersperse)@@ -12,23 +13,26 @@ formatJudgement level (Bonus (points, comments)) = (text $ replicate level '#') <+> text "Bonus" <> colon <+> text "+" <> pointsDoc points-formatJudgement level (Judgement (header, comments, judgements)) =+formatJudgement level (Judgement (header, properties, comments, judgements)) = formatHeader level header $+$- (nest 2 $ vcat $ map (formatComment) comments) $+$+ (nest 2 $ vcat $ map formatProperty properties) $+$+ (nest 2 $ vcat $ map formatComment comments) $+$ (vcat $ map (formatJudgement (level + 1)) judgements) -isIntegral :: Double -> Bool-isIntegral x = x == fromInteger (round x)--pointsDoc :: Double -> Doc-pointsDoc v | isNaN v = empty-pointsDoc v | isIntegral v = integer (round v)-pointsDoc v = double v- formatHeader :: Int -> Header -> Doc formatHeader level (Header (title, point, maxPoints)) =- (text $ replicate level '#') <+> text title <> colon <> space <>+ (text $ replicate level '#') <+> text title <> colon <> pointsDoc point <> text "/" <> pointsDoc maxPoints++formatProperty :: Property -> Doc+formatProperty (Property (name, value)) =+ colon <> text name <> colon <+> formatPropertyExp value++formatPropertyExp :: PropertyExp -> Doc+formatPropertyExp (Lookup (index, name)) =+ brackets $ int index <> text "." <> text name+formatPropertyExp (Value value) = text value+formatPropertyExp (Num double) = pointsDoc double formatComment :: Comment -> Doc formatComment (Comment (mood, commentParts)) =
+ src/PropertyInterp.hs view
@@ -0,0 +1,60 @@+{-# LANGUAGE DeriveGeneric #-}++module PropertyInterp ( interpProps ) where++import Ast+import Invalid++import Text.PrettyPrint.GenericPretty++-- In the lists index 0 is used for predifined variables: Title, Total, MaxPoints+-- This should refactored to real monadic programming++data PropertyValue+ = StrVal String+ | DoubVal Double+ deriving (Eq, Show, Generic)++instance Out PropertyValue++interpProps :: Judgement -> Either Invalid Judgement+interpProps j = + case (interpJudgement j) of+ Right (jnew, _) -> Right jnew+ Left invalid -> Left invalid++interpJudgement :: Judgement -> Either Invalid (Judgement, [(String, PropertyValue)])+interpJudgement j @ (Judgement (h, prop, cs, js)) = do+ updlist <- mapM interpJudgement js+ let (jupdates, jsPropVals) = unzip updlist+ predef <- generatePredefinedValues h+ propVals <- mapM (bindProp (predef:jsPropVals)) (addPredifinedProps prop)+ let newProps = map propValToProperties propVals+ pure (Judgement (h, newProps, cs, jupdates), propVals)++propValToProperties :: (String, PropertyValue) -> Property+propValToProperties (str, StrVal string) = Property (str, Value string)+propValToProperties (str, DoubVal double) = Property (str, Num double)++addPredifinedProps :: [Property] -> [Property]+addPredifinedProps p = + Property("Title", Lookup (0, "Title")) : + Property("Total", Lookup (0, "Total")) : + Property("MaxPoints", Lookup (0, "MaxPoints")) : p++generatePredefinedValues :: Header -> Either Invalid [(String, PropertyValue)]+generatePredefinedValues (Header (t, p, maxP)) = + pure [("Title", StrVal t), ("Total", DoubVal p), ("MaxPoints", DoubVal maxP)]++bindProp :: [[(String, PropertyValue)]] -> Property -> Either Invalid (String, PropertyValue)+bindProp propEnv (Property (name, propExp)) = + case (evalPropExp propExp propEnv) of+ Right (val) -> pure (name, val)+ Left invalid -> Left invalid++evalPropExp :: PropertyExp -> [[(String, PropertyValue)]] -> Either Invalid PropertyValue+evalPropExp (Value s) _ = pure $ StrVal s+evalPropExp (Lookup (i, p)) propEnv = + case (lookup p (propEnv !! (i))) of+ Nothing -> Left $ PropertyNotFound p+ (Just s) -> pure s