-
Notifications
You must be signed in to change notification settings - Fork 0
/
scanner.go
171 lines (150 loc) · 3.29 KB
/
scanner.go
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
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
package interpol
import (
"fmt"
"strings"
"unicode/utf8"
)
type scantype int
// data types reported by the scanner
const (
teof scantype = iota
ttext
toperator
terror
)
type scanner struct {
input string
curr int
}
func (l *scanner) next() (string, scantype) {
//
for l.curr < len(l.input) {
rune, width := utf8.DecodeRuneInString(l.input[l.curr:])
if rune != ' ' && rune != '\t' && rune != ',' {
break
}
l.curr += width
}
start, qoute, meta := l.curr, false, false
for l.curr < len(l.input) {
rune, width := utf8.DecodeRuneInString(l.input[l.curr:])
if meta {
meta = false
} else if qoute {
if rune == '\'' || rune == '"' {
qoute = false
}
} else {
if rune == '\\' {
meta = true
} else if rune == '\'' || rune == '"' {
qoute = true
} else if rune == ' ' || rune == '\t' || rune == ',' || (rune == '=' && l.curr != start) {
break
} else if rune == '=' {
l.curr += width
break
}
}
l.curr += width
}
str := l.input[start:l.curr]
if start == l.curr {
return "", teof
}
// unexpected end
if qoute || meta {
return "", terror
}
if str == "=" {
return str, toperator
}
// remove qoute marks if any
str = removeQoute(str)
return str, ttext
}
func newScanner(str string) *scanner {
return &scanner{input: str}
}
// parseInterpolator parses the interpolator textElements.
// It is basically a betterversion of strings.Split() that handles ' and " and \
func parseInterpolator(text string) (*Parameters, error) {
ret := &Parameters{
Properties: make(map[string]string),
}
s := newScanner(text)
state := 0
varname := ""
for {
str, typ := s.next()
if typ == teof {
if state != 1 {
return nil, fmt.Errorf("Unexpected end in '%s'", text)
}
break
}
switch state {
case 0:
if typ != ttext {
return nil, fmt.Errorf("Expected type, got %s", str)
}
ret.Type = str
state = 1
case 1:
if typ == toperator {
state = 2
} else {
varname = str
ret.Properties[str] = ""
}
case 2:
if varname == "" {
return nil, fmt.Errorf("Unexpected '=' in '%s'", text)
}
ret.Properties[varname] = str
varname = ""
state = 1
}
}
return ret, nil
}
// textElement represents a sub-string that is either static or an interpolator
type textElement struct {
static bool
text string
}
// ParseLine divides a line into a number of textElements that
// are either a static string or an interpolator description
func parseLine(line string) ([]textElement, error) {
if len(line) == 0 {
return nil, fmt.Errorf("Empty line")
}
ret := make([]textElement, 0)
for len(line) > 0 {
n := strings.Index(line, "{{")
if n == -1 {
ret = append(ret, textElement{static: true, text: line})
line = ""
} else {
if n != 0 {
ret = append(ret, textElement{static: true, text: line[:n]})
}
line = line[n+2:]
m := strings.Index(line, "}}")
if m == -1 {
return nil, fmt.Errorf("Open {{ not closed")
}
ret = append(ret, textElement{static: false, text: line[:m]})
line = line[m+2:]
}
}
return ret, nil
}
func removeQoute(str string) string {
// remove qoute marks if any
if len(str) > 1 && ((strings.HasPrefix(str, "'") && strings.HasSuffix(str, "'")) ||
(strings.HasPrefix(str, "\"") && strings.HasSuffix(str, "\""))) {
str = str[1 : len(str)-1]
}
return str
}