aboutsummaryrefslogtreecommitdiffstats
path: root/lexer.go
blob: a996451e4354dd4234a3e701a2296241f18dc9dc (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
107
108
109
110
111
112
113
114
115
package ccl

import (
	"bytes"
	"unicode"
	"unicode/utf8"
)

type token struct {
	i int
	b []byte
}

type lexer struct {
	data []byte
	i    int
}

func (l *lexer) error(reason string, args ...any) error {
	return newSyntaxError(l.data, l.i, reason, args...)
}

func (l *lexer) yield(n int) token {
	t := token{l.i, l.data[l.i : l.i+n]}
	l.i += n
	return t
}

func (l *lexer) skipSpace() {
	for l.i < len(l.data) {
		if bytes.HasPrefix(l.data[l.i:], []byte("#")) || bytes.HasPrefix(l.data[l.i:], []byte("//")) {
			for ; l.i < len(l.data) && l.data[l.i] != '\n'; l.i++ {
			}
			continue
		}
		if bytes.HasPrefix(l.data[l.i:], []byte("/*")) {
			for ; l.i < len(l.data) && !bytes.HasPrefix(l.data[l.i:], []byte("*/")); l.i++ {
			}
			l.i += 2
			continue
		}
		if r, n := utf8.DecodeRune(l.data[l.i:]); unicode.IsSpace(r) {
			l.i += n
			continue
		}
		break
	}
}

func numFirstByte(b byte) bool {
	return b == '-' ||
		b == '+' ||
		b == '.' ||
		'0' <= b && b <= '9'
}

func numTailByte(b byte) bool {
	return numFirstByte(b) ||
		'a' <= b && b <= 'z' ||
		'A' <= b && b <= 'Z'
}

func fieldFirstByte(b byte) bool {
	return b == '_' ||
		'a' <= b && b <= 'z' ||
		'A' <= b && b <= 'Z'
}

func fieldTailByte(b byte) bool {
	return fieldFirstByte(b) ||
		'0' <= b && b <= '9'
}

func (l *lexer) next() (token, error) {
	l.skipSpace()
	if l.i == len(l.data) {
		return token{}, errEOF
	}
	switch l.data[l.i] {
	case
		'{',
		'}',
		'[',
		']',
		':',
		',':

		return l.yield(1), nil
	case '\'', '"':
		q := l.data[l.i]
		i := l.i + 1
		for ; i < len(l.data) && l.data[i] != q; i++ {
			if l.data[i] == '\\' {
				i++
			}
		}
		if i >= len(l.data) {
			return token{}, l.error("unterminated string")
		}
		return l.yield(i + 1 - l.i), nil
	}
	switch b := l.data[l.i]; {
	case numFirstByte(b):
		i := l.i + 1
		for ; i < len(l.data) && numTailByte(l.data[i]); i++ {
		}
		return l.yield(i - l.i), nil
	case fieldFirstByte(b):
		i := l.i + 1
		for ; i < len(l.data) && fieldTailByte(l.data[i]); i++ {
		}
		return l.yield(i - l.i), nil
	}
	return token{}, l.error("invalid lexeme")
}