all repos

clerk @ cb14fd1

missing tooling for ledger/hledger

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

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