This implementation of {Puppet::Pops::Evaluator} performs evaluation using the puppet 3.x runtime system in a manner largely compatible with Puppet 3.x, but adds new features and introduces constraints.
The evaluation uses _polymorphic dispatch_ which works by dispatching to the first found method named after the class or one of its super-classes. The EvaluatorImpl itself mainly deals with evaluation (it currently also handles assignment), and it uses a delegation pattern to more specialized handlers of some operators that in turn use polymorphic dispatch; this to not clutter EvaluatorImpl with too much responsibility).
Since a pattern is used, only the main entry points are fully documented. The parameters o and scope are the same in all the polymorphic methods, (the type of the parameter o is reflected in the method’s name; either the actual class, or one of its super classes). The scope parameter is always the scope in which the evaluation takes place. If nothing else is mentioned, the return is always the result of evaluation.
See {Puppet::Pops::Visitable} and {Puppet::Pops::Visitor} for more information about polymorphic calling.
This constant is not defined as Float::INFINITY in Ruby 1.8.7 (but is available in later version Refactor when support is dropped for Ruby 1.8.7.
Reference to Issues name space makes it easier to refer to issues (Issues are shared with the validator).
Assigns the given value to the given target. The additional argument o is the instruction that produced the target/value tuple and it is used to set the origin of the result.
@param target [Object] assignment target - see methods on the pattern assign_TYPE for actual supported types. @param value [Object] the value to assign to `target` @param o [Puppet::Pops::Model::PopsObject] originating instruction @param scope [Object] the runtime specific scope where evaluation should take place
@api private
# File lib/puppet/pops/evaluator/evaluator_impl.rb, line 131 def assign(target, value, o, scope) @@assign_visitor.visit_this_3(self, target, value, o, scope) end
Evaluates the given target object in the given scope.
@overload evaluate(target, scope) @param target [Object] evaluation target - see methods on the pattern assign_TYPE for actual supported types. @param scope [Object] the runtime specific scope class where evaluation should take place @return [Object] the result of the evaluation
@api public
# File lib/puppet/pops/evaluator/evaluator_impl.rb, line 75 def evaluate(target, scope) begin @@eval_visitor.visit_this_1(self, target, scope) rescue Puppet::Pops::SemanticError => e # A raised issue may not know the semantic target, use errors call stack, but fill in the # rest from a supplied semantic object, or the target instruction if there is not semantic # object. # fail(e.issue, e.semantic || target, e.options, e) rescue Puppet::PreformattedError => e # Already formatted with location information, and with the wanted call stack. # Note this is currently a specialized ParseError, so rescue-order is important # raise e rescue Puppet::ParseError => e # ParseError may be raised in ruby code without knowing the location # in puppet code. # Accept a ParseError that has file or line information available # as an error that should be used verbatim. (Tests typically run without # setting a file name). # ParseError can supply an original - it is impossible to determine which # call stack that should be propagated, using the ParseError's backtrace. # if e.file || e.line raise e else # Since it had no location information, treat it as user intended a general purpose # error. Pass on its call stack. fail(Issues::RUNTIME_ERROR, target, {:detail => e.message}, e) end rescue Puppet::Error => e # PuppetError has the ability to wrap an exception, if so, use the wrapped exception's # call stack instead fail(Issues::RUNTIME_ERROR, target, {:detail => e.message}, e.original || e) rescue StandardError => e # All other errors, use its message and call stack fail(Issues::RUNTIME_ERROR, target, {:detail => e.message}, e) end end
Evaluate a BlockExpression in a new scope with variables bound to the given values.
@param scope [Puppet::Parser::Scope] the parent scope @param variable_bindings [Hash{String => Object}] the variable names and values to bind (names are keys, bound values are values) @param block [Puppet::Pops::Model::BlockExpression] the sequence of expressions to evaluate in the new scope
@api private
# File lib/puppet/pops/evaluator/evaluator_impl.rb, line 164 def evaluate_block_with_bindings(scope, variable_bindings, block_expr) with_guarded_scope(scope) do # change to create local scope_from - cannot give it file and line - # that is the place of the call, not "here" create_local_scope_from(variable_bindings, scope) evaluate(block_expr, scope) end end
Computes a value that can be used as the LHS in an assignment. @param o [Object] the expression to evaluate as a left (assignable) entity @param scope [Object] the runtime specific scope where evaluation should take place
@api private
# File lib/puppet/pops/evaluator/evaluator_impl.rb, line 141 def lvalue(o, scope) @@lvalue_visitor.visit_this_1(self, o, scope) end
Produces a String representation of the given object o as used in interpolation. @param o [Object] the expression of which a string representation is wanted @param scope [Object] the runtime specific scope where evaluation should take place
@api public
# File lib/puppet/pops/evaluator/evaluator_impl.rb, line 151 def string(o, scope) @@string_visitor.visit_this_1(self, o, scope) end
@api private
# File lib/puppet/pops/evaluator/evaluator_impl.rb, line 62 def type_calculator @@type_calculator end
# File lib/puppet/pops/evaluator/evaluator_impl.rb, line 204 def assign_Numeric(n, value, o, scope) fail(Issues::ILLEGAL_NUMERIC_ASSIGNMENT, o.left_expr, {:varname => n.to_s}) end
Catches all illegal assignment (e.g. 1 = 2, {‘a’=>1} = 2, etc)
# File lib/puppet/pops/evaluator/evaluator_impl.rb, line 210 def assign_Object(name, value, o, scope) fail(Issues::ILLEGAL_ASSIGNMENT, o) end
Assign value to named variable. The ‘$’ sign is never part of the name. @example In Puppet DSL
$name = value
@param name [String] name of variable without $ @param value [Object] value to assign to the variable @param o [Puppet::Pops::Model::PopsObject] originating instruction @param scope [Object] the runtime specific scope where evaluation should take place @return [value<Object>]
# File lib/puppet/pops/evaluator/evaluator_impl.rb, line 196 def assign_String(name, value, o, scope) if name =~ /::/ fail(Issues::CROSS_SCOPE_ASSIGNMENT, o.left_expr, {:name => name}) end set_variable(name, value, o, scope) value end
Handles binary expression where lhs and rhs are array/hash or numeric and operator is +, - , *, % / << >>
# File lib/puppet/pops/evaluator/evaluator_impl.rb, line 333 def calculate(left, right, operator, left_o, right_o, scope) unless ARITHMETIC_OPERATORS.include?(operator) fail(Issues::UNSUPPORTED_OPERATOR, left_o.eContainer, {:operator => o.operator}) end if (left.is_a?(Array) || left.is_a?(Hash)) && COLLECTION_OPERATORS.include?(operator) # Handle operation on collections case operator when :'+' concatenate(left, right) when :'-' delete(left, right) when :'<<' unless left.is_a?(Array) fail(Issues::OPERATOR_NOT_APPLICABLE, left_o, {:operator => operator, :left_value => left}) end left + [right] end else # Handle operation on numeric left = coerce_numeric(left, left_o, scope) right = coerce_numeric(right, right_o, scope) begin if operator == :'%' && (left.is_a?(Float) || right.is_a?(Float)) # Deny users the fun of seeing severe rounding errors and confusing results fail(Issues::OPERATOR_NOT_APPLICABLE, left_o, {:operator => operator, :left_value => left}) end result = left.send(operator, right) rescue NoMethodError => e fail(Issues::OPERATOR_NOT_APPLICABLE, left_o, {:operator => operator, :left_value => left}) rescue ZeroDivisionError => e fail(Issues::DIV_BY_ZERO, right_o) end if result == INFINITY || result == -INFINITY fail(Issues::RESULT_IS_INFINITY, left_o, {:operator => operator}) end result end end
Produces concatenation / merge of x and y.
When x is an Array, y of type produces:
Array => concatenation `[1,2], [3,4] => [1,2,3,4]`
Hash => concatenation of hash as array `[key, value, key, value, …]`
any other => concatenation of single value
When x is a Hash, y of type produces:
Array => merge of array interpreted as `[key, value, key, value,…]`
Hash => a merge, where entries in `y` overrides
any other => error
When x is something else, wrap it in an array first.
When x is nil, an empty array is used instead.
@note to concatenate an Array, nest the array - i.e. `[1,2], [[2,3]]`
@overload concatenate(obj_x, obj_y)
@param obj_x [Object] object to wrap in an array and concatenate to; see other overloaded methods for return type @param ary_y [Object] array to concatenate at end of `ary_x` @return [Object] wraps obj_x in array before using other overloaded option based on type of obj_y
@overload concatenate(ary_x, ary_y)
@param ary_x [Array] array to concatenate to @param ary_y [Array] array to concatenate at end of `ary_x` @return [Array] new array with `ary_x` + `ary_y`
@overload concatenate(ary_x, hsh_y)
@param ary_x [Array] array to concatenate to @param hsh_y [Hash] converted to array form, and concatenated to array @return [Array] new array with `ary_x` + `hsh_y` converted to array
@overload concatenate (ary_x, obj_y)
@param ary_x [Array] array to concatenate to @param obj_y [Object] non array or hash object to add to array @return [Array] new array with `ary_x` + `obj_y` added as last entry
@overload concatenate(hsh_x, ary_y)
@param hsh_x [Hash] the hash to merge with @param ary_y [Array] array interpreted as even numbered sequence of key, value merged with `hsh_x` @return [Hash] new hash with `hsh_x` merged with `ary_y` interpreted as hash in array form
@overload concatenate(hsh_x, hsh_y)
@param hsh_x [Hash] the hash to merge to @param hsh_y [Hash] hash merged with `hsh_x` @return [Hash] new hash with `hsh_x` merged with `hsh_y`
@raise [ArgumentError] when `xxx_x` is neither an Array nor a Hash @raise [ArgumentError] when `xxx_x` is a Hash, and `xxx_y` is neither Array nor Hash.
# File lib/puppet/pops/evaluator/evaluator_impl.rb, line 1098 def concatenate(x, y) x = [x] unless x.is_a?(Array) || x.is_a?(Hash) case x when Array y = case y when Array then y when Hash then y.to_a else [y] end x + y # new array with concatenation when Hash y = case y when Hash then y when Array # Hash[[a, 1, b, 2]] => {} # Hash[a,1,b,2] => {a => 1, b => 2} # Hash[[a,1], [b,2]] => {[a,1] => [b,2]} # Hash[[[a,1], [b,2]]] => {a => 1, b => 2} # Use type calcultor to determine if array is Array[Array[?]], and if so use second form # of call t = @@type_calculator.infer(y) if t.element_type.is_a? Puppet::Pops::Types::PArrayType Hash[y] else Hash[*y] end else raise ArgumentError.new("Can only append Array or Hash to a Hash") end x.merge y # new hash with overwrite else raise ArgumentError.new("Can only append to an Array or a Hash.") end end
Produces the result x \ y (set difference) When `x` is an Array, `y` is transformed to an array and then all matching elements removed from x. When `x` is a Hash, all contained keys are removed from x as listed in `y` if it is an Array, or all its keys if it is a Hash. The difference is returned. The given `x` and `y` are not modified by this operation. @raise [ArgumentError] when `x` is neither an Array nor a Hash
# File lib/puppet/pops/evaluator/evaluator_impl.rb, line 1140 def delete(x, y) result = x.dup case x when Array y = case y when Array then y when Hash then y.to_a else [y] end y.each {|e| result.delete(e) } when Hash y = case y when Array then y when Hash then y.keys else [y] end y.each {|e| result.delete(e) } else raise ArgumentError.new("Can only delete from an Array or Hash.") end result end
Evaluates x[key, key, …]
# File lib/puppet/pops/evaluator/evaluator_impl.rb, line 402 def eval_AccessExpression(o, scope) left = evaluate(o.left_expr, scope) keys = o.keys.nil? ? [] : o.keys.collect {|key| evaluate(key, scope) } Puppet::Pops::Evaluator::AccessOperator.new(o).access(left, scope, *keys) end
@example
$a and $b
b is only evaluated if a is true
# File lib/puppet/pops/evaluator/evaluator_impl.rb, line 535 def eval_AndExpression o, scope is_true?(evaluate(o.left_expr, scope), o.left_expr) ? is_true?(evaluate(o.right_expr, scope), o.right_expr) : false end
Handles binary expression where lhs and rhs are array/hash or numeric and operator is +, - , *, % / << >>
# File lib/puppet/pops/evaluator/evaluator_impl.rb, line 318 def eval_ArithmeticExpression(o, scope) left = evaluate(o.left_expr, scope) right = evaluate(o.right_expr, scope) begin result = calculate(left, right, o.operator, o.left_expr, o.right_expr, scope) rescue ArgumentError => e fail(Issues::RUNTIME_ERROR, o, {:detail => e.message}, e) end result end
Evaluates assignment with operators =, +=, -= and
$a = 1 $a += 1 $a -= 1
# File lib/puppet/pops/evaluator/evaluator_impl.rb, line 301 def eval_AssignmentExpression(o, scope) name = lvalue(o.left_expr, scope) value = evaluate(o.right_expr, scope) if o.operator == :'=' assign(name, value, o, scope) else fail(Issues::UNSUPPORTED_OPERATOR, o, {:operator => o.operator}) end value end
Produces 3x parameter
# File lib/puppet/pops/evaluator/evaluator_impl.rb, line 770 def eval_AttributeOperation(o, scope) create_resource_parameter(o, scope, o.attribute_name, evaluate(o.value_expr, scope), o.operator) end
# File lib/puppet/pops/evaluator/evaluator_impl.rb, line 774 def eval_AttributesOperation(o, scope) hashed_params = evaluate(o.expr, scope) unless hashed_params.is_a?(Hash) actual = type_calculator.generalize!(type_calculator.infer(hashed_params)).to_s fail(Puppet::Pops::Issues::TYPE_MISMATCH, o.expr, {:expected => 'Hash', :actual => actual}) end hashed_params.map { |k,v| create_resource_parameter(o, scope, k, v, :'=>') } end
Abstract evaluation, returns array [left, right] with the evaluated result of left_expr and right_expr @return <Array<Object, Object>> array with result of evaluating left and right expressions
# File lib/puppet/pops/evaluator/evaluator_impl.rb, line 290 def eval_BinaryExpression o, scope [ evaluate(o.left_expr, scope), evaluate(o.right_expr, scope) ] end
Evaluates all statements and produces the last evaluated value
# File lib/puppet/pops/evaluator/evaluator_impl.rb, line 565 def eval_BlockExpression o, scope r = nil o.statements.each {|s| r = evaluate(s, scope)} r end
Evaluation of CallMethodExpression handles a NamedAccessExpression functor (receiver.function_name)
# File lib/puppet/pops/evaluator/evaluator_impl.rb, line 820 def eval_CallMethodExpression(o, scope) unless o.functor_expr.is_a? Puppet::Pops::Model::NamedAccessExpression fail(Issues::ILLEGAL_EXPRESSION, o.functor_expr, {:feature=>'function accessor', :container => o}) end receiver = unfold([], [o.functor_expr.left_expr], scope) name = o.functor_expr.right_expr unless name.is_a? Puppet::Pops::Model::QualifiedName fail(Issues::ILLEGAL_EXPRESSION, o.functor_expr, {:feature=>'function name', :container => o}) end name = name.value # the string function name call_function_with_block(name, unfold(receiver, o.arguments || [], scope), o, scope) end
Evaluates function call by name.
# File lib/puppet/pops/evaluator/evaluator_impl.rb, line 802 def eval_CallNamedFunctionExpression(o, scope) # The functor expression is not evaluated, it is not possible to select the function to call # via an expression like $a() case o.functor_expr when Puppet::Pops::Model::QualifiedName # ok when Puppet::Pops::Model::RenderStringExpression # helpful to point out this easy to make Epp error fail(Issues::ILLEGAL_EPP_PARAMETERS, o) else fail(Issues::ILLEGAL_EXPRESSION, o.functor_expr, {:feature=>'function name', :container => o}) end name = o.functor_expr.value call_function_with_block(name, unfold([], o.arguments, scope), o, scope) end
Performs optimized search over case option values, lazily evaluating each until there is a match. If no match is found, the case expression’s default expression is evaluated (it may be nil or Nop if there is no default, thus producing nil). If an option matches, the result of evaluating that option is returned. @return [Object, nil] what a matched option returns, or nil if nothing matched.
# File lib/puppet/pops/evaluator/evaluator_impl.rb, line 577 def eval_CaseExpression(o, scope) # memo scope level before evaluating test - don't want a match in the case test to leak $n match vars # to expressions after the case expression. # with_guarded_scope(scope) do test = evaluate(o.test, scope) @migration_checker.report_uc_bareword_type(test, o.test) result = nil the_default = nil if o.options.find do |co| # the first case option that matches if co.values.find do |c| c = unwind_parentheses(c) case c when Puppet::Pops::Model::LiteralDefault the_default = co.then_expr next false when Puppet::Pops::Model::UnfoldExpression # not ideal for error reporting, since it is not known which unfolded result # that caused an error - the entire unfold expression is blamed (i.e. the var c, passed to is_match?) evaluate(c, scope).any? {|v| is_match?(test, v, c, co, scope) } else is_match?(test, evaluate(c, scope), c, co, scope) end end result = evaluate(co.then_expr, scope) true # the option was picked end end result # an option was picked, and produced a result else evaluate(the_default, scope) # evaluate the default (should be a nop/nil) if there is no default). end end end
Evaluates a CollectExpression by creating a collector transformer. The transformer will evaulate the collection, create the appropriate collector, and hand it off to the compiler to collect the resources specified by the query.
# File lib/puppet/pops/evaluator/evaluator_impl.rb, line 618 def eval_CollectExpression o, scope Puppet::Pops::Evaluator::CollectorTransformer.new().transform(o,scope) end
Evaluates <, <=, >, >=, and ==
# File lib/puppet/pops/evaluator/evaluator_impl.rb, line 410 def eval_ComparisonExpression o, scope left = evaluate(o.left_expr, scope) right = evaluate(o.right_expr, scope) @migration_checker.report_uc_bareword_type(left, o.left_expr) @migration_checker.report_uc_bareword_type(right, o.right_expr) begin # Left is a type if left.is_a?(Puppet::Pops::Types::PAnyType) case o.operator when :'==' @migration_checker.report_equality_type_mismatch(left, right, o) @@type_calculator.equals(left,right) when :'!=' @migration_checker.report_equality_type_mismatch(left, right, o) !@@type_calculator.equals(left,right) when :'<' # left can be assigned to right, but they are not equal @@type_calculator.assignable?(right, left) && ! @@type_calculator.equals(left,right) when :'<=' # left can be assigned to right @@type_calculator.assignable?(right, left) when :'>' # right can be assigned to left, but they are not equal @@type_calculator.assignable?(left,right) && ! @@type_calculator.equals(left,right) when :'>=' # right can be assigned to left @@type_calculator.assignable?(left, right) else fail(Issues::UNSUPPORTED_OPERATOR, o, {:operator => o.operator}) end else case o.operator when :'==' @migration_checker.report_equality_type_mismatch(left, right, o) @@compare_operator.equals(left,right) when :'!=' @migration_checker.report_equality_type_mismatch(left, right, o) ! @@compare_operator.equals(left,right) when :'<' @@compare_operator.compare(left,right) < 0 when :'<=' @@compare_operator.compare(left,right) <= 0 when :'>' @@compare_operator.compare(left,right) > 0 when :'>=' @@compare_operator.compare(left,right) >= 0 else fail(Issues::UNSUPPORTED_OPERATOR, o, {:operator => o.operator}) end end rescue ArgumentError => e fail(Issues::COMPARISON_NOT_POSSIBLE, o, { :operator => o.operator, :left_value => left, :right_value => right, :detail => e.message}, e) end end
Evaluates double quoted strings that may contain interpolation
# File lib/puppet/pops/evaluator/evaluator_impl.rb, line 1000 def eval_ConcatenatedString o, scope o.segments.collect {|expr| string(evaluate(expr, scope), scope)}.join end
This evaluates classes, nodes and resource type definitions to nil, since 3x: instantiates them, and evaluates their parameters and body. This is achieved by providing bridge AST classes in Puppet::Parser::AST::PopsBridge that bridges a Pops Program and a Pops Expression.
Since all Definitions are handled “out of band”, they are treated as a no-op when evaluated.
# File lib/puppet/pops/evaluator/evaluator_impl.rb, line 634 def eval_Definition(o, scope) nil end
# File lib/puppet/pops/evaluator/evaluator_impl.rb, line 373 def eval_EppExpression(o, scope) scope["@epp"] = [] evaluate(o.body, scope) result = scope["@epp"].join result end
# File lib/puppet/pops/evaluator/evaluator_impl.rb, line 214 def eval_Factory(o, scope) evaluate(o.current, scope) end
Evaluates Puppet DSL `in` expression
# File lib/puppet/pops/evaluator/evaluator_impl.rb, line 523 def eval_InExpression o, scope left = evaluate(o.left_expr, scope) right = evaluate(o.right_expr, scope) @migration_checker.report_uc_bareword_type(left, o.left_expr) @migration_checker.report_in_expression(o) @@compare_operator.include?(right, left, scope) end
# File lib/puppet/pops/evaluator/evaluator_impl.rb, line 249 def eval_LiteralDefault(o, scope) :default end
Evaluates each entry of the literal hash and creates a new Hash. @return [Hash] with the evaluated content
# File lib/puppet/pops/evaluator/evaluator_impl.rb, line 558 def eval_LiteralHash o, scope # optimized o.entries.reduce({}) {|h,entry| h[evaluate(entry.key, scope)] = evaluate(entry.value, scope); h } end
Evaluates each entry of the literal list and creates a new Array Supports unfolding of entries @return [Array] with the evaluated content
# File lib/puppet/pops/evaluator/evaluator_impl.rb, line 551 def eval_LiteralList o, scope unfold([], o.values, scope) end
# File lib/puppet/pops/evaluator/evaluator_impl.rb, line 253 def eval_LiteralUndef(o, scope) nil end
Captures all LiteralValues not handled elsewhere.
# File lib/puppet/pops/evaluator/evaluator_impl.rb, line 235 def eval_LiteralValue(o, scope) o.value end
Evaluates matching expressions with type, string or regexp rhs expression. If RHS is a type, the =~ matches compatible (instance? of) type.
@example
x =~ /abc.*/
@example
x =~ "abc.*/"
@example
y = "abc"
x =~ "${y}.*"
@example
[1,2,3] =~ Array[Integer[1,10]]
Note that a string is not instance? of Regexp, only Regular expressions are. The Pattern type should instead be used as it is specified as subtype of String.
@return [Boolean] if a match was made or not. Also sets $0..$n to matchdata in current scope.
# File lib/puppet/pops/evaluator/evaluator_impl.rb, line 491 def eval_MatchExpression o, scope left = evaluate(o.left_expr, scope) pattern = evaluate(o.right_expr, scope) @migration_checker.report_uc_bareword_type(left, o.left_expr) # matches RHS types as instance of for all types except a parameterized Regexp[R] if pattern.is_a?(Puppet::Pops::Types::PAnyType) # evaluate as instance? of type check matched = @@type_calculator.instance?(pattern, left) # convert match result to Boolean true, or false return o.operator == :'=~' ? !!matched : !matched end begin pattern = Regexp.new(pattern) unless pattern.is_a?(Regexp) rescue StandardError => e fail(Issues::MATCH_NOT_REGEXP, o.right_expr, {:detail => e.message}, e) end unless left.is_a?(String) fail(Issues::MATCH_NOT_STRING, o.left_expr, {:left_value => left}) end matched = pattern.match(left) # nil, or MatchData set_match_data(matched,scope) # creates ephemeral # convert match result to Boolean true, or false o.operator == :'=~' ? !!matched : !matched end
Allows nil to be used as a Nop, Evaluates to nil
# File lib/puppet/pops/evaluator/evaluator_impl.rb, line 224 def eval_NilClass(o, scope) nil end
Evaluates Nop to nil.
# File lib/puppet/pops/evaluator/evaluator_impl.rb, line 229 def eval_Nop(o, scope) nil end
# File lib/puppet/pops/evaluator/evaluator_impl.rb, line 263 def eval_NotExpression(o, scope) ! is_true?(evaluate(o.expr, scope), o.expr) end
Evaluates any object not evaluated to something else to itself.
# File lib/puppet/pops/evaluator/evaluator_impl.rb, line 219 def eval_Object o, scope o end
@example
a or b
b is only evaluated if a is false
# File lib/puppet/pops/evaluator/evaluator_impl.rb, line 543 def eval_OrExpression o, scope is_true?(evaluate(o.left_expr, scope), o.left_expr) ? true : is_true?(evaluate(o.right_expr, scope), o.right_expr) end
# File lib/puppet/pops/evaluator/evaluator_impl.rb, line 622 def eval_ParenthesizedExpression(o, scope) evaluate(o.expr, scope) end
# File lib/puppet/pops/evaluator/evaluator_impl.rb, line 638 def eval_Program(o, scope) evaluate(o.body, scope) end
A QualifiedReference (i.e. a capitalized qualified name such as Foo, or Foo::Bar) evaluates to a PType
# File lib/puppet/pops/evaluator/evaluator_impl.rb, line 259 def eval_QualifiedReference(o, scope) @@type_parser.interpret(o) end
Evaluates Puppet DSL ->, ~>, <-, and <~
# File lib/puppet/pops/evaluator/evaluator_impl.rb, line 391 def eval_RelationshipExpression(o, scope) # First level evaluation, reduction to basic data types or puppet types, the relationship operator then translates this # to the final set of references (turning strings into references, which can not naturally be done by the main evaluator since # all strings should not be turned into references. # real = eval_BinaryExpression(o, scope) @@relationship_operator.evaluate(real, o, scope) end
# File lib/puppet/pops/evaluator/evaluator_impl.rb, line 385 def eval_RenderExpression(o, scope) scope["@epp"] << string(evaluate(o.expr, scope), scope) nil end
# File lib/puppet/pops/evaluator/evaluator_impl.rb, line 380 def eval_RenderStringExpression(o, scope) scope["@epp"] << o.value.dup nil end
Reserved Words fail to evaluate
# File lib/puppet/pops/evaluator/evaluator_impl.rb, line 241 def eval_ReservedWord(o, scope) if !o.future fail(Puppet::Pops::Issues::RESERVED_WORD, o, {:word => o.word}) else o.word end end
Sets default parameter values for a type, produces the type
# File lib/puppet/pops/evaluator/evaluator_impl.rb, line 785 def eval_ResourceDefaultsExpression(o, scope) type = evaluate(o.type_ref, scope) type_name = if type.is_a?(Puppet::Pops::Types::PResourceType) && !type.type_name.nil? && type.title.nil? type.type_name # assume it is a valid name else actual = type_calculator.generalize!(type_calculator.infer(type)) fail(Issues::ILLEGAL_RESOURCE_TYPE, o.type_ref, {:actual => actual}) end evaluated_parameters = o.operations.map {|op| evaluate(op, scope) } create_resource_defaults(o, scope, type_name, evaluated_parameters) # Produce the type type end
Produces Array, an array of resource references
# File lib/puppet/pops/evaluator/evaluator_impl.rb, line 644 def eval_ResourceExpression(o, scope) exported = o.exported virtual = o.virtual # Get the type name type_name = if (tmp_name = o.type_name).is_a?(Puppet::Pops::Model::QualifiedName) tmp_name.value # already validated as a name else type_name_acceptable = case o.type_name when Puppet::Pops::Model::QualifiedReference true when Puppet::Pops::Model::AccessExpression o.type_name.left_expr.is_a?(Puppet::Pops::Model::QualifiedReference) end evaluated_name = evaluate(tmp_name, scope) unless type_name_acceptable actual = type_calculator.generalize!(type_calculator.infer(evaluated_name)).to_s fail(Puppet::Pops::Issues::ILLEGAL_RESOURCE_TYPE, o.type_name, {:actual => actual}) end # must be a CatalogEntry subtype case evaluated_name when Puppet::Pops::Types::PHostClassType unless evaluated_name.class_name.nil? fail(Puppet::Pops::Issues::ILLEGAL_RESOURCE_TYPE, o.type_name, {:actual=> evaluated_name.to_s}) end 'class' when Puppet::Pops::Types::PResourceType unless evaluated_name.title().nil? fail(Puppet::Pops::Issues::ILLEGAL_RESOURCE_TYPE, o.type_name, {:actual=> evaluated_name.to_s}) end evaluated_name.type_name # assume validated else actual = type_calculator.generalize!(type_calculator.infer(evaluated_name)).to_s fail(Puppet::Pops::Issues::ILLEGAL_RESOURCE_TYPE, o.type_name, {:actual=>actual}) end end # This is a runtime check - the model is valid, but will have runtime issues when evaluated # and storeconfigs is not set. if(o.exported) optionally_fail(Puppet::Pops::Issues::RT_NO_STORECONFIGS_EXPORT, o); end titles_to_body = {} body_to_titles = {} body_to_params = {} # titles are evaluated before attribute operations o.bodies.map do | body | titles = evaluate(body.title, scope) # Title may not be nil # Titles may be given as an array, it is ok if it is empty, but not if it contains nil entries # Titles may not be an empty String # Titles must be unique in the same resource expression # There may be a :default entry, its entries apply with lower precedence # if titles.nil? fail(Puppet::Pops::Issues::MISSING_TITLE, body.title) end titles = [titles].flatten # Check types of evaluated titles and duplicate entries titles.each_with_index do |title, index| if title.nil? fail(Puppet::Pops::Issues::MISSING_TITLE_AT, body.title, {:index => index}) elsif !title.is_a?(String) && title != :default actual = type_calculator.generalize!(type_calculator.infer(title)).to_s fail(Puppet::Pops::Issues::ILLEGAL_TITLE_TYPE_AT, body.title, {:index => index, :actual => actual}) elsif title == EMPTY_STRING fail(Puppet::Pops::Issues::EMPTY_STRING_TITLE_AT, body.title, {:index => index}) elsif titles_to_body[title] fail(Puppet::Pops::Issues::DUPLICATE_TITLE, o, {:title => title}) end titles_to_body[title] = body end # Do not create a real instance from the :default case titles.delete(:default) body_to_titles[body] = titles # Store evaluated parameters in a hash associated with the body, but do not yet create resource # since the entry containing :defaults may appear later body_to_params[body] = body.operations.reduce({}) do |param_memo, op| params = evaluate(op, scope) params = [params] unless params.is_a?(Array) params.each do |p| if param_memo.include? p.name fail(Puppet::Pops::Issues::DUPLICATE_ATTRIBUTE, o, {:attribute => p.name}) end param_memo[p.name] = p end param_memo end end # Titles and Operations have now been evaluated and resources can be created # Each production is a PResource, and an array of all is produced as the result of # evaluating the ResourceExpression. # defaults_hash = body_to_params[titles_to_body[:default]] || {} o.bodies.map do | body | titles = body_to_titles[body] params = defaults_hash.merge(body_to_params[body] || {}) create_resources(o, scope, virtual, exported, type_name, titles, params.values) end.flatten.compact end
# File lib/puppet/pops/evaluator/evaluator_impl.rb, line 762 def eval_ResourceOverrideExpression(o, scope) evaluated_resources = evaluate(o.resources, scope) evaluated_parameters = o.operations.map { |op| evaluate(op, scope) } create_resource_overrides(o, scope, [evaluated_resources].flatten, evaluated_parameters) evaluated_resources end
@example
$x ? { 10 => true, 20 => false, default => 0 }
# File lib/puppet/pops/evaluator/evaluator_impl.rb, line 910 def eval_SelectorExpression o, scope # memo scope level before evaluating test - don't want a match in the case test to leak $n match vars # to expressions after the selector expression. # with_guarded_scope(scope) do test = evaluate(o.left_expr, scope) @migration_checker.report_uc_bareword_type(test, o.left_expr) the_default = nil selected = o.selectors.find do |s| me = unwind_parentheses(s.matching_expr) case me when Puppet::Pops::Model::LiteralDefault the_default = s.value_expr false when Puppet::Pops::Model::UnfoldExpression # not ideal for error reporting, since it is not known which unfolded result # that caused an error - the entire unfold expression is blamed (i.e. the var c, passed to is_match?) evaluate(me, scope).any? {|v| is_match?(test, v, me, s, scope) } else is_match?(test, evaluate(me, scope), me, s, scope) end end if selected evaluate(selected.value_expr, scope) elsif the_default evaluate(the_default, scope) else fail(Issues::UNMATCHED_SELECTOR, o.left_expr, :param_value => test) end end end
SubLocatable is simply an expression that holds location information
# File lib/puppet/pops/evaluator/evaluator_impl.rb, line 944 def eval_SubLocatedExpression o, scope evaluate(o.expr, scope) end
If the wrapped expression is a QualifiedName, it is taken as the name of a variable in scope. Note that this is different from the 3.x implementation, where an initial qualified name is accepted. (e.g. `“—${var + 1}—”` is legal. This implementation requires such concrete syntax to be expressed in a model as `(TextExpression (+ (Variable var) 1)` - i.e. moving the decision to the parser.
Semantics; the result of an expression is turned into a string, nil is silently transformed to empty string. @return [String] the interpolated result
# File lib/puppet/pops/evaluator/evaluator_impl.rb, line 1015 def eval_TextExpression o, scope if o.expr.is_a?(Puppet::Pops::Model::QualifiedName) string(get_variable_value(o.expr.value, o, scope), scope) else string(evaluate(o.expr, scope), scope) end end
# File lib/puppet/pops/evaluator/evaluator_impl.rb, line 267 def eval_UnaryMinusExpression(o, scope) - coerce_numeric(evaluate(o.expr, scope), o, scope) end
# File lib/puppet/pops/evaluator/evaluator_impl.rb, line 271 def eval_UnfoldExpression(o, scope) candidate = evaluate(o.expr, scope) case candidate when nil [] when Array candidate when Hash candidate.to_a else # turns anything else into an array (so result can be unfolded) [candidate] end end
Evaluates a variable (getting its value) The evaluator is lenient; any expression producing a String is used as a name of a variable.
# File lib/puppet/pops/evaluator/evaluator_impl.rb, line 981 def eval_VariableExpression o, scope # Evaluator is not too fussy about what constitutes a name as long as the result # is a String and a valid variable name # name = evaluate(o.expr, scope) # Should be caught by validation, but make this explicit here as well, or mysterious evaluation issues # may occur for some evaluation use cases. case name when String when Numeric else fail(Issues::ILLEGAL_VARIABLE_EXPRESSION, o.expr) end get_variable_value(name, o, scope) end
Implementation of case option matching.
This is the type of matching performed in a case option, using == for every type of value except regular expression where a match is performed.
# File lib/puppet/pops/evaluator/evaluator_impl.rb, line 1170 def is_match?(left, right, o, option_expr, scope) @migration_checker.report_option_type_mismatch(left, right, option_expr, o) if right.is_a?(Puppet::Pops::Types::PAnyType) @migration_checker.report_uc_bareword_type(right, o) end @@compare_operator.match(left, right, scope) end
Catches all illegal lvalues
# File lib/puppet/pops/evaluator/evaluator_impl.rb, line 182 def lvalue_Object(o, scope) fail(Issues::ILLEGAL_ASSIGNMENT, o) end
# File lib/puppet/pops/evaluator/evaluator_impl.rb, line 175 def lvalue_VariableExpression(o, scope) # evaluate the name evaluate(o.expr, scope) end
# File lib/puppet/pops/evaluator/evaluator_impl.rb, line 1035 def string_Array(o, scope) "[#{o.map {|e| string(e, scope)}.join(COMMA_SEPARATOR)}]" end
# File lib/puppet/pops/evaluator/evaluator_impl.rb, line 1039 def string_Hash(o, scope) "{#{o.map {|k,v| "#{string(k, scope)} => #{string(v, scope)}"}.join(COMMA_SEPARATOR)}}" end
# File lib/puppet/pops/evaluator/evaluator_impl.rb, line 1023 def string_Object(o, scope) o.to_s end
# File lib/puppet/pops/evaluator/evaluator_impl.rb, line 1047 def string_PAnyType(o, scope) @@type_calculator.string(o) end
# File lib/puppet/pops/evaluator/evaluator_impl.rb, line 1043 def string_Regexp(o, scope) "/#{o.source}/" end
# File lib/puppet/pops/evaluator/evaluator_impl.rb, line 1027 def string_Symbol(o, scope) if :undef == o # optimized comparison 1.44 vs 1.95 EMPTY_STRING else o.to_s end end
# File lib/puppet/pops/evaluator/evaluator_impl.rb, line 1178 def with_guarded_scope(scope) scope_memo = get_scope_nesting_level(scope) begin yield ensure set_scope_nesting_level(scope, scope_memo) end end
# File lib/puppet/pops/evaluator/evaluator_impl.rb, line 45 def initialize @@eval_visitor ||= Puppet::Pops::Visitor.new(self, "eval", 1, 1) @@lvalue_visitor ||= Puppet::Pops::Visitor.new(self, "lvalue", 1, 1) @@assign_visitor ||= Puppet::Pops::Visitor.new(self, "assign", 3, 3) @@string_visitor ||= Puppet::Pops::Visitor.new(self, "string", 1, 1) @@type_calculator ||= Puppet::Pops::Types::TypeCalculator.new() @@type_parser ||= Puppet::Pops::Types::TypeParser.new() @@compare_operator ||= Puppet::Pops::Evaluator::CompareOperator.new() @@relationship_operator ||= Puppet::Pops::Evaluator::RelationshipOperator.new() # Use null migration checker unless given in context @migration_checker = (Puppet.lookup(:migration_checker) { Puppet::Pops::Migration::MigrationChecker.new() }) end