{-# LANGUAGE StandaloneDeriving #-} {-# LANGUAGE NoMonomorphismRestriction #-} {-# LANGUAGE UndecidableInstances #-} {-# OPTIONS_GHC -fno-warn-orphans #-} {-# OPTIONS_GHC -O0 #-} module Read.Test where import Test.Tasty import Test.Tasty.HUnit import Control.Applicative (Applicative(..), Alternative(..)) -- import Control.Arrow (first) import Control.Monad (Monad(..), MonadPlus(..)) import Control.Monad.Trans.Class (MonadTrans(..)) import Data.Bool import Data.Char (Char) import qualified Data.Kind as Kind import Data.Data () -- import Data.Decimal (DecimalRaw(..)) import Data.Either (Either(..)) import Data.Eq (Eq) import Data.Fixed (Pico) import Data.Function (($), (.), const, flip) import Data.Functor (Functor(..), (<$>)) import Data.Functor.Identity (Identity(..)) import Data.Int (Int) import Data.List.NonEmpty (NonEmpty(..)) import Data.Map.Strict (Map) import Data.Maybe (Maybe(..)) import Data.Monoid (Monoid(..), (<>)) import Data.Ord (Ord(..)) import Data.Proxy import Data.String (String) import Data.Text (Text) import Data.Type.Equality ((:~:)(..)) import Data.Word (Word) import Prelude (Integer) import System.FilePath.Posix (FilePath) import Text.Show (Show(..)) import qualified Control.Exception.Safe as Exn import qualified Control.Monad.Classes as MC import qualified Control.Monad.Trans.State.Strict as SS import qualified Data.Char as Char import qualified Data.Foldable as Foldable import qualified Data.List as List -- import qualified Data.List.NonEmpty as NonEmpty import qualified Data.Map.Strict as Map import qualified Data.NonNull as NonNull -- import qualified Data.Set as Set import qualified Data.Strict as S import qualified Data.Text as Text import qualified Data.Time.Calendar as Time import qualified Data.Time.LocalTime as Time import qualified Data.TreeMap.Strict as TreeMap import qualified Data.TreeMap.Strict.Zipper as TreeMap import qualified Language.Symantic as Sym import qualified Language.Symantic.Lib as Sym -- import qualified Language.Symantic.Parsing as Sym import qualified System.FilePath.Posix as FilePath import qualified System.IO.Error as IO import qualified Text.Megaparsec as P import qualified Text.Megaparsec.Prim as P import qualified Hcompta as H import qualified Hcompta.LCC as LCC import qualified Hcompta.LCC.Lib.Strict as S import qualified Hcompta.LCC.Sym as Sym -- import System.IO (IO) import Prelude (Bounded) -- import Control.Applicative (Alternative) import Data.NonNull (NonNull) test :: Text -> Assertion -> TestTree test = testCase . elide . Foldable.foldMap escapeChar . Text.unpack escapeChar :: Char -> String escapeChar c | Char.isPrint c = [c] escapeChar c = Char.showLitChar c "" elide :: String -> String elide s | List.length s > 42 = List.take 42 s <> ['…'] elide s = s account :: [Text] -> LCC.Account account = LCC.Account . NonNull.impureNonNull . (LCC.Name <$>) tag :: [Text] -> Text -> LCC.Tag tag p v = LCC.Tag (LCC.Tag_Path $ NonNull.impureNonNull (LCC.Name <$> p)) (LCC.Tag_Data v) account_ref :: [Text] -> LCC.Tag_Path account_ref p = LCC.Tag_Path $ NonNull.impureNonNull $ LCC.Name <$> p account_refs :: [([Text], [[Text]])] -> Map LCC.Tag_Path (Map LCC.Account ()) account_refs l = Map.fromList $ (<$> l) $ \(anch, accts) -> ( LCC.Tag_Path $ NonNull.impureNonNull $ LCC.Name <$> anch , Map.fromList $ (,()) . account <$> accts ) tags :: [([Text], Text)] -> LCC.Tags tags l = LCC.Tags $ TreeMap.from_List (flip (<>)) $ (<$> l) $ \(p, v) -> (NonNull.impureNonNull (LCC.Name <$> p), [LCC.Tag_Data v]) amounts :: [(Text, LCC.Quantity)] -> LCC.Amounts amounts l = LCC.Amounts $ Map.fromList $ (<$> l) $ \(u, q) -> (LCC.Unit u, q) postings :: [LCC.Posting] -> LCC.Postings postings l = LCC.Postings $ Map.fromListWith (flip (<>)) $ (<$> l) $ \p -> (LCC.posting_account p, [p]) comments :: [Text] -> [LCC.Comment] comments = (LCC.Comment <$>) sourcePos :: FilePath -> Word -> Word -> P.SourcePos sourcePos fp l c = P.SourcePos fp (P.unsafePos l) (P.unsafePos c) date :: Integer -> Int -> Int -> Int -> Int -> Pico -> Time.TimeZone -> LCC.Date date y m d h m' s tz = Time.zonedTimeToUTC $ Time.ZonedTime (Time.LocalTime (Time.fromGregorian y m d) (Time.TimeOfDay h m' s)) tz -- * Type 'Parsec' newtype Parsec e s m a = Parsec { unParsec :: P.ParsecT e s m a } deriving (Functor, Applicative, Monad, MonadTrans, Alternative, MonadPlus, P.MonadParsec e s) type instance MC.CanDo (Parsec e s m) (MC.EffState a) = 'False type instance MC.CanDo (Parsec e s m) (MC.EffReader P.SourcePos) = 'True type instance MC.CanDo (Parsec e s m) (MC.EffReader (S.Either Exn.IOException LCC.CanonFile)) = 'True instance -- Gram_File ( LCC.ParsecC e s , Monad m , MC.MonadState Context_Test (Parsec e s m) , P.MonadParsec e s (P.ParsecT e s m) , P.MonadParsec e s (Parsec e s m) , s ~ Text ) => LCC.Gram_IO (Parsec e Text m) where g_canonfile g = do fp <- g return (fp, Right $ LCC.CanonFile fp) g_read g_path g = do lr <- LCC.g_at $ do lr_path <- g_path case lr_path of S.Left e -> return $ \at -> S.Left $ at e S.Right fp -> do db <- context_test_files <$> MC.get case Map.lookup fp db of Nothing -> return $ \at -> S.Left $ at $ LCC.Error_Journal_Read fp $ IO.userError $ show db Just txt -> return $ const $ S.Right (fp, txt) case lr of S.Left e -> return $ S.Left [e] S.Right (LCC.PathFile fp_new, s_new) -> do P.pushPosition $ P.initialPos fp_new s_old <- P.getInput; P.setInput s_new lr_a <- g P.setInput s_old P.popPosition return lr_a deriving instance LCC.ParsecC e s => Sym.Alter (Parsec e s m) deriving instance LCC.ParsecC e s => Sym.Alt (Parsec e s m) deriving instance LCC.ParsecC e s => Sym.App (Parsec e s m) deriving instance LCC.ParsecC e s => Sym.Try (Parsec e s m) deriving instance LCC.ParsecC e s => Sym.Gram_Rule (Parsec e s m) deriving instance LCC.ParsecC e s => Sym.Gram_Terminal (Parsec e s m) deriving instance LCC.ParsecC e s => Sym.Gram_RegR (Parsec e s m) deriving instance LCC.ParsecC e s => Sym.Gram_RegL (Parsec e s m) deriving instance LCC.ParsecC e s => Sym.Gram_CF (Parsec e s m) deriving instance LCC.ParsecC e s => Sym.Gram_Meta P.SourcePos (Parsec e s m) deriving instance LCC.ParsecC e s => Sym.Gram_Lexer (Parsec e s m) deriving instance LCC.ParsecC e s => Sym.Gram_Op (Parsec e s m) deriving instance LCC.ParsecC e s => LCC.Gram_Count (Parsec e s m) deriving instance LCC.ParsecC e s => LCC.Gram_At (Parsec e s m) deriving instance LCC.ParsecC e s => LCC.Gram_Char (Parsec e s m) deriving instance LCC.ParsecC e s => LCC.Gram_Comment (Parsec e s m) deriving instance LCC.ParsecC e s => LCC.Gram_Tag (Parsec e s m) deriving instance LCC.ParsecC e s => LCC.Gram_Account (Parsec e s m) deriving instance LCC.ParsecC e s => LCC.Gram_Amount (Parsec e s m) deriving instance -- Gram_Posting ( LCC.ParsecC e s , LCC.Gram_Posting (P.ParsecT e s m) , MC.MonadState (S.Maybe LCC.Unit) m , MC.MonadState LCC.Chart m , MC.MonadState LCC.Style_Amounts m , MC.MonadState LCC.Year m ) => LCC.Gram_Posting (Parsec e s m) deriving instance -- Gram_Date ( LCC.ParsecC e s , LCC.Gram_Date (P.ParsecT e s m) , MC.MonadState LCC.Year m ) => LCC.Gram_Date (Parsec e s m) deriving instance -- Gram_Transaction ( LCC.ParsecC e s , LCC.Gram_Transaction (P.ParsecT e s m) , MC.MonadState (S.Maybe LCC.Unit) m , MC.MonadState LCC.Chart m , MC.MonadState LCC.Section m , MC.MonadState LCC.Style_Amounts m , MC.MonadState LCC.Year m ) => LCC.Gram_Transaction (Parsec e s m) deriving instance -- Gram_Chart ( LCC.ParsecC e s , LCC.Gram_Chart (P.ParsecT e s m) , MC.MonadState LCC.Chart m , MC.MonadState LCC.Section m ) => LCC.Gram_Chart (Parsec e s m) deriving instance (LCC.ParsecC e s, LCC.Gram_File (P.ParsecT e s m)) => LCC.Gram_File (Parsec e s m) instance -- Gram_Journal ( LCC.Gram_Account g , LCC.Gram_At g , LCC.Gram_Chart g , LCC.Gram_File g , LCC.Gram_IO g , LCC.Gram_Reader (S.Either Exn.IOException LCC.CanonFile) g , LCC.Gram_State Context_Test g , LCC.Gram_State (LCC.Context_Read j) g , LCC.Gram_State (LCC.Journal j) g , LCC.Gram_State (LCC.Journals j) g , LCC.Gram_State (LCC.Env cs is) g , LCC.Gram_State (LCC.ProtoEnv cs is) g , LCC.Gram_Transaction g , LCC.Gram_Term cs is (Parsec e Text m) , LCC.ParsecC e Text , Sym.Try g , Sym.Gram_Lexer g , Monoid j , g ~ Parsec e Text m ) => LCC.Gram_Journal cs is j (Parsec e Text m) deriving instance -- Gram_Term ( Sym.Gram_Term is LCC.Meta (P.ParsecT e s m) , Sym.Gram_Term_AtomsR LCC.Meta is is (Parsec e s m) , LCC.ParsecC e s ) => Sym.Gram_Term is LCC.Meta (Parsec e s m) deriving instance LCC.ParsecC e s => Sym.Gram_Meta LCC.Meta (Parsec e s m) deriving instance LCC.ParsecC e s => Sym.Gram_Error (Parsec e s m) deriving instance LCC.ParsecC e s => Sym.Gram_Name (Parsec e s m) deriving instance LCC.ParsecC e s => Sym.Gram_Term_Type LCC.Meta (Parsec e s m) deriving instance LCC.ParsecC e s => Sym.Gram_Type LCC.Meta (Parsec e s m) deriving instance -- Gram_Term ( LCC.Gram_Term cs is (P.ParsecT e s m) , Sym.Gram_Term is LCC.Meta (Parsec e s m) , MC.MonadState (LCC.Env cs is) m , MC.MonadState (LCC.ProtoEnv cs is) m , LCC.ParsecC e s ) => LCC.Gram_Term cs is (Parsec e s m) instance -- Gram_State ( LCC.ParsecC e s , MC.MonadState ctx (Parsec e s m) ) => LCC.Gram_State ctx (Parsec e s m) where g_get g = do f <- g s <- MC.get return (f s) g_state g = do f <- g s <- MC.get let (s', a) = f s MC.put s' return a g_put g = do (s, a) <- g MC.put s return a instance -- Gram_Reader ( LCC.ParsecC e s , MC.MonadReader ctx (Parsec e s m) ) => LCC.Gram_Reader ctx (Parsec e s m) where g_ask g = do f <- g s <- MC.ask return (f s) g_ask_before g = do s <- MC.ask f <- g return (f s) instance LCC.ParsecC e s => MC.MonadReaderN 'MC.Zero P.SourcePos (Parsec e s m) where askN _px = Parsec P.getPosition instance LCC.ParsecC e s => MC.MonadReaderN 'MC.Zero (NonEmpty P.SourcePos) (Parsec e s m) where askN _px = Parsec $ P.statePos <$> P.getParserState instance LCC.ParsecC e s => MC.MonadReaderN 'MC.Zero (S.Either Exn.IOException LCC.CanonFile) (Parsec e s m) where askN _px = Parsec $ S.Right . LCC.CanonFile . LCC.PathFile . P.sourceName <$> P.getPosition -- * Type 'Context_Test' data Context_Test = Context_Test { context_test_files :: Map LCC.PathFile Text } deriving (Eq, Show) type instance MC.CanDo (S.StateT (LCC.Context_Read j) m) (MC.EffState Context_Test) = 'False type instance MC.CanDo (S.StateT Context_Test m) (MC.EffState Context_Test) = 'True type instance MC.CanDo (S.StateT (LCC.Context_Sym cs is) m) (MC.EffState Context_Test) = 'False instance Monad m => MC.MonadStateN 'MC.Zero Context_Test (S.StateT Context_Test m) where stateN _px = S.StateT . SS.state type instance MC.CanDo (S.StateT Context_Test m) (MC.EffState (Sym.Tokenizers meta is)) = 'False read :: forall is j cs e m a. ( Monoid j , LCC.Gram_File (P.ParsecT P.Dec Text m) , Sym.Tokenize LCC.Meta is , m ~ S.StateT (LCC.Context_Read j) (S.StateT (LCC.Context_Sym cs is) (S.StateT Context_Test Identity)) , e ~ P.ParseError Char P.Dec , cs ~ Sym.TyConsts_of_Ifaces is ) => Sym.CF (Parsec P.Dec Text m) a -> [(LCC.PathFile, Text)] -> FilePath -> Text -> Either (P.ParseError Char P.Dec) a read g files fp inp = runIdentity $ S.evalState Context_Test{ context_test_files = Map.fromList files } $ S.evalState LCC.context_sym $ S.evalState LCC.context_read $ P.runParserT (unParsec . Sym.unCF $ g <* Sym.eoi) (case fp of "" -> ""; _ -> FilePath.normalise fp) inp type M cs is j = S.StateT (LCC.Context_Read j) (S.StateT (LCC.Context_Sym cs is) (S.StateT Context_Test Identity)) test_compile :: forall is is' cs h j. ( Eq h , is ~ (Proxy LCC.Quantity ': Proxy Bool ': {-Proxy LCC.Journal ': Proxy LCC.Transaction ': Proxy [] ':-} is') , Sym.Tokenize LCC.Meta is , Sym.Inj_Token LCC.Meta is (->) , Sym.Inj_TyConst cs (->) , Sym.Inj_TyConst cs Bool , Sym.Inj_TyConst cs LCC.Journal , Sym.Inj_TyConst cs LCC.Transaction , Sym.Inj_TyConst cs LCC.Quantity , Sym.Inj_TyConst cs [] -- , Sym.Inj_TyConst cs Show -- , Sym.Inj_TyConst cs Eq , Show h , Sym.Show_Token LCC.Meta is , Sym.Show_TyConst cs , Sym.Compile cs is , Sym.Eq_Token LCC.Meta is , Sym.Gram_Term is LCC.Meta (P.ParsecT P.Dec Text (M cs is j)) , Sym.Sym_of_Ifaces is Sym.HostI , Sym.Gram_Term_AtomsR LCC.Meta is is (P.ParsecT P.Dec Text (M cs is j)) , Sym.Gram_Term_AtomsR LCC.Meta is is (Parsec P.Dec Text (M cs is j)) , cs ~ Sym.TyConsts_of_Ifaces is , Monoid j ) => Proxy j -> [Text] -> ( Sym.TeName , Sym.Type cs h , Either (Either (P.ParseError Char P.Dec) (LCC.At (Sym.Error_Term LCC.Meta cs is))) h ) -> TestTree test_compile _j i (n_exp, ty_exp, lr_exp) = let inp = Text.intercalate "\n" i in {- let env :: LCC.Env cs is = Map.fromList [ ("j" , Sym.ETermClosed (Sym.ty @Bool) $ Sym.TermClosed $ Sym.bool True) , ("jnl", Sym.ETermClosed (Sym.ty @LCC.Journal Sym.:$ (Sym.ty @[] Sym.:$ Sym.ty @LCC.Transaction)) $ Sym.TermClosed $ Sym.journal LCC.journal) ] in -} test inp $ case read @is @j ({-LCC.g_put (pure (env, ())) *>-} LCC.g_term) [] "" inp of Left err_syn -> Left (Left err_syn) @?= lr_exp Right (n_got, lr_sem) -> case lr_sem of Left err_sem -> Left (Right err_sem) @?= lr_exp Right got -> case lr_exp of Left err -> Right ("…"::Text) @?= Left err Right (_te_exp::h) -> (>>= (@?= (n_exp, lr_exp))) $ ((n_got,) <$>) $ return $ case got `Sym.feed_args` [ Sym.ETermClosed (Sym.ty @LCC.Quantity) (Sym.TermClosed $ Sym.quantity 42) , Sym.ETermClosed (Sym.ty @Bool) (Sym.TermClosed $ Sym.bool True) ] of Sym.ETermClosed ty_got (Sym.TermClosed te_got) -> case ty_got `Sym.eq_Type` ty_exp of Nothing -> err_type (Sym.EType ty_got) Just Refl -> Right $ Sym.host_from_term te_got where err_type ty_got = Left $ Right $ LCC.At (P.initialPos "" :| []) (P.initialPos "") $ Sym.Error_Term_Con_Type $ Right $ Sym.Con_TyEq (Right $ Sym.At Nothing ty_got) (Sym.At Nothing $ Sym.EType ty_exp) read_gram :: forall is cs j e m a. ( Monoid j , LCC.Gram_File (P.ParsecT P.Dec Text m) , Sym.Tokenize LCC.Meta is , m ~ S.StateT (LCC.Context_Read j) (S.StateT (LCC.Context_Sym cs is) (S.StateT Context_Test Identity)) , e ~ P.ParseError Char P.Dec , j ~ [LCC.Transaction] , cs ~ Sym.TyConsts_of_Ifaces is , is ~ '[Proxy (->)] ) => Sym.CF (Parsec P.Dec Text m) a -> Text -> Either (P.ParseError Char P.Dec) a read_gram g inp = runIdentity $ S.evalState Context_Test{ context_test_files = Map.fromList [] } $ S.evalState LCC.context_sym $ S.evalState LCC.context_read $ P.runParserT (unParsec . Sym.unCF $ g <* Sym.eoi) "" inp tests :: TestTree tests = testGroup "Read" [{- testGroup "Date" $ let (==>) inp exp = test inp $ read_gram LCC.g_date inp @?= Right (S.Right exp) in [ "2000-01-13" ==> date 2000 01 13 0 0 0 Time.utc , "2000-01-13_12:34" ==> date 2000 01 13 12 34 0 Time.utc , "2000-01-13_12:34:56" ==> date 2000 01 13 12 34 56 Time.utc , "2000-01-13_12:34_CET" ==> date 2000 01 13 12 34 0 (Time.TimeZone 60 True "CET") , "2000-01-13_12:34+01:30" ==> date 2000 01 13 12 34 0 (Time.TimeZone 90 False "+01:30") , "2000-01-13_12:34:56_CET" ==> date 2000 01 13 12 34 56 (Time.TimeZone 60 True "CET") , "01-01" ==> date 1970 01 01 0 0 0 Time.utc , testGroup "Parsing errors" $ let (!=>) inp exp = test inp $ read_gram LCC.g_date inp @?= Left exp in [ "2000/01/13" !=> P.ParseError { P.errorPos = sourcePos "" 1 5 :| [] , P.errorUnexpected = Set.fromList [P.Tokens ('/' :| "")] , P.errorExpected = Set.fromList [P.Tokens ('-' :| "")] , P.errorCustom = Set.fromList [] } ] , testGroup "Semantic errors" $ let (=!>) inp exp = test inp $ read_gram LCC.g_date inp @?= Right (S.Left exp) in [ "2000-13-01" =!> LCC.At { LCC.atBegin = pure $ sourcePos "" 1 1 , LCC.atEnd = sourcePos "" 1 11 , LCC.atItem = LCC.Error_Date_Day_invalid (2000, 13, 01) } , "2001-02-29" =!> LCC.At { LCC.atBegin = pure $ sourcePos "" 1 1 , LCC.atEnd = sourcePos "" 1 11 , LCC.atItem = LCC.Error_Date_Day_invalid (2001, 2, 29) } , "2000-01-13_12:60" =!> LCC.At { LCC.atBegin = pure $ sourcePos "" 1 12 , LCC.atEnd = sourcePos "" 1 17 , LCC.atItem = LCC.Error_Date_TimeOfDay_invalid (12, 60, 0) } ] ] , testGroup "Account_Section" $ let (==>) inp exp = test inp $ rights [read_gram LCC.g_account_section inp] @?= [LCC.Name inp | exp] in [ "A" ==> True , "AA" ==> True , "(A)A" ==> True , "(A)" ==> True , "A(A)" ==> True , "[A]A" ==> True , "[A]" ==> True , testGroup "Parsing errors" [ "" ==> False , " " ==> False , "/" ==> False , "A/" ==> False , "/A" ==> False , "A " ==> False , "A A" ==> False , "A " ==> False , "A\t" ==> False , "A \n" ==> False , "( )A" ==> False , "(A) A" ==> False , "[ ] A" ==> False , "(A) " ==> False , "[A] A" ==> False , "[A] " ==> False ] ] , testGroup "Account" $ let (==>) inp exp = test inp $ read_gram LCC.g_account inp @?= Right (account exp) in [ "/A" ==> ["A"] , "/A/B" ==> ["A", "B"] , "/A/B/C" ==> ["A", "B","C"] , "/Aa/Bbb/Cccc" ==> ["Aa", "Bbb", "Cccc"] , "/A/B/(C)" ==> ["A", "B", "(C)"] , testGroup "Parsing errors" $ let (!=>) inp _exp = test inp $ rights [read_gram LCC.g_account inp] @?= [] in [ "" !=> [] , "A" !=> [] , "A/" !=> [] , "A " !=> [] , " A" !=> [] , "/A/ /C" !=> [] , "/A//C" !=> [] , "/A a / B b b / C c c c" !=> [] ] ] , testGroup "Amount" $ let (==>) inp exp = test inp $ read_gram LCC.g_amount inp @?= Right exp in [ "0" ==> ( LCC.style_amount , LCC.amount { LCC.amount_quantity = Decimal 0 0 } ) , "00" ==> ( LCC.style_amount , LCC.amount { LCC.amount_quantity = Decimal 0 0 } ) , "0." ==> ( LCC.style_amount { LCC.style_amount_fractioning = pure '.' } , LCC.amount { LCC.amount_quantity = Decimal 0 0 } ) , "0," ==> ( LCC.style_amount { LCC.style_amount_fractioning = pure ',' } , LCC.amount { LCC.amount_quantity = Decimal 0 0 } ) , "0.0" ==> ( LCC.style_amount { LCC.style_amount_fractioning = pure '.' } , LCC.amount { LCC.amount_quantity = Decimal 1 0 } ) , "00.00" ==> ( LCC.style_amount { LCC.style_amount_fractioning = pure '.' } , LCC.amount { LCC.amount_quantity = Decimal 2 0 } ) , "0,0" ==> ( LCC.style_amount { LCC.style_amount_fractioning = pure ',' } , LCC.amount { LCC.amount_quantity = Decimal 1 0 } ) , "00,00" ==> ( LCC.style_amount { LCC.style_amount_fractioning = pure ',' } , LCC.amount { LCC.amount_quantity = Decimal 2 0 } ) , "0_0" ==> ( LCC.style_amount { LCC.style_amount_grouping_integral = pure $ LCC.Style_Amount_Grouping '_' [1] } , LCC.amount { LCC.amount_quantity = Decimal 0 0 } ) , "00_00" ==> ( LCC.style_amount { LCC.style_amount_grouping_integral = pure $ LCC.Style_Amount_Grouping '_' [2] } , LCC.amount { LCC.amount_quantity = Decimal 0 0 } ) , "0,000.00" ==> ( LCC.style_amount { LCC.style_amount_fractioning = pure '.' , LCC.style_amount_grouping_integral = pure $ LCC.Style_Amount_Grouping ',' [3] } , LCC.amount { LCC.amount_quantity = Decimal 2 0 } ) , "0.000,00" ==> ( LCC.style_amount { LCC.style_amount_fractioning = pure ',' , LCC.style_amount_grouping_integral = pure $ LCC.Style_Amount_Grouping '.' [3] } , LCC.amount { LCC.amount_quantity = Decimal 2 0 } ) , "1,000.00" ==> ( LCC.style_amount { LCC.style_amount_fractioning = pure '.' , LCC.style_amount_grouping_integral = pure $ LCC.Style_Amount_Grouping ',' [3] } , LCC.amount { LCC.amount_quantity = Decimal 2 100000 } ) , "1.000,00" ==> ( LCC.style_amount { LCC.style_amount_fractioning = pure ',' , LCC.style_amount_grouping_integral = pure $ LCC.Style_Amount_Grouping '.' [3] } , LCC.amount { LCC.amount_quantity = Decimal 2 100000 } ) , "123" ==> ( LCC.style_amount , LCC.amount { LCC.amount_quantity = Decimal 0 123 } ) , "1.2" ==> ( LCC.style_amount { LCC.style_amount_fractioning = pure '.' } , LCC.amount { LCC.amount_quantity = Decimal 1 12 } ) , "1,2" ==> ( LCC.style_amount { LCC.style_amount_fractioning = pure ',' } , LCC.amount { LCC.amount_quantity = Decimal 1 12 } ) , "12.34" ==> ( LCC.style_amount { LCC.style_amount_fractioning = pure '.' } , LCC.amount { LCC.amount_quantity = Decimal 2 1234 } ) , "12,34" ==> ( LCC.style_amount { LCC.style_amount_fractioning = pure ',' } , LCC.amount { LCC.amount_quantity = Decimal 2 1234 } ) , "1_2" ==> ( LCC.style_amount { LCC.style_amount_grouping_integral = pure $ LCC.Style_Amount_Grouping '_' [1] } , LCC.amount { LCC.amount_quantity = Decimal 0 12 } ) , "1_23" ==> ( LCC.style_amount { LCC.style_amount_grouping_integral = pure $ LCC.Style_Amount_Grouping '_' [2] } , LCC.amount { LCC.amount_quantity = Decimal 0 123 } ) , "1_23_456" ==> ( LCC.style_amount { LCC.style_amount_grouping_integral = pure $ LCC.Style_Amount_Grouping '_' [3, 2] } , LCC.amount { LCC.amount_quantity = Decimal 0 123456 } ) , "1_23_456,7890_12345_678901" ==> ( LCC.style_amount { LCC.style_amount_fractioning = pure ',' , LCC.style_amount_grouping_integral = pure $ LCC.Style_Amount_Grouping '_' [3, 2] , LCC.style_amount_grouping_fractional = pure $ LCC.Style_Amount_Grouping '_' [4, 5, 6] } , LCC.amount { LCC.amount_quantity = Decimal 15 123456789012345678901 } ) , "1_23_456.7890_12345_678901" ==> ( LCC.style_amount { LCC.style_amount_fractioning = pure '.' , LCC.style_amount_grouping_integral = pure $ LCC.Style_Amount_Grouping '_' [3, 2] , LCC.style_amount_grouping_fractional = pure $ LCC.Style_Amount_Grouping '_' [4, 5, 6] } , LCC.amount { LCC.amount_quantity = Decimal 15 123456789012345678901 } ) , "1,23,456.7890_12345_678901" ==> ( LCC.style_amount { LCC.style_amount_fractioning = pure '.' , LCC.style_amount_grouping_integral = pure $ LCC.Style_Amount_Grouping ',' [3, 2] , LCC.style_amount_grouping_fractional = pure $ LCC.Style_Amount_Grouping '_' [4, 5, 6] } , LCC.amount { LCC.amount_quantity = Decimal 15 123456789012345678901 } ) , "1.23.456,7890_12345_678901" ==> ( LCC.style_amount { LCC.style_amount_fractioning = pure ',' , LCC.style_amount_grouping_integral = pure $ LCC.Style_Amount_Grouping '.' [3, 2] , LCC.style_amount_grouping_fractional = pure $ LCC.Style_Amount_Grouping '_' [4, 5, 6] } , LCC.amount { LCC.amount_quantity = Decimal 15 123456789012345678901 } ) , "123456_78901_2345.678_90_1" ==> ( LCC.style_amount { LCC.style_amount_fractioning = pure '.' , LCC.style_amount_grouping_integral = pure $ LCC.Style_Amount_Grouping '_' [4, 5, 6] , LCC.style_amount_grouping_fractional = pure $ LCC.Style_Amount_Grouping '_' [3, 2] } , LCC.amount { LCC.amount_quantity = Decimal 6 123456789012345678901 } ) , "$1" ==> ( LCC.style_amount { LCC.style_amount_unit_side = pure LCC.L , LCC.style_amount_unit_spaced = pure False } , LCC.amount { LCC.amount_quantity = Decimal 0 1 , LCC.amount_unit = "$" } ) , "1$" ==> ( LCC.style_amount { LCC.style_amount_unit_side = pure LCC.R , LCC.style_amount_unit_spaced = pure False } , LCC.amount { LCC.amount_quantity = Decimal 0 1 , LCC.amount_unit = "$" } ) , "$ 1" ==> ( LCC.style_amount { LCC.style_amount_unit_side = pure LCC.L , LCC.style_amount_unit_spaced = pure True } , LCC.amount { LCC.amount_quantity = Decimal 0 1 , LCC.amount_unit = "$" } ) , "1 $" ==> ( LCC.style_amount { LCC.style_amount_unit_side = pure LCC.R , LCC.style_amount_unit_spaced = pure True } , LCC.amount { LCC.amount_quantity = Decimal 0 1 , LCC.amount_unit = "$" } ) , "-$1" ==> ( LCC.style_amount { LCC.style_amount_unit_side = pure LCC.L , LCC.style_amount_unit_spaced = pure False } , LCC.amount { LCC.amount_quantity = Decimal 0 (-1) , LCC.amount_unit = "$" } ) , "$1.000,00" ==> ( LCC.style_amount { LCC.style_amount_fractioning = pure ',' , LCC.style_amount_grouping_integral = pure $ LCC.Style_Amount_Grouping '.' [3] , LCC.style_amount_unit_side = pure LCC.L , LCC.style_amount_unit_spaced = pure False } , LCC.amount { LCC.amount_quantity = Decimal 2 100000 , LCC.amount_unit = "$" } ) , "1.000,00$" ==> ( LCC.style_amount { LCC.style_amount_fractioning = pure ',' , LCC.style_amount_grouping_integral = pure $ LCC.Style_Amount_Grouping '.' [3] , LCC.style_amount_unit_side = pure LCC.R , LCC.style_amount_unit_spaced = pure False } , LCC.amount { LCC.amount_quantity = Decimal 2 100000 , LCC.amount_unit = "$" } ) , testGroup "Parsing errors" $ let (!=>) inp _exp = test inp $ rights [read_gram LCC.g_amount inp] @?= [] in [ "" !=> [] , ".0" !=> [] , ",0" !=> [] , "0_" !=> [] , "_0" !=> [] , "1,000.00." !=> [] , "1.000,00," !=> [] , "1,000.00_" !=> [] ] ] , testGroup "Comment" $ let (==>) (inp, post) exp = test inp $ rights [read_gram (LCC.g_comment <* post) inp] @?= (LCC.Comment <$> exp) in [ ("; a b c" , Sym.eoi) ==> [ "a b c" ] , ("; #a" , Sym.eoi) ==> [ "#a" ] , ("; a b c \n" , Sym.string " \n") ==> [ "a b c" ] , ("; a b c \r\n", Sym.string " \r\n") ==> [ "a b c" ] -- , ("; a b c\n ; d e f", Sym.eoi) ==> [ ["a b c", "d e f"] ] -- , ("; a b c \n", Sym.string " \n") ==> [ ["a b c"] ] ] , testGroup "Transaction_Tag" $ let (==>) inp exp = test inp $ read_gram LCC.g_transaction_tag inp @?= Right (LCC.Transaction_Tag exp) in [ "#Name" ==> tag ["Name"] "" , "#Name:name" ==> tag ["Name", "name"] "" , "#Name=Value" ==> tag ["Name"] "Value" , "#Name = Value" ==> tag ["Name"] "Value" , "#Name=Val ue" ==> tag ["Name"] "Val ue" , "#Name=," ==> tag ["Name"] "," , "#Name=Val,ue" ==> tag ["Name"] "Val,ue" , "#Name=Val,ue:" ==> tag ["Name"] "Val,ue:" , "#Name=Val,ue :" ==> tag ["Name"] "Val,ue :" , testGroup "Parsing errors" $ let (!=>) inp _exp = test inp $ rights [read_gram LCC.g_transaction_tag inp] @?= [] in [ "#Name:" !=> [] , "#Name=Value\n" !=> [] ] ] , testGroup "Posting" $ let (==>) inp exp = test inp $ read_gram LCC.g_posting inp @?= Right (S.Right exp) in [ "/A/B/C" ==> LCC.posting (account ["A", "B", "C"]) , "/A/B/C $1" ==> (LCC.posting $ account ["A", "B", "C"]) { LCC.posting_amounts = amounts [("$", 1)] } , "/A/B/C $1" ==> (LCC.posting $ account ["A", "B", "C"]) { LCC.posting_amounts = amounts [("$", 1)] } , "/A/B/C 1€" ==> (LCC.posting $ account ["A", "B", "C"]) { LCC.posting_amounts = amounts [("€", 1)] } , "/A/B/C $1; some comment" ==> (LCC.posting $ account ["A", "B", "C"]) { LCC.posting_amounts = amounts [("$", 1)] , LCC.posting_comments = comments ["some comment"] } , "/A/B/C; some comment" ==> (LCC.posting $ account ["A", "B", "C"]) { LCC.posting_comments = comments ["some comment"] } , "/A/B/C ; some comment" ==> (LCC.posting $ account ["A", "B", "C"]) { LCC.posting_amounts = amounts [] , LCC.posting_comments = comments ["some comment"] } , "/A/B/C ; some comment\n ; some other comment" ==> (LCC.posting $ account ["A", "B", "C"]) { LCC.posting_amounts = amounts [] , LCC.posting_comments = comments ["some comment", "some other comment"] } , "/A/B/C $1 ; some comment" ==> (LCC.posting $ account ["A", "B", "C"]) { LCC.posting_amounts = amounts [("$", 1)] , LCC.posting_comments = comments ["some comment"] } , "/A/B/C #N=V" ==> (LCC.posting $ account ["A", "B", "C"]) { LCC.posting_tags = LCC.Posting_Tags $ tags [ (["N"], "V") ] } , "/A/B/C #N:O=V" ==> (LCC.posting $ account ["A", "B", "C"]) { LCC.posting_tags = LCC.Posting_Tags $ tags [ (["N", "O"], "V") ] } , "/A/B/C #N=Val;ue" ==> (LCC.posting $ account ["A", "B", "C"]) { LCC.posting_tags = LCC.Posting_Tags $ tags [ (["N"], "Val;ue") ] } , "/A/B/C #N=Val#ue" ==> (LCC.posting $ account ["A", "B", "C"]) { LCC.posting_tags = LCC.Posting_Tags $ tags [ (["N"], "Val#ue") ] } , "/A/B/C #N=V ; not a comment" ==> (LCC.posting $ account ["A", "B", "C"]) { LCC.posting_tags = LCC.Posting_Tags $ tags [ (["N"], "V ; not a comment") ] } , "/A/B/C #N=V #O" ==> (LCC.posting $ account ["A", "B", "C"]) { LCC.posting_tags = LCC.Posting_Tags $ tags [ (["N"], "V #O") ] } , "/A/B/C #N#O" ==> (LCC.posting $ account ["A", "B", "C"]) { LCC.posting_tags = LCC.Posting_Tags $ tags [ (,) ["N"] "", (,) ["O"] "" ] } , "/A/B/C #N; #O" ==> (LCC.posting $ account ["A", "B", "C"]) { LCC.posting_tags = LCC.Posting_Tags $ tags [ (["N"], "") ] , LCC.posting_comments = comments ["#O"] } , "/A/B/C #N #O" ==> (LCC.posting $ account ["A", "B", "C"]) { LCC.posting_tags = LCC.Posting_Tags $ tags [ (,) ["N"] "", (,) ["O"] "" ] } , "/A/B/C \n #N=V" ==> (LCC.posting $ account ["A", "B", "C"]) { LCC.posting_tags = LCC.Posting_Tags $ tags [ (["N"], "V") ] } , "/A/B/C ; some comment\n #N=V" ==> (LCC.posting $ account ["A", "B", "C"]) { LCC.posting_comments = comments ["some comment"] , LCC.posting_tags = LCC.Posting_Tags $ tags [ (["N"], "V") ] } , "/A/B/C ; some comment\n #N=V v\n #N2=V2 v2" ==> (LCC.posting $ account ["A", "B", "C"]) { LCC.posting_comments = comments ["some comment"] , LCC.posting_tags = LCC.Posting_Tags $ tags [ (["N"], "V v") , (["N2"], "V2 v2") ] } , "/A/B/C\n #N=V\n #N=V2" ==> (LCC.posting $ account ["A", "B", "C"]) { LCC.posting_tags = LCC.Posting_Tags $ tags [ (["N"], "V") , (["N"], "V2") ] } , "/A/B/C\n #N=V\n #N2=V" ==> (LCC.posting $ account ["A", "B", "C"]) { LCC.posting_tags = LCC.Posting_Tags $ tags [ (["N"], "V") , (["N2"], "V") ] } ] , testGroup "Transaction" $ let (==>) i e = let inp = Text.intercalate "\n" i in test inp $ read_gram LCC.g_transaction inp @?= Right (S.Right e) in [ [ "2000-01-01 some wording" , " /A/B/C $1" , " /D/E/F $-1" ] ==> LCC.transaction { LCC.transaction_dates = date 2000 01 01 0 0 0 Time.utc `NonNull.ncons` [] , LCC.transaction_wording = "some wording" , LCC.transaction_postings = postings [ (LCC.posting $ account ["A", "B", "C"]) { LCC.posting_amounts = amounts [ ("$", 1) ] , LCC.posting_sourcepos = sourcePos "" 2 2 } , (LCC.posting $ account ["D", "E", "F"]) { LCC.posting_amounts = amounts [ ("$", -1) ] , LCC.posting_sourcepos = sourcePos "" 3 2 } ] } , [ "2000-01-01 some wording ; not a comment" , "; some other;comment" , " ; some last comment" , " /A/B/C $1" , " /D/E/F" ] ==> LCC.transaction { LCC.transaction_comments = comments [ "some other;comment" , "some last comment" ] , LCC.transaction_dates = date 2000 01 01 0 0 0 Time.utc `NonNull.ncons` [] , LCC.transaction_wording = "some wording ; not a comment" , LCC.transaction_postings = postings [ (LCC.posting $ account ["A", "B", "C"]) { LCC.posting_amounts = amounts [ ("$", 1) ] , LCC.posting_sourcepos = sourcePos "" 4 2 } , (LCC.posting $ account ["D", "E", "F"]) { LCC.posting_amounts = amounts [ ("$", -1) ] , LCC.posting_sourcepos = sourcePos "" 5 2 } ] } , testGroup "Semantic errors" $ let (=!>) i e = let inp = Text.intercalate "\n" i in test inp $ read_gram LCC.g_transaction inp @?= Right (S.Left e) in [ [ "2000-01-01 wording" , " /A/B/C $1" , " /D/E/F $-2" ] =!> LCC.At { LCC.atBegin = pure $ sourcePos "" 1 1 , LCC.atEnd = sourcePos "" 3 12 , LCC.atItem = LCC.Error_Transaction_not_equilibrated LCC.transaction { LCC.transaction_dates = date 2000 01 01 0 0 0 Time.utc `NonNull.ncons` [] , LCC.transaction_wording = "wording" , LCC.transaction_postings = postings [ (LCC.posting $ account ["A", "B", "C"]) { LCC.posting_amounts = amounts [ ("$", 1) ] , LCC.posting_sourcepos = sourcePos "" 2 2 } , (LCC.posting $ account ["D", "E", "F"]) { LCC.posting_amounts = amounts [ ("$", -2) ] , LCC.posting_sourcepos = sourcePos "" 3 2 } ] } [( LCC.Unit "$" , H.SumByUnit { H.sumByUnit_quantity = H.Polarized_Both (-2) 1 , H.sumByUnit_accounts = Map.fromList [] })] } ] ] ,-} testGroup "Term" $ let (==>) = test_compile @'[ Proxy LCC.Quantity , Proxy Bool , Proxy LCC.Journal , Proxy LCC.Transaction , Proxy [] -- , Proxy LCC.Postings , Proxy LCC.Posting , Proxy (->) , Proxy Alternative , Proxy Bounded , Proxy Either , Proxy H.Addable , Proxy H.Negable , Proxy H.Subable , Proxy LCC.Account , Proxy LCC.Amounts , Proxy LCC.Date , Proxy LCC.PathFile , Proxy LCC.Unit , Proxy Map , Proxy NonNull , Proxy TreeMap.Zipper , Proxy [] , Proxy (,) ] (Proxy @[LCC.Transaction]) in [ [ "x = 42" ] ==> ("x", Sym.ty @LCC.Quantity, Right 42.0) , [ "x = 40 + 2" ] ==> ("x", Sym.ty @LCC.Quantity, Right 42.0) , [ "x = $4.2" ] ==> ("x", Sym.ty @LCC.Amounts, Right $ amounts [ ("$", 4.2) ]) , [ "x = 4,2€" ] ==> ("x", Sym.ty @LCC.Amounts, Right $ amounts [ ("€", 4.2) ]) , [ "x = 4,2€ + $2.4" ] ==> ("x", Sym.ty @LCC.Amounts, Right $ amounts [ ("$", 2.4), ("€", 4.2) ]) , [ "x = 4,0€ + 0.2€" ] ==> ("x", Sym.ty @LCC.Amounts, Right $ amounts [ ("€", 4.2) ]) , [ "x (q:Quantity) = q" ] ==> ("x", Sym.ty @LCC.Quantity, Right 42.0) , [ "x (b:Bool) = b" ] ==> ("x", Sym.ty @Bool, Right True) , [ "x (b:Bool) = (b,b)" ] ==> ("x", Sym.ty @(,) Sym.:$ Sym.ty @Bool Sym.:$ Sym.ty @Bool, Right (True, True)) , [ "x (b:Bool) (q:Quantity) = (b,q)" ] ==> ("x", Sym.ty @(,) Sym.:$ Sym.ty @Bool Sym.:$ Sym.ty @LCC.Quantity, Right (True, 42)) -- , [ "x = j" ] ==> ("x", Sym.ty @Bool, Right $ True ) -- , [ "x = q" ] ==> ("x", Sym.ty @LCC.Quantity, Right $ 42 ) , testGroup "Semantic errors" $ let (=!>) i e = let inp = Text.intercalate "\n" i in test inp $ read_gram LCC.g_transaction inp @?= Right (S.Left e) in [ ] ] {-, testGroup "Chart" $ let (==>) i e = let inp = Text.intercalate "\n" i in test inp $ read_gram ( LCC.g_get $ (\_txn ch -> ch) <$> LCC.g_journal @(Sym.TyConsts_of_Ifaces '[Proxy (->)]) @'[Proxy (->)] (:) ) inp @?= Right e in let acct_path = NonEmpty.fromList . (LCC.Name <$>) in let acct_tags = LCC.Account_Tags . tags in [ [ "/A/B/C" , "/D/E/F" ] ==> LCC.Chart { LCC.chart_accounts = TreeMap.from_List (<>) [ (acct_path ["A", "B", "C"], acct_tags []) , (acct_path ["D", "E", "F"], acct_tags []) ] , LCC.chart_tags = Map.empty } , [ "/A/B/C" , " ~t0:t1" , " ~a0:a1:a2" , "/D/E/F" , " ~t0:t1 = v0" , " ~t0:t1 = v1" ] ==> LCC.Chart { LCC.chart_accounts = TreeMap.from_List (<>) [ (acct_path ["A", "B", "C"], acct_tags [ (["t0", "t1"], "") , (["a0", "a1", "a2"], "") ]) , (acct_path ["D", "E", "F"], acct_tags [ (["t0", "t1"], "v0") , (["t0", "t1"], "v1") ]) ] , LCC.chart_tags = account_refs [ (,) ["t0", "t1"] [ ["A", "B", "C"] , ["D", "E", "F"] ] , (,) ["a0", "a1", "a2"] [ ["A", "B", "C"] ] ] } ] , testGroup "Journal" $ let run fe i e = let inp = (Text.intercalate "\n" <$>) <$> i in let jnl = fromMaybe "" $ List.lookup "" inp in let exp = ((LCC.Journals . Map.fromList . (first LCC.CanonFile <$>)) <$>) <$> fe e in test jnl $ read @'[Proxy (->)] @[LCC.Transaction] ( LCC.g_get $ (\j (js::LCC.Journals [LCC.Transaction]) -> const js <$> j) <$> LCC.g_journal @(Sym.TyConsts_of_Ifaces '[Proxy (->)]) @'[Proxy (->)] (:) ) inp "" jnl @?= exp in let (==>) = run (Right . S.Right) ; infixr 0 ==> in let jnl :: LCC.Journal [LCC.Transaction] = LCC.journal in [ [ ("",) [ "2000-01-01 wording" , " /A/B/C $1" , " /D/E/F" ] ] ==> [ ("",) jnl { LCC.journal_content = [ LCC.transaction { LCC.transaction_dates = date 2000 01 01 0 0 0 Time.utc `NonNull.ncons` [] , LCC.transaction_wording = "wording" , LCC.transaction_sourcepos = sourcePos "" 1 1 , LCC.transaction_postings = postings [ (LCC.posting $ account ["A", "B", "C"]) { LCC.posting_amounts = amounts [ ("$", 1) ] , LCC.posting_sourcepos = sourcePos "" 2 2 } , (LCC.posting $ account ["D", "E", "F"]) { LCC.posting_amounts = amounts [ ("$", -1) ] , LCC.posting_sourcepos = sourcePos "" 3 2 } ] } ] {-, LCC.journal_amount_styles = LCC.Amount_Styles $ Map.fromList [ ( LCC.Unit "$" , mempty { LCC.amount_style_unit_side = Just LCC.L , LCC.amount_style_unit_spaced = Just False } ) ] -} } ] , [ ("",) [ "2000-01-01 1° wording" , " /A/B/C $1" , " /D/E/F" , "2000-01-02 2° wording" , " /A/B/C $1" , " /x/y/z" ] ] ==> [ ("",) jnl { LCC.journal_content = [ LCC.transaction { LCC.transaction_dates = date 2000 01 02 0 0 0 Time.utc `NonNull.ncons` [] , LCC.transaction_wording = "2° wording" , LCC.transaction_sourcepos = sourcePos "" 4 1 , LCC.transaction_postings = postings [ (LCC.posting $ account ["A", "B", "C"]) { LCC.posting_amounts = amounts [ ("$", 1) ] , LCC.posting_sourcepos = sourcePos "" 5 2 } , (LCC.posting $ account ["x", "y", "z"]) { LCC.posting_amounts = amounts [ ("$", -1) ] , LCC.posting_sourcepos = sourcePos "" 6 2 } ] } , LCC.transaction { LCC.transaction_dates = date 2000 01 01 0 0 0 Time.utc `NonNull.ncons` [] , LCC.transaction_wording = "1° wording" , LCC.transaction_sourcepos = sourcePos "" 1 1 , LCC.transaction_postings = postings [ (LCC.posting $ account ["A", "B", "C"]) { LCC.posting_amounts = amounts [ ("$", 1) ] , LCC.posting_sourcepos = sourcePos "" 2 2 } , (LCC.posting $ account ["D", "E", "F"]) { LCC.posting_amounts = amounts [ ("$", -1) ] , LCC.posting_sourcepos = sourcePos "" 3 2 } ] } ] } ] , [ ("",) [ "/A/B ~AB" , "" , "2000-01-01 wording" , " ~AB/C $1" , " /D/E/F" ] ] ==> [ ("",) jnl { LCC.journal_content = [ LCC.transaction { LCC.transaction_dates = date 2000 01 01 0 0 0 Time.utc `NonNull.ncons` [] , LCC.transaction_wording = "wording" , LCC.transaction_sourcepos = sourcePos "" 3 1 , LCC.transaction_postings = postings [ (LCC.posting $ account ["A", "B", "C"]) { LCC.posting_amounts = amounts [ ("$", 1) ] , LCC.posting_account_ref = S.Just $ account_ref ["AB"] S.:!: S.Just (account ["C"]) , LCC.posting_sourcepos = sourcePos "" 4 2 } , (LCC.posting $ account ["D", "E", "F"]) { LCC.posting_amounts = amounts [ ("$", -1) ] , LCC.posting_sourcepos = sourcePos "" 5 2 } ] } ] } ] , [ ("",) [ "./chart" , "" , "2000-01-01 wording" , " ~AB/C $1" , " ~D/E/F" ] , ("chart",) [ "/A/B ~AB" , "/D" , "; comment" , " ~D" ] ] ==> [ ("",) jnl { LCC.journal_includes = [ LCC.CanonFile "chart" ] , LCC.journal_content = [ LCC.transaction { LCC.transaction_dates = date 2000 01 01 0 0 0 Time.utc `NonNull.ncons` [] , LCC.transaction_wording = "wording" , LCC.transaction_sourcepos = sourcePos "" 3 1 , LCC.transaction_postings = postings [ (LCC.posting $ account ["A", "B", "C"]) { LCC.posting_amounts = amounts [ ("$", 1) ] , LCC.posting_account_ref = S.Just $ account_ref ["AB"] S.:!: S.Just (account ["C"]) , LCC.posting_sourcepos = sourcePos "" 4 2 } , (LCC.posting $ account ["D", "E", "F"]) { LCC.posting_amounts = amounts [ ("$", -1) ] , LCC.posting_account_ref = S.Just $ account_ref ["D"] S.:!: S.Just (account ["E", "F"]) , LCC.posting_sourcepos = sourcePos "" 5 2 } ] } ] } , ("chart",) jnl { LCC.journal_file = "chart" } ] , [ ("",) [ "2000-01-01 w" , " /A/B/C $1" , " /D/E/F" , "./0" , "./1" ] , ("0",) [ "2000-01-02 w0" , " /A/B/C $2" , " /D/E/F" ] , ("1",) [ "2000-01-03 w1" , " /A/B/C $3" , " /D/E/F" ] ] ==> [ ("",) jnl { LCC.journal_includes = [ LCC.CanonFile "0" , LCC.CanonFile "1" ] , LCC.journal_content = [ LCC.transaction { LCC.transaction_dates = date 2000 01 01 0 0 0 Time.utc `NonNull.ncons` [] , LCC.transaction_wording = "w" , LCC.transaction_sourcepos = sourcePos "" 1 1 , LCC.transaction_postings = postings [ (LCC.posting $ account ["A", "B", "C"]) { LCC.posting_amounts = amounts [ ("$", 1) ] , LCC.posting_sourcepos = sourcePos "" 2 2 } , (LCC.posting $ account ["D", "E", "F"]) { LCC.posting_amounts = amounts [ ("$", -1) ] , LCC.posting_sourcepos = sourcePos "" 3 2 } ] } ] } , ("0",) jnl { LCC.journal_file = "0" , LCC.journal_content = [ LCC.transaction { LCC.transaction_dates = date 2000 01 02 0 0 0 Time.utc `NonNull.ncons` [] , LCC.transaction_wording = "w0" , LCC.transaction_sourcepos = sourcePos "0" 1 1 , LCC.transaction_postings = postings [ (LCC.posting $ account ["A", "B", "C"]) { LCC.posting_amounts = amounts [ ("$", 2) ] , LCC.posting_sourcepos = sourcePos "0" 2 2 } , (LCC.posting $ account ["D", "E", "F"]) { LCC.posting_amounts = amounts [ ("$", -2) ] , LCC.posting_sourcepos = sourcePos "0" 3 2 } ] } ] } , ("1",) jnl { LCC.journal_file = "1" , LCC.journal_content = [ LCC.transaction { LCC.transaction_dates = date 2000 01 03 0 0 0 Time.utc `NonNull.ncons` [] , LCC.transaction_wording = "w1" , LCC.transaction_sourcepos = sourcePos "1" 1 1 , LCC.transaction_postings = postings [ (LCC.posting $ account ["A", "B", "C"]) { LCC.posting_amounts = amounts [ ("$", 3) ] , LCC.posting_sourcepos = sourcePos "1" 2 2 } , (LCC.posting $ account ["D", "E", "F"]) { LCC.posting_amounts = amounts [ ("$", -3) ] , LCC.posting_sourcepos = sourcePos "1" 3 2 } ] } ] } ] , testGroup "Parsing errors" $ let (!=>) = run Left ; infixr 0 !=> in [ [ ("",) [ "./j" ] , ("j",) [ "2000-01_01 wording" , " /A/B/C $1" , " /D/E/F" ] ] !=> P.ParseError { P.errorPos = sourcePos "j" 1 8 :| [sourcePos "" 1 4] , P.errorUnexpected = Set.fromList [P.Tokens ('_' :| "")] , P.errorExpected = Set.fromList [P.Tokens ('-' :| "")] , P.errorCustom = Set.fromList [] } ] , testGroup "Semantic errors" $ let (=!>) = run (Right . S.Left) ; infixr 0 =!> in [ [ ("",) [ "2000-01-01 wording" , " /A/B/C $1" , " /D/E/F" , "" , "./chart" , "" , "2000-01-01 wording" , " ~AB/C $1" , " /D/E/F" ] , ("chart",) [ "/A/B ~AB" ] ] =!> [ LCC.At { LCC.atBegin = sourcePos "chart" 1 1 :| [sourcePos "" 5 8] , LCC.atEnd = sourcePos "chart" 1 9 , LCC.atItem = LCC.Error_Journal_Section LCC.Section_Transaction LCC.Section_Chart } , LCC.At { LCC.atBegin = sourcePos "" 8 2 :| [] , LCC.atEnd = sourcePos "" 8 5 , LCC.atItem = LCC.Error_Journal_Transaction $ LCC.Error_Transaction_Posting $ LCC.Error_Posting_Account_Ref_unknown $ account_ref ["AB"] } ] , [ ("",) [ "./j" ] , ("j",) [ "2000-01-01 wording" , " /A/B/C $1" , " /D/E/F" , "" , "./j" ] ] =!> [ LCC.At { LCC.atBegin = sourcePos "j" 5 1 :| [sourcePos "" 1 4] , LCC.atEnd = sourcePos "j" 5 4 , LCC.atItem = LCC.Error_Journal_Include_loop $ LCC.CanonFile "j" } ] ] ] -}]