hcl-checker 1.6.2 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +27 -0
- data/.gitignore +1 -0
- data/.ruby-version +1 -0
- data/Gemfile +0 -9
- data/Gemfile.lock +27 -40
- data/README.md +124 -20
- data/assets/lexer.rex +120 -20
- data/assets/parse.y +174 -26
- data/bin/setup +6 -0
- data/hcl-checker.gemspec +5 -15
- data/lib/hcl/checker/lexer.rb +119 -21
- data/lib/hcl/checker/parser.rb +785 -155
- data/lib/hcl/checker/version.rb +1 -1
- data/lib/hcl/checker.rb +3 -6
- metadata +16 -39
- data/.travis.yml +0 -5
data/assets/parse.y
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
class HCL::Checker::Parser
|
|
2
2
|
token BOOL
|
|
3
|
+
NULL
|
|
3
4
|
FLOAT
|
|
4
5
|
NUMBER
|
|
5
6
|
COMMA
|
|
@@ -8,6 +9,25 @@ token BOOL
|
|
|
8
9
|
EQUAL
|
|
9
10
|
STRING
|
|
10
11
|
MINUS
|
|
12
|
+
PLUS
|
|
13
|
+
STAR
|
|
14
|
+
SLASH
|
|
15
|
+
PERCENT
|
|
16
|
+
ANDAND
|
|
17
|
+
OROR
|
|
18
|
+
EQEQ
|
|
19
|
+
NEQ
|
|
20
|
+
LT
|
|
21
|
+
GT
|
|
22
|
+
LE
|
|
23
|
+
GE
|
|
24
|
+
BANG
|
|
25
|
+
QUESTION
|
|
26
|
+
COLON
|
|
27
|
+
ARROW
|
|
28
|
+
FOR
|
|
29
|
+
IN
|
|
30
|
+
IF
|
|
11
31
|
LEFTBRACE
|
|
12
32
|
RIGHTBRACE
|
|
13
33
|
LEFTBRACKET
|
|
@@ -18,6 +38,18 @@ token BOOL
|
|
|
18
38
|
EPLUS
|
|
19
39
|
EMINUS
|
|
20
40
|
|
|
41
|
+
prechigh
|
|
42
|
+
nonassoc UMINUS
|
|
43
|
+
right BANG
|
|
44
|
+
left STAR SLASH PERCENT
|
|
45
|
+
left PLUS MINUS
|
|
46
|
+
nonassoc LT GT LE GE
|
|
47
|
+
nonassoc EQEQ NEQ
|
|
48
|
+
left ANDAND
|
|
49
|
+
left OROR
|
|
50
|
+
right QUESTION COLON
|
|
51
|
+
preclow
|
|
52
|
+
|
|
21
53
|
rule
|
|
22
54
|
target: top { @result = flatten_objectlist(val[0])}
|
|
23
55
|
|
|
@@ -42,6 +74,15 @@ rule
|
|
|
42
74
|
{ result = flatten_objectlist(val[1]) }
|
|
43
75
|
| LEFTBRACE RIGHTBRACE
|
|
44
76
|
{ return {} }
|
|
77
|
+
| LEFTBRACE forobjectexpr RIGHTBRACE
|
|
78
|
+
{ result = val[1] }
|
|
79
|
+
;
|
|
80
|
+
|
|
81
|
+
forobjectexpr:
|
|
82
|
+
FOR foriter COLON expr ARROW expr
|
|
83
|
+
{ result = "for #{val[1]} : #{render(val[3])} => #{render(val[5])}" }
|
|
84
|
+
| FOR foriter COLON expr ARROW expr IF expr
|
|
85
|
+
{ result = "for #{val[1]} : #{render(val[3])} => #{render(val[5])} if #{render(val[7])}" }
|
|
45
86
|
;
|
|
46
87
|
|
|
47
88
|
objectkey:
|
|
@@ -49,27 +90,103 @@ rule
|
|
|
49
90
|
{ result = val[0] }
|
|
50
91
|
| STRING
|
|
51
92
|
{ result = val[0] }
|
|
93
|
+
| FOR
|
|
94
|
+
{ result = val[0] }
|
|
95
|
+
| IN
|
|
96
|
+
{ result = val[0] }
|
|
97
|
+
| IF
|
|
98
|
+
{ result = val[0] }
|
|
99
|
+
| NULL
|
|
100
|
+
{ result = val[0] }
|
|
52
101
|
;
|
|
53
102
|
|
|
54
103
|
objectitem:
|
|
55
|
-
objectkey EQUAL
|
|
56
|
-
{ result = val[0], val[2] }
|
|
57
|
-
| objectkey EQUAL BOOL
|
|
58
|
-
{ result = val[0], val[2] }
|
|
59
|
-
| objectkey EQUAL STRING
|
|
60
|
-
{ result = val[0], val[2] }
|
|
61
|
-
| objectkey EQUAL object
|
|
62
|
-
{ result = val[0], val[2] }
|
|
63
|
-
| objectkey EQUAL list
|
|
64
|
-
{ result = val[0], val[2] }
|
|
65
|
-
| objectkey EQUAL IDENTIFIER LEFTPARENTHESES IDENTIFIER RIGHTPARENTHESES
|
|
66
|
-
{ result = val[0], "#{val[2]}(#{val[4]})" }
|
|
67
|
-
| objectkey EQUAL IDENTIFIER
|
|
104
|
+
objectkey EQUAL expr
|
|
68
105
|
{ result = val[0], val[2] }
|
|
69
106
|
| block
|
|
70
107
|
{ result = val[0] }
|
|
71
108
|
;
|
|
72
109
|
|
|
110
|
+
expr:
|
|
111
|
+
primary
|
|
112
|
+
{ result = val[0] }
|
|
113
|
+
| expr PLUS expr
|
|
114
|
+
{ result = "#{render(val[0])} + #{render(val[2])}" }
|
|
115
|
+
| expr MINUS expr
|
|
116
|
+
{ result = "#{render(val[0])} - #{render(val[2])}" }
|
|
117
|
+
| expr STAR expr
|
|
118
|
+
{ result = "#{render(val[0])} * #{render(val[2])}" }
|
|
119
|
+
| expr SLASH expr
|
|
120
|
+
{ result = "#{render(val[0])} / #{render(val[2])}" }
|
|
121
|
+
| expr PERCENT expr
|
|
122
|
+
{ result = "#{render(val[0])} % #{render(val[2])}" }
|
|
123
|
+
| expr ANDAND expr
|
|
124
|
+
{ result = "#{render(val[0])} && #{render(val[2])}" }
|
|
125
|
+
| expr OROR expr
|
|
126
|
+
{ result = "#{render(val[0])} || #{render(val[2])}" }
|
|
127
|
+
| expr EQEQ expr
|
|
128
|
+
{ result = "#{render(val[0])} == #{render(val[2])}" }
|
|
129
|
+
| expr NEQ expr
|
|
130
|
+
{ result = "#{render(val[0])} != #{render(val[2])}" }
|
|
131
|
+
| expr LT expr
|
|
132
|
+
{ result = "#{render(val[0])} < #{render(val[2])}" }
|
|
133
|
+
| expr GT expr
|
|
134
|
+
{ result = "#{render(val[0])} > #{render(val[2])}" }
|
|
135
|
+
| expr LE expr
|
|
136
|
+
{ result = "#{render(val[0])} <= #{render(val[2])}" }
|
|
137
|
+
| expr GE expr
|
|
138
|
+
{ result = "#{render(val[0])} >= #{render(val[2])}" }
|
|
139
|
+
| expr QUESTION expr COLON expr
|
|
140
|
+
{ result = "#{render(val[0])} ? #{render(val[2])} : #{render(val[4])}" }
|
|
141
|
+
| BANG expr
|
|
142
|
+
{ result = "!#{render(val[1])}" }
|
|
143
|
+
| MINUS expr =UMINUS
|
|
144
|
+
{ result = negate(val[1]) }
|
|
145
|
+
;
|
|
146
|
+
|
|
147
|
+
primary:
|
|
148
|
+
traversal
|
|
149
|
+
{ result = val[0] }
|
|
150
|
+
| number
|
|
151
|
+
{ result = val[0] }
|
|
152
|
+
| BOOL
|
|
153
|
+
{ result = val[0] }
|
|
154
|
+
| NULL
|
|
155
|
+
{ result = nil }
|
|
156
|
+
| STRING
|
|
157
|
+
{ result = val[0] }
|
|
158
|
+
| list
|
|
159
|
+
{ result = val[0] }
|
|
160
|
+
| object
|
|
161
|
+
{ result = val[0] }
|
|
162
|
+
| LEFTPARENTHESES expr RIGHTPARENTHESES
|
|
163
|
+
{ result = "(#{render(val[1])})" }
|
|
164
|
+
;
|
|
165
|
+
|
|
166
|
+
traversal:
|
|
167
|
+
IDENTIFIER
|
|
168
|
+
{ result = val[0] }
|
|
169
|
+
| traversal PERIOD IDENTIFIER
|
|
170
|
+
{ result = "#{val[0]}.#{val[2]}" }
|
|
171
|
+
| traversal PERIOD STAR
|
|
172
|
+
{ result = "#{val[0]}.*" }
|
|
173
|
+
| traversal LEFTBRACKET expr RIGHTBRACKET
|
|
174
|
+
{ result = "#{val[0]}[#{render(val[2])}]" }
|
|
175
|
+
| traversal LEFTBRACKET STAR RIGHTBRACKET
|
|
176
|
+
{ result = "#{val[0]}[*]" }
|
|
177
|
+
| traversal LEFTPARENTHESES RIGHTPARENTHESES
|
|
178
|
+
{ result = "#{val[0]}()" }
|
|
179
|
+
| traversal LEFTPARENTHESES arglist RIGHTPARENTHESES
|
|
180
|
+
{ result = "#{val[0]}(#{val[2].map { |a| render(a) }.join(', ')})" }
|
|
181
|
+
;
|
|
182
|
+
|
|
183
|
+
arglist:
|
|
184
|
+
expr
|
|
185
|
+
{ result = [val[0]] }
|
|
186
|
+
| arglist COMMA expr
|
|
187
|
+
{ result = val[0] << val[2] }
|
|
188
|
+
;
|
|
189
|
+
|
|
73
190
|
block:
|
|
74
191
|
block_id object
|
|
75
192
|
{ result = val[0], val[1] }
|
|
@@ -82,6 +199,14 @@ rule
|
|
|
82
199
|
{ result = val[0] }
|
|
83
200
|
| STRING
|
|
84
201
|
{ result = val[0] }
|
|
202
|
+
| FOR
|
|
203
|
+
{ result = val[0] }
|
|
204
|
+
| IN
|
|
205
|
+
{ result = val[0] }
|
|
206
|
+
| IF
|
|
207
|
+
{ result = val[0] }
|
|
208
|
+
| NULL
|
|
209
|
+
{ result = val[0] }
|
|
85
210
|
;
|
|
86
211
|
|
|
87
212
|
list:
|
|
@@ -89,6 +214,22 @@ rule
|
|
|
89
214
|
{ result = val[1] }
|
|
90
215
|
| LEFTBRACKET RIGHTBRACKET
|
|
91
216
|
{ return [] }
|
|
217
|
+
| LEFTBRACKET forlistexpr RIGHTBRACKET
|
|
218
|
+
{ result = val[1] }
|
|
219
|
+
;
|
|
220
|
+
|
|
221
|
+
forlistexpr:
|
|
222
|
+
FOR foriter COLON expr
|
|
223
|
+
{ result = "for #{val[1]} : #{render(val[3])}" }
|
|
224
|
+
| FOR foriter COLON expr IF expr
|
|
225
|
+
{ result = "for #{val[1]} : #{render(val[3])} if #{render(val[5])}" }
|
|
226
|
+
;
|
|
227
|
+
|
|
228
|
+
foriter:
|
|
229
|
+
IDENTIFIER IN expr
|
|
230
|
+
{ result = "#{val[0]} in #{render(val[2])}" }
|
|
231
|
+
| IDENTIFIER COMMA IDENTIFIER IN expr
|
|
232
|
+
{ result = "#{val[0]}, #{val[2]} in #{render(val[4])}" }
|
|
92
233
|
;
|
|
93
234
|
|
|
94
235
|
listitems:
|
|
@@ -101,15 +242,7 @@ rule
|
|
|
101
242
|
;
|
|
102
243
|
|
|
103
244
|
listitem:
|
|
104
|
-
|
|
105
|
-
{ result = val[0] }
|
|
106
|
-
| STRING
|
|
107
|
-
{ result = val[0] }
|
|
108
|
-
| list
|
|
109
|
-
{ result = val[0] }
|
|
110
|
-
| object
|
|
111
|
-
{ result = val[0] }
|
|
112
|
-
| IDENTIFIER
|
|
245
|
+
expr
|
|
113
246
|
{ result = val[0] }
|
|
114
247
|
;
|
|
115
248
|
|
|
@@ -162,15 +295,21 @@ require_relative 'lexer'
|
|
|
162
295
|
|
|
163
296
|
|
|
164
297
|
def on_error(error_token_id, error_value, value_stack)
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
298
|
+
line, column = @current_position
|
|
299
|
+
message = "Parse error on line #{line}, column #{column}: unexpected #{error_value.inspect}"
|
|
300
|
+
message += " (after #{value_stack.last.inspect})" unless value_stack.empty?
|
|
301
|
+
|
|
302
|
+
raise Racc::ParseError, message
|
|
168
303
|
end
|
|
169
304
|
|
|
170
305
|
|
|
171
306
|
def parse(input, duplicate_mode = :array)
|
|
172
307
|
@duplicate_mode = duplicate_mode
|
|
173
|
-
|
|
308
|
+
|
|
309
|
+
lexer = HCL::Checker::Lexer.new
|
|
310
|
+
@lexer = lexer.lex(input)
|
|
311
|
+
@lexer_positions = lexer.token_positions
|
|
312
|
+
|
|
174
313
|
do_parse
|
|
175
314
|
|
|
176
315
|
@result
|
|
@@ -178,9 +317,18 @@ require_relative 'lexer'
|
|
|
178
317
|
|
|
179
318
|
|
|
180
319
|
def next_token
|
|
320
|
+
@current_position = @lexer_positions.shift
|
|
181
321
|
@lexer.shift
|
|
182
322
|
end
|
|
183
323
|
|
|
324
|
+
def negate(value)
|
|
325
|
+
value.is_a?(Numeric) ? -value : "-#{render(value)}"
|
|
326
|
+
end
|
|
327
|
+
|
|
328
|
+
def render(value)
|
|
329
|
+
value.nil? ? 'null' : value.to_s
|
|
330
|
+
end
|
|
331
|
+
|
|
184
332
|
def deep_merge(hash1, hash2)
|
|
185
333
|
hash2.keys.each do |key|
|
|
186
334
|
value1 = hash1[key]
|
data/bin/setup
CHANGED
|
@@ -3,6 +3,12 @@ set -euo pipefail
|
|
|
3
3
|
IFS=$'\n\t'
|
|
4
4
|
set -vx
|
|
5
5
|
|
|
6
|
+
# Installs gems into ./vendor/bundle instead of the system gem directory.
|
|
7
|
+
# On a Ruby installed via a distro package (e.g. apt's ruby3.3), the system
|
|
8
|
+
# gem directory is usually root-owned, so a plain `bundle install` fails
|
|
9
|
+
# with a Bundler::PermissionError unless this repo is configured to use a
|
|
10
|
+
# local, writable path instead.
|
|
11
|
+
bundle config set --local path 'vendor/bundle'
|
|
6
12
|
bundle install
|
|
7
13
|
|
|
8
14
|
# Do any other automated setup that you need to do here
|
data/hcl-checker.gemspec
CHANGED
|
@@ -17,21 +17,11 @@ Gem::Specification.new do |spec|
|
|
|
17
17
|
f.match(%r{^(test|spec|features)/})
|
|
18
18
|
end
|
|
19
19
|
spec.require_paths = ['lib']
|
|
20
|
-
spec.required_ruby_version = '>=
|
|
20
|
+
spec.required_ruby_version = '>= 3.3'
|
|
21
21
|
|
|
22
|
-
spec.add_development_dependency 'bundler', '~> 2.
|
|
23
|
-
spec.add_development_dependency 'racc', '1.
|
|
24
|
-
spec.add_development_dependency 'rake', '~>
|
|
25
|
-
spec.add_development_dependency '
|
|
26
|
-
spec.add_development_dependency 'rexical', '>= 1.0.7'
|
|
22
|
+
spec.add_development_dependency 'bundler', '~> 2.4'
|
|
23
|
+
spec.add_development_dependency 'racc', '~> 1.8'
|
|
24
|
+
spec.add_development_dependency 'rake', '~> 13.0'
|
|
25
|
+
spec.add_development_dependency 'rexical', '>= 1.0.8'
|
|
27
26
|
spec.add_development_dependency 'rspec', '~> 3.0'
|
|
28
|
-
|
|
29
|
-
spec.post_install_message = '
|
|
30
|
-
Hello, me again. This version fixes:
|
|
31
|
-
|
|
32
|
-
- Update dependencies due security issues
|
|
33
|
-
- Support interpolation only expression list
|
|
34
|
-
|
|
35
|
-
Thank you :)
|
|
36
|
-
'
|
|
37
27
|
end
|
data/lib/hcl/checker/lexer.rb
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#--
|
|
2
2
|
# DO NOT MODIFY!!!!
|
|
3
|
-
# This file is automatically generated by rex 1.0.
|
|
3
|
+
# This file is automatically generated by rex 1.0.8
|
|
4
4
|
# from lexical definition file "./assets/lexer.rex".
|
|
5
5
|
#++
|
|
6
6
|
|
|
@@ -71,17 +71,29 @@ class HCL::Checker::Lexer
|
|
|
71
71
|
when (text = @ss.scan(/true|false/))
|
|
72
72
|
action { [:BOOL, to_boolean(text)]}
|
|
73
73
|
|
|
74
|
-
when (text = @ss.scan(
|
|
74
|
+
when (text = @ss.scan(/null(?![a-zA-Z0-9_\-])/))
|
|
75
|
+
action { [:NULL, text]}
|
|
76
|
+
|
|
77
|
+
when (text = @ss.scan(/for(?![a-zA-Z0-9_\-])/))
|
|
78
|
+
action { [:FOR, text]}
|
|
79
|
+
|
|
80
|
+
when (text = @ss.scan(/in(?![a-zA-Z0-9_\-])/))
|
|
81
|
+
action { [:IN, text]}
|
|
82
|
+
|
|
83
|
+
when (text = @ss.scan(/if(?![a-zA-Z0-9_\-])/))
|
|
84
|
+
action { [:IF, text]}
|
|
85
|
+
|
|
86
|
+
when (text = @ss.scan(/\d+(\.\d+)?[eE][-+]?\d+|\d+\.\d+/))
|
|
75
87
|
action { [:FLOAT, text.to_f] }
|
|
76
88
|
|
|
77
|
-
when (text = @ss.scan(
|
|
89
|
+
when (text = @ss.scan(/\d+/))
|
|
78
90
|
action { [:NUMBER, text.to_i] }
|
|
79
91
|
|
|
80
92
|
when (text = @ss.scan(/\"/))
|
|
81
93
|
action { [:STRING, consume_string(text)] }
|
|
82
94
|
|
|
83
95
|
when (text = @ss.scan(/\<<\-?/))
|
|
84
|
-
action { [:STRING, consume_heredoc] }
|
|
96
|
+
action { [:STRING, consume_heredoc(text.end_with?('-'))] }
|
|
85
97
|
|
|
86
98
|
when (text = @ss.scan(/\{/))
|
|
87
99
|
action { [:LEFTBRACE, text]}
|
|
@@ -104,15 +116,66 @@ class HCL::Checker::Lexer
|
|
|
104
116
|
when (text = @ss.scan(/\,/))
|
|
105
117
|
action { [:COMMA, text]}
|
|
106
118
|
|
|
107
|
-
when (text = @ss.scan(/[a-zA-Z_][a-zA-Z0-9_
|
|
119
|
+
when (text = @ss.scan(/[a-zA-Z_][a-zA-Z0-9_\-]*/))
|
|
108
120
|
action { [:IDENTIFIER, text]}
|
|
109
121
|
|
|
122
|
+
when (text = @ss.scan(/\=\=/))
|
|
123
|
+
action { [:EQEQ, text]}
|
|
124
|
+
|
|
125
|
+
when (text = @ss.scan(/\!\=/))
|
|
126
|
+
action { [:NEQ, text]}
|
|
127
|
+
|
|
128
|
+
when (text = @ss.scan(/\<\=/))
|
|
129
|
+
action { [:LE, text]}
|
|
130
|
+
|
|
131
|
+
when (text = @ss.scan(/\>\=/))
|
|
132
|
+
action { [:GE, text]}
|
|
133
|
+
|
|
134
|
+
when (text = @ss.scan(/\=\>/))
|
|
135
|
+
action { [:ARROW, text]}
|
|
136
|
+
|
|
137
|
+
when (text = @ss.scan(/\</))
|
|
138
|
+
action { [:LT, text]}
|
|
139
|
+
|
|
140
|
+
when (text = @ss.scan(/\>/))
|
|
141
|
+
action { [:GT, text]}
|
|
142
|
+
|
|
110
143
|
when (text = @ss.scan(/\=/))
|
|
111
144
|
action { [:EQUAL, text]}
|
|
112
145
|
|
|
113
146
|
when (text = @ss.scan(/\-/))
|
|
114
147
|
action { [:MINUS, text]}
|
|
115
148
|
|
|
149
|
+
when (text = @ss.scan(/\+/))
|
|
150
|
+
action { [:PLUS, text]}
|
|
151
|
+
|
|
152
|
+
when (text = @ss.scan(/\*/))
|
|
153
|
+
action { [:STAR, text]}
|
|
154
|
+
|
|
155
|
+
when (text = @ss.scan(/\//))
|
|
156
|
+
action { [:SLASH, text]}
|
|
157
|
+
|
|
158
|
+
when (text = @ss.scan(/\%/))
|
|
159
|
+
action { [:PERCENT, text]}
|
|
160
|
+
|
|
161
|
+
when (text = @ss.scan(/\&\&/))
|
|
162
|
+
action { [:ANDAND, text]}
|
|
163
|
+
|
|
164
|
+
when (text = @ss.scan(/\|\|/))
|
|
165
|
+
action { [:OROR, text]}
|
|
166
|
+
|
|
167
|
+
when (text = @ss.scan(/\?/))
|
|
168
|
+
action { [:QUESTION, text]}
|
|
169
|
+
|
|
170
|
+
when (text = @ss.scan(/\:/))
|
|
171
|
+
action { [:COLON, text]}
|
|
172
|
+
|
|
173
|
+
when (text = @ss.scan(/\./))
|
|
174
|
+
action { [:PERIOD, text]}
|
|
175
|
+
|
|
176
|
+
when (text = @ss.scan(/\!/))
|
|
177
|
+
action { [:BANG, text]}
|
|
178
|
+
|
|
116
179
|
|
|
117
180
|
else
|
|
118
181
|
text = @ss.string[@ss.pos .. -1]
|
|
@@ -125,34 +188,36 @@ class HCL::Checker::Lexer
|
|
|
125
188
|
token
|
|
126
189
|
end # def _next_token
|
|
127
190
|
|
|
191
|
+
attr_reader :token_positions
|
|
128
192
|
def lex(input)
|
|
129
193
|
scan_setup(input)
|
|
130
194
|
tokens = []
|
|
195
|
+
@token_positions = []
|
|
131
196
|
while token = next_token
|
|
132
197
|
tokens << token
|
|
198
|
+
@token_positions << current_position
|
|
133
199
|
end
|
|
134
200
|
tokens
|
|
135
201
|
end
|
|
202
|
+
def current_position
|
|
203
|
+
consumed = @ss.string[0...@ss.pos]
|
|
204
|
+
line = consumed.count("\n") + 1
|
|
205
|
+
column = consumed.length - (consumed.rindex("\n") || -1)
|
|
206
|
+
[line, column]
|
|
207
|
+
end
|
|
136
208
|
def to_boolean(input)
|
|
137
|
-
|
|
138
|
-
when /true/
|
|
139
|
-
true
|
|
140
|
-
when /false/
|
|
141
|
-
false
|
|
142
|
-
else
|
|
143
|
-
raise "Invalid value for `to_boolean`, expected true/false got #{input}"
|
|
144
|
-
end
|
|
209
|
+
input == 'true'
|
|
145
210
|
end
|
|
146
211
|
def consume_comment(input)
|
|
147
212
|
nested = 1
|
|
148
213
|
until nested.zero?
|
|
149
214
|
case(text = @ss.scan_until(%r{/\*|\*/|\z}) )
|
|
150
215
|
when %r{/\*\z}
|
|
151
|
-
nested
|
|
216
|
+
nested += 1
|
|
152
217
|
when %r{\*/\z}
|
|
153
218
|
nested -= 1
|
|
154
219
|
else
|
|
155
|
-
|
|
220
|
+
raise ScanError, 'unterminated block comment'
|
|
156
221
|
end
|
|
157
222
|
end
|
|
158
223
|
end
|
|
@@ -160,13 +225,17 @@ class HCL::Checker::Lexer
|
|
|
160
225
|
result = ''
|
|
161
226
|
nested = 0
|
|
162
227
|
loop do
|
|
163
|
-
|
|
228
|
+
text = @ss.scan_until(%r{\"|\$\{|%\{|\}|\\})
|
|
229
|
+
raise ScanError, 'unterminated string' if text.nil?
|
|
230
|
+
case text
|
|
164
231
|
when %r{\$\{\z}
|
|
165
232
|
nested += 1
|
|
233
|
+
when %r{%\{\z}
|
|
234
|
+
nested += 1
|
|
166
235
|
when %r{\}\z}
|
|
167
236
|
nested -= 1 if nested.positive?
|
|
168
237
|
when %r{\\\z}
|
|
169
|
-
result += text.chop +
|
|
238
|
+
result += text.chop + decode_escape
|
|
170
239
|
next
|
|
171
240
|
end
|
|
172
241
|
result += text.to_s
|
|
@@ -174,9 +243,38 @@ class HCL::Checker::Lexer
|
|
|
174
243
|
end
|
|
175
244
|
result.chop
|
|
176
245
|
end
|
|
177
|
-
def
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
246
|
+
def decode_escape
|
|
247
|
+
char = @ss.getch
|
|
248
|
+
case char
|
|
249
|
+
when 'n' then "\n"
|
|
250
|
+
when 't' then "\t"
|
|
251
|
+
when 'r' then "\r"
|
|
252
|
+
when '"' then '"'
|
|
253
|
+
when '\\' then '\\'
|
|
254
|
+
when 'u'
|
|
255
|
+
hex = @ss.scan(/[0-9a-fA-F]{4}/)
|
|
256
|
+
hex ? [hex.to_i(16)].pack('U') : 'u'
|
|
257
|
+
when 'U'
|
|
258
|
+
hex = @ss.scan(/[0-9a-fA-F]{8}/)
|
|
259
|
+
hex ? [hex.to_i(16)].pack('U') : 'U'
|
|
260
|
+
else
|
|
261
|
+
char.to_s
|
|
262
|
+
end
|
|
263
|
+
end
|
|
264
|
+
def consume_heredoc(indented)
|
|
265
|
+
marker_line = @ss.scan_until(%r{\n})
|
|
266
|
+
raise ScanError, 'unterminated heredoc: missing marker' if marker_line.nil?
|
|
267
|
+
marker = marker_line.strip
|
|
268
|
+
closing = /^[ \t]*#{Regexp.escape(marker)}[ \t]*(?:\n|\z)/
|
|
269
|
+
raw = @ss.scan_until(closing)
|
|
270
|
+
raise ScanError, "unterminated heredoc: missing closing marker '#{marker}'" if raw.nil?
|
|
271
|
+
content = raw.sub(closing, '')
|
|
272
|
+
indented ? dedent(content) : content
|
|
273
|
+
end
|
|
274
|
+
def dedent(text)
|
|
275
|
+
lines = text.split("\n", -1)
|
|
276
|
+
indents = lines.reject { |line| line.strip.empty? }.map { |line| line[/\A[ \t]*/].length }
|
|
277
|
+
indent = indents.min || 0
|
|
278
|
+
lines.map { |line| line.strip.empty? ? line : line[indent..] }.join("\n")
|
|
181
279
|
end
|
|
182
280
|
end # class
|