diff options
Diffstat (limited to 'lib/puppet/parser/ast/lambda.rb')
| -rw-r--r-- | lib/puppet/parser/ast/lambda.rb | 107 |
1 files changed, 107 insertions, 0 deletions
diff --git a/lib/puppet/parser/ast/lambda.rb b/lib/puppet/parser/ast/lambda.rb new file mode 100644 index 000000000..00c0e860a --- /dev/null +++ b/lib/puppet/parser/ast/lambda.rb @@ -0,0 +1,107 @@ +require 'puppet/parser/ast/block_expression' + +class Puppet::Parser::AST + # A block of statements/expressions with additional parameters + # Requires scope to contain the values for the defined parameters when evaluated + # If evaluated without a prepared scope, the lambda will behave like its super class. + # + class Lambda < AST::BlockExpression + + # The lambda parameters. + # These are encoded as an array where each entry is an array of one or two object. The first + # is the parameter name, and the optional second object is the value expression (that will + # be evaluated when bound to a scope). + # The value expression is the default value for the parameter. All default values must be + # at the end of the parameter list. + # + # @return [Array<Array<String,String>>] list of parameter names with optional value expression + attr_accessor :parameters + # Evaluates each expression/statement and produce the last expression evaluation result + # @return [Object] what the last expression evaluated to + def evaluate(scope) + if @children.is_a? Puppet::Parser::AST::ASTArray + result = nil + @children.each {|expr| result = expr.evaluate(scope) } + result + else + @children.evaluate(scope) + end + end + + # Calls the lambda. + # Assigns argument values in a nested local scope that should be used to evaluate the lambda + # and then evaluates the lambda. + # @param scope [Puppet::Scope] the calling scope + # @return [Object] the result of evaluating the expression(s) in the lambda + # + def call(scope, *args) + raise Puppet::ParseError, "Too many arguments: #{args.size} for #{parameters.size}" unless args.size <= parameters.size + merged = parameters.zip(args) + missing = merged.select { |e| !e[1] && e[0].size == 1 } + unless missing.empty? + optional = parameters.count { |p| p.size == 2 } + raise Puppet::ParseError, "Too few arguments; #{args.size} for #{optional > 0 ? ' min ' : ''}#{parameters.size - optional}" + end + + evaluated = merged.collect do |m| + # Ruby 1.8.7 zip seems to produce a different result than Ruby 1.9.3 in some situations + n = m[0].is_a?(Array) ? m[0][0] : m[0] + v = m[1] || (m[0][1]).safeevaluate(scope) # given value or default expression value + [n, v] + end + + # Store the evaluated name => value associations in a new inner/local/ephemeral scope + # (This is made complicated due to the fact that the implementation of scope is overloaded with + # functionality and an inner ephemeral scope must be used (as opposed to just pushing a local scope + # on a scope "stack"). + begin + elevel = scope.ephemeral_level + scope.ephemeral_from(Hash[evaluated], file, line) + result = safeevaluate(scope) + ensure + scope.unset_ephemeral_var(elevel) + result ||= nil + end + result + end + + # Validates the lambda. + # Validation checks if parameters with default values are at the end of the list. (It is illegal + # to have a parameter with default value followed by one without). + # + # @raise [Puppet::ParseError] if a parameter with a default comes before a parameter without default value + # + def validate + params = parameters || [] + defaults = params.drop_while {|p| p.size < 2 } + trailing = defaults.drop_while {|p| p.size == 2 } + raise Puppet::ParseError, "Lambda parameters with default values must be placed last" unless trailing.empty? + end + + # Returns the number of parameters (required and optional) + # @return [Integer] the total number of accepted parameters + def parameter_count + @parameters.size + end + + # Returns the number of optional parameters. + # @return [Integer] the number of optional accepted parameters + def optional_parameter_count + @parameters.count {|p| p.size == 2 } + end + + def initialize(options) + super(options) + # ensure there is an empty parameters structure if not given by creator + @parameters = [] unless options[:parameters] + validate + end + + def to_s + result = ["{|"] + result += @parameters.collect {|p| "#{p[0]}" + (p.size == 2 && p[1]) ? p[1].to_s() : '' }.join(', ') + result << "| ... }" + result.join('') + end + end +end |
