diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -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
 
diff --git a/app/Main.hs b/app/Main.hs
--- a/app/Main.hs
+++ b/app/Main.hs
@@ -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
diff --git a/remarks.cabal b/remarks.cabal
--- a/remarks.cabal
+++ b/remarks.cabal
@@ -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
diff --git a/src/Ast.hs b/src/Ast.hs
--- a/src/Ast.hs
+++ b/src/Ast.hs
@@ -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)
 
diff --git a/src/Collector.hs b/src/Collector.hs
deleted file mode 100644
--- a/src/Collector.hs
+++ /dev/null
@@ -1,9 +0,0 @@
-module Collector (collectHTML) where
-
-import Ast
-import Collector.Html
-
-import Text.PrettyPrint
-
-collectHTML :: [Judgement] -> String
-collectHTML = render . htmlRemarks
diff --git a/src/Collector/Html.hs b/src/Collector/Html.hs
deleted file mode 100644
--- a/src/Collector/Html.hs
+++ /dev/null
@@ -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
diff --git a/src/Export.hs b/src/Export.hs
new file mode 100644
--- /dev/null
+++ b/src/Export.hs
@@ -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
+
diff --git a/src/Export/CSV.hs b/src/Export/CSV.hs
new file mode 100644
--- /dev/null
+++ b/src/Export/CSV.hs
@@ -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
diff --git a/src/Export/Generic.hs b/src/Export/Generic.hs
new file mode 100644
--- /dev/null
+++ b/src/Export/Generic.hs
@@ -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
diff --git a/src/Export/Html.hs b/src/Export/Html.hs
new file mode 100644
--- /dev/null
+++ b/src/Export/Html.hs
@@ -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
diff --git a/src/Invalid.hs b/src/Invalid.hs
new file mode 100644
--- /dev/null
+++ b/src/Invalid.hs
@@ -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
+
diff --git a/src/Parser/Impl.hs b/src/Parser/Impl.hs
--- a/src/Parser/Impl.hs
+++ b/src/Parser/Impl.hs
@@ -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
diff --git a/src/PointsChecker.hs b/src/PointsChecker.hs
--- a/src/PointsChecker.hs
+++ b/src/PointsChecker.hs
@@ -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
diff --git a/src/PrettyPrinter.hs b/src/PrettyPrinter.hs
--- a/src/PrettyPrinter.hs
+++ b/src/PrettyPrinter.hs
@@ -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)) =
diff --git a/src/PropertyInterp.hs b/src/PropertyInterp.hs
new file mode 100644
--- /dev/null
+++ b/src/PropertyInterp.hs
@@ -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
