-
Notifications
You must be signed in to change notification settings - Fork 23
/
Copy pathwildcard.go
131 lines (122 loc) · 3.68 KB
/
wildcard.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
package quamina
import (
"encoding/json"
"errors"
"fmt"
)
type wcState int
const (
wcChilling wcState = iota
wcAfterBS
wcAfterGlob
)
func readWildcardSpecial(pb *patternBuild, valsIn []typedVal) ([]typedVal, error) {
t, err := pb.jd.Token()
if err != nil {
return nil, err
}
pathVals := valsIn
wcInput, ok := t.(string)
if !ok {
return nil, errors.New("value for `wildcard` must be a string")
}
inBytes := []byte(wcInput)
state := wcChilling
for i, b := range inBytes {
switch state {
case wcChilling:
switch b {
case '\\':
if i == len(inBytes)-1 {
return nil, errors.New("'\\' at end of string not allowed")
}
state = wcAfterBS
case '*':
state = wcAfterGlob
}
case wcAfterBS:
switch b {
case '\\', '*':
state = wcChilling
default:
return nil, errors.New("`\\` can only be followed by '\\' or '*'")
}
case wcAfterGlob:
switch b {
case '*':
return nil, fmt.Errorf("adjacent '*' characters not allowed")
case '\\':
state = wcAfterBS
default:
state = wcChilling
}
}
}
pathVals = append(pathVals, typedVal{vType: wildcardType, val: `"` + wcInput + `"`})
t, err = pb.jd.Token()
if err != nil {
return nil, err
}
switch t.(type) {
case json.Delim:
// } is all that will be returned
default:
return nil, errors.New("trailing garbage in wildcard pattern")
}
return pathVals, nil
}
// makeWildcardFA is a replacement for shellstyle patterns, the only difference being that escaping is
// provided for * and \.
func makeWildcardFA(val []byte, printer printer) (start *smallTable, nextField *fieldMatcher) {
table := newSmallTable()
start = table
nextField = newFieldMatcher()
// for each byte in the pattern. \-escape processing is simplified because illegal constructs such as \a and \
// at the end of the value have been rejected by readWildcardSpecial.
valIndex := 0
for valIndex < len(val) {
ch := val[valIndex]
escaped := ch == '\\'
if escaped {
valIndex++
ch = val[valIndex]
}
if ch == '*' && !escaped {
// special-case handling for string ending in '*"' - transition to field match on any character.
// we know the trailing '"' will be there because of JSON syntax. We could use an epsilon state
// but then the matcher will process through all the rest of the bytes, when it doesn't need to
if valIndex == len(val)-2 {
step := &faState{
table: newSmallTable(),
fieldTransitions: []*fieldMatcher{nextField},
}
table.epsilon = []*faState{step}
printer.labelTable(table, fmt.Sprintf("prefix escape at %d", valIndex))
return
}
globStep := &faState{table: table}
printer.labelTable(table, fmt.Sprintf("gS at %d", valIndex))
table.epsilon = []*faState{globStep}
valIndex++
// ** is forbidden, if we're seeing *\* then the second * is non-magic, if we're seeing *\\, it
// just means \, so either way, all we need to do is hop over this \
if val[valIndex] == '\\' {
valIndex++
}
globNext := &faState{table: newSmallTable()}
printer.labelTable(globNext.table, fmt.Sprintf("gX on %c at %d", val[valIndex], valIndex))
table.addByteStep(val[valIndex], &faNext{states: []*faState{globNext}})
table = globNext.table
} else {
nextStep := &faState{table: newSmallTable()}
printer.labelTable(nextStep.table, fmt.Sprintf("on %c at %d", val[valIndex], valIndex))
table.addByteStep(ch, &faNext{states: []*faState{nextStep}})
table = nextStep.table
}
valIndex++
}
lastStep := &faState{table: newSmallTable(), fieldTransitions: []*fieldMatcher{nextField}}
printer.labelTable(lastStep.table, fmt.Sprintf("last step at %d", valIndex))
table.addByteStep(valueTerminator, &faNext{states: []*faState{lastStep}})
return
}