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 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249
|
# This module is an integral part of the Lexer.
# It defines interpolation support
# PERFORMANCE NOTE: There are 4 very similar methods in this module that are designed to be as
# performant as possible. While it is possible to parameterize them into one common method, the overhead
# of passing parameters and evaluating conditional logic has a negative impact on performance.
#
module Puppet::Pops::Parser::InterpolationSupport
PATTERN_VARIABLE = %r{(::)?(\w+::)*\w+}
# This is the starting point for a double quoted string with possible interpolation
# The structure mimics that of the grammar.
# The logic is explicit (where the former implementation used parameters/structures) given to a
# generic handler.
# (This is both easier to understand and faster).
#
def interpolate_dq
scn = @scanner
ctx = @lexing_context
before = scn.pos
# skip the leading " by doing a scan since the slurp_dqstring uses last matched when there is an error
scn.scan(/"/)
value,terminator = slurp_dqstring()
text = value
after = scn.pos
loop do
case terminator
when '"'
# simple case, there was no interpolation, return directly
return emit_completed([:STRING, text, scn.pos-before], before)
when '${'
count = ctx[:brace_count]
ctx[:brace_count] += 1
# The ${ terminator is counted towards the string part
enqueue_completed([:DQPRE, text, scn.pos-before], before)
# Lex expression tokens until a closing (balanced) brace count is reached
enqueue_until count
break
when '$'
varname = scn.scan(PATTERN_VARIABLE)
if varname
# The $ is counted towards the variable
enqueue_completed([:DQPRE, text, after-before-1], before)
enqueue_completed([:VARIABLE, varname, scn.pos - after + 1], after - 1)
break
else
# false $ variable start
text += terminator
value,terminator = slurp_dqstring()
text += value
after = scn.pos
end
end
end
interpolate_tail_dq
# return the first enqueued token and shift the queue
@token_queue.shift
end
def interpolate_tail_dq
scn = @scanner
ctx = @lexing_context
before = scn.pos
value,terminator = slurp_dqstring
text = value
after = scn.pos
loop do
case terminator
when '"'
# simple case, there was no further interpolation, return directly
enqueue_completed([:DQPOST, text, scn.pos-before], before)
return
when '${'
count = ctx[:brace_count]
ctx[:brace_count] += 1
# The ${ terminator is counted towards the string part
enqueue_completed([:DQMID, text, scn.pos-before], before)
# Lex expression tokens until a closing (balanced) brace count is reached
enqueue_until count
break
when '$'
varname = scn.scan(PATTERN_VARIABLE)
if varname
# The $ is counted towards the variable
enqueue_completed([:DQMID, text, after-before-1], before)
enqueue_completed([:VARIABLE, varname, scn.pos - after + 1], after - 1)
break
else
# false $ variable start
text += terminator
value,terminator = slurp_dqstring
text += value
after = scn.pos
end
end
end
interpolate_tail_dq
end
# This is the starting point for a un-quoted string with possible interpolation
# The logic is explicit (where the former implementation used parameters/strucures) given to a
# generic handler.
# (This is both easier to understand and faster).
#
def interpolate_uq
scn = @scanner
ctx = @lexing_context
before = scn.pos
value,terminator = slurp_uqstring()
text = value
after = scn.pos
loop do
case terminator
when ''
# simple case, there was no interpolation, return directly
enqueue_completed([:STRING, text, scn.pos-before], before)
return
when '${'
count = ctx[:brace_count]
ctx[:brace_count] += 1
# The ${ terminator is counted towards the string part
enqueue_completed([:DQPRE, text, scn.pos-before], before)
# Lex expression tokens until a closing (balanced) brace count is reached
enqueue_until count
break
when '$'
varname = scn.scan(PATTERN_VARIABLE)
if varname
# The $ is counted towards the variable
enqueue_completed([:DQPRE, text, after-before-1], before)
enqueue_completed([:VARIABLE, varname, scn.pos - after + 1], after - 1)
break
else
# false $ variable start
text += terminator
value,terminator = slurp_uqstring()
text += value
after = scn.pos
end
end
end
interpolate_tail_uq
nil
end
def interpolate_tail_uq
scn = @scanner
ctx = @lexing_context
before = scn.pos
value,terminator = slurp_uqstring
text = value
after = scn.pos
loop do
case terminator
when ''
# simple case, there was no further interpolation, return directly
enqueue_completed([:DQPOST, text, scn.pos-before], before)
return
when '${'
count = ctx[:brace_count]
ctx[:brace_count] += 1
# The ${ terminator is counted towards the string part
enqueue_completed([:DQMID, text, scn.pos-before], before)
# Lex expression tokens until a closing (balanced) brace count is reached
enqueue_until count
break
when '$'
varname = scn.scan(PATTERN_VARIABLE)
if varname
# The $ is counted towards the variable
enqueue_completed([:DQMID, text, after-before-1], before)
enqueue_completed([:VARIABLE, varname, scn.pos - after + 1], after - 1)
break
else
# false $ variable start
text += terminator
value,terminator = slurp_uqstring
text += value
after = scn.pos
end
end
end
interpolate_tail_uq
end
# Enqueues lexed tokens until either end of input, or the given brace_count is reached
#
def enqueue_until brace_count
scn = @scanner
ctx = @lexing_context
queue = @token_queue
queue_base = @token_queue[0]
scn.skip(self.class::PATTERN_WS)
queue_size = queue.size
until scn.eos? do
token = lex_token
if token
if token.equal?(queue_base)
# A nested #interpolate_dq call shifted the queue_base token from the @token_queue. It must
# be put back since it is intended for the top level #interpolate_dq call only.
queue.insert(0, token)
next # all relevant tokens are already on the queue
end
token_name = token[0]
ctx[:after] = token_name
if token_name == :RBRACE && ctx[:brace_count] == brace_count
qlength = queue.size - queue_size
if qlength == 1
# Single token is subject to replacement
queue[-1] = transform_to_variable(queue[-1])
elsif qlength > 1 && [:DOT, :LBRACK].include?(queue[queue_size + 1][0])
# A first word, number of name token followed by '[' or '.' is subject to replacement
# But not for other operators such as ?, +, - etc. where user must use a $ before the name
# to get a variable
queue[queue_size] = transform_to_variable(queue[queue_size])
end
return
end
queue << token
else
scn.skip(self.class::PATTERN_WS)
end
end
end
def transform_to_variable(token)
token_name = token[0]
if [:NUMBER, :NAME, :WORD].include?(token_name) || self.class::KEYWORD_NAMES[token_name] || @taskm_keywords[token_name]
t = token[1]
ta = t.token_array
[:VARIABLE, self.class::TokenValue.new([:VARIABLE, ta[1], ta[2]], t.offset, t.locator)]
else
token
end
end
# Interpolates unquoted string and transfers the result to the given lexer
# (This is used when a second lexer instance is used to lex a substring)
#
def interpolate_uq_to(lexer)
interpolate_uq
queue = @token_queue
until queue.empty? do
lexer.enqueue(queue.shift)
end
end
end
|