Skip to content

Commit c7450f6

Browse files
committed
lib: journal: Add scalar multiplication and division
1 parent 11830eb commit c7450f6

File tree

2 files changed

+198
-4
lines changed

2 files changed

+198
-4
lines changed

hledger-lib/Hledger/Read/Common.hs

+31-2
Original file line numberDiff line numberDiff line change
@@ -690,11 +690,12 @@ amountp' s =
690690
-- joined as an arithmetic expression.
691691
mamountp :: Bool -> JournalParser m MixedAmount
692692
mamountp requireOp = label "mixed amount" $ do
693+
pos <- getOffset
693694
opc <- ( if requireOp
694695
then id
695696
else option '+'
696697
) $ do
697-
c <- satisfy (`elem` ("+-" :: String))
698+
c <- oneOf ("+-" :: String)
698699
lift (skipMany spacenonewline)
699700
pure c
700701
paren <- option False $ try $ do
@@ -710,13 +711,40 @@ mamountp requireOp = label "mixed amount" $ do
710711
else do
711712
inner <- amountp
712713
pure $ Mixed [inner]
714+
mult <- multiplierp pos
713715
tail <- option nullmixedamt $ try $ do
714716
lift (skipMany spacenonewline)
715717
mamountp True
716718
let op = case opc of
717719
'-' -> negate
718720
_ -> id
719-
return $ op amount + tail
721+
return $ multiplyMixedAmount mult (op amount) + tail
722+
723+
multiplierp :: Int -> JournalParser m Quantity
724+
multiplierp startOffset = do
725+
lift (skipMany spacenonewline)
726+
c <- optional $ try $ oneOf ("*/" :: String)
727+
case c of
728+
Nothing -> return 1
729+
Just c' -> do
730+
lift (skipMany spacenonewline)
731+
(m, _, _, _) <- lift $ numberp Nothing
732+
endOffset <- getOffset
733+
f <- if c' == '/'
734+
then if m == 0
735+
then dividebyzeroerr startOffset endOffset
736+
-- The "Decimal" docs recommend against using '(/)', but the
737+
-- alternate interface provided might be more cumbersome than
738+
-- necessary for this circumstance, as it would require
739+
-- keeping track of multiple potential results. Maybe revisit
740+
-- this as part of a larger patch focused on fair rounding?
741+
else return (/ m)
742+
else return (* m)
743+
ms <- multiplierp startOffset
744+
return $ f ms
745+
746+
dividebyzeroerr :: Int -> Int -> JournalParser m a
747+
dividebyzeroerr startOffset endOffset = customFailure $ parseErrorAtRegion startOffset endOffset "attempted division by 0"
720748

