5 files changed,
91 insertions(+),
93 deletions(-)
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/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 }