1 {-# LANGUAGE AllowAmbiguousTypes #-}
2 {-# LANGUAGE UndecidableSuperClasses #-}
3 module Hcompta.LCC.Grammar where
5 import Control.Applicative (Applicative(..), liftA2)
6 import Control.Monad (Monad(..), void)
8 import Data.Char (Char)
10 import Data.Either (Either(..), either)
11 import Data.Eq (Eq(..))
13 import Data.Function (($), (.), const, id, flip)
14 import Data.Functor (Functor(..), (<$>), (<$))
15 import Data.Functor.Compose (Compose(..))
16 import Data.List.NonEmpty (NonEmpty(..), (<|))
17 import Data.Map.Strict (Map)
18 import Data.Maybe (Maybe(..), maybe, isJust)
19 import Data.Monoid (Monoid(..))
20 import Data.NonNull (NonNull)
21 import Data.Ord (Ord(..))
22 import Data.Semigroup (Semigroup(..))
23 import Data.String (String)
24 import Data.Text (Text)
25 import Data.Time.LocalTime (TimeZone(..))
26 import Data.Traversable (sequenceA)
27 import Data.Tuple (fst)
28 import Data.Typeable ()
29 import Prelude (Int, Integer, Enum(..), Num(..), Integral(..), fromIntegral, error)
30 import System.FilePath ((</>))
31 import Text.Show (Show(..))
32 import qualified Control.Exception.Safe as Exn
33 import qualified Control.Monad.Classes as MC
34 import qualified Control.Monad.Trans.State.Strict as SS
35 import qualified Data.Char as Char
36 import qualified Data.List as L
37 import qualified Data.List.NonEmpty as NonEmpty
38 import qualified Data.Map.Strict as Map
39 import qualified Data.NonNull as NonNull
40 import qualified Data.Strict as S
41 import qualified Data.Text as Text
42 import qualified Data.Time.Calendar as Time
43 import qualified Data.Time.LocalTime as Time
44 import qualified Data.TreeMap.Strict as TreeMap
45 import qualified Hcompta as H
46 import qualified System.FilePath as FilePath
48 import qualified Language.Symantic.Grammar as G
49 import Language.Symantic.Grammar (CF, At(..), Gram_Rule(..), Gram_Terminal(..), Gram_Alt(..), Gram_AltApp(..), Gram_Try(..), Gram_CF(..))
50 import Language.Symantic.Lib ()
51 import qualified Language.Symantic as Sym
52 import qualified Language.Symantic.Grammar as Sym
54 import Hcompta.LCC.Account
55 import Hcompta.LCC.Name
56 import Hcompta.LCC.Tag
57 import Hcompta.LCC.Amount
58 import Hcompta.LCC.Chart
59 import Hcompta.LCC.Posting
60 import Hcompta.LCC.Transaction
61 import Hcompta.LCC.Journal
62 import Hcompta.LCC.Compta
64 import qualified Hcompta.LCC.Lib.Strict as S
67 import Debug.Trace (trace)
68 dbg :: Show a => String -> a -> a
69 dbg msg x = trace (msg <> " = " <> show x) x
72 -- * Type 'Context_Read'
73 data Context_Read src j
75 { context_read_year :: !Year
76 , context_read_style_amounts :: !Style_Amounts
77 , context_read_chart :: !Chart
78 , context_read_unit :: !(S.Maybe Unit)
79 , context_read_journals :: !(Journals j)
80 , context_read_journal :: !(NonEmpty (Journal j))
81 , context_read_canonfiles :: !(NonEmpty CanonFile)
82 , context_read_warnings :: ![At src Warning_Compta]
83 , context_read_section :: !Section
91 type instance MC.CanDo (S.StateT (Context_Read src j) m) (MC.EffReader (NonEmpty CanonFile)) = 'True
92 instance Monad m => MC.MonadReaderN 'MC.Zero (NonEmpty CanonFile) (S.StateT (Context_Read src j) m) where
93 askN _n = MC.gets $ \(x::Context_Read src j) -> context_read_canonfiles x
96 -- States handled by a nested Monad
98 type instance MC.CanDo (S.StateT (Context_Read src j) m) (MC.EffState (Sym.Modules src ss)) = 'False
99 type instance MC.CanDo (S.StateT (Context_Read src j) m) (MC.EffState Sym.Imports) = 'False
100 type instance MC.CanDo (S.StateT (Context_Read src j) m) (MC.EffState (Sym.Imports, Sym.Modules src ss)) = 'False
101 type instance MC.CanDo (S.StateT (Context_Read src j) m) (MC.EffState (Sym.Name2Type src)) = 'False
102 type instance MC.CanDo (S.StateT (Context_Read src j) m) (MC.EffState (Env src ss)) = 'False
103 type instance MC.CanDo (S.StateT (Context_Read src j) m) (MC.EffState Terms) = 'False
105 context_read :: Monoid j => Context_Read src j
108 { context_read_year = Year $ H.date_year (H.date_epoch::Date)
109 , context_read_style_amounts = mempty
110 , context_read_chart = mempty
111 , context_read_unit = S.Nothing
112 , context_read_journals = Journals Map.empty
113 , context_read_journal = journal :| []
114 , context_read_canonfiles = CanonFile "" :| []
115 , context_read_warnings = []
116 , context_read_section = Section_Terms
119 -- * Type 'Context_Sym'
120 data Context_Sym src ss
122 { context_sym_imports :: !Sym.Imports
123 , context_sym_modules :: !(Sym.Modules src ss)
124 , context_sym_name2type :: !(Sym.Name2Type src)
125 , context_sym_env :: !(Env src ss)
126 , context_sym_terms :: !Terms
127 } deriving (Eq, Show)
132 Sym.ModulesInj src ss =>
133 Sym.Name2TypeInj ss =>
136 let mods = either (error . show) id Sym.modulesInj in
138 { context_sym_imports = Sym.importQualifiedAs [] mods
139 , context_sym_modules = mods
140 , context_sym_name2type = Sym.name2typeInj @ss
141 , context_sym_env = Map.empty
142 , context_sym_terms = Map.empty
149 -- Sym.Modules src ss
150 type instance MC.CanDo (S.StateT (Context_Sym src ss) m) (MC.EffState (Sym.Modules src ss)) = 'True
151 instance Monad m => MC.MonadStateN 'MC.Zero (Sym.Modules src ss) (S.StateT (Context_Sym src ss) m) where
152 stateN _px f = S.StateT $ SS.state $ \ctx ->
153 (\a -> ctx{context_sym_modules = a})
154 <$> f (context_sym_modules ctx)
157 type instance MC.CanDo (S.StateT (Context_Sym src ss) m) (MC.EffState Sym.Imports) = 'True
158 instance Monad m => MC.MonadStateN 'MC.Zero Sym.Imports (S.StateT (Context_Sym src ss) m) where
159 stateN _px f = S.StateT $ SS.state $ \ctx ->
160 (\a -> ctx{context_sym_imports = a})
161 <$> f (context_sym_imports ctx)
163 -- (Sym.Imports, Sym.Modules src ss)
164 type instance MC.CanDo (S.StateT (Context_Sym src ss) m) (MC.EffState (Sym.Imports, Sym.Modules src ss)) = 'True
165 instance Monad m => MC.MonadStateN 'MC.Zero (Sym.Imports, Sym.Modules src ss) (S.StateT (Context_Sym src ss) m) where
166 stateN _px f = S.StateT $ SS.state $ \ctx ->
167 (\(imps, mods) -> ctx{context_sym_imports=imps, context_sym_modules=mods})
168 <$> f (context_sym_imports ctx, context_sym_modules ctx)
171 type instance MC.CanDo (S.StateT (Context_Sym src ss) m) (MC.EffState Terms) = 'True
172 instance Monad m => MC.MonadStateN 'MC.Zero Terms (S.StateT (Context_Sym src ss) m) where
173 stateN _px f = S.StateT $ SS.state $ \ctx ->
174 (\a -> ctx{context_sym_terms = a})
175 <$> f (context_sym_terms ctx)
178 type instance MC.CanDo (S.StateT (Context_Sym src ss) m) (MC.EffState (Sym.Name2Type src)) = 'True
179 instance Monad m => MC.MonadStateN 'MC.Zero (Sym.Name2Type src) (S.StateT (Context_Sym src ss) m) where
180 stateN _px f = S.StateT $ SS.state $ \ctx ->
181 (\a -> ctx{context_sym_name2type = a})
182 <$> f (context_sym_name2type ctx)
185 type instance MC.CanDo (S.StateT (Context_Sym src ss) m) (MC.EffState (Env src ss)) = 'True
186 instance Monad m => MC.MonadStateN 'MC.Zero (Env src ss) (S.StateT (Context_Sym src ss) m) where
187 stateN _px f = S.StateT $ SS.state $ \ctx ->
188 (\a -> ctx{context_sym_env = a})
189 <$> f (context_sym_env ctx)
191 -- Context_Read src j
192 type instance MC.CanDo (S.StateT (Context_Read src j) m) (MC.EffState (Context_Read src j)) = 'True
193 instance Monad m => MC.MonadStateN 'MC.Zero (Context_Read src j) (S.StateT (Context_Read src j) m) where
194 stateN _px = S.StateT . SS.state
197 type instance MC.CanDo (S.StateT (Context_Read src j) m) (MC.EffState (S.Maybe Unit)) = 'True
198 instance Monad m => MC.MonadStateN 'MC.Zero (S.Maybe Unit) (S.StateT (Context_Read src j) m) where
199 stateN _px f = S.StateT $ SS.state $ \ctx ->
200 (\a -> ctx{context_read_unit = a})
201 <$> f (context_read_unit ctx)
204 type instance MC.CanDo (S.StateT (Context_Read src j) m) (MC.EffState Chart) = 'True
205 instance Monad m => MC.MonadStateN 'MC.Zero Chart (S.StateT (Context_Read src j) m) where
206 stateN _px f = S.StateT $ SS.state $ \ctx ->
207 (\a -> ctx{context_read_chart = a})
208 <$> f (context_read_chart ctx)
211 type instance MC.CanDo (S.StateT (Context_Read src j) m) (MC.EffState Year) = 'True
212 instance Monad m => MC.MonadStateN 'MC.Zero Year (S.StateT (Context_Read src j) m) where
213 stateN _px f = S.StateT $ SS.state $ \ctx ->
214 (\a -> ctx{context_read_year = a})
215 <$> f (context_read_year ctx)
218 type instance MC.CanDo (S.StateT (Context_Read src j) m) (MC.EffState Section) = 'True
219 instance Monad m => MC.MonadStateN 'MC.Zero Section (S.StateT (Context_Read src j) m) where
220 stateN _px f = S.StateT $ SS.state $ \ctx ->
221 (\a -> ctx{context_read_section = a})
222 <$> f (context_read_section ctx)
225 type instance MC.CanDo (S.StateT (Context_Read src j) m) (MC.EffState (Journal j)) = 'True
226 instance Monad m => MC.MonadStateN 'MC.Zero (Journal j) (S.StateT (Context_Read src j) m) where
227 stateN _px f = S.StateT $ SS.state $ \ctx@Context_Read{context_read_journal = j:|js} ->
228 (\a -> ctx{context_read_journal = a:|js}) <$> f j
231 type instance MC.CanDo (S.StateT (Context_Read src j) m) (MC.EffState (Journals j)) = 'True
232 instance Monad m => MC.MonadStateN 'MC.Zero (Journals j) (S.StateT (Context_Read src j) m) where
233 stateN _px f = S.StateT $ SS.state $ \ctx ->
234 (\a -> ctx{context_read_journals = a})
235 <$> f (context_read_journals ctx)
238 type instance MC.CanDo (S.StateT (Context_Read src j) m) (MC.EffState Style_Amounts) = 'True
239 instance Monad m => MC.MonadStateN 'MC.Zero Style_Amounts (S.StateT (Context_Read src j) m) where
240 stateN _px f = S.StateT $ SS.state $ \ctx ->
241 (\s -> ctx{context_read_style_amounts = s})
242 <$> f (context_read_style_amounts ctx)
244 -- * Class 'Gram_Path'
245 class Gram_Path g where
248 -> g (PathFile, Either Exn.IOException CanonFile)
249 deriving instance Gram_Path g => Gram_Path (CF g)
252 class G.Gram_Source src g => Gram_IO src g where
254 :: g (S.Either (Error_Compta src) PathFile)
255 -> g (S.Either [At src (Error_Compta src)] (CanonFile, a))
256 -> g (S.Either [At src (Error_Compta src)] (CanonFile, a))
257 deriving instance Gram_IO src g => Gram_IO src (CF g)
259 -- * Class 'Gram_Count'
264 ) => Gram_Count g where
265 count :: Int -> CF g a -> CF g [a]
268 | otherwise = sequenceA $ L.replicate n p
269 count' :: Int -> Int -> CF g a -> CF g [a]
271 | n <= 0 || m > n = pure []
272 | m > 0 = (:) <$> p <*> count' (pred m) (pred n) p
274 let f t ts = maybe [] (:ts) t
275 in f <$> G.optional p <*> count' 0 (pred n) p
277 -- * Class 'Gram_Char'
287 ) => Gram_Char g where
289 g_eol = rule "EOL" $ void (char '\n') <+> void (G.string "\r\n")
291 g_tab = rule "Tab" $ void $ char '\t'
293 g_space = rule "Space" $ char ' '
294 g_spaces :: CF g Text
295 g_spaces = Text.pack <$> many g_space
297 g_spaces1 = void $ some g_space
299 g_char = g_char_passive <+> g_char_active
300 g_char_passive :: CF g Char
301 g_char_passive = choice $ G.unicat <$> [G.Unicat_Letter, G.Unicat_Number, G.Unicat_Mark]
302 g_char_active :: CF g Char
303 g_char_active = choice $ G.unicat <$> [G.Unicat_Punctuation, G.Unicat_Symbol]
304 g_char_attribute :: G.Reg lr g Char
305 g_char_attribute = choice $ char <$> "#/:;@~="
307 g_word = rule "Word" $ Text.pack <$> some g_char
309 g_words = Text.concat <$> many (try $ (<>) <$> g_spaces <*> g_word)
311 g_09 = range ('0', '9')
313 g_19 = range ('1', '9')
314 g_sign :: Num int => CF g (int -> int)
316 (negate <$ char '-') <+>
319 -- * Class 'Gram_Date'
321 ( G.Gram_State Year g
330 ) => Gram_Date g where
332 G.Gram_Source src g =>
333 CF g (S.Either (At src Error_Date) Date)
334 g_date = rule "Date" $
335 liftA2 (\day (tod, tz) ->
336 Time.localTimeToUTC tz $
337 Time.LocalTime day tod)
340 (S.Right (Time.midnight, Time.utc))
344 <*> option (S.Right Time.utc) g_timezone)
346 G.Gram_Source src g =>
347 CF g (S.Either (At src Error_Date) Time.Day)
358 <$> G.getAfter (pure $ \(Year y) -> y)
364 case Time.fromGregorianValid y m d of
365 Nothing -> S.Left $ At src $ Error_Date_Day_invalid (y, m, d)
366 Just day -> S.Right day
368 G.Gram_Source src g =>
369 CF g (S.Either (At src Error_Date) Time.TimeOfDay)
370 g_tod = rule "TimeOfDay" $
373 case Time.makeTimeOfDayValid hr mn (fromInteger $ toInteger sc) of
374 Nothing -> S.Left $ At src $ Error_Date_TimeOfDay_invalid (hr, mn, sc)
375 Just tod -> S.Right $ tod)
379 <$> (char char_tod_sep *> g_minute)
380 <*> option 0 (char char_tod_sep *> g_second))
381 g_year :: CF g Integer
382 g_year = rule "Year" $
383 (\sg y -> sg $ integer_of_digits 10 y)
384 <$> option id (negate <$ char '-')
387 g_month = rule "Month" $ int_of_digits 10 <$> count' 1 2 g_09
389 g_dom = rule "DayOfMonth" $ int_of_digits 10 <$> count' 1 2 g_09
391 g_hour = rule "Hour" $ int_of_digits 10 <$> count' 1 2 g_09
393 g_minute = rule "Minute" $ int_of_digits 10 <$> count' 1 2 g_09
395 g_second = rule "Second" $ int_of_digits 10 <$> count' 1 2 g_09
398 G.Gram_Source src g =>
399 CF g (S.Either (At src Error_Date) TimeZone)
400 g_timezone = rule "TimeZone" $
401 -- DOC: http://www.timeanddate.com/time/zones/
402 -- TODO: only a few time zones are suported below.
403 -- TODO: check the timeZoneSummerOnly values
404 (S.Right <$> g_timezone_digits) <+>
405 (G.source $ read_tz <$ char '_' <*> some (range ('A', 'Z')))
407 read_tz n src = case n of
408 "AST" -> S.Right $ TimeZone (- 4 * 60) False n
409 "ADT" -> S.Right $ TimeZone (- 3 * 60) True n
410 "A" -> S.Right $ TimeZone (- 1 * 60) False n
411 "BST" -> S.Right $ TimeZone (-11 * 60) False n
412 "BDT" -> S.Right $ TimeZone (-10 * 60) True n
413 "CET" -> S.Right $ TimeZone ( 1 * 60) True n
414 "CEST" -> S.Right $ TimeZone ( 2 * 60) False n
415 "CST" -> S.Right $ TimeZone (- 6 * 60) False n
416 "CDT" -> S.Right $ TimeZone (- 5 * 60) True n
417 "EST" -> S.Right $ TimeZone (- 5 * 60) False n
418 "EDT" -> S.Right $ TimeZone (- 4 * 60) True n
419 "GMT" -> S.Right $ TimeZone 0 False n
420 "HST" -> S.Right $ TimeZone (-10 * 60) False n
421 "HDT" -> S.Right $ TimeZone (- 9 * 60) True n
422 "MST" -> S.Right $ TimeZone (- 7 * 60) False n
423 "MDT" -> S.Right $ TimeZone (- 6 * 60) True n
424 "M" -> S.Right $ TimeZone (-12 * 60) False n
425 "NST" -> S.Right $ TimeZone (- 3 * 60 - 30) False n
426 "N" -> S.Right $ TimeZone ( 1 * 60) False n
427 "PST" -> S.Right $ TimeZone (- 8 * 60) False n
428 "PDT" -> S.Right $ TimeZone (- 7 * 60) True n
429 "YST" -> S.Right $ TimeZone (- 9 * 60) False n
430 "YDT" -> S.Right $ TimeZone (- 8 * 60) True n
431 "Y" -> S.Right $ TimeZone ( 12 * 60) False n
432 "Z" -> S.Right $ TimeZone 0 False n
433 _ -> S.Left $ At src $ Error_Date_TimeZone_unknown (Text.pack n)
434 g_timezone_digits :: CF g TimeZone
439 { timeZoneMinutes = sg $ hr * 60 + mn
440 , timeZoneSummerOnly = False
441 , timeZoneName = Time.timeZoneOffsetString tz
446 <*> option 0 (optional (char char_tod_sep) *> g_minute)
448 -- * Class 'Gram_Tag'
454 ) => Gram_Tag g where
457 <$ char char_tag_prefix
459 <*> option (Tag_Data "")
461 *> char char_tag_data_prefix
464 g_tag_path :: CF g Tag_Path
466 (\x xs -> Tag_Path $ NonNull.ncons x xs)
468 <*> many (try $ char char_tag_sep *> g_tag_section)
469 g_tag_section :: CF g Tag_Path_Section
472 <$> some (g_char `minus` g_char_attribute)
473 g_tag_value :: CF g Tag_Data
474 g_tag_value = Tag_Data <$> g_words
476 -- * Class 'Gram_Comment'
480 ) => Gram_Comment g where
481 g_comment :: CF g Comment
482 g_comment = rule "Comment" $
483 Comment <$ char ';' <* g_spaces <*> g_words
485 -- * Class 'Gram_Account'
491 ) => Gram_Account g where
492 g_account_section :: CF g Account_Section
495 <$> some (g_char `minus` g_char_attribute)
496 g_account :: CF g Account
497 g_account = rule "Account" $
498 Account . NonNull.impureNonNull
499 <$> some (try $ char '/' *> g_account_section)
500 g_account_tag :: CF g Account_Tag
504 <$ char char_account_tag_prefix
506 <*> option (Tag_Data "")
508 *> char char_tag_data_prefix
511 g_account_tag_path :: CF g Tag_Path
512 g_account_tag_path = rule "Tag_Path" $
513 char char_account_tag_prefix
516 g_anchor_section :: CF g Anchor_Section
517 g_anchor_section = rule "Anchor_Section" $
519 <$> some (g_char `minus` g_char_attribute)
522 -- * Class 'Gram_Amount'
527 ) => Gram_Amount g where
529 g_unit = rule "Unit" $
530 Unit . Text.singleton
531 <$> G.unicat (G.Unicat Char.CurrencySymbol)
532 g_quantity :: CF g (Quantity, Style_Amount)
533 g_quantity = rule "Quantity" $
534 (\(i, f, fr, gi, gf) ->
535 let int = concat i in
536 let frac = concat f in
537 let precision = length frac in
538 -- guard (precision <= 255)
539 let mantissa = integer_of_digits 10 $ int <> frac in
541 (fromIntegral precision)
544 { style_amount_fractioning=fr
545 , style_amount_grouping_integral=gi
546 , style_amount_grouping_fractional=gf
550 [ g_qty '_' ',' '_' <* (pure () `minus` choice (char <$> ",._"))
551 , g_qty '_' '.' '_' <* (pure () `minus` choice (char <$> ",._"))
552 , g_qty ',' '.' '_' <* (pure () `minus` choice (char <$> ",._"))
553 , g_qty '.' ',' '_' <* (pure () `minus` choice (char <$> ",._"))
556 :: Char -- ^ Integral grouping separator.
557 -> Char -- ^ Fractioning separator.
558 -> Char -- ^ Fractional grouping separator.
560 ( [String] -- integral
561 , [String] -- fractional
562 , S.Maybe Style_Amount_Fractioning -- fractioning
563 , S.Maybe Style_Amount_Grouping -- grouping_integral
564 , S.Maybe Style_Amount_Grouping -- grouping_fractional
566 g_qty int_group_sep frac_sep frac_group_sep =
573 , grouping_of_digits int_group_sep int
576 Just (fractioning, frac) ->
580 , grouping_of_digits int_group_sep int
581 , grouping_of_digits frac_group_sep $ L.reverse frac
585 <*> option [] (many $ try $ char int_group_sep *> some g_09))
586 <*> option Nothing (Just <$> ((,)
590 <*> option [] (many $ try $ char frac_group_sep *> some g_09))))
592 grouping_of_digits :: Char -> [String] -> S.Maybe Style_Amount_Grouping
593 grouping_of_digits group_sep digits =
598 Style_Amount_Grouping group_sep $
599 canonicalize_grouping $
601 canonicalize_grouping :: [Int] -> [Int]
602 canonicalize_grouping groups =
603 foldl' -- NOTE: remove duplicates at beginning and reverse.
604 (\acc l0 -> case acc of
605 l1:_ -> if l0 == l1 then acc else l0:acc
607 case groups of -- NOTE: keep only longer at beginning.
608 l0:l1:t -> if l0 > l1 then groups else l1:t
611 g_amount :: CF g (Styled_Amount Amount)
612 g_amount = rule "Amount" $ g_amount_minus <+> g_amount_plus
613 g_amount_minus :: CF g (Styled_Amount Amount)
617 <$> ((,) <$> g_unit <*> g_spaces)
622 <*> option ("", H.unit_empty)
623 (try $ flip (,) <$> g_spaces <*> g_unit) )
626 <$> ((,) <$> g_unit <*> g_spaces)
630 mk_amount :: LR -> (Unit, Text) -> (Quantity, Style_Amount) -> (Style_Amount, Amount)
631 mk_amount side (unit, sp) (qty, sty) =
635 { style_amount_unit_side = S.Just side
636 , style_amount_unit_spaced = S.Just $ not $ Text.null sp
639 { amount_quantity = negate qty
643 g_amount_plus :: CF g (Styled_Amount Amount)
647 <$> ((,) <$> g_unit <*> g_spaces)
652 <*> option ("", H.unit_empty)
653 (try $ flip (,) <$> g_spaces <*> g_unit) )
656 <$> ((,) <$> g_unit <*> g_spaces)
657 <* optional (char '+')
662 <*> option ("", H.unit_empty)
663 (try $ flip (,) <$> g_spaces <*> g_unit)
665 mk_amount :: LR -> (Unit, Text) -> (Quantity, Style_Amount) -> (Style_Amount, Amount)
666 mk_amount side (unit, sp) (qty, sty) =
670 { style_amount_unit_side = S.Just side
671 , style_amount_unit_spaced = S.Just $ not $ Text.null sp
674 { amount_quantity = qty
679 -- * Class 'Gram_Posting'
685 , G.Gram_Reader SourcePos g
686 , G.Gram_State (S.Maybe Unit) g
687 , G.Gram_State Chart g
688 , G.Gram_State Style_Amounts g
690 ) => Gram_Posting g where
692 G.Gram_Source src g =>
693 CF g (S.Either (At src Error_Posting) [Posting])
697 many (try $ g_spaces *> g_eol) *>
698 g_spaces1 *> g_posting
700 G.Gram_Source src g =>
701 CF g (S.Either (At src Error_Posting) Posting)
702 g_posting = rule "Posting" $
703 G.stateAfter $ G.getAfter $ G.askBefore $
706 posting_sourcepos ctx_unit
707 (Style_Amounts ctx_stys) -> do
708 let (posting_tags, posting_comments) = attrs
709 let (stys, posting_amounts) =
711 Nothing -> (Style_Amounts ctx_stys, mempty)
715 Map.insertWith (flip (<>))
719 case amount_unit amt of
720 u | u == H.unit_empty -> S.fromMaybe u ctx_unit
727 (posting_account, posting_account_ref) <- lr_acct
731 , posting_account_ref
738 <$> g_posting_account
739 <*> optional (try $ g_spaces1 *> g_amount)
742 G.Gram_Source src g =>
743 CF g (S.Either (At src Error_Posting)
744 (Account, S.Maybe (S.Pair Tag_Path (S.Maybe Account))))
745 g_posting_account = rule "Posting_Account" $
746 (S.Right . (, S.Nothing) <$> g_account) <+>
748 <$> (G.source $ G.getAfter $ expand_tag_path <$> g_account_tag_path)
749 <*> option S.Nothing (S.Just <$> g_account))
751 mk_posting_account path acct =
754 (S.maybe a (a <>) acct)
755 (S.Just (p S.:!: acct)) )
757 expand_tag_path tag chart src =
758 case Map.lookup tag $ chart_tags chart of
759 Just accts | Map.size accts > 0 ->
760 if Map.size accts == 1
762 let acct = fst $ Map.elemAt 0 accts in
764 else S.Left $ At src $ Error_Posting_Account_Ref_multiple tag accts
765 _ -> S.Left $ At src $ Error_Posting_Account_Ref_unknown tag
766 g_posting_tag :: CF g Posting_Tag
767 g_posting_tag = rule "Posting_Tag" $ Posting_Tag <$> g_tag
768 g_posting_attrs :: CF g (Posting_Tags, [Comment])
770 foldr ($) mempty . Compose
772 many (try $ g_spaces *> g_eol *> g_spaces1) *>
776 [ add_tag <$> g_posting_tag
777 , add_comment <$> g_comment
780 add_tag (Posting_Tag (Tag (Tag_Path p) v)) =
781 \(Posting_Tags (Tags tags), cmts) ->
782 ( Posting_Tags (Tags (TreeMap.insert (<>) p [v] tags))
788 -- * Class 'Gram_Transaction'
797 , G.Gram_State Section g
798 ) => Gram_Transaction g where
800 G.Gram_Source src g =>
801 CF g (S.Either (At src Error_Transaction) Transaction)
802 g_transaction = rule "Transaction" $
803 G.stateAfter $ (update_year <$>) $
804 G.source $ G.askBefore $
808 , transaction_comments )
810 transaction_sourcepos src -> do
811 date <- fmap Error_Transaction_Date `S.left` lr_date
812 posts <- fmap Error_Transaction_Posting `S.left` lr_posts
813 let postsByAcct = postings_by_account posts
817 , transaction_comments
818 , transaction_dates = NonNull.ncons date []
819 , transaction_wording
820 , transaction_postings = Postings postsByAcct
821 , transaction_sourcepos
823 case H.equilibrium postsByAcct of
824 (_, Left ko) -> S.Left $ At src $ Error_Transaction_not_equilibrated txn ko
825 (_bal, Right ok) -> S.Right txn{transaction_postings = Postings ok}
830 <*> g_transaction_attrs
833 update_year lr_txn y =
836 S.Right txn -> Year $ H.date_year $ NonNull.head $ transaction_dates txn
838 g_wording :: CF g Wording
839 g_wording = rule "Wording" $
840 Wording . Text.concat
845 <$> some (g_char `minus` char char_tag_prefix)))
846 g_transaction_tag :: CF g Transaction_Tag
847 g_transaction_tag = rule "Transaction_Tag" $ Transaction_Tag <$> g_tag
848 g_transaction_attrs :: CF g (Transaction_Tags, [Comment])
849 g_transaction_attrs =
853 [ add_tag <$ many (try $ g_spaces *> g_eol *> g_spaces1) <*> g_transaction_tag
854 , add_comment <$ many (try $ g_spaces *> g_eol *> g_spaces) <*> g_comment
857 add_tag (Transaction_Tag (Tag (Tag_Path p) v)) =
858 \(Transaction_Tags (Tags tags), cmts) ->
859 ( Transaction_Tags (Tags (TreeMap.insert (<>) p [v] tags))
865 -- * Class 'Gram_File'
872 ) => Gram_File g where
873 g_pathfile :: CF g PathFile
874 g_pathfile = rule "PathFile" $
876 <$> some (try $ (:) <$> char '/' <*> some (g_char `minus` char '/'))
878 -- * Class 'Gram_Chart'
883 , G.Gram_State Chart g
884 , G.Gram_State Section g
886 ) => Gram_Chart g where
888 G.Gram_Source src g =>
889 CF g (S.Either (At src (Error_Compta src)) Chart)
890 g_chart_entry = rule "Chart" $
892 let (tags, tags2, _comments) = attrs in
895 { chart_accounts = TreeMap.singleton (H.get acct) tags
896 , chart_tags = Map.singleton acct () <$ tags2
901 g_chart_attrs :: CF g (Account_Tags, Map Tag_Path (), [Comment])
905 many (try $ g_spaces *> g_eol) *>
907 [ add_tag <$ g_spaces1 <*> g_account_tag
908 , add_comment <$ g_spaces <*> g_comment
911 add_tag (Account_Tag (Tag (Tag_Path p) v)) =
912 \(Account_Tags (Tags tags), tags2, cmts) ->
913 ( Account_Tags (Tags (TreeMap.insert (<>) p [v] tags))
914 , Map.insert (Tag_Path p) () tags2
917 \(tags, tags2, cmts) ->
918 (tags, tags2, c:cmts)
920 class Gram_Input g where
921 g_input :: g (Text -> a) -> g a
922 deriving instance Gram_Input g => Gram_Input (CF g)
924 -- * Class 'Gram_Term_Def'
926 ( G.Gram_Source src g
927 , Sym.Gram_Term src ss g
928 , G.Gram_State (Sym.Name2Type src) g
929 , G.SourceInj (Sym.TypeVT src) src
930 , G.SourceInj (Sym.KindK src) src
931 , G.SourceInj (Sym.AST_Type src) src
932 ) => Gram_Term_Def src ss g where
933 g_term_def :: CF g (S.Either (At src (Sym.NameTe, Sym.Error_Term src)) (Sym.NameTe, Sym.TermVT src ss '[]))
934 g_term_def = rule "TermDef" $
935 G.source $ G.getAfter $
936 (\n args v n2t src ->
938 Sym.readTerm n2t Sym.CtxTyZ $
939 foldr (\(x, ty_x) -> G.BinTree0 . Sym.Token_Term_Abst src x ty_x) v args in
941 Right t -> S.Right (n, t)
942 Left err -> S.Left $ At src (n, err)
945 <*> many Sym.g_term_abst_decl
949 -- * Class 'Gram_Compta'
951 ( G.Gram_Source src g
960 , Gram_Term_Def src ss g
961 , G.Gram_Reader (S.Either Exn.IOException CanonFile) g
962 , G.Gram_State (Context_Read src j) g
963 , G.Gram_State (Sym.Modules src ss) g
964 , G.Gram_State (Journal j) g
965 , G.Gram_State (Journals j) g
966 , G.Gram_State Terms g
970 ) => Gram_Compta ss src j g where
972 :: (Transaction -> j -> j)
973 -> CF g (S.Either [At src (Error_Compta src)]
974 (CanonFile, Journal j))
975 g_compta consTxn = rule "Journal" $
976 G.stateAfter $ G.askBefore $
978 <$> (G.stateAfter $ G.source $ G.askBefore $ G.askBefore $ pure init_journal)
980 [ G.stateAfter $ mk_include <$> g_include @ss consTxn
981 -- NOTE: g_include must be the first choice
982 -- in order to have Megaparsec reporting the errors
983 -- of the included journal.
984 , G.stateAfter $ mk_transaction
985 <$> g_compta_section Section_Transactions g_transaction
986 , G.stateAfter $ mk_chart
987 <$> g_compta_section Section_Chart g_chart_entry
988 , G.stateBefore $ G.stateBefore $ g_input $ G.source $ mk_term
989 <$> g_compta_section Section_Terms g_term_def
990 , ([], []) <$ try (g_spaces <* g_eol)
994 (SourcePos jf _ _) lr_cf src
996 { context_read_journals = Journals js
997 , context_read_journal = jnls
998 , context_read_canonfiles = cfs
999 }::Context_Read src j) =
1001 S.Left e -> (ctx, S.Left $ At src $ Error_Compta_Read (PathFile jf) e)
1003 let jnl = journal{journal_file=PathFile jf} in
1006 { context_read_journals = Journals $ Map.insert cf jnl js
1007 , context_read_journal = jnl <| jnls
1008 , context_read_canonfiles = cf <| cfs
1010 mk_journal err errs_warns
1013 { context_read_journals = Journals js
1014 , context_read_journal = jnl :| jnls
1015 , context_read_canonfiles = cf :| cfs
1016 , context_read_warnings = warnings
1017 }::Context_Read src j) =
1018 let (errs, warns) = L.unzip errs_warns in
1019 case S.either pure (const []) err <> L.concat errs of
1021 let jnl' = jnl{journal_file=PathFile jf} in
1022 (,S.Right (cf, jnl'))
1024 { context_read_journals = Journals $ Map.insert cf jnl' js
1025 , context_read_journal = NonEmpty.fromList jnls
1026 , context_read_canonfiles = NonEmpty.fromList cfs
1027 , context_read_warnings = warnings <> L.concat warns
1029 es -> (ctx, S.Left es)
1030 mk_transaction lr_txn jnl@Journal{journal_content=j} =
1032 S.Left err -> (jnl, ([err], []))
1033 S.Right txn -> (jnl{ journal_content = txn `consTxn` j }, ([], []))
1034 mk_include lr_inc (jnl::Journal j) =
1036 S.Left errs -> (jnl, (errs, []))
1037 S.Right (cf, _inc) -> (jnl{ journal_includes = journal_includes jnl <> [cf] }, ([], []))
1038 mk_chart lr_ch chart =
1040 S.Left err -> (chart, ([err], []))
1041 S.Right ch -> (chart <> ch, ([], []))
1042 mk_term lr_te src body mods =
1044 S.Left err -> (mods, (, ([err], [])))
1045 S.Right (n, te) -> (ins_term n te mods, \ts -> (ins_body n body ts, ([], warn_redef n ts)))
1047 ins_term :: Sym.NameTe -> Sym.TermVT src ss '[] -> Sym.Modules src ss -> Sym.Modules src ss
1048 ins_term n t = Sym.insertTermVT ([] `Sym.Mod` t) n (Sym.Fixity2 Sym.infixN5)
1049 ins_body :: Sym.NameTe -> Text -> Terms -> Terms
1050 ins_body n = Map.insert ([] `Sym.Mod` n)
1051 warn_redef :: Sym.NameTe -> Terms -> [At src Warning_Compta]
1053 case Map.lookup ([] `Sym.Mod` n) ts of
1054 Just{} -> [At src $ Warning_Compta_Term_redefined n]
1057 :: (Transaction -> j -> j)
1058 -> CF g (S.Either [At src (Error_Compta src)]
1059 (CanonFile, Journal j))
1060 g_include consTxn = rule "Include" $
1061 g_read g_path (g_compta @ss consTxn <* G.eoi)
1064 G.stateAfter $ G.source $ check_path
1065 <$> (g_canonfile $ G.askBefore $ fmap mk_path $
1066 (\d (PathFile p) -> PathFile $ d:p)
1067 <$> char '.' <*> g_pathfile)
1068 mk_path (PathFile fp) (SourcePos fp_old _ _) =
1070 FilePath.normalise $
1071 FilePath.takeDirectory fp_old </> fp
1072 check_path (fp, lr_cf) src
1074 { context_read_journals = Journals js
1075 , context_read_canonfiles = cfs
1076 , context_read_warnings = warns
1077 }::Context_Read src j) =
1079 Left e -> (ctx, S.Left $ Error_Compta_Read fp e)
1081 if cf `Map.member` js
1084 then (ctx, S.Left $ Error_Compta_Include_loop cf)
1087 if isJust $ (`L.find` warns) $ \case
1088 At{unAt=Warning_Compta_Include_multiple cf'} -> cf' `elem` cf<|cfs
1092 { context_read_warnings =
1093 At src (Warning_Compta_Include_multiple cf) : warns }
1094 else (ctx, S.Right fp)
1098 -- | Return the 'Integer' obtained by multiplying the given digits
1099 -- with the power of the given base respective to their rank.
1101 :: Integer -- ^ Base.
1102 -> String -- ^ Digits (MUST be recognised by 'Char.digitToInt').
1104 integer_of_digits base =
1105 foldl' (\x d -> base*x + toInteger (Char.digitToInt d)) 0
1107 -- | Return the 'Int' obtained by multiplying the given digits
1108 -- with the power of the given base respective to their rank.
1111 -> String -- ^ Digits (MUST be recognised by 'Char.digitToInt').
1113 int_of_digits base =
1114 foldl' (\x d -> base*x + Char.digitToInt d) 0
1117 char_account_sep :: Char
1118 char_account_sep = '/'
1119 char_account_tag_prefix :: Char
1120 char_account_tag_prefix = '~'
1121 char_ymd_sep :: Char
1123 char_tod_sep :: Char
1125 char_comment_prefix :: Char
1126 char_comment_prefix = ';'
1127 char_tag_prefix :: Char
1128 char_tag_prefix = '#'
1129 char_tag_sep :: Char
1131 char_tag_data_prefix :: Char
1132 char_tag_data_prefix = '='
1133 char_transaction_date_sep :: Char
1134 char_transaction_date_sep = '='
1137 type Env src ss = Map Sym.NameTe (Sym.TermVT src ss '[])
1143 | Section_Transactions
1144 deriving (Eq, Ord, Show)
1148 Sym.ErrorInj err (Error_Compta src) =>
1149 G.Gram_State Section g =>
1150 G.Gram_Source src g =>
1153 g (S.Either (At src err) a) ->
1154 g (S.Either (At src (Error_Compta src)) a)
1155 g_compta_section sec g =
1156 G.stateBefore $ G.source $
1160 then fmap Sym.errorInj `S.left` a
1161 else S.Left $ At src $ Error_Compta_Section sec_curr sec
1165 newtype Year = Year (H.Date_Year Date)
1169 -- * Type 'Error_Date'
1171 = Error_Date_Day_invalid (Integer, Int, Int)
1172 | Error_Date_TimeOfDay_invalid (Int, Int, Int)
1173 | Error_Date_TimeZone_unknown Text
1176 -- * Type 'Error_Posting'
1178 = Error_Posting_Account_Ref_unknown Tag_Path
1179 | Error_Posting_Account_Ref_multiple Tag_Path (Map Account ())
1180 | Error_Postings_not_equilibrated Postings
1183 -- * Type 'Error_Transaction'
1184 data Error_Transaction
1185 = Error_Transaction_Date Error_Date
1186 | Error_Transaction_Posting Error_Posting
1187 | Error_Transaction_not_equilibrated
1190 , H.SumByUnit (NonNull [Account_Section]) (H.Polarized Quantity)
1194 -- * Type 'Error_Chart'
1199 -- * Type 'Error_Compta'
1200 data Error_Compta src
1201 = Error_Compta_Transaction Error_Transaction
1202 | Error_Compta_Read PathFile Exn.IOException
1203 | Error_Compta_Include_loop CanonFile
1204 | Error_Compta_Chart Error_Chart
1205 | Error_Compta_Section Section Section
1206 | Error_Compta_Term Sym.NameTe (Sym.Error_Term src)
1209 instance Sym.ErrorInj (Sym.NameTe, Sym.Error_Term src) (Error_Compta src) where
1210 errorInj (n, t) = Error_Compta_Term n t
1211 instance Sym.ErrorInj Error_Transaction (Error_Compta src) where
1212 errorInj = Error_Compta_Transaction
1213 instance Sym.ErrorInj (Error_Compta src) (Error_Compta src) where
1216 -- * Type 'Warning_Compta'
1218 = Warning_Compta_Include_multiple CanonFile
1219 | Warning_Compta_Term_redefined Sym.NameTe
1223 nonEmpty :: NonNull [a] -> NonEmpty a
1224 nonEmpty n = x :| xs where (x, xs) = NonNull.splitFirst n
1225 nonNull :: NonEmpty a -> NonNull [a]
1226 nonNull n = NonNull.ncons x xs where x :| xs = n