module Puppet::Pops
module Evaluator
class Jumper < Exception
attr_reader :value
attr_reader :file
attr_reader :line
def initialize(value, file, line)
@value = value
@file = file
@line = line
end
end
class Next < Jumper
def initialize(value, file, line)
super
end
end
class Return < Jumper
def initialize(value, file, line)
super
end
end
class PuppetStopIteration < StopIteration
attr_reader :file
attr_reader :line
attr_reader :pos
def initialize(file, line, pos = nil)
@file = file
@line = line
@pos = pos
end
def message
"break() from context where this is illegal"
end
end
# A Closure represents logic bound to a particular scope.
# As long as the runtime (basically the scope implementation) has the behavior of Puppet 3x it is not
# safe to return and later use this closure.
#
# The 3x scope is essentially a named scope with an additional internal local/ephemeral nested scope state.
# In 3x there is no way to directly refer to the nested scopes, instead, the named scope must be in a particular
# state. Specifically, closures that require a local/ephemeral scope to exist at a later point will fail.
# It is safe to call a closure (even with 3x scope) from the very same place it was defined, but not
# returning it and expecting the closure to reference the scope's state at the point it was created.
#
# 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.
#
class Closure < CallableSignature
attr_reader :evaluator
attr_reader :model
attr_reader :enclosing_scope
def initialize(evaluator, model)
@evaluator = evaluator
@model = model
end
# Evaluates a closure in its enclosing scope after having matched given arguments with parameters (from left to right)
# @api public
def call(*args)
call_with_scope(enclosing_scope, args)
end
# This method makes a Closure compatible with a Dispatch. This is used when the closure is wrapped in a Function
# and the function is called. (Saves an extra Dispatch that just delegates to a Closure and avoids having two
# checks of the argument type/arity validity).
# @api private
def invoke(instance, calling_scope, args, &block)
enclosing_scope.with_global_scope do |global_scope|
call_with_scope(global_scope, args, &block)
end
end
def call_by_name_with_scope(scope, args_hash, enforce_parameters)
call_by_name_internal(scope, args_hash, enforce_parameters)
end
def call_by_name(args_hash, enforce_parameters)
call_by_name_internal(enclosing_scope, args_hash, enforce_parameters)
end
# Call closure with argument assignment by name
def call_by_name_internal(closure_scope, args_hash, enforce_parameters)
if enforce_parameters
# Push a temporary parameter scope used while resolving the parameter defaults
closure_scope.with_parameter_scope(closure_name, parameter_names) do |param_scope|
# Assign all non-nil values, even those that represent non-existent parameters.
args_hash.each { |k, v| param_scope[k] = v unless v.nil? }
parameters.each do |p|
name = p.name
arg = args_hash[name]
if arg.nil?
# Arg either wasn't given, or it was undef
if p.value.nil?
# No default. Assign nil if the args_hash included it
param_scope[name] = nil if args_hash.include?(name)
else
param_scope[name] = param_scope.evaluate(name, p.value, closure_scope, @evaluator)
end
end
end
args_hash = param_scope.to_hash
end
Types::TypeMismatchDescriber.validate_parameters(closure_name, params_struct, args_hash)
result = catch(:next) do
@evaluator.evaluate_block_with_bindings(closure_scope, args_hash, @model.body)
end
Types::TypeAsserter.assert_instance_of(nil, return_type, result) do
"value returned from #{closure_name}"
end
else
@evaluator.evaluate_block_with_bindings(closure_scope, args_hash, @model.body)
end
end
private :call_by_name_internal
def parameters
@model.parameters
end
# Returns the number of parameters (required and optional)
# @return [Integer] the total number of accepted parameters
def parameter_count
# yes, this is duplication of code, but it saves a method call
@model.parameters.size
end
# @api public
def parameter_names
@model.parameters.collect(&:name)
end
def return_type
@return_type ||= create_return_type
end
# @api public
def type
@callable ||= create_callable_type
end
# @api public
def params_struct
@params_struct ||= create_params_struct
end
# @api public
def last_captures_rest?
last = @model.parameters[-1]
last && last.captures_rest
end
# @api public
def block_name
# TODO: Lambda's does not support blocks yet. This is a placeholder
'unsupported_block'
end
CLOSURE_NAME = 'lambda'.freeze
# @api public
def closure_name()
CLOSURE_NAME
end
class Dynamic < Closure
def initialize(evaluator, model, scope)
@enclosing_scope = scope
super(evaluator, model)
end
def enclosing_scope
@enclosing_scope
end
def call(*args)
# A return from an unnamed closure is treated as a return from the context evaluating
# calling this closure - that is, as if it was the return call itself.
#
jumper = catch(:return) do
return call_with_scope(enclosing_scope, args)
end
raise jumper
end
end
class Named < Closure
def initialize(name, evaluator, model)
@name = name
super(evaluator, model)
end
def closure_name
@name
end
# The assigned enclosing scope, or global scope if enclosing scope was initialized to nil
#
def enclosing_scope
# Named closures are typically used for puppet functions and they cannot be defined
# in an enclosing scope as they are cashed and reused. They need to bind to the
# global scope at time of use rather at time of definition.
# Unnamed closures are always a runtime construct, they are never bound by a loader
# and are thus garbage collected at end of a compilation.
#
Puppet.lookup(:global_scope) { {} }
end
end
private
def call_with_scope(scope, args)
variable_bindings = combine_values_with_parameters(scope, args)
final_args = parameters.reduce([]) do |tmp_args, param|
if param.captures_rest
tmp_args.concat(variable_bindings[param.name])
else
tmp_args << variable_bindings[param.name]
end
end
if type.callable_with?(final_args, block_type)
result = catch(:next) do
@evaluator.evaluate_block_with_bindings(scope, variable_bindings, @model.body)
end
Types::TypeAsserter.assert_instance_of(nil, return_type, result) do
"value returned from #{closure_name}"
end
else
tc = Types::TypeCalculator.singleton
args_type = tc.infer_set(final_args)
raise ArgumentError, Types::TypeMismatchDescriber.describe_signatures(closure_name, [self], args_type)
end
end
def combine_values_with_parameters(scope, args)
scope.with_parameter_scope(closure_name, parameter_names) do |param_scope|
parameters.each_with_index do |parameter, index|
param_captures = parameter.captures_rest
default_expression = parameter.value
if index >= args.size
if default_expression
# not given, has default
value = param_scope.evaluate(parameter.name, default_expression, scope, @evaluator)
if param_captures && !value.is_a?(Array)
# correct non array default value
value = [value]
end
else
# not given, does not have default
if param_captures
# default for captures rest is an empty array
value = []
else
@evaluator.fail(Issues::MISSING_REQUIRED_PARAMETER, parameter, { :param_name => parameter.name })
end
end
else
given_argument = args[index]
if param_captures
# get excess arguments
value = args[(parameter_count-1)..-1]
# If the input was a single nil, or undef, and there is a default, use the default
# This supports :undef in case it was used in a 3x data structure and it is passed as an arg
#
if value.size == 1 && (given_argument.nil? || given_argument == :undef) && default_expression
value = param_scope.evaluate(parameter.name, default_expression, scope, @evaluator)
# and ensure it is an array
value = [value] unless value.is_a?(Array)
end
else
value = given_argument
end
end
param_scope[parameter.name] = value
end
param_scope.to_hash
end
end
def create_callable_type()
types = []
from = 0
to = 0
in_optional_parameters = false
closure_scope = enclosing_scope
parameters.each do |param|
type, param_range = create_param_type(param, closure_scope)
types << type
if param_range[0] == 0
in_optional_parameters = true
elsif param_range[0] != 0 && in_optional_parameters
@evaluator.fail(Issues::REQUIRED_PARAMETER_AFTER_OPTIONAL, param, { :param_name => param.name })
end
from += param_range[0]
to += param_range[1]
end
param_types = Types::PTupleType.new(types, Types::PIntegerType.new(from, to))
# The block_type for a Closure is always nil for now, see comment in block_name above
Types::PCallableType.new(param_types, nil, return_type)
end
def create_params_struct
type_factory = Types::TypeFactory
members = {}
closure_scope = enclosing_scope
parameters.each do |param|
arg_type, _ = create_param_type(param, closure_scope)
key_type = type_factory.string(param.name.to_s)
key_type = type_factory.optional(key_type) unless param.value.nil?
members[key_type] = arg_type
end
type_factory.struct(members)
end
def create_return_type
if @model.return_type
@evaluator.evaluate(@model.return_type, @enclosing_scope)
else
Types::PAnyType::DEFAULT
end
end
def create_param_type(param, closure_scope)
type = if param.type_expr
@evaluator.evaluate(param.type_expr, closure_scope)
else
Types::PAnyType::DEFAULT
end
if param.captures_rest && type.is_a?(Types::PArrayType)
# An array on a slurp parameter is how a size range is defined for a
# slurp (Array[Integer, 1, 3] *$param). However, the callable that is
# created can't have the array in that position or else type checking
# will require the parameters to be arrays, which isn't what is
# intended. The array type contains the intended information and needs
# to be unpacked.
param_range = type.size_range
type = type.element_type
elsif param.captures_rest && !type.is_a?(Types::PArrayType)
param_range = ANY_NUMBER_RANGE
elsif param.value
param_range = OPTIONAL_SINGLE_RANGE
else
param_range = REQUIRED_SINGLE_RANGE
end
[type, param_range]
end
# Produces information about parameters compatible with a 4x Function (which can have multiple signatures)
def signatures
[ self ]
end
ANY_NUMBER_RANGE = [0, Float::INFINITY]
OPTIONAL_SINGLE_RANGE = [0, 1]
REQUIRED_SINGLE_RANGE = [1, 1]
end
end
end
Anons79 File Manager Version 1.0, Coded By Anons79
Email: [email protected]