all repos

clerk @ 7136c82232d67825b9ccd590797f85659c3a23bd

missing tooling for ledger/hledger
5 files changed, 91 insertions(+), 93 deletions(-)
parser: some directives were missing inline comment & improve memory layout of some directives
Author: Oleksandr Smirnov olexsmir@gmail.com
Committed at: 2026-05-23 17:52:23 +0300
Authored at: 2026-05-15 18:40:48 +0300
Change ID: zzqtlrrvkyoszvkukwznuuusqrmuykup
Parent: 0b03250
M journal/ast/ast.go
···
        24
        24
         

      
        25
        25
         type Date struct {

      
        26
        26
         	Year, Month, Day int

      
        27
        
        -	Sep              byte //  '-' '/' '.'

      
        
        27
        +	Sep              byte // '-' '/' '.'

      
        28
        28
         	Span             token.Span

      
        29
        29
         }

      
        30
        30
         

      
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"

      
        16
        
        -	Comment   *Comment

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

      
        
        16
        +	Comment   *Comment // optional inline comment

      
        17
        17
         	Span      token.Span

      
        18
        18
         }

      
        19
        19
         

      ···
        29
        29
         

      
        30
        30
         type TagDirective struct {

      
        31
        31
         	Name    string

      
        32
        
        -	Comment *Comment

      
        
        32
        +	Comment *Comment // optional inline comment

      
        33
        33
         	Span    token.Span

      
        34
        34
         }

      
        35
        35
         

      ···
        37
        37
         

      
        38
        38
         type IncludeDirective struct {

      
        39
        39
         	Path    string

      
        40
        
        -	Comment *Comment

      
        
        40
        +	Comment *Comment // optional inline comment

      
        41
        41
         	Span    token.Span

      
        42
        42
         }

      
        43
        43
         

      ···
        45
        45
         

      
        46
        46
         type AliasDirective struct {

      
        47
        47
         	From, To string

      
        
        48
        +	Comment  *Comment // optional inline comment

      
        48
        49
         	Span     token.Span

      
        49
        50
         }

      
        50
        51
         

      
        51
        52
         func (AliasDirective) entryNode() {}

      
        52
        53
         

      
        53
        54
         type YearDirective struct {

      
        54
        
        -	Year int

      
        55
        
        -	Span token.Span

      
        
        55
        +	Year    int

      
        
        56
        +	Comment *Comment // optional inline comment

      
        
        57
        +	Span    token.Span

      
        56
        58
         }

      
        57
        59
         

      
        58
        60
         func (YearDirective) entryNode() {}

      
        59
        61
         

      
        60
        62
         type DecimalMarkDirective struct {

      
        61
        
        -	Mark byte // '.' ','

      
        62
        
        -	Span token.Span

      
        
        63
        +	Mark    byte     // '.' ','

      
        
        64
        +	Comment *Comment // optional inline comment

      
        
        65
        +	Span    token.Span

      
        63
        66
         }

      
        64
        67
         

      
        65
        68
         func (DecimalMarkDirective) entryNode() {}

      
        66
        69
         

      
        67
        70
         type DefaultCommodityDirective struct {

      
        68
        
        -	Amount Amount

      
        69
        
        -	Span   token.Span

      
        
        71
        +	Amount  Amount

      
        
        72
        +	Comment *Comment // optional inline comment

      
        
        73
        +	Span    token.Span

      
        70
        74
         }

      
        71
        75
         

      
        72
        76
         func (DefaultCommodityDirective) entryNode() {}

      ···
        75
        79
         	DateTime  DateTime

      
        76
        80
         	Commodity string

      
        77
        81
         	Amount    Amount

      
        
        82
        +	Comment   *Comment // optional inline comment

      
        78
        83
         	Span      token.Span

      
        79
        84
         }

      
        80
        85
         

      ···
        89
        94
         func (ApplyDirective) entryNode() {}

      
        90
        95
         

      
        91
        96
         type EndDirective struct {

      
        92
        
        -	Expr    string // text after end e.g "tag"

      
        93
        
        -	Comment *Comment

      
        
        97
        +	Expr    string   // text after end e.g "tag"

      
        
        98
        +	Comment *Comment // optional inline comment

      
        94
        99
         	Span    token.Span

      
        95
        100
         }

      
        96
        101
         

      ···
        99
        104
         type CommentBlockDirective struct {

      
        100
        105
         	Header  string // text after "comment" on the same line

      
        101
        106
         	Content string

      
        102
        
        -	Comment *Comment

      
        
        107
        +	Comment *Comment // optional inline comment

      
        103
        108
         	Span    token.Span

      
        104
        109
         }

      
        105
        110
         

      
        106
        111
         func (CommentBlockDirective) entryNode() {}

      
        107
        112
         

      
        108
        113
         type IgnoredDirective struct {

      
        109
        
        -	Span token.Span

      
        
        114
        +	Comment *Comment // optional inline comment

      
        
        115
        +	Span    token.Span

      
        110
        116
         }

      
        111
        117
         

      
        112
        118
         func (IgnoredDirective) entryNode() {}

      
