-
Notifications
You must be signed in to change notification settings - Fork 0
/
lift.go
195 lines (158 loc) · 4.25 KB
/
lift.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
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
package expr
import (
"fmt"
"strconv"
"strings"
)
const (
// VarPrefix is the lifted variable name used when extracting idents from an
// expression.
VarPrefix = "vars"
)
var (
// replace is truly hack city. these are 20 variable names for values that are
// lifted out of expressions via liftLiterals.
replace = []string{
"a", "b", "c", "d", "e",
"f", "g", "h", "i", "j",
"k", "l", "m", "n", "o",
"p", "q", "r", "s", "t",
"u", "v", "w", "x", "y",
"z",
}
)
// LiftedArgs represents a set of variables that have been lifted from expressions and
// replaced with identifiers, eg `id == "foo"` becomes `id == vars.a`, with "foo" lifted
// as "vars.a".
type LiftedArgs interface {
// Get a lifted variable argument from the parsed expression.
Get(val string) (any, bool)
// Return all lifted variables as a map.
Map() map[string]any
}
// liftLiterals lifts quoted literals into variables, allowing us to normalize
// expressions to increase cache hit rates.
func liftLiterals(expr string) (string, LiftedArgs) {
if strings.Contains(expr, VarPrefix+".") {
// Do not lift an expression twice, else we run the risk of using
// eg. `vars.a` to reference two separate strings, breaking the
// expression.
return expr, nil
}
// TODO: Lift numeric literals out of expressions.
lp := liftParser{expr: expr}
return lp.lift()
}
type liftParser struct {
expr string
idx int
rewritten *strings.Builder
// varCounter counts the number of variables lifted.
varCounter int
vars pointerArgMap
}
func (l *liftParser) lift() (string, LiftedArgs) {
l.vars = pointerArgMap{
expr: l.expr,
vars: map[string]argMapValue{},
}
l.rewritten = &strings.Builder{}
for l.idx < len(l.expr) {
char := l.expr[l.idx]
l.idx++
switch char {
case '"':
// Consume the string arg.
val := l.consumeString('"')
l.addLiftedVar(val)
case '\'':
val := l.consumeString('\'')
l.addLiftedVar(val)
default:
l.rewritten.WriteByte(char)
}
}
return l.rewritten.String(), &l.vars
}
func (l *liftParser) addLiftedVar(val argMapValue) {
if l.varCounter >= len(replace) {
// Do nothing.
str := val.get(l.expr)
l.rewritten.WriteString(strconv.Quote(str.(string)))
return
}
letter := replace[l.varCounter]
l.vars.vars[letter] = val
l.varCounter++
l.rewritten.WriteString(VarPrefix + "." + letter)
}
func (l *liftParser) consumeString(quoteChar byte) argMapValue {
offset := l.idx
length := 0
for l.idx < len(l.expr) {
char := l.expr[l.idx]
if char == '\\' && l.peek() == quoteChar {
// If we're escaping the quote character, ignore it.
l.idx += 2
length += 2
continue
}
if char == quoteChar {
// Skip over the end quote.
l.idx++
// Return the substring offset/length
return argMapValue{offset, length}
}
// Grab the next char for evaluation.
l.idx++
// Only now has the length of the inner quote increased.
length++
}
// Should never happen: we should always find the ending string quote, as the
// expression should have already been validated.
panic(fmt.Sprintf("unable to parse quoted string: `%s` (offset %d)", l.expr, offset))
}
func (l *liftParser) peek() byte {
if (l.idx + 1) >= len(l.expr) {
return 0x0
}
return l.expr[l.idx+1]
}
// pointerArgMap takes the original expression, and adds pointers to the original expression
// in order to grab variables.
//
// It does this by pointing to the offset and length of data within the expression, as opposed
// to extracting the value into a new string. This greatly reduces memory growth & heap allocations.
type pointerArgMap struct {
expr string
vars map[string]argMapValue
}
func (p pointerArgMap) Map() map[string]any {
res := map[string]any{}
for k, v := range p.vars {
res[k] = v.get(p.expr)
}
return res
}
func (p pointerArgMap) Get(key string) (any, bool) {
val, ok := p.vars[key]
if !ok {
return nil, false
}
data := val.get(p.expr)
return data, true
}
// argMapValue represents an offset and length for an argument in an expression string
type argMapValue [2]int
func (a argMapValue) get(expr string) any {
data := expr[a[0] : a[0]+a[1]]
return data
}
type regularArgMap map[string]any
func (p regularArgMap) Get(key string) (any, bool) {
val, ok := p[key]
return val, ok
}
func (p regularArgMap) Map() map[string]any {
return p
}