12 files changed,
327 insertions(+),
44 deletions(-)
Author:
Oleksandr Smirnov
olexsmir@gmail.com
Committed at:
2026-06-09 15:33:31 +0300
Authored at:
2026-06-09 11:10:26 +0300
Change ID:
txnsuzsnlvlnuovmqmyomvqlnznowszv
Parent:
540e892
jump to
M
journal/lexer/lexer.go
··· 51 51 col int // current column (1-based) 52 52 line int // current line (1-based) 53 53 54 - transactionPastStatus bool 55 - postingExpectAccount bool 54 + transactionPastStatus bool 55 + postingExpectAccount bool 56 + readingNoteAfterPipe bool 56 57 } 57 58 58 59 func New(file string, input []byte) *Lexer { ··· 170 171 } 171 172 172 173 func (l *Lexer) lexTransaction() token.Token { 174 + if l.readingNoteAfterPipe { 175 + l.readingNoteAfterPipe = false 176 + return l.lexNote() 177 + } 178 + 173 179 switch l.ch { 174 180 case 0: 175 181 return l.token(token.EOF, "") ··· 180 186 l.col = 0 181 187 l.advance() 182 188 return l.lexNewline() 189 + case ' ', '\t': 190 + return l.lexWhitespace() 183 191 case ';': 184 192 l.mode = ModeComment 185 193 return l.lexSingle(token.SEMICOLON) 186 - case ' ', '\t': 187 - return l.lexWhitespace() 188 194 case '*': 189 195 if !l.transactionPastStatus { 190 196 l.transactionPastStatus = true ··· 198 204 } 199 205 return l.lexText() 200 206 case '|': 207 + l.transactionPastStatus = true 208 + l.readingNoteAfterPipe = true 201 209 return l.lexSingle(token.PIPE) 202 210 case '+': 203 211 return l.lexSingle(token.PLUS) ··· 213 221 } 214 222 return l.lexText() 215 223 } 224 +} 225 + 226 +func (l *Lexer) lexNote() token.Token { 227 + for l.ch == ' ' || l.ch == '\t' { 228 + l.advance() 229 + } 230 + if l.ch == 0 || l.ch == '\n' || l.ch == ';' { 231 + return l.Next() 232 + } 233 + s := l.save() 234 + for l.ch != 0 && l.ch != '\n' && l.ch != ';' { 235 + l.advance() 236 + } 237 + lit := string(l.input[s.offset:l.pos]) 238 + for len(lit) > 0 && (lit[len(lit)-1] == ' ' || lit[len(lit)-1] == '\t') { 239 + lit = lit[:len(lit)-1] 240 + } 241 + return token.Token{Type: token.TEXT, Literal: lit, Span: l.span(s)} 216 242 } 217 243 218 244 func (l *Lexer) lexPeriodic() token.Token {
M
journal/lexer/lexer_test.go
··· 34 34 {"transaction with code", `2024/01/01 (123) groceries 35 35 expenses:food $10.00 36 36 assets:checking 37 + 38 +2024/01/02 (ABS) groceries 37 39 `}, 38 40 {"transaction with virtual accounts", `2024/01/01 * groceries 39 41 (virtual:account) 1 PESO ··· 46 48 expenses:food 40.00 гривні 47 49 assets:cash 48 50 `}, 49 -{"bangs and stars in transaction description", `2026-06-07 * payee !one | something *important* 51 + {"special chars in description", ` 52 +2024/01/01 * groceries + water| 1 + 2 53 +2024/01/01 groceries * water | 1 * 2 54 +2024/01/01 groceries ! water | 1 ! 2 55 +2024/01/01 groceries # water | 1 # 2 56 +2024/01/01 groceries % water | 1 % 2 57 +2024/01/01 groceries ^ water | 1 ^ 2 58 +2024/01/01 groceries & water | 1 & 2 59 +2024/01/01 groceries ( water | 1 ( 2 60 +2024/01/01 groceries ) water | 1 ) 2 61 +2024/01/01 groceries [ water | 1 [ 2 62 +2024/01/01 groceries ] water | 1 ] 2 63 +2024/01/01 groceries { water | 1 { 2 64 +2024/01/01 groceries } water | 1 } 2 65 +2026-06-07 * payee !one | something *important* 50 66 expenses:food 40.00 51 67 assets:cash 52 68 `},
D
journal/lexer/testdata/golden/Lexer__bangs_and_stars_in_transaction_description.golden
··· 1 -DATE "2026-06-07" 1:1-1:11 2 -WHITESPACE " " 1:11-1:12 3 -STAR "*" 1:12-1:13 4 -WHITESPACE " " 1:13-1:14 5 -TEXT "payee" 1:14-1:19 6 -WHITESPACE " " 1:19-1:20 7 -TEXT "!one" 1:20-1:24 8 -WHITESPACE " " 1:24-1:25 9 -PIPE "|" 1:25-1:26 10 -WHITESPACE " " 1:26-1:27 11 -TEXT "something" 1:27-1:36 12 -WHITESPACE " " 1:36-1:37 13 -TEXT "*important*" 1:37-2:0 14 -NEWLINE "\n" 2:0-2:1 15 -INDENT " " 2:1-2:5 16 -TEXT "expenses:food" 2:5-2:18 17 -WHITESPACE " " 2:18-2:20 18 -DECIMAL "40.00" 2:20-3:0 19 -NEWLINE "\n" 3:0-3:1 20 -INDENT " " 3:1-3:5 21 -TEXT "assets:cash" 3:5-4:0 22 -NEWLINE "\n" 4:0-4:1 23 -EOF "" 4:1-4:1
D
journal/lexer/testdata/golden/Lexer__better-date.golden
··· 1 -DATE "2024-01-02" 1:1-1:11 2 -EOF "" 1:11-1:11
A
journal/lexer/testdata/golden/Lexer__special_chars_in_description.golden
··· 1 +NEWLINE "\n" 2:0-2:1 2 +DATE "2024/01/01" 2:1-2:11 3 +WHITESPACE " " 2:11-2:12 4 +STAR "*" 2:12-2:13 5 +WHITESPACE " " 2:13-2:14 6 +TEXT "groceries" 2:14-2:23 7 +WHITESPACE " " 2:23-2:24 8 +PLUS "+" 2:24-2:25 9 +WHITESPACE " " 2:25-2:26 10 +TEXT "water|" 2:26-2:32 11 +WHITESPACE " " 2:32-2:33 12 +TEXT "1" 2:33-2:34 13 +WHITESPACE " " 2:34-2:35 14 +PLUS "+" 2:35-2:36 15 +WHITESPACE " " 2:36-2:37 16 +TEXT "2" 2:37-3:0 17 +NEWLINE "\n" 3:0-3:1 18 +DATE "2024/01/01" 3:1-3:11 19 +WHITESPACE " " 3:11-3:12 20 +TEXT "groceries" 3:12-3:21 21 +WHITESPACE " " 3:21-3:22 22 +STAR "*" 3:22-3:23 23 +WHITESPACE " " 3:23-3:24 24 +TEXT "water" 3:24-3:29 25 +WHITESPACE " " 3:29-3:30 26 +PIPE "|" 3:30-3:31 27 +TEXT "1 * 2" 3:32-4:0 28 +NEWLINE "\n" 4:0-4:1 29 +DATE "2024/01/01" 4:1-4:11 30 +WHITESPACE " " 4:11-4:12 31 +TEXT "groceries" 4:12-4:21 32 +WHITESPACE " " 4:21-4:22 33 +BANG "!" 4:22-4:23 34 +WHITESPACE " " 4:23-4:24 35 +TEXT "water" 4:24-4:29 36 +WHITESPACE " " 4:29-4:30 37 +PIPE "|" 4:30-4:31 38 +TEXT "1 ! 2" 4:32-5:0 39 +NEWLINE "\n" 5:0-5:1 40 +DATE "2024/01/01" 5:1-5:11 41 +WHITESPACE " " 5:11-5:12 42 +TEXT "groceries" 5:12-5:21 43 +WHITESPACE " " 5:21-5:22 44 +TEXT "#" 5:22-5:23 45 +WHITESPACE " " 5:23-5:24 46 +TEXT "water" 5:24-5:29 47 +WHITESPACE " " 5:29-5:30 48 +PIPE "|" 5:30-5:31 49 +TEXT "1 # 2" 5:32-6:0 50 +NEWLINE "\n" 6:0-6:1 51 +DATE "2024/01/01" 6:1-6:11 52 +WHITESPACE " " 6:11-6:12 53 +TEXT "groceries" 6:12-6:21 54 +WHITESPACE " " 6:21-6:22 55 +TEXT "%" 6:22-6:23 56 +WHITESPACE " " 6:23-6:24 57 +TEXT "water" 6:24-6:29 58 +WHITESPACE " " 6:29-6:30 59 +PIPE "|" 6:30-6:31 60 +TEXT "1 % 2" 6:32-7:0 61 +NEWLINE "\n" 7:0-7:1 62 +DATE "2024/01/01" 7:1-7:11 63 +WHITESPACE " " 7:11-7:12 64 +TEXT "groceries" 7:12-7:21 65 +WHITESPACE " " 7:21-7:22 66 +TEXT "^" 7:22-7:23 67 +WHITESPACE " " 7:23-7:24 68 +TEXT "water" 7:24-7:29 69 +WHITESPACE " " 7:29-7:30 70 +PIPE "|" 7:30-7:31 71 +TEXT "1 ^ 2" 7:32-8:0 72 +NEWLINE "\n" 8:0-8:1 73 +DATE "2024/01/01" 8:1-8:11 74 +WHITESPACE " " 8:11-8:12 75 +TEXT "groceries" 8:12-8:21 76 +WHITESPACE " " 8:21-8:22 77 +TEXT "&" 8:22-8:23 78 +WHITESPACE " " 8:23-8:24 79 +TEXT "water" 8:24-8:29 80 +WHITESPACE " " 8:29-8:30 81 +PIPE "|" 8:30-8:31 82 +TEXT "1 & 2" 8:32-9:0 83 +NEWLINE "\n" 9:0-9:1 84 +DATE "2024/01/01" 9:1-9:11 85 +WHITESPACE " " 9:11-9:12 86 +TEXT "groceries" 9:12-9:21 87 +WHITESPACE " " 9:21-9:22 88 +TEXT "(" 9:22-9:23 89 +WHITESPACE " " 9:23-9:24 90 +TEXT "water" 9:24-9:29 91 +WHITESPACE " " 9:29-9:30 92 +PIPE "|" 9:30-9:31 93 +TEXT "1 ( 2" 9:32-10:0 94 +NEWLINE "\n" 10:0-10:1 95 +DATE "2024/01/01" 10:1-10:11 96 +WHITESPACE " " 10:11-10:12 97 +TEXT "groceries" 10:12-10:21 98 +WHITESPACE " " 10:21-10:22 99 +TEXT ")" 10:22-10:23 100 +WHITESPACE " " 10:23-10:24 101 +TEXT "water" 10:24-10:29 102 +WHITESPACE " " 10:29-10:30 103 +PIPE "|" 10:30-10:31 104 +TEXT "1 ) 2" 10:32-11:0 105 +NEWLINE "\n" 11:0-11:1 106 +DATE "2024/01/01" 11:1-11:11 107 +WHITESPACE " " 11:11-11:12 108 +TEXT "groceries" 11:12-11:21 109 +WHITESPACE " " 11:21-11:22 110 +TEXT "[" 11:22-11:23 111 +WHITESPACE " " 11:23-11:24 112 +TEXT "water" 11:24-11:29 113 +WHITESPACE " " 11:29-11:30 114 +PIPE "|" 11:30-11:31 115 +TEXT "1 [ 2" 11:32-12:0 116 +NEWLINE "\n" 12:0-12:1 117 +DATE "2024/01/01" 12:1-12:11 118 +WHITESPACE " " 12:11-12:12 119 +TEXT "groceries" 12:12-12:21 120 +WHITESPACE " " 12:21-12:22 121 +TEXT "]" 12:22-12:23 122 +WHITESPACE " " 12:23-12:24 123 +TEXT "water" 12:24-12:29 124 +WHITESPACE " " 12:29-12:30 125 +PIPE "|" 12:30-12:31 126 +TEXT "1 ] 2" 12:32-13:0 127 +NEWLINE "\n" 13:0-13:1 128 +DATE "2024/01/01" 13:1-13:11 129 +WHITESPACE " " 13:11-13:12 130 +TEXT "groceries" 13:12-13:21 131 +WHITESPACE " " 13:21-13:22 132 +TEXT "{" 13:22-13:23 133 +WHITESPACE " " 13:23-13:24 134 +TEXT "water" 13:24-13:29 135 +WHITESPACE " " 13:29-13:30 136 +PIPE "|" 13:30-13:31 137 +TEXT "1 { 2" 13:32-14:0 138 +NEWLINE "\n" 14:0-14:1 139 +DATE "2024/01/01" 14:1-14:11 140 +WHITESPACE " " 14:11-14:12 141 +TEXT "groceries" 14:12-14:21 142 +WHITESPACE " " 14:21-14:22 143 +TEXT "}" 14:22-14:23 144 +WHITESPACE " " 14:23-14:24 145 +TEXT "water" 14:24-14:29 146 +WHITESPACE " " 14:29-14:30 147 +PIPE "|" 14:30-14:31 148 +TEXT "1 } 2" 14:32-15:0 149 +NEWLINE "\n" 15:0-15:1 150 +DATE "2026-06-07" 15:1-15:11 151 +WHITESPACE " " 15:11-15:12 152 +STAR "*" 15:12-15:13 153 +WHITESPACE " " 15:13-15:14 154 +TEXT "payee" 15:14-15:19 155 +WHITESPACE " " 15:19-15:20 156 +TEXT "!one" 15:20-15:24 157 +WHITESPACE " " 15:24-15:25 158 +PIPE "|" 15:25-15:26 159 +TEXT "something *important*" 15:27-16:0 160 +NEWLINE "\n" 16:0-16:1 161 +INDENT " " 16:1-16:5 162 +TEXT "expenses:food" 16:5-16:18 163 +WHITESPACE " " 16:18-16:20 164 +DECIMAL "40.00" 16:20-17:0 165 +NEWLINE "\n" 17:0-17:1 166 +INDENT " " 17:1-17:5 167 +TEXT "assets:cash" 17:5-18:0 168 +NEWLINE "\n" 18:0-18:1 169 +EOF "" 18:1-18:1
M
journal/lexer/testdata/golden/Lexer__transaction_with_code.golden
··· 12 12 NEWLINE "\n" 3:0-3:1 13 13 INDENT " " 3:1-3:5 14 14 TEXT "assets:checking" 3:5-4:0 15 -NEWLINE "\n" 4:0-4:1 16 -EOF "" 4:1-4:1 15 +NEWLINE "\n" 4:0-5:0 16 +NEWLINE "\n" 5:0-5:1 17 +DATE "2024/01/02" 5:1-5:11 18 +WHITESPACE " " 5:11-5:12 19 +TEXT "(ABS)" 5:12-5:17 20 +WHITESPACE " " 5:17-5:18 21 +TEXT "groceries" 5:18-6:0 22 +NEWLINE "\n" 6:0-6:1 23 +EOF "" 6:1-6:1
M
journal/parser/parser.go
··· 114 114 115 115 tx.Date = p.parseDate() 116 116 117 + p.skipWhitespace() 118 + 117 119 // optional secondary date 118 120 if p.got(token.EQ) { 119 121 p.advance() 122 + p.skipWhitespace() 120 123 d := p.parseDate() 121 124 tx.SecondDate = &d 122 125 } ··· 135 138 p.advance() 136 139 } 137 140 tx.Code = new(code.String()) 141 + p.advance() // TODO: why? 138 142 p.skipWhitespace() 139 143 } 140 144 ··· 149 153 150 154 if p.got(token.PIPE) { 151 155 p.advance() 152 - p.skipWhitespace() 153 - 154 - var note strings.Builder 155 - for p.got(token.TEXT) || p.got(token.WHITESPACE) { 156 - _, _ = note.WriteString(p.cur.Literal) 156 + if p.got(token.TEXT) { 157 + n := p.cur.Literal 157 158 p.advance() 159 + tx.Note = &n 158 160 } 159 - tx.Note = new(note.String()) 160 161 } 161 162 } 162 163
M
journal/parser/parser_test.go
··· 131 131 asdf 123 bytes 132 132 asdf2 133 133 `}, 134 - 134 + {"special chars in description", ` 135 +2024/01/01 groceries | 1 + 2 136 +2024/01/01 groceries | 1 * 2 137 +2024/01/01 groceries | 1 ! 2 138 +2024/01/01 groceries | 1 # 2 139 +2024/01/01 groceries | 1 % 2 140 +2024/01/01 groceries | 1 ^ 2 141 +2024/01/01 groceries | 1 & 2 142 +2024/01/01 groceries | 1 ( 2 143 +2024/01/01 groceries | 1 ) 2 144 +2024/01/01 groceries | 1 [ 2 145 +2024/01/01 groceries | 1 ] 2 146 +2024/01/01 groceries | 1 { 2 147 +2024/01/01 groceries | 1 } 2 148 +2026-06-07 * payee !one | something *important* 149 + expenses:food 40.00 150 + assets:cash 151 +`}, 135 152 {"transaction with tabs", `2024-01-01 groceries 136 153 expenses:food $10.00 137 154 assets:checking ··· 161 178 expenses:food $10.00 162 179 assets:checking 163 180 `}, 164 - {"transaction with trilling indent", ` 181 + {"transaction with trailing indent", ` 165 182 2013/1/1 * pay taxes 166 183 expenses:personal:tax $1250 167 184 assets:bank:checking $-1250
A
journal/parser/testdata/golden/Parser_ParseFile__special_chars_in_description.golden
··· 1 +Journal 2 + BlankLine j:2:0-2:1 3 + Transaction j:2:1-3:1 4 + Date: 2024/01/01 5 + Payee: "groceries" j:2:12-2:21 6 + Note: "1 + 2" 7 + Transaction j:3:1-4:1 8 + Date: 2024/01/01 9 + Payee: "groceries" j:3:12-3:21 10 + Note: "1 * 2" 11 + Transaction j:4:1-5:1 12 + Date: 2024/01/01 13 + Payee: "groceries" j:4:12-4:21 14 + Note: "1 ! 2" 15 + Transaction j:5:1-6:1 16 + Date: 2024/01/01 17 + Payee: "groceries" j:5:12-5:21 18 + Note: "1 # 2" 19 + Transaction j:6:1-7:1 20 + Date: 2024/01/01 21 + Payee: "groceries" j:6:12-6:21 22 + Note: "1 % 2" 23 + Transaction j:7:1-8:1 24 + Date: 2024/01/01 25 + Payee: "groceries" j:7:12-7:21 26 + Note: "1 ^ 2" 27 + Transaction j:8:1-9:1 28 + Date: 2024/01/01 29 + Payee: "groceries" j:8:12-8:21 30 + Note: "1 & 2" 31 + Transaction j:9:1-10:1 32 + Date: 2024/01/01 33 + Payee: "groceries" j:9:12-9:21 34 + Note: "1 ( 2" 35 + Transaction j:10:1-11:1 36 + Date: 2024/01/01 37 + Payee: "groceries" j:10:12-10:21 38 + Note: "1 ) 2" 39 + Transaction j:11:1-12:1 40 + Date: 2024/01/01 41 + Payee: "groceries" j:11:12-11:21 42 + Note: "1 [ 2" 43 + Transaction j:12:1-13:1 44 + Date: 2024/01/01 45 + Payee: "groceries" j:12:12-12:21 46 + Note: "1 ] 2" 47 + Transaction j:13:1-14:1 48 + Date: 2024/01/01 49 + Payee: "groceries" j:13:12-13:21 50 + Note: "1 { 2" 51 + Transaction j:14:1-15:1 52 + Date: 2024/01/01 53 + Payee: "groceries" j:14:12-14:21 54 + Note: "1 } 2" 55 + Transaction j:15:1-18:1 56 + Date: 2026-06-07 57 + State: "*" 58 + Payee: "payee !one" j:15:14-15:24 59 + Note: "something *important*" 60 + Posting j:16:1-17:1 61 + Account "expenses:food" j:16:5-16:18 62 + Amount j:16:20-16:20 63 + Quantity: 40 64 + Commodity: "" 65 + CommodityPos: Before 66 + HasSpace: false 67 + Precision: 2 68 + Decimal: "." 69 + Posting j:17:1-18:1 70 + Account "assets:cash" j:17:5-18:0 71 + Amount: <elided>
M
journal/parser/testdata/golden/Parser_ParseFile__transaction_with_trilling_indent.golden
→ journal/parser/testdata/golden/Parser_ParseFile__transaction_with_trailing_indent.golden
··· 1 1 Journal 2 2 BlankLine j:2:0-2:1 3 - Transaction j:2:1-6:1 3 + Transaction j:2:1-6:0 4 4 Date: 2013/01/01 5 5 State: "*" 6 6 Payee: "pay taxes" j:2:12-3:0 ··· 13 13 HasSpace: false 14 14 Precision: 0 15 15 Decimal: "." 16 - Posting j:4:1-5:1 16 + Posting j:4:1-6:0 17 17 Account "assets:bank:checking" j:4:5-4:25 18 18 Amount j:4:40-4:40 19 19 Quantity: -1250 ··· 22 22 HasSpace: false 23 23 Precision: 0 24 24 Decimal: "." 25 + BlankLine j:6:0-6:1
M
journal/printer/testdata/sample.golden
··· 24 24 expenses:utilities 350.00 UAH 25 25 assets:bank 26 26 27 -2026-02-10 * something | 2 months of *income* 27 +2026-02-10 * something | 2 months of *income* + idk 28 28 assets:bank 1.50 USD ; jan 29 29 assets:bank 1.00 USD ; feb 30 30 income:invenstment