all repos

clerk @ 8e80bf966dac8f581e04ce06b823f0ec9dfdaa96

missing tooling for ledger/hledger
8 files changed, 84 insertions(+), 6 deletions(-)
parser: support C conversion directive
Author: Oleksandr Smirnov olexsmir@gmail.com
Committed at: 2026-05-23 17:52:23 +0300
Authored at: 2026-05-21 14:49:17 +0300
Change ID: tvzyzpzxlzorwpmwwluvotnnlvlnmkvk
Parent: fc9cae1
M journal/ast/directives.go
···
        12
        12
         

      
        13
        13
         type CommodityDirective struct {

      
        14
        14
         	Commodity string

      
        15
        
        -	Format    Amount  // optional format hint: "1,000.00 UAH"

      
        
        15
        +	Format    Amount   // optional format hint: "1,000.00 UAH"

      
        16
        16
         	Comment   *Comment // optional inline comment

      
        17
        17
         	Span      token.Span

      
        18
        18
         }

      ···
        84
        84
         }

      
        85
        85
         

      
        86
        86
         func (MarketPriceDirective) entryNode() {}

      
        
        87
        +

      
        
        88
        +type ConversionDirective struct {

      
        
        89
        +	From    Amount

      
        
        90
        +	To      Amount

      
        
        91
        +	Comment *Comment // optional inline comment

      
        
        92
        +	Span    token.Span

      
        
        93
        +}

      
        
        94
        +

      
        
        95
        +func (ConversionDirective) entryNode() {}

      
        87
        96
         

      
        88
        97
         type ApplyDirective struct {

      
        89
        98
         	Expr    string // text after apply e.g "tag foo"

      
M journal/ast/dump.go
···
        78
        78
         		fmt.Fprintf(b, "Mark: %q\n", string(e.Mark))

      
        79
        79
         	case *MarketPriceDirective:

      
        80
        80
         		dumpMarketPriceDirective(b, e, depth)

      
        
        81
        +	case *ConversionDirective:

      
        
        82
        +		dumpConversionDirective(b, e, depth)

      
        81
        83
         	case *DefaultCommodityDirective:

      
        82
        84
         		indent(b, depth)

      
        83
        85
         		fmt.Fprintf(b, "DefaultCommodityDirective %s\n", e.Span)

      ···
        325
        327
         	indent(b, depth+1)

      
        326
        328
         	fmt.Fprintf(b, "Commodity: %q\n", m.Commodity)

      
        327
        329
         	dumpAmount(b, &m.Amount, depth+1)

      
        
        330
        +}

      
        
        331
        +

      
        
        332
        +func dumpConversionDirective(b *strings.Builder, c *ConversionDirective, depth int) {

      
        
        333
        +	indent(b, depth)

      
        
        334
        +	fmt.Fprintf(b, "ConversionDirective %s\n", c.Span)

      
        
        335
        +	indent(b, depth+1)

      
        
        336
        +	fmt.Fprintf(b, "From:\n")

      
        
        337
        +	dumpAmount(b, &c.From, depth+2)

      
        
        338
        +	indent(b, depth+1)

      
        
        339
        +	fmt.Fprintf(b, "To:\n")

      
        
        340
        +	dumpAmount(b, &c.To, depth+2)

      
        
        341
        +	dumpOptComment(b, c.Comment, depth+1)

      
        328
        342
         }

      
        329
        343
         

      
        330
        344
         func dumpComment(b *strings.Builder, c *Comment, depth int) {

      
M journal/lexer/lexer.go
···
        723
        723
         		return token.P

      
        724
        724
         	case "N":

      
        725
        725
         		return token.N

      
        
        726
        +	case "C":

      
        
        727
        +		return token.C

      
        726
        728
         	default:

      
        727
        729
         		return token.ILLEGAL

      
        728
        730
         	}

      
