]> git.immae.eu Git - github/fretlink/terraform-provider-statuscake.git/blob - vendor/github.com/hashicorp/hcl2/hcl/hclsyntax/peeker.go
Merge branch 'fix_read_test' of github.com:alexandreFre/terraform-provider-statuscake
[github/fretlink/terraform-provider-statuscake.git] / vendor / github.com / hashicorp / hcl2 / hcl / hclsyntax / peeker.go
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 }