all repos

clerk @ 8540205899ad32eb84a181f05f2ae0eab57e0a18

missing tooling for ledger/hledger

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

Oleksandr Smirnov Oleksandr Smirnov
olexsmir@gmail.com
journal/parser: fix include directive, 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.lexString()
362
	default:
363
		if l.isCommodityStart() {
364
			return l.lexCommodityMark()
365
		}
366
		if l.isTime() {
367
			return l.lexTime()
368
		}
369
		if l.isDate() {
370
			return l.lexDate()
371
		}
372
		if l.isDigit() {
373
			return l.lexNumber()
374
		}
375
		return l.lexText()
376
	}
377
	// case l.ch == '/': // regex in 'alias'
378
	// 	return l.lexSingle(token.SLASH)
379
}
380
381
func (l *Lexer) lexSingle(kind token.Type) token.Token {
382
	s := l.save()
383
	l.advance()
384
	return token.Token{
385
		Type:    kind,
386
		Literal: string(l.input[s.offset:l.pos]),
387
		Span:    l.span(s),
388
	}
389
}
390
391
func (l *Lexer) lexNewline() token.Token {
392
	s := l.save()
393
	l.advance()
394
	l.mode = ModeDefault
395
	return token.Token{Type: token.NEWLINE, Literal: "\n", Span: l.span(s)}
396
}
397
398
func (l *Lexer) lexWhitespace() token.Token {
399
	s := l.save()
400
	for l.ch == ' ' || l.ch == '\t' {
401
		l.advance()
402
	}
403
	lit := string(l.input[s.offset:l.pos])
404
	return token.Token{Type: token.WHITESPACE, Literal: lit, Span: l.span(s)}
405
}
406
407
func (l *Lexer) lexIndent() token.Token {
408
	s := l.save()
409
	for l.ch == ' ' || l.ch == '\t' {
410
		l.advance()
411
	}
412
	lit := string(l.input[s.offset:l.pos])
413
	return token.Token{Type: token.INDENT, Literal: lit, Span: l.span(s)}
414
}
415
416
func (l *Lexer) lexEquals() token.Token {
417
	s := l.save()
418
	l.advance()
419
	if l.ch == '=' {
420
		l.advance()
421
		switch l.ch {
422
		case '=':
423
			l.advance()
424
			return token.Token{Type: token.EQEQEQ, Literal: "===", Span: l.span(s)}
425
		case '*':
426
			l.advance()
427
			return token.Token{Type: token.EQEQEQ, Literal: "==*", Span: l.span(s)}
428
		default:
429
			return token.Token{Type: token.EQEQ, Literal: "==", Span: l.span(s)}
430
		}
431
	}
432
	return token.Token{Type: token.EQ, Literal: "=", Span: l.span(s)}
433
}
434
435
func (l *Lexer) lexAt() token.Token {
436
	s := l.save()
437
	l.advance()
438
	if l.ch == '@' {
439
		l.advance()
440
		return token.Token{Type: token.ATAT, Literal: "@@", Span: l.span(s)}
441
	}
442
	return token.Token{Type: token.AT, Literal: "@", Span: l.span(s)}
443
}
444
445
func (l *Lexer) lexText() token.Token {
446
	s := l.save()
447
	l.advance()
448
	for l.ch != '\n' && l.ch != ';' && l.ch != 0 && l.ch != ' ' && l.ch != '\t' {
449
		l.advance()
450
	}
451
	lit := string(l.input[s.offset:l.pos])
452
	return token.Token{Type: token.TEXT, Literal: lit, Span: l.span(s)}
453
}
454
455
func (l *Lexer) lexAccountText() token.Token {
456
	s := l.save()
457
	for l.ch != '\n' && l.ch != ';' && l.ch != 0 && l.ch != ')' && l.ch != ']' {
458
		// two spaces = end of account name
459
		if l.isTwoSpaces() {
460
			break
461
		}
462
		l.advance()
463
	}
464
	lit := string(l.input[s.offset:l.pos])
465
	return token.Token{Type: token.TEXT, Literal: lit, Span: l.span(s)}
466
}
467
468
func (l *Lexer) lexParenExpr() token.Token {
469
	s := l.save()
470
	depth := 0
471
	for l.ch != '\n' && l.ch != 0 {
472
		if l.ch == '(' {
473
			depth++
474
		} else if l.ch == ')' {
475
			depth--
476
			if depth == 0 {
477
				l.advance()
478
				break
479
			}
480
		}
481
		l.advance()
482
	}
483
	lit := string(l.input[s.offset:l.pos])
484
	return token.Token{Type: token.PARENEXPR, Literal: lit, Span: l.span(s)}
485
}
486
487
func (l *Lexer) lexNumber() token.Token {
488
	s := l.save()
489
	for {
490
		if l.isDigit() || l.ch == '.' || l.ch == ',' || l.ch == '_' || l.ch == '\'' {
491
			l.advance()
492
		} else if l.ch == ' ' && (l.peek() >= '0' && l.peek() <= '9') {
493
			l.advance()
494
		} else {
495
			break
496
		}
497
	}
498
	lit := string(l.input[s.offset:l.pos])
499
	kind := token.INT
500
	if strings.ContainsAny(lit, "., ") {
501
		kind = token.DECIMAL
502
	}
503
	return token.Token{Type: kind, Literal: lit, Span: l.span(s)}
504
}
505
506
func (l *Lexer) lexKeyword() token.Token {
507
	s := l.save()
508
	for l.ch != 0 && l.ch != '\n' && l.ch != '\r' && l.ch != ' ' && l.ch != '\t' && l.ch != ';' {
509
		l.advance()
510
	}
511
	lit := string(l.input[s.offset:l.pos])
512
	kind := l.keyword(lit)
513
	if kind == token.ILLEGAL { // todo: report an error ??
514
		kind = token.TEXT
515
	} else {
516
		l.mode = ModeDirective
517
	}
518
	return token.Token{Type: kind, Literal: lit, Span: l.span(s)}
519
}
520
521
func (l *Lexer) lexDate() token.Token {
522
	s := l.save()
523
	for l.isDigit() || (l.isDateSep() && l.peekIsDigit()) {
524
		l.advance()
525
	}
526
	return token.Token{Type: token.DATE, Literal: string(l.input[s.offset:l.pos]), Span: l.span(s)}
527
}
528
529
func isSymbolChar(r rune) bool {
530
	return r == '$' || unicode.In(r, unicode.Sc)
531
}
532
533
func (l *Lexer) lexString() token.Token {
534
	s := l.save()
535
	quote := l.ch
536
	l.advance() // consume the quote character
537
	for l.ch != quote && l.ch != '\n' && l.ch != 0 {
538
		l.advance()
539
	}
540
	if l.ch == quote {
541
		l.advance()
542
	}
543
	return token.Token{Type: token.STRING, Literal: string(l.input[s.offset:l.pos]), Span: l.span(s)}
544
}
545
546
func (l *Lexer) lexCommodityMark() token.Token {
547
	s := l.save()
548
549
	if l.ch == '"' {
550
		l.advance()
551
		for l.ch != '"' && l.ch != '\n' && l.ch != 0 {
552
			l.advance()
553
		}
554
		if l.ch == '"' {
555
			l.advance()
556
		}
557
		return token.Token{Type: token.COMMODITYMARK, Literal: string(l.input[s.offset:l.pos]), Span: l.span(s)}
558
	}
559
560
	if unicode.IsLetter(l.ch) {
561
		for unicode.IsLetter(l.ch) || unicode.IsDigit(l.ch) {
562
			l.advance()
563
		}
564
		return token.Token{Type: token.COMMODITYMARK, Literal: string(l.input[s.offset:l.pos]), Span: l.span(s)}
565
	}
566
567
	if isSymbolChar(l.ch) {
568
		for isSymbolChar(l.ch) {
569
			l.advance()
570
		}
571
		return token.Token{Type: token.COMMODITYMARK, Literal: string(l.input[s.offset:l.pos]), Span: l.span(s)}
572
	}
573
574
	l.advance()
575
	return token.Token{Type: token.COMMODITYMARK, Literal: string(l.input[s.offset:l.pos]), Span: l.span(s)}
576
}
577
578
func (l *Lexer) lexLBrace() token.Token {
579
	s := l.save()
580
	l.advance()
581
	if l.ch == '{' {
582
		l.advance()
583
		return token.Token{Type: token.LBRACELBRACE, Literal: "{{", Span: l.span(s)}
584
	}
585
	return token.Token{Type: token.LBRACE, Literal: "{", Span: l.span(s)}
586
}
587
588
func (l *Lexer) lexRBrace() token.Token {
589
	s := l.save()
590
	l.advance()
591
	if l.ch == '}' {
592
		l.advance()
593
		return token.Token{Type: token.RBRACERBRACE, Literal: "}}", Span: l.span(s)}
594
	}
595
	return token.Token{Type: token.RBRACE, Literal: "}", Span: l.span(s)}
596
}
597
598
func (l *Lexer) advance() {
599
	if l.rpos >= len(l.input) {
600
		l.ch = 0
601
		l.chSize = 0
602
	} else {
603
		r, size := utf8.DecodeRune(l.input[l.rpos:])
604
		l.ch = r
605
		l.chSize = size
606
	}
607
	l.pos = l.rpos
608
	l.rpos += l.chSize
609
	if l.ch == '\n' || l.ch == '\r' {
610
		l.line++
611
		l.col = 0
612
	} else {
613
		l.col++
614
	}
615
}
616
617
func (l *Lexer) peek() rune {
618
	r, _ := utf8.DecodeRune(l.input[l.rpos:])
619
	return r
620
}
621
622
func (l *Lexer) peekN(n int) byte {
623
	if l.pos+n >= len(l.input) {
624
		return 0
625
	}
626
	return l.input[l.pos+n]
627
}
628
629
func (l *Lexer) isDigit() bool { return l.ch >= '0' && l.ch <= '9' }
630
func (l *Lexer) isAlpha() bool {
631
	return (l.ch >= 'a' && l.ch <= 'z') ||
632
		(l.ch >= 'A' && l.ch <= 'Z')
633
}
634
635
func (l *Lexer) isTwoSpaces() bool { return l.ch == ' ' && l.peek() == ' ' }
636
637
func (l *Lexer) isDateSep() bool { return l.ch == '-' || l.ch == '/' || l.ch == '.' }
638
639
func (l *Lexer) peekIsDigit() bool {
640
	r := l.peek()
641
	return r >= '0' && r <= '9'
642
}
643
644
func (l *Lexer) isCommodityStart() bool {
645
	if l.ch == '$' || (l.ch >= 'A' && l.ch <= 'Z') {
646
		return true
647
	}
648
	if l.ch < utf8.RuneSelf {
649
		return false
650
	}
651
	return unicode.In(l.ch, unicode.Sc) || unicode.IsLetter(l.ch)
652
}
653
654
func (l *Lexer) isDate() bool {
655
	if !l.isDigit() {
656
		return false
657
	}
658
	// YYYY/M/D or YYYY/MM/DD
659
	if l.peekN(1) >= '0' && l.peekN(1) <= '9' &&
660
		l.peekN(2) >= '0' && l.peekN(2) <= '9' &&
661
		l.peekN(3) >= '0' && l.peekN(3) <= '9' {
662
		sep := l.peekN(4)
663
		if sep == '/' || sep == '-' || sep == '.' {
664
			if l.peekN(5) >= '0' && l.peekN(5) <= '9' {
665
				if l.peekN(6) == sep {
666
					return l.peekN(7) >= '0' && l.peekN(7) <= '9'
667
				}
668
				if l.peekN(7) == sep {
669
					return l.peekN(8) >= '0' && l.peekN(8) <= '9'
670
				}
671
			}
672
		}
673
		return false
674
	}
675
	// M/D or MM/DD(year inferred, only / and - separators; . is ambiguous with decimal numbers like 1.01)
676
	if (l.peekN(1) == '/' || l.peekN(1) == '-') &&
677
		l.peekN(2) >= '0' && l.peekN(2) <= '9' &&
678
		l.ch >= '1' && l.ch <= '9' {
679
		return validDay(l.peekN(2), l.peekN(3))
680
	}
681
	if (l.peekN(2) == '/' || l.peekN(2) == '-') &&
682
		l.peekN(3) >= '0' && l.peekN(3) <= '9' {
683
		m := int(l.ch-'0')*10 + int(l.peekN(1)-'0')
684
		return m >= 1 && m <= 12 && validDay(l.peekN(3), l.peekN(4))
685
	}
686
	return false
687
}
688
689
func validDay(first, second byte) bool {
690
	d := int(first - '0')
691
	if second >= '0' && second <= '9' {
692
		d = d*10 + int(second-'0')
693
	}
694
	return d >= 1 && d <= 31
695
}
696
697
func (l *Lexer) isTime() bool {
698
	if !l.isDigit() {
699
		return false
700
	}
701
	return l.peekN(2) == ':'
702
}
703
704
func (l *Lexer) lexTime() token.Token {
705
	s := l.save()
706
	for l.isDigit() || l.ch == ':' {
707
		l.advance()
708
	}
709
	return token.Token{Type: token.TIME, Literal: string(l.input[s.offset:l.pos]), Span: l.span(s)}
710
}
711
712
type savedPos struct{ offset, line, col int }
713
714
func (l *Lexer) save() savedPos {
715
	return savedPos{l.pos, l.line, l.col}
716
}
717
718
func (l *Lexer) span(s savedPos) token.Span {
719
	return token.Span{
720
		Start: token.Pos{File: l.file, Offset: s.offset, Line: s.line, Col: s.col},
721
		End:   token.Pos{File: l.file, Offset: l.pos, Line: l.line, Col: l.col},
722
	}
723
}
724
725
func (l *Lexer) token(kind token.Type, literal string) token.Token {
726
	s := savedPos{l.pos, l.line, l.col}
727
	return token.Token{Type: kind, Literal: literal, Span: l.span(s)}
728
}
729
730
func (l *Lexer) keyword(s string) token.Type {
731
	switch s {
732
	case "comment":
733
		return token.COMMENTKW
734
	case "account":
735
		return token.ACCOUNT
736
	case "commodity":
737
		return token.COMMODITY
738
	case "include":
739
		return token.INCLUDE
740
	case "alias":
741
		return token.ALIAS
742
	case "payee":
743
		return token.PAYEE
744
	case "tag":
745
		return token.TAG
746
	case "apply":
747
		return token.APPLY
748
	case "end":
749
		return token.END
750
	case "Y", "year":
751
		return token.YEAR
752
	case "decimal-mark":
753
		return token.DECIMALMARK
754
	case "D":
755
		return token.D
756
	case "P":
757
		return token.P
758
	case "N":
759
		return token.N
760
	case "C":
761
		return token.C
762
	default:
763
		return token.ILLEGAL
764
	}
765
}