Class: Puppet::Pops::Evaluator::Closure

Inherits:
CallableSignature show all
Defined in:
lib/puppet/pops/evaluator/closure.rb

Overview

A Closure represents logic bound to a particular scope. As long as the runtime (basically the scope implementation) has the behaviour of Puppet 3x it is not safe to use this closure when the scope given to it when initialized goes “out of scope”.

Note that the implementation is backwards compatible in that the call method accepts a scope, but this scope is not used.

Note that this class is a CallableSignature, and the methods defined there should be used as the API for obtaining information in a callable implementation agnostic way.

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods inherited from CallableSignature

#args_range, #block_range, #block_type, #infinity?

Constructor Details

#initialize(evaluator, model, scope) ⇒ Closure

Returns a new instance of Closure.



17
18
19
20
21
# File 'lib/puppet/pops/evaluator/closure.rb', line 17

def initialize(evaluator, model, scope)
  @evaluator = evaluator
  @model = model
  @enclosing_scope = scope
end

Instance Attribute Details

#enclosing_scopeObject (readonly)



15
16
17
# File 'lib/puppet/pops/evaluator/closure.rb', line 15

def enclosing_scope
  @enclosing_scope
end

#evaluatorObject (readonly)



13
14
15
# File 'lib/puppet/pops/evaluator/closure.rb', line 13

def evaluator
  @evaluator
end

#modelObject (readonly)



14
15
16
# File 'lib/puppet/pops/evaluator/closure.rb', line 14

def model
  @model
end

Instance Method Details

#block_nameObject



118
119
120
121
# File 'lib/puppet/pops/evaluator/closure.rb', line 118

def block_name
  # TODO: Lambda's does not support blocks yet. This is a placeholder
  'unsupported_block'
end

#call(*args) ⇒ Object

compatible with 3x AST::Lambda



32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
# File 'lib/puppet/pops/evaluator/closure.rb', line 32

def call(*args)
  variable_bindings = combine_values_with_parameters(args)

  tc = Puppet::Pops::Types::TypeCalculator
  final_args = tc.infer_set(parameters.inject([]) do |final_args, param|
    if param.captures_rest
      final_args.concat(variable_bindings[param.name])
    else
      final_args << variable_bindings[param.name]
    end
  end)

  if tc.callable?(type, final_args)
    @evaluator.evaluate_block_with_bindings(@enclosing_scope, variable_bindings, @model.body)
  else
    raise ArgumentError, "lambda called with mis-matched arguments\n#{Puppet::Pops::Evaluator::CallableMismatchDescriber.diff_string('lambda', final_args, [self])}"
  end
end

#call_by_name(scope, args_hash, enforce_parameters) ⇒ Object

Call closure with argument assignment by name



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
# File 'lib/puppet/pops/evaluator/closure.rb', line 52

def call_by_name(scope, args_hash, enforce_parameters)
  if enforce_parameters
    if args_hash.size > parameters.size
      raise ArgumentError, "Too many arguments: #{args_hash.size} for #{parameters.size}"
    end

    # associate values with parameters
    scope_hash = {}
    parameters.each do |p|
      name = p.name
      if (arg_value = args_hash[name]).nil?
        # only set result of default expr if it is defined (it is otherwise not possible to differentiate
        # between explicit undef and no default expression
        unless p.value.nil?
          scope_hash[name] = @evaluator.evaluate(p.value, @enclosing_scope)
        end
      else
        scope_hash[name] = arg_value
      end
    end

    missing = parameters.select { |p| !scope_hash.include?(p.name) }
    if missing.any?
      raise ArgumentError, "Too few arguments; no value given for required parameters #{missing.collect(&:name).join(" ,")}"
    end

    tc = Puppet::Pops::Types::TypeCalculator
    final_args = tc.infer_set(parameter_names.collect { |param| scope_hash[param] })
    if !tc.callable?(type, final_args)
      raise ArgumentError, "lambda called with mis-matched arguments\n#{Puppet::Pops::Evaluator::CallableMismatchDescriber.diff_string('lambda', final_args, [self])}"
    end
  else
    scope_hash = args_hash
  end

  @evaluator.evaluate_block_with_bindings(@enclosing_scope, scope_hash, @model.body)
end

#last_captures_rest?Boolean

Returns:

  • (Boolean)


112
113
114
115
# File 'lib/puppet/pops/evaluator/closure.rb', line 112

def last_captures_rest?
  last = @model.parameters[-1]
  last && last.captures_rest
end

#parameter_countInteger

Returns the number of parameters (required and optional)

Returns:

  • (Integer)

    the total number of accepted parameters



96
97
98
99
# File 'lib/puppet/pops/evaluator/closure.rb', line 96

def parameter_count
  # yes, this is duplication of code, but it saves a method call
  @model.parameters.size
end

#parameter_namesObject



102
103
104
# File 'lib/puppet/pops/evaluator/closure.rb', line 102

def parameter_names
  @model.parameters.collect(&:name)
end

#parametersObject



90
91
92
# File 'lib/puppet/pops/evaluator/closure.rb', line 90

def parameters
  @model.parameters
end

#puppet_lambdaObject

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Deprecated.

Use the type system to query if an object is of Callable type, then use its signatures method for info

marker method checked with respond_to :puppet_lambda



26
27
28
# File 'lib/puppet/pops/evaluator/closure.rb', line 26

def puppet_lambda()
  true
end

#typeObject



107
108
109
# File 'lib/puppet/pops/evaluator/closure.rb', line 107

def type
  @callable ||= create_callable_type
end