M journal/parser/parser.go
···
        40
        40
         	switch t {

      
        41
        41
         	case token.COMMENTKW, token.ACCOUNT, token.COMMODITY, token.INCLUDE,

      
        42
        42
         		token.ALIAS, token.PAYEE, token.TAG, token.APPLY, token.END,

      
        43
        
        -		token.YEAR, token.DECIMALMARK, token.D, token.P, token.N:

      
        
        43
        +		token.YEAR, token.DECIMALMARK, token.D, token.P, token.N, token.C:

      
        44
        44
         		return true

      
        45
        45
         	}

      
        46
        46
         	return false

      ···
        93
        93
         		return p.parseMarketPriceDirective()

      
        94
        94
         	case token.N:

      
        95
        95
         		return p.parseIgnoredDirective()

      
        
        96
        +	case token.C:

      
        
        97
        +		return p.parseConversionDirective()

      
        96
        98
         	case token.APPLY:

      
        97
        99
         		return p.parseApplyDirective()

      
        98
        100
         	case token.END:

      ···
        561
        563
         	return com

      
        562
        564
         }

      
        563
        565
         

      
        
        566
        +func (p *Parser) parseConversionDirective() *ast.ConversionDirective {

      
        
        567
        +	s := p.cur.Span

      
        
        568
        +	cd := &ast.ConversionDirective{}

      
        
        569
        +	p.expect(token.C)

      
        
        570
        +	p.skipWhitespace()

      
        
        571
        +

      
        
        572
        +	if p.isAmountStart() {

      
        
        573
        +		cd.From = *p.parseAmount()

      
        
        574
        +	} else {

      
        
        575
        +		p.errorf("expected amount, got %s", p.cur.Type)

      
        
        576
        +	}

      
        
        577
        +

      
        
        578
        +	p.skipWhitespace()

      
        
        579
        +	if p.got(token.EQ) {

      
        
        580
        +		p.advance()

      
        
        581
        +		p.skipWhitespace()

      
        
        582
        +		if p.isAmountStart() {

      
        
        583
        +			cd.To = *p.parseAmount()

      
        
        584
        +		} else {

      
        
        585
        +			p.errorf("expected amount, got %s", p.cur.Type)

      
        
        586
        +		}

      
        
        587
        +	}

      
        
        588
        +

      
        
        589
        +	p.skipWhitespace()

      
        
        590
        +	cd.Comment = p.parseOptInlineComment()

      
        
        591
        +	p.expectNewline()

      
        
        592
        +	cd.Span = p.span(s)

      
        
        593
        +	return cd

      
        
        594
        +}

      
        
        595
        +

      
        564
        596
         func (p *Parser) parseIgnoredDirective() *ast.IgnoredDirective {

      
        565
        597
         	s := p.cur.Span

      
        566
        598
         	p.expect(token.N)

      ···
        797
        829
         			switch p.cur.Type {

      
        798
        830
         			case token.WHITESPACE:

      
        799
        831
         				p.skipWhitespace()

      
        800
        
        -				if p.got(token.COMMODITYMARK) {

      
        
        832
        +				if p.got(token.COMMODITYMARK) || p.got(token.TEXT) {

      
        801
        833
         					amt.HasSpace = true

      
        802
        834
         					amt.Commodity = p.cur.Literal

      
        803
        835
         					amt.CommodityPos = ast.CommodityAfter

      
        804
        836
         					p.advance()

      
        805
        837
         				}

      
        806
        
        -			case token.COMMODITYMARK:

      
        
        838
        +			case token.COMMODITYMARK, token.TEXT:

      
        807
        839
         				amt.Commodity = p.cur.Literal

      
        808
        840
         				amt.CommodityPos = ast.CommodityAfter

      
        809
        841
         				p.advance()

      
M journal/parser/parser_test.go
···
        19
        19
         		{"tag directive", "tag project-xyz\n"},

      
        20
        20
         		{"year directive", "year 1488\n"},

      
        21
        21
         		{"decimal-mark directive", "decimal-mark ,\n"},

      
        
        22
        +		{"C directive", "C 1.00s = 100c\n"},

      
        22
        23
         		{"D directive", `D $1.00

      
        23
        24
         D 10 UAH

      
        24
        25
         `},

      
A journal/parser/testdata/golden/Parser_ParseFile__C_directive.golden
···
        
        1
        +Journal

      
        
        2
        +  ConversionDirective j:1:1-2:1

      
        
        3
        +    From:

      
        
        4
        +      Amount j:1:3-1:3

      
        
        5
        +        Quantity: 1

      
        
        6
        +        Commodity: "s"

      
        
        7
        +        CommodityPos: After

      
        
        8
        +        HasSpace: false

      
        
        9
        +        Precision: 2

      
        
        10
        +        Decimal: "."

      
        
        11
        +    To:

      
        
        12
        +      Amount j:1:11-1:11

      
        
        13
        +        Quantity: 100

      
        
        14
        +        Commodity: "c"

      
        
        15
        +        CommodityPos: After

      
        
        16
        +        HasSpace: false

      
        
        17
        +        Precision: 0

      
        
        18
        +        Decimal: "."

      
M journal/token/token.go
···
        90
        90
         	D           // "D" default commodity

      
        91
        91
         	P           // "P" market price

      
        92
        92
         	N           // "N" ignored price commodity

      
        
        93
        +	C           // "C" commodity conversion

      
        93
        94
         )

      
M journal/token/type_string.go
···
        58
        58
         	_ = x[D-47]

      
        59
        59
         	_ = x[P-48]

      
        60
        60
         	_ = x[N-49]

      
        
        61
        +	_ = x[C-50]

      
        61
        62
         }

      
        62
        63
         

      
        63
        
        -const _Type_name = "ILLEGALEOFWHITESPACEINDENTNEWLINEINTDECIMALSTRINGTEXTBANGSTARPERCENTHASHSEMICOLONCOLONEQEQEQEQEQEQATATATPIPEPLUSMINUSTILDELPARENRPARENLBRACELBRACELBRACERBRACERBRACERBRACELBRACKETRBRACKETCOMMODITYMARKDATETIMEPARENEXPRCOMMENTKWACCOUNTCOMMODITYINCLUDEALIASPAYEETAGAPPLYENDYEARDECIMALMARKDPN"

      
        
        64
        +const _Type_name = "ILLEGALEOFWHITESPACEINDENTNEWLINEINTDECIMALSTRINGTEXTBANGSTARPERCENTHASHSEMICOLONCOLONEQEQEQEQEQEQATATATPIPEPLUSMINUSTILDELPARENRPARENLBRACELBRACELBRACERBRACERBRACERBRACELBRACKETRBRACKETCOMMODITYMARKDATETIMEPARENEXPRCOMMENTKWACCOUNTCOMMODITYINCLUDEALIASPAYEETAGAPPLYENDYEARDECIMALMARKDPNC"

      
        64
        65
         

      
        65
        
        -var _Type_index = [...]uint16{0, 7, 10, 20, 26, 33, 36, 43, 49, 53, 57, 61, 68, 72, 81, 86, 88, 92, 98, 100, 104, 108, 112, 117, 122, 128, 134, 140, 152, 164, 170, 178, 186, 199, 203, 207, 216, 225, 232, 241, 248, 253, 258, 261, 266, 269, 273, 284, 285, 286, 287}

      
        
        66
        +var _Type_index = [...]uint16{0, 7, 10, 20, 26, 33, 36, 43, 49, 53, 57, 61, 68, 72, 81, 86, 88, 92, 98, 100, 104, 108, 112, 117, 122, 128, 134, 140, 152, 164, 170, 178, 186, 199, 203, 207, 216, 225, 232, 241, 248, 253, 258, 261, 266, 269, 273, 284, 285, 286, 287, 288}

      
        66
        67
         

      
        67
        68
         func (i Type) String() string {

      
        68
        69
         	idx := int(i) - 0