5 files changed,
46 insertions(+),
23 deletions(-)
Author:
Oleksandr Smirnov
olexsmir@gmail.com
Committed at:
2026-06-22 18:00:13 +0300
Authored at:
2026-06-16 23:05:03 +0300
Change ID:
uozptzskqwsvzqwnvymwqyxrknrmlvwp
Parent:
ecc31e9
M
journal/lexer/lexer.go
··· 576 576 } 577 577 578 578 if unicode.IsLetter(l.ch) { 579 - for unicode.IsLetter(l.ch) || unicode.IsDigit(l.ch) { 579 + for unicode.IsLetter(l.ch) { 580 580 l.advance() 581 581 } 582 582 return token.Token{Type: token.COMMODITYMARK, Literal: string(l.input[s.offset:l.pos]), Span: l.span(s)}
M
journal/parser/parser.go
··· 378 378 var format *ast.Amount 379 379 380 380 switch p.cur.Type { 381 - case token.TEXT, token.INT, token.DECIMAL: 382 - format = p.parseAmount() 383 - commodity = format.Commodity 384 - case token.COMMODITYMARK: 381 + case token.COMMODITYMARK, token.TEXT, token.STRING: 385 382 commodity = p.cur.Literal 383 + if p.got(token.STRING) { 384 + commodity = unquote(commodity) 385 + } 386 386 p.advance() 387 387 hadSpace := p.got(token.WHITESPACE) 388 388 p.skipWhitespace() ··· 392 392 format.CommodityPos = ast.CommodityBefore 393 393 format.HasSpace = hadSpace 394 394 } 395 + case token.INT, token.DECIMAL: 396 + format = p.parseAmount() 397 + commodity = format.Commodity 395 398 default: 396 399 p.errorf("expected commodity name or amount, got %s", p.cur.Type) 397 400 } ··· 770 773 switch p.cur.Type { 771 774 default: 772 775 return false 773 - case token.COMMODITYMARK, token.INT, token.DECIMAL, token.MINUS, token.PLUS, token.PARENEXPR: 776 + case token.COMMODITYMARK, token.STRING, token.INT, token.DECIMAL, token.MINUS, token.PLUS, token.PARENEXPR: 774 777 return true 775 778 case token.TEXT: 776 779 return len(p.cur.Literal) > 0 && p.cur.Literal[0] >= '0' && p.cur.Literal[0] <= '9' ··· 784 787 Span: p.span(s), 785 788 } 786 789 787 - // commodity before quantity: $10.00 788 - if p.got(token.COMMODITYMARK) { 789 - amt.Commodity = p.cur.Literal 790 + // commodity before quantity: $10.00, eur 10.00 791 + if p.got(token.COMMODITYMARK) || p.got(token.TEXT) || p.got(token.STRING) { 792 + amt.Commodity = unquote(p.cur.Literal) 790 793 amt.CommodityPos = ast.CommodityBefore 791 794 p.advance() 792 795 if p.got(token.WHITESPACE) { ··· 811 814 p.advance() 812 815 } 813 816 814 - // commodity before quantity: -$120: 815 - if p.got(token.COMMODITYMARK) { 816 - amt.Commodity = p.cur.Literal 817 + // commodity before quantity: -$120, -eur 120: 818 + if p.got(token.COMMODITYMARK) || p.got(token.TEXT) || p.got(token.STRING) { 819 + amt.Commodity = unquote(p.cur.Literal) 817 820 amt.CommodityPos = ast.CommodityBefore 818 821 p.advance() 819 822 if p.got(token.WHITESPACE) { ··· 824 827 825 828 p.parseQuantityInto(amt) 826 829 827 - // commodity after quantity: 10.00 UAH (only if not set) 830 + // commodity after quantity: 10.00 UAH, 10.00 "EUR" (only if not set) 828 831 if amt.Commodity == "" { 829 832 switch p.cur.Type { 830 833 case token.WHITESPACE: 831 834 p.skipWhitespace() 832 - if p.got(token.COMMODITYMARK) || p.got(token.TEXT) { 835 + if p.got(token.COMMODITYMARK) || p.got(token.TEXT) || p.got(token.STRING) { 833 836 amt.HasSpace = true 834 - amt.Commodity = p.cur.Literal 837 + amt.Commodity = unquote(p.cur.Literal) 835 838 amt.CommodityPos = ast.CommodityAfter 836 839 p.advance() 837 840 } 838 - case token.COMMODITYMARK, token.TEXT: 839 - amt.Commodity = p.cur.Literal 841 + case token.COMMODITYMARK, token.TEXT, token.STRING: 842 + amt.Commodity = unquote(p.cur.Literal) 840 843 amt.CommodityPos = ast.CommodityAfter 841 844 p.advance() 842 845 } ··· 930 933 // optional amount - after two spaces 931 934 if p.got(token.WHITESPACE) { 932 935 p.skipWhitespace() 933 - if p.got(token.STRING) { // e.g "Plans: Wildthorn Mail" 1 @ 125s 934 - p.advance() 935 - p.skipWhitespace() 936 - } 937 936 if p.isAmountStart() || p.got(token.STAR) { 938 937 posting.Amount = p.parseAmountWithOptExpr() 939 938 }
M
journal/parser/parser_test.go
··· 111 111 assets:cash 150.60 HRK 112 112 `}, 113 113 {"transaction with cost and assertion", `2026-05-11 testies 114 - expenses:atm 20.00 UAH @ 1 USD = 0 UAH 114 + expenses:atm UAH 20.00 @ 1 USD = 0 UAH 115 115 assets:bank 116 116 `}, 117 117 {"transaction with posting", `2024-01-01 groceries ··· 123 123 expenses:food £5.00 124 124 expenses:food ₹700.00 125 125 assets:checking 126 +`}, 127 + {"transaction with quoted commodity", `2025-06-16 groceries 128 + expenses:food 10.00 "EUR" 129 + assets:cash 126 130 `}, 127 131 {"transaction with strange commodity symbols", `2024-01-01 groceries 128 132 2026-05-20
A
journal/parser/testdata/transaction_with_quoted_commodity.golden
··· 1 +Journal 2 + Transaction j:1:1-4:1 3 + Date: 2025-06-16 4 + Payee: "groceries" j:1:12-2:0 5 + Posting j:2:1-3:1 6 + Account j:2:5-2:18 7 + SubAccount: "expenses" j:2:5-2:13 8 + SubAccount: "food" j:2:14-2:18 9 + Amount j:2:20-2:20 10 + Quantity: 10 11 + Commodity: "EUR" 12 + CommodityPos: After 13 + HasSpace: true 14 + Precision: 2 15 + Decimal: "." 16 + Posting j:3:1-4:1 17 + Account j:3:5-4:0 18 + SubAccount: "assets" j:3:5-3:11 19 + SubAccount: "cash" j:3:12-4:0 20 + Amount: <elided>