diff options
author | Brice Figureau <brice-puppet@daysofwonder.com> | 2010-02-08 20:17:38 +0100 |
---|---|---|
committer | test branch <puppet-dev@googlegroups.com> | 2010-02-17 06:50:53 -0800 |
commit | e93eab81a58282db5306de6fec42703795c85523 (patch) | |
tree | 86968377febee8044e168ef3f4e1e528e6e32120 /lib | |
parent | b8832725e2bf1933af0d583ddbd81c8a8db4ae8f (diff) | |
download | puppet-e93eab81a58282db5306de6fec42703795c85523.tar.gz puppet-e93eab81a58282db5306de6fec42703795c85523.tar.xz puppet-e93eab81a58282db5306de6fec42703795c85523.zip |
Fix #3155 - prevent error when using two matching regex in cascade
The following manifest:
case $var {
/match/: {
if $var =~ /matchagain/ {
}
}
}
is failing because the "=~" operators when matching sets an ephemeral
variable in the scope. But the case regex also did it, and since they
both belong to the same scope, and Puppet variables are immutables, the
scope raises an error.
This patch fixes this issue by adding to the current scope a stack
of ephemeral symbol tables. Each new match operator or case/selector
with regex adds a new scope. When we get out of the case/if/selector
structure the scope is reset to the ephemeral level we were when
entering it.
This way the following manifest produces the correct output:
case $var {
/match(rematch)/: {
notice("1. \$0 = $0, \$1 = $1")
if $var =~ /matchagain/ {
notice("2. \$0 = $0, \$1 = $1")
}
notice("3. \$0 = $0, \$1 = $1")
}
}
notice("4. \$0 = $0")
And the output is:
1. $0 = match, $1 = rematch
2. $0 = matchagain, $1 = rematch
3. $0 = match, $1 = rematch
4. $0 =
Signed-off-by: Brice Figureau <brice-puppet@daysofwonder.com>
Diffstat (limited to 'lib')
-rw-r--r-- | lib/puppet/parser/ast/casestatement.rb | 4 | ||||
-rw-r--r-- | lib/puppet/parser/ast/ifstatement.rb | 3 | ||||
-rw-r--r-- | lib/puppet/parser/ast/selector.rb | 3 | ||||
-rw-r--r-- | lib/puppet/parser/scope.rb | 70 |
4 files changed, 68 insertions, 12 deletions
diff --git a/lib/puppet/parser/ast/casestatement.rb b/lib/puppet/parser/ast/casestatement.rb index 720ef240b..0cddef0f0 100644 --- a/lib/puppet/parser/ast/casestatement.rb +++ b/lib/puppet/parser/ast/casestatement.rb @@ -11,6 +11,8 @@ class Puppet::Parser::AST # Short-curcuit evaluation. Return the value of the statements for # the first option that matches. def evaluate(scope) + level = scope.ephemeral_level + value = @test.safeevaluate(scope) retvalue = nil @@ -32,7 +34,7 @@ class Puppet::Parser::AST Puppet.debug "No true answers and no default" return nil ensure - scope.unset_ephemeral_var + scope.unset_ephemeral_var(level) end def each diff --git a/lib/puppet/parser/ast/ifstatement.rb b/lib/puppet/parser/ast/ifstatement.rb index 9d52123b6..d84445bbd 100644 --- a/lib/puppet/parser/ast/ifstatement.rb +++ b/lib/puppet/parser/ast/ifstatement.rb @@ -16,6 +16,7 @@ class Puppet::Parser::AST # else if there's an 'else' setting, evaluate it. # the first option that matches. def evaluate(scope) + level = scope.ephemeral_level value = @test.safeevaluate(scope) # let's emulate a new scope for each branches @@ -30,7 +31,7 @@ class Puppet::Parser::AST end end ensure - scope.unset_ephemeral_var + scope.unset_ephemeral_var(level) end end end diff --git a/lib/puppet/parser/ast/selector.rb b/lib/puppet/parser/ast/selector.rb index d27773c4b..647bdcde1 100644 --- a/lib/puppet/parser/ast/selector.rb +++ b/lib/puppet/parser/ast/selector.rb @@ -12,6 +12,7 @@ class Puppet::Parser::AST # Find the value that corresponds with the test. def evaluate(scope) + level = scope.ephemeral_level # Get our parameter. paramvalue = @param.safeevaluate(scope) @@ -35,7 +36,7 @@ class Puppet::Parser::AST self.fail Puppet::ParseError, "No matching value for selector param '%s'" % paramvalue ensure - scope.unset_ephemeral_var + scope.unset_ephemeral_var(level) end def to_s diff --git a/lib/puppet/parser/scope.rb b/lib/puppet/parser/scope.rb index bb8725096..19d79e6d0 100644 --- a/lib/puppet/parser/scope.rb +++ b/lib/puppet/parser/scope.rb @@ -24,6 +24,30 @@ class Puppet::Parser::Scope attr_accessor :parent attr_reader :namespaces + # thin wrapper around an ephemeral + # symbol table. + # when a symbol + class Ephemeral + def initialize(parent=nil) + @symbols = {} + @parent = parent + end + + [:include?, :delete, :[]=].each do |m| + define_method(m) do |*args| + @symbols.send(m, *args) + end + end + + def [](name) + unless @symbols.include?(name) or @parent.nil? + @parent[name] + else + @symbols[name] + end + end + end + # A demeterific shortcut to the catalog. def catalog compiler.catalog @@ -125,7 +149,9 @@ class Puppet::Parser::Scope # the ephemeral symbol tables # those should not persist long, and are used for the moment only # for $0..$xy capture variables of regexes - @ephemeral = {} + # this is actually implemented as a stack, with each ephemeral scope + # shadowing the previous one + @ephemeral = [ Ephemeral.new ] # All of the defaults set for types. It's a hash of hashes, # with the first key being the type, then the second key being @@ -216,13 +242,13 @@ class Puppet::Parser::Scope # Look up a variable. The simplest value search we do. Default to returning # an empty string for missing values, but support returning a constant. def lookupvar(name, usestring = true) - table = ephemeral?(name) ? @ephemeral : @symtable + table = ephemeral?(name) ? @ephemeral.last : @symtable # If the variable is qualified, then find the specified scope and look the variable up there instead. if name =~ /::/ return lookup_qualified_var(name, usestring) end # We can't use "if table[name]" here because the value might be false - if table.include?(name) + if ephemeral_include?(name) or table.include?(name) if usestring and table[name] == :undef return "" else @@ -306,7 +332,7 @@ class Puppet::Parser::Scope # in scopes above, but will not allow variables in the current scope # to be reassigned. def setvar(name,value, options = {}) - table = options[:ephemeral] ? @ephemeral : @symtable + table = options[:ephemeral] ? @ephemeral.last : @symtable #Puppet.debug "Setting %s to '%s' at level %s mode append %s" % # [name.inspect,value,self.level, append] if table.include?(name) @@ -355,7 +381,7 @@ class Puppet::Parser::Scope else # look the variable up # make sure $0-$9 are lookupable only if ephemeral var = ss[1] || ss[3] || ss[4] - if var and var =~ /^[0-9]+$/ and not ephemeral?(var) + if var and var =~ /^[0-9]+$/ and not ephemeral_include?(var) next end out << lookupvar(var).to_s || "" @@ -420,23 +446,49 @@ class Puppet::Parser::Scope # Undefine a variable; only used for testing. def unsetvar(var) - table = ephemeral?(var) ? @ephemeral : @symtable + table = ephemeral?(var) ? @ephemeral.last : @symtable if table.include?(var) table.delete(var) end end - def unset_ephemeral_var - @ephemeral = {} + # remove ephemeral scope up to level + def unset_ephemeral_var(level=:all) + if level == :all + @ephemeral = [ Ephemeral.new ] + else + (@ephemeral.size - level).times do + @ephemeral.pop + end + end + end + + # check if name exists in one of the ephemeral scope. + def ephemeral_include?(name) + @ephemeral.reverse.each do |eph| + return true if eph.include?(name) + end + false end + # is name an ephemeral variable? def ephemeral?(name) - @ephemeral.include?(name) + name =~ /^\d+$/ + end + + def ephemeral_level + @ephemeral.size + end + + def new_ephemeral + @ephemeral.push(Ephemeral.new(@ephemeral.last)) end def ephemeral_from(match, file = nil, line = nil) raise(ArgumentError,"Invalid regex match data") unless match.is_a?(MatchData) + new_ephemeral + setvar("0", match[0], :file => file, :line => line, :ephemeral => true) match.captures.each_with_index do |m,i| setvar("#{i+1}", m, :file => file, :line => line, :ephemeral => true) |