all repos

clerk @ dc54e906021a5d4e2caeca03c52769c41bd80b28

missing tooling for ledger/hledger

clerk/journal/lexer/lexer.go (view raw)

Oleksandr Smirnov Oleksandr Smirnov
olexsmir@gmail.com
journal: dont lex */+ etc in transaction notes, 1 month ago
1
package lexer
2
3
import (
4
	"strings"
5
	"unicode"
6
	"unicode/utf8"
7
8
	"olexsmir.xyz/clerk/journal/token"
9
)
10
11
type Mode uint
12
13
const (
14
	// start of a line, nothing consumed
15
	ModeDefault Mode = iota
16
17
	// after ; # * % ; at start of line, or anywhere inline
18
	// everything until \n is comment text
19
	ModeComment
20
21
	// after lexing a date at column 0
22
	// expects: optional status, optional code, description, comment
23
	ModeTransaction
24
25
	// after lexing an indent at start of line
26
	// expects: account name, then two spaces, then amount
27
	ModePosting
28
29
	// after ~, period expression
30
	// expects: period, optional description (after 2+ spaces), optional comment
31
	ModePeriodic
32
33
	// after =, automates transaction
34
	// expects: expression
35
	ModeAutomated
36
37
	// after a directive keyword like account, commodity, include
38
	// expects: rest of directive content
39
	ModeDirective
40
)
41
42
type Lexer struct {
43
	file  string
44
	input []byte
45
	mode  Mode
46
47
	ch     rune // current rune (0 = EOF/sentinel)
48
	chSize int  // byte size of current rune
49
	pos    int  // current byte offset (points at ch)
50
	rpos   int  // next byte offset to read (one ahead of pos)
51
	col    int  // current column (1-based)
52
	line   int  // current line (1-based)
53
54
	transactionPastStatus  bool
55
	postingExpectAccount   bool
56
	readingNoteAfterPipe  bool
57
}
58
59
func New(file string, input []byte) *Lexer {
60
	l := &Lexer{
61
		file:  file,
62
		input: input,
63
		line:  1,
64
	}
65
	l.advance()
66
	if l.ch == '\uFEFF' { // start of the input
67
		l.advance()
68
	}
69
	return l
70
}
71
72
// Next returns next token in the input
73
func (l *Lexer) Next() token.Token {
74
	switch l.mode {
75
	case ModeDefault:
76
		return l.lexDefault()
77
	case ModeComment:
78
		return l.lexComment()
79
	case ModeTransaction:
80
		return l.lexTransaction()
81
	case ModePosting:
82
		return l.lexPosting()
83
	case ModePeriodic:
84
		return l.lexPeriodic()
85
	case ModeAutomated:
86
		return l.lexAutomated()
87
	case ModeDirective:
88
		return l.lexDirective()
89
	}
90
	panic("unreachable")
91
}
92
93
func (l *Lexer) lexDefault() token.Token {
94
	switch {
95
	case l.ch == 0:
96
		return l.token(token.EOF, "")
97
	case l.ch == '\n':
98
		return l.lexNewline()
99
	case l.ch == '\r':
100
		l.col = 0
101
		l.advance()
102
		return l.lexNewline()
103
	case l.ch == ' ' || l.ch == '\t':
104
		tok := l.lexIndent()
105
		l.mode = ModePosting
106
		l.postingExpectAccount = true
107
		return tok
108
	case l.ch == ';' || l.ch == '#' || l.ch == '%':
109
		l.mode = ModeComment
110
		return l.lexSingle(token.SEMICOLON) // todo: ??
111
	case l.ch == '*': // * at col 0 == comment
112
		l.mode = ModeComment
113
		return l.lexSingle(token.STAR)
114
	case l.ch == '~':
115
		l.mode = ModePeriodic
116
		return l.lexSingle(token.TILDE)
117
	case l.ch == '=':
118
		l.mode = ModeAutomated
119
		return l.lexSingle(token.EQ)
120
	case l.ch == '+':
121
		return l.lexSingle(token.PLUS)
122
	case l.ch == '-':
123
		return l.lexSingle(token.MINUS)
124
	case l.ch == '.':
125
		return l.lexSingle(token.TEXT)
126
	case l.ch == '!':
127
		return l.lexSingle(token.BANG)
128
	case l.ch == '@':
129
		return l.lexSingle(token.AT)
130
	case l.isAlpha():
131
		return l.lexKeyword()
132
	case l.isDigit():
133
		if !l.isDate() {
134
			s := l.save()
135
			for l.isDigit() || l.ch == '-' || l.ch == '/' || l.ch == '.' {
136
				l.advance()
137
			}
138
			return token.Token{Type: token.ILLEGAL, Literal: string(l.input[s.offset:l.pos]), Span: l.span(s)}
139
		}
140
		tok := l.lexDate()
141
		l.mode = ModeTransaction
142
		l.transactionPastStatus = false
143
		return tok
144
	default:
145
		s := l.save()
146
		l.advance()
147
		return token.Token{Type: token.ILLEGAL, Literal: string(l.input[s.offset:l.pos]), Span: l.span(s)}
148
	}
149
}
150
151
func (l *Lexer) lexComment() token.Token {
152
	if l.ch == '\n' || l.ch == 0 {
153
		l.mode = ModeDefault
154
		return l.lexNewline()
155
	}
156
157
	for l.ch == ' ' || l.ch == '\t' {
158
		l.lexWhitespace()
159
	}
160
161
	if l.ch == '\n' || l.ch == 0 {
162
		l.mode = ModeDefault
163
		return l.lexNewline()
164
	}
165
166
	s := l.save()
167
	for l.ch != '\n' && l.ch != 0 {
168
		l.advance()
169
	}
170
	return token.Token{Type: token.TEXT, Literal: string(l.input[s.offset:l.pos]), Span: l.span(s)}
171
}
172
173
func (l *Lexer) lexTransaction() token.Token {
174
	if l.readingNoteAfterPipe {
175
		l.readingNoteAfterPipe = false
176
		return l.lexNote()
177
	}
178
179
	switch l.ch {
180
	case 0:
181
		return l.token(token.EOF, "")
182
	case '\n':
183
		l.mode = ModeDefault
184
		return l.lexNewline()
185
	case '\r':
186
		l.col = 0
187
		l.advance()
188
		return l.lexNewline()
189
	case ' ', '\t':
190
		return l.lexWhitespace()
191
	case ';':
192
		l.mode = ModeComment
193
		return l.lexSingle(token.SEMICOLON)
194
	case '*':
195
		if !l.transactionPastStatus {
196
			l.transactionPastStatus = true
197
			return l.lexSingle(token.STAR)
198
		}
199
		return l.lexText()
200
	case '!':
201
		if !l.transactionPastStatus {
202
			l.transactionPastStatus = true
203
			return l.lexSingle(token.BANG)
204
		}
205
		return l.lexText()
206
	case '|':
207
		l.transactionPastStatus = true
208
		l.readingNoteAfterPipe = true
209
		return l.lexSingle(token.PIPE)
210
	case '+':
211
		return l.lexSingle(token.PLUS)
212
	case '-':
213
		return l.lexSingle(token.MINUS)
214
	case '=':
215
		return l.lexEquals()
216
	case '"', '\'':
217
		return l.lexString()
218
	default: // description / payee
219
		if l.isDate() { // secondsry date after =
220
			return l.lexDate()
221
		}
222
		return l.lexText()
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)}
242
}
243
244
func (l *Lexer) lexPeriodic() token.Token {
245
	switch l.ch {
246
	case 0:
247
		return l.token(token.EOF, "")
248
	case '\n':
249
		l.mode = ModeDefault
250
		return l.lexNewline()
251
	case '\r':
252
		l.col = 0
253
		l.advance()
254
		return l.lexNewline()
255
	case ';':
256
		l.mode = ModeComment
257
		return l.lexSingle(token.SEMICOLON)
258
	case ' ', '\t':
259
		return l.lexWhitespace()
260
	default:
261
		return l.lexText()
262
	}
263
}
264
265
func (l *Lexer) lexAutomated() token.Token {
266
	switch l.ch {
267
	case 0:
268
		return l.token(token.EOF, "")
269
	case '\n':
270
		l.mode = ModeDefault
271
		return l.lexNewline()
272
	case '\r':
273
		l.col = 0
274
		l.advance()
275
		return l.lexNewline()
276
	case ' ', '\t':
277
		return l.lexWhitespace()
278
	case ';':
279
		l.mode = ModeComment
280
		return l.lexSingle(token.SEMICOLON)
281
	default:
282
		return l.lexText()
283
	}
284
}
285
286
func (l *Lexer) lexPosting() token.Token {
287
	switch {
288
	case l.ch == 0:
289
		l.postingExpectAccount = false
290
		return l.token(token.EOF, "")
291
	case l.ch == '\n':
292
		l.postingExpectAccount = false
293
		l.mode = ModeDefault
294
		return l.lexNewline()
295
	case l.ch == ';':
296
		l.postingExpectAccount = false
297
		l.mode = ModeComment
298
		return l.lexSingle(token.SEMICOLON)
299
	case l.ch == ' ' || l.ch == '\t':
300
		return l.lexWhitespace()
301
	case l.postingExpectAccount && l.ch == '*':
302
		return l.lexSingle(token.STAR)
303
	case l.postingExpectAccount && l.ch == '!':
304
		return l.lexSingle(token.BANG)
305
	case l.ch == '=':
306
		return l.lexEquals()
307
	case l.ch == '@':
308
		return l.lexAt()
309
	case l.ch == '{':
310
		return l.lexLBrace()
311
	case l.ch == '}':
312
		return l.lexRBrace()
313
	case l.ch == '(':
314
		if !l.postingExpectAccount {
315
			return l.lexParenExpr()
316
		}
317
		return l.lexSingle(token.LPAREN)
318
	case l.ch == ')':
319
		return l.lexSingle(token.RPAREN)
320
	case l.ch == '[':
321
		return l.lexSingle(token.LBRACKET)
322
	case l.ch == ']':
323
		return l.lexSingle(token.RBRACKET)
324
	case l.postingExpectAccount && l.ch != '*' && l.ch != '!' && l.ch != '(' && l.ch != '[':
325
		l.postingExpectAccount = false
326
		return l.lexAccountText()
327
	case l.ch == '*': // after account name
328
		return l.lexSingle(token.STAR)
329
	case l.isDigit(), l.ch == '.':
330
		return l.lexNumber()
331
	case l.ch == '-':
332
		return l.lexSingle(token.MINUS)
333
	case l.ch == '+':
334
		return l.lexSingle(token.PLUS)
335
	case l.isCommodityStart():
336
		return l.lexCommodityMark()
337
	case l.ch >= 'a' && l.ch <= 'z':
338
		return l.lexCommodityMark()
339
	default:
340
		return l.lexAccountText()
341
	}
342
}
343
344
func (l *Lexer) lexDirective() token.Token {
345
	switch l.ch {
346
	case '\n', 0:
347
		l.mode = ModeDefault
348
		return l.lexNewline()
349
	case ';':
350
		l.mode = ModeComment
351
		return l.lexSingle(token.SEMICOLON)
352
	case ' ', '\t':
353
		return l.lexWhitespace()
354
	case '=':
355
		return l.lexSingle(token.EQ)
356
	case '+':
357
		return l.lexSingle(token.PLUS)
358
	case '-':
359
		return l.lexSingle(token.MINUS)
360
	case '.':
361
		return l.lexSingle(token.TEXT)
362
	case '"', '\'':
363
		return l.lexString()
364
	default:
365
		if l.isCommodityStart() {
366
			return l.lexCommodityMark()
367
		}
368
		if l.isTime() {
369
			return l.lexTime()
370
		}
371
		if l.isDate() {
372
			return l.lexDate()
373
		}
374
		if l.isDigit() {
375
			return l.lexNumber()
376
		}
377
		return l.lexText()
378
	}
379
	// case l.ch == '/': // regex in 'alias'
380
	// 	return l.lexSingle(token.SLASH)
381
}
382
383
func (l *Lexer) lexSingle(kind token.Type) token.Token {
384
	s := l.save()
385
	l.advance()
386
	return token.Token{
387
		Type:    kind,
388
		Literal: string(l.input[s.offset:l.pos]),
389
		Span:    l.span(s),
390
	}
391
}
392
393
func (l *Lexer) lexNewline() token.Token {
394
	s := l.save()
395
	l.advance()
396
	l.mode = ModeDefault
397
	return token.Token{Type: token.NEWLINE, Literal: "\n", Span: l.span(s)}
398
}
399
400
func (l *Lexer) lexWhitespace() token.Token {
401
	s := l.save()
402
	for l.ch == ' ' || l.ch == '\t' {
403
		l.advance()
404
	}
405
	lit := string(l.input[s.offset:l.pos])
406
	return token.Token{Type: token.WHITESPACE, Literal: lit, Span: l.span(s)}
407
}
408
409
func (l *Lexer) lexIndent() token.Token {
410
	s := l.save()
411
	for l.ch == ' ' || l.ch == '\t' {
412
		l.advance()
413
	}
414
	lit := string(l.input[s.offset:l.pos])
415
	return token.Token{Type: token.INDENT, Literal: lit, Span: l.span(s)}
416
}
417
418
func (l *Lexer) lexEquals() token.Token {
419
	s := l.save()
420
	l.advance()
421
	if l.ch == '=' {
422
		l.advance()
423
		switch l.ch {
424
		case '=':
425
			l.advance()
426
			return token.Token{Type: token.EQEQEQ, Literal: "===", Span: l.span(s)}
427
		case '*':
428
			l.advance()
429
			return token.Token{Type: token.EQEQEQ, Literal: "==*", Span: l.span(s)}
430
		default:
431
			return token.Token{Type: token.EQEQ, Literal: "==", Span: l.span(s)}
432
		}
433
	}
434
	return token.Token{Type: token.EQ, Literal: "=", Span: l.span(s)}
435
}
436
437
func (l *Lexer) lexAt() token.Token {
438
	s := l.save()
439
	l.advance()
440
	if l.ch == '@' {
441
		l.advance()
442
		return token.Token{Type: token.ATAT, Literal: "@@", Span: l.span(s)}
443
	}
444
	return token.Token{Type: token.AT, Literal: "@", Span: l.span(s)}
445
}
446
447
func (l *Lexer) lexText() token.Token {
448
	s := l.save()
449
	l.advance()
450
	for l.ch != '\n' && l.ch != ';' && l.ch != 0 && l.ch != ' ' && l.ch != '\t' {
451
		l.advance()
452
	}
453
	lit := string(l.input[s.offset:l.pos])
454
	return token.Token{Type: token.TEXT, Literal: lit, Span: l.span(s)}
455
}
456
457
func (l *Lexer) lexAccountText() token.Token {
458
	s := l.save()
459
	for l.ch != '\n' && l.ch != ';' && l.ch != 0 && l.ch != ')' && l.ch != ']' {
460
		// two spaces = end of account name
461
		if l.isTwoSpaces() {
462
			break
463
		}
464
		l.advance()
465
	}
466
	lit := string(l.input[s.offset:l.pos])
467
	return token.Token{Type: token.TEXT, Literal: lit, Span: l.span(s)}
468
}
469
470
func (l *Lexer) lexParenExpr() token.Token {
471
	s := l.save()
472
	depth := 0
473
	for l.ch != '\n' && l.ch != 0 {
474
		if l.ch == '(' {
475
			depth++
476
		} else if l.ch == ')' {
477
			depth--
478
			if depth == 0 {
479
				l.advance()
480
				break
481
			}
482
		}
483
		l.advance()
484
	}
485
	lit := string(l.input[s.offset:l.pos])
486
	return token.Token{Type: token.PARENEXPR, Literal: lit, Span: l.span(s)}
487
}
488
489
func (l *Lexer) lexNumber() token.Token {
490
	s := l.save()
491
	for {
492
		if l.isDigit() || l.ch == '.' || l.ch == ',' || l.ch == '_' || l.ch == '\'' {
493
			l.advance()
494
		} else if l.ch == ' ' && (l.peek() >= '0' && l.peek() <= '9') {
495
			l.advance()
496
		} else {
497
			break
498
		}
499
	}
500
	lit := string(l.input[s.offset:l.pos])
501
	kind := token.INT
502
	if strings.ContainsAny(lit, "., ") {
503
		kind = token.DECIMAL
504
	}
505
	return token.Token{Type: kind, Literal: lit, Span: l.span(s)}
506
}
507
508
func (l *Lexer) lexKeyword() token.Token {
509
	s := l.save()
510
	for l.ch != 0 && l.ch != '\n' && l.ch != '\r' && l.ch != ' ' && l.ch != '\t' && l.ch != ';' {
511
		l.advance()
512
	}
513
	lit := string(l.input[s.offset:l.pos])
514
	kind := l.keyword(lit)
515
	if kind == token.ILLEGAL { // todo: report an error ??
516
		kind = token.TEXT
517
	} else {
518
		l.mode = ModeDirective
519
	}
520
	return token.Token{Type: kind, Literal: lit, Span: l.span(s)}
521
}
522
523
func (l *Lexer) lexDate() token.Token {
524
	s := l.save()
525
	for l.isDigit() || (l.isDateSep() && l.peekIsDigit()) {
526
		l.advance()
527
	}
528
	return token.Token{Type: token.DATE, Literal: string(l.input[s.offset:l.pos]), Span: l.span(s)}
529
}
530
531
func isSymbolChar(r rune) bool {
532
	return r == '$' || unicode.In(r, unicode.Sc)
533
}
534
535
func (l *Lexer) lexString() token.Token {
536
	s := l.save()
537
	quote := l.ch
538
	l.advance() // consume the quote character
539
	for l.ch != quote && l.ch != '\n' && l.ch != 0 {
540
		l.advance()
541
	}
542
	if l.ch == quote {
543
		l.advance()
544
	}
545
	return token.Token{Type: token.STRING, Literal: string(l.input[s.offset:l.pos]), Span: l.span(s)}
546
}
547
548
func (l *Lexer) lexCommodityMark() token.Token {
549
	s := l.save()
550
551
	if l.ch == '"' {
552
		l.advance()
553
		for l.ch != '"' && l.ch != '\n' && l.ch != 0 {
554
			l.advance()
555
		}
556
		if l.ch == '"' {
557
			l.advance()
558
		}
559
		return token.Token{Type: token.COMMODITYMARK, Literal: string(l.input[s.offset:l.pos]), Span: l.span(s)}
560
	}
561
562
	if unicode.IsLetter(l.ch) {
563
		for unicode.IsLetter(l.ch) || unicode.IsDigit(l.ch) {
564
			l.advance()
565
		}
566
		return token.Token{Type: token.COMMODITYMARK, Literal: string(l.input[s.offset:l.pos]), Span: l.span(s)}
567
	}
568
569
	if isSymbolChar(l.ch) {
570
		for isSymbolChar(l.ch) {
571
			l.advance()
572
		}
573
		return token.Token{Type: token.COMMODITYMARK, Literal: string(l.input[s.offset:l.pos]), Span: l.span(s)}
574
	}
575
576
	l.advance()
577
	return token.Token{Type: token.COMMODITYMARK, Literal: string(l.input[s.offset:l.pos]), Span: l.span(s)}
578
}
579
580
func (l *Lexer) lexLBrace() token.Token {
581
	s := l.save()
582
	l.advance()
583
	if l.ch == '{' {
584
		l.advance()
585
		return token.Token{Type: token.LBRACELBRACE, Literal: "{{", Span: l.span(s)}
586
	}
587
	return token.Token{Type: token.LBRACE, Literal: "{", Span: l.span(s)}
588
}
589
590
func (l *Lexer) lexRBrace() token.Token {
591
	s := l.save()
592
	l.advance()
593
	if l.ch == '}' {
594
		l.advance()
595
		return token.Token{Type: token.RBRACERBRACE, Literal: "}}", Span: l.span(s)}
596
	}
597
	return token.Token{Type: token.RBRACE, Literal: "}", Span: l.span(s)}
598
}
599
600
func (l *Lexer) advance() {
601
	if l.rpos >= len(l.input) {
602
		l.ch = 0
603
		l.chSize = 0
604
	} else {
605
		r, size := utf8.DecodeRune(l.input[l.rpos:])
606
		l.ch = r
607
		l.chSize = size
608
	}
609
	l.pos = l.rpos
610
	l.rpos += l.chSize
611
	if l.ch == '\n' || l.ch == '\r' {
612
		l.line++
613
		l.col = 0
614
	} else {
615
		l.col++
616
	}
617
}
618
619
func (l *Lexer) peek() rune {
620
	r, _ := utf8.DecodeRune(l.input[l.rpos:])
621
	return r
622
}
623
624
func (l *Lexer) peekN(n int) byte {
625
	if l.pos+n >= len(l.input) {
626
		return 0
627
	}
628
	return l.input[l.pos+n]
629
}
630
631
func (l *Lexer) isDigit() bool { return l.ch >= '0' && l.ch <= '9' }
632
func (l *Lexer) isAlpha() bool {
633
	return (l.ch >= 'a' && l.ch <= 'z') ||
634
		(l.ch >= 'A' && l.ch <= 'Z')
635
}
636
637
func (l *Lexer) isTwoSpaces() bool { return l.ch == ' ' && l.peek() == ' ' }
638
639
func (l *Lexer) isDateSep() bool { return l.ch == '-' || l.ch == '/' || l.ch == '.' }
640
641
func (l *Lexer) peekIsDigit() bool {
642
	r := l.peek()
643
	return r >= '0' && r <= '9'
644
}
645
646
func (l *Lexer) isCommodityStart() bool {
647
	if l.ch == '$' || (l.ch >= 'A' && l.ch <= 'Z') {
648
		return true
649
	}
650
	if l.ch < utf8.RuneSelf {
651
		return false
652
	}
653
	return unicode.In(l.ch, unicode.Sc) || unicode.IsLetter(l.ch)
654
}
655
656
func (l *Lexer) isDate() bool {
657
	if !l.isDigit() {
658
		return false
659
	}
660
	// YYYY/M/D or YYYY/MM/DD
661
	if l.peekN(1) >= '0' && l.peekN(1) <= '9' &&
662
		l.peekN(2) >= '0' && l.peekN(2) <= '9' &&
663
		l.peekN(3) >= '0' && l.peekN(3) <= '9' {
664
		sep := l.peekN(4)
665
		if sep == '/' || sep == '-' || sep == '.' {
666
			if l.peekN(5) >= '0' && l.peekN(5) <= '9' {
667
				if l.peekN(6) == sep {
668
					return l.peekN(7) >= '0' && l.peekN(7) <= '9'
669
				}
670
				if l.peekN(7) == sep {
671
					return l.peekN(8) >= '0' && l.peekN(8) <= '9'
672
				}
673
			}
674
		}
675
		return false
676
	}
677
	// M/D or MM/DD(year inferred, only / and - separators; . is ambiguous with decimal numbers like 1.01)
678
	if (l.peekN(1) == '/' || l.peekN(1) == '-') &&
679
		l.peekN(2) >= '0' && l.peekN(2) <= '9' &&
680
		l.ch >= '1' && l.ch <= '9' {
681
		return validDay(l.peekN(2), l.peekN(3))
682
	}
683
	if (l.peekN(2) == '/' || l.peekN(2) == '-') &&
684
		l.peekN(3) >= '0' && l.peekN(3) <= '9' {
685
		m := int(l.ch-'0')*10 + int(l.peekN(1)-'0')
686
		return m >= 1 && m <= 12 && validDay(l.peekN(3), l.peekN(4))
687
	}
688
	return false
689
}
690
691
func validDay(first, second byte) bool {
692
	d := int(first - '0')
693
	if second >= '0' && second <= '9' {
694
		d = d*10 + int(second-'0')
695
	}
696
	return d >= 1 && d <= 31
697
}
698
699
func (l *Lexer) isTime() bool {
700
	if !l.isDigit() {
701
		return false
702
	}
703
	return l.peekN(2) == ':'
704
}
705
706
func (l *Lexer) lexTime() token.Token {
707
	s := l.save()
708
	for l.isDigit() || l.ch == ':' {
709
		l.advance()
710
	}
711
	return token.Token{Type: token.TIME, Literal: string(l.input[s.offset:l.pos]), Span: l.span(s)}
712
}
713
714
type savedPos struct{ offset, line, col int }
715
716
func (l *Lexer) save() savedPos {
717
	return savedPos{l.pos, l.line, l.col}
718
}
719
720
func (l *Lexer) span(s savedPos) token.Span {
721
	return token.Span{
722
		Start: token.Pos{File: l.file, Offset: s.offset, Line: s.line, Col: s.col},
723
		End:   token.Pos{File: l.file, Offset: l.pos, Line: l.line, Col: l.col},
724
	}
725
}
726
727
func (l *Lexer) token(kind token.Type, literal string) token.Token {
728
	s := savedPos{l.pos, l.line, l.col}
729
	return token.Token{Type: kind, Literal: literal, Span: l.span(s)}
730
}
731
732
func (l *Lexer) keyword(s string) token.Type {
733
	switch s {
734
	case "comment":
735
		return token.COMMENTKW
736
	case "account":
737
		return token.ACCOUNT
738
	case "commodity":
739
		return token.COMMODITY
740
	case "include":
741
		return token.INCLUDE
742
	case "alias":
743
		return token.ALIAS
744
	case "payee":
745
		return token.PAYEE
746
	case "tag":
747
		return token.TAG
748
	case "apply":
749
		return token.APPLY
750
	case "end":
751
		return token.END
752
	case "Y", "year":
753
		return token.YEAR
754
	case "decimal-mark":
755
		return token.DECIMALMARK
756
	case "D":
757
		return token.D
758
	case "P":
759
		return token.P
760
	case "N":
761
		return token.N
762
	case "C":
763
		return token.C
764
	default:
765
		return token.ILLEGAL
766
	}
767
}