diff --git a/lib/puppet/pops/loader/ruby_function_instantiator.rb b/lib/puppet/pops/loader/ruby_function_instantiator.rb index 6c2b716cf..de372ab27 100644 --- a/lib/puppet/pops/loader/ruby_function_instantiator.rb +++ b/lib/puppet/pops/loader/ruby_function_instantiator.rb @@ -1,34 +1,34 @@ # The RubyFunctionInstantiator instantiates a Puppet::Functions::Function given the ruby source # that calls Puppet::Functions.create_function. # class Puppet::Pops::Loader::RubyFunctionInstantiator # Produces an instance of the Function class with the given typed_name, or fails with an error if the # given ruby source does not produce this instance when evaluated. # # @param loader [Puppet::Pops::Loader::Loader] The loader the function is associated with # @param typed_name [Puppet::Pops::Loader::TypedName] the type / name of the function to load # @param source_ref [URI, String] a reference to the source / origin of the ruby code to evaluate # @param ruby_code_string [String] ruby code in a string # # @return [Puppet::Pops::Functions.Function] - an instantiated function with global scope closure associated with the given loader # def self.create(loader, typed_name, source_ref, ruby_code_string) unless ruby_code_string.is_a?(String) && ruby_code_string =~ /Puppet\:\:Functions\.create_function/ raise ArgumentError, "The code loaded from #{source_ref} does not seem to be a Puppet 4x API function - no create_function call." end - created = eval(ruby_code_string) + created = eval(ruby_code_string, nil, source_ref, 1) unless created.is_a?(Class) raise ArgumentError, "The code loaded from #{source_ref} did not produce a Function class when evaluated. Got '#{created.class}'" end unless created.name.to_s == typed_name.name() raise ArgumentError, "The code loaded from #{source_ref} produced mis-matched name, expected '#{typed_name.name}', got #{created.name}" end # create the function instance - it needs closure (scope), and loader (i.e. where it should start searching for things # when calling functions etc. # It should be bound to global scope # TODO: Cheating wrt. scope - assuming it is found in the context closure_scope = Puppet.lookup(:global_scope) { {} } created.new(closure_scope, loader.private_loader) end end diff --git a/lib/puppet/pops/loader/ruby_legacy_function_instantiator.rb b/lib/puppet/pops/loader/ruby_legacy_function_instantiator.rb index de2261ed6..4b2d9cd6c 100644 --- a/lib/puppet/pops/loader/ruby_legacy_function_instantiator.rb +++ b/lib/puppet/pops/loader/ruby_legacy_function_instantiator.rb @@ -1,108 +1,108 @@ # The RubyLegacyFunctionInstantiator loads a 3x function and turns it into a 4x function # that is called with 3x semantics (values are transformed to be 3x compliant). # # The code is loaded from a string obtained by reading the 3x function ruby code into a string # and then passing it to the loaders class method `create`. When Puppet[:biff] == true, the # 3x Puppet::Parser::Function.newfunction method relays back to this function loader's # class method legacy_newfunction which creates a Puppet::Functions class wrapping the # 3x function's block into a method in a function class derived from Puppet::Function. # This class is then returned, and the Legacy loader continues the same way as it does # for a 4x function. # # TODO: Wrapping of Scope # The 3x function expects itself to be Scope. It passes itself as scope to other parts of the runtime, # it expects to find all sorts of information in itself, get/set variables, get compiler, get environment # etc. # TODO: Transformation of arguments to 3x compliant objects # class Puppet::Pops::Loader::RubyLegacyFunctionInstantiator # Produces an instance of the Function class with the given typed_name, or fails with an error if the # given ruby source does not produce this instance when evaluated. # # @param loader [Puppet::Pops::Loader::Loader] The loader the function is associated with # @param typed_name [Puppet::Pops::Loader::TypedName] the type / name of the function to load # @param source_ref [URI, String] a reference to the source / origin of the ruby code to evaluate # @param ruby_code_string [String] ruby code in a string # # @return [Puppet::Pops::Functions.Function] - an instantiated function with global scope closure associated with the given loader # def self.create(loader, typed_name, source_ref, ruby_code_string) # Old Ruby API supports calling a method via :: # this must also be checked as well as call with '.' # unless ruby_code_string.is_a?(String) && ruby_code_string =~ /Puppet\:\:Parser\:\:Functions(?:\.|\:\:)newfunction/ raise ArgumentError, "The code loaded from #{source_ref} does not seem to be a Puppet 3x API function - no newfunction call." end # The evaluation of the 3x function creation source should result in a call to the legacy_newfunction # - created = eval(ruby_code_string) + created = eval(ruby_code_string, nil, source_ref, 1) unless created.is_a?(Class) raise ArgumentError, "The code loaded from #{source_ref} did not produce a Function class when evaluated. Got '#{created.class}'" end unless created.name.to_s == typed_name.name() raise ArgumentError, "The code loaded from #{source_ref} produced mis-matched name, expected '#{typed_name.name}', got #{created.name}" end # create the function instance - it needs closure (scope), and loader (i.e. where it should start searching for things # when calling functions etc. # It should be bound to global scope # TODO: Cheating wrt. scope - assuming it is found in the context closure_scope = Puppet.lookup(:global_scope) { {} } created.new(closure_scope, loader) end # This is a new implementation of the method that is used in 3x to create a function. # The arguments are the same as those passed to Puppet::Parser::Functions.newfunction, hence its # deviation from regular method naming practice. # def self.legacy_newfunction(name, options, &block) # 3x api allows arity to be specified, if unspecified it is 0 or more arguments # arity >= 0, is an exact count # airty < 0 is the number of required arguments -1 (i.e. -1 is 0 or more) # (there is no upper cap, there is no support for optional values, or defaults) # arity = options[:arity] || -1 if arity >= 0 min_arg_count = arity max_arg_count = arity else min_arg_count = (arity + 1).abs # infinity max_arg_count = :default end # Create a 4x function wrapper around the 3x Function created_function_class = Puppet::Functions.create_function(name) do # define a method on the new Function class with the same name as the function, but # padded with __ because the function may represent a ruby method with the same name that # expects to have inherited from Kernel, and then Object. # (This can otherwise lead to infinite recursion, or that an ArgumentError is raised). # __name__ = :"__#{name}__" define_method(__name__, &block) # Define the method that is called from dispatch - this method just changes a call # with multiple unknown arguments to passing all in an array (since this is expected in the 3x API). # We want the call to be checked for type and number of arguments so cannot call the function # defined by the block directly since it is defined to take a single argument. # define_method(:__relay__call__) do |*args| # dup the args since the function may destroy them # TODO: Should convert arguments to 3x, now :undef is send to the function send(__name__, args.dup) end # Define a dispatch that performs argument type/count checking # dispatch :__relay__call__ do param 'Any', 'args' # Specify arg count (transformed from 3x function arity specification). arg_count(min_arg_count, max_arg_count) end end created_function_class end end