diff --git a/cabal.project b/cabal.project
index 920ecd76..ba959c04 100644
--- a/cabal.project
+++ b/cabal.project
@@ -15,4 +15,5 @@ package *
-- Build static linked, vanilla libraries to reduce build time.
shared: False
-executable-dynamic: False
\ No newline at end of file
+executable-dynamic: False
+tests: True
\ No newline at end of file
diff --git a/lam4-backend/lam4-backend.cabal b/lam4-backend/lam4-backend.cabal
index bcdc902c..9500a60e 100644
--- a/lam4-backend/lam4-backend.cabal
+++ b/lam4-backend/lam4-backend.cabal
@@ -43,10 +43,12 @@ library
Base.Aeson
Base.ByteString
Base.Grisette
+ Lam4.Main
Lam4.Expr.Name
Lam4.Expr.ConcreteSyntax
Lam4.Expr.CommonSyntax
Lam4.Expr.Parser
+ Lam4.Expr.Printer
Lam4.Parser.Type
Lam4.Parser.Monad
Lam4.Expr.ConEvalAST
@@ -60,7 +62,8 @@ library
base,
containers,
foldable1-classes-compat,
- lens,
+ lens-regex-pcre,
+ lens,
-- just for Control.Lens.Plated
optics,
mtl,
@@ -70,6 +73,10 @@ library
aeson,
aeson-optics,
bytestring,
+ cradle,
+ optparse-applicative,
+ filepath,
+ string-interpolate,
either,
pretty-show,
grisette >= 0.8,
@@ -94,23 +101,35 @@ library
-- bytestring,
-- lam4-backend
--- test-suite lam4-backend-test
--- import: defaults
--- default-language: GHC2021
-
--- -- Modules included in this executable, other than Main.
--- -- other-modules:
-
--- -- The interface type and version of the test suite.
--- type: exitcode-stdio-1.0
-
--- -- Directories containing source files.
--- hs-source-dirs: test
-
--- -- The entrypoint to the test suite.
--- main-is: Main.hs
+test-suite lam4-backend-test
+ import: defaults
+ ghc-options: -threaded
+ default-language: GHC2021
--- -- Test dependencies.
--- build-depends:
--- base ^>=4.18.2.0,
--- lam4-backend
+ -- Modules included in this executable, other than Main.
+ other-modules:
+ PrinterSpec
+
+ -- The interface type and version of the test suite.
+ type:
+ exitcode-stdio-1.0
+
+ -- Directories containing source files.
+ hs-source-dirs:
+ test
+ build-tool-depends:
+ hspec-discover:hspec-discover
+ -- The entrypoint to the test suite.
+ main-is:
+ Spec.hs
+
+ -- Test dependencies.
+ build-depends:
+ lam4-backend,
+ base,
+ hspec,
+ hspec-golden,
+ filepath,
+ directory,
+ text,
+ bytestring
diff --git a/lam4-backend/src/Lam4/Expr/ConcreteSyntax.hs b/lam4-backend/src/Lam4/Expr/ConcreteSyntax.hs
index b8dad28e..0c0d8bd5 100644
--- a/lam4-backend/src/Lam4/Expr/ConcreteSyntax.hs
+++ b/lam4-backend/src/Lam4/Expr/ConcreteSyntax.hs
@@ -28,6 +28,8 @@ module Lam4.Expr.ConcreteSyntax
-- * Statements
, Statement(..)
, DeonticModal(..)
+ , Action(..)
+ , PrimAction(..)
-- * Traversals
, exprSubexprs
diff --git a/lam4-backend/src/Lam4/Expr/Printer.hs b/lam4-backend/src/Lam4/Expr/Printer.hs
new file mode 100644
index 00000000..4b788766
--- /dev/null
+++ b/lam4-backend/src/Lam4/Expr/Printer.hs
@@ -0,0 +1,471 @@
+{-# LANGUAGE FlexibleInstances, LambdaCase, OverloadedRecordDot, DisambiguateRecordFields, QuasiQuotes #-}
+
+module Lam4.Expr.Printer (printTree)
+ where
+import Base.NonEmpty (NonEmpty(..))
+import qualified Base.Text as T
+import Lam4.Expr.CommonSyntax
+import Lam4.Expr.ConcreteSyntax
+import Lam4.Expr.Name (Name (..))
+import Data.Char (isSpace)
+import qualified Data.String.Interpolate as I (i)
+
+-- NB. Most of this file is generated by BNFC from a very ugly grammar that Inari hacked together.
+-- This printer (I dare not call it pretty) is a first proof-of-concept, which I intend to throw away and make a better one. Better in the sense of more readable and maintainable.
+
+-- | The top-level printing method.
+printTree :: Print a => a -> String
+printTree = render . prt 0
+
+type Doc = [ShowS] -> [ShowS]
+
+doc :: ShowS -> Doc
+doc = (:)
+
+render :: Doc -> String
+render d = rend 0 False (map ($ "") $ d []) ""
+ where
+ rend
+ :: Int -- ^ Indentation level.
+ -> Bool -- ^ Pending indentation to be output before next character?
+ -> [String]
+ -> ShowS
+ rend i p = \case
+ "STRUCTURE":t:"END":ts -> showString [I.i|STRUCTURE #{t} END|] . new i ts
+ "STRUCTURE":t :ts -> showString [I.i|STRUCTURE #{t}|] . new (i+1) ts
+ "END" :ts -> onNewLine (i-1) p . showString "END" . new (i-1) ts
+ "[" :ts -> char '[' . rend i False ts
+ "(" :ts -> char '(' . rend i False ts
+ "{": "}" :ts -> showString "{}" . rend i False ts
+ "{" :ts -> onNewLine i p . showChar '{' . new (i+1) ts
+ "}" : ";":ts -> onNewLine (i-1) p . showString "};" . new (i-1) ts
+ "}" :ts -> onNewLine (i-1) p . showChar '}' . new (i-1) ts
+ "--" : t :ts -> onNewLine i p . showString "-- " . showString t . new i ts
+ "/-" :ts -> onNewLine i p . showString "/-" . new (i+1) ts
+ "-/" :ts -> onNewLine (i-1) p . showString "-/" . new (i-1) ts
+ "AND" :ts -> onNewLine i p . showString "AND" . spaces 1 . rend i False ts
+ "FOLD_LEFT" : ts -> onNewLine (i+1) p . showString "FOLD_LEFT" . new (i+2) ts
+ "FOLD_RIGHT" : ts -> onNewLine (i+1) p . showString "FOLD_RIGHT" . new (i+2) ts
+ "using" :t:ts -> pending . showString "using" . spaces 10 . showString t . new i ts
+ "starting_with" :t:ts -> pending . showString "starting_with" . spaces 2 . showString t . new i ts
+ "over" :t:"'s":u:ts -> pending . showString "over" . spaces 11 . showString [I.i|#{t}'s #{u}|] . new (i-1) ts
+ "over" :t:ts -> pending . showString "over" . spaces 11 . showString t . new (i-1) ts
+ "FUNCTION" : ts -> onNewLine i p . showString "FUNCTION //eventual type signature" . new i ts
+ [";"] -> char ';'
+ ";" :ts -> char ';' . new i ts
+ t : ts@(s:_) | closingOrPunctuation s
+ -> pending . showString t . rend i False ts
+ t :ts -> pending . space t . rend i False ts
+ [] -> id
+ where
+ -- Output character after pending indentation.
+ char :: Char -> ShowS
+ char c = pending . showChar c
+
+ -- Output pending indentation.
+ pending :: ShowS
+ pending = if p then indent i else id
+
+ -- Indentation (spaces) for given indentation level.
+ indent :: Int -> ShowS
+ indent i = replicateS (2*i) (showChar ' ')
+
+ spaces :: Int -> ShowS
+ spaces i = replicateS i (showChar ' ')
+
+
+ -- Continue rendering in new line with new indentation.
+ new :: Int -> [String] -> ShowS
+ new j ts = showChar '\n' . rend j True ts
+
+ -- Make sure we are on a fresh line.
+ onNewLine :: Int -> Bool -> ShowS
+ onNewLine i p = (if p then id else showChar '\n') . indent i
+
+ -- Separate given string from following text by a space (if needed).
+ space :: String -> ShowS
+ space t s =
+ case (all isSpace t, null spc, null rest) of
+ (True , _ , True ) -> [] -- remove trailing space
+ (False, _ , True ) -> t -- remove trailing space
+ (False, True, False) -> t ++ ' ' : s -- add space if none
+ _ -> t ++ s
+ where
+ (spc, rest) = span isSpace s
+
+ closingOrPunctuation :: String -> Bool
+ closingOrPunctuation [c] = c `elem` closerOrPunct
+ closingOrPunctuation _ = False
+
+ closerOrPunct :: String
+ closerOrPunct = ")],;"
+
+parenth :: Doc -> Doc
+parenth ss = doc (showChar '(') . ss . doc (showChar ')')
+
+concatS :: [ShowS] -> ShowS
+concatS = foldr (.) id
+
+concatD :: [Doc] -> Doc
+concatD = foldr (.) id
+
+replicateS :: Int -> ShowS -> ShowS
+replicateS n f = concatS (replicate n f)
+
+-- | The printer class does the job.
+
+class Print a where
+ prt :: Int -> a -> Doc
+
+instance {-# OVERLAPPABLE #-} Print a => Print [a] where
+ prt i = concatD . map (prt i)
+
+instance Print Char where
+ prt _ c = doc (showChar '\'' . mkEsc '\'' c . showChar '\'')
+
+instance Print String where
+ prt _ = printString
+
+instance Print T.Text where
+ prt _ x = doc (showString (backtickIfSpaces x))
+
+backtickIfSpaces :: T.Text -> String
+backtickIfSpaces t = if T.any (==' ') t then [I.i|`#{t}`|] else T.unpack t
+
+printString :: String -> Doc
+printString s = doc (showChar '"' . concatS (map (mkEsc '"') s) . showChar '"')
+
+mkEsc :: Char -> Char -> ShowS
+mkEsc q = \case
+ s | s == q -> showChar '\\' . showChar s
+ '\\' -> showString "\\\\"
+ '\n' -> showString "\\n"
+ '\t' -> showString "\\t"
+ s -> showChar s
+
+prPrec :: Int -> Int -> Doc -> Doc
+prPrec i j = if j < i then parenth else id
+
+instance Print Int where
+ prt _ x = doc (shows x)
+
+instance Print Double where
+ prt _ x = doc (shows x)
+
+
+-- Function arguments are separated by newlines
+newtype FunArg = FunArg {unFunArg :: String}
+
+-- Anonymous functions have a special empty list
+newtype AnonFunArg = AnonFunArg {unAnonFunArg :: Name}
+
+mkFunArg :: T.Text -> FunArg
+mkFunArg t = FunArg [I.i|#{backtickIfSpaces t}|]
+-- mkFunArg t = FunArg [I.i|#{backtickIfSpaces t} : TypeMissing|] -- TODO: add type information
+
+instance Print FunArg where
+ prt _ x = doc (showString (unFunArg x))
+
+instance Print [FunArg] where
+ prt _ [] = concatD []
+ prt _ [x] = concatD [prt 0 x]
+ prt _ (x:xs) = concatD [prt 0 x, doc (showChar '\n'), prt 0 xs]
+
+instance Print AnonFunArg where
+ prt i x = prt i (unAnonFunArg x)
+
+instance Print [AnonFunArg] where
+ prt _ [] = doc (showString "()")
+ prt _ [x] = concatD [prt 0 x]
+ prt _ (x:xs) = concatD [prt 0 x, doc (showChar ' '), prt 0 xs]
+
+instance Print [Expr] where
+ prt _ [] = concatD []
+ prt _ [x] = concatD [prt 0 x]
+ prt _ (x:xs) = concatD [prt 0 x, doc (showString ","), prt 0 xs]
+
+instance Print [Name] where
+ prt _ [] = concatD []
+ prt _ [x] = concatD [prt 0 x]
+ prt _ (x:xs) = concatD [prt 0 x, doc (showString ","), prt 0 xs]
+
+instance Print [Statement] where
+ prt _ [] = concatD []
+ prt _ [x] = concatD [prt 0 x]
+ prt _ (x:xs) = concatD [prt 0 x, doc (showString "\n\n"), prt 0 xs]
+
+instance Print [Decl] where
+ prt _ [] = concatD []
+ prt _ [x] = concatD [prt 0 x]
+ prt _ (x:xs) = concatD [prt 0 x, doc (showString "\n\n"), prt 0 xs]
+
+instance Print [RowTypeDecl] where
+ prt _ [] = concatD []
+ prt _ [x] = concatD [prt 0 x]
+ prt _ (x:xs) = concatD [prt 0 x, doc (showString "\n "), prt 0 xs]
+
+-- type Row a = [(Name, a)] where
+instance Print (Row Expr) where
+ prt _ [] = concatD []
+ prt _ [x] = concatD [prt 0 x]
+ prt _ (x:xs) = concatD [prt 0 x, doc (showString ","), prt 0 xs]
+
+-- Building block for Row
+instance Print (Name, Expr) where
+ prt i (name, expr) = prPrec i 0 (concatD [prt 0 name, doc (showString "="), prt 0 expr])
+
+instance Print Decl where
+ prt i = \case
+ NonRec name expr -> prPrec i 0 (concatD [
+ doc (showString "DEFINE")
+ , prt 0 (mkFunArg name.name)
+ , doc (showString "=")
+ , prt 0 expr])
+
+ -- Name is actually part of the Predicate.
+ -- doing this quick hack to move it to where it belongs.
+ Rec name (Predicate md vars expr) -> prPrec i 0 (concatD [prt 0 (Predicate md (name:vars) expr)])
+
+ Rec name (Fun md vars expr) -> prPrec i 0 (concatD [prt 0 (Fun md (name:vars) expr)])
+
+ -- if Expr is not Predicate or Fun, render Name here
+ Rec name expr -> prPrec i 0 (concatD [prt 0 name, prt 0 expr])
+
+ -- Name is actually part of the RecordDecl.
+ -- doing this quick hack to move it to where it belongs.
+ DataDecl name (RecordDecl rowtypedecls parents descr) ->
+ let newDecl = RecordDecl (dummyRowTypeDecl name:rowtypedecls) parents descr
+ in prPrec i 0 (concatD [prt 0 newDecl])
+
+ Eval (FunApp (Fun _md args body) xs) -> prPrec i 0 (concatD [
+ doc (showString "@REPORT (\\")
+ , prt 0 (fmap AnonFunArg args)
+ , doc (showString "=>")
+ , prt 0 body
+ , doc (showString ")")
+ , parenth (prt 0 xs)
+ ])
+ Eval expr -> prPrec i 0 (concatD [doc (showString "@REPORT"), prt 0 expr])
+
+dummyRowTypeDecl :: Name -> RowTypeDecl
+dummyRowTypeDecl name = MkRowTypeDecl name (TyBuiltin BuiltinTypeBoolean) (MkRowMetadata Nothing)
+
+instance Print RuleMetadata where
+ prt i md =
+ case md.description of
+ Nothing -> id
+ Just descr -> prPrec i 0 (concatD [doc (showString "--"), prt 0 (T.unpack descr)])
+
+instance Print RecordDeclMetadata where
+ prt i md =
+ case md.description of
+ Nothing -> id
+ Just descr -> prPrec i 0 (concatD [doc (showString "/-"), doc (showString "About:"), prt 0 (T.unpack descr), doc (showString "-/")])
+
+instance Print RowMetadata where
+ prt i md =
+ case md.description of
+ Nothing -> id
+ Just descr -> prPrec i 0 (concatD [
+ doc (showString "--")
+ , prt 0 (T.unpack descr)
+ ])
+
+instance Print DataDecl where
+ prt i = \case
+ decl@(RecordDecl [] _ _) -> error [I.i|Trying to print DataDecl without a name: #{decl}|]
+ RecordDecl (recname:rowtypedecls) [] metadata ->
+ prPrec i 0 (concatD [
+ prt 0 metadata
+ , doc (showString "STRUCTURE")
+ , prt 0 recname.name
+ , prt 0 rowtypedecls
+ , doc (showString "END")
+ ])
+ RecordDecl (recname:rowtypedecls) parents metadata ->
+ prPrec i 0 (concatD [
+ prt 0 metadata
+ , doc (showString "STRUCTURE")
+ , prt 0 recname.name
+ , doc (showString "SPECIALIZES")
+ , prt 0 parents
+ , prt 0 rowtypedecls
+ , doc (showString "END")
+ ])
+
+instance Print Expr where
+ prt i = \case
+ Var name -> prPrec i 0 (concatD [prt 0 name])
+ Lit lit -> prPrec i 0 (concatD [prt 0 lit])
+ Cons expr1 expr2 -> prPrec i 0 (concatD [prt 0 expr1, doc (showString "followed_by_items_in"), prt 0 expr2])
+ List exprs -> prPrec i 0 (concatD [doc (showString "LIST_OF"), prt 0 exprs, doc (showString ".")])
+ -- TODO: prPrec is actually supposed to do something smart with the numbers, but whatever, this will be thrown away anyway
+ Unary IntegerToFraction expr@(BinExpr{}) -> prPrec i 0 (concatD [prt 0 IntegerToFraction, parenth (prt 0 expr)])
+ Unary IntegerToFraction expr@(Lit{}) -> parenth (prPrec i 0 (concatD [prt 0 IntegerToFraction, prt 0 expr]))
+ Unary unaryop expr -> prPrec i 0 (concatD [prt 0 unaryop, prt 0 expr])
+ BinExpr binop expr1 expr2 -> prPrec i 0 (concatD [prt 0 expr1, prt 0 binop, prt 0 expr2])
+ IfThenElse expr1 expr2 expr3 -> prPrec i 0 (concatD [doc (showString "IF"), prt 0 expr1, doc (showString "THEN"), prt 0 expr2, doc (showString "ELSE"), prt 0 expr3])
+ FunApp expr exprs -> prPrec i 0 (concatD [prt 0 expr, parenth (prt 0 exprs)])
+ Record rows -> prPrec i 0 (concatD [doc (showString "{|"), prt 0 rows, doc (showString "|}")])
+ Project expr name -> prPrec i 0 (concatD [prt 0 expr, doc (showString "'s"), prt 0 name])
+ -- f@(Fun _mdata [] _expr) -> error [I.i|Trying to print Fun without a name: #{f}|]
+ Fun metadata (name:names) expr ->
+ prPrec i 0 (concatD [
+ prt 0 metadata
+ , doc (showString "FUNCTION")
+ , prt 0 name
+ , parenth (prt 0 names)
+ , doc (showString "=")
+ , prt 0 expr
+ , doc (showString "END")])
+ Fun metadata names expr ->
+ prPrec i 0 (concatD [
+ prt 0 metadata
+ , doc (showString "FUNCTION")
+ , parenth (prt 0 names)
+ , doc (showString "=")
+ , prt 0 expr
+ , doc (showString "END")])
+ Let decl expr -> prPrec i 0 (concatD [doc (showString "LET"), doc (showString "{"), prt 0 decl, doc (showString "}"), doc (showString "IN"), doc (showString "{"), prt 0 expr, doc (showString "}")])
+ StatementBlock (st :| sts) -> prPrec i 0 (concatD [prt 0 (st:sts)])
+ NormIsInfringed name -> prPrec i 0 (concatD [prt 0 name, doc (showString "IS_INFRINGED")])
+ p@(Predicate _mdata [] _expr) -> error [I.i|Trying to print Predicate without a name: #{p}|]
+ Predicate metadata [funname] expr ->
+ prPrec i 0 (concatD [
+ prt 0 metadata
+ , doc (showString "\nDECIDE")
+ , prt 0 funname
+ , doc (showString "\nIF")
+ , prt 0 expr
+ ])
+ Predicate metadata (funname:args) expr ->
+ prPrec i 0 (concatD [
+ prt 0 metadata
+ , doc (showString "GIVEN")
+ , parenth $ prt 0 (fmap (\a -> mkFunArg a.name) args) -- custom newtype to print args in a specific way
+ , doc (showString "\nDECIDE")
+ , prt 0 funname
+ , doc (showString "\nIF")
+ , prt 0 expr])
+ PredApp f [] ->
+ prPrec i 0 (concatD [
+ prt 0 f
+ , doc (showString "HOLDS?")
+ ])
+ PredApp f [arg] ->
+ prPrec i 0 (concatD [
+ prt 0 arg
+ , prt 0 f -- postfix by default. Could match prefix "is" to determine if postfix or prefix?
+ ])
+ PredApp f [left,right] ->
+ prPrec i 0 (concatD [
+ prt 0 left
+ , prt 0 f -- infix by default
+ , prt 0 right
+ ])
+ PredApp f args -> prPrec i 0 (concatD [prt 0 f, prt 0 args])
+ Foldr using starting over -> prPrec i 0 (concatD [
+ doc (showString "FOLD_RIGHT")
+ , doc (showString "using")
+ , prt 0 using
+ , doc (showString "starting_with")
+ , prt 0 starting
+ , doc (showString "over")
+ , prt 0 over
+ ])
+ Foldl using starting over -> prPrec i 0 (concatD [
+ doc (showString "FOLD_LEFT")
+ , doc (showString "using")
+ , prt 0 using
+ , doc (showString "starting_with")
+ , prt 0 starting
+ , doc (showString "over")
+ , prt 0 over
+ ])
+ Sig{} -> error "not yet implemented: Sig"
+ Relation{} -> error "not yet implemented: Relation"
+
+instance Print OriginalRuleRef where
+ prt i = \case
+ MkOriginalRuleRef n -> prPrec i 0 (concatD [doc (showString "\167"), prt 0 n])
+
+instance Print Lit where
+ prt i = \case
+ IntLit n -> prPrec i 0 (concatD [prt 0 n])
+ BoolLit bool -> prPrec i 0 (concatD [prt 0 bool])
+ StringLit str -> prPrec i 0 (concatD [prt 0 str])
+ FracLit frac -> prPrec i 0 (concatD [prt 0 frac])
+
+instance Print Statement where
+ prt i = \case
+ IfStatement expr statement statements -> prPrec i 0 (concatD [doc (showString "IF"), prt 0 expr, doc (showString "THEN"), prt 0 statement, doc (showString "ELSE"), prt 0 statements])
+ Norm name deonticmodal action -> prPrec i 0 (concatD [prt 0 name, prt 0 deonticmodal, prt 0 action])
+
+instance Print Action where
+ prt i = \case
+ ActionBlock (Just name) params primactions -> prPrec i 0 (concatD [doc (showString "ACTION"), prt 0 name, doc (showString "="), doc (showString "DO"), doc (showString "{"), prt 0 params, prt 0 primactions, doc (showString "}")])
+ ActionBlock Nothing params primactions -> prPrec i 0 (concatD [doc (showString "DO"), doc (showString "{"), prt 0 params, prt 0 primactions, doc (showString "}")])
+ PrimitiveAction primaction -> prPrec i 0 (concatD [prt 0 primaction])
+
+instance Print [PrimAction] where
+ prt _ [] = concatD []
+ prt _ [x] = concatD [prt 0 x]
+ prt _ (x:xs) = concatD [prt 0 x, doc (showString "then"), prt 0 xs]
+
+instance Print PrimAction where
+ prt i = \case
+ Assign name expr -> prPrec i 0 (concatD [prt 0 name, doc (showString "="), prt 0 expr])
+ ActionRef name -> prPrec i 0 (concatD [prt 0 name])
+
+instance Print DeonticModal where
+ prt i = \case
+ Obligation -> prPrec i 0 (concatD [doc (showString "MUST")])
+ Permission -> prPrec i 0 (concatD [doc (showString "MAY")])
+
+instance Print UnaryOp where
+ prt i = \case
+ Not -> prPrec i 0 (concatD [doc (showString "NOT")])
+ UnaryMinus -> prPrec i 0 (concatD [doc (showString "-")])
+ Floor -> prPrec i 0 (concatD [doc (showString "floor")])
+ Ceiling -> prPrec i 0 (concatD [doc (showString "ceiling")])
+ IntegerToFraction -> prPrec i 0 (concatD [doc (showString "integer_to_fraction")])
+
+
+instance Print BinOp where
+ prt i = \case
+ Or -> prPrec i 0 (concatD [doc (showString "OR")])
+ And -> prPrec i 0 (concatD [doc (showString "AND")])
+ Plus -> prPrec i 0 (concatD [doc (showString "+")])
+ Minus -> prPrec i 0 (concatD [doc (showString "-")])
+ Modulo -> prPrec i 0 (concatD [doc (showString "%")])
+ Mult -> prPrec i 0 (concatD [doc (showString "*")])
+ Divide -> prPrec i 0 (concatD [doc (showString "/")])
+ Lt -> prPrec i 0 (concatD [doc (showString "<")])
+ Le -> prPrec i 0 (concatD [doc (showString "<=")])
+ Gt -> prPrec i 0 (concatD [doc (showString ">")])
+ Ge -> prPrec i 0 (concatD [doc (showString ">=")])
+ Eq -> prPrec i 0 (concatD [doc (showString "EQUALS")])
+ Ne -> prPrec i 0 (concatD [doc (showString "!=")])
+ StrAppend -> prPrec i 0 (concatD [doc (showString "++")]) -- is this even in the Langium grammar yet?
+instance Print Bool where
+ prt i = \case
+ True -> prPrec i 0 (concatD [doc (showString "True")])
+ False -> prPrec i 0 (concatD [doc (showString "False")])
+
+instance Print Name where
+ prt i n = prPrec i 0 (concatD [prt 0 n.name])
+
+instance Print TypeExpr where
+ prt i = \case
+ TyCustom name -> prPrec i 0 (concatD [prt 0 name])
+ TyBuiltin builtintype -> prPrec i 0 (concatD [prt 0 builtintype])
+ TyFun arg ret -> prPrec i 0 (concatD [prt 0 arg, doc (showString "=>"), prt 0 ret])
+
+instance Print TyBuiltin where
+ prt i = \case
+ BuiltinTypeString -> prPrec i 0 (concatD [doc (showString "String")])
+ BuiltinTypeInteger -> prPrec i 0 (concatD [doc (showString "Integer")])
+ BuiltinTypeBoolean -> prPrec i 0 (concatD [doc (showString "Boolean")])
+
+instance Print RowTypeDecl where
+ prt i rtd = prPrec i 0 (concatD [prt 0 rtd.metadata, prt 0 rtd.name, doc (showString ":"), prt 0 rtd.typeAnnot])
diff --git a/lam4-backend/src/Lam4/Expr/ToSimala.hs b/lam4-backend/src/Lam4/Expr/ToSimala.hs
index c4412649..81a246d4 100644
--- a/lam4-backend/src/Lam4/Expr/ToSimala.hs
+++ b/lam4-backend/src/Lam4/Expr/ToSimala.hs
@@ -99,7 +99,7 @@ compileDecl = \case
------------------------------------
NonRec name Atom {} ->
let smName = lam4ToSimalaName name
- in SM.NonRec defaultTransparency smName (SM.Atom $ SM.MkAtom smName)
+ in SM.NonRec defaultTransparency smName (SM.Atom smName)
-- TODO: May not want to translate ONE SIG with no relations this way
-- TODO: Not sure if naming is ok
diff --git a/lam4-backend/src/Lam4/Main.hs b/lam4-backend/src/Lam4/Main.hs
new file mode 100644
index 00000000..930781c6
--- /dev/null
+++ b/lam4-backend/src/Lam4/Main.hs
@@ -0,0 +1,123 @@
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE QuasiQuotes #-}
+{-# LANGUAGE TemplateHaskell #-}
+
+module Lam4.Main where
+
+import Base
+import qualified Base.ByteString as BL
+import Control.Lens.Regex.ByteString (groups, regex)
+import Data.ByteString as BS hiding (concat, concatMap,
+ map, null, putStr)
+import qualified Data.Text as T
+import qualified Data.String.Interpolate as I (i)
+
+import Cradle
+import qualified Lam4.Expr.ConcreteSyntax as CST (Decl)
+import Lam4.Expr.Parser (parseProgramByteStr)
+import Lam4.Expr.ToConcreteEvalAST (cstProgramToConEvalProgram)
+import Lam4.Expr.ToSimala ()
+import qualified Lam4.Expr.ToSimala as ToSimala
+import Lam4.Parser.Monad (evalParserFromScratch)
+import Lam4.Expr.Printer (printTree)
+import Options.Applicative as Options
+import System.FilePath ((>))
+
+data FrontendConfig =
+ MkFrontendConfig { frontendDir :: FilePath
+ , runner :: String
+ , args :: [String]
+ }
+
+lam4_frontend_dir :: FilePath
+lam4_frontend_dir = "lam4-frontend"
+
+frontendConfig :: FrontendConfig
+frontendConfig = MkFrontendConfig { runner = "node"
+ , frontendDir = lam4_frontend_dir
+ , args = [lam4_frontend_dir > "bin" > "cli", "toMinimalAst"] }
+
+-- TODO: Think about exposing a tracing option?
+data Options =
+ MkOptions
+ { tracing :: ToSimala.TraceMode
+ , files :: [FilePath]
+ }
+
+-- | Copied from Simala
+toTracingMode :: String -> ToSimala.TraceMode
+toTracingMode "full" = ToSimala.TraceFull
+toTracingMode "results" = ToSimala.TraceResults
+toTracingMode "off" = ToSimala.TraceOff
+toTracingMode _ = ToSimala.TraceFull
+
+
+optionsDescription :: Options.Parser Options
+optionsDescription =
+ MkOptions
+ <$> (toTracingMode <$> strOption (long "tracing" <> help "Tracing, one of \"off\", \"full\" (default), \"results\"") <|> pure ToSimala.TraceResults)
+ <*> many (strArgument (metavar ".l4 FILES..."))
+
+optionsConfig :: Options.ParserInfo Options
+optionsConfig =
+ info (optionsDescription <**> helper)
+ ( fullDesc
+ <> header "Lam4 (an experimental variant of the L4 legal DSL)"
+ )
+
+main :: IO ()
+main = do
+ options <- Options.execParser optionsConfig
+ if null options.files
+ then do
+ hPutStrLn stderr "Lam4: no input files given; use --help for help"
+ else do
+ frontendCSTJsons <- getCSTJsonFromFrontend frontendConfig options.files
+ let cstDecls = concatMap parseCSTByteString frontendCSTJsons
+ smDecls = ToSimala.compile . cstProgramToConEvalProgram $ cstDecls
+ putStrLn "------- Prettyprinted -------------"
+ mapM_ (\x -> putStrLn (printTree x) >> putStrLn "") cstDecls
+ print "------- CST -------------"
+ pPrint cstDecls
+ print "-------- Simala exprs ---------"
+ putStr $ T.unpack $ ToSimala.render smDecls
+ print "-------------------------------"
+ -- TODO: What to do if no explicit Eval?
+ _ <- ToSimala.doEvalDeclsTracing options.tracing ToSimala.emptyEnv smDecls
+ pure ()
+
+getCSTJsonFromFrontend :: FrontendConfig -> [FilePath] -> IO [ByteString]
+getCSTJsonFromFrontend config files = do
+ let argsForFrontendCLI = config.args <> files
+ (exitCode :: ExitCode, StdoutRaw rawstdout, StderrRaw err) <- run $ cmd config.runner
+ & addArgs argsForFrontendCLI
+ case exitCode of
+ ExitFailure _ ->
+ error ("Frontend parser failed:\n" <> ppShow err)
+ -- TODO: Improve the error reporting
+ ExitSuccess ->
+ pure $ concat $ rawstdout ^.. cstJsonTraversal
+ where
+ cstJsonTraversal = traversalVL [regex|(?s)(.*?)|] % traversalVL groups
+
+parseCSTByteString :: StrictByteString -> [CST.Decl]
+parseCSTByteString bs =
+ case evalParserFromScratch . parseProgramByteStr . BL.fromStrict $ bs of
+ Left err -> error ("Parse error:\n" <> ppShow err)
+ Right cstDecls -> cstDecls
+
+-- Version that doesn't call error, but returns the error in the [ByteString].
+-- Because I want to print out the failure in the test.
+-- TODO: nicer error logging, less copypaste
+getCSTJsonFromFrontendNoFail :: FrontendConfig -> [FilePath] -> IO [ByteString]
+getCSTJsonFromFrontendNoFail config files = do
+ let argsForFrontendCLI = config.args <> files
+ (exitCode :: ExitCode, StdoutRaw rawstdout, StderrRaw err) <- run $ cmd config.runner
+ & addArgs argsForFrontendCLI
+ case exitCode of
+ ExitFailure _ -> pure [ [I.i|trying to parse #{files}, got #{ppShow err}|] ]
+ -- TODO: Improve the error reporting
+ ExitSuccess ->
+ pure $ concat $ rawstdout ^.. cstJsonTraversal
+ where
+ cstJsonTraversal = traversalVL [regex|(?s)(.*?)|] % traversalVL groups
\ No newline at end of file
diff --git a/lam4-backend/test/PrinterSpec.hs b/lam4-backend/test/PrinterSpec.hs
new file mode 100644
index 00000000..0591e3ac
--- /dev/null
+++ b/lam4-backend/test/PrinterSpec.hs
@@ -0,0 +1,54 @@
+{-# LANGUAGE QuasiQuotes #-}
+
+module PrinterSpec (spec) where
+
+import Test.Hspec
+import Test.Hspec.Golden
+import Lam4.Main (getCSTJsonFromFrontendNoFail, parseCSTByteString, FrontendConfig(..))
+import Lam4.Expr.Printer (printTree)
+import Control.Monad (forM_)
+import Data.List (intercalate)
+import qualified Data.Text.Lazy as TL
+import qualified Data.Text.Lazy.IO as TL
+import System.FilePath ((<.>), (>), takeBaseName)
+import System.Directory (listDirectory)
+
+goldenGeneric :: String -> String -> Golden TL.Text
+goldenGeneric name output_ = Golden
+ { output = TL.pack output_
+ , encodePretty = TL.unpack
+ , writeToFile = TL.writeFile
+ , readFromFile = TL.readFile
+ , goldenFile = testPath <.> "expected"
+ , actualFile = Just (testPath <.> "actual")
+ , failFirstTime = False
+ }
+ where
+ testPath = "test" > "testdata" > "golden" > "PrinterSpec" > name
+
+spec :: Spec
+spec = do
+ files <- runIO $ listDirectory examplesDir
+ forM_ files $ \file -> do
+ frontendCSTJsons <- runIO $ getCSTJsonFromFrontendNoFail frontendConfig [examplesDir > file]
+ let decls = concatMap parseCSTByteString frontendCSTJsons
+ fname = takeBaseName file
+ descr = "Testing " <> fname
+ printedDecls = if null decls
+ then show frontendCSTJsons
+ else intercalate "\n\n" $ fmap printTree decls
+ testGolden descr fname printedDecls
+ where
+ examplesDir = "../examples"
+
+testGolden :: String -> String -> String -> Spec
+testGolden desc fname expected = it desc $ goldenGeneric fname expected
+
+lam4_frontend_dir :: FilePath
+lam4_frontend_dir = "../lam4-frontend"
+
+frontendConfig :: FrontendConfig
+frontendConfig = MkFrontendConfig { runner = "node"
+ , frontendDir = lam4_frontend_dir
+ , args = [lam4_frontend_dir > "bin" > "cli", "toMinimalAst"] }
+
diff --git a/lam4-backend/test/Spec.hs b/lam4-backend/test/Spec.hs
new file mode 100644
index 00000000..52ef578f
--- /dev/null
+++ b/lam4-backend/test/Spec.hs
@@ -0,0 +1 @@
+{-# OPTIONS_GHC -F -pgmF hspec-discover #-}
\ No newline at end of file
diff --git a/lam4-backend/test/testdata/golden/PrinterSpec/PostfixPredicateApplication.expected b/lam4-backend/test/testdata/golden/PrinterSpec/PostfixPredicateApplication.expected
new file mode 100644
index 00000000..7d1ea7d0
--- /dev/null
+++ b/lam4-backend/test/testdata/golden/PrinterSpec/PostfixPredicateApplication.expected
@@ -0,0 +1,27 @@
+
+/-
+ About: "A Person is pretty much what you would expect"
+-/
+STRUCTURE Person
+ some_number : Integer
+END
+
+
+
+FUNCTION //eventual type signature
+f (x) = 2
+END
+
+
+
+DECIDE `some fact`
+IF True
+
+GIVEN (person)
+DECIDE `is eligible`
+IF `some fact` HOLDS?
+AND f (1) + person 's some_number EQUALS 2
+
+DEFINE x = {| some_number = 1 |}
+
+@REPORT x `is eligible`
\ No newline at end of file
diff --git a/lam4-backend/test/testdata/golden/PrinterSpec/anon_function.expected b/lam4-backend/test/testdata/golden/PrinterSpec/anon_function.expected
new file mode 100644
index 00000000..cd1db7c2
--- /dev/null
+++ b/lam4-backend/test/testdata/golden/PrinterSpec/anon_function.expected
@@ -0,0 +1,5 @@
+@REPORT (\ a b => 1 + a + b) (7, 3)
+
+@REPORT (\ () => 3) ()
+
+@REPORT (\ x y z => x - y - z) (1, 2, 3)
\ No newline at end of file
diff --git a/lam4-backend/test/testdata/golden/PrinterSpec/arithmetic.expected b/lam4-backend/test/testdata/golden/PrinterSpec/arithmetic.expected
new file mode 100644
index 00000000..1a525eea
--- /dev/null
+++ b/lam4-backend/test/testdata/golden/PrinterSpec/arithmetic.expected
@@ -0,0 +1,17 @@
+
+FUNCTION //eventual type signature
+f () = 3 + 5
+END
+
+
+
+FUNCTION //eventual type signature
+g (x) = x * 2 - 1 / 3
+END
+
+
+@REPORT g (0)
+
+@REPORT g (- 1)
+
+@REPORT f ()
\ No newline at end of file
diff --git a/lam4-backend/test/testdata/golden/PrinterSpec/ceiling.expected b/lam4-backend/test/testdata/golden/PrinterSpec/ceiling.expected
new file mode 100644
index 00000000..c4f0a0ef
--- /dev/null
+++ b/lam4-backend/test/testdata/golden/PrinterSpec/ceiling.expected
@@ -0,0 +1,5 @@
+DEFINE ceilinged = ceiling 0.5
+
+DEFINE ceilinged2 = ceiling 0.5
+
+@REPORT ceilinged EQUALS ceilinged2
\ No newline at end of file
diff --git a/lam4-backend/test/testdata/golden/PrinterSpec/chained_record_deref.expected b/lam4-backend/test/testdata/golden/PrinterSpec/chained_record_deref.expected
new file mode 100644
index 00000000..9149af9f
--- /dev/null
+++ b/lam4-backend/test/testdata/golden/PrinterSpec/chained_record_deref.expected
@@ -0,0 +1,27 @@
+
+/-
+ About: "a description could go here"
+-/
+STRUCTURE F
+ a : Integer
+ c : G
+END
+
+
+STRUCTURE G
+ b : Integer
+END
+
+
+
+FUNCTION //eventual type signature
+f (x) = x 's c 's b
+END
+
+
+@REPORT `test syntax highlighting`
+
+
+FUNCTION //eventual type signature
+f (x) = x 's a + 2
+END
diff --git a/lam4-backend/test/testdata/golden/PrinterSpec/cons.expected b/lam4-backend/test/testdata/golden/PrinterSpec/cons.expected
new file mode 100644
index 00000000..c528fea2
--- /dev/null
+++ b/lam4-backend/test/testdata/golden/PrinterSpec/cons.expected
@@ -0,0 +1 @@
+@REPORT 1 followed_by_items_in LIST_OF 2, 3, 4 .
\ No newline at end of file
diff --git a/lam4-backend/test/testdata/golden/PrinterSpec/decimal.expected b/lam4-backend/test/testdata/golden/PrinterSpec/decimal.expected
new file mode 100644
index 00000000..be1ae156
--- /dev/null
+++ b/lam4-backend/test/testdata/golden/PrinterSpec/decimal.expected
@@ -0,0 +1,15 @@
+@REPORT 1.333
+
+@REPORT 1333.0 / 1000.0
+
+@REPORT 1.0 / 1000.0 + 333.0 / 1000.0
+
+@REPORT 1.0
+
+@REPORT 1
+
+@REPORT 0.0
+
+@REPORT 0
+
+@REPORT 1.0 + 0.333
\ No newline at end of file
diff --git a/lam4-backend/test/testdata/golden/PrinterSpec/floor.expected b/lam4-backend/test/testdata/golden/PrinterSpec/floor.expected
new file mode 100644
index 00000000..93d92466
--- /dev/null
+++ b/lam4-backend/test/testdata/golden/PrinterSpec/floor.expected
@@ -0,0 +1,3 @@
+DEFINE floored = floor 0.5
+
+@REPORT 1 + floored
\ No newline at end of file
diff --git a/lam4-backend/test/testdata/golden/PrinterSpec/foldr_length.expected b/lam4-backend/test/testdata/golden/PrinterSpec/foldr_length.expected
new file mode 100644
index 00000000..84016b1b
--- /dev/null
+++ b/lam4-backend/test/testdata/golden/PrinterSpec/foldr_length.expected
@@ -0,0 +1,27 @@
+STRUCTURE Publication END
+
+
+STRUCTURE Applicant
+ publications : Publication
+END
+
+
+
+FUNCTION //eventual type signature
+increment (_, acc) = acc + 1
+END
+
+
+
+FUNCTION //eventual type signature
+numberOfPublications (applicant) =
+ FOLD_RIGHT
+ using increment
+ starting_with 0
+ over applicant's publications
+END
+
+
+GIVEN (applicant)
+DECIDE `is eligible for grant`
+IF numberOfPublications (applicant) >= 3
\ No newline at end of file
diff --git a/lam4-backend/test/testdata/golden/PrinterSpec/fractionals.expected b/lam4-backend/test/testdata/golden/PrinterSpec/fractionals.expected
new file mode 100644
index 00000000..dbb1b970
--- /dev/null
+++ b/lam4-backend/test/testdata/golden/PrinterSpec/fractionals.expected
@@ -0,0 +1,17 @@
+DEFINE fractional = 0.5 * 0.5
+
+DEFINE negative = - 0.3
+
+DEFINE squared = negative * negative
+
+@REPORT fractional
+
+@REPORT floor fractional
+
+@REPORT ceiling fractional
+
+@REPORT fractional * (integer_to_fraction 2)
+
+@REPORT negative
+
+@REPORT squared
\ No newline at end of file
diff --git a/lam4-backend/test/testdata/golden/PrinterSpec/function_application.expected b/lam4-backend/test/testdata/golden/PrinterSpec/function_application.expected
new file mode 100644
index 00000000..746e5029
--- /dev/null
+++ b/lam4-backend/test/testdata/golden/PrinterSpec/function_application.expected
@@ -0,0 +1,11 @@
+
+FUNCTION //eventual type signature
+sumIntegers (x, y) = x + y
+END
+
+
+
+DECIDE `some predicate`
+IF sumIntegers (1, 2) > 2
+
+@REPORT `some predicate` HOLDS?
\ No newline at end of file
diff --git a/lam4-backend/test/testdata/golden/PrinterSpec/infix_pred_app.expected b/lam4-backend/test/testdata/golden/PrinterSpec/infix_pred_app.expected
new file mode 100644
index 00000000..88de8df3
--- /dev/null
+++ b/lam4-backend/test/testdata/golden/PrinterSpec/infix_pred_app.expected
@@ -0,0 +1,24 @@
+
+/-
+ About: "A Person is pretty much what you would expect"
+-/
+STRUCTURE Person END
+
+
+GIVEN (some_person
+another_person)
+DECIDE `has helped`
+IF True
+
+GIVEN (some_person
+another_person)
+DECIDE `has met`
+IF some_person `has helped` another_person
+
+GIVEN (person)
+DECIDE `is fun`
+IF True
+
+GIVEN (x)
+DECIDE bleh
+IF x `is fun`
\ No newline at end of file
diff --git a/lam4-backend/test/testdata/golden/PrinterSpec/integer_to_fraction.expected b/lam4-backend/test/testdata/golden/PrinterSpec/integer_to_fraction.expected
new file mode 100644
index 00000000..be9a43ee
--- /dev/null
+++ b/lam4-backend/test/testdata/golden/PrinterSpec/integer_to_fraction.expected
@@ -0,0 +1,3 @@
+@REPORT (integer_to_fraction 2) + (integer_to_fraction 1) + 0.0
+
+@REPORT 2.0 - integer_to_fraction (2 - 0 + 0)
\ No newline at end of file
diff --git a/lam4-backend/test/testdata/golden/PrinterSpec/life_assured_predicate_fun_decl.expected b/lam4-backend/test/testdata/golden/PrinterSpec/life_assured_predicate_fun_decl.expected
new file mode 100644
index 00000000..a89e0f67
--- /dev/null
+++ b/lam4-backend/test/testdata/golden/PrinterSpec/life_assured_predicate_fun_decl.expected
@@ -0,0 +1,22 @@
+STRUCTURE Accident END
+
+
+STRUCTURE LifeAssured END
+
+
+
+-- "Calculates how much the life assured can get for their claim"
+FUNCTION //eventual type signature
+payout (life_assured, accident) = 0
+END
+
+
+
+-- "Checks if the life assured is eligible for a payout."
+GIVEN (x)
+DECIDE `is eligible for a payout`
+IF True
+
+DEFINE x = {| |}
+
+@REPORT x `is eligible for a payout`
\ No newline at end of file
diff --git a/lam4-backend/test/testdata/golden/PrinterSpec/list.expected b/lam4-backend/test/testdata/golden/PrinterSpec/list.expected
new file mode 100644
index 00000000..0a2e9ce3
--- /dev/null
+++ b/lam4-backend/test/testdata/golden/PrinterSpec/list.expected
@@ -0,0 +1 @@
+@REPORT LIST_OF 1, 2, 3, 4 .
\ No newline at end of file
diff --git a/lam4-backend/test/testdata/golden/PrinterSpec/list2.expected b/lam4-backend/test/testdata/golden/PrinterSpec/list2.expected
new file mode 100644
index 00000000..67a839a8
--- /dev/null
+++ b/lam4-backend/test/testdata/golden/PrinterSpec/list2.expected
@@ -0,0 +1 @@
+@REPORT LIST_OF 1 + 1, 2 + 2, 3 + 3 .
\ No newline at end of file
diff --git a/lam4-backend/test/testdata/golden/PrinterSpec/lottery.expected b/lam4-backend/test/testdata/golden/PrinterSpec/lottery.expected
new file mode 100644
index 00000000..b9ba7b76
--- /dev/null
+++ b/lam4-backend/test/testdata/golden/PrinterSpec/lottery.expected
@@ -0,0 +1,11 @@
+
+/-
+ About: "Info about a lottery."
+-/
+STRUCTURE Lottery
+ -- "how much can be won from the jackpot."
+ total_jackpot : Integer
+
+ -- "whether buying tickets from this lottery is tax deductible. "
+ `tax deductible status` : Boolean
+END
diff --git a/lam4-backend/test/testdata/golden/PrinterSpec/not.expected b/lam4-backend/test/testdata/golden/PrinterSpec/not.expected
new file mode 100644
index 00000000..84b9ec3e
--- /dev/null
+++ b/lam4-backend/test/testdata/golden/PrinterSpec/not.expected
@@ -0,0 +1,23 @@
+STRUCTURE Applicant
+ publications : Integer
+END
+
+
+
+FUNCTION //eventual type signature
+numberOfPublications (applicant) = applicant 's publications
+END
+
+
+GIVEN (x)
+DECIDE `is eligible for grant`
+IF NOT x 's publications < 2
+
+GIVEN (x)
+DECIDE happy
+IF x `is eligible for grant`
+AND NOT NOT x `is eligible for grant`
+
+DEFINE x = {| publications = 3 |}
+
+@REPORT x `is eligible for grant`
\ No newline at end of file
diff --git a/lam4-backend/test/testdata/golden/PrinterSpec/predicate_and_givens_variations.expected b/lam4-backend/test/testdata/golden/PrinterSpec/predicate_and_givens_variations.expected
new file mode 100644
index 00000000..d4bd607e
--- /dev/null
+++ b/lam4-backend/test/testdata/golden/PrinterSpec/predicate_and_givens_variations.expected
@@ -0,0 +1,20 @@
+STRUCTURE Person
+ age : Integer
+END
+
+
+
+DECIDE `pred blah blah`
+IF True
+
+GIVEN (x
+y
+z)
+DECIDE pred2
+IF x 's age > 10
+AND y 's age > 10
+AND z 's age > 10
+
+
+DECIDE no_givens_pred
+IF True
\ No newline at end of file
diff --git a/lam4-backend/test/testdata/golden/PrinterSpec/simple_join_or_record_field_deref.expected b/lam4-backend/test/testdata/golden/PrinterSpec/simple_join_or_record_field_deref.expected
new file mode 100644
index 00000000..d738e8b4
--- /dev/null
+++ b/lam4-backend/test/testdata/golden/PrinterSpec/simple_join_or_record_field_deref.expected
@@ -0,0 +1,20 @@
+STRUCTURE Applicant
+ publications : Integer
+END
+
+
+DEFINE someApplicant = {| publications = 1000 |}
+
+
+FUNCTION //eventual type signature
+numberOfPublications (applicant) = applicant 's publications
+END
+
+
+GIVEN (x)
+DECIDE `is eligible for grant`
+IF x 's publications > 10
+
+GIVEN (x)
+DECIDE happy
+IF x `is eligible for grant`
\ No newline at end of file
diff --git a/lam4-backend/test/testdata/golden/PrinterSpec/simple_life_assured.expected b/lam4-backend/test/testdata/golden/PrinterSpec/simple_life_assured.expected
new file mode 100644
index 00000000..337c4fdf
--- /dev/null
+++ b/lam4-backend/test/testdata/golden/PrinterSpec/simple_life_assured.expected
@@ -0,0 +1,11 @@
+
+/-
+ About: "Information about the life assured for this policy."
+-/
+STRUCTURE LifeAssured
+ -- "whether policy active "
+ policy_active : Boolean
+
+ -- "how old the life assured is"
+ age : Integer
+END
diff --git a/lam4-backend/test/testdata/golden/PrinterSpec/stringlit.expected b/lam4-backend/test/testdata/golden/PrinterSpec/stringlit.expected
new file mode 100644
index 00000000..51fdfcda
--- /dev/null
+++ b/lam4-backend/test/testdata/golden/PrinterSpec/stringlit.expected
@@ -0,0 +1 @@
+@REPORT "I am a lovely string literal"
\ No newline at end of file
diff --git a/lam4-backend/test/testdata/golden/PrinterSpec/unary_minus_integer.expected b/lam4-backend/test/testdata/golden/PrinterSpec/unary_minus_integer.expected
new file mode 100644
index 00000000..5cff6851
--- /dev/null
+++ b/lam4-backend/test/testdata/golden/PrinterSpec/unary_minus_integer.expected
@@ -0,0 +1,21 @@
+DEFINE negativeIntegerLiteralZero = 0
+
+DEFINE negativeInteger = -3
+
+DEFINE squared = negativeInteger * negativeInteger
+
+DEFINE subtracted = 0 - 3 + 0 + 1 - 1
+
+@REPORT negativeIntegerLiteralZero
+
+@REPORT negativeIntegerLiteralZero EQUALS 0
+
+@REPORT negativeInteger
+
+@REPORT negativeInteger EQUALS -3
+
+@REPORT squared
+
+@REPORT subtracted
+
+@REPORT subtracted * subtracted EQUALS squared
\ No newline at end of file
diff --git a/lam4-cli/app/Main.hs b/lam4-cli/app/Main.hs
index 60a6045f..695ae2de 100644
--- a/lam4-cli/app/Main.hs
+++ b/lam4-cli/app/Main.hs
@@ -19,6 +19,7 @@ import Lam4.Expr.ToConcreteEvalAST (cstProgramToConEvalProgram)
import Lam4.Expr.ToSimala ()
import qualified Lam4.Expr.ToSimala as ToSimala
import Lam4.Parser.Monad (evalParserFromScratch)
+import Lam4.Expr.Printer (printTree)
import Options.Applicative as Options
import System.FilePath ((>))
import System.Directory
@@ -75,6 +76,8 @@ main = do
frontendCSTJsons <- getCSTJsonFromFrontend frontendConfig options.files
let cstDecls = concatMap parseCSTByteString frontendCSTJsons
smDecls = ToSimala.compile . cstProgramToConEvalProgram $ cstDecls
+ putStrLn "------- Prettyprinted -------------"
+ mapM_ (\x -> putStrLn (printTree x) >> putStrLn "") cstDecls
print "------- CST -------------"
pPrint cstDecls
print "-------- Simala exprs ---------"