diff options
Diffstat (limited to 'vendor/github.com/hashicorp/hcl2/hcl/hclsyntax/peeker.go')
-rw-r--r-- | vendor/github.com/hashicorp/hcl2/hcl/hclsyntax/peeker.go | 212 |
1 files changed, 212 insertions, 0 deletions
diff --git a/vendor/github.com/hashicorp/hcl2/hcl/hclsyntax/peeker.go b/vendor/github.com/hashicorp/hcl2/hcl/hclsyntax/peeker.go new file mode 100644 index 0000000..5a4b50e --- /dev/null +++ b/vendor/github.com/hashicorp/hcl2/hcl/hclsyntax/peeker.go | |||
@@ -0,0 +1,212 @@ | |||
1 | package hclsyntax | ||
2 | |||
3 | import ( | ||
4 | "bytes" | ||
5 | "fmt" | ||
6 | "path/filepath" | ||
7 | "runtime" | ||
8 | "strings" | ||
9 | |||
10 | "github.com/hashicorp/hcl2/hcl" | ||
11 | ) | ||
12 | |||
13 | // This is set to true at init() time in tests, to enable more useful output | ||
14 | // if a stack discipline error is detected. It should not be enabled in | ||
15 | // normal mode since there is a performance penalty from accessing the | ||
16 | // runtime stack to produce the traces, but could be temporarily set to | ||
17 | // true for debugging if desired. | ||
18 | var tracePeekerNewlinesStack = false | ||
19 | |||
20 | type peeker struct { | ||
21 | Tokens Tokens | ||
22 | NextIndex int | ||
23 | |||
24 | IncludeComments bool | ||
25 | IncludeNewlinesStack []bool | ||
26 | |||
27 | // used only when tracePeekerNewlinesStack is set | ||
28 | newlineStackChanges []peekerNewlineStackChange | ||
29 | } | ||
30 | |||
31 | // for use in debugging the stack usage only | ||
32 | type peekerNewlineStackChange struct { | ||
33 | Pushing bool // if false, then popping | ||
34 | Frame runtime.Frame | ||
35 | Include bool | ||
36 | } | ||
37 | |||
38 | func newPeeker(tokens Tokens, includeComments bool) *peeker { | ||
39 | return &peeker{ | ||
40 | Tokens: tokens, | ||
41 | IncludeComments: includeComments, | ||
42 | |||
43 | IncludeNewlinesStack: []bool{true}, | ||
44 | } | ||
45 | } | ||
46 | |||
47 | func (p *peeker) Peek() Token { | ||
48 | ret, _ := p.nextToken() | ||
49 | return ret | ||
50 | } | ||
51 | |||
52 | func (p *peeker) Read() Token { | ||
53 | ret, nextIdx := p.nextToken() | ||
54 | p.NextIndex = nextIdx | ||
55 | return ret | ||
56 | } | ||
57 | |||
58 | func (p *peeker) NextRange() hcl.Range { | ||
59 | return p.Peek().Range | ||
60 | } | ||
61 | |||
62 | func (p *peeker) PrevRange() hcl.Range { | ||
63 | if p.NextIndex == 0 { | ||
64 | return p.NextRange() | ||
65 | } | ||
66 | |||
67 | return p.Tokens[p.NextIndex-1].Range | ||
68 | } | ||
69 | |||
70 | func (p *peeker) nextToken() (Token, int) { | ||
71 | for i := p.NextIndex; i < len(p.Tokens); i++ { | ||
72 | tok := p.Tokens[i] | ||
73 | switch tok.Type { | ||
74 | case TokenComment: | ||
75 | if !p.IncludeComments { | ||
76 | // Single-line comment tokens, starting with # or //, absorb | ||
77 | // the trailing newline that terminates them as part of their | ||
78 | // bytes. When we're filtering out comments, we must as a | ||
79 | // special case transform these to newline tokens in order | ||
80 | // to properly parse newline-terminated block items. | ||
81 | |||
82 | if p.includingNewlines() { | ||
83 | if len(tok.Bytes) > 0 && tok.Bytes[len(tok.Bytes)-1] == '\n' { | ||
84 | fakeNewline := Token{ | ||
85 | Type: TokenNewline, | ||
86 | Bytes: tok.Bytes[len(tok.Bytes)-1 : len(tok.Bytes)], | ||
87 | |||
88 | // We use the whole token range as the newline | ||
89 | // range, even though that's a little... weird, | ||
90 | // because otherwise we'd need to go count | ||
91 | // characters again in order to figure out the | ||
92 | // column of the newline, and that complexity | ||
93 | // isn't justified when ranges of newlines are | ||
94 | // so rarely printed anyway. | ||
95 | Range: tok.Range, | ||
96 | } | ||
97 | return fakeNewline, i + 1 | ||
98 | } | ||
99 | } | ||
100 | |||
101 | continue | ||
102 | } | ||
103 | case TokenNewline: | ||
104 | if !p.includingNewlines() { | ||
105 | continue | ||
106 | } | ||
107 | } | ||
108 | |||
109 | return tok, i + 1 | ||
110 | } | ||
111 | |||
112 | // if we fall out here then we'll return the EOF token, and leave | ||
113 | // our index pointed off the end of the array so we'll keep | ||
114 | // returning EOF in future too. | ||
115 | return p.Tokens[len(p.Tokens)-1], len(p.Tokens) | ||
116 | } | ||
117 | |||
118 | func (p *peeker) includingNewlines() bool { | ||
119 | return p.IncludeNewlinesStack[len(p.IncludeNewlinesStack)-1] | ||
120 | } | ||
121 | |||
122 | func (p *peeker) PushIncludeNewlines(include bool) { | ||
123 | if tracePeekerNewlinesStack { | ||
124 | // Record who called us so that we can more easily track down any | ||
125 | // mismanagement of the stack in the parser. | ||
126 | callers := []uintptr{0} | ||
127 | runtime.Callers(2, callers) | ||
128 | frames := runtime.CallersFrames(callers) | ||
129 | frame, _ := frames.Next() | ||
130 | p.newlineStackChanges = append(p.newlineStackChanges, peekerNewlineStackChange{ | ||
131 | true, frame, include, | ||
132 | }) | ||
133 | } | ||
134 | |||
135 | p.IncludeNewlinesStack = append(p.IncludeNewlinesStack, include) | ||
136 | } | ||
137 | |||
138 | func (p *peeker) PopIncludeNewlines() bool { | ||
139 | stack := p.IncludeNewlinesStack | ||
140 | remain, ret := stack[:len(stack)-1], stack[len(stack)-1] | ||
141 | p.IncludeNewlinesStack = remain | ||
142 | |||
143 | if tracePeekerNewlinesStack { | ||
144 | // Record who called us so that we can more easily track down any | ||
145 | // mismanagement of the stack in the parser. | ||
146 | callers := []uintptr{0} | ||
147 | runtime.Callers(2, callers) | ||
148 | frames := runtime.CallersFrames(callers) | ||
149 | frame, _ := frames.Next() | ||
150 | p.newlineStackChanges = append(p.newlineStackChanges, peekerNewlineStackChange{ | ||
151 | false, frame, ret, | ||
152 | }) | ||
153 | } | ||
154 | |||
155 | return ret | ||
156 | } | ||
157 | |||
158 | // AssertEmptyNewlinesStack checks if the IncludeNewlinesStack is empty, doing | ||
159 | // panicking if it is not. This can be used to catch stack mismanagement that | ||
160 | // might otherwise just cause confusing downstream errors. | ||
161 | // | ||
162 | // This function is a no-op if the stack is empty when called. | ||
163 | // | ||
164 | // If newlines stack tracing is enabled by setting the global variable | ||
165 | // tracePeekerNewlinesStack at init time, a full log of all of the push/pop | ||
166 | // calls will be produced to help identify which caller in the parser is | ||
167 | // misbehaving. | ||
168 | func (p *peeker) AssertEmptyIncludeNewlinesStack() { | ||
169 | if len(p.IncludeNewlinesStack) != 1 { | ||
170 | // Should never happen; indicates mismanagement of the stack inside | ||
171 | // the parser. | ||
172 | if p.newlineStackChanges != nil { // only if traceNewlinesStack is enabled above | ||
173 | panic(fmt.Errorf( | ||
174 | "non-empty IncludeNewlinesStack after parse with %d calls unaccounted for:\n%s", | ||
175 | len(p.IncludeNewlinesStack)-1, | ||
176 | formatPeekerNewlineStackChanges(p.newlineStackChanges), | ||
177 | )) | ||
178 | } else { | ||
179 | panic(fmt.Errorf("non-empty IncludeNewlinesStack after parse: %#v", p.IncludeNewlinesStack)) | ||
180 | } | ||
181 | } | ||
182 | } | ||
183 | |||
184 | func formatPeekerNewlineStackChanges(changes []peekerNewlineStackChange) string { | ||
185 | indent := 0 | ||
186 | var buf bytes.Buffer | ||
187 | for _, change := range changes { | ||
188 | funcName := change.Frame.Function | ||
189 | if idx := strings.LastIndexByte(funcName, '.'); idx != -1 { | ||
190 | funcName = funcName[idx+1:] | ||
191 | } | ||
192 | filename := change.Frame.File | ||
193 | if idx := strings.LastIndexByte(filename, filepath.Separator); idx != -1 { | ||
194 | filename = filename[idx+1:] | ||
195 | } | ||
196 | |||
197 | switch change.Pushing { | ||
198 | |||
199 | case true: | ||
200 | buf.WriteString(strings.Repeat(" ", indent)) | ||
201 | fmt.Fprintf(&buf, "PUSH %#v (%s at %s:%d)\n", change.Include, funcName, filename, change.Frame.Line) | ||
202 | indent++ | ||
203 | |||
204 | case false: | ||
205 | indent-- | ||
206 | buf.WriteString(strings.Repeat(" ", indent)) | ||
207 | fmt.Fprintf(&buf, "POP %#v (%s at %s:%d)\n", change.Include, funcName, filename, change.Frame.Line) | ||
208 | |||
209 | } | ||
210 | } | ||
211 | return buf.String() | ||
212 | } | ||