summaryrefslogtreecommitdiff
path: root/tokenizer.go
blob: 1ddd8d07aeb130cabc4cb5a5b77c770aaab792ba (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
// Package lex представляет собой достаточно простой лексер произвольных выражений.
// Практически полностью аналогичен лексеру от Роба Пайка.
package lex

import (
	"strings"
	"unicode/utf8"
)

func Do(initState StateFunc, input string) <-chan Token {
	t := &Lexer{
		pos:   0,
		input: input,
		start: 0,
		ch:    make(chan Token),
	}

	go func() {
		defer close(t.ch)
		for state := initState; state != nil; {
			state = state(t)
		}
	}()

	return t.ch
}

type Lexer struct {
	pos   int
	start int
	input string
	width int
	ch    chan Token
}

func (l *Lexer) Next() (ch rune) {
	if l.pos >= len(l.input) {
		l.width = 0
		return 0
	}
	ch, l.width = utf8.DecodeRuneInString(l.input[l.pos:])
	l.pos += l.width
	return ch
}

func (l *Lexer) Peek() rune {
	ch := l.Next()
	l.Back()
	return ch
}

func (l *Lexer) Back() {
	l.pos -= l.width
}

func (l *Lexer) Skip() {
	l.start = l.pos
}

func (l *Lexer) EmitToken(typ Typ) {
	value := ""
	if l.pos > l.start {
		value = l.input[l.start:l.pos]
	}
	l.ch <- Token{
		Typ:   typ,
		Value: value,
		Pos:   l.pos,
	}
	l.start = l.pos
}

func (l *Lexer) EmitError(err error) {
	value := ""
	if l.pos > l.start {
		value = l.input[l.start:l.pos]
	}
	l.ch <- Token{
		Value: value,
		Pos:   l.pos,
		Error: err,
	}
}

func (l *Lexer) Accept(valid string) bool {
	if strings.ContainsRune(valid, l.Next()) {
		return true
	}
	l.Back()

	return false
}

func (l *Lexer) AcceptRun(valid string) {
	for strings.ContainsRune(valid, l.Next()) {
	}
	l.Back()
}

func (l *Lexer) AcceptNotRun(invalid string, greedy bool) {
	for ch := l.Next(); !strings.ContainsRune(invalid, ch) && ch != 0; ch = l.Next() {
	}
	if !greedy {
		l.Back()
	}
}