all repos

clerk @ 540e892

missing tooling for ledger/hledger

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

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