721749
-- | Parse a mixed amount from a string, or get an error.
722750
mamountp' :: String -> MixedAmount
@@ -1376,6 +1404,7 @@ tests_Common = tests "Common" [
13761404

13771405
,tests "mamountp" [
13781406
test "basic" $ expectParseEq (mamountp False) "$47.18" $ Mixed [usd 47.18]
1407+
,test "operations" $ expectParseEq (mamountp False) "$6.00 + $5.00 - $4.00 / 2 * 3" $ Mixed [usd 5.00]
13791408
,test "multiple commodities" $ expectParseEq (mamountp False) "$47.18+€20,59" $ Mixed [
13801409
amount{
13811410
acommodity="$"

tests/journal/amount-expressions.test

+167-2
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,27 @@ hledger -f - print --auto
135135
>>>2
136136
>>>=0
137137

138-
# 9. Standard postings may not be headed by multipliers
138+
# 9. Modifiers operate on all commodities in a posting
139+
hledger -f - print --auto
140+
<<<
141+
= a
142+
(c) *-1 + $8
143+
144+
2018-01-01
145+
a $5 - 5 CAD
146+
b
147+
>>>
148+
2018/01/01
149+
a $5
150+
a -5 CAD
151+
(c) $3
152+
(c) 5 CAD
153+
b
154+
155+
>>>2
156+
>>>=0
157+
158+
# 10. Standard postings may not be headed by multipliers
139159
hledger -f - print
140160
<<<
141161
2018-01-01
@@ -145,7 +165,7 @@ hledger -f - print
145165
>>>2 /unexpected '*'/
146166
>>>=1
147167

148-
# 10. Auto-postings require an operator between multiplier and expression
168+
# 11. Auto-postings require an operator between multiplier and expression
149169
# The error message could be a bit more helpful, but at least it mentions
150170
# expecting a mixed amount
151171
hledger -f - print --auto
@@ -161,3 +181,148 @@ hledger -f - print --auto
161181
>>>2 /unexpected '8'/
162182
>>>=1
163183

184+
# 12. Multiplication by plain values works as expected
185+
hledger -f - print
186+
<<<
187+
2018-01-01
188+
a $1 * 2
189+
b -$2 * 0.5
190+
c $2 * -1
191+
d -$1 * -1
192+
>>>
193+
2018/01/01
194+
a $2
195+
b $-1
196+
c $-2
197+
d $1
198+
199+
>>>2
200+
>>>=0
201+
202+
# 13. ... as does division
203+
hledger -f - print
204+
<<<
205+
2018-01-01
206+
a $2.00 / 2
207+
b -$1.00 / 0.5
208+
c $2.00 / -1
209+
d -$3.00 / -1
210+
>>>
211+
2018/01/01
212+
a $1.00
213+
b $-2.00
214+
c $-2.00
215+
d $3.00
216+
217+
>>>2
218+
>>>=0
219+
220+
# 14. Multiplication disallows commodities before the multiplier
221+
hledger -f - print
222+
<<<
223+
2018-01-01
224+
a $1 * $2
225+
c
226+
>>>
227+
>>>2 /unexpected '\$'/
228+
>>>=1
229+
230+
# 15. ... and after
231+
hledger -f - print
232+
<<<
233+
2018-01-01
234+
a $1 * 2 CAD
235+
c
236+
>>>
237+
>>>2 /unexpected 'C'/
238+
>>>=1
239+
240+
# 16. Division prevents divide-by-zero errors
241+
hledger -f - print
242+
<<<
243+
2018-01-01
244+
a $1 / 0
245+
b
246+
>>>
247+
>>>2 /division by 0/
248+
>>>=1
249+
250+
# 17. ... but multiplication just simplifies them
251+
hledger -f - print
252+
<<<
253+
2018-01-01
254+
a $1 * 0
255+
b
256+
>>>
257+
2018/01/01
258+
a 0
259+
b
260+
261+
>>>2
262+
>>>=0
263+
264+
# 18. Multiplication and division rounds values according to the multiplicand precision
265+
hledger -f - print
266+
<<<
267+
2018-01-01
268+
a $3 * 0.5
269+
b $1 / 3
270+
c
271+
>>>
272+
2018/01/01
273+
a $2
274+
b 0
275+
c
276+
277+
>>>2
278+
>>>=0
279+
280+
# 19. Commodity directives affect multiplication and division rounding
281+
hledger -f - print
282+
<<<
283+
commodity $1,000.00
284+
285+
2018-01-01
286+
a $3 * 0.5
287+
b $1 / 3
288+
c
289+
>>>
290+
2018/01/01
291+
a $1.50
292+
b $0.33
293+
c
294+
295+
>>>2
296+
>>>=0
297+
298+
# 20. Expressions respect order of operations
299+
hledger -f - print
300+
<<<
301+
2018-01-01
302+
a $1 + $2 * 3 - $4
303+
b ($1 + $2) * 3 - $4
304+
c
305+
>>>
306+
2018/01/01
307+
a $3
308+
b $5
309+
c
310+
311+
>>>2
312+
>>>=0
313+
314+
# 21. Multiplication and division work over multiple commodities
315+
hledger -f - print
316+
<<<
317+
2018-01-01
318+
a ($1 + 2 CAD) * 3
319+
b
320+
>>>
321+
2018/01/01
322+
a $3
323+
a 6 CAD
324+
b
325+
326+
>>>2
327+
>>>=0
328+

0 commit comments

Comments
 (0)