M journal/ast/dump.go
···
        174
        174
         func dumpPeriodicTransaction(b *strings.Builder, t *PeriodicTransaction, depth int) {

      
        175
        175
         	indent(b, depth)

      
        176
        176
         	fmt.Fprintf(b, "PeriodicTransaction %s\n", t.Span)

      
        177
        
        -	if t.Period != nil {

      
        
        177
        +	indent(b, depth+1)

      
        
        178
        +	fmt.Fprintf(b, "Period: %q\n", t.Period.Raw)

      
        
        179
        +	if t.Period.From != nil {

      
        178
        180
         		indent(b, depth+1)

      
        179
        
        -		fmt.Fprintf(b, "Period: %q\n", t.Period.Raw)

      
        180
        
        -		if t.Period.From != nil {

      
        181
        
        -			indent(b, depth+1)

      
        182
        
        -			fmt.Fprintf(b, "From: %s\n", dumpDate(*t.Period.From))

      
        183
        
        -		}

      
        184
        
        -		if t.Period.To != nil {

      
        185
        
        -			indent(b, depth+1)

      
        186
        
        -			fmt.Fprintf(b, "To: %s\n", dumpDate(*t.Period.To))

      
        187
        
        -		}

      
        
        181
        +		fmt.Fprintf(b, "From: %s\n", dumpDate(*t.Period.From))

      
        
        182
        +	}

      
        
        183
        +	if t.Period.To != nil {

      
        
        184
        +		indent(b, depth+1)

      
        
        185
        +		fmt.Fprintf(b, "To: %s\n", dumpDate(*t.Period.To))

      
        188
        186
         	}

      
        189
        187
         	if t.Status != nil {

      
        190
        188
         		indent(b, depth+1)

      ···
        197
        195
         	if t.Description != nil {

      
        198
        196
         		indent(b, depth+1)

      
        199
        197
         		fmt.Fprintf(b, "Description: %q\n", *t.Description)

      
        200
        
        -	}

      
        201
        
        -	if t.Note != nil {

      
        202
        
        -		indent(b, depth+1)

      
        203
        
        -		fmt.Fprintf(b, "Note: %q\n", *t.Note)

      
        204
        198
         	}

      
        205
        199
         	dumpOptComment(b, t.Comment, depth+1)

      
        206
        200
         	if len(t.HeaderComments) > 0 {

      ···
        283
        277
         	} else {

      
        284
        278
         		fmt.Fprintf(b, "Cost(unit) %s\n", c.Span)

      
        285
        279
         	}

      
        286
        
        -	dumpAmount(b, c.Amount, depth+1)

      
        
        280
        +	dumpAmount(b, &c.Amount, depth+1)

      
        287
        281
         }

      
        288
        282
         

      
        289
        283
         func dumpBalanceAssertion(b *strings.Builder, ba *BalanceAssertion, depth int) {

      ···
        313
        307
         	fmt.Fprintf(b, "CommodityDirective %s\n", c.Span)

      
        314
        308
         	indent(b, depth+1)

      
        315
        309
         	fmt.Fprintf(b, "Commodity: %q\n", c.Commodity)

      
        316
        
        -	if c.Format != nil {

      
        317
        
        -		dumpAmount(b, c.Format, depth+1)

      
        318
        
        -	}

      
        
        310
        +	dumpAmount(b, &c.Format, depth+1)

      
        319
        311
         	dumpOptComment(b, c.Comment, depth+1)

      
        320
        312
         }

      
        321
        313
         

      
M journal/ast/entries.go
···
        1
        1
         package ast

      
        2
        2
         

      
        3
        3
         import (

      
        
        4
        +	"github.com/shopspring/decimal"

      
        
        5
        +

      
        4
        6
         	"olexsmir.xyz/clerk/journal/token"

      
        5
        
        -	"github.com/shopspring/decimal"

      
        6
        7
         )

      
        7
        8
         

      
        8
        9
         type BlankLine struct{ Span token.Span }

      ···
        11
        12
         

      
        12
        13
         type Transaction struct {

      
        13
        14
         	Date           Date

      
        14
        
        -	SecondDate     *Date

      
        15
        
        -	Status         *Status

      
        16
        
        -	Code           *string

      
        17
        
        -	Payee          *Payee

      
        
        15
        +	SecondDate     *Date     // optional =2026-05-18 date

      
        
        16
        +	Status         *Status   // optional */! status

      
        
        17
        +	Code           *string   // optional (123) code

      
        
        18
        +	Payee          *Payee    // optional payee

      
        18
        19
         	Note           *string   // part after |

      
        19
        20
         	Comment        *Comment  // inline ; on header line

      
        20
        21
         	HeaderComments []Comment // indented ; lines before first posting

      ···
        34
        35
         func (Period) entryNode() {}

      
        35
        36
         

      
        36
        37
         type PeriodicTransaction struct {

      
        37
        
        -	Period         *Period

      
        38
        
        -	Status         *Status

      
        39
        
        -	Code           *string

      
        40
        
        -	Description    *string

      
        41
        
        -	Note           *string

      
        42
        
        -	Comment        *Comment

      
        
        38
        +	Period         Period   // period-expr

      
        
        39
        +	Status         *Status  // optional */! status

      
        
        40
        +	Code           *string  // optional (123) code

      
        
        41
        +	Description    *string  // optional description

      
        
        42
        +	Comment        *Comment // optional inline comment

      
        43
        43
         	HeaderComments []*Comment

      
        44
        44
         	Postings       []*Posting

      
        45
        45
         	Span           token.Span

      ···
        104
        104
         

      
        105
        105
         type Cost struct {

      
        106
        106
         	IsTotal bool // @ vs @@

      
        107
        
        -	Amount  *Amount

      
        
        107
        +	Amount  Amount

      
        108
        108
         	Span    token.Span

      
        109
        109
         }

      
        110
        110
         

      
M journal/parser/parser.go
···
        5
        5
         	"strconv"

      
        6
        6
         	"strings"

      
        7
        7
         

      
        
        8
        +	"github.com/shopspring/decimal"

      
        
        9
        +

      
        8
        10
         	"olexsmir.xyz/clerk/journal/ast"

      
        9
        11
         	"olexsmir.xyz/clerk/journal/lexer"

      
        10
        12
         	"olexsmir.xyz/clerk/journal/token"

      
        11
        
        -	"github.com/shopspring/decimal"

      
        12
        13
         )

      
        13
        14
         

      
        14
        15
         type Parser struct {

      ···
        241
        242
         	return at

      
        242
        243
         }

      
        243
        244
         

      
        244
        
        -func (p *Parser) parsePeriod() *ast.Period {

      
        
        245
        +func (p *Parser) parsePeriod() ast.Period {

      
        245
        246
         	s := p.cur.Span

      
        246
        247
         

      
        247
        248
         	var periodBuf strings.Builder

      ···
        266
        267
         	}

      
        267
        268
         

      
        268
        269
         	str := periodBuf.String()

      
        269
        
        -	period := &ast.Period{Raw: str, Span: p.span(s)}

      
        
        270
        +	period := ast.Period{Raw: str, Span: p.span(s)}

      
        270
        271
         

      
        271
        272
         	if _, after, ok := strings.Cut(str, " from "); ok {

      
        272
        273
         		end := strings.Index(after, " ")

      ···
        365
        366
         

      
        366
        367
         	return &ast.CommodityDirective{

      
        367
        368
         		Commodity: commodity,

      
        368
        
        -		Format:    format,

      
        
        369
        +		Format:    *format,

      
        369
        370
         		Comment:   comment,

      
        370
        371
         		Span:      p.span(s),

      
        371
        372
         	}

      ···
        396
        397
         

      
        397
        398
         func (p *Parser) parseAliasDirective() *ast.AliasDirective {

      
        398
        399
         	s := p.cur.Span

      
        
        400
        +	alias := &ast.AliasDirective{}

      
        399
        401
         	p.expect(token.ALIAS)

      
        400
        402
         	p.skipWhitespace()

      
        401
        
        -

      
        402
        
        -	from := p.parseAccount().Name

      
        
        403
        +	alias.From = p.parseAccount().Name

      
        403
        404
         	p.skipWhitespace()

      
        404
        405
         	p.expect(token.EQ)

      
        405
        406
         	p.skipWhitespace()

      
        406
        
        -	to := p.parseAccount().Name

      
        
        407
        +	alias.To = p.parseAccount().Name

      
        407
        408
         	p.skipWhitespace()

      
        408
        
        -

      
        
        409
        +	alias.Comment = p.parseOptInlineComment()

      
        409
        410
         	p.expectNewline()

      
        410
        
        -	return &ast.AliasDirective{

      
        411
        
        -		From: from,

      
        412
        
        -		To:   to,

      
        413
        
        -		Span: p.span(s),

      
        414
        
        -	}

      
        
        411
        +	alias.Span = p.span(s)

      
        
        412
        +	return alias

      
        415
        413
         }

      
        416
        414
         

      
        417
        415
         func (p *Parser) parsePayeeDirective() *ast.PayeeDirective {

      ···
        457
        455
         

      
        458
        456
         func (p *Parser) parseYearDirective() *ast.YearDirective {

      
        459
        457
         	s := p.cur.Span

      
        
        458
        +	year := &ast.YearDirective{}

      
        460
        459
         	p.expect(token.YEAR)

      
        461
        460
         	p.skipWhitespace()

      
        462
        461
         

      
        463
        
        -	year := 0

      
        464
        462
         	if p.got(token.INT) {

      
        465
        
        -		_, _ = fmt.Sscanf(p.cur.Literal, "%d", &year)

      
        
        463
        +		year.Year, _ = strconv.Atoi(p.cur.Literal)

      
        466
        464
         		p.advance()

      
        467
        465
         	} else {

      
        468
        466
         		p.errorf("expected year, got %s", p.cur.Type)

      
        469
        467
         	}

      
        470
        468
         

      
        
        469
        +	p.skipWhitespace()

      
        
        470
        +	year.Comment = p.parseOptInlineComment()

      
        471
        471
         	p.expectNewline()

      
        472
        
        -	return &ast.YearDirective{

      
        473
        
        -		Year: year,

      
        474
        
        -		Span: p.span(s),

      
        475
        
        -	}

      
        
        472
        +	year.Span = p.span(s)

      
        
        473
        +	return year

      
        476
        474
         }

      
        477
        475
         

      
        478
        476
         func (p *Parser) parseDecimalMarkDirective() *ast.DecimalMarkDirective {

      
        479
        477
         	s := p.cur.Span

      
        
        478
        +	mark := &ast.DecimalMarkDirective{}

      
        480
        479
         	p.expect(token.DECIMALMARK)

      
        481
        480
         	p.skipWhitespace()

      
        482
        481
         

      
        483
        
        -	mark := byte('.')

      
        
        482
        +	mark.Mark = byte('.')

      
        484
        483
         	if p.got(token.TEXT) {

      
        485
        484
         		if len(p.cur.Literal) > 0 {

      
        486
        
        -			mark = p.cur.Literal[0]

      
        
        485
        +			mark.Mark = p.cur.Literal[0]

      
        487
        486
         		}

      
        488
        487
         		p.advance()

      
        489
        488
         	}

      
        490
        489
         

      
        
        490
        +	p.skipWhitespace()

      
        
        491
        +	mark.Comment = p.parseOptInlineComment()

      
        491
        492
         	p.expectNewline()

      
        492
        
        -	return &ast.DecimalMarkDirective{

      
        493
        
        -		Mark: mark,

      
        494
        
        -		Span: p.span(s),

      
        495
        
        -	}

      
        
        493
        +	mark.Span = p.span(s)

      
        
        494
        +	return mark

      
        496
        495
         }

      
        497
        496
         

      
        498
        497
         func (p *Parser) parseDefaultCommodityDirective() *ast.DefaultCommodityDirective {

      
        499
        498
         	s := p.cur.Span

      
        
        499
        +	com := &ast.DefaultCommodityDirective{}

      
        500
        500
         	p.expect(token.D)

      
        501
        501
         	p.skipWhitespace()

      
        502
        
        -

      
        503
        
        -	amt := p.parseAmount()

      
        
        502
        +	com.Amount = *p.parseAmount()

      
        
        503
        +	p.skipWhitespace()

      
        
        504
        +	com.Comment = p.parseOptInlineComment()

      
        504
        505
         	p.expectNewline()

      
        505
        
        -

      
        506
        
        -	return &ast.DefaultCommodityDirective{

      
        507
        
        -		Amount: *amt,

      
        508
        
        -		Span:   p.span(s),

      
        509
        
        -	}

      
        
        506
        +	com.Span = p.span(s)

      
        
        507
        +	return com

      
        510
        508
         }

      
        511
        509
         

      
        512
        510
         func (p *Parser) parseIgnoredDirective() *ast.IgnoredDirective {

      ···
        516
        514
         	if p.got(token.TEXT) || p.got(token.COMMODITYMARK) {

      
        517
        515
         		p.advance()

      
        518
        516
         	}

      
        519
        
        -	p.parseOptInlineComment()

      
        
        517
        +	p.skipWhitespace()

      
        
        518
        +	comment := p.parseOptInlineComment()

      
        520
        519
         	p.expectNewline()

      
        521
        
        -	return &ast.IgnoredDirective{Span: p.span(s)}

      
        
        520
        +	return &ast.IgnoredDirective{

      
        
        521
        +		Comment: comment,

      
        
        522
        +		Span:    p.span(s),

      
        
        523
        +	}

      
        522
        524
         }

      
        523
        525
         

      
        524
        526
         func (p *Parser) parseMarketPriceDirective() *ast.MarketPriceDirective {

      ···
        526
        528
         	p.expect(token.P)

      
        527
        529
         	p.skipWhitespace()

      
        528
        530
         

      
        529
        
        -	date := p.parseDate()

      
        
        531
        +	mp := &ast.MarketPriceDirective{}

      
        
        532
        +	mp.DateTime.Date = p.parseDate()

      
        530
        533
         	p.skipWhitespace()

      
        531
        534
         

      
        532
        
        -	var t *ast.Time

      
        533
        535
         	if p.got(token.TIME) {

      
        534
        
        -		tm := p.parseTime()

      
        535
        
        -		t = &tm

      
        
        536
        +		mp.DateTime.Time = new(p.parseTime())

      
        536
        537
         		p.skipWhitespace()

      
        537
        538
         	}

      
        538
        539
         

      
        539
        540
         	tok, _ := p.expect(token.COMMODITYMARK)

      
        540
        
        -	commodity := tok.Literal

      
        
        541
        +	mp.Commodity = tok.Literal

      
        541
        542
         	p.advance()

      
        542
        543
         	p.skipWhitespace()

      
        543
        544
         

      
        544
        
        -	amt := p.parseAmount()

      
        545
        
        -	p.expectNewline()

      
        
        545
        +	mp.Amount = *p.parseAmount()

      
        
        546
        +

      
        
        547
        +	p.skipWhitespace()

      
        
        548
        +	mp.Comment = p.parseOptInlineComment()

      
        546
        549
         

      
        547
        
        -	return &ast.MarketPriceDirective{

      
        548
        
        -		DateTime:  ast.DateTime{Date: date, Time: t},

      
        549
        
        -		Commodity: commodity,

      
        550
        
        -		Amount:    *amt,

      
        551
        
        -		Span:      p.span(s),

      
        552
        
        -	}

      
        
        550
        +	p.expectNewline()

      
        
        551
        +	mp.Span = p.span(s)

      
        
        552
        +	return mp

      
        553
        553
         }

      
        554
        554
         

      
        555
        555
         func (p *Parser) parseTime() ast.Time {

      ···
        890
        890
         	p.skipWhitespace()

      
        891
        891
         	return &ast.Cost{

      
        892
        892
         		IsTotal: isTotal,

      
        893
        
        -		Amount:  p.parseAmount(),

      
        
        893
        +		Amount:  *p.parseAmount(),

      
        894
        894
         		Span:    p.span(s),

      
        895
        895
         	}

      
        896
        896
         }