mirror of
https://github.com/simonmichael/hledger.git
synced 2024-09-19 10:17:35 +03:00
dev: lib: clarify some amount parsers; describe Ledger lot notation
amountpwithmultiplier -> amountp' amountpnolotpricesp -> amountnobasisp amountwithoutpricep -> simpleamountp priceamountp -> costp
This commit is contained in:
parent
04d5813a41
commit
aa54c3273a
@ -76,9 +76,9 @@ module Hledger.Read.Common (
|
|||||||
-- ** amounts
|
-- ** amounts
|
||||||
spaceandamountormissingp,
|
spaceandamountormissingp,
|
||||||
amountp,
|
amountp,
|
||||||
amountpwithmultiplier,
|
amountp',
|
||||||
commoditysymbolp,
|
commoditysymbolp,
|
||||||
priceamountp,
|
costp,
|
||||||
balanceassertionp,
|
balanceassertionp,
|
||||||
lotpricep,
|
lotpricep,
|
||||||
numberp,
|
numberp,
|
||||||
@ -675,21 +675,42 @@ singlespacep = spacenonewline *> notFollowedBy spacenonewline
|
|||||||
|
|
||||||
--- *** amounts
|
--- *** amounts
|
||||||
|
|
||||||
-- | Parse whitespace then an amount, with an optional left or right
|
-- | Parse whitespace then an amount, or return the special "missing" marker amount.
|
||||||
-- currency symbol and optional price, or return the special
|
|
||||||
-- "missing" marker amount.
|
|
||||||
spaceandamountormissingp :: JournalParser m MixedAmount
|
spaceandamountormissingp :: JournalParser m MixedAmount
|
||||||
spaceandamountormissingp =
|
spaceandamountormissingp =
|
||||||
option missingmixedamt $ try $ do
|
option missingmixedamt $ try $ do
|
||||||
lift $ skipNonNewlineSpaces1
|
lift $ skipNonNewlineSpaces1
|
||||||
mixedAmount <$> amountp
|
mixedAmount <$> amountp
|
||||||
|
|
||||||
-- | Parse a single-commodity amount, with optional symbol on the left
|
-- | Parse a single-commodity amount, applying the default commodity if there is no commodity symbol;
|
||||||
-- or right, followed by, in any order: an optional transaction price,
|
-- optionally followed by, in any order:
|
||||||
-- an optional ledger-style lot price, and/or an optional ledger-style
|
-- a Ledger-style cost and/or one or more parts of a Ledger-style cost basis:
|
||||||
-- lot date. A lot price and lot date will be ignored.
|
-- lot cost, lot date, and/or lot note (we loosely call this triple the lot's cost basis).
|
||||||
|
-- The cost basis makes it a lot rather than just an amount. The cost basis info is discarded for now.
|
||||||
|
-- The main amount's sign is significant; here are the possibilities and their interpretation:
|
||||||
|
-- @
|
||||||
--
|
--
|
||||||
-- To parse the amount's quantity (number) we need to know which character
|
-- AMT -- acquiring an amount
|
||||||
|
-- AMT COST -- acquiring an amount at some cost
|
||||||
|
-- AMT COST COSTBASIS -- acquiring a lot at some cost, saving its cost basis
|
||||||
|
-- AMT COSTBASIS COST -- like the above
|
||||||
|
-- AMT COSTBASIS -- like the above with cost same as the cost basis
|
||||||
|
--
|
||||||
|
-- -AMT -- releasing an amount
|
||||||
|
-- -AMT SELLPRICE -- releasing an amount at some selling price
|
||||||
|
-- -AMT SELLPRICE COSTBASISSEL -- releasing a lot at some selling price, selecting it by its cost basis
|
||||||
|
-- -AMT COSTBASISSEL SELLPRICE -- like the above
|
||||||
|
-- -AMT COSTBASISSEL -- like the above with selling price same as the selected lot's cost basis amount
|
||||||
|
--
|
||||||
|
-- COST/SELLPRICE can be @ UNITAMT, @@ TOTALAMT, (@) UNITAMT, or (@@) TOTALAMT. The () are ignored.
|
||||||
|
-- COSTBASIS is one or more of {LOTCOST}, [LOTDATE], (LOTNOTE), in any order, with LOTCOST defaulting to COST.
|
||||||
|
-- COSTBASISSEL is one or more of {LOTCOST}, [LOTDATE], (LOTNOTE), in any order.
|
||||||
|
-- {LOTCOST} can be {UNITAMT}, {{TOTALAMT}}, {=UNITAMT}, or {{=TOTALAMT}}. The = is ignored.
|
||||||
|
-- Rule of thumb: curly braces, parentheses, and/or square brackets in an amount means a Ledger-style cost basis is involved.
|
||||||
|
--
|
||||||
|
-- @
|
||||||
|
--
|
||||||
|
-- To parse an amount's numeric quantity we need to know which character
|
||||||
-- represents a decimal mark. We find it in one of three ways:
|
-- represents a decimal mark. We find it in one of three ways:
|
||||||
--
|
--
|
||||||
-- 1. If a decimal mark has been set explicitly in the journal parse state,
|
-- 1. If a decimal mark has been set explicitly in the journal parse state,
|
||||||
@ -700,38 +721,45 @@ spaceandamountormissingp =
|
|||||||
--
|
--
|
||||||
-- 3. Otherwise we will parse any valid decimal mark appearing in the
|
-- 3. Otherwise we will parse any valid decimal mark appearing in the
|
||||||
-- number, as long as the number appears well formed.
|
-- number, as long as the number appears well formed.
|
||||||
|
-- (This means we handle files with any supported decimal mark without configuration,
|
||||||
|
-- but it also allows different decimal marks in different amounts,
|
||||||
|
-- which is a bit too loose. There's an open issue.)
|
||||||
--
|
--
|
||||||
-- Note 3 is the default zero-config case; it means we automatically handle
|
|
||||||
-- files with any supported decimal mark, but it also allows different decimal marks
|
|
||||||
-- in different amounts, which is a bit too loose. There's an open issue.
|
|
||||||
amountp :: JournalParser m Amount
|
amountp :: JournalParser m Amount
|
||||||
amountp = amountpwithmultiplier False
|
amountp = amountp' False
|
||||||
|
|
||||||
amountpwithmultiplier :: Bool -> JournalParser m Amount
|
-- An amount with optional cost and/or cost basis, as described above.
|
||||||
amountpwithmultiplier mult = label "amount" $ do
|
-- A flag indicates whether we are parsing a multiplier amount;
|
||||||
|
-- if not, a commodity-less amount will have the default commodity applied to it.
|
||||||
|
amountp' :: Bool -> JournalParser m Amount
|
||||||
|
amountp' mult = label "amount" $ do
|
||||||
let spaces = lift $ skipNonNewlineSpaces
|
let spaces = lift $ skipNonNewlineSpaces
|
||||||
amt <- amountwithoutpricep mult <* spaces
|
amt <- simpleamountp mult <* spaces
|
||||||
(mprice, _elotprice, _elotdate) <- runPermutation $
|
(mprice, _elotprice, _elotdate) <- runPermutation $
|
||||||
(,,) <$> toPermutationWithDefault Nothing (Just <$> priceamountp amt <* spaces)
|
(,,) <$> toPermutationWithDefault Nothing (Just <$> costp amt <* spaces)
|
||||||
<*> toPermutationWithDefault Nothing (Just <$> lotpricep <* spaces)
|
<*> toPermutationWithDefault Nothing (Just <$> lotpricep <* spaces)
|
||||||
<*> toPermutationWithDefault Nothing (Just <$> lotdatep <* spaces)
|
<*> toPermutationWithDefault Nothing (Just <$> lotdatep <* spaces)
|
||||||
pure $ amt { aprice = mprice }
|
pure $ amt { aprice = mprice }
|
||||||
|
|
||||||
amountpnolotpricesp :: JournalParser m Amount
|
-- An amount with optional cost, but no cost basis.
|
||||||
amountpnolotpricesp = label "amount" $ do
|
amountnobasisp :: JournalParser m Amount
|
||||||
|
amountnobasisp = label "amount" $ do
|
||||||
let spaces = lift $ skipNonNewlineSpaces
|
let spaces = lift $ skipNonNewlineSpaces
|
||||||
amt <- amountwithoutpricep False
|
amt <- simpleamountp False
|
||||||
spaces
|
spaces
|
||||||
mprice <- optional $ priceamountp amt <* spaces
|
mprice <- optional $ costp amt <* spaces
|
||||||
pure $ amt { aprice = mprice }
|
pure $ amt { aprice = mprice }
|
||||||
|
|
||||||
amountwithoutpricep :: Bool -> JournalParser m Amount
|
-- An amount with no cost or cost basis.
|
||||||
amountwithoutpricep mult = do
|
-- A flag indicates whether we are parsing a multiplier amount;
|
||||||
|
-- if not, a commodity-less amount will have the default commodity applied to it.
|
||||||
|
simpleamountp :: Bool -> JournalParser m Amount
|
||||||
|
simpleamountp mult = do
|
||||||
sign <- lift signp
|
sign <- lift signp
|
||||||
leftsymbolamountp sign <|> rightornosymbolamountp sign
|
leftsymbolamountp sign <|> rightornosymbolamountp sign
|
||||||
|
|
||||||
where
|
where
|
||||||
|
-- An amount with commodity symbol on the left.
|
||||||
leftsymbolamountp :: (Decimal -> Decimal) -> JournalParser m Amount
|
leftsymbolamountp :: (Decimal -> Decimal) -> JournalParser m Amount
|
||||||
leftsymbolamountp sign = label "amount" $ do
|
leftsymbolamountp sign = label "amount" $ do
|
||||||
c <- lift commoditysymbolp
|
c <- lift commoditysymbolp
|
||||||
@ -750,6 +778,9 @@ amountwithoutpricep mult = do
|
|||||||
let s = amountstyle{ascommodityside=L, ascommodityspaced=commodityspaced, asprecision=prec, asdecimalpoint=mdec, asdigitgroups=mgrps}
|
let s = amountstyle{ascommodityside=L, ascommodityspaced=commodityspaced, asprecision=prec, asdecimalpoint=mdec, asdigitgroups=mgrps}
|
||||||
return nullamt{acommodity=c, aquantity=sign (sign2 q), astyle=s, aprice=Nothing}
|
return nullamt{acommodity=c, aquantity=sign (sign2 q), astyle=s, aprice=Nothing}
|
||||||
|
|
||||||
|
-- An amount with commodity symbol on the right or no commodity symbol.
|
||||||
|
-- A no-symbol amount will have the default commodity applied to it
|
||||||
|
-- unless we are parsing a multiplier amount (*AMT).
|
||||||
rightornosymbolamountp :: (Decimal -> Decimal) -> JournalParser m Amount
|
rightornosymbolamountp :: (Decimal -> Decimal) -> JournalParser m Amount
|
||||||
rightornosymbolamountp sign = label "amount" $ do
|
rightornosymbolamountp sign = label "amount" $ do
|
||||||
offBeforeNum <- getOffset
|
offBeforeNum <- getOffset
|
||||||
@ -837,8 +868,10 @@ quotedcommoditysymbolp =
|
|||||||
simplecommoditysymbolp :: TextParser m CommoditySymbol
|
simplecommoditysymbolp :: TextParser m CommoditySymbol
|
||||||
simplecommoditysymbolp = takeWhile1P Nothing (not . isNonsimpleCommodityChar)
|
simplecommoditysymbolp = takeWhile1P Nothing (not . isNonsimpleCommodityChar)
|
||||||
|
|
||||||
priceamountp :: Amount -> JournalParser m AmountPrice
|
-- | Ledger-style cost notation:
|
||||||
priceamountp baseAmt = label "transaction price" $ do
|
-- @ UNITAMT, @@ TOTALAMT, (@) UNITAMT, or (@@) TOTALAMT. The () are ignored.
|
||||||
|
costp :: Amount -> JournalParser m AmountPrice
|
||||||
|
costp baseAmt = label "transaction price" $ do
|
||||||
-- https://www.ledger-cli.org/3.0/doc/ledger3.html#Virtual-posting-costs
|
-- https://www.ledger-cli.org/3.0/doc/ledger3.html#Virtual-posting-costs
|
||||||
parenthesised <- option False $ char '(' >> pure True
|
parenthesised <- option False $ char '(' >> pure True
|
||||||
char '@'
|
char '@'
|
||||||
@ -846,7 +879,7 @@ priceamountp baseAmt = label "transaction price" $ do
|
|||||||
when parenthesised $ void $ char ')'
|
when parenthesised $ void $ char ')'
|
||||||
|
|
||||||
lift skipNonNewlineSpaces
|
lift skipNonNewlineSpaces
|
||||||
priceAmount <- amountwithoutpricep False -- <?> "unpriced amount (specifying a price)"
|
priceAmount <- simpleamountp False -- <?> "unpriced amount (specifying a price)"
|
||||||
|
|
||||||
let amtsign' = signum $ aquantity baseAmt
|
let amtsign' = signum $ aquantity baseAmt
|
||||||
amtsign = if amtsign' == 0 then 1 else amtsign'
|
amtsign = if amtsign' == 0 then 1 else amtsign'
|
||||||
@ -863,9 +896,9 @@ balanceassertionp = do
|
|||||||
istotal <- fmap isJust $ optional $ try $ char '='
|
istotal <- fmap isJust $ optional $ try $ char '='
|
||||||
isinclusive <- fmap isJust $ optional $ try $ char '*'
|
isinclusive <- fmap isJust $ optional $ try $ char '*'
|
||||||
lift skipNonNewlineSpaces
|
lift skipNonNewlineSpaces
|
||||||
-- this amount can have a price; balance assertions ignore it,
|
-- this amount can have a cost, but not a cost basis.
|
||||||
-- but balance assignments will use it
|
-- balance assertions ignore it, but balance assignments will use it
|
||||||
a <- amountpnolotpricesp <?> "amount (for a balance assertion or assignment)"
|
a <- amountnobasisp <?> "amount (for a balance assertion or assignment)"
|
||||||
return BalanceAssertion
|
return BalanceAssertion
|
||||||
{ baamount = a
|
{ baamount = a
|
||||||
, batotal = istotal
|
, batotal = istotal
|
||||||
@ -883,7 +916,7 @@ lotpricep = label "ledger-style lot price" $ do
|
|||||||
doublebrace <- option False $ char '{' >> pure True
|
doublebrace <- option False $ char '{' >> pure True
|
||||||
_fixed <- fmap isJust $ optional $ lift skipNonNewlineSpaces >> char '='
|
_fixed <- fmap isJust $ optional $ lift skipNonNewlineSpaces >> char '='
|
||||||
lift skipNonNewlineSpaces
|
lift skipNonNewlineSpaces
|
||||||
_a <- amountwithoutpricep False
|
_a <- simpleamountp False
|
||||||
lift skipNonNewlineSpaces
|
lift skipNonNewlineSpaces
|
||||||
char '}'
|
char '}'
|
||||||
when (doublebrace) $ void $ char '}'
|
when (doublebrace) $ void $ char '}'
|
||||||
|
@ -821,7 +821,7 @@ postingphelper isPostingRule mTransactionYear = do
|
|||||||
let (ptype, account') = (accountNamePostingType account, textUnbracket account)
|
let (ptype, account') = (accountNamePostingType account, textUnbracket account)
|
||||||
lift skipNonNewlineSpaces
|
lift skipNonNewlineSpaces
|
||||||
mult <- if isPostingRule then multiplierp else pure False
|
mult <- if isPostingRule then multiplierp else pure False
|
||||||
amt <- optional $ amountpwithmultiplier mult
|
amt <- optional $ amountp' mult
|
||||||
lift skipNonNewlineSpaces
|
lift skipNonNewlineSpaces
|
||||||
massertion <- optional balanceassertionp
|
massertion <- optional balanceassertionp
|
||||||
lift skipNonNewlineSpaces
|
lift skipNonNewlineSpaces
|
||||||
|
Loading…
Reference in New Issue
Block a user