diff --git a/lib/puppet/functions.rb b/lib/puppet/functions.rb index a15e166f6..c29dfc249 100644 --- a/lib/puppet/functions.rb +++ b/lib/puppet/functions.rb @@ -1,548 +1,548 @@ # @note WARNING: This new function API is still under development and may change at any time # # Functions in the puppet language can be written in Ruby and distributed in # puppet modules. The function is written by creating a file in the module's # `lib/puppet/functions/` directory, where `` is # replaced with the module's name. The file should have the name of the function. # For example, to create a function named `min` in a module named `math` create # a file named `lib/puppet/functions/math/min.rb` in the module. # # A function is implemented by calling {Puppet::Functions.create_function}, and # passing it a block that defines the implementation of the function. # # Functions are namespaced inside the module that contains them. The name of # the function is prefixed with the name of the module. For example, # `math::min`. # # @example A simple function # Puppet::Functions.create_function('math::min') do # def min(a, b) # a <= b ? a : b # end # end # # Anatomy of a function # --- # # Functions are composed of four parts: the name, the implementation methods, # the signatures, and the dispatches. # # The name is the string given to the {Puppet::Functions.create_function} # method. It specifies the name to use when calling the function in the puppet # language, or from other functions. # # The implementation methods are ruby methods (there can be one or more) that # provide that actual implementation of the function's behavior. In the # simplest case the name of the function (excluding any namespace) and the name # of the method are the same. When that is done no other parts (signatures and # dispatches) need to be used. # # Signatures are a way of specifying the types of the function's parameters. # The types of any arguments will be checked against the types declared in the # signature and an error will be produced if they don't match. The types are # defined by using the same syntax for types as in the puppet language. # # Dispatches are how signatures and implementation methods are tied together. # When the function is called, puppet searches the signatures for one that # matches the supplied arguments. Each signature is part of a dispatch, which # specifies the method that should be called for that signature. When a # matching signature is found, the corrosponding method is called. # # Documentation for the function should be placed as comments to the # implementation method(s). # # @todo Documentation for individual instances of these new functions is not # yet tied into the puppet doc system. # # @example Dispatching to different methods by type # Puppet::Functions.create_function('math::min') do # dispatch :numeric_min do # param 'Numeric', 'a' # param 'Numeric', 'b' # end # # dispatch :string_min do # param 'String', 'a' # param 'String', 'b' # end # # def numeric_min(a, b) # a <= b ? a : b # end # # def string_min(a, b) # a.downcase <= b.downcase ? a : b # end # end # # Specifying Signatures # --- # # If nothing is specified, the number of arguments given to the function must # be the same as the number of parameters, and all of the parameters are of # type 'Object'. # # To express that the last parameter captures the rest, the method # `last_captures_rest` can be called. This indicates that the last parameter is # a varargs parameter and will be passed to the implementing method as an array # of the given type. # # When defining a dispatch for a function, the resulting dispatch matches # against the specified argument types and min/max occurrence of optional # entries. When the dispatch makes the call to the implementation method the # arguments are simply passed and it is the responsibility of the method's # implementor to ensure it can handle those arguments (i.e. there is no check # that what was declared as optional actually has a default value, and that # a "captures rest" is declared using a `*`). # # @example Varargs # Puppet::Functions.create_function('foo') do # dispatch :foo do # param 'Numeric', 'first' # param 'Numeric', 'values' # last_captures_rest # end # # def foo(first, *values) # # do something # end # end # # Access to Scope # --- # In general, functions should not need access to scope; they should be # written to act on their given input only. If they absolutely must look up # variable values, they should do so via the closure scope (the scope where # they are defined) - this is done by calling `closure_scope()`. # # Calling other Functions # --- # Calling other functions by name is directly supported via # {Puppet::Pops::Functions::Function#call_function}. This allows a function to # call other functions visible from its loader. # # @api public module Puppet::Functions # @param func_name [String, Symbol] a simple or qualified function name # @param block [Proc] the block that defines the methods and dispatch of the # Function to create # @return [Class] the newly created Function class # # @api public def self.create_function(func_name, function_base = Function, &block) if function_base.ancestors.none? { |s| s == Puppet::Pops::Functions::Function } raise ArgumentError, "Functions must be based on Puppet::Pops::Functions::Function. Got #{function_base}" end func_name = func_name.to_s # Creates an anonymous class to represent the function # The idea being that it is garbage collected when there are no more # references to it. # the_class = Class.new(function_base, &block) # Make the anonymous class appear to have the class-name # Even if this class is not bound to such a symbol in a global ruby scope and # must be resolved via the loader. # This also overrides any attempt to define a name method in the given block # (Since it redefines it) # # TODO, enforce name in lower case (to further make it stand out since Ruby # class names are upper case) # the_class.instance_eval do @func_name = func_name def name @func_name end end # Automatically create an object dispatcher based on introspection if the # loaded user code did not define any dispatchers. Fail if function name # does not match a given method name in user code. # if the_class.dispatcher.empty? simple_name = func_name.split(/::/)[-1] type, names = default_dispatcher(the_class, simple_name) last_captures_rest = (type.size_range[1] == Puppet::Pops::Types::INFINITY) the_class.dispatcher.add_dispatch(type, simple_name, names, nil, nil, nil, last_captures_rest) end # The function class is returned as the result of the create function method the_class end # Creates a default dispatcher configured from a method with the same name as the function # # @api private def self.default_dispatcher(the_class, func_name) unless the_class.method_defined?(func_name) raise ArgumentError, "Function Creation Error, cannot create a default dispatcher for function '#{func_name}', no method with this name found" end object_signature(*min_max_param(the_class.instance_method(func_name))) end # @api private def self.min_max_param(method) # Ruby 1.8.7 does not have support for details about parameters if method.respond_to?(:parameters) result = {:req => 0, :opt => 0, :rest => 0 } # TODO: Optimize into one map iteration that produces names map, and sets # count as side effect method.parameters.each { |p| result[p[0]] += 1 } from = result[:req] to = result[:rest] > 0 ? :default : from + result[:opt] names = method.parameters.map {|p| p[1].to_s } else # Cannot correctly compute the signature in Ruby 1.8.7 because arity for # optional values is screwed up (there is no way to get the upper limit), # an optional looks the same as a varargs In this case - the failure will # simply come later when the call fails # arity = method.arity from = arity >= 0 ? arity : -arity -1 to = arity >= 0 ? arity : :default # i.e. infinite (which is wrong when there are optional - flaw in 1.8.7) names = [] # no names available end [from, to, names] end # Construct a signature consisting of Object type, with min, and max, and given names. - # (there is only one type entry). Note that this signature is Object, not Optional[Object]. + # (there is only one type entry). # # @api private def self.object_signature(from, to, names) # Construct the type for the signature # Tuple[Object, from, to] factory = Puppet::Pops::Types::TypeFactory [factory.callable(factory.object, from, to), names] end # Function # === # This class is the base class for all Puppet 4x Function API functions. A # specialized class is created for each puppet function. # # @api public class Function < Puppet::Pops::Functions::Function # @api private def self.builder @type_parser ||= Puppet::Pops::Types::TypeParser.new @all_callables ||= Puppet::Pops::Types::TypeFactory.all_callables DispatcherBuilder.new(dispatcher, @type_parser, @all_callables) end # Dispatch any calls that match the signature to the provided method name. # # @param meth_name [Symbol] The name of the implementation method to call # when the signature defined in the block matches the arguments to a call # to the function. # @return [Void] # # @api public def self.dispatch(meth_name, &block) builder().instance_eval do dispatch(meth_name, &block) end end end # Public api methods of the DispatcherBuilder are available within dispatch() # blocks declared in a Puppet::Function.create_function() call. # # @api public class DispatcherBuilder # @api private def initialize(dispatcher, type_parser, all_callables) @type_parser = type_parser @all_callables = all_callables @dispatcher = dispatcher end # Defines a positional parameter with type and name # # @param type [String] The type specification for the parameter. # @param name [String] The name of the parameter. This is primarily used # for error message output and does not have to match the name of the # parameter on the implementation method. # @return [Void] # # @api public def param(type, name) if type.is_a?(String) @types << type @names << name # mark what should be picked for this position when dispatching @weaving << @names.size()-1 else raise ArgumentError, "Type signature argument must be a String reference to a Puppet Data Type. Got #{type.class}" end end # Defines one required block parameter that may appear last. If type and name is missing the # default type is "Callable", and the name is "block". If only one # parameter is given, then that is the name and the type is "Callable". # # @api public def required_block_param(*type_and_name) case type_and_name.size when 0 type = @all_callables name = 'block' when 1 type = @all_callables name = type_and_name[0] when 2 type_string, name = type_and_name type = @type_parser.parse(type_string) else raise ArgumentError, "block_param accepts max 2 arguments (type, name), got #{type_and_name.size}." end unless type.is_a?(Puppet::Pops::Types::PCallableType) raise ArgumentError, "Expected PCallableType, got #{type.class}" end unless name.is_a?(String) raise ArgumentError, "Expected block_param name to be a String, got #{name.class}" end if @block_type.nil? @block_type = type @block_name = name else raise ArgumentError, "Attempt to redefine block" end end # Defines one optional block parameter that may appear last. If type or name is missing the # defaults are "any callable", and the name is "block". The implementor of the dispatch target # must use block = nil when it is optional (or an error is raised when the call is made). # # @api public def optional_block_param(*type_and_name) # same as required, only wrap the result in an optional type required_block_param(*type_and_name) @block_type = Puppet::Pops::Types::TypeFactory.optional(@block_type) end # Specifies the min and max occurance of arguments (of the specified types) # if something other than the exact count from the number of specified # types). The max value may be specified as -1 if an infinite number of # arguments are supported. When max is > than the number of specified # types, the last specified type repeats. # # @api public def arg_count(min_occurs, max_occurs) @min = min_occurs @max = max_occurs unless min_occurs.is_a?(Integer) && min_occurs >= 0 raise ArgumentError, "min arg_count of function parameter must be an Integer >=0, got #{min_occurs.class} '#{min_occurs}'" end unless max_occurs == :default || (max_occurs.is_a?(Integer) && max_occurs >= 0) raise ArgumentError, "max arg_count of function parameter must be an Integer >= 0, or :default, got #{max_occurs.class} '#{max_occurs}'" end unless max_occurs == :default || (max_occurs.is_a?(Integer) && max_occurs >= min_occurs) raise ArgumentError, "max arg_count must be :default (infinite) or >= min arg_count, got min: '#{min_occurs}, max: '#{max_occurs}'" end end # Specifies that the last argument captures the rest. # # @api public def last_captures_rest @last_captures = true end private # @api private def dispatch(meth_name, &block) # an array of either an index into names/types, or an array with # injection information [type, name, injection_name] used when the call # is being made to weave injections into the given arguments. # @types = [] @names = [] @weaving = [] @injections = [] @min = nil @max = nil @last_captures = false @block_type = nil @block_name = nil self.instance_eval &block callable_t = create_callable(@types, @block_type, @min, @max) @dispatcher.add_dispatch(callable_t, meth_name, @names, @block_name, @injections, @weaving, @last_captures) end # Handles creation of a callable type from strings specifications of puppet # types and allows the min/max occurs of the given types to be given as one # or two integer values at the end. The given block_type should be # Optional[Callable], Callable, or nil. # # @api private def create_callable(types, block_type, from, to) mapped_types = types.map do |t| @type_parser.parse(t) end if !(from.nil? && to.nil?) mapped_types << from mapped_types << to end if block_type mapped_types << block_type end Puppet::Pops::Types::TypeFactory.callable(*mapped_types) end end private # @note WARNING: This style of creating functions is not public. It is a system # under development that will be used for creating "system" functions. # # This is a private, internal, system for creating functions. It supports # everything that the public function definition system supports as well as a # few extra features. # # Injection Support # === # The Function API supports injection of data and services. It is possible to # make injection that takes effect when the function is loaded (for services # and runtime configuration that does not change depending on how/from where # in what context the function is called. It is also possible to inject and # weave argument values into a call. # # Injection of attributes # --- # Injection of attributes is performed by one of the methods `attr_injected`, # and `attr_injected_producer`. The injected attributes are available via # accessor method calls. # # @example using injected attributes # Puppet::Functions.create_function('test') do # attr_injected String, :larger, 'message_larger' # attr_injected String, :smaller, 'message_smaller' # def test(a, b) # a > b ? larger() : smaller() # end # end # # @api private class InternalFunction < Function # @api private def self.builder @type_parser ||= Puppet::Pops::Types::TypeParser.new @all_callables ||= Puppet::Pops::Types::TypeFactory.all_callables InternalDispatchBuilder.new(dispatcher, @type_parser, @all_callables) end # Defines class level injected attribute with reader method # # @api private def self.attr_injected(type, attribute_name, injection_name = nil) define_method(attribute_name) do ivar = :"@#{attribute_name.to_s}" unless instance_variable_defined?(ivar) injector = Puppet.lookup(:injector) instance_variable_set(ivar, injector.lookup(closure_scope, type, injection_name)) end instance_variable_get(ivar) end end # Defines class level injected producer attribute with reader method # # @api private def self.attr_injected_producer(type, attribute_name, injection_name = nil) define_method(attribute_name) do ivar = :"@#{attribute_name.to_s}" unless instance_variable_defined?(ivar) injector = Puppet.lookup(:injector) instance_variable_set(ivar, injector.lookup_producer(closure_scope, type, injection_name)) end instance_variable_get(ivar) end end end # @note WARNING: This style of creating functions is not public. It is a system # under development that will be used for creating "system" functions. # # Injection and Weaving of parameters # --- # It is possible to inject and weave parameters into a call. These extra # parameters are not part of the parameters passed from the Puppet logic, and # they can not be overridden by parameters given as arguments in the call. # They are invisible to the Puppet Language. # # @example using injected parameters # Puppet::Functions.create_function('test') do # dispatch :test do # param 'Scalar', 'a' # param 'Scalar', 'b' # injected_param 'String', 'larger', 'message_larger' # injected_param 'String', 'smaller', 'message_smaller' # end # def test(a, b, larger, smaller) # a > b ? larger : smaller # end # end # # The function in the example above is called like this: # # test(10, 20) # # Using injected value as default # --- # Default value assignment is handled by using the regular Ruby mechanism (a # value is assigned to the variable). The dispatch simply indicates that the # value is optional. If the default value should be injected, it can be # handled different ways depending on what is desired: # # * by calling the accessor method for an injected Function class attribute. # This is suitable if the value is constant across all instantiations of the # function, and across all calls. # * by injecting a parameter into the call # to the left of the parameter, and then assigning that as the default value. # * One of the above forms, but using an injected producer instead of a # directly injected value. # # @example method with injected default values # Puppet::Functions.create_function('test') do # dispatch :test do # injected_param String, 'b_default', 'b_default_value_key' # param 'Scalar', 'a' # param 'Scalar', 'b' # end # def test(b_default, a, b = b_default) # # ... # end # end # # @api private class InternalDispatchBuilder < DispatcherBuilder # TODO: is param name really needed? Perhaps for error messages? (it is unused now) # # @api private def injected_param(type, name, injection_name = '') @injections << [type, name, injection_name] # mark what should be picked for this position when dispatching @weaving << [@injections.size() -1] end # TODO: is param name really needed? Perhaps for error messages? (it is unused now) # # @api private def injected_producer_param(type, name, injection_name = '') @injections << [type, name, injection_name, :producer] # mark what should be picked for this position when dispatching @weaving << [@injections.size()-1] end end end diff --git a/lib/puppet/functions/assert_type.rb b/lib/puppet/functions/assert_type.rb index 7ff216a5e..ccd42c299 100644 --- a/lib/puppet/functions/assert_type.rb +++ b/lib/puppet/functions/assert_type.rb @@ -1,56 +1,56 @@ # Returns the given value if it is an instance of the given type, and raises an error otherwise. # Optionally, if a block is given (accepting two parameters), it will be called instead of raising # an error. This to enable giving the user richer feedback, or to supply a default value. # # @example how to assert type # # assert that `$b` is a non empty `String` and assign to `$a` # $a = assert_type(String[1], $b) # # @example using custom error message # $a = assert_type(String[1], $b) |$expected, $actual| { fail("The name cannot be empty") } # # @example, using a warning and a default # $a = assert_type(String[1], $b) |$expected, $actual| { warning("Name is empty, using default") 'anonymous' } # # See the documentation for "The Puppet Type System" for more information about types. # Puppet::Functions.create_function(:assert_type) do dispatch :assert_type do param 'Type', 'type' - param 'Optional[Object]', 'value' - optional_block_param 'Callable[Optional[Object],Optional[Object]]', 'block' + param 'Object', 'value' + optional_block_param 'Callable[Object, Object]', 'block' end dispatch :assert_type_s do param 'String', 'type_string' - param 'Optional[Object]', 'value' - optional_block_param 'Callable[Optional[Object], Optional[Object]]', 'block' + param 'Object', 'value' + optional_block_param 'Callable[Object, Object]', 'block' end # @param type [Type] the type the value must be an instance of - # @param value [Optional[Object]] the value to assert + # @param value [Object] the value to assert # def assert_type(type, value, block=nil) unless Puppet::Pops::Types::TypeCalculator.instance?(type,value) inferred_type = Puppet::Pops::Types::TypeCalculator.infer(value) # Do not give all the details - i.e. format as Integer, instead of Integer[n, n] for exact value, which # is just confusing. (OTOH: may need to revisit, or provide a better "type diff" output. # actual = Puppet::Pops::Types::TypeCalculator.generalize!(inferred_type) if block value = block.call(nil, type, actual) else raise Puppet::ParseError, "assert_type(): Expected type #{type} does not match actual: #{actual}" end end value end # @param type_string [String] the type the value must be an instance of given in String form - # @param value [Optional[Object]] the value to assert + # @param value [Object] the value to assert # def assert_type_s(type_string, value) t = Puppet::Pops::Types::TypeParser.new.parse(type_string) assert_type(t, value) end end diff --git a/lib/puppet/functions/with.rb b/lib/puppet/functions/with.rb new file mode 100644 index 000000000..1b2b1e0e4 --- /dev/null +++ b/lib/puppet/functions/with.rb @@ -0,0 +1,15 @@ +# Call a lambda with the given arguments. Since the parameters of the lambda +# are local to the lambda's scope, this can be used to create private sections +# of logic in a class so that the variables are not visible outside of the +# class. +Puppet::Functions.create_function(:with) do + dispatch :with do + param 'Object', 'arg' + arg_count(0, :default) + required_block_param + end + + def with(*args) + args[-1].call({}, *args[0..-2]) + end +end diff --git a/lib/puppet/parser/ast/pops_bridge.rb b/lib/puppet/parser/ast/pops_bridge.rb index 77d06da9d..cf3ab656a 100644 --- a/lib/puppet/parser/ast/pops_bridge.rb +++ b/lib/puppet/parser/ast/pops_bridge.rb @@ -1,209 +1,246 @@ require 'puppet/parser/ast/top_level_construct' require 'puppet/pops' # The AST::Bridge contains classes that bridges between the new Pops based model # and the 3.x AST. This is required to be able to reuse the Puppet::Resource::Type which is # fundamental for the rest of the logic. # class Puppet::Parser::AST::PopsBridge # Bridges to one Pops Model Expression # The @value is the expression # This is used to represent the body of a class, definition, or node, and for each parameter's default value # expression. # class Expression < Puppet::Parser::AST::Leaf def initialize args super @@evaluator ||= Puppet::Pops::Parser::EvaluatingParser::Transitional.new() end def to_s Puppet::Pops::Model::ModelTreeDumper.new.dump(@value) end def evaluate(scope) @@evaluator.evaluate(scope, @value) end # Adapts to 3x where top level constructs needs to have each to iterate over children. Short circuit this # by yielding self. By adding this there is no need to wrap a pops expression inside an AST::BlockExpression # def each yield self end def sequence_with(other) if value.nil? # This happens when testing and not having a complete setup other else # When does this happen ? Ever ? raise "sequence_with called on Puppet::Parser::AST::PopsBridge::Expression - please report use case" # What should be done if the above happens (We don't want this to happen). # Puppet::Parser::AST::BlockExpression.new(:children => [self] + other.children) end end # The 3x requires code plugged in to an AST to have this in certain positions in the tree. The purpose # is to either print the content, or to look for things that needs to be defined. This implementation # cheats by always returning an empty array. (This allows simple files to not require a "Program" at the top. # def children [] end end class NilAsUndefExpression < Expression def evaluate(scope) result = super result.nil? ? :undef : result end end # Bridges the top level "Program" produced by the pops parser. # Its main purpose is to give one point where all definitions are instantiated (actually defined since the # Puppet 3x terminology is somewhat misleading - the definitions are instantiated, but instances of the created types # are not created, that happens when classes are included / required, nodes are matched and when resources are instantiated # by a resource expression (which is also used to instantiate a host class). # class Program < Puppet::Parser::AST::TopLevelConstruct attr_reader :program_model, :context def initialize(program_model, context = {}) @program_model = program_model @context = context @ast_transformer ||= Puppet::Pops::Model::AstTransformer.new(@context[:file]) @@evaluator ||= Puppet::Pops::Parser::EvaluatingParser::Transitional.new() end # This is the 3x API, the 3x AST searches through all code to find the instructions that can be instantiated. # This Pops-model based instantiation relies on the parser to build this list while parsing (which is more # efficient as it avoids one full scan of all logic via recursive enumeration/yield) # def instantiate(modname) @program_model.definitions.collect do |d| case d when Puppet::Pops::Model::HostClassDefinition instantiate_HostClassDefinition(d, modname) when Puppet::Pops::Model::ResourceTypeDefinition instantiate_ResourceTypeDefinition(d, modname) when Puppet::Pops::Model::NodeDefinition instantiate_NodeDefinition(d, modname) else raise Puppet::ParseError, "Internal Error: Unknown type of definition - got '#{d.class}'" end end.flatten().compact() # flatten since node definition may have returned an array # Compact since functions are not understood by compiler end def evaluate(scope) @@evaluator.evaluate(scope, program_model) end # Adapts to 3x where top level constructs needs to have each to iterate over children. Short circuit this # by yielding self. This means that the HostClass container will call this bridge instance with `instantiate`. # def each yield self end private def instantiate_Parameter(o) # 3x needs parameters as an array of `[name]` or `[name, value_expr]` # One problem is that the parameter evaluation takes place in the wrong context in 3x (the caller's and # can thus reference all sorts of information. Here the value expression is wrapped in an AST Bridge to a Pops # expression since the Pops side can not control the evaluation if o.value - [ o.name, NilAsUndefExpression.new(:value => o.value) ] + [o.name, NilAsUndefExpression.new(:value => o.value)] else - [ o.name ] + [o.name] end end + def create_type_map(definition) + result = {} + # No need to do anything if there are no parameters + return result unless definition.parameters.size > 0 + + # No need to do anything if there are no typed parameters + typed_parameters = definition.parameters.select {|p| p.type_expr } + return result if typed_parameters.empty? + + # If there are typed parameters, they need to be evaluated to produce the corresponding type + # instances. This evaluation requires a scope. A scope is not available when doing deserialization + # (there is also no initialized evaluator). When running apply and test however, the environment is + # reused and we may reenter without a scope (which is fine). A debug message is then output in case + # there is the need to track down the odd corner case. See {#obtain_scope}. + # + if scope = obtain_scope + typed_parameters.each do |p| + result[p.name] = @@evaluator.evaluate(scope, p.type_expr) + end + end + result + end + + # Obtains the scope or issues a warning if :global_scope is not bound + def obtain_scope + scope = Puppet.lookup(:global_scope) do + # This occurs when testing and when applying a catalog (there is no scope available then), and + # when running tests that run a partial setup. + # This is bad if the logic is trying to compile, but a warning can not be issues since it is a normal + # use case that there is no scope when requesting the type in order to just get the parameters. + Puppet.debug("Instantiating Resource with type checked parameters - scope is missing, skipping type checking.") + nil + end + scope + end + # Produces a hash with data for Definition and HostClass def args_from_definition(o, modname) args = { :arguments => o.parameters.collect {|p| instantiate_Parameter(p) }, + :argument_types => create_type_map(o), :module_name => modname } unless is_nop?(o.body) args[:code] = Expression.new(:value => o.body) end @ast_transformer.merge_location(args, o) end def instantiate_HostClassDefinition(o, modname) args = args_from_definition(o, modname) args[:parent] = o.parent_class Puppet::Resource::Type.new(:hostclass, o.name, @context.merge(args)) end def instantiate_ResourceTypeDefinition(o, modname) Puppet::Resource::Type.new(:definition, o.name, @context.merge(args_from_definition(o, modname))) end def instantiate_NodeDefinition(o, modname) args = { :module_name => modname } unless is_nop?(o.body) args[:code] = Expression.new(:value => o.body) end unless is_nop?(o.parent) args[:parent] = @ast_transformer.hostname(o.parent) end host_matches = @ast_transformer.hostname(o.host_matches) @ast_transformer.merge_location(args, o) host_matches.collect do |name| Puppet::Resource::Type.new(:node, name, @context.merge(args)) end end # Propagates a found Function to the appropriate loader. # This is for 4x future-evaluator/loader # def instantiate_FunctionDefinition(function_definition, modname) loaders = (Puppet.lookup(:loaders) { nil }) unless loaders raise Puppet::ParseError, "Internal Error: Puppet Context ':loaders' missing - cannot define any functions" end loader = if modname.nil? || modname == "" # TODO : Later when functions can be private, a decision is needed regarding what that means. # A private environment loader could be used for logic outside of modules, then only that logic # would see the function. # # Use the private loader, this function may see the environment's dependencies (currently, all modules) loaders.private_environment_loader() else # TODO : Later check if function is private, and then add it to # private_loader_for_module # loaders.public_loader_for_module(modname) end unless loader raise Puppet::ParseError, "Internal Error: did not find public loader for module: '#{modname}'" end # Instantiate Function, and store it in the environment loader typed_name, f = Puppet::Pops::Loader::PuppetFunctionInstantiator.create_from_model(function_definition, loader) loader.set_entry(typed_name, f, Puppet::Pops::Adapters::SourcePosAdapter.adapt(function_definition).to_uri) nil # do not want the function to inadvertently leak into 3x end def code() Expression.new(:value => @value) end def is_nop?(o) @ast_transformer.is_nop?(o) end end end diff --git a/lib/puppet/parser/functions/each.rb b/lib/puppet/parser/functions/each.rb index 39d26ba38..6c8b6356f 100644 --- a/lib/puppet/parser/functions/each.rb +++ b/lib/puppet/parser/functions/each.rb @@ -1,109 +1,110 @@ Puppet::Parser::Functions::newfunction( :each, :type => :rvalue, :arity => 2, :doc => <<-'ENDHEREDOC') do |args| Applies a parameterized block to each element in a sequence of selected entries from the first argument and returns the first argument. This function takes two mandatory arguments: the first should be an Array or a Hash or something that is of enumerable type (integer, Integer range, or String), and the second a parameterized block as produced by the puppet syntax: $a.each |$x| { ... } each($a) |$x| { ... } When the first argument is an Array (or of enumerable type other than Hash), the parameterized block should define one or two block parameters. For each application of the block, the next element from the array is selected, and it is passed to the block if the block has one parameter. If the block has two parameters, the first is the elements index, and the second the value. The index starts from 0. $a.each |$index, $value| { ... } each($a) |$index, $value| { ... } When the first argument is a Hash, the parameterized block should define one or two parameters. When one parameter is defined, the iteration is performed with each entry as an array of `[key, value]`, and when two parameters are defined the iteration is performed with key and value. $a.each |$entry| { ..."key ${$entry[0]}, value ${$entry[1]}" } $a.each |$key, $value| { ..."key ${key}, value ${value}" } *Examples* [1,2,3].each |$val| { ... } # 1, 2, 3 [5,6,7].each |$index, $val| { ... } # (0, 5), (1, 6), (2, 7) {a=>1, b=>2, c=>3}].each |$val| { ... } # ['a', 1], ['b', 2], ['c', 3] {a=>1, b=>2, c=>3}.each |$key, $val| { ... } # ('a', 1), ('b', 2), ('c', 3) Integer[ 10, 20 ].each |$index, $value| { ... } # (0, 10), (1, 11) ... "hello".each |$char| { ... } # 'h', 'e', 'l', 'l', 'o' 3.each |$number| { ... } # 0, 1, 2 - Since 3.2 for Array and Hash - Since 3.5 for other enumerables - requires `parser = future`. ENDHEREDOC require 'puppet/parser/ast/lambda' def foreach_Hash(o, scope, pblock, serving_size) enumerator = o.each_pair if serving_size == 1 (o.size).times do pblock.call(scope, enumerator.next) end else (o.size).times do pblock.call(scope, *enumerator.next) end end end def foreach_Enumerator(enumerator, scope, pblock, serving_size) index = 0 if serving_size == 1 begin loop { pblock.call(scope, enumerator.next) } rescue StopIteration end else begin loop do pblock.call(scope, index, enumerator.next) index = index +1 end rescue StopIteration end end end raise ArgumentError, ("each(): wrong number of arguments (#{args.length}; expected 2, got #{args.length})") if args.length != 2 receiver = args[0] pblock = args[1] raise ArgumentError, ("each(): wrong argument type (#{args[1].class}; must be a parameterized block.") unless pblock.respond_to?(:puppet_lambda) - serving_size = pblock.parameter_count + # if captures rest, use a serving size of 2 + serving_size = pblock.last_captures_rest? ? 2 : pblock.parameter_count if serving_size == 0 raise ArgumentError, "each(): block must define at least one parameter; value. Block has 0." end case receiver when Hash if serving_size > 2 raise ArgumentError, "each(): block must define at most two parameters; key, value. Block has #{serving_size}; "+ pblock.parameter_names.join(', ') end foreach_Hash(receiver, self, pblock, serving_size) else if serving_size > 2 raise ArgumentError, "each(): block must define at most two parameters; index, value. Block has #{serving_size}; "+ pblock.parameter_names.join(', ') end enum = Puppet::Pops::Types::Enumeration.enumerator(receiver) unless enum raise ArgumentError, ("each(): wrong argument type (#{receiver.class}; must be something enumerable.") end foreach_Enumerator(enum, self, pblock, serving_size) end # each always produces the receiver receiver end diff --git a/lib/puppet/parser/functions/filter.rb b/lib/puppet/parser/functions/filter.rb index 5760a8a75..17c3f132c 100644 --- a/lib/puppet/parser/functions/filter.rb +++ b/lib/puppet/parser/functions/filter.rb @@ -1,100 +1,102 @@ require 'puppet/parser/ast/lambda' Puppet::Parser::Functions::newfunction( :filter, :type => :rvalue, :arity => 2, :doc => <<-'ENDHEREDOC') do |args| Applies a parameterized block to each element in a sequence of entries from the first argument and returns an array or hash (same type as left operand for array/hash, and array for other enumerable types) with the entries for which the block evaluates to `true`. This function takes two mandatory arguments: the first should be an Array, a Hash, or an Enumerable object (integer, Integer range, or String), and the second a parameterized block as produced by the puppet syntax: $a.filter |$x| { ... } filter($a) |$x| { ... } When the first argument is something other than a Hash, the block is called with each entry in turn. When the first argument is a Hash the entry is an array with `[key, value]`. *Examples* # selects all that end with berry $a = ["raspberry", "blueberry", "orange"] $a.filter |$x| { $x =~ /berry$/ } # rasberry, blueberry If the block defines two parameters, they will be set to `index, value` (with index starting at 0) for all enumerables except Hash, and to `key, value` for a Hash. *Examples* # selects all that end with 'berry' at an even numbered index $a = ["raspberry", "blueberry", "orange"] $a.filter |$index, $x| { $index % 2 == 0 and $x =~ /berry$/ } # raspberry # selects all that end with 'berry' and value >= 1 $a = {"raspberry"=>0, "blueberry"=>1, "orange"=>1} $a.filter |$key, $x| { $x =~ /berry$/ and $x >= 1 } # blueberry - Since 3.4 for Array and Hash - Since 3.5 for other enumerables - requires `parser = future` ENDHEREDOC def filter_Enumerator(enumerator, scope, pblock, serving_size) result = [] index = 0 if serving_size == 1 begin loop { pblock.call(scope, it = enumerator.next) == true ? result << it : nil } rescue StopIteration end else begin loop do pblock.call(scope, index, it = enumerator.next) == true ? result << it : nil index = index +1 end rescue StopIteration end end result end receiver = args[0] pblock = args[1] raise ArgumentError, ("filter(): wrong argument type (#{pblock.class}; must be a parameterized block.") unless pblock.respond_to?(:puppet_lambda) - serving_size = pblock.parameter_count + + # if captures rest, use a serving size of 2 + serving_size = pblock.last_captures_rest? ? 2 : pblock.parameter_count if serving_size == 0 raise ArgumentError, "filter(): block must define at least one parameter; value. Block has 0." end case receiver when Hash if serving_size > 2 raise ArgumentError, "filter(): block must define at most two parameters; key, value. Block has #{serving_size}; "+ pblock.parameter_names.join(', ') end if serving_size == 1 result = receiver.select {|x, y| pblock.call(self, [x, y]) } else result = receiver.select {|x, y| pblock.call(self, x, y) } end # Ruby 1.8.7 returns Array result = Hash[result] unless result.is_a? Hash result else if serving_size > 2 raise ArgumentError, "filter(): block must define at most two parameters; index, value. Block has #{serving_size}; "+ pblock.parameter_names.join(', ') end enum = Puppet::Pops::Types::Enumeration.enumerator(receiver) unless enum raise ArgumentError, ("filter(): wrong argument type (#{receiver.class}; must be something enumerable.") end filter_Enumerator(enum, self, pblock, serving_size) end end diff --git a/lib/puppet/parser/functions/map.rb b/lib/puppet/parser/functions/map.rb index 8bc9fd383..fb10c7177 100644 --- a/lib/puppet/parser/functions/map.rb +++ b/lib/puppet/parser/functions/map.rb @@ -1,96 +1,98 @@ require 'puppet/parser/ast/lambda' Puppet::Parser::Functions::newfunction( :map, :type => :rvalue, :arity => 2, :doc => <<-'ENDHEREDOC') do |args| Applies a parameterized block to each element in a sequence of entries from the first argument and returns an array with the result of each invocation of the parameterized block. This function takes two mandatory arguments: the first should be an Array, Hash, or of Enumerable type (integer, Integer range, or String), and the second a parameterized block as produced by the puppet syntax: $a.map |$x| { ... } map($a) |$x| { ... } When the first argument `$a` is an Array or of enumerable type, the block is called with each entry in turn. When the first argument is a hash the entry is an array with `[key, value]`. *Examples* # Turns hash into array of values $a.map |$x|{ $x[1] } # Turns hash into array of keys $a.map |$x| { $x[0] } When using a block with 2 parameters, the element's index (starting from 0) for an array, and the key for a hash is given to the block's first parameter, and the value is given to the block's second parameter.args. *Examples* # Turns hash into array of values $a.map |$key,$val|{ $val } # Turns hash into array of keys $a.map |$key,$val|{ $key } - Since 3.4 for Array and Hash - Since 3.5 for other enumerables, and support for blocks with 2 parameters - requires `parser = future` ENDHEREDOC def map_Enumerator(enumerator, scope, pblock, serving_size) result = [] index = 0 if serving_size == 1 begin loop { result << pblock.call(scope, enumerator.next) } rescue StopIteration end else begin loop do result << pblock.call(scope, index, enumerator.next) index = index +1 end rescue StopIteration end end result end receiver = args[0] pblock = args[1] - raise ArgumentError, ("map(): wrong argument type (#{pblock.class}; must be a parameterized block.") unless pblock.respond_to?(:puppet_lambda) - serving_size = pblock.parameter_count + + # if captures rest, use a serving size of 2 + serving_size = pblock.last_captures_rest? ? 2 : pblock.parameter_count + if serving_size == 0 raise ArgumentError, "map(): block must define at least one parameter; value. Block has 0." end case receiver when Hash if serving_size > 2 raise ArgumentError, "map(): block must define at most two parameters; key, value.args Block has #{serving_size}; "+ pblock.parameter_names.join(', ') end if serving_size == 1 result = receiver.map {|x, y| pblock.call(self, [x, y]) } else result = receiver.map {|x, y| pblock.call(self, x, y) } end else if serving_size > 2 raise ArgumentError, "map(): block must define at most two parameters; index, value. Block has #{serving_size}; "+ pblock.parameter_names.join(', ') end enum = Puppet::Pops::Types::Enumeration.enumerator(receiver) unless enum raise ArgumentError, ("map(): wrong argument type (#{receiver.class}; must be something enumerable.") end result = map_Enumerator(enum, self, pblock, serving_size) end result end diff --git a/lib/puppet/parser/functions/reduce.rb b/lib/puppet/parser/functions/reduce.rb index 078ebc2e9..3686bdb03 100644 --- a/lib/puppet/parser/functions/reduce.rb +++ b/lib/puppet/parser/functions/reduce.rb @@ -1,100 +1,102 @@ Puppet::Parser::Functions::newfunction( :reduce, :type => :rvalue, :arity => -2, :doc => <<-'ENDHEREDOC') do |args| Applies a parameterized block to each element in a sequence of entries from the first argument (_the enumerable_) and returns the last result of the invocation of the parameterized block. This function takes two mandatory arguments: the first should be an Array, Hash, or something of enumerable type, and the last a parameterized block as produced by the puppet syntax: $a.reduce |$memo, $x| { ... } reduce($a) |$memo, $x| { ... } When the first argument is an Array or someting of an enumerable type, the block is called with each entry in turn. When the first argument is a hash each entry is converted to an array with `[key, value]` before being fed to the block. An optional 'start memo' value may be supplied as an argument between the array/hash and mandatory block. $a.reduce(start) |$memo, $x| { ... } reduce($a, start) |$memo, $x| { ... } If no 'start memo' is given, the first invocation of the parameterized block will be given the first and second elements of the enumeration, and if the enumerable has fewer than 2 elements, the first element is produced as the result of the reduction without invocation of the block. On each subsequent invocation, the produced value of the invoked parameterized block is given as the memo in the next invocation. *Examples* # Reduce an array $a = [1,2,3] $a.reduce |$memo, $entry| { $memo + $entry } #=> 6 # Reduce hash values $a = {a => 1, b => 2, c => 3} $a.reduce |$memo, $entry| { [sum, $memo[1]+$entry[1]] } #=> [sum, 6] # reverse a string "abc".reduce |$memo, $char| { "$char$memo" } #=>"cbe" It is possible to provide a starting 'memo' as an argument. *Examples* # Reduce an array $a = [1,2,3] $a.reduce(4) |$memo, $entry| { $memo + $entry } #=> 10 # Reduce hash values $a = {a => 1, b => 2, c => 3} $a.reduce([na, 4]) |$memo, $entry| { [sum, $memo[1]+$entry[1]] } #=> [sum, 10] *Examples* Integer[1,4].reduce |$memo, $x| { $memo + $x } #=> 10 - Since 3.2 for Array and Hash - Since 3.5 for additional enumerable types - requires `parser = future`. ENDHEREDOC require 'puppet/parser/ast/lambda' case args.length when 2 pblock = args[1] when 3 pblock = args[2] else raise ArgumentError, ("reduce(): wrong number of arguments (#{args.length}; expected 2 or 3, got #{args.length})") end unless pblock.respond_to?(:puppet_lambda) raise ArgumentError, ("reduce(): wrong argument type (#{pblock.class}; must be a parameterized block.") end receiver = args[0] enum = Puppet::Pops::Types::Enumeration.enumerator(receiver) unless enum raise ArgumentError, ("reduce(): wrong argument type (#{receiver.class}; must be something enumerable.") end - serving_size = pblock.parameter_count + # if captures rest, use a serving size of 2 + serving_size = pblock.last_captures_rest? ? 2 : pblock.parameter_count + if serving_size != 2 raise ArgumentError, "reduce(): block must define 2 parameters; memo, value. Block has #{serving_size}; "+ pblock.parameter_names.join(', ') end if args.length == 3 enum.reduce(args[1]) {|memo, x| pblock.call(self, memo, x) } else enum.reduce {|memo, x| pblock.call(self, memo, x) } end end diff --git a/lib/puppet/parser/functions/slice.rb b/lib/puppet/parser/functions/slice.rb index bcc830f74..b50726d3f 100644 --- a/lib/puppet/parser/functions/slice.rb +++ b/lib/puppet/parser/functions/slice.rb @@ -1,116 +1,120 @@ Puppet::Parser::Functions::newfunction( :slice, :type => :rvalue, :arity => -2, :doc => <<-'ENDHEREDOC') do |args| Applies a parameterized block to each _slice_ of elements in a sequence of selected entries from the first argument and returns the first argument, or if no block is given returns a new array with a concatenation of the slices. This function takes two mandatory arguments: the first, `$a`, should be an Array, Hash, or something of enumerable type (integer, Integer range, or String), and the second, `$n`, the number of elements to include in each slice. The optional third argument should be a a parameterized block as produced by the puppet syntax: $a.slice($n) |$x| { ... } slice($a) |$x| { ... } The parameterized block should have either one parameter (receiving an array with the slice), or the same number of parameters as specified by the slice size (each parameter receiving its part of the slice). In case there are fewer remaining elements than the slice size for the last slice it will contain the remaining elements. When the block has multiple parameters, excess parameters are set to :undef for an array or enumerable type, and to empty arrays for a Hash. $a.slice(2) |$first, $second| { ... } When the first argument is a Hash, each `key,value` entry is counted as one, e.g, a slice size of 2 will produce an array of two arrays with key, and value. $a.slice(2) |$entry| { notice "first ${$entry[0]}, second ${$entry[1]}" } $a.slice(2) |$first, $second| { notice "first ${first}, second ${second}" } When called without a block, the function produces a concatenated result of the slices. slice([1,2,3,4,5,6], 2) # produces [[1,2], [3,4], [5,6]] slice(Integer[1,6], 2) # produces [[1,2], [3,4], [5,6]] slice(4,2) # produces [[0,1], [2,3]] slice('hello',2) # produces [[h, e], [l, l], [o]] - Since 3.2 for Array and Hash - Since 3.5 for additional enumerable types - requires `parser = future`. ENDHEREDOC require 'puppet/parser/ast/lambda' require 'puppet/parser/scope' def each_Common(o, slice_size, filler, scope, pblock) - serving_size = pblock ? pblock.parameter_count : 1 + if pblock + serving_size = pblock.last_captures_rest? ? slice_size : pblock.parameter_count + else + serving_size = 1 + end if serving_size == 0 raise ArgumentError, "slice(): block must define at least one parameter. Block has 0." end unless serving_size == 1 || serving_size == slice_size raise ArgumentError, "slice(): block must define one parameter, or " + "the same number of parameters as the given size of the slice (#{slice_size}). Block has #{serving_size}; "+ pblock.parameter_names.join(', ') end enumerator = o.each_slice(slice_size) result = [] if serving_size == 1 begin if pblock loop do pblock.call(scope, enumerator.next) end else loop do result << enumerator.next end end rescue StopIteration end else begin loop do a = enumerator.next if a.size < serving_size a = a.dup.fill(filler, a.length...serving_size) end pblock.call(scope, *a) end rescue StopIteration end end if pblock o else result end end raise ArgumentError, ("slice(): wrong number of arguments (#{args.length}; must be 2 or 3)") unless args.length == 2 || args.length == 3 if args.length >= 2 begin slice_size = Puppet::Parser::Scope.number?(args[1]) rescue raise ArgumentError, ("slice(): wrong argument type (#{args[1]}; must be number.") end end raise ArgumentError, ("slice(): wrong argument type (#{args[1]}; must be number.") unless slice_size raise ArgumentError, ("slice(): wrong argument value: #{slice_size}; is not a positive integer number > 0") unless slice_size.is_a?(Fixnum) && slice_size > 0 receiver = args[0] # the block is optional, ok if nil, function then produces an array pblock = args[2] raise ArgumentError, ("slice(): wrong argument type (#{args[2].class}; must be a parameterized block.") unless pblock.respond_to?(:puppet_lambda) || args.length == 2 case receiver when Hash each_Common(receiver, slice_size, [], self, pblock) else enum = Puppet::Pops::Types::Enumeration.enumerator(receiver) if enum.nil? raise ArgumentError, ("slice(): given type '#{tc.string(receiver)}' is not enumerable") end result = each_Common(enum, slice_size, :undef, self, pblock) pblock ? receiver : result end end diff --git a/lib/puppet/pops.rb b/lib/puppet/pops.rb index 3c06c5d71..b4688000b 100644 --- a/lib/puppet/pops.rb +++ b/lib/puppet/pops.rb @@ -1,120 +1,121 @@ module Puppet # The Pops language system. This includes the parser, evaluator, AST model, and # Binder. # # @todo Explain how a user should use this to parse and evaluate the puppet # language. # # @note Warning: Pops is still considered experimental, as such the API may # change at any time. # # @api public module Pops require 'puppet/pops/patterns' require 'puppet/pops/utils' require 'puppet/pops/adaptable' require 'puppet/pops/adapters' require 'puppet/pops/visitable' require 'puppet/pops/visitor' require 'puppet/pops/containment' require 'puppet/pops/issues' require 'puppet/pops/semantic_error' require 'puppet/pops/label_provider' require 'puppet/pops/validation' require 'puppet/pops/issue_reporter' require 'puppet/pops/model/model' module Types require 'puppet/pops/types/types' require 'puppet/pops/types/type_calculator' require 'puppet/pops/types/type_factory' require 'puppet/pops/types/type_parser' require 'puppet/pops/types/class_loader' require 'puppet/pops/types/enumeration' end module Model require 'puppet/pops/model/tree_dumper' require 'puppet/pops/model/ast_transformer' require 'puppet/pops/model/ast_tree_dumper' require 'puppet/pops/model/factory' require 'puppet/pops/model/model_tree_dumper' require 'puppet/pops/model/model_label_provider' end module Binder module SchemeHandler # the handlers are auto loaded via bindings end module Producers require 'puppet/pops/binder/producers' end require 'puppet/pops/binder/binder' require 'puppet/pops/binder/bindings_model' require 'puppet/pops/binder/binder_issues' require 'puppet/pops/binder/bindings_checker' require 'puppet/pops/binder/bindings_factory' require 'puppet/pops/binder/bindings_label_provider' require 'puppet/pops/binder/bindings_validator_factory' require 'puppet/pops/binder/injector_entry' require 'puppet/pops/binder/key_factory' require 'puppet/pops/binder/injector' require 'puppet/pops/binder/bindings_composer' require 'puppet/pops/binder/bindings_model_dumper' require 'puppet/pops/binder/system_bindings' require 'puppet/pops/binder/bindings_loader' require 'puppet/pops/binder/lookup' module Config require 'puppet/pops/binder/config/binder_config' require 'puppet/pops/binder/config/binder_config_checker' require 'puppet/pops/binder/config/issues' require 'puppet/pops/binder/config/diagnostic_producer' end end module Parser require 'puppet/pops/parser/eparser' require 'puppet/pops/parser/parser_support' require 'puppet/pops/parser/locator' require 'puppet/pops/parser/locatable' require 'puppet/pops/parser/lexer' require 'puppet/pops/parser/lexer2' require 'puppet/pops/parser/evaluating_parser' require 'puppet/pops/parser/epp_parser' end module Validation require 'puppet/pops/validation/checker3_1' require 'puppet/pops/validation/validator_factory_3_1' require 'puppet/pops/validation/checker4_0' require 'puppet/pops/validation/validator_factory_4_0' end module Evaluator require 'puppet/pops/evaluator/callable_signature' require 'puppet/pops/evaluator/runtime3_support' require 'puppet/pops/evaluator/evaluator_impl' require 'puppet/pops/evaluator/epp_evaluator' + require 'puppet/pops/evaluator/callable_mismatch_describer' end # Subsystem for puppet functions defined in ruby. # # @api public module Functions require 'puppet/pops/functions/function' require 'puppet/pops/functions/dispatch' require 'puppet/pops/functions/dispatcher' end end require 'puppet/parser/ast/pops_bridge' require 'puppet/bindings' require 'puppet/functions' end diff --git a/lib/puppet/pops/evaluator/access_operator.rb b/lib/puppet/pops/evaluator/access_operator.rb index 29a981538..c37e9f0f0 100644 --- a/lib/puppet/pops/evaluator/access_operator.rb +++ b/lib/puppet/pops/evaluator/access_operator.rb @@ -1,598 +1,609 @@ # AccessOperator handles operator [] # This operator is part of evaluation. # class Puppet::Pops::Evaluator::AccessOperator # Provides access to the Puppet 3.x runtime (scope, etc.) # This separation has been made to make it easier to later migrate the evaluator to an improved runtime. # include Puppet::Pops::Evaluator::Runtime3Support Issues = Puppet::Pops::Issues TYPEFACTORY = Puppet::Pops::Types::TypeFactory attr_reader :semantic # Initialize with AccessExpression to enable reporting issues # @param access_expression [Puppet::Pops::Model::AccessExpression] the semantic object being evaluated # @return [void] # def initialize(access_expression) @@access_visitor ||= Puppet::Pops::Visitor.new(self, "access", 2, nil) @semantic = access_expression end def access (o, scope, *keys) @@access_visitor.visit_this_2(self, o, scope, keys) end protected def access_Object(o, scope, keys) fail(Issues::OPERATOR_NOT_APPLICABLE, @semantic.left_expr, :operator=>'[]', :left_value => o) end def access_String(o, scope, keys) keys.flatten! result = case keys.size when 0 fail(Puppet::Pops::Issues::BAD_STRING_SLICE_ARITY, @semantic.left_expr, {:actual => keys.size}) when 1 # Note that Ruby 1.8.7 requires a length of 1 to produce a String k1 = coerce_numeric(keys[0], @semantic.keys, scope) bad_access_key_type(o, 0, k1, Integer) unless k1.is_a?(Integer) k2 = 1 k1 = k1 < 0 ? o.length + k1 : k1 # abs pos # if k1 is outside, a length of 1 always produces an empty string if k1 < 0 '' else o[ k1, k2 ] end when 2 k1 = coerce_numeric(keys[0], @semantic.keys, scope) k2 = coerce_numeric(keys[1], @semantic.keys, scope) [k1, k2].each_with_index { |k,i| bad_access_key_type(o, i, k, Integer) unless k.is_a?(Integer) } k1 = k1 < 0 ? o.length + k1 : k1 # abs pos (negative is count from end) k2 = k2 < 0 ? o.length - k1 + k2 + 1 : k2 # abs length (negative k2 is length from pos to end count) # if k1 is outside, adjust to first position, and adjust length if k1 < 0 k2 = k2 + k1 k1 = 0 end o[ k1, k2 ] else fail(Puppet::Pops::Issues::BAD_STRING_SLICE_ARITY, @semantic.left_expr, {:actual => keys.size}) end # Specified as: an index outside of range, or empty result == empty string (result.nil? || result.empty?) ? '' : result end # Parameterizes a PRegexp Type with a pattern string or r ruby egexp # def access_PRegexpType(o, scope, keys) keys.flatten! unless keys.size == 1 blamed = keys.size == 0 ? @semantic : @semantic.keys[1] fail(Puppet::Pops::Issues::BAD_TYPE_SLICE_ARITY, blamed, :base_type => o, :min=>1, :actual => keys.size) end assert_keys(keys, o, 1, 1, String, Regexp) Puppet::Pops::Types::TypeFactory.regexp(*keys) end # Evaluates [] with 1 or 2 arguments. One argument is an index lookup, two arguments is a slice from/to. # def access_Array(o, scope, keys) keys.flatten! case keys.size when 0 fail(Puppet::Pops::Issues::BAD_ARRAY_SLICE_ARITY, @semantic.left_expr, {:actual => keys.size}) when 1 k = coerce_numeric(keys[0], @semantic.keys[0], scope) unless k.is_a?(Integer) bad_access_key_type(o, 0, k, Integer) end o[k] when 2 # A slice [from, to] with support for -1 to mean start, or end respectively. k1 = coerce_numeric(keys[0], @semantic.keys[0], scope) k2 = coerce_numeric(keys[1], @semantic.keys[1], scope) [k1, k2].each_with_index { |k,i| bad_access_key_type(o, i, k, Integer) unless k.is_a?(Integer) } # Help confused Ruby do the right thing (it truncates to the right, but negative index + length can never overlap # the available range. k1 = k1 < 0 ? o.length + k1 : k1 # abs pos (negative is count from end) k2 = k2 < 0 ? o.length - k1 + k2 + 1 : k2 # abs length (negative k2 is length from pos to end count) # if k1 is outside, adjust to first position, and adjust length if k1 < 0 k2 = k2 + k1 k1 = 0 end # Help ruby always return empty array when asking for a sub array result = o[ k1, k2 ] result.nil? ? [] : result else fail(Puppet::Pops::Issues::BAD_ARRAY_SLICE_ARITY, @semantic.left_expr, {:actual => keys.size}) end end # Evaluates [] with support for one or more arguments. If more than one argument is used, the result # is an array with each lookup. # @note # Does not flatten its keys to enable looking up with a structure # def access_Hash(o, scope, keys) # Look up key in hash, if key is nil or :undef, try alternate form before giving up. # This makes :undef and nil "be the same key". (The alternative is to always only write one or the other # in all hashes - that is much harder to guarantee since the Hash is a regular Ruby hash. # result = keys.collect do |k| o.fetch(k) do |key| case key when nil o[:undef] when :undef o[:nil] else nil end end end case result.size when 0 fail(Puppet::Pops::Issues::BAD_HASH_SLICE_ARITY, @semantic.left_expr, {:actual => keys.size}) when 1 result.pop else # remove nil elements and return result.compact! result end end # Ruby does not have an infinity constant. TODO: Consider having one constant in Puppet. Now it is in several places. INFINITY = 1.0 / 0.0 def access_PEnumType(o, scope, keys) keys.flatten! assert_keys(keys, o, 1, INFINITY, String) Puppet::Pops::Types::TypeFactory.enum(*keys) end def access_PVariantType(o, scope, keys) keys.flatten! assert_keys(keys, o, 1, INFINITY, Puppet::Pops::Types::PAbstractType) Puppet::Pops::Types::TypeFactory.variant(*keys) end def access_PTupleType(o, scope, keys) keys.flatten! if TYPEFACTORY.is_range_parameter?(keys[-2]) && TYPEFACTORY.is_range_parameter?(keys[-1]) size_type = TYPEFACTORY.range(keys[-2], keys[-1]) keys = keys[0, keys.size - 2] elsif TYPEFACTORY.is_range_parameter?(keys[-1]) size_type = TYPEFACTORY.range(keys[-1], :default) keys = keys[0, keys.size - 1] end assert_keys(keys, o, 1, INFINITY, Puppet::Pops::Types::PAbstractType) t = Puppet::Pops::Types::TypeFactory.tuple(*keys) # set size type, or nil for default (exactly 1) t.size_type = size_type t end def access_PCallableType(o, scope, keys) TYPEFACTORY.callable(*keys) end def access_PStructType(o, scope, keys) assert_keys(keys, o, 1, 1, Hash) TYPEFACTORY.struct(keys[0]) end def access_PStringType(o, scope, keys) keys.flatten! case keys.size when 1 size_t = collection_size_t(0, keys[0]) when 2 size_t = collection_size_t(0, keys[0], keys[1]) else fail(Puppet::Pops::Issues::BAD_STRING_SLICE_ARITY, @semantic, {:actual => keys.size}) end string_t = Puppet::Pops::Types::TypeFactory.string() string_t.size_type = size_t string_t end # Asserts type of each key and calls fail with BAD_TYPE_SPECIFICATION # @param keys [Array] the evaluated keys # @param o [Object] evaluated LHS reported as :base_type # @param min [Integer] the minimum number of keys (typically 1) # @param max [Numeric] the maximum number of keys (use same as min, specific number, or INFINITY) # @param allowed_classes [Class] a variable number of classes that each key must be an instance of (any) # @api private # def assert_keys(keys, o, min, max, *allowed_classes) size = keys.size unless size.between?(min, max || INFINITY) fail(Puppet::Pops::Issues::BAD_TYPE_SLICE_ARITY, @semantic, :base_type => o, :min=>1, :max => max, :actual => keys.size) end keys.each_with_index do |k, i| unless allowed_classes.any? {|clazz| k.is_a?(clazz) } bad_type_specialization_key_type(o, i, k, *allowed_classes) end end end def bad_access_key_type(lhs, key_index, actual, *expected_classes) fail(Puppet::Pops::Issues::BAD_SLICE_KEY_TYPE, @semantic.keys[key_index], { :left_value => lhs, :actual => bad_key_type_name(actual), :expected_classes => expected_classes }) end def bad_key_type_name(actual) case actual when nil, :undef 'Undef' when :default 'Default' else actual.class.name end end def bad_type_specialization_key_type(type, key_index, actual, *expected_classes) label_provider = Puppet::Pops::Model::ModelLabelProvider.new() expected = expected_classes.map {|c| label_provider.label(c) }.join(' or ') fail(Puppet::Pops::Issues::BAD_TYPE_SPECIALIZATION, @semantic.keys[key_index], { :type => type, :message => "Cannot use #{bad_key_type_name(actual)} where #{expected} is expected" }) end def access_PPatternType(o, scope, keys) keys.flatten! assert_keys(keys, o, 1, INFINITY, String, Regexp, Puppet::Pops::Types::PPatternType, Puppet::Pops::Types::PRegexpType) Puppet::Pops::Types::TypeFactory.pattern(*keys) end def access_POptionalType(o, scope, keys) keys.flatten! if keys.size == 1 unless keys[0].is_a?(Puppet::Pops::Types::PAbstractType) fail(Puppet::Pops::Issues::BAD_TYPE_SLICE_TYPE, @semantic.keys[0], {:base_type => 'Optional-Type', :actual => keys[0].class}) end result = Puppet::Pops::Types::POptionalType.new() result.optional_type = keys[0] result else fail(Puppet::Pops::Issues::BAD_TYPE_SLICE_ARITY, @semantic, {:base_type => 'Optional-Type', :min => 1, :actual => keys.size}) end end def access_PType(o, scope, keys) keys.flatten! if keys.size == 1 unless keys[0].is_a?(Puppet::Pops::Types::PAbstractType) fail(Puppet::Pops::Issues::BAD_TYPE_SLICE_TYPE, @semantic.keys[0], {:base_type => 'Type-Type', :actual => keys[0].class}) end result = Puppet::Pops::Types::PType.new() result.type = keys[0] result else fail(Puppet::Pops::Issues::BAD_TYPE_SLICE_ARITY, @semantic, {:base_type => 'Type-Type', :min => 1, :actual => keys.size}) end end def access_PRubyType(o, scope, keys) keys.flatten! assert_keys(keys, o, 1, 1, String) # create ruby type based on name of class, not inference of key's type Puppet::Pops::Types::TypeFactory.ruby_type(keys[0]) end def access_PIntegerType(o, scope, keys) keys.flatten! unless keys.size.between?(1, 2) fail(Puppet::Pops::Issues::BAD_INTEGER_SLICE_ARITY, @semantic, {:actual => keys.size}) end keys.each_with_index do |x, index| fail(Puppet::Pops::Issues::BAD_INTEGER_SLICE_TYPE, @semantic.keys[index], {:actual => x.class}) unless (x.is_a?(Integer) || x == :default) end ranged_integer = Puppet::Pops::Types::PIntegerType.new() from, to = keys ranged_integer.from = from == :default ? nil : from ranged_integer.to = to == :default ? nil : to ranged_integer end def access_PFloatType(o, scope, keys) keys.flatten! unless keys.size.between?(1, 2) fail(Puppet::Pops::Issues::BAD_FLOAT_SLICE_ARITY, @semantic, {:actual => keys.size}) end keys.each_with_index do |x, index| fail(Puppet::Pops::Issues::BAD_FLOAT_SLICE_TYPE, @semantic.keys[index], {:actual => x.class}) unless (x.is_a?(Float) || x.is_a?(Integer) || x == :default) end ranged_float = Puppet::Pops::Types::PFloatType.new() from, to = keys ranged_float.from = from == :default || from.nil? ? nil : Float(from) ranged_float.to = to == :default || to.nil? ? nil : Float(to) ranged_float end # A Hash can create a new Hash type, one arg sets value type, two args sets key and value type in new type. # With 3 or 4 arguments, these are used to create a size constraint. # It is not possible to create a collection of Hash types directly. # def access_PHashType(o, scope, keys) keys.flatten! keys[0,2].each_with_index do |k, index| unless k.is_a?(Puppet::Pops::Types::PAbstractType) fail(Puppet::Pops::Issues::BAD_TYPE_SLICE_TYPE, @semantic.keys[index], {:base_type => 'Hash-Type', :actual => k.class}) end end case keys.size when 1 result = Puppet::Pops::Types::PHashType.new() result.key_type = o.key_type.copy result.element_type = keys[0] result when 2 result = Puppet::Pops::Types::PHashType.new() result.key_type = keys[0] result.element_type = keys[1] result when 3 result = Puppet::Pops::Types::PHashType.new() result.key_type = keys[0] result.element_type = keys[1] size_t = collection_size_t(1, keys[2]) result when 4 result = Puppet::Pops::Types::PHashType.new() result.key_type = keys[0] result.element_type = keys[1] size_t = collection_size_t(1, keys[2], keys[3]) result else fail(Puppet::Pops::Issues::BAD_TYPE_SLICE_ARITY, @semantic, { :base_type => 'Hash-Type', :min => 1, :max => 4, :actual => keys.size }) end result.size_type = size_t if size_t result end # CollectionType is parameterized with a range def access_PCollectionType(o, scope, keys) keys.flatten! case keys.size when 1 size_t = collection_size_t(1, keys[0]) when 2 size_t = collection_size_t(1, keys[0], keys[1]) else fail(Puppet::Pops::Issues::BAD_TYPE_SLICE_ARITY, @semantic, {:base_type => 'Collection-Type', :min => 1, :max => 2, :actual => keys.size}) end result = Puppet::Pops::Types::PCollectionType.new() result.size_type = size_t result end # An Array can create a new Array type. It is not possible to create a collection of Array types. # def access_PArrayType(o, scope, keys) keys.flatten! case keys.size when 1 size_t = nil when 2 size_t = collection_size_t(1, keys[1]) when 3 size_t = collection_size_t(1, keys[1], keys[2]) else fail(Puppet::Pops::Issues::BAD_TYPE_SLICE_ARITY, @semantic, {:base_type => 'Array-Type', :min => 1, :max => 3, :actual => keys.size}) end unless keys[0].is_a?(Puppet::Pops::Types::PAbstractType) fail(Puppet::Pops::Issues::BAD_TYPE_SLICE_TYPE, @semantic.keys[0], {:base_type => 'Array-Type', :actual => keys[0].class}) end result = Puppet::Pops::Types::PArrayType.new() result.element_type = keys[0] result.size_type = size_t result end # Produces an PIntegerType (range) given one or two keys. def collection_size_t(start_index, *keys) if keys.size == 1 && keys[0].is_a?(Puppet::Pops::Types::PIntegerType) keys[0].copy else keys.each_with_index do |x, index| fail(Puppet::Pops::Issues::BAD_COLLECTION_SLICE_TYPE, @semantic.keys[start_index + index], {:actual => x.class}) unless (x.is_a?(Integer) || x == :default) end ranged_integer = Puppet::Pops::Types::PIntegerType.new() from, to = keys ranged_integer.from = from == :default ? nil : from ranged_integer.to = to == :default ? nil : to ranged_integer end end + # A Puppet::Resource represents either just a type (no title), or is a fully qualified type/title. + # + def access_Resource(o, scope, keys) + # To access a Puppet::Resource as if it was a PResourceType, simply infer it, and take the type of + # the parameterized meta type (i.e. Type[Resource[the_resource_type, the_resource_title]]) + t = Puppet::Pops::Types::TypeCalculator.infer(o).type + # must map "undefined title" from resource to nil + t.title = nil if t.title == '' + access(t, scope, *keys) + end + # A Resource can create a new more specific Resource type, and/or an array of resource types # If the given type has title set, it can not be specified further. # @example # Resource[File] # => File # Resource[File, 'foo'] # => File[foo] # Resource[File. 'foo', 'bar'] # => [File[foo], File[bar]] # File['foo', 'bar'] # => [File[foo], File[bar]] # File['foo']['bar'] # => Value of the 'bar' parameter in the File['foo'] resource # Resource[File]['foo', 'bar'] # => [File[Foo], File[bar]] # Resource[File, 'foo', 'bar'] # => [File[foo], File[bar]] # Resource[File, 'foo']['bar'] # => Value of the 'bar' parameter in the File['foo'] resource # def access_PResourceType(o, scope, keys) blamed = keys.size == 0 ? @semantic : @semantic.keys[0] if keys.size == 0 fail(Puppet::Pops::Issues::BAD_TYPE_SLICE_ARITY, blamed, :base_type => Puppet::Pops::Types::TypeCalculator.new().string(o), :min => 1, :max => -1, :actual => 0) end # Must know which concrete resource type to operate on in all cases. # It is not allowed to specify the type in an array arg - e.g. Resource[[File, 'foo']] # type_name is LHS type_name if set, else the first given arg type_name = o.type_name || keys.shift type_name = case type_name when Puppet::Pops::Types::PResourceType type_name.type_name when String type_name.downcase else # blame given left expression if it defined the type, else the first given key expression blame = o.type_name.nil? ? @semantic.keys[0] : @semantic.left_expr fail(Puppet::Pops::Issues::ILLEGAL_RESOURCE_SPECIALIZATION, blame, {:actual => type_name.class}) end # The result is an array if multiple titles are given, or if titles are specified with an array # (possibly multiple arrays, and nested arrays). result_type_array = keys.size > 1 || keys[0].is_a?(Array) keys_orig_size = keys.size keys.flatten! keys.compact! # If given keys that were just a mix of empty/nil with empty array as a result. # As opposed to calling the function the wrong way (without any arguments), (configurable issue), # Return an empty array # if keys.empty? && keys_orig_size > 0 optionally_fail(Puppet::Pops::Issues::EMPTY_RESOURCE_SPECIALIZATION, blamed) return result_type_array ? [] : nil end if !o.title.nil? # lookup resource and return one or more parameter values resource = find_resource(scope, o.type_name, o.title) unless resource fail(Puppet::Pops::Issues::UNKNOWN_RESOURCE, @semantic, {:type_name => o.type_name, :title => o.title}) end result = keys.map do |k| unless is_parameter_of_resource?(scope, resource, k) fail(Puppet::Pops::Issues::UNKNOWN_RESOURCE_PARAMETER, @semantic, {:type_name => o.type_name, :title => o.title, :param_name=>k}) end get_resource_parameter_value(scope, resource, k) end return result_type_array ? result : result.pop end keys = [:no_title] if keys.size < 1 # if there was only a type_name and it was consumed result = keys.each_with_index.map do |t, i| unless t.is_a?(String) || t == :no_title type_to_report = case t when nil, :undef 'Undef' when :default 'Default' else t.class.name end index = keys_orig_size != keys.size ? i+1 : i fail(Puppet::Pops::Issues::BAD_TYPE_SPECIALIZATION, @semantic.keys[index], { :type => o, :message => "Cannot use #{type_to_report} where String is expected" }) end rtype = Puppet::Pops::Types::PResourceType.new() rtype.type_name = type_name rtype.title = (t == :no_title ? nil : t) rtype end # returns single type if request was for a single entity, else an array of types (possibly empty) return result_type_array ? result : result.pop end def access_PHostClassType(o, scope, keys) blamed = keys.size == 0 ? @semantic : @semantic.keys[0] keys_orig_size = keys.size if keys_orig_size == 0 fail(Puppet::Pops::Issues::BAD_TYPE_SLICE_ARITY, blamed, :base_type => Puppet::Pops::Types::TypeCalculator.new().string(o), :min => 1, :max => -1, :actual => 0) end # The result is an array if multiple classnames are given, or if classnames are specified with an array # (possibly multiple arrays, and nested arrays). result_type_array = keys.size > 1 || keys[0].is_a?(Array) keys.flatten! keys.compact! # If given keys that were just a mix of empty/nil with empty array as a result. # As opposed to calling the function the wrong way (without any arguments), (configurable issue), # Return an empty array # if keys.empty? && keys_orig_size > 0 optionally_fail(Puppet::Pops::Issues::EMPTY_RESOURCE_SPECIALIZATION, blamed) return result_type_array ? [] : nil end if o.class_name.nil? # The type argument may be a Resource Type - the Puppet Language allows a reference such as # Class[Foo], and this is interpreted as Class[Resource[Foo]] - which is ok as long as the resource # does not have a title. This should probably be deprecated. # result = keys.each_with_index.map do |c, i| name = if c.is_a?(Puppet::Pops::Types::PResourceType) && !c.type_name.nil? && c.title.nil? # type_name is already downcase. Don't waste time trying to downcase again c.type_name elsif c.is_a?(String) c.downcase else fail(Puppet::Pops::Issues::ILLEGAL_HOSTCLASS_NAME, @semantic.keys[i], {:name => c}) end if name =~ Puppet::Pops::Patterns::NAME ctype = Puppet::Pops::Types::PHostClassType.new() # Remove leading '::' since all references are global, and 3x runtime does the wrong thing ctype.class_name = name.sub(/^::/, '') ctype else fail(Issues::ILLEGAL_NAME, @semantic.keys[i], {:name=>c}) end end else # lookup class resource and return one or more parameter values resource = find_resource(scope, 'class', o.class_name) if resource result = keys.map do |k| if is_parameter_of_resource?(scope, resource, k) get_resource_parameter_value(scope, resource, k) else fail(Puppet::Pops::Issues::UNKNOWN_RESOURCE_PARAMETER, @semantic, {:type_name => 'Class', :title => o.class_name, :param_name=>k}) end end else fail(Puppet::Pops::Issues::UNKNOWN_RESOURCE, @semantic, {:type_name => 'Class', :title => o.class_name}) end end # returns single type as type, else an array of types return result_type_array ? result : result.pop end end diff --git a/lib/puppet/pops/functions/dispatcher.rb b/lib/puppet/pops/evaluator/callable_mismatch_describer.rb similarity index 54% copy from lib/puppet/pops/functions/dispatcher.rb copy to lib/puppet/pops/evaluator/callable_mismatch_describer.rb index a4f912dc4..9d1108f8d 100644 --- a/lib/puppet/pops/functions/dispatcher.rb +++ b/lib/puppet/pops/evaluator/callable_mismatch_describer.rb @@ -1,237 +1,175 @@ -# Evaluate the dispatches defined as {Puppet::Pops::Functions::Dispatch} -# instances to call the appropriate method on the -# {Puppet::Pops::Functions::Function} instance. -# # @api private -class Puppet::Pops::Functions::Dispatcher - attr_reader :dispatchers - -# @api private - def initialize() - @dispatchers = [ ] - end - - # Answers if dispatching has been defined - # @return [Boolean] true if dispatching has been defined - # - # @api private - def empty? - @dispatchers.empty? - end - - # Dispatches the call to the first found signature (entry with matching type). - # - # @param instance [Puppet::Functions::Function] - the function to call - # @param calling_scope [T.B.D::Scope] - the scope of the caller - # @param args [Array] - the given arguments in the form of an Array - # @return [Object] - what the called function produced - # - # @api private - def dispatch(instance, calling_scope, args) - tc = Puppet::Pops::Types::TypeCalculator - actual = tc.infer_set(args) - found = @dispatchers.find { |d| tc.callable?(d.type, actual) } - if found - found.invoke(instance, calling_scope, args) - else - raise ArgumentError, "function '#{instance.class.name}' called with mis-matched arguments\n#{diff_string(instance.class.name, actual)}" - end - end - - # Adds a regular dispatch for one method name - # - # @param type [Puppet::Pops::Types::PArrayType, Puppet::Pops::Types::PTupleType] - type describing signature - # @param method_name [String] - the name of the method that will be called when type matches given arguments - # @param names [Array] - array with names matching the number of parameters specified by type (or empty array) - # - # @api private - def add_dispatch(type, method_name, param_names, block_name, injections, weaving, last_captures) - @dispatchers << Puppet::Pops::Functions::Dispatch.new(type, method_name, param_names, block_name, injections, weaving, last_captures) - end - - # Produces a CallableType for a single signature, and a Variant[] otherwise - # - # @api private - def to_type() - # make a copy to make sure it can be contained by someone else (even if it is not contained here, it - # should be treated as immutable). - # - callables = dispatchers.map { | dispatch | dispatch.type.copy } - - # multiple signatures, produce a Variant type of Callable1-n (must copy them) - # single signature, produce single Callable - callables.size > 1 ? Puppet::Pops::Types::TypeFactory.variant(*callables) : callables.pop - end - - # @api private - def signatures - @dispatchers - end - - private - +module Puppet::Pops::Evaluator::CallableMismatchDescriber # Produces a string with the difference between the given arguments and support signature(s). # + # @param name [String] The name of the callable to describe + # @param args_type [Puppet::Pops::Types::Tuple] The tuple of argument types. + # @param supported_signatures [Array] The available signatures that were available for calling. + # # @api private - def diff_string(name, args_type) + def self.diff_string(name, args_type, supported_signatures) result = [ ] - if @dispatchers.size < 2 - dispatch = @dispatchers[ 0 ] - params_type = dispatch.type.param_types - block_type = dispatch.type.block_type - params_names = dispatch.param_names - result << "expected:\n #{name}(#{signature_string(dispatch)}) - #{arg_count_string(dispatch.type)}" + if supported_signatures.size == 1 + signature = supported_signatures[0] + params_type = signature.type.param_types + block_type = signature.type.block_type + params_names = signature.parameter_names + result << "expected:\n #{name}(#{signature_string(signature)}) - #{arg_count_string(signature.type)}" else result << "expected one of:\n" - result << (@dispatchers.map do |d| - params_type = d.type.param_types - " #{name}(#{signature_string(d)}) - #{arg_count_string(d.type)}" - end.join("\n")) + result << supported_signatures.map do |signature| + params_type = signature.type.param_types + " #{name}(#{signature_string(signature)}) - #{arg_count_string(signature.type)}" + end.join("\n") end + result << "\nactual:\n #{name}(#{arg_types_string(args_type)}) - #{arg_count_string(args_type)}" + result.join('') end + private + # Produces a string for the signature(s) # # @api private - def signature_string(dispatch) # args_type, param_names - param_types = dispatch.type.param_types - block_type = dispatch.type.block_type - param_names = dispatch.param_names + def self.signature_string(signature) + param_types = signature.type.param_types + block_type = signature.type.block_type + param_names = signature.parameter_names from, to = param_types.size_range if from == 0 && to == 0 # No parameters function return '' end required_count = from # there may be more names than there are types, and count needs to be subtracted from the count # to make it correct for the last named element adjust = max(0, param_names.size() -1) last_range = [max(0, (from - adjust)), (to - adjust)] types = case param_types when Puppet::Pops::Types::PTupleType param_types.types when Puppet::Pops::Types::PArrayType [ param_types.element_type ] end tc = Puppet::Pops::Types::TypeCalculator # join type with names (types are always present, names are optional) # separate entries with comma # result = if param_names.empty? types.each_with_index.map {|t, index| tc.string(t) + opt_value_indicator(index, required_count, 0) } else limit = param_names.size result = param_names.each_with_index.map do |name, index| [tc.string(types[index] || types[-1]), name].join(' ') + opt_value_indicator(index, required_count, limit) end end.join(', ') # Add {from, to} for the last type # This works for both Array and Tuple since it describes the allowed count of the "last" type element # for both. It does not show anything when the range is {1,1}. # result += range_string(last_range) # If there is a block, include it with its own optional count {0,1} - case dispatch.type.block_type + case signature.type.block_type when Puppet::Pops::Types::POptionalType result << ', ' unless result == '' - result << "#{tc.string(dispatch.type.block_type.optional_type)} #{dispatch.block_name} {0,1}" + result << "#{tc.string(signature.type.block_type.optional_type)} #{signature.block_name} {0,1}" when Puppet::Pops::Types::PCallableType result << ', ' unless result == '' - result << "#{tc.string(dispatch.type.block_type)} #{dispatch.block_name}" + result << "#{tc.string(signature.type.block_type)} #{signature.block_name}" when NilClass # nothing end result end # Why oh why Ruby do you not have a standard Math.max ? # @api private - def max(a, b) + def self.max(a, b) a >= b ? a : b end # @api private - def opt_value_indicator(index, required_count, limit) + def self.opt_value_indicator(index, required_count, limit) count = index + 1 (count > required_count && count < limit) ? '?' : '' end # @api private - def arg_count_string(args_type) + def self.arg_count_string(args_type) if args_type.is_a?(Puppet::Pops::Types::PCallableType) size_range = args_type.param_types.size_range # regular parameters adjust_range= case args_type.block_type when Puppet::Pops::Types::POptionalType size_range[1] += 1 when Puppet::Pops::Types::PCallableType size_range[0] += 1 size_range[1] += 1 when NilClass # nothing else raise ArgumentError, "Internal Error, only nil, Callable, and Optional[Callable] supported by Callable block type" end else size_range = args_type.size_range end "arg count #{range_string(size_range, false)}" end # @api private - def arg_types_string(args_type) + def self.arg_types_string(args_type) types = case args_type when Puppet::Pops::Types::PTupleType last_range = args_type.repeat_last_range args_type.types when Puppet::Pops::Types::PArrayType last_range = args_type.size_range [ args_type.element_type ] end # stringify generalized versions or it will display Integer[10,10] for "10", String['the content'] etc. # note that type must be copied since generalize is a mutating operation tc = Puppet::Pops::Types::TypeCalculator result = types.map { |t| tc.string(tc.generalize!(t.copy)) }.join(', ') # Add {from, to} for the last type # This works for both Array and Tuple since it describes the allowed count of the "last" type element # for both. It does not show anything when the range is {1,1}. # result += range_string(last_range) result end # Formats a range into a string of the form: `{from, to}` # # The following cases are optimized: # # * from and to are equal => `{from}` # * from and to are both and 1 and squelch_one == true => `''` # * from is 0 and to is 1 => `'?'` # * to is INFINITY => `{from, }` # # @api private - def range_string(size_range, squelch_one = true) + def self.range_string(size_range, squelch_one = true) from, to = size_range if from == to (squelch_one && from == 1) ? '' : "{#{from}}" elsif to == Puppet::Pops::Types::INFINITY "{#{from},}" elsif from == 0 && to == 1 '?' else "{#{from},#{to}}" end end end diff --git a/lib/puppet/pops/evaluator/callable_signature.rb b/lib/puppet/pops/evaluator/callable_signature.rb index e953a4409..ab5fb0a84 100644 --- a/lib/puppet/pops/evaluator/callable_signature.rb +++ b/lib/puppet/pops/evaluator/callable_signature.rb @@ -1,101 +1,100 @@ # CallableSignature # === # A CallableSignature describes how something callable expects to be called. # Different implementation of this class are used for different types of callables. # # @api public # class Puppet::Pops::Evaluator::CallableSignature # Returns the names of the parameters as an array of strings. This does not include the name # of an optional block parameter. # # All implementations are not required to supply names for parameters. They may be used if present, # to provide user feedback in errors etc. but they are not authoritative about the number of # required arguments, optional arguments, etc. # # A derived class must implement this method. # # @return [Array] - an array of names (that may be empty if names are unavailable) # # @api public # def parameter_names raise NotImplementedError.new end # Returns a PCallableType with the type information, required and optional count, and type information about # an optional block. # # A derived class must implement this method. # # @return [Puppet::Pops::Types::PCallableType] # @api public # def type raise NotImplementedError.new end # Returns the expected type for an optional block. The type may be nil, which means that the callable does # not accept a block. If a type is returned it is one of Callable, Optional[Callable], Variant[Callable,...], # or Optional[Variant[Callable, ...]]. The Variant type is used when multiple signatures are acceptable. # The Optional type is used when the block is optional. # # @return [Puppet::Pops::Types::PAbstractType, nil] the expected type of a block given as the last parameter in a call. # # @api public # def block_type type.block_type end # Returns the name of the block parameter if the callable accepts a block. # @return [String] the name of the block parameter # A derived class must implement this method. # @api public # def block_name raise NotImplementedError.new end # Returns a range indicating the optionality of a block. One of [0,0] (does not accept block), [0,1] (optional # block), and [1,1] (block required) # # @return [Array(Integer, Integer)] the range of the block parameter # def block_range type.block_range end # Returns the range of required/optional argument values as an array of [min, max], where an infinite # end is given as INFINITY. To test against infinity, use the infinity? method. # # @return [Array[Integer, Numeric]] - an Array with [min, max] # # @api public # def args_range type.size_range end # Returns true if the last parameter captures the rest of the arguments, with a possible cap, as indicated # by the `args_range` method. # A derived class must implement this method. # # @return [Boolean] true if last parameter captures the rest of the given arguments (up to a possible cap) # @api public # def last_captures_rest? raise NotImplementedError.new end # Returns true if the given x is infinity # @return [Boolean] true, if given value represents infinity # # @api public # def infinity?(x) x == Puppet::Pops::Types::INFINITY end - end diff --git a/lib/puppet/pops/evaluator/closure.rb b/lib/puppet/pops/evaluator/closure.rb index 47c0a54e0..7a6162528 100644 --- a/lib/puppet/pops/evaluator/closure.rb +++ b/lib/puppet/pops/evaluator/closure.rb @@ -1,112 +1,217 @@ # 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. # class Puppet::Pops::Evaluator::Closure < Puppet::Pops::Evaluator::CallableSignature attr_reader :evaluator attr_reader :model attr_reader :enclosing_scope def initialize(evaluator, model, scope) @evaluator = evaluator @model = model @enclosing_scope = scope end # marker method checked with respond_to :puppet_lambda # @api private # @deprecated Use the type system to query if an object is of Callable type, then use its signatures method for info def puppet_lambda() true end # compatible with 3x AST::Lambda # @api public def call(scope, *args) - @evaluator.call(self, args, @enclosing_scope) + 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 closure with argument assignment by name - def call_by_name(scope, args_hash, spill_over = false) - @evaluator.call_by_name(self, args_hash, @enclosing_scope, spill_over) + 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| + scope_hash[p.name] = args_hash[p.name] || @evaluator.evaluate(p.value, @enclosing_scope) + end + + missing = scope_hash.select { |name, value| value.nil? } + if missing.any? + raise ArgumentError, "Too few arguments; no value given for required parameters #{missing.collect(&:first).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 - # incompatible with 3x except that it is an array of the same size - def parameters() - @model.parameters || [] + 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 - - # Returns the number of optional parameters. - # @return [Integer] the number of optional accepted parameters - def optional_parameter_count - @model.parameters.count { |p| !p.value.nil? } + @model.parameters.size end # @api public def parameter_names - @model.parameters.collect {|p| p.name } + @model.parameters.collect(&:name) end # @api public def type - @callable || create_callable_type + @callable ||= create_callable_type end # @api public def last_captures_rest? - # TODO: No support for this yet - false + 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 private + def combine_values_with_parameters(args) + variable_bindings = {} + + 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 = @evaluator.evaluate(default_expression, @enclosing_scope) + 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(Puppet::Pops::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 + if value.size == 1 && (given_argument.nil? || given_argument == :undef) && default_expression + value = @evaluator.evaluate(default_expression, scope) + # and ensure it is an array + value = [value] unless value.is_a?(Array) + end + else + value = given_argument + end + end + + variable_bindings[parameter.name] = value + end + + variable_bindings + end + def create_callable_type() - t = Puppet::Pops::Types::PCallableType.new() - tuple_t = Puppet::Pops::Types::PTupleType.new() - # since closure lambdas are currently untyped, each parameter becomes Optional[Object] - parameter_names.each do |name| - # TODO: Change when Closure supports typed parameters - tuple_t.addTypes(Puppet::Pops::Types::TypeFactory.optional_object()) + types = [] + range = [0, 0] + in_optional_parameters = false + parameters.each do |param| + type = if param.type_expr + @evaluator.evaluate(param.type_expr, @enclosing_scope) + else + Puppet::Pops::Types::TypeFactory.object() + end + + if param.captures_rest && type.is_a?(Puppet::Pops::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?(Puppet::Pops::Types::PArrayType) + param_range = ANY_NUMBER_RANGE + elsif param.value + param_range = OPTIONAL_SINGLE_RANGE + else + param_range = REQUIRED_SINGLE_RANGE + end + + types << type + + if param_range[0] == 0 + in_optional_parameters = true + elsif param_range[0] != 0 && in_optional_parameters + @evaluator.fail(Puppet::Pops::Issues::REQUIRED_PARAMETER_AFTER_OPTIONAL, param, { :param_name => param.name }) + end + + range[0] += param_range[0] + range[1] += param_range[1] end - # TODO: A Lambda can not currently declare varargs - to = parameter_count - from = to - optional_parameter_count - if from != to - size_t = Puppet::Pops::Types::PIntegerType.new() - size_t.from = from - size_t.to = to - tuple_t.size_type = size_t + if range[1] == Puppet::Pops::Types::INFINITY + range[1] = :default end - t.param_types = tuple_t - # TODO: A Lambda can not currently declare that it accepts a lambda, except as an explicit parameter - # being a Callable - t + + Puppet::Pops::Types::TypeFactory.callable(*(types + range)) end # Produces information about parameters compatible with a 4x Function (which can have multiple signatures) def signatures [ self ] end + ANY_NUMBER_RANGE = [0, Puppet::Pops::Types::INFINITY] + OPTIONAL_SINGLE_RANGE = [0, 1] + REQUIRED_SINGLE_RANGE = [1, 1] end diff --git a/lib/puppet/pops/evaluator/epp_evaluator.rb b/lib/puppet/pops/evaluator/epp_evaluator.rb index 31e59aea7..57a95bc71 100644 --- a/lib/puppet/pops/evaluator/epp_evaluator.rb +++ b/lib/puppet/pops/evaluator/epp_evaluator.rb @@ -1,87 +1,88 @@ # Handler of Epp call/evaluation from the epp and inline_epp functions # class Puppet::Pops::Evaluator::EppEvaluator def self.inline_epp(scope, epp_source, template_args = nil) - unless epp_source.is_a? String + unless epp_source.is_a?(String) raise ArgumentError, "inline_epp(): the first argument must be a String with the epp source text, got a #{epp_source.class}" end # Parse and validate the source parser = Puppet::Pops::Parser::EvaluatingParser::EvaluatingEppParser.new begin - result = parser.parse_string(epp_source, 'inlined-epp-text') + result = parser.parse_string(epp_source, 'inlined-epp-text') rescue Puppet::ParseError => e raise ArgumentError, "inline_epp(): Invalid EPP: #{e.message}" end # Evaluate (and check template_args) evaluate(parser, 'inline_epp', scope, false, result, template_args) end def self.epp(scope, file, env_name, template_args = nil) - unless file.is_a? String + unless file.is_a?(String) raise ArgumentError, "epp(): the first argument must be a String with the filename, got a #{file.class}" end file = file + ".epp" unless file =~ /\.epp$/ scope.debug "Retrieving epp template #{file}" template_file = Puppet::Parser::Files.find_template(file, env_name) unless template_file raise Puppet::ParseError, "Could not find template '#{file}'" end # Parse and validate the source parser = Puppet::Pops::Parser::EvaluatingParser::EvaluatingEppParser.new begin - result = parser.parse_file(template_file) + result = parser.parse_file(template_file) rescue Puppet::ParseError => e raise ArgumentError, "epp(): Invalid EPP: #{e.message}" end # Evaluate (and check template_args) evaluate(parser, 'epp', scope, true, result, template_args) end private def self.evaluate(parser, func_name, scope, use_global_scope_only, parse_result, template_args) template_args, template_args_set = handle_template_args(func_name, template_args) body = parse_result.body unless body.is_a?(Puppet::Pops::Model::LambdaExpression) raise ArgumentError, "#{func_name}(): the parser did not produce a LambdaExpression, got '#{body.class}'" end unless body.body.is_a?(Puppet::Pops::Model::EppExpression) raise ArgumentError, "#{func_name}(): the parser did not produce an EppExpression, got '#{body.body.class}'" end unless parse_result.definitions.empty? raise ArgumentError, "#{func_name}(): The EPP template contains illegal expressions (definitions)" end - see_scope = body.body.see_scope - if see_scope && !template_args_set - # no epp params and no arguments were given => inline_epp logic sees all local variables, epp all global - closure_scope = use_global_scope_only ? scope.find_global_scope : scope - spill_over = false - else - # no epp params or user provided arguments in a hash, epp logic only sees global + what was given + parameters_specified = body.body.parameters_specified + if parameters_specified || template_args_set + # no epp params or user provided arguments in a hash, epp() logic + # only sees global + what was given closure_scope = scope.find_global_scope - # given spill over if there are no params (e.g. replace closure scope by a new scope with the given args) - spill_over = see_scope + enforce_parameters = parameters_specified + else + # no epp params and no arguments were given => inline_epp() logic + # sees all local variables, epp() all global + closure_scope = use_global_scope_only ? scope.find_global_scope : scope + enforce_parameters = true end - evaluated_result = parser.closure(body, closure_scope).call_by_name(scope, template_args, spill_over) + evaluated_result = parser.closure(body, closure_scope).call_by_name(scope, template_args, enforce_parameters) evaluated_result end def self.handle_template_args(func_name, template_args) if template_args.nil? [{}, false] else unless template_args.is_a?(Hash) raise ArgumentError, "#{func_name}(): the template_args must be a Hash, got a #{template_args.class}" end [template_args, true] end end -end \ No newline at end of file +end diff --git a/lib/puppet/pops/evaluator/evaluator_impl.rb b/lib/puppet/pops/evaluator/evaluator_impl.rb index b16dc31c6..112b0380e 100644 --- a/lib/puppet/pops/evaluator/evaluator_impl.rb +++ b/lib/puppet/pops/evaluator/evaluator_impl.rb @@ -1,1138 +1,1028 @@ require 'rgen/ecore/ecore' require 'puppet/pops/evaluator/compare_operator' require 'puppet/pops/evaluator/relationship_operator' require 'puppet/pops/evaluator/access_operator' require 'puppet/pops/evaluator/closure' require 'puppet/pops/evaluator/external_syntax_support' # 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. # class Puppet::Pops::Evaluator::EvaluatorImpl include Puppet::Pops::Utils # Provides access to the Puppet 3.x runtime (scope, etc.) # This separation has been made to make it easier to later migrate the evaluator to an improved runtime. # include Puppet::Pops::Evaluator::Runtime3Support include Puppet::Pops::Evaluator::ExternalSyntaxSupport # 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. # INFINITY = 1.0 / 0.0 # Reference to Issues name space makes it easier to refer to issues # (Issues are shared with the validator). # Issues = Puppet::Pops::Issues 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() # Initialize the runtime module Puppet::Pops::Evaluator::Runtime3Support.instance_method(:initialize).bind(self).call() end # @api private def type_calculator @@type_calculator end # Polymorphic evaluate - calls eval_TYPE # # ## Polymorphic evaluate # Polymorphic evaluate calls a method on the format eval_TYPE where classname is the last # part of the class of the given _target_. A search is performed starting with the actual class, continuing # with each of the _target_ class's super classes until a matching method is found. # # # Description # Evaluates the given _target_ object in the given scope, optionally passing a block which will be # called with the result of the evaluation. # # @overload evaluate(target, scope, {|result| block}) # @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 # 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 fail(e.issue, e.semantic || target, e.options, e) rescue StandardError => e if e.is_a? Puppet::ParseError # ParseError's are supposed to be fully configured with location information raise e end fail(Issues::RUNTIME_ERROR, target, {:detail => e.message}, e) end end # Polymorphic assign - calls assign_TYPE # # ## Polymorphic assign # Polymorphic assign calls a method on the format assign_TYPE where TYPE is the last # part of the class of the given _target_. A search is performed starting with the actual class, continuing # with each of the _target_ class's super classes until a matching method is found. # # # Description # 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 # def assign(target, value, o, scope) @@assign_visitor.visit_this_3(self, target, value, o, scope) end def lvalue(o, scope) @@lvalue_visitor.visit_this_1(self, o, scope) end def string(o, scope) @@string_visitor.visit_this_1(self, o, scope) end - # Call a closure matching arguments by name - Can only be called with a Closure (for now), may be refactored later - # to also handle other types of calls (function calls are also handled by CallNamedFunction and CallMethod, they - # could create similar objects to Closure, wait until other types of defines are instantiated - they may behave - # as special cases of calls - i.e. 'new'). + # Evaluate a BlockExpression in a new scope with variables bound to the + # given values. # - # Call by name supports a "spill_over" mode where extra arguments in the given args_hash are introduced - # as variables in the resulting scope. - # - # @raise ArgumentError, if there are to many or too few arguments - # @raise ArgumentError, if given closure is not a Puppet::Pops::Evaluator::Closure - # - def call_by_name(closure, args_hash, scope, spill_over = false) - raise ArgumentError, "Can only call a Lambda" unless closure.is_a?(Puppet::Pops::Evaluator::Closure) - pblock = closure.model - parameters = pblock.parameters || [] - - if !spill_over && 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| - scope_hash[p.name] = args_hash[p.name] || evaluate(p.value, scope) - end - missing = scope_hash.reduce([]) {|memo, entry| memo << entry[0] if entry[1].nil?; memo } - unless missing.empty? - optional = parameters.count { |p| !p.value.nil? } - raise ArgumentError, "Too few arguments; no value given for required parameters #{missing.join(" ,")}" - end - if spill_over - # all args from given hash should be used, nil entries replaced by default values should win - scope_hash = args_hash.merge(scope_hash) - 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"). - - # Ensure variable exists with nil value if error occurs. - # Some ruby implementations does not like creating variable on return - result = nil - begin - scope_memo = get_scope_nesting_level(scope) - # 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(scope_hash, scope) - result = evaluate(pblock.body, scope) - ensure - set_scope_nesting_level(scope, scope_memo) - end - result - end - - # Call a closure - Can only be called with a Closure (for now), may be refactored later - # to also handle other types of calls (function calls are also handled by CallNamedFunction and CallMethod, they - # could create similar objects to Closure, wait until other types of defines are instantiated - they may behave - # as special cases of calls - i.e. 'new') - # - # @raise ArgumentError, if there are to many or too few arguments - # @raise ArgumentError, if given closure is not a Puppet::Pops::Evaluator::Closure - # - def call(closure, args, scope) - raise ArgumentError, "Can only call a Lambda" unless closure.is_a?(Puppet::Pops::Evaluator::Closure) - pblock = closure.model - parameters = pblock.parameters || [] - - raise ArgumentError, "Too many arguments: #{args.size} for #{parameters.size}" unless args.size <= parameters.size - - # calculate missing arguments - args_diff = parameters.size - args.size - missing = parameters.slice(args.size, args_diff).select {|p| p.value.nil? } - unless missing.empty? - optional = parameters.count { |p| !p.value.nil? } - raise ArgumentError, "Too few arguments; #{args.size} for #{optional > 0 ? ' min ' : ''}#{parameters.size - optional}" - end - # associate values with parameters (pad missing with :missing) - merged = parameters.zip(args.fill(:missing, args.size, args_diff)) - - evaluated = merged.collect do |m| - # m can be one of - # m = [Parameter{name => "name", value => nil], "given"] - # | [Parameter{name => "name", value => Expression}, "given"] - # | [Parameter{name => "name", value => Expression}, :missing] - # - # "given" may be nil or :undef which means that this is the value to use, - # not a default expression. - # - given_argument = m[1] - argument_name = m[0].name - default_expression = m[0].value - - # Use default value if a value was not given (NOTE: An :undef overrides - just a nil overrides default in ruby). - value = - if given_argument == :missing - # nothing was given, use default (it is guaranteed to exist) - evaluate(default_expression, scope) - else - # use the given value - given_argument - end - [argument_name, value] - 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"). - - # Ensure variable exists with nil value if error occurs. - # Some ruby implementations does not like creating variable on return - result = nil - begin - scope_memo = get_scope_nesting_level(scope) - # 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(Hash[evaluated], scope) - result = evaluate(pblock.body, scope) - ensure - set_scope_nesting_level(scope, scope_memo) + # @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 + 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 - result end protected def lvalue_VariableExpression(o, scope) # evaluate the name evaluate(o.expr, scope) end # Catches all illegal lvalues # def lvalue_Object(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] # 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 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) # def assign_Object(name, value, o, scope) fail(Issues::ILLEGAL_ASSIGNMENT, o) end def eval_Factory(o, scope) evaluate(o.current, scope) end # Evaluates any object not evaluated to something else to itself. def eval_Object o, scope o end # Allows nil to be used as a Nop. # Evaluates to nil # TODO: What is the difference between literal undef, nil, and nop? # def eval_NilClass(o, scope) nil end # Evaluates Nop to nil. # TODO: or is this the same as :undef # TODO: is this even needed as a separate instruction when there is a literal undef? def eval_Nop(o, scope) nil end # Captures all LiteralValues not handled elsewhere. # def eval_LiteralValue(o, scope) o.value end # Reserved Words fail to evaluate # def eval_ReservedWord(o, scope) fail(Puppet::Pops::Issues::RESERVED_WORD, o, {:word => o.word}) end def eval_LiteralDefault(o, scope) :default end def eval_LiteralUndef(o, scope) :undef # TODO: or just use nil for this? end # A QualifiedReference (i.e. a capitalized qualified name such as Foo, or Foo::Bar) evaluates to a PType # def eval_QualifiedReference(o, scope) @@type_parser.interpret(o) end def eval_NotExpression(o, scope) ! is_true?(evaluate(o.expr, scope)) end def eval_UnaryMinusExpression(o, scope) - coerce_numeric(evaluate(o.expr, scope), o, scope) end def eval_UnfoldExpression(o, scope) candidate = evaluate(o.expr, scope) case candidate when Array candidate when Hash candidate.to_a else # turns anything else into an array (so result can be unfolded) [candidate] end end # Abstract evaluation, returns array [left, right] with the evaluated result of left_expr and # right_expr # @return > array with result of evaluating left and right expressions # def eval_BinaryExpression o, scope [ evaluate(o.left_expr, scope), evaluate(o.right_expr, scope) ] end # Evaluates assignment with operators =, +=, -= and # # @example Puppet DSL # $a = 1 # $a += 1 # $a -= 1 # def eval_AssignmentExpression(o, scope) name = lvalue(o.left_expr, scope) value = evaluate(o.right_expr, scope) case o.operator when :'=' # regular assignment assign(name, value, o, scope) when :'+=' # if value does not exist and strict is on, looking it up fails, else it is nil or :undef existing_value = get_variable_value(name, o, scope) begin if existing_value.nil? || existing_value == :undef assign(name, value, o, scope) else # Delegate to calculate function to deal with check of LHS, and perform ´+´ as arithmetic or concatenation the # same way as ArithmeticExpression performs `+`. assign(name, calculate(existing_value, value, :'+', o.left_expr, o.right_expr, scope), o, scope) end rescue ArgumentError => e fail(Issues::APPEND_FAILED, o, {:message => e.message}) end when :'-=' # If an attempt is made to delete values from something that does not exists, the value is :undef (it is guaranteed to not # include any values the user wants deleted anyway :-) # # if value does not exist and strict is on, looking it up fails, else it is nil or :undef existing_value = get_variable_value(name, o, scope) begin if existing_value.nil? || existing_value == :undef assign(name, :undef, o, scope) else # Delegate to delete function to deal with check of LHS, and perform deletion assign(name, delete(get_variable_value(name, o, scope), value), o, scope) end rescue ArgumentError => e fail(Issues::APPEND_FAILED, o, {:message => e.message}, e) end else fail(Issues::UNSUPPORTED_OPERATOR, o, {:operator => o.operator}) end value end ARITHMETIC_OPERATORS = [:'+', :'-', :'*', :'/', :'%', :'<<', :'>>'] COLLECTION_OPERATORS = [:'+', :'-', :'<<'] # Handles binary expression where lhs and rhs are array/hash or numeric and operator is +, - , *, % / << >> # def eval_ArithmeticExpression(o, scope) left, right = eval_BinaryExpression(o, 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 # Handles binary expression where lhs and rhs are array/hash or numeric and operator is +, - , *, % / << >> # 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 def eval_EppExpression(o, scope) scope["@epp"] = [] evaluate(o.body, scope) result = scope["@epp"].join('') result end def eval_RenderStringExpression(o, scope) scope["@epp"] << o.value.dup nil end def eval_RenderExpression(o, scope) scope["@epp"] << string(evaluate(o.expr, scope), scope) nil end # Evaluates Puppet DSL ->, ~>, <-, and <~ 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 # Evaluates x[key, key, ...] # 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 # Evaluates <, <=, >, >=, and == # def eval_ComparisonExpression o, scope left, right = eval_BinaryExpression o, scope begin # Left is a type if left.is_a?(Puppet::Pops::Types::PAbstractType) case o.operator when :'==' @@type_calculator.equals(left,right) when :'!=' !@@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 :'==' @@compare_operator.equals(left,right) when :'!=' ! @@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 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. # def eval_MatchExpression o, scope left, pattern = eval_BinaryExpression o, scope # matches RHS types as instance of for all types except a parameterized Regexp[R] if pattern.is_a?(Puppet::Pops::Types::PAbstractType) # 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, o, scope) # creates ephemeral # convert match result to Boolean true, or false o.operator == :'=~' ? !!matched : !matched end # Evaluates Puppet DSL `in` expression # def eval_InExpression o, scope left, right = eval_BinaryExpression o, scope @@compare_operator.include?(right, left) end # @example # $a and $b # b is only evaluated if a is true # def eval_AndExpression o, scope is_true?(evaluate(o.left_expr, scope)) ? is_true?(evaluate(o.right_expr, scope)) : false end # @example # a or b # b is only evaluated if a is false # def eval_OrExpression o, scope is_true?(evaluate(o.left_expr, scope)) ? true : is_true?(evaluate(o.right_expr, scope)) end # Evaluates each entry of the literal list and creates a new Array # Supports unfolding of entries # @return [Array] with the evaluated content # def eval_LiteralList o, scope unfold([], o.values, scope) end # Evaluates each entry of the literal hash and creates a new Hash. # @return [Hash] with the evaluated content # def eval_LiteralHash o, scope h = Hash.new o.entries.each {|entry| h[ evaluate(entry.key, scope)]= evaluate(entry.value, scope)} h end # Evaluates all statements and produces the last evaluated value # def eval_BlockExpression o, scope r = nil o.statements.each {|s| r = evaluate(s, scope)} r 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. # 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) result = nil the_default = nil if o.options.find do |co| # the first case option that matches if co.values.find do |c| case c when Puppet::Pops::Model::LiteralDefault the_default = co.then_expr is_match?(test, evaluate(c, scope), c, scope) 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, scope) } else is_match?(test, evaluate(c, scope), c, 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 transforming it into a 3x AST::Collection and then evaluating that. # This is done because of the complex API between compiler, indirector, backends, and difference between # collecting virtual resources and exported resources. # def eval_CollectExpression o, scope # The Collect Expression and its contained query expressions are implemented in such a way in # 3x that it is almost impossible to do anything about them (the AST objects are lazily evaluated, # and the built structure consists of both higher order functions and arrays with query expressions # that are either used as a predicate filter, or given to an indirection terminus (such as the Puppet DB # resource terminus). Unfortunately, the 3x implementation has many inconsistencies that the implementation # below carries forward. # collect_3x = Puppet::Pops::Model::AstTransformer.new().transform(o) collected = collect_3x.evaluate(scope) # the 3x returns an instance of Parser::Collector (but it is only registered with the compiler at this # point and does not contain any valuable information (like the result) # Dilemma: If this object is returned, it is a first class value in the Puppet Language and we # need to be able to perform operations on it. We can forbid it from leaking by making CollectExpression # a non R-value. This makes it possible for the evaluator logic to make use of the Collector. collected end def eval_ParenthesizedExpression(o, scope) evaluate(o.expr, scope) 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. # def eval_Definition(o, scope) nil end def eval_Program(o, scope) evaluate(o.body, scope) end # Produces Array[PObjectType], an array of resource references # def eval_ResourceExpression(o, scope) exported = o.exported virtual = o.virtual type_name = evaluate(o.type_name, scope) o.bodies.map do |body| titles = [evaluate(body.title, scope)].flatten evaluated_parameters = body.operations.map {|op| evaluate(op, scope) } create_resources(o, scope, virtual, exported, type_name, titles, evaluated_parameters) end.flatten.compact end 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 # Produces 3x array of parameters def eval_AttributeOperation(o, scope) create_resource_parameter(o, scope, o.attribute_name, evaluate(o.value_expr, scope), o.operator) end # Sets default parameter values for a type, produces the type # def eval_ResourceDefaultsExpression(o, scope) type_name = o.type_ref.value # a QualifiedName's string value evaluated_parameters = o.operations.map {|op| evaluate(op, scope) } create_resource_defaults(o, scope, type_name, evaluated_parameters) # Produce the type evaluate(o.type_ref, scope) end # Evaluates function call by name. # 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 evaluated_arguments = unfold([], o.arguments, scope) # wrap lambda in a callable block if it is present evaluated_arguments << Puppet::Pops::Evaluator::Closure.new(self, o.lambda, scope) if o.lambda call_function(name, evaluated_arguments, o, scope) end # Evaluation of CallMethodExpression handles a NamedAccessExpression functor (receiver.function_name) # 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 = evaluate(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 evaluated_arguments = unfold([receiver], o.arguments || [], scope) # wrap lambda in a callable block if it is present evaluated_arguments << Puppet::Pops::Evaluator::Closure.new(self, o.lambda, scope) if o.lambda call_function(name, evaluated_arguments, o, scope) end # @example # $x ? { 10 => true, 20 => false, default => 0 } # 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) the_default = nil selected = o.selectors.find do |s| me = 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, scope) } else is_match?(test, evaluate(me, scope), me, 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 def eval_SubLocatedExpression o, scope evaluate(o.expr, scope) end # Evaluates Puppet DSL Heredoc def eval_HeredocExpression o, scope result = evaluate(o.text_expr, scope) assert_external_syntax(scope, result, o.syntax, o.text_expr) result end # Evaluates Puppet DSL `if` def eval_IfExpression o, scope with_guarded_scope(scope) do if is_true?(evaluate(o.test, scope)) evaluate(o.then_expr, scope) else evaluate(o.else_expr, scope) end end end # Evaluates Puppet DSL `unless` def eval_UnlessExpression o, scope with_guarded_scope(scope) do unless is_true?(evaluate(o.test, scope)) evaluate(o.then_expr, scope) else evaluate(o.else_expr, scope) end 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. # 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. case name when String when Numeric else fail(Issues::ILLEGAL_VARIABLE_EXPRESSION, o.expr) end # TODO: Check for valid variable name (Task for validator) # TODO: semantics of undefined variable in scope, this just returns what scope does == value or nil get_variable_value(name, o, scope) end # Evaluates double quoted strings that may contain interpolation # def eval_ConcatenatedString o, scope o.segments.collect {|expr| string(evaluate(expr, scope), scope)}.join 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 # def eval_TextExpression o, scope if o.expr.is_a?(Puppet::Pops::Model::QualifiedName) # TODO: formalize, when scope returns nil, vs error string(get_variable_value(o.expr.value, o, scope), scope) else string(evaluate(o.expr, scope), scope) end end def string_Object(o, scope) o.to_s end def string_Symbol(o, scope) case o when :undef '' else o.to_s end end def string_Array(o, scope) ['[', o.map {|e| string(e, scope)}.join(', '), ']'].join() end def string_Hash(o, scope) ['{', o.map {|k,v| string(k, scope) + " => " + string(v, scope)}.join(', '), '}'].join() end def string_Regexp(o, scope) ['/', o.source, '/'].join() end def string_PAbstractType(o, scope) @@type_calculator.string(o) 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. # 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 # 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 # 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. # def is_match? left, right, o, scope if right.is_a?(Regexp) return false unless left.is_a? String matched = right.match(left) set_match_data(matched, o, scope) # creates or clears ephemeral !!matched # convert to boolean elsif right.is_a?(Puppet::Pops::Types::PAbstractType) # right is a type and left is not - check if left is an instance of the given type # (The reverse is not terribly meaningful - computing which of the case options that first produces # an instance of a given type). # @@type_calculator.instance?(right, left) else # Handle equality the same way as the language '==' operator (case insensitive etc.) @@compare_operator.equals(left,right) end end def with_guarded_scope(scope) scope_memo = get_scope_nesting_level(scope) begin yield ensure set_scope_nesting_level(scope, scope_memo) end end # Maps the expression in the given array to their product except for UnfoldExpressions which are first unfolded. # The result is added to the given result Array. # @param result [Array] Where to add the result (may contain information to add to) # @param array [Array[Puppet::Pops::Model::Expression] the expressions to map # @param scope [Puppet::Parser::Scope] the scope to evaluate in # @return [Array] the given result array with content added from the operation # def unfold(result, array, scope) array.each do |x| if x.is_a?(Puppet::Pops::Model::UnfoldExpression) result.concat(evaluate(x, scope)) else result << evaluate(x, scope) end end result end private :unfold end diff --git a/lib/puppet/pops/evaluator/relationship_operator.rb b/lib/puppet/pops/evaluator/relationship_operator.rb index 7ffd20c8c..407ab79b0 100644 --- a/lib/puppet/pops/evaluator/relationship_operator.rb +++ b/lib/puppet/pops/evaluator/relationship_operator.rb @@ -1,156 +1,158 @@ # The RelationshipOperator implements the semantics of the -> <- ~> <~ operators creating relationships or notification # relationships between the left and right hand side's references to resources. # # This is separate class since a second level of evaluation is required that transforms string in left or right hand # to type references. The task of "making a relationship" is delegated to the "runtime support" class that is included. # This is done to separate the concerns of the new evaluator from the 3x runtime; messy logic goes into the runtime support # module. Later when more is cleaned up this can be simplified further. # class Puppet::Pops::Evaluator::RelationshipOperator # Provides access to the Puppet 3.x runtime (scope, etc.) # This separation has been made to make it easier to later migrate the evaluator to an improved runtime. # include Puppet::Pops::Evaluator::Runtime3Support Issues = Puppet::Pops::Issues class IllegalRelationshipOperandError < RuntimeError attr_reader :operand def initialize operand @operand = operand end end class NotCatalogTypeError < RuntimeError attr_reader :type def initialize type @type = type end end def initialize @type_transformer_visitor = Puppet::Pops::Visitor.new(self, "transform", 1, 1) @type_calculator = Puppet::Pops::Types::TypeCalculator.new() @type_parser = Puppet::Pops::Types::TypeParser.new() - @catalog_type = Puppet::Pops::Types::TypeFactory.catalog_entry() + tf = Puppet::Pops::Types::TypeFactory + @catalog_type = tf.variant(tf.catalog_entry, tf.type_type(tf.catalog_entry)) end def transform(o, scope) @type_transformer_visitor.visit_this_1(self, o, scope) end # Catch all non transformable objects # @api private def transform_Object(o, scope) raise IllegalRelationshipOperandError.new(o) end # A string must be a type reference in string format # @api private def transform_String(o, scope) assert_catalog_type(@type_parser.parse(o), scope) end # A qualified name is short hand for a class with this name # @api private def transform_QualifiedName(o, scope) Puppet::Pops::Types::TypeFactory.host_class(o.value) end # Types are what they are, just check the type # @api private def transform_PAbstractType(o, scope) assert_catalog_type(o, scope) end # This transforms a 3x Collector (the result of evaluating a 3x AST::Collection). # It is passed through verbatim since it is evaluated late by the compiler. At the point # where the relationship is evaluated, it is simply recorded with the compiler for later evaluation. # If one of the sides of the relationship is a Collector it is evaluated before the actual # relationship is formed. (All of this happens at a later point in time. # def transform_Collector(o, scope) o end # Array content needs to be transformed def transform_Array(o, scope) o.map{|x| transform(x, scope) } end # Asserts (and returns) the type if it is a PCatalogEntryType # (A PCatalogEntryType is the base class of PHostClassType, and PResourceType). # def assert_catalog_type(o, scope) unless @type_calculator.assignable?(@catalog_type, o) + require 'debugger'; debugger raise NotCatalogTypeError.new(o) end # TODO must check if this is an abstract PResourceType (i.e. without a type_name) - which should fail ? # e.g. File -> File (and other similar constructs) - maybe the catalog protects against this since references # may be to future objects... o end RELATIONSHIP_OPERATORS = [:'->', :'~>', :'<-', :'<~'] REVERSE_OPERATORS = [:'<-', :'<~'] RELATION_TYPE = { :'->' => :relationship, :'<-' => :relationship, :'~>' => :subscription, :'<~' => :subscription } # Evaluate a relationship. # TODO: The error reporting is not fine grained since evaluation has already taken place # There is no references to the original source expressions at this point, only the overall # relationship expression. (e.g.. the expression may be ['string', func_call(), etc.] -> func_call()) # To implement this, the general evaluator needs to be able to track each evaluation result and associate # it with a corresponding expression. This structure should then be passed to the relationship operator. # def evaluate (left_right_evaluated, relationship_expression, scope) # assert operator (should have been validated, but this logic makes assumptions which would # screw things up royally). Better safe than sorry. unless RELATIONSHIP_OPERATORS.include?(relationship_expression.operator) fail(Issues::UNSUPPORTED_OPERATOR, relationship_expression, {:operator => relationship_expression.operator}) end begin # Turn each side into an array of types (this also asserts their type) # (note wrap in array first if value is not already an array) # # TODO: Later when objects are Puppet Runtime Objects and know their type, it will be more efficient to check/infer # the type first since a chained operation then does not have to visit each element again. This is not meaningful now # since inference needs to visit each object each time, and this is what the transformation does anyway). # # real is [left, right], and both the left and right may be a single value or an array. In each case all content # should be flattened, and then transformed to a type. left or right may also be a value that is transformed # into an array, and thus the resulting left and right must be flattened individually # Once flattened, the operands should be sets (to remove duplicate entries) # real = left_right_evaluated.collect {|x| [x].flatten.collect {|x| transform(x, scope) }} real[0].flatten! real[1].flatten! real[0].uniq! real[1].uniq! # reverse order if operator is Right to Left source, target = reverse_operator?(relationship_expression) ? real.reverse : real # Add the relationships to the catalog source.each {|s| target.each {|t| add_relationship(s, t, RELATION_TYPE[relationship_expression.operator], scope) }} # Produce the transformed source RHS (if this is a chain, this does not need to be done again) real.slice(1) rescue NotCatalogTypeError => e fail(Issues::ILLEGAL_RELATIONSHIP_OPERAND_TYPE, relationship_expression, {:type => @type_calculator.string(e.type)}) rescue IllegalRelationshipOperandError => e fail(Issues::ILLEGAL_RELATIONSHIP_OPERAND_TYPE, relationship_expression, {:operand => e.operand}) end end def reverse_operator?(o) REVERSE_OPERATORS.include?(o.operator) end end diff --git a/lib/puppet/pops/evaluator/runtime3_support.rb b/lib/puppet/pops/evaluator/runtime3_support.rb index 5c20bfa98..740fe95af 100644 --- a/lib/puppet/pops/evaluator/runtime3_support.rb +++ b/lib/puppet/pops/evaluator/runtime3_support.rb @@ -1,537 +1,546 @@ # A module with bindings between the new evaluator and the 3x runtime. # The intention is to separate all calls into scope, compiler, resource, etc. in this module # to make it easier to later refactor the evaluator for better implementations of the 3x classes. # # @api private module Puppet::Pops::Evaluator::Runtime3Support # Fails the evaluation of _semantic_ with a given issue. # # @param issue [Puppet::Pops::Issue] the issue to report # @param semantic [Puppet::Pops::ModelPopsObject] the object for which evaluation failed in some way. Used to determine origin. # @param options [Hash] hash of optional named data elements for the given issue # @return [!] this method does not return # @raise [Puppet::ParseError] an evaluation error initialized from the arguments (TODO: Change to EvaluationError?) # def fail(issue, semantic, options={}, except=nil) optionally_fail(issue, semantic, options, except) # an error should have been raised since fail always fails raise ArgumentError, "Internal Error: Configuration of runtime error handling wrong: should have raised exception" end # Optionally (based on severity) Fails the evaluation of _semantic_ with a given issue # If the given issue is configured to be of severity < :error it is only reported, and the function returns. # # @param issue [Puppet::Pops::Issue] the issue to report # @param semantic [Puppet::Pops::ModelPopsObject] the object for which evaluation failed in some way. Used to determine origin. # @param options [Hash] hash of optional named data elements for the given issue # @return [!] this method does not return # @raise [Puppet::ParseError] an evaluation error initialized from the arguments (TODO: Change to EvaluationError?) # def optionally_fail(issue, semantic, options={}, except=nil) if except.nil? # Want a stacktrace, and it must be passed as an exception begin raise EvaluationError.new() rescue EvaluationError => e except = e end end diagnostic_producer.accept(issue, semantic, options, except) end # Binds the given variable name to the given value in the given scope. # The reference object `o` is intended to be used for origin information - the 3x scope implementation # only makes use of location when there is an error. This is now handled by other mechanisms; first a check # is made if a variable exists and an error is raised if attempting to change an immutable value. Errors # in name, numeric variable assignment etc. have also been validated prior to this call. In the event the # scope.setvar still raises an error, the general exception handling for evaluation of the assignment # expression knows about its location. Because of this, there is no need to extract the location for each # setting (extraction is somewhat expensive since 3x requires line instead of offset). # def set_variable(name, value, o, scope) # Scope also checks this but requires that location information are passed as options. # Those are expensive to calculate and a test is instead made here to enable failing with better information. # The error is not specific enough to allow catching it - need to check the actual message text. # TODO: Improve the messy implementation in Scope. # if scope.bound?(name) if Puppet::Parser::Scope::RESERVED_VARIABLE_NAMES.include?(name) fail(Puppet::Pops::Issues::ILLEGAL_RESERVED_ASSIGNMENT, o, {:name => name} ) else fail(Puppet::Pops::Issues::ILLEGAL_REASSIGNMENT, o, {:name => name} ) end end scope.setvar(name, value) end # Returns the value of the variable (nil is returned if variable has no value, or if variable does not exist) # def get_variable_value(name, o, scope) # Puppet 3x stores all variables as strings (then converts them back to numeric with a regexp... to see if it is a match variable) # Not ideal, scope should support numeric lookup directly instead. # TODO: consider fixing scope catch(:undefined_variable) { return scope.lookupvar(name.to_s) } # It is always ok to reference numeric variables even if they are not assigned. They are always undef # if not set by a match expression. # unless name =~ Puppet::Pops::Patterns::NUMERIC_VAR_NAME fail(Puppet::Pops::Issues::UNKNOWN_VARIABLE, o, {:name => name}) end end # Returns true if the variable of the given name is set in the given most nested scope. True is returned even if # variable is bound to nil. # def variable_bound?(name, scope) scope.bound?(name.to_s) end # Returns true if the variable is bound to a value or nil, in the scope or it's parent scopes. # def variable_exists?(name, scope) scope.exist?(name.to_s) end def set_match_data(match_data, o, scope) # See set_variable for rationale for not passing file and line to ephemeral_from. # NOTE: The 3x scope adds one ephemeral(match) to its internal stack per match that succeeds ! It never # clears anything. Thus a context that performs many matches will get very deep (there simply is no way to # clear the match variables without rolling back the ephemeral stack.) # This implementation does not attempt to fix this, it behaves the same bad way. unless match_data.nil? scope.ephemeral_from(match_data) end end # Creates a local scope with vairalbes set from a hash of variable name to value # def create_local_scope_from(hash, scope) # two dummy values are needed since the scope tries to give an error message (can not happen in this # case - it is just wrong, the error should be reported by the caller who knows in more detail where it # is in the source. # raise ArgumentError, "Internal error - attempt to create a local scope without a hash" unless hash.is_a?(Hash) scope.ephemeral_from(hash) end # Creates a nested match scope def create_match_scope_from(scope) # Create a transparent match scope (for future matches) scope.new_match_scope(nil) end def get_scope_nesting_level(scope) scope.ephemeral_level end def set_scope_nesting_level(scope, level) # Yup, 3x uses this method to reset the level, it also supports passing :all to destroy all # ephemeral/local scopes - which is a sure way to create havoc. # scope.unset_ephemeral_var(level) end # Adds a relationship between the given `source` and `target` of the given `relationship_type` # @param source [Puppet:Pops::Types::PCatalogEntryType] the source end of the relationship (from) # @param target [Puppet:Pops::Types::PCatalogEntryType] the target end of the relationship (to) # @param relationship_type [:relationship, :subscription] the type of the relationship # def add_relationship(source, target, relationship_type, scope) # The 3x way is to record a Puppet::Parser::Relationship that is evaluated at the end of the compilation. # This means it is not possible to detect any duplicates at this point (and signal where an attempt is made to # add a duplicate. There is also no location information to signal the original place in the logic. The user will have # to go fish. # The 3.x implementation is based on Strings :-o, so the source and target must be transformed. The resolution is # done by Catalog#resource(type, title). To do that, it creates a Puppet::Resource since it is responsible for # translating the name/type/title and create index-keys used by the catalog. The Puppet::Resource has bizarre parsing of # the type and title (scan for [] that is interpreted as type/title (but it gets it wrong). # Moreover if the type is "" or "component", the type is Class, and if the type is :main, it is :main, all other cases # undergo capitalization of name-segments (foo::bar becomes Foo::Bar). (This was earlier done in the reverse by the parser). # Further, the title undergoes the same munging !!! # # That bug infested nest of messy logic needs serious Exorcism! # # Unfortunately it is not easy to simply call more intelligent methods at a lower level as the compiler evaluates the recorded # Relationship object at a much later point, and it is responsible for invoking all the messy logic. # # TODO: Revisit the below logic when there is a sane implementation of the catalog, compiler and resource. For now # concentrate on transforming the type references to what is expected by the wacky logic. # # HOWEVER, the Compiler only records the Relationships, and the only method it calls is @relationships.each{|x| x.evaluate(catalog) } # Which means a smarter Relationship class could do this right. Instead of obtaining the resource from the catalog using # the borked resource(type, title) which creates a resource for the purpose of looking it up, it needs to instead # scan the catalog's resources # # GAAAH, it is even worse! # It starts in the parser, which parses "File['foo']" into an AST::ResourceReference with type = File, and title = foo # This AST is evaluated by looking up the type/title in the scope - causing it to be loaded if it exists, and if not, the given # type name/title is used. It does not search for resource instances, only classes and types. It returns symbolic information # [type, [title, title]]. From this, instances of Puppet::Resource are created and returned. These only have type/title information # filled out. One or an array of resources are returned. # This set of evaluated (empty reference) Resource instances are then passed to the relationship operator. It creates a # Puppet::Parser::Relationship giving it a source and a target that are (empty reference) Resource instances. These are then remembered # until the relationship is evaluated by the compiler (at the end). When evaluation takes place, the (empty reference) Resource instances # are converted to String (!?! WTF) on the simple format "#{type}[#{title}]", and the catalog is told to find a resource, by giving # it this string. If it cannot find the resource it fails, else the before/notify parameter is appended with the target. # The search for the resource begin with (you guessed it) again creating an (empty reference) resource from type and title (WTF?!?!). # The catalog now uses the reference resource to compute a key [r.type, r.title.to_s] and also gets a uniqueness key from the # resource (This is only a reference type created from title and type). If it cannot find it with the first key, it uses the # uniqueness key to lookup. # # This is probably done to allow a resource type to munge/translate the title in some way (but it is quite unclear from the long # and convoluted path of evaluation. # In order to do this in a way that is similar to 3.x two resources are created to be used as keys. # # And if that is not enough, a source/target may be a Collector (a baked query that will be evaluated by the # compiler - it is simply passed through here for processing by the compiler at the right time). # if source.is_a?(Puppet::Parser::Collector) # use verbatim - behavior defined by 3x source_resource = source else # transform into the wonderful String representation in 3x type, title = catalog_type_to_split_type_title(source) source_resource = Puppet::Resource.new(type, title) end if target.is_a?(Puppet::Parser::Collector) # use verbatim - behavior defined by 3x target_resource = target else # transform into the wonderful String representation in 3x type, title = catalog_type_to_split_type_title(target) target_resource = Puppet::Resource.new(type, title) end # Add the relationship to the compiler for later evaluation. scope.compiler.add_relationship(Puppet::Parser::Relationship.new(source_resource, target_resource, relationship_type)) end # Coerce value `v` to numeric or fails. # The given value `v` is coerced to Numeric, and if that fails the operation # calls {#fail}. # @param v [Object] the value to convert # @param o [Object] originating instruction # @param scope [Object] the (runtime specific) scope where evaluation of o takes place # @return [Numeric] value `v` converted to Numeric. # def coerce_numeric(v, o, scope) unless n = Puppet::Pops::Utils.to_n(v) fail(Puppet::Pops::Issues::NOT_NUMERIC, o, {:value => v}) end n end + # Horrible cheat while waiting for iterative functions to be 4x + FUNCTIONS_4x = { 'map' => true, 'each'=>true, 'filter' => true, 'reduce' => true, 'slice' => true } def call_function(name, args, o, scope) # Call via 4x API if it is available, and the function exists # if loaders = Puppet.lookup(:loaders) {nil} # find the loader that loaded the code, or use the private_environment_loader (sees env + all modules) adapter = Puppet::Pops::Utils.find_adapter(o, Puppet::Pops::Adapters::LoaderAdapter) loader = adapter.nil? ? loaders.private_environment_loader : adapter.loader if loader && func = loader.load(:function, name) return func.call(scope, *args) end end fail(Puppet::Pops::Issues::UNKNOWN_FUNCTION, o, {:name => name}) unless Puppet::Parser::Functions.function(name) # TODO: if Puppet[:biff] == true, then 3x functions should be called via loaders above # Arguments must be mapped since functions are unaware of the new and magical creatures in 4x. + + # Do not map the iterative functions, they are capable of dealing with 4x API, and they do + # call out to lambdas, and thus, given arguments needs to be preserved (instead of transforming to + # '' when undefined). TODO: The iterative functions should be refactored to use the new function API + # directly, when this has been done, this special filtering out can be removed + # NOTE: Passing an empty string last converts :undef to empty string - mapped_args = args.map {|a| convert(a, scope, '') } + mapped_args = FUNCTIONS_4x[name] ? args : args.map {|a| convert(a, scope, '') } result = scope.send("function_#{name}", mapped_args) # Prevent non r-value functions from leaking their result (they are not written to care about this) Puppet::Parser::Functions.rvalue?(name) ? result : nil end # The o is used for source reference def create_resource_parameter(o, scope, name, value, operator) file, line = extract_file_line(o) Puppet::Parser::Resource::Param.new( :name => name, :value => convert(value, scope, :undef), # converted to 3x since 4x supports additional objects / types :source => scope.source, :line => line, :file => file, :add => operator == :'+>' ) end def create_resources(o, scope, virtual, exported, type_name, resource_titles, evaluated_parameters) # TODO: Unknown resource causes creation of Resource to fail with ArgumentError, should give # a proper Issue. Now the result is "Error while evaluating a Resource Statement" with the message # from the raised exception. (It may be good enough). # resolve in scope. fully_qualified_type, resource_titles = scope.resolve_type_and_titles(type_name, resource_titles) # Not 100% accurate as this is the resource expression location and each title is processed separately # The titles are however the result of evaluation and they have no location at this point (an array # of positions for the source expressions are required for this to work). # TODO: Revisit and possible improve the accuracy. # file, line = extract_file_line(o) # Build a resource for each title resource_titles.map do |resource_title| resource = Puppet::Parser::Resource.new( fully_qualified_type, resource_title, :parameters => evaluated_parameters, :file => file, :line => line, :exported => exported, :virtual => virtual, # WTF is this? Which source is this? The file? The name of the context ? :source => scope.source, :scope => scope, :strict => true ) if resource.resource_type.is_a? Puppet::Resource::Type resource.resource_type.instantiate_resource(scope, resource) end scope.compiler.add_resource(scope, resource) scope.compiler.evaluate_classes([resource_title], scope, false, true) if fully_qualified_type == 'class' # Turn the resource into a PType (a reference to a resource type) # weed out nil's resource_to_ptype(resource) end end # Defines default parameters for a type with the given name. # def create_resource_defaults(o, scope, type_name, evaluated_parameters) # Note that name must be capitalized in this 3x call # The 3x impl creates a Resource instance with a bogus title and then asks the created resource # for the type of the name. # Note, locations are available per parameter. # scope.define_settings(capitalize_qualified_name(type_name), evaluated_parameters) end # Capitalizes each segment of a qualified name # def capitalize_qualified_name(name) name.split(/::/).map(&:capitalize).join('::') end # Creates resource overrides for all resource type objects in evaluated_resources. The same set of # evaluated parameters are applied to all. # def create_resource_overrides(o, scope, evaluated_resources, evaluated_parameters) # Not 100% accurate as this is the resource expression location and each title is processed separately # The titles are however the result of evaluation and they have no location at this point (an array # of positions for the source expressions are required for this to work. # TODO: Revisit and possible improve the accuracy. # file, line = extract_file_line(o) evaluated_resources.each do |r| resource = Puppet::Parser::Resource.new( r.type_name, r.title, :parameters => evaluated_parameters, :file => file, :line => line, # WTF is this? Which source is this? The file? The name of the context ? :source => scope.source, :scope => scope ) scope.compiler.add_override(resource) end end # Finds a resource given a type and a title. # def find_resource(scope, type_name, title) scope.compiler.findresource(type_name, title) end # Returns the value of a resource's parameter by first looking up the parameter in the resource # and then in the defaults for the resource. Since the resource exists (it must in order to look up its # parameters, any overrides have already been applied). Defaults are not applied to a resource until it # has been finished (which typically has not taken place when this is evaluated; hence the dual lookup). # def get_resource_parameter_value(scope, resource, parameter_name) # This gets the parameter value, or nil (for both valid parameters and parameters that do not exist). val = resource[parameter_name] if val.nil? && defaults = scope.lookupdefaults(resource.type) # NOTE: 3x resource keeps defaults as hash using symbol for name as key to Parameter which (again) holds # name and value. # NOTE: meta parameters that are unset ends up here, and there are no defaults for those encoded # in the defaults, they may receive hardcoded defaults later (e.g. 'tag'). param = defaults[parameter_name.to_sym] # Some parameters (meta parameters like 'tag') does not return a param from which the value can be obtained # at all times. Instead, they return a nil param until a value has been set. val = param.nil? ? nil : param.value end val end # Returns true, if the given name is the name of a resource parameter. # def is_parameter_of_resource?(scope, resource, name) resource.valid_parameter?(name) end def resource_to_ptype(resource) nil if resource.nil? type_calculator.infer(resource) end # This is the same type of "truth" as used in the current Puppet DSL. # def is_true? o # Is the value true? This allows us to control the definition of truth # in one place. case o when :undef false else !!o end end # Utility method for TrueClass || FalseClass # @param x [Object] the object to test if it is instance of TrueClass or FalseClass def is_boolean? x x.is_a?(TrueClass) || x.is_a?(FalseClass) end def initialize @@convert_visitor ||= Puppet::Pops::Visitor.new(self, "convert", 2, 2) end # Converts 4x supported values to 3x values. This is required because # resources and other objects do not know about the new type system, and does not support # regular expressions. Unfortunately this has to be done for array and hash as well. # A complication is that catalog types needs to be resolved against the scope. # def convert(o, scope, undef_value) @@convert_visitor.visit_this_2(self, o, scope, undef_value) end def convert_NilClass(o, scope, undef_value) undef_value end def convert_Object(o, scope, undef_value) o end def convert_Array(o, scope, undef_value) o.map {|x| convert(x, scope, undef_value) } end def convert_Hash(o, scope, undef_value) result = {} o.each {|k,v| result[convert(k, scope, undef_value)] = convert(v, scope, undef_value) } result end def convert_Regexp(o, scope, undef_value) # Puppet 3x cannot handle parameter values that are reqular expressions. Turn into regexp string in # source form o.inspect end def convert_Symbol(o, scope, undef_value) case o when :undef undef_value # 3x wants :undef as empty string in function else o # :default, and all others are verbatim since they are new in future evaluator end end def convert_PAbstractType(o, scope, undef_value) o end def convert_PCatalogEntryType(o, scope, undef_value) # Since 4x does not support dynamic scoping, all names are absolute and can be # used as is (with some check/transformation/mangling between absolute/relative form # due to Puppet::Resource's idiosyncratic behavior where some references must be # absolute and others cannot be. # Thus there is no need to call scope.resolve_type_and_titles to do dynamic lookup. Puppet::Resource.new(*catalog_type_to_split_type_title(o)) end private # Produces an array with [type, title] from a PCatalogEntryType # This method is used to produce the arguments for creation of reference resource instances # (used when 3x is operating on a resource). # Ensures that resources are *not* absolute. # def catalog_type_to_split_type_title(catalog_type) - case catalog_type + split_type = catalog_type.is_a?(Puppet::Pops::Types::PType) ? catalog_type.type : catalog_type + case split_type when Puppet::Pops::Types::PHostClassType - class_name = catalog_type.class_name + class_name = split_type.class_name ['class', class_name.nil? ? nil : class_name.sub(/^::/, '')] when Puppet::Pops::Types::PResourceType - type_name = catalog_type.type_name - title = catalog_type.title + type_name = split_type.type_name + title = split_type.title if type_name =~ /^(::)?[Cc]lass/ ['class', title.nil? ? nil : title.sub(/^::/, '')] else # Ensure that title is '' if nil # Resources with absolute name always results in error because tagging does not support leading :: [type_name.nil? ? nil : type_name.sub(/^::/, ''), title.nil? ? '' : title] end else - raise ArgumentError, "Cannot split the type #{catalog_type.class}, it is neither a PHostClassType, nor a PResourceClass." + raise ArgumentError, "Cannot split the type #{catalog_type.class}, it represents neither a PHostClassType, nor a PResourceType." end end def extract_file_line(o) source_pos = Puppet::Pops::Utils.find_closest_positioned(o) return [nil, -1] unless source_pos [source_pos.locator.file, source_pos.line] end def find_closest_positioned(o) return nil if o.nil? || o.is_a?(Puppet::Pops::Model::Program) o.offset.nil? ? find_closest_positioned(o.eContainer) : Puppet::Pops::Adapters::SourcePosAdapter.adapt(o) end # Creates a diagnostic producer def diagnostic_producer Puppet::Pops::Validation::DiagnosticProducer.new( ExceptionRaisingAcceptor.new(), # Raises exception on all issues SeverityProducer.new(), # All issues are errors Puppet::Pops::Model::ModelLabelProvider.new()) end # Configure the severity of failures class SeverityProducer < Puppet::Pops::Validation::SeverityProducer Issues = Puppet::Pops::Issues def initialize super p = self # Issues triggering warning only if --debug is on if Puppet[:debug] p[Issues::EMPTY_RESOURCE_SPECIALIZATION] = :warning else p[Issues::EMPTY_RESOURCE_SPECIALIZATION] = :ignore end end end # An acceptor of diagnostics that immediately raises an exception. class ExceptionRaisingAcceptor < Puppet::Pops::Validation::Acceptor def accept(diagnostic) super Puppet::Pops::IssueReporter.assert_and_report(self, {:message => "Evaluation Error:", :emit_warnings => true }) if errors? raise ArgumentError, "Internal Error: Configuration of runtime error handling wrong: should have raised exception" end end end class EvaluationError < StandardError end end diff --git a/lib/puppet/pops/functions/dispatcher.rb b/lib/puppet/pops/functions/dispatcher.rb index a4f912dc4..f15ad373b 100644 --- a/lib/puppet/pops/functions/dispatcher.rb +++ b/lib/puppet/pops/functions/dispatcher.rb @@ -1,237 +1,70 @@ # Evaluate the dispatches defined as {Puppet::Pops::Functions::Dispatch} # instances to call the appropriate method on the # {Puppet::Pops::Functions::Function} instance. # # @api private class Puppet::Pops::Functions::Dispatcher attr_reader :dispatchers -# @api private + # @api private def initialize() @dispatchers = [ ] end # Answers if dispatching has been defined # @return [Boolean] true if dispatching has been defined # # @api private def empty? @dispatchers.empty? end # Dispatches the call to the first found signature (entry with matching type). # # @param instance [Puppet::Functions::Function] - the function to call # @param calling_scope [T.B.D::Scope] - the scope of the caller # @param args [Array] - the given arguments in the form of an Array # @return [Object] - what the called function produced # # @api private def dispatch(instance, calling_scope, args) tc = Puppet::Pops::Types::TypeCalculator actual = tc.infer_set(args) found = @dispatchers.find { |d| tc.callable?(d.type, actual) } if found found.invoke(instance, calling_scope, args) else - raise ArgumentError, "function '#{instance.class.name}' called with mis-matched arguments\n#{diff_string(instance.class.name, actual)}" + raise ArgumentError, "function '#{instance.class.name}' called with mis-matched arguments\n#{Puppet::Pops::Evaluator::CallableMismatchDescriber.diff_string(instance.class.name, actual, @dispatchers)}" end end # Adds a regular dispatch for one method name # # @param type [Puppet::Pops::Types::PArrayType, Puppet::Pops::Types::PTupleType] - type describing signature # @param method_name [String] - the name of the method that will be called when type matches given arguments # @param names [Array] - array with names matching the number of parameters specified by type (or empty array) # # @api private def add_dispatch(type, method_name, param_names, block_name, injections, weaving, last_captures) @dispatchers << Puppet::Pops::Functions::Dispatch.new(type, method_name, param_names, block_name, injections, weaving, last_captures) end # Produces a CallableType for a single signature, and a Variant[] otherwise # # @api private def to_type() # make a copy to make sure it can be contained by someone else (even if it is not contained here, it # should be treated as immutable). # callables = dispatchers.map { | dispatch | dispatch.type.copy } # multiple signatures, produce a Variant type of Callable1-n (must copy them) # single signature, produce single Callable callables.size > 1 ? Puppet::Pops::Types::TypeFactory.variant(*callables) : callables.pop end # @api private def signatures @dispatchers end - - private - - # Produces a string with the difference between the given arguments and support signature(s). - # - # @api private - def diff_string(name, args_type) - result = [ ] - if @dispatchers.size < 2 - dispatch = @dispatchers[ 0 ] - params_type = dispatch.type.param_types - block_type = dispatch.type.block_type - params_names = dispatch.param_names - result << "expected:\n #{name}(#{signature_string(dispatch)}) - #{arg_count_string(dispatch.type)}" - else - result << "expected one of:\n" - result << (@dispatchers.map do |d| - params_type = d.type.param_types - " #{name}(#{signature_string(d)}) - #{arg_count_string(d.type)}" - end.join("\n")) - end - result << "\nactual:\n #{name}(#{arg_types_string(args_type)}) - #{arg_count_string(args_type)}" - result.join('') - end - - # Produces a string for the signature(s) - # - # @api private - def signature_string(dispatch) # args_type, param_names - param_types = dispatch.type.param_types - block_type = dispatch.type.block_type - param_names = dispatch.param_names - - from, to = param_types.size_range - if from == 0 && to == 0 - # No parameters function - return '' - end - - required_count = from - # there may be more names than there are types, and count needs to be subtracted from the count - # to make it correct for the last named element - adjust = max(0, param_names.size() -1) - last_range = [max(0, (from - adjust)), (to - adjust)] - - types = - case param_types - when Puppet::Pops::Types::PTupleType - param_types.types - when Puppet::Pops::Types::PArrayType - [ param_types.element_type ] - end - tc = Puppet::Pops::Types::TypeCalculator - - # join type with names (types are always present, names are optional) - # separate entries with comma - # - result = - if param_names.empty? - types.each_with_index.map {|t, index| tc.string(t) + opt_value_indicator(index, required_count, 0) } - else - limit = param_names.size - result = param_names.each_with_index.map do |name, index| - [tc.string(types[index] || types[-1]), name].join(' ') + opt_value_indicator(index, required_count, limit) - end - end.join(', ') - - # Add {from, to} for the last type - # This works for both Array and Tuple since it describes the allowed count of the "last" type element - # for both. It does not show anything when the range is {1,1}. - # - result += range_string(last_range) - - # If there is a block, include it with its own optional count {0,1} - case dispatch.type.block_type - when Puppet::Pops::Types::POptionalType - result << ', ' unless result == '' - result << "#{tc.string(dispatch.type.block_type.optional_type)} #{dispatch.block_name} {0,1}" - when Puppet::Pops::Types::PCallableType - result << ', ' unless result == '' - result << "#{tc.string(dispatch.type.block_type)} #{dispatch.block_name}" - when NilClass - # nothing - end - result - end - - # Why oh why Ruby do you not have a standard Math.max ? - # @api private - def max(a, b) - a >= b ? a : b - end - - # @api private - def opt_value_indicator(index, required_count, limit) - count = index + 1 - (count > required_count && count < limit) ? '?' : '' - end - - # @api private - def arg_count_string(args_type) - if args_type.is_a?(Puppet::Pops::Types::PCallableType) - size_range = args_type.param_types.size_range # regular parameters - adjust_range= - case args_type.block_type - when Puppet::Pops::Types::POptionalType - size_range[1] += 1 - when Puppet::Pops::Types::PCallableType - size_range[0] += 1 - size_range[1] += 1 - when NilClass - # nothing - else - raise ArgumentError, "Internal Error, only nil, Callable, and Optional[Callable] supported by Callable block type" - end - else - size_range = args_type.size_range - end - "arg count #{range_string(size_range, false)}" - end - - # @api private - def arg_types_string(args_type) - types = - case args_type - when Puppet::Pops::Types::PTupleType - last_range = args_type.repeat_last_range - args_type.types - when Puppet::Pops::Types::PArrayType - last_range = args_type.size_range - [ args_type.element_type ] - end - # stringify generalized versions or it will display Integer[10,10] for "10", String['the content'] etc. - # note that type must be copied since generalize is a mutating operation - tc = Puppet::Pops::Types::TypeCalculator - result = types.map { |t| tc.string(tc.generalize!(t.copy)) }.join(', ') - - # Add {from, to} for the last type - # This works for both Array and Tuple since it describes the allowed count of the "last" type element - # for both. It does not show anything when the range is {1,1}. - # - result += range_string(last_range) - result - end - - # Formats a range into a string of the form: `{from, to}` - # - # The following cases are optimized: - # - # * from and to are equal => `{from}` - # * from and to are both and 1 and squelch_one == true => `''` - # * from is 0 and to is 1 => `'?'` - # * to is INFINITY => `{from, }` - # - # @api private - def range_string(size_range, squelch_one = true) - from, to = size_range - if from == to - (squelch_one && from == 1) ? '' : "{#{from}}" - elsif to == Puppet::Pops::Types::INFINITY - "{#{from},}" - elsif from == 0 && to == 1 - '?' - else - "{#{from},#{to}}" - end - end end diff --git a/lib/puppet/pops/issues.rb b/lib/puppet/pops/issues.rb index 90b34686f..8b598b6df 100644 --- a/lib/puppet/pops/issues.rb +++ b/lib/puppet/pops/issues.rb @@ -1,489 +1,501 @@ # Defines classes to deal with issues, and message formatting and defines constants with Issues. # @api public # module Puppet::Pops::Issues # Describes an issue, and can produce a message for an occurrence of the issue. # class Issue # The issue code # @return [Symbol] attr_reader :issue_code # A block producing the message # @return [Proc] attr_reader :message_block # Names that must be bound in an occurrence of the issue to be able to produce a message. # These are the names in addition to requirements stipulated by the Issue formatter contract; i.e. :label`, # and `:semantic`. # attr_reader :arg_names # If this issue can have its severity lowered to :warning, :deprecation, or :ignored attr_writer :demotable # Configures the Issue with required arguments (bound by occurrence), and a block producing a message. def initialize issue_code, *args, &block @issue_code = issue_code @message_block = block @arg_names = args @demotable = true end # Returns true if it is allowed to demote this issue def demotable? @demotable end # Formats a message for an occurrence of the issue with argument bindings passed in a hash. # The hash must contain a LabelProvider bound to the key `label` and the semantic model element # bound to the key `semantic`. All required arguments as specified by `arg_names` must be bound # in the given `hash`. # @api public # def format(hash ={}) # Create a Message Data where all hash keys become methods for convenient interpolation # in issue text. msgdata = MessageData.new(*arg_names) begin # Evaluate the message block in the msg data's binding msgdata.format(hash, &message_block) rescue StandardError => e Puppet::Pops::Issues::MessageData raise RuntimeError, "Error while reporting issue: #{issue_code}. #{e.message}", caller end end end # Provides a binding of arguments passed to Issue.format to method names available # in the issue's message producing block. # @api private # class MessageData def initialize *argnames singleton = class << self; self end argnames.each do |name| singleton.send(:define_method, name) do @data[name] end end end def format(hash, &block) @data = hash instance_eval &block end # Returns the label provider given as a key in the hash passed to #format. # If given an argument, calls #label on the label provider (caller would otherwise have to # call label.label(it) # def label(it = nil) raise "Label provider key :label must be set to produce the text of the message!" unless @data[:label] it.nil? ? @data[:label] : @data[:label].label(it) end # Returns the label provider given as a key in the hash passed to #format. # def semantic raise "Label provider key :semantic must be set to produce the text of the message!" unless @data[:semantic] @data[:semantic] end end # Defines an issue with the given `issue_code`, additional required parameters, and a block producing a message. # The block is evaluated in the context of a MessageData which provides convenient access to all required arguments # via accessor methods. In addition to accessors for specified arguments, these are also available: # * `label` - a `LabelProvider` that provides human understandable names for model elements and production of article (a/an/the). # * `semantic` - the model element for which the issue is reported # # @param issue_code [Symbol] the issue code for the issue used as an identifier, should be the same as the constant # the issue is bound to. # @param args [Symbol] required arguments that must be passed when formatting the message, may be empty # @param block [Proc] a block producing the message string, evaluated in a MessageData scope. The produced string # should not end with a period as additional information may be appended. # # @see MessageData # @api public # def self.issue (issue_code, *args, &block) Issue.new(issue_code, *args, &block) end # Creates a non demotable issue. # @see Issue.issue # def self.hard_issue(issue_code, *args, &block) result = Issue.new(issue_code, *args, &block) result.demotable = false result end # @comment Here follows definitions of issues. The intent is to provide a list from which yardoc can be generated # containing more detailed information / explanation of the issue. # These issues are set as constants, but it is unfortunately not possible for the created object to easily know which # name it is bound to. Instead the constant has to be repeated. (Alternatively, it could be done by instead calling # #const_set on the module, but the extra work required to get yardoc output vs. the extra effort to repeat the name # twice makes it not worth it (if doable at all, since there is no tag to artificially construct a constant, and # the parse tag does not produce any result for a constant assignment). # This is allowed (3.1) and has not yet been deprecated. # @todo configuration # NAME_WITH_HYPHEN = issue :NAME_WITH_HYPHEN, :name do "#{label.a_an_uc(semantic)} may not have a name containing a hyphen. The name '#{name}' is not legal" end # When a variable name contains a hyphen and these are illegal. # It is possible to control if a hyphen is legal in a name or not using the setting TODO # @todo describe the setting # @api public # @todo configuration if this is error or warning # VAR_WITH_HYPHEN = issue :VAR_WITH_HYPHEN, :name do "A variable name may not contain a hyphen. The name '#{name}' is not legal" end # A class, definition, or node may only appear at top level or inside other classes # @todo Is this really true for nodes? Can they be inside classes? Isn't that too late? # @api public # NOT_TOP_LEVEL = hard_issue :NOT_TOP_LEVEL do "Classes, definitions, and nodes may only appear at toplevel or inside other classes" end CROSS_SCOPE_ASSIGNMENT = hard_issue :CROSS_SCOPE_ASSIGNMENT, :name do "Illegal attempt to assign to '#{name}'. Cannot assign to variables in other namespaces" end # Assignment can only be made to certain types of left hand expressions such as variables. ILLEGAL_ASSIGNMENT = hard_issue :ILLEGAL_ASSIGNMENT do "Illegal attempt to assign to '#{label.a_an(semantic)}'. Not an assignable reference" end # Variables are immutable, cannot reassign in the same assignment scope ILLEGAL_REASSIGNMENT = hard_issue :ILLEGAL_REASSIGNMENT, :name do "Cannot reassign variable #{name}" end ILLEGAL_RESERVED_ASSIGNMENT = hard_issue :ILLEGAL_RESERVED_ASSIGNMENT, :name do "Attempt to assign to a reserved variable name: '#{name}'" end # Assignment cannot be made to numeric match result variables ILLEGAL_NUMERIC_ASSIGNMENT = issue :ILLEGAL_NUMERIC_ASSIGNMENT, :varname do "Illegal attempt to assign to the numeric match result variable '$#{varname}'. Numeric variables are not assignable" end APPEND_FAILED = issue :APPEND_FAILED, :message do "Append assignment += failed with error: #{message}" end DELETE_FAILED = issue :DELETE_FAILED, :message do "'Delete' assignment -= failed with error: #{message}" end # parameters cannot have numeric names, clashes with match result variables ILLEGAL_NUMERIC_PARAMETER = issue :ILLEGAL_NUMERIC_PARAMETER, :name do "The numeric parameter name '$#{varname}' cannot be used (clashes with numeric match result variables)" end # In certain versions of Puppet it may be allowed to assign to a not already assigned key # in an array or a hash. This is an optional validation that may be turned on to prevent accidental # mutation. # ILLEGAL_INDEXED_ASSIGNMENT = issue :ILLEGAL_INDEXED_ASSIGNMENT do "Illegal attempt to assign via [index/key]. Not an assignable reference" end # When indexed assignment ($x[]=) is allowed, the leftmost expression must be # a variable expression. # ILLEGAL_ASSIGNMENT_VIA_INDEX = hard_issue :ILLEGAL_ASSIGNMENT_VIA_INDEX do "Illegal attempt to assign to #{label.a_an(semantic)} via [index/key]. Not an assignable reference" end # For unsupported operators (e.g. -= in puppet 3). # UNSUPPORTED_OPERATOR = hard_issue :UNSUPPORTED_OPERATOR, :operator do "The operator '#{operator}' in #{label.a_an(semantic)} is not supported." end # For non applicable operators (e.g. << on Hash). # OPERATOR_NOT_APPLICABLE = hard_issue :OPERATOR_NOT_APPLICABLE, :operator, :left_value do "Operator '#{operator}' is not applicable to #{label.a_an(left_value)}." end COMPARISON_NOT_POSSIBLE = hard_issue :COMPARISON_NOT_POSSIBLE, :operator, :left_value, :right_value, :detail do "Comparison of: #{label(left_value)} #{operator} #{label(right_value)}, is not possible. Caused by '#{detail}'." end MATCH_NOT_REGEXP = hard_issue :MATCH_NOT_REGEXP, :detail do "Can not convert right match operand to a regular expression. Caused by '#{detail}'." end MATCH_NOT_STRING = hard_issue :MATCH_NOT_STRING, :left_value do "Left match operand must result in a String value. Got #{label.a_an(left_value)}." end # Some expressions/statements may not produce a value (known as right-value, or rvalue). # This may vary between puppet versions. # NOT_RVALUE = issue :NOT_RVALUE do "Invalid use of expression. #{label.a_an_uc(semantic)} does not produce a value" end # Appending to attributes is only allowed in certain types of resource expressions. # ILLEGAL_ATTRIBUTE_APPEND = hard_issue :ILLEGAL_ATTRIBUTE_APPEND, :name, :parent do "Illegal +> operation on attribute #{name}. This operator can not be used in #{label.a_an(parent)}" end ILLEGAL_NAME = hard_issue :ILLEGAL_NAME, :name do "Illegal name. The given name #{name} does not conform to the naming rule /^((::)?[a-z_]\w*)(::[a-z]\w*)*$/" end ILLEGAL_VAR_NAME = hard_issue :ILLEGAL_VAR_NAME, :name do "Illegal variable name, The given name '#{name}' does not conform to the naming rule /^((::)?[a-z]\w*)*((::)?[a-z_]\w*)$/" end ILLEGAL_NUMERIC_VAR_NAME = hard_issue :ILLEGAL_NUMERIC_VAR_NAME, :name do "Illegal numeric variable name, The given name '#{name}' must be a decimal value if it starts with a digit 0-9" end # In case a model is constructed programmatically, it must create valid type references. # ILLEGAL_CLASSREF = hard_issue :ILLEGAL_CLASSREF, :name do "Illegal type reference. The given name '#{name}' does not conform to the naming rule" end # This is a runtime issue - storeconfigs must be on in order to collect exported. This issue should be # set to :ignore when just checking syntax. # @todo should be a :warning by default # RT_NO_STORECONFIGS = issue :RT_NO_STORECONFIGS do "You cannot collect exported resources without storeconfigs being set; the collection will be ignored" end # This is a runtime issue - storeconfigs must be on in order to export a resource. This issue should be # set to :ignore when just checking syntax. # @todo should be a :warning by default # RT_NO_STORECONFIGS_EXPORT = issue :RT_NO_STORECONFIGS_EXPORT do "You cannot collect exported resources without storeconfigs being set; the export is ignored" end # A hostname may only contain letters, digits, '_', '-', and '.'. # ILLEGAL_HOSTNAME_CHARS = hard_issue :ILLEGAL_HOSTNAME_CHARS, :hostname do "The hostname '#{hostname}' contains illegal characters (only letters, digits, '_', '-', and '.' are allowed)" end # A hostname may only contain letters, digits, '_', '-', and '.'. # ILLEGAL_HOSTNAME_INTERPOLATION = hard_issue :ILLEGAL_HOSTNAME_INTERPOLATION do "An interpolated expression is not allowed in a hostname of a node" end # Issues when an expression is used where it is not legal. # E.g. an arithmetic expression where a hostname is expected. # ILLEGAL_EXPRESSION = hard_issue :ILLEGAL_EXPRESSION, :feature, :container do "Illegal expression. #{label.a_an_uc(semantic)} is unacceptable as #{feature} in #{label.a_an(container)}" end # Issues when an expression is used where it is not legal. # E.g. an arithmetic expression where a hostname is expected. # ILLEGAL_VARIABLE_EXPRESSION = hard_issue :ILLEGAL_VARIABLE_EXPRESSION do "Illegal variable expression. #{label.a_an_uc(semantic)} did not produce a variable name (String or Numeric)." end # Issues when an expression is used illegaly in a query. # query only supports == and !=, and not <, > etc. # ILLEGAL_QUERY_EXPRESSION = hard_issue :ILLEGAL_QUERY_EXPRESSION do "Illegal query expression. #{label.a_an_uc(semantic)} cannot be used in a query" end # If an attempt is made to make a resource default virtual or exported. # NOT_VIRTUALIZEABLE = hard_issue :NOT_VIRTUALIZEABLE do "Resource Defaults are not virtualizable" end # When an attempt is made to use multiple keys (to produce a range in Ruby - e.g. $arr[2,-1]). # This is not supported in 3x, but it allowed in 4x. # UNSUPPORTED_RANGE = issue :UNSUPPORTED_RANGE, :count do "Attempt to use unsupported range in #{label.a_an(semantic)}, #{count} values given for max 1" end DEPRECATED_NAME_AS_TYPE = issue :DEPRECATED_NAME_AS_TYPE, :name do "Resource references should now be capitalized. The given '#{name}' does not have the correct form" end ILLEGAL_RELATIONSHIP_OPERAND_TYPE = issue :ILLEGAL_RELATIONSHIP_OPERAND_TYPE, :operand do "Illegal relationship operand, can not form a relationship with #{label.a_an(operand)}. A Catalog type is required." end NOT_CATALOG_TYPE = issue :NOT_CATALOG_TYPE, :type do "Illegal relationship operand, can not form a relationship with something of type #{type}. A Catalog type is required." end BAD_STRING_SLICE_ARITY = issue :BAD_STRING_SLICE_ARITY, :actual do "String supports [] with one or two arguments. Got #{actual}" end BAD_STRING_SLICE_TYPE = issue :BAD_STRING_SLICE_TYPE, :actual do "String-Type [] requires all arguments to be integers (or default). Got #{actual}" end BAD_ARRAY_SLICE_ARITY = issue :BAD_ARRAY_SLICE_ARITY, :actual do "Array supports [] with one or two arguments. Got #{actual}" end BAD_HASH_SLICE_ARITY = issue :BAD_HASH_SLICE_ARITY, :actual do "Hash supports [] with one or more arguments. Got #{actual}" end BAD_INTEGER_SLICE_ARITY = issue :BAD_INTEGER_SLICE_ARITY, :actual do "Integer-Type supports [] with one or two arguments (from, to). Got #{actual}" end BAD_INTEGER_SLICE_TYPE = issue :BAD_INTEGER_SLICE_TYPE, :actual do "Integer-Type [] requires all arguments to be integers (or default). Got #{actual}" end BAD_COLLECTION_SLICE_TYPE = issue :BAD_COLLECTION_SLICE_TYPE, :actual do "A Type's size constraint arguments must be a single Integer type, or 1-2 integers (or default). Got #{label.a_an(actual)}" end BAD_FLOAT_SLICE_ARITY = issue :BAD_INTEGER_SLICE_ARITY, :actual do "Float-Type supports [] with one or two arguments (from, to). Got #{actual}" end BAD_FLOAT_SLICE_TYPE = issue :BAD_INTEGER_SLICE_TYPE, :actual do "Float-Type [] requires all arguments to be floats, or integers (or default). Got #{actual}" end BAD_SLICE_KEY_TYPE = issue :BAD_SLICE_KEY_TYPE, :left_value, :expected_classes, :actual do expected_text = if expected_classes.size > 1 "one of #{expected_classes.join(', ')} are" else "#{expected_classes[0]} is" end "#{label.a_an_uc(left_value)}[] cannot use #{actual} where #{expected_text} expected" end BAD_TYPE_SLICE_TYPE = issue :BAD_TYPE_SLICE_TYPE, :base_type, :actual do "#{base_type}[] arguments must be types. Got #{actual}" end BAD_TYPE_SLICE_ARITY = issue :BAD_TYPE_SLICE_ARITY, :base_type, :min, :max, :actual do base_type_label = base_type.is_a?(String) ? base_type : label.a_an_uc(base_type) if max == -1 || max == 1.0 / 0.0 # Infinity "#{base_type_label}[] accepts #{min} or more arguments. Got #{actual}" elsif max && max != min "#{base_type_label}[] accepts #{min} to #{max} arguments. Got #{actual}" else "#{base_type_label}[] accepts #{min} #{label.plural_s(min, 'argument')}. Got #{actual}" end end BAD_TYPE_SPECIALIZATION = hard_issue :BAD_TYPE_SPECIALIZATION, :type, :message do "Error creating type specialization of #{label.a_an(type)}, #{message}" end ILLEGAL_TYPE_SPECIALIZATION = issue :ILLEGAL_TYPE_SPECIALIZATION, :kind do "Cannot specialize an already specialized #{kind} type" end ILLEGAL_RESOURCE_SPECIALIZATION = issue :ILLEGAL_RESOURCE_SPECIALIZATION, :actual do "First argument to Resource[] must be a resource type or a String. Got #{actual}." end EMPTY_RESOURCE_SPECIALIZATION = issue :EMPTY_RESOURCE_SPECIALIZATION do "Arguments to Resource[] are all empty/undefined" end ILLEGAL_HOSTCLASS_NAME = hard_issue :ILLEGAL_HOSTCLASS_NAME, :name do "Illegal Class name in class reference. #{label.a_an_uc(name)} cannot be used where a String is expected" end # Issues when an expression is used where it is not legal. # E.g. an arithmetic expression where a hostname is expected. # ILLEGAL_DEFINITION_NAME = hard_issue :ILLEGAL_DEFINTION_NAME, :name do "Unacceptable name. The name '#{name}' is unacceptable as the name of #{label.a_an(semantic)}" end - NON_NAMESPACED_FUNCTION = hard_issue :NON_NAMESPACED_FUNCTION, :name do - "A Puppet Function must be defined within a module name-space. The name '#{name}' is unacceptable." + CAPTURES_REST_NOT_LAST = hard_issue :CAPTURES_REST_NOT_LAST, :param_name do + "Parameter $#{param_name} is not last, and has 'captures rest'" + end + + CAPTURES_REST_NOT_SUPPORTED = hard_issue :CAPTURES_REST_NOT_SUPPORTED, :container, :param_name do + "Parameter $#{param_name} has 'captures rest' - not supported in #{label.a_an(container)}" + end + + REQUIRED_PARAMETER_AFTER_OPTIONAL = hard_issue :REQUIRED_PARAMETER_AFTER_OPTIONAL, :param_name do + "Parameter $#{param_name} is required but appears after optional parameters" + end + + MISSING_REQUIRED_PARAMETER = hard_issue :MISSING_REQUIRED_PARAMETER, :param_name do + "Parameter $#{param_name} is required but no value was given" end NOT_NUMERIC = issue :NOT_NUMERIC, :value do "The value '#{value}' cannot be converted to Numeric." end UNKNOWN_FUNCTION = issue :UNKNOWN_FUNCTION, :name do "Unknown function: '#{name}'." end UNKNOWN_VARIABLE = issue :UNKNOWN_VARIABLE, :name do "Unknown variable: '#{name}'." end RUNTIME_ERROR = issue :RUNTIME_ERROR, :detail do "Error while evaluating #{label.a_an(semantic)}, #{detail}" end UNKNOWN_RESOURCE_TYPE = issue :UNKNOWN_RESOURCE_TYPE, :type_name do "Resource type not found: #{type_name.capitalize}" end UNKNOWN_RESOURCE = issue :UNKNOWN_RESOURCE, :type_name, :title do "Resource not found: #{type_name.capitalize}['#{title}']" end UNKNOWN_RESOURCE_PARAMETER = issue :UNKNOWN_RESOURCE_PARAMETER, :type_name, :title, :param_name do "The resource #{type_name.capitalize}['#{title}'] does not have a parameter called '#{param_name}'" end DIV_BY_ZERO = hard_issue :DIV_BY_ZERO do "Division by 0" end RESULT_IS_INFINITY = hard_issue :RESULT_IS_INFINITY, :operator do "The result of the #{operator} expression is Infinity" end # TODO_HEREDOC EMPTY_HEREDOC_SYNTAX_SEGMENT = issue :EMPTY_HEREDOC_SYNTAX_SEGMENT, :syntax do "Heredoc syntax specification has empty segment between '+' : '#{syntax}'" end ILLEGAL_EPP_PARAMETERS = issue :ILLEGAL_EPP_PARAMETERS do "Ambiguous EPP parameter expression. Probably missing '<%-' before parameters to remove leading whitespace" end DISCONTINUED_IMPORT = hard_issue :DISCONTINUED_IMPORT do "Use of 'import' has been discontinued in favor of a manifest directory. See http://links.puppetlabs.com/puppet-import-deprecation" end IDEM_EXPRESSION_NOT_LAST = issue :IDEM_EXPRESSION_NOT_LAST do "This #{label.label(semantic)} is not productive. A non productive construct may only be placed last in a block/sequence" end IDEM_NOT_ALLOWED_LAST = hard_issue :IDEM_NOT_ALLOWED_LAST, :container do "This #{label.label(semantic)} is not productive. #{label.a_an_uc(container)} can not end with a non productive construct" end RESERVED_WORD = hard_issue :RESERWED_WORD, :word do "Use of reserved word: #{word}, must be quoted if intended to be a String value" end UNMATCHED_SELECTOR = hard_issue :UNMATCHED_SELECTOR, :param_value do "No matching entry for selector parameter with value '#{param_value}'" 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 589b05c05..1f7abacef 100644 --- a/lib/puppet/pops/loader/ruby_legacy_function_instantiator.rb +++ b/lib/puppet/pops/loader/ruby_legacy_function_instantiator.rb @@ -1,109 +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) 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 - # Use Puppet Type Object (not Optional[Object] since the 3x API passes undef as empty string). param 'Object', '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 diff --git a/lib/puppet/pops/loader/static_loader.rb b/lib/puppet/pops/loader/static_loader.rb index 27cfbe462..336d33e91 100644 --- a/lib/puppet/pops/loader/static_loader.rb +++ b/lib/puppet/pops/loader/static_loader.rb @@ -1,69 +1,69 @@ # Static Loader contains constants, basic data types and other types required for the system # to boot. # class Puppet::Pops::Loader::StaticLoader < Puppet::Pops::Loader::Loader attr_reader :loaded def initialize @loaded = {} create_logging_functions() end def load_typed(typed_name) load_constant(typed_name) end def get_entry(typed_name) load_constant(typed_name) end def find(name) # There is nothing to search for, everything this loader knows about is already available nil end def parent nil # at top of the hierarchy end def to_s() "(StaticLoader)" end private def load_constant(typed_name) @loaded[typed_name] end private # Creates a function for each of the specified log levels # def create_logging_functions() Puppet::Util::Log.levels.each do |level| fc = Puppet::Functions.create_function(level) do # create empty dispatcher to stop it from complaining about missing method since # an override of :call is made instead of using dispatch. dispatch(:log) { } # Logs per the specified level, outputs formatted information for arrays, hashes etc. # Overrides the implementation in Function that uses dispatching. This is not needed here - # since it accepts 0-n Optional[Object] + # since it accepts 0-n Object. # define_method(:call) do |scope, *vals| # NOTE: 3x, does this: vals.join(" ") # New implementation uses the evaluator to get proper formatting per type # TODO: uses a fake scope (nil) - fix when :scopes are available via settings mapped = vals.map {|v| Puppet::Pops::Evaluator::EvaluatorImpl.new.string(v, nil) } Puppet.send(level, mapped.join(" ")) end end typed_name = Puppet::Pops::Loader::Loader::TypedName.new(:function, level) # TODO:closure scope is fake (an empty hash) - waiting for new global scope to be available via lookup of :scopes func = fc.new({},self) @loaded[ typed_name ] = Puppet::Pops::Loader::Loader::NamedEntry.new(typed_name, func, __FILE__) end end end diff --git a/lib/puppet/pops/model/factory.rb b/lib/puppet/pops/model/factory.rb index d69e50e80..bdac5a644 100644 --- a/lib/puppet/pops/model/factory.rb +++ b/lib/puppet/pops/model/factory.rb @@ -1,990 +1,1003 @@ # Factory is a helper class that makes construction of a Pops Model # much more convenient. It can be viewed as a small internal DSL for model # constructions. # For usage see tests using the factory. # # @todo All those uppercase methods ... they look bad in one way, but stand out nicely in the grammar... # decide if they should change into lower case names (some of the are lower case)... # class Puppet::Pops::Model::Factory Model = Puppet::Pops::Model attr_accessor :current alias_method :model, :current # Shared build_visitor, since there are many instances of Factory being used @@build_visitor = Puppet::Pops::Visitor.new(self, "build") @@interpolation_visitor = Puppet::Pops::Visitor.new(self, "interpolate") # Initialize a factory with a single object, or a class with arguments applied to build of # created instance # def initialize o, *args @current = case o when Model::PopsObject o when Puppet::Pops::Model::Factory o.current else build(o, *args) end end # Polymorphic build def build(o, *args) begin @@build_visitor.visit_this(self, o, *args) rescue =>e # debug here when in trouble... raise e end end # Polymorphic interpolate def interpolate() begin @@interpolation_visitor.visit_this_0(self, current) rescue =>e # debug here when in trouble... raise e end end # Building of Model classes def build_ArithmeticExpression(o, op, a, b) o.operator = op build_BinaryExpression(o, a, b) end def build_AssignmentExpression(o, op, a, b) o.operator = op build_BinaryExpression(o, a, b) end def build_AttributeOperation(o, name, op, value) o.operator = op o.attribute_name = name.to_s # BOOLEAN is allowed in the grammar o.value_expr = build(value) o end def build_AccessExpression(o, left, *keys) o.left_expr = to_ops(left) keys.each {|expr| o.addKeys(to_ops(expr)) } o end def build_BinaryExpression(o, left, right) o.left_expr = to_ops(left) o.right_expr = to_ops(right) o end def build_BlockExpression(o, *args) args.each {|expr| o.addStatements(to_ops(expr)) } o end def build_CollectExpression(o, type_expr, query_expr, attribute_operations) o.type_expr = to_ops(type_expr) o.query = build(query_expr) attribute_operations.each {|op| o.addOperations(build(op)) } o end def build_ComparisonExpression(o, op, a, b) o.operator = op build_BinaryExpression(o, a, b) end def build_ConcatenatedString(o, *args) args.each {|expr| o.addSegments(build(expr)) } o end def build_CreateTypeExpression(o, name, super_name = nil) o.name = name o.super_name = super_name o end def build_CreateEnumExpression(o, *args) o.name = args.slice(0) if args.size == 2 o.values = build(args.last) o end def build_CreateAttributeExpression(o, name, datatype_expr) o.name = name o.type = to_ops(datatype_expr) o end def build_HeredocExpression(o, name, expr) o.syntax = name o.text_expr = build(expr) o end # @param name [String] a valid classname # @param parameters [Array] may be empty # @param parent_class_name [String, nil] a valid classname referencing a parent class, optional. # @param body [Array, Expression, nil] expression that constitute the body # @return [Model::HostClassDefinition] configured from the parameters # def build_HostClassDefinition(o, name, parameters, parent_class_name, body) build_NamedDefinition(o, name, parameters, body) o.parent_class = parent_class_name if parent_class_name o end def build_ResourceOverrideExpression(o, resources, attribute_operations) o.resources = build(resources) attribute_operations.each {|ao| o.addOperations(build(ao)) } o end def build_ReservedWord(o, name) o.word = name o end def build_KeyedEntry(o, k, v) o.key = to_ops(k) o.value = to_ops(v) o end def build_LiteralHash(o, *keyed_entries) keyed_entries.each {|entry| o.addEntries build(entry) } o end def build_LiteralList(o, *values) values.each {|v| o.addValues build(v) } o end def build_LiteralFloat(o, val) o.value = val o end def build_LiteralInteger(o, val, radix) o.value = val o.radix = radix o end def build_IfExpression(o, t, ift, els) o.test = build(t) o.then_expr = build(ift) o.else_expr= build(els) o end def build_MatchExpression(o, op, a, b) o.operator = op build_BinaryExpression(o, a, b) end # Builds body :) from different kinds of input # @overload f_build_body(nothing) # @param nothing [nil] unchanged, produces nil # @overload f_build_body(array) # @param array [Array] turns into a BlockExpression # @overload f_build_body(expr) # @param expr [Expression] produces the given expression # @overload f_build_body(obj) # @param obj [Object] produces the result of calling #build with body as argument def f_build_body(body) case body when NilClass nil when Array Puppet::Pops::Model::Factory.new(Model::BlockExpression, *body) else build(body) end end def build_LambdaExpression(o, parameters, body) parameters.each {|p| o.addParameters(build(p)) } b = f_build_body(body) o.body = to_ops(b) if b o end def build_NamedDefinition(o, name, parameters, body) parameters.each {|p| o.addParameters(build(p)) } b = f_build_body(body) o.body = b.current if b o.name = name o end # @param o [Model::NodeDefinition] # @param hosts [Array] host matches # @param parent [Expression] parent node matcher # @param body [Object] see {#f_build_body} def build_NodeDefinition(o, hosts, parent, body) hosts.each {|h| o.addHost_matches(build(h)) } o.parent = build(parent) if parent # no nop here b = f_build_body(body) o.body = b.current if b o end def build_Parameter(o, name, expr) o.name = name o.value = build(expr) if expr # don't build a nil/nop o end def build_QualifiedReference(o, name) o.value = name.to_s.downcase o end def build_RelationshipExpression(o, op, a, b) o.operator = op build_BinaryExpression(o, a, b) end def build_ResourceExpression(o, type_name, bodies) o.type_name = build(type_name) bodies.each {|b| o.addBodies(build(b)) } o end def build_RenderStringExpression(o, string) o.value = string; o end def build_ResourceBody(o, title_expression, attribute_operations) o.title = build(title_expression) attribute_operations.each {|ao| o.addOperations(build(ao)) } o end def build_ResourceDefaultsExpression(o, type_ref, attribute_operations) o.type_ref = build(type_ref) attribute_operations.each {|ao| o.addOperations(build(ao)) } o end def build_SelectorExpression(o, left, *selectors) o.left_expr = to_ops(left) selectors.each {|s| o.addSelectors(build(s)) } o end # Builds a SubLocatedExpression - this wraps the expression in a sublocation configured # from the given token # A SubLocated holds its own locator that is used for subexpressions holding positions relative # to what it describes. # def build_SubLocatedExpression(o, token, expression) o.expr = build(expression) o.offset = token.offset o.length = token.length locator = token.locator o.locator = locator o.leading_line_count = locator.leading_line_count o.leading_line_offset = locator.leading_line_offset # Index is held in sublocator's parent locator - needed to be able to reconstruct o.line_offsets = locator.locator.line_index o end def build_SelectorEntry(o, matching, value) o.matching_expr = build(matching) o.value_expr = build(value) o end def build_QueryExpression(o, expr) ops = to_ops(expr) o.expr = ops unless Puppet::Pops::Model::Factory.nop? ops o end def build_UnaryExpression(o, expr) ops = to_ops(expr) o.expr = ops unless Puppet::Pops::Model::Factory.nop? ops o end def build_Program(o, body, definitions, locator) o.body = to_ops(body) # non containment definitions.each { |d| o.addDefinitions(d) } o.source_ref = locator.file o.source_text = locator.string o.line_offsets = locator.line_index o.locator = locator o end def build_QualifiedName(o, name) o.value = name.to_s o end # Puppet::Pops::Model::Factory helpers def f_build_unary(klazz, expr) Puppet::Pops::Model::Factory.new(build(klazz.new, expr)) end def f_build_binary_op(klazz, op, left, right) Puppet::Pops::Model::Factory.new(build(klazz.new, op, left, right)) end def f_build_binary(klazz, left, right) Puppet::Pops::Model::Factory.new(build(klazz.new, left, right)) end def f_build_vararg(klazz, left, *arg) Puppet::Pops::Model::Factory.new(build(klazz.new, left, *arg)) end def f_arithmetic(op, r) f_build_binary_op(Model::ArithmeticExpression, op, current, r) end def f_comparison(op, r) f_build_binary_op(Model::ComparisonExpression, op, current, r) end def f_match(op, r) f_build_binary_op(Model::MatchExpression, op, current, r) end # Operator helpers def in(r) f_build_binary(Model::InExpression, current, r); end def or(r) f_build_binary(Model::OrExpression, current, r); end def and(r) f_build_binary(Model::AndExpression, current, r); end def not(); f_build_unary(Model::NotExpression, self); end def minus(); f_build_unary(Model::UnaryMinusExpression, self); end def unfold(); f_build_unary(Model::UnfoldExpression, self); end def text(); f_build_unary(Model::TextExpression, self); end def var(); f_build_unary(Model::VariableExpression, self); end def [](*r); f_build_vararg(Model::AccessExpression, current, *r); end def dot r; f_build_binary(Model::NamedAccessExpression, current, r); end def + r; f_arithmetic(:+, r); end def - r; f_arithmetic(:-, r); end def / r; f_arithmetic(:/, r); end def * r; f_arithmetic(:*, r); end def % r; f_arithmetic(:%, r); end def << r; f_arithmetic(:<<, r); end def >> r; f_arithmetic(:>>, r); end def < r; f_comparison(:<, r); end def <= r; f_comparison(:<=, r); end def > r; f_comparison(:>, r); end def >= r; f_comparison(:>=, r); end def == r; f_comparison(:==, r); end def ne r; f_comparison(:'!=', r); end def =~ r; f_match(:'=~', r); end def mne r; f_match(:'!~', r); end def paren(); f_build_unary(Model::ParenthesizedExpression, current); end def relop op, r f_build_binary_op(Model::RelationshipExpression, op.to_sym, current, r) end def select *args Puppet::Pops::Model::Factory.new(build(Model::SelectorExpression, current, *args)) end # For CaseExpression, setting the default for an already build CaseExpression def default r current.addOptions(Puppet::Pops::Model::Factory.WHEN(:default, r).current) self end def lambda=(lambda) current.lambda = lambda.current self end # Assignment = def set(r) f_build_binary_op(Model::AssignmentExpression, :'=', current, r) end # Assignment += def plus_set(r) f_build_binary_op(Model::AssignmentExpression, :'+=', current, r) end # Assignment -= def minus_set(r) f_build_binary_op(Model::AssignmentExpression, :'-=', current, r) end def attributes(*args) args.each {|a| current.addAttributes(build(a)) } self end # Catch all delegation to current def method_missing(meth, *args, &block) if current.respond_to?(meth) current.send(meth, *args, &block) else super end end def respond_to?(meth, include_all=false) current.respond_to?(meth, include_all) || super end def self.record_position(o, start_locatable, end_locateable) new(o).record_position(start_locatable, end_locateable) end # Records the position (start -> end) and computes the resulting length. # def record_position(start_locatable, end_locatable) from = start_locatable.is_a?(Puppet::Pops::Model::Factory) ? start_locatable.current : start_locatable to = end_locatable.is_a?(Puppet::Pops::Model::Factory) ? end_locatable.current : end_locatable to = from if to.nil? || to.offset.nil? o = current # record information directly in the Model::Positioned object o.offset = from.offset o.length ||= to.offset - from.offset + to.length self end # @return [Puppet::Pops::Adapters::SourcePosAdapter] with location information def loc() Puppet::Pops::Adapters::SourcePosAdapter.adapt(current) end # Returns symbolic information about an expected share of a resource expression given the LHS of a resource expr. # # * `name { }` => `:resource`, create a resource of the given type # * `Name { }` => ':defaults`, set defaults for the referenced type # * `Name[] { }` => `:override`, overrides instances referenced by LHS # * _any other_ => ':error', all other are considered illegal # def self.resource_shape(expr) expr = expr.current if expr.is_a?(Puppet::Pops::Model::Factory) case expr when Model::QualifiedName :resource when Model::QualifiedReference :defaults when Model::AccessExpression :override when 'class' :class else :error end end # Factory starting points def self.literal(o); new(o); end def self.minus(o); new(o).minus; end def self.unfold(o); new(o).unfold; end def self.var(o); new(o).var; end def self.block(*args); new(Model::BlockExpression, *args); end def self.string(*args); new(Model::ConcatenatedString, *args); end def self.text(o); new(o).text; end def self.IF(test_e,then_e,else_e); new(Model::IfExpression, test_e, then_e, else_e); end def self.UNLESS(test_e,then_e,else_e); new(Model::UnlessExpression, test_e, then_e, else_e); end def self.CASE(test_e,*options); new(Model::CaseExpression, test_e, *options); end def self.WHEN(values_list, block); new(Model::CaseOption, values_list, block); end def self.MAP(match, value); new(Model::SelectorEntry, match, value); end def self.TYPE(name, super_name=nil); new(Model::CreateTypeExpression, name, super_name); end def self.ATTR(name, type_expr=nil); new(Model::CreateAttributeExpression, name, type_expr); end def self.ENUM(*args); new(Model::CreateEnumExpression, *args); end def self.KEY_ENTRY(key, val); new(Model::KeyedEntry, key, val); end def self.HASH(entries); new(Model::LiteralHash, *entries); end - # TODO_HEREDOC def self.HEREDOC(name, expr); new(Model::HeredocExpression, name, expr); end def self.SUBLOCATE(token, expr) new(Model::SubLocatedExpression, token, expr); end def self.LIST(entries); new(Model::LiteralList, *entries); end def self.PARAM(name, expr=nil); new(Model::Parameter, name, expr); end def self.NODE(hosts, parent, body); new(Model::NodeDefinition, hosts, parent, body); end + + # Parameters + + # Mark parameter as capturing the rest of arguments + def captures_rest() + current.captures_rest = true + end + + # Set Expression that should evaluate to the parameter's type + def type_expr(o) + current.type_expr = to_ops(o) + end + # Creates a QualifiedName representation of o, unless o already represents a QualifiedName in which # case it is returned. # def self.fqn(o) o = o.current if o.is_a?(Puppet::Pops::Model::Factory) o = new(Model::QualifiedName, o) unless o.is_a? Model::QualifiedName o end # Creates a QualifiedName representation of o, unless o already represents a QualifiedName in which # case it is returned. # def self.fqr(o) o = o.current if o.is_a?(Puppet::Pops::Model::Factory) o = new(Model::QualifiedReference, o) unless o.is_a? Model::QualifiedReference o end def self.TEXT(expr) new(Model::TextExpression, new(expr).interpolate) end # TODO_EPP def self.RENDER_STRING(o) new(Model::RenderStringExpression, o) end def self.RENDER_EXPR(expr) new(Model::RenderExpression, expr) end def self.EPP(parameters, body) - see_scope = false - params = parameters if parameters.nil? params = [] - see_scope = true + parameters_specified = false + else + params = parameters + parameters_specified = true end - LAMBDA(params, new(Model::EppExpression, see_scope, body)) + LAMBDA(params, new(Model::EppExpression, parameters_specified, body)) end def self.RESERVED(name) new(Model::ReservedWord, name) end # TODO: This is the same a fqn factory method, don't know if callers to fqn and QNAME can live with the # same result or not yet - refactor into one method when decided. # def self.QNAME(name) new(Model::QualifiedName, name) end def self.NUMBER(name_or_numeric) if n_radix = Puppet::Pops::Utils.to_n_with_radix(name_or_numeric) val, radix = n_radix if val.is_a?(Float) new(Model::LiteralFloat, val) else new(Model::LiteralInteger, val, radix) end else # Bad number should already have been caught by lexer - this should never happen raise ArgumentError, "Internal Error, NUMBER token does not contain a valid number, #{name_or_numeric}" end end # Convert input string to either a qualified name, a LiteralInteger with radix, or a LiteralFloat # def self.QNAME_OR_NUMBER(name) if n_radix = Puppet::Pops::Utils.to_n_with_radix(name) val, radix = n_radix if val.is_a?(Float) new(Model::LiteralFloat, val) else new(Model::LiteralInteger, val, radix) end else new(Model::QualifiedName, name) end end def self.QREF(name) new(Model::QualifiedReference, name) end def self.VIRTUAL_QUERY(query_expr) new(Model::VirtualQuery, query_expr) end def self.EXPORTED_QUERY(query_expr) new(Model::ExportedQuery, query_expr) end def self.ATTRIBUTE_OP(name, op, expr) new(Model::AttributeOperation, name, op, expr) end def self.CALL_NAMED(name, rval_required, argument_list) unless name.kind_of?(Model::PopsObject) name = Puppet::Pops::Model::Factory.fqn(name) unless name.is_a?(Puppet::Pops::Model::Factory) end new(Model::CallNamedFunctionExpression, name, rval_required, *argument_list) end def self.CALL_METHOD(functor, argument_list) new(Model::CallMethodExpression, functor, true, nil, *argument_list) end def self.COLLECT(type_expr, query_expr, attribute_operations) new(Model::CollectExpression, type_expr, query_expr, attribute_operations) end def self.NAMED_ACCESS(type_name, bodies) new(Model::NamedAccessExpression, type_name, bodies) end def self.RESOURCE(type_name, bodies) new(Model::ResourceExpression, type_name, bodies) end def self.RESOURCE_DEFAULTS(type_name, attribute_operations) new(Model::ResourceDefaultsExpression, type_name, attribute_operations) end def self.RESOURCE_OVERRIDE(resource_ref, attribute_operations) new(Model::ResourceOverrideExpression, resource_ref, attribute_operations) end def self.RESOURCE_BODY(resource_title, attribute_operations) new(Model::ResourceBody, resource_title, attribute_operations) end def self.PROGRAM(body, definitions, locator) new(Model::Program, body, definitions, locator) end # Builds a BlockExpression if args size > 1, else the single expression/value in args def self.block_or_expression(*args) if args.size > 1 new(Model::BlockExpression, *args) else new(args[0]) end end def self.HOSTCLASS(name, parameters, parent, body) new(Model::HostClassDefinition, name, parameters, parent, body) end def self.DEFINITION(name, parameters, body) new(Model::ResourceTypeDefinition, name, parameters, body) end def self.LAMBDA(parameters, body) new(Model::LambdaExpression, parameters, body) end def self.nop? o o.nil? || o.is_a?(Puppet::Pops::Model::Nop) end STATEMENT_CALLS = { 'require' => true, 'realize' => true, 'include' => true, 'contain' => true, 'debug' => true, 'info' => true, 'notice' => true, 'warning' => true, 'error' => true, 'fail' => true, 'import' => true # discontinued, but transform it to make it call error reporting function } # Returns true if the given name is a "statement keyword" (require, include, contain, # error, notice, info, debug # def name_is_statement(name) STATEMENT_CALLS[name] end # Transforms an array of expressions containing literal name expressions to calls if followed by an # expression, or expression list. # def self.transform_calls(expressions) expressions.reduce([]) do |memo, expr| expr = expr.current if expr.is_a?(Puppet::Pops::Model::Factory) name = memo[-1] if name.is_a?(Model::QualifiedName) && STATEMENT_CALLS[name.value] the_call = Puppet::Pops::Model::Factory.CALL_NAMED(name, false, expr.is_a?(Array) ? expr : [expr]) # last positioned is last arg if there are several record_position(the_call, name, expr.is_a?(Array) ? expr[-1] : expr) memo[-1] = the_call if expr.is_a?(Model::CallNamedFunctionExpression) # Patch statement function call to expression style # This is needed because it is first parsed as a "statement" and the requirement changes as it becomes # an argument to the name to call transform above. expr.rval_required = true end else memo << expr if expr.is_a?(Model::CallNamedFunctionExpression) # Patch rvalue expression function call to statement style. # This is not really required but done to be AST model compliant expr.rval_required = false end end memo end end # Transforms a left expression followed by an untitled resource (in the form of attribute_operations) # @param left [Factory, Expression] the lhs followed what may be a hash def self.transform_resource_wo_title(left, attribute_ops) return nil unless attribute_ops.is_a? Array keyed_entries = attribute_ops.map do |ao| return nil if ao.operator == :'+>' KEY_ENTRY(ao.attribute_name, ao.value_expr) end result = block_or_expression(*transform_calls([left, HASH(keyed_entries)])) result end # Building model equivalences of Ruby objects # Allows passing regular ruby objects to the factory to produce instructions # that when evaluated produce the same thing. def build_String(o) x = Model::LiteralString.new x.value = o; x end def build_NilClass(o) x = Model::Nop.new x end def build_TrueClass(o) x = Model::LiteralBoolean.new x.value = o x end def build_FalseClass(o) x = Model::LiteralBoolean.new x.value = o x end def build_Fixnum(o) x = Model::LiteralInteger.new x.value = o; x end def build_Float(o) x = Model::LiteralFloat.new x.value = o; x end def build_Regexp(o) x = Model::LiteralRegularExpression.new x.value = o; x end - def build_EppExpression(o, see_scope, body) - o.see_scope = see_scope + def build_EppExpression(o, parameters_specified, body) + o.parameters_specified = parameters_specified b = f_build_body(body) o.body = b.current if b o end # If building a factory, simply unwrap the model oject contained in the factory. def build_Factory(o) o.current end # Creates a String literal, unless the symbol is one of the special :undef, or :default # which instead creates a LiterlUndef, or a LiteralDefault. def build_Symbol(o) case o when :undef Model::LiteralUndef.new when :default Model::LiteralDefault.new else build_String(o.to_s) end end # Creates a LiteralList instruction from an Array, where the entries are built. def build_Array(o) x = Model::LiteralList.new o.each { |v| x.addValues(build(v)) } x end # Create a LiteralHash instruction from a hash, where keys and values are built # The hash entries are added in sorted order based on key.to_s # def build_Hash(o) x = Model::LiteralHash.new (o.sort_by {|k,v| k.to_s}).each {|k,v| x.addEntries(build(Model::KeyedEntry.new, k, v)) } x end # @param rval_required [Boolean] if the call must produce a value def build_CallExpression(o, functor, rval_required, *args) o.functor_expr = to_ops(functor) o.rval_required = rval_required args.each {|x| o.addArguments(to_ops(x)) } o end def build_CallMethodExpression(o, functor, rval_required, lambda, *args) build_CallExpression(o, functor, rval_required, *args) o.lambda = lambda o end def build_CaseExpression(o, test, *args) o.test = build(test) args.each {|opt| o.addOptions(build(opt)) } o end def build_CaseOption(o, value_list, then_expr) value_list = [value_list] unless value_list.is_a? Array value_list.each { |v| o.addValues(build(v)) } b = f_build_body(then_expr) o.then_expr = to_ops(b) if b o end # Build a Class by creating an instance of it, and then calling build on the created instance # with the given arguments def build_Class(o, *args) build(o.new(), *args) end def interpolate_Factory(o) interpolate(o.current) end def interpolate_LiteralInteger(o) # convert number to a variable self.class.new(o).var end def interpolate_Object(o) o end def interpolate_QualifiedName(o) self.class.new(o).var end # rewrite left expression to variable if it is name, number, and recurse if it is an access expression # this is for interpolation support in new lexer (${NAME}, ${NAME[}}, ${NUMBER}, ${NUMBER[]} - all # other expressions requires variables to be preceded with $ # def interpolate_AccessExpression(o) if is_interop_rewriteable?(o.left_expr) o.left_expr = to_ops(self.class.new(o.left_expr).interpolate) end o end def interpolate_NamedAccessExpression(o) if is_interop_rewriteable?(o.left_expr) o.left_expr = to_ops(self.class.new(o.left_expr).interpolate) end o end # Rewrite method calls on the form ${x.each ...} to ${$x.each} def interpolate_CallMethodExpression(o) if is_interop_rewriteable?(o.functor_expr) o.functor_expr = to_ops(self.class.new(o.functor_expr).interpolate) end o end def is_interop_rewriteable?(o) case o when Model::AccessExpression, Model::QualifiedName, Model::NamedAccessExpression, Model::CallMethodExpression true when Model::LiteralInteger # Only decimal integers can represent variables, else it is a number o.radix == 10 else false end end # Checks if the object is already a model object, or build it def to_ops(o, *args) case o when Model::PopsObject o when Puppet::Pops::Model::Factory o.current else build(o, *args) end end def self.concat(*args) new(args.map do |e| e = e.current if e.is_a?(self) case e when Model::LiteralString e.value when String e else raise ArgumentError, "can only concatenate strings, got #{e.class}" end end.join('')) end end diff --git a/lib/puppet/pops/model/model.rb b/lib/puppet/pops/model/model.rb index e3de3ed31..d75c003b4 100644 --- a/lib/puppet/pops/model/model.rb +++ b/lib/puppet/pops/model/model.rb @@ -1,614 +1,620 @@ # # The Puppet Pops Metamodel # # This module contains a formal description of the Puppet Pops (*P*uppet *OP*eration instruction*S*). # It describes a Metamodel containing DSL instructions, a description of PuppetType and related # classes needed to evaluate puppet logic. # The metamodel resembles the existing AST model, but it is a semantic model of instructions and # the types that they operate on rather than an Abstract Syntax Tree, although closely related. # # The metamodel is anemic (has no behavior) except basic datatype and type # assertions and reference/containment assertions. # The metamodel is also a generalized description of the Puppet DSL to enable the # same metamodel to be used to express Puppet DSL models (instances) with different semantics as # the language evolves. # # The metamodel is concretized by a validator for a particular version of # the Puppet DSL language. # # This metamodel is expressed using RGen. # require 'rgen/metamodel_builder' module Puppet::Pops::Model extend RGen::MetamodelBuilder::ModuleExtension # A base class for modeled objects that makes them Visitable, and Adaptable. # class PopsObject < RGen::MetamodelBuilder::MMBase include Puppet::Pops::Visitable include Puppet::Pops::Adaptable include Puppet::Pops::Containment abstract end # A Positioned object has an offset measured in an opaque unit (representing characters) from the start # of a source text (starting # from 0), and a length measured in the same opaque unit. The resolution of the opaque unit requires the # aid of a Locator instance that knows about the measure. This information is stored in the model's # root node - a Program. # # The offset and length are optional if the source of the model is not from parsed text. # class Positioned < PopsObject abstract has_attr 'offset', Integer has_attr 'length', Integer end # @abstract base class for expressions class Expression < Positioned abstract end # A Nop - the "no op" expression. # @note not really needed since the evaluator can evaluate nil with the meaning of NoOp # @todo deprecate? May be useful if there is the need to differentiate between nil and Nop when transforming model. # class Nop < Expression end # A binary expression is abstract and has a left and a right expression. The order of evaluation # and semantics are determined by the concrete subclass. # class BinaryExpression < Expression abstract # # @!attribute [rw] left_expr # @return [Expression] contains_one_uni 'left_expr', Expression, :lowerBound => 1 contains_one_uni 'right_expr', Expression, :lowerBound => 1 end # An unary expression is abstract and contains one expression. The semantics are determined by # a concrete subclass. # class UnaryExpression < Expression abstract contains_one_uni 'expr', Expression, :lowerBound => 1 end # A class that simply evaluates to the contained expression. # It is of value in order to preserve user entered parentheses in transformations, and # transformations from model to source. # class ParenthesizedExpression < UnaryExpression; end # A boolean not expression, reversing the truth of the unary expr. # class NotExpression < UnaryExpression; end # An arithmetic expression reversing the polarity of the numeric unary expr. # class UnaryMinusExpression < UnaryExpression; end # Unfolds an array (a.k.a 'splat') class UnfoldExpression < UnaryExpression; end # An assignment expression assigns a value to the lval() of the left_expr. # class AssignmentExpression < BinaryExpression has_attr 'operator', RGen::MetamodelBuilder::DataTypes::Enum.new([:'=', :'+=', :'-=']), :lowerBound => 1 end # An arithmetic expression applies an arithmetic operator on left and right expressions. # class ArithmeticExpression < BinaryExpression has_attr 'operator', RGen::MetamodelBuilder::DataTypes::Enum.new([:'+', :'-', :'*', :'%', :'/', :'<<', :'>>' ]), :lowerBound => 1 end # A relationship expression associates the left and right expressions # class RelationshipExpression < BinaryExpression has_attr 'operator', RGen::MetamodelBuilder::DataTypes::Enum.new([:'->', :'<-', :'~>', :'<~']), :lowerBound => 1 end # A binary expression, that accesses the value denoted by right in left. i.e. typically # expressed concretely in a language as left[right]. # class AccessExpression < Expression contains_one_uni 'left_expr', Expression, :lowerBound => 1 contains_many_uni 'keys', Expression, :lowerBound => 1 end # A comparison expression compares left and right using a comparison operator. # class ComparisonExpression < BinaryExpression has_attr 'operator', RGen::MetamodelBuilder::DataTypes::Enum.new([:'==', :'!=', :'<', :'>', :'<=', :'>=' ]), :lowerBound => 1 end # A match expression matches left and right using a matching operator. # class MatchExpression < BinaryExpression has_attr 'operator', RGen::MetamodelBuilder::DataTypes::Enum.new([:'!~', :'=~']), :lowerBound => 1 end # An 'in' expression checks if left is 'in' right # class InExpression < BinaryExpression; end # A boolean expression applies a logical connective operator (and, or) to left and right expressions. # class BooleanExpression < BinaryExpression abstract end # An and expression applies the logical connective operator and to left and right expression # and does not evaluate the right expression if the left expression is false. # class AndExpression < BooleanExpression; end # An or expression applies the logical connective operator or to the left and right expression # and does not evaluate the right expression if the left expression is true # class OrExpression < BooleanExpression; end # A literal list / array containing 0:M expressions. # class LiteralList < Expression contains_many_uni 'values', Expression end # A Keyed entry has a key and a value expression. It is typically used as an entry in a Hash. # class KeyedEntry < Positioned contains_one_uni 'key', Expression, :lowerBound => 1 contains_one_uni 'value', Expression, :lowerBound => 1 end # A literal hash is a collection of KeyedEntry objects # class LiteralHash < Expression contains_many_uni 'entries', KeyedEntry end # A block contains a list of expressions # class BlockExpression < Expression contains_many_uni 'statements', Expression end # A case option entry in a CaseStatement # class CaseOption < Expression contains_many_uni 'values', Expression, :lowerBound => 1 contains_one_uni 'then_expr', Expression, :lowerBound => 1 end # A case expression has a test, a list of options (multi values => block map). # One CaseOption may contain a LiteralDefault as value. This option will be picked if nothing # else matched. # class CaseExpression < Expression contains_one_uni 'test', Expression, :lowerBound => 1 contains_many_uni 'options', CaseOption end # A query expression is an expression that is applied to some collection. # The contained optional expression may contain different types of relational expressions depending # on what the query is applied to. # class QueryExpression < Expression abstract contains_one_uni 'expr', Expression, :lowerBound => 0 end # An exported query is a special form of query that searches for exported objects. # class ExportedQuery < QueryExpression end # A virtual query is a special form of query that searches for virtual objects. # class VirtualQuery < QueryExpression end # An attribute operation sets or appends a value to a named attribute. # class AttributeOperation < Positioned has_attr 'attribute_name', String, :lowerBound => 1 has_attr 'operator', RGen::MetamodelBuilder::DataTypes::Enum.new([:'=>', :'+>', ]), :lowerBound => 1 contains_one_uni 'value_expr', Expression, :lowerBound => 1 end # An object that collects stored objects from the central cache and returns # them to the current host. Operations may optionally be applied. # class CollectExpression < Expression contains_one_uni 'type_expr', Expression, :lowerBound => 1 contains_one_uni 'query', QueryExpression, :lowerBound => 1 contains_many_uni 'operations', AttributeOperation end class Parameter < Positioned has_attr 'name', String, :lowerBound => 1 contains_one_uni 'value', Expression + contains_one_uni 'type_expr', Expression, :lowerBound => 0 + has_attr 'captures_rest', Boolean end # Abstract base class for definitions. # class Definition < Expression abstract end # Abstract base class for named and parameterized definitions. class NamedDefinition < Definition abstract has_attr 'name', String, :lowerBound => 1 contains_many_uni 'parameters', Parameter contains_one_uni 'body', Expression end # A resource type definition (a 'define' in the DSL). # class ResourceTypeDefinition < NamedDefinition end # A node definition matches hosts using Strings, or Regular expressions. It may inherit from # a parent node (also using a String or Regular expression). # class NodeDefinition < Definition contains_one_uni 'parent', Expression contains_many_uni 'host_matches', Expression, :lowerBound => 1 contains_one_uni 'body', Expression end class LocatableExpression < Expression has_many_attr 'line_offsets', Integer has_attr 'locator', Object, :lowerBound => 1, :transient => true module ClassModule # Go through the gymnastics of making either value or pattern settable # with synchronization to the other form. A derived value cannot be serialized # and we want to serialize the pattern. When recreating the object we need to # recreate it from the pattern string. # The below sets both values if one is changed. # def locator unless result = getLocator setLocator(result = Puppet::Pops::Parser::Locator.locator(source_text, source_ref(), line_offsets)) end result end end end # Contains one expression which has offsets reported virtually (offset against the Program's # overall locator). # class SubLocatedExpression < Expression contains_one_uni 'expr', Expression, :lowerBound => 1 # line offset index for contained expressions has_many_attr 'line_offsets', Integer # Number of preceding lines (before the line_offsets) has_attr 'leading_line_count', Integer # The offset of the leading source line (i.e. size of "left margin"). has_attr 'leading_line_offset', Integer # The locator for the sub-locatable's children (not for the sublocator itself) # The locator is not serialized and is recreated on demand from the indexing information # in self. # has_attr 'locator', Object, :lowerBound => 1, :transient => true module ClassModule def locator unless result = getLocator # Adapt myself to get the Locator for me adapter = Puppet::Pops::Adapters::SourcePosAdapter.adapt(self) # Get the program (root), and deal with case when not contained in a program program = eAllContainers.find {|c| c.is_a?(Program) } source_ref = program.nil? ? '' : program.source_ref # An outer locator is needed since SubLocator only deals with offsets. This outer locator # has 0,0 as origin. outer_locator = Puppet::Pops::Parser::Locator.locator(adpater.extract_text, source_ref, line_offsets) # Create a sublocator that describes an offset from the outer # NOTE: the offset of self is the same as the sublocator's leading_offset result = Puppet::Pops::Parser::Locator::SubLocator.new(outer_locator, leading_line_count, offset, leading_line_offset) setLocator(result) end result end end end # A heredoc is a wrapper around a LiteralString or a ConcatenatedStringExpression with a specification # of syntax. The expectation is that "syntax" has meaning to a validator. A syntax of nil or '' means # "unspecified syntax". # class HeredocExpression < Expression has_attr 'syntax', String contains_one_uni 'text_expr', Expression, :lowerBound => 1 end # A class definition # class HostClassDefinition < NamedDefinition has_attr 'parent_class', String end # i.e {|parameters| body } class LambdaExpression < Expression contains_many_uni 'parameters', Parameter contains_one_uni 'body', Expression end # If expression. If test is true, the then_expr part should be evaluated, else the (optional) # else_expr. An 'elsif' is simply an else_expr = IfExpression, and 'else' is simply else == Block. # a 'then' is typically a Block. # class IfExpression < Expression contains_one_uni 'test', Expression, :lowerBound => 1 contains_one_uni 'then_expr', Expression, :lowerBound => 1 contains_one_uni 'else_expr', Expression end # An if expression with boolean reversed test. # class UnlessExpression < IfExpression end # An abstract call. # class CallExpression < Expression abstract # A bit of a crutch; functions are either procedures (void return) or has an rvalue # this flag tells the evaluator that it is a failure to call a function that is void/procedure # where a value is expected. # has_attr 'rval_required', Boolean, :defaultValueLiteral => "false" contains_one_uni 'functor_expr', Expression, :lowerBound => 1 contains_many_uni 'arguments', Expression contains_one_uni 'lambda', Expression end # A function call where the functor_expr should evaluate to something callable. # class CallFunctionExpression < CallExpression; end # A function call where the given functor_expr should evaluate to the name # of a function. # class CallNamedFunctionExpression < CallExpression; end # A method/function call where the function expr is a NamedAccess and with support for # an optional lambda block # class CallMethodExpression < CallExpression end # Abstract base class for literals. # class Literal < Expression abstract end # A literal value is an abstract value holder. The type of the contained value is # determined by the concrete subclass. # class LiteralValue < Literal abstract end # A Regular Expression Literal. # class LiteralRegularExpression < LiteralValue has_attr 'value', Object, :lowerBound => 1, :transient => true has_attr 'pattern', String, :lowerBound => 1 module ClassModule # Go through the gymnastics of making either value or pattern settable # with synchronization to the other form. A derived value cannot be serialized # and we want to serialize the pattern. When recreating the object we need to # recreate it from the pattern string. # The below sets both values if one is changed. # def value= regexp setValue regexp setPattern regexp.to_s end def pattern= regexp_string setPattern regexp_string setValue Regexp.new(regexp_string) end end end # A Literal String # class LiteralString < LiteralValue has_attr 'value', String, :lowerBound => 1 end class LiteralNumber < LiteralValue abstract end # A literal number has a radix of decimal (10), octal (8), or hex (16) to enable string conversion with the input radix. # By default, a radix of 10 is used. # class LiteralInteger < LiteralNumber has_attr 'radix', Integer, :lowerBound => 1, :defaultValueLiteral => "10" has_attr 'value', Integer, :lowerBound => 1 end class LiteralFloat < LiteralNumber has_attr 'value', Float, :lowerBound => 1 end # The DSL `undef`. # class LiteralUndef < Literal; end # The DSL `default` class LiteralDefault < Literal; end # DSL `true` or `false` class LiteralBoolean < LiteralValue has_attr 'value', Boolean, :lowerBound => 1 end # A text expression is an interpolation of an expression. If the embedded expression is # a QualifiedName, it is taken as a variable name and resolved. All other expressions are evaluated. # The result is transformed to a string. # class TextExpression < UnaryExpression; end # An interpolated/concatenated string. The contained segments are expressions. Verbatim sections # should be LiteralString instances, and interpolated expressions should either be # TextExpression instances (if QualifiedNames should be turned into variables), or any other expression # if such treatment is not needed. # class ConcatenatedString < Expression contains_many_uni 'segments', Expression end # A DSL NAME (one or multiple parts separated by '::'). # class QualifiedName < LiteralValue has_attr 'value', String, :lowerBound => 1 end # Represents a parsed reserved word class ReservedWord < LiteralValue has_attr 'word', String, :lowerBound => 1 end # A DSL CLASSREF (one or multiple parts separated by '::' where (at least) the first part starts with an upper case letter). # class QualifiedReference < LiteralValue has_attr 'value', String, :lowerBound => 1 end # A Variable expression looks up value of expr (some kind of name) in scope. # The expression is typically a QualifiedName, or QualifiedReference. # class VariableExpression < UnaryExpression; end # Epp start class EppExpression < Expression - has_attr 'see_scope', Boolean + # EPP can be specified without giving any parameter specification. + # However, the parameters of the lambda in that case are the empty + # array, which is the same as when the parameters are explicity + # specified as empty. This attribute tracks that difference. + has_attr 'parameters_specified', Boolean contains_one_uni 'body', Expression end # A string to render class RenderStringExpression < LiteralString end # An expression to evluate and render class RenderExpression < UnaryExpression end # A resource body describes one resource instance # class ResourceBody < Positioned contains_one_uni 'title', Expression contains_many_uni 'operations', AttributeOperation end # An abstract resource describes the form of the resource (regular, virtual or exported) # and adds convenience methods to ask if it is virtual or exported. # All derived classes may not support all forms, and these needs to be validated # class AbstractResource < Expression abstract has_attr 'form', RGen::MetamodelBuilder::DataTypes::Enum.new([:regular, :virtual, :exported ]), :lowerBound => 1, :defaultValueLiteral => "regular" has_attr 'virtual', Boolean, :derived => true has_attr 'exported', Boolean, :derived => true module ClassModule def virtual_derived form == :virtual || form == :exported end def exported_derived form == :exported end end end # A resource expression is used to instantiate one or many resource. Resources may optionally # be virtual or exported, an exported resource is always virtual. # class ResourceExpression < AbstractResource contains_one_uni 'type_name', Expression, :lowerBound => 1 contains_many_uni 'bodies', ResourceBody end # A resource defaults sets defaults for a resource type. This class inherits from AbstractResource # but does only support the :regular form (this is intentional to be able to produce better error messages # when illegal forms are applied to a model. # class ResourceDefaultsExpression < AbstractResource contains_one_uni 'type_ref', QualifiedReference contains_many_uni 'operations', AttributeOperation end # A resource override overrides already set values. # class ResourceOverrideExpression < Expression contains_one_uni 'resources', Expression, :lowerBound => 1 contains_many_uni 'operations', AttributeOperation end # A selector entry describes a map from matching_expr to value_expr. # class SelectorEntry < Positioned contains_one_uni 'matching_expr', Expression, :lowerBound => 1 contains_one_uni 'value_expr', Expression, :lowerBound => 1 end # A selector expression represents a mapping from a left_expr to a matching SelectorEntry. # class SelectorExpression < Expression contains_one_uni 'left_expr', Expression, :lowerBound => 1 contains_many_uni 'selectors', SelectorEntry end # A named access expression looks up a named part. (e.g. $a.b) # class NamedAccessExpression < BinaryExpression; end # A Program is the top level construct returned by the parser # it contains the parsed result in the body, and has a reference to the full source text, # and its origin. The line_offset's is an array with the start offset of each line. # class Program < PopsObject contains_one_uni 'body', Expression has_many 'definitions', Definition has_attr 'source_text', String has_attr 'source_ref', String has_many_attr 'line_offsets', Integer has_attr 'locator', Object, :lowerBound => 1, :transient => true module ClassModule def locator unless result = getLocator setLocator(result = Puppet::Pops::Parser::Locator.locator(source_text, source_ref(), line_offsets)) end result end end end end diff --git a/lib/puppet/pops/model/model_tree_dumper.rb b/lib/puppet/pops/model/model_tree_dumper.rb index 0d5e2b79f..2791a01ba 100644 --- a/lib/puppet/pops/model/model_tree_dumper.rb +++ b/lib/puppet/pops/model/model_tree_dumper.rb @@ -1,393 +1,398 @@ # Dumps a Pops::Model in reverse polish notation; i.e. LISP style # The intention is to use this for debugging output # TODO: BAD NAME - A DUMP is a Ruby Serialization # class Puppet::Pops::Model::ModelTreeDumper < Puppet::Pops::Model::TreeDumper def dump_Array o o.collect {|e| do_dump(e) } end def dump_LiteralFloat o o.value.to_s end def dump_LiteralInteger o case o.radix when 10 o.value.to_s when 8 "0%o" % o.value when 16 "0x%X" % o.value else "bad radix:" + o.value.to_s end end def dump_LiteralValue o o.value.to_s end def dump_Factory o do_dump(o.current) end def dump_ArithmeticExpression o [o.operator.to_s, do_dump(o.left_expr), do_dump(o.right_expr)] end # x[y] prints as (slice x y) def dump_AccessExpression o if o.keys.size <= 1 ["slice", do_dump(o.left_expr), do_dump(o.keys[0])] else ["slice", do_dump(o.left_expr), do_dump(o.keys)] end end def dump_MatchesExpression o [o.operator.to_s, do_dump(o.left_expr), do_dump(o.right_expr)] end def dump_CollectExpression o result = ["collect", do_dump(o.type_expr), :indent, :break, do_dump(o.query), :indent] o.operations do |ao| result << :break << do_dump(ao) end result += [:dedent, :dedent ] result end def dump_EppExpression o result = ["epp"] # result << ["parameters"] + o.parameters.collect {|p| do_dump(p) } if o.parameters.size() > 0 if o.body result << do_dump(o.body) else result << [] end result end def dump_ExportedQuery o result = ["<<| |>>"] result += dump_QueryExpression(o) unless is_nop?(o.expr) result end def dump_VirtualQuery o result = ["<| |>"] result += dump_QueryExpression(o) unless is_nop?(o.expr) result end def dump_QueryExpression o [do_dump(o.expr)] end def dump_ComparisonExpression o [o.operator.to_s, do_dump(o.left_expr), do_dump(o.right_expr)] end def dump_AndExpression o ["&&", do_dump(o.left_expr), do_dump(o.right_expr)] end def dump_OrExpression o ["||", do_dump(o.left_expr), do_dump(o.right_expr)] end def dump_InExpression o ["in", do_dump(o.left_expr), do_dump(o.right_expr)] end def dump_AssignmentExpression o [o.operator.to_s, do_dump(o.left_expr), do_dump(o.right_expr)] end # Produces (name => expr) or (name +> expr) def dump_AttributeOperation o [o.attribute_name, o.operator, do_dump(o.value_expr)] end def dump_LiteralList o ["[]"] + o.values.collect {|x| do_dump(x)} end def dump_LiteralHash o ["{}"] + o.entries.collect {|x| do_dump(x)} end def dump_KeyedEntry o [do_dump(o.key), do_dump(o.value)] end def dump_MatchExpression o [o.operator.to_s, do_dump(o.left_expr), do_dump(o.right_expr)] end def dump_LiteralString o "'#{o.value}'" end def dump_LambdaExpression o result = ["lambda"] result << ["parameters"] + o.parameters.collect {|p| do_dump(p) } if o.parameters.size() > 0 if o.body result << do_dump(o.body) else result << [] end result end def dump_LiteralDefault o ":default" end def dump_LiteralUndef o ":undef" end def dump_LiteralRegularExpression o "/#{o.value.source}/" end def dump_Nop o ":nop" end def dump_NamedAccessExpression o [".", do_dump(o.left_expr), do_dump(o.right_expr)] end def dump_NilClass o "()" end def dump_NotExpression o ['!', dump(o.expr)] end def dump_VariableExpression o "$#{dump(o.expr)}" end # Interpolation (to string) shown as (str expr) def dump_TextExpression o ["str", do_dump(o.expr)] end def dump_UnaryMinusExpression o ['-', do_dump(o.expr)] end def dump_UnfoldExpression o ['unfold', do_dump(o.expr)] end def dump_BlockExpression o ["block"] + o.statements.collect {|x| do_dump(x) } end # Interpolated strings are shown as (cat seg0 seg1 ... segN) def dump_ConcatenatedString o ["cat"] + o.segments.collect {|x| do_dump(x)} end def dump_HeredocExpression(o) result = ["@(#{o.syntax})", :indent, :break, do_dump(o.text_expr), :dedent, :break] end def dump_HostClassDefinition o result = ["class", o.name] result << ["inherits", o.parent_class] if o.parent_class result << ["parameters"] + o.parameters.collect {|p| do_dump(p) } if o.parameters.size() > 0 if o.body result << do_dump(o.body) else result << [] end result end def dump_NodeDefinition o result = ["node"] result << ["matches"] + o.host_matches.collect {|m| do_dump(m) } result << ["parent", do_dump(o.parent)] if o.parent if o.body result << do_dump(o.body) else result << [] end result end def dump_NamedDefinition o # the nil must be replaced with a string result = [nil, o.name] result << ["parameters"] + o.parameters.collect {|p| do_dump(p) } if o.parameters.size() > 0 if o.body result << do_dump(o.body) else result << [] end result end def dump_ResourceTypeDefinition o result = dump_NamedDefinition(o) result[0] = 'define' result end def dump_ResourceOverrideExpression o result = ["override", do_dump(o.resources), :indent] o.operations.each do |p| result << :break << do_dump(p) end result << :dedent result end def dump_ReservedWord o [ 'reserved', o.word ] end # Produces parameters as name, or (= name value) def dump_Parameter o - name_part = "#{o.name}" - if o.value + name_prefix = o.captures_rest ? '*' : '' + name_part = "#{name_prefix}#{o.name}" + if o.value && o.type_expr + ["=t", do_dump(o.type_expr), name_part, do_dump(o.value)] + elsif o.value ["=", name_part, do_dump(o.value)] + elsif o.type_expr + ["t", do_dump(o.type_expr), name_part] else name_part end end def dump_ParenthesizedExpression o do_dump(o.expr) end # Hides that Program exists in the output (only its body is shown), the definitions are just # references to contained classes, resource types, and nodes def dump_Program(o) dump(o.body) end def dump_IfExpression o result = ["if", do_dump(o.test), :indent, :break, ["then", :indent, do_dump(o.then_expr), :dedent]] result += [:break, ["else", :indent, do_dump(o.else_expr), :dedent], :dedent] unless is_nop? o.else_expr result end def dump_UnlessExpression o result = ["unless", do_dump(o.test), :indent, :break, ["then", :indent, do_dump(o.then_expr), :dedent]] result += [:break, ["else", :indent, do_dump(o.else_expr), :dedent], :dedent] unless is_nop? o.else_expr result end # Produces (invoke name args...) when not required to produce an rvalue, and # (call name args ... ) otherwise. # def dump_CallNamedFunctionExpression o result = [o.rval_required ? "call" : "invoke", do_dump(o.functor_expr)] o.arguments.collect {|a| result << do_dump(a) } result end # def dump_CallNamedFunctionExpression o # result = [o.rval_required ? "call" : "invoke", do_dump(o.functor_expr)] # o.arguments.collect {|a| result << do_dump(a) } # result # end def dump_CallMethodExpression o result = [o.rval_required ? "call-method" : "invoke-method", do_dump(o.functor_expr)] o.arguments.collect {|a| result << do_dump(a) } result << do_dump(o.lambda) if o.lambda result end def dump_CaseExpression o result = ["case", do_dump(o.test), :indent] o.options.each do |s| result << :break << do_dump(s) end result << :dedent end def dump_CaseOption o result = ["when"] result << o.values.collect {|x| do_dump(x) } result << ["then", do_dump(o.then_expr) ] result end def dump_RelationshipExpression o [o.operator.to_s, do_dump(o.left_expr), do_dump(o.right_expr)] end def dump_RenderStringExpression o ["render-s", " '#{o.value}'"] end def dump_RenderExpression o ["render", do_dump(o.expr)] end def dump_ResourceBody o result = [do_dump(o.title), :indent] o.operations.each do |p| result << :break << do_dump(p) end result << :dedent result end def dump_ResourceDefaultsExpression o result = ["resource-defaults", do_dump(o.type_ref), :indent] o.operations.each do |p| result << :break << do_dump(p) end result << :dedent result end def dump_ResourceExpression o form = o.form == :regular ? '' : o.form.to_s + "-" result = [form+"resource", do_dump(o.type_name), :indent] o.bodies.each do |b| result << :break << do_dump(b) end result << :dedent result end def dump_SelectorExpression o ["?", do_dump(o.left_expr)] + o.selectors.collect {|x| do_dump(x) } end def dump_SelectorEntry o [do_dump(o.matching_expr), "=>", do_dump(o.value_expr)] end def dump_SubLocatedExpression o ["sublocated", do_dump(o.expr)] end def dump_Object o [o.class.to_s, o.to_s] end def is_nop? o o.nil? || o.is_a?(Puppet::Pops::Model::Nop) end end diff --git a/lib/puppet/pops/parser/egrammar.ra b/lib/puppet/pops/parser/egrammar.ra index 3477cde1c..4aa8c03b9 100644 --- a/lib/puppet/pops/parser/egrammar.ra +++ b/lib/puppet/pops/parser/egrammar.ra @@ -1,783 +1,801 @@ # vim: syntax=ruby # Parser using the Pops model, expression based class Puppet::Pops::Parser::Parser token STRING DQPRE DQMID DQPOST token WORD token LBRACK RBRACK LBRACE RBRACE SYMBOL FARROW COMMA TRUE token FALSE EQUALS APPENDS DELETES LESSEQUAL NOTEQUAL DOT COLON LLCOLLECT RRCOLLECT token QMARK LPAREN RPAREN ISEQUAL GREATEREQUAL GREATERTHAN LESSTHAN token IF ELSE token DEFINE ELSIF VARIABLE CLASS INHERITS NODE BOOLEAN token NAME SEMIC CASE DEFAULT AT ATAT LCOLLECT RCOLLECT CLASSREF token NOT OR AND UNDEF PARROW PLUS MINUS TIMES DIV LSHIFT RSHIFT UMINUS token MATCH NOMATCH REGEX IN_EDGE OUT_EDGE IN_EDGE_SUB OUT_EDGE_SUB token IN UNLESS PIPE token LAMBDA SELBRACE token NUMBER token HEREDOC SUBLOCATE token RENDER_STRING RENDER_EXPR EPP_START EPP_END EPP_END_TRIM token FUNCTION token PRIVATE ATTR TYPE token LOW prechigh left HIGH left SEMIC left PIPE left LPAREN left RPAREN left AT ATAT left DOT left CALL nonassoc EPP_START left LBRACK LISTSTART left RBRACK left QMARK left LCOLLECT LLCOLLECT right NOT nonassoc SPLAT nonassoc UMINUS left IN left MATCH NOMATCH left TIMES DIV MODULO left MINUS PLUS left LSHIFT RSHIFT left NOTEQUAL ISEQUAL left GREATEREQUAL GREATERTHAN LESSTHAN LESSEQUAL left AND left OR right APPENDS DELETES EQUALS left LBRACE left SELBRACE left RBRACE left IN_EDGE OUT_EDGE IN_EDGE_SUB OUT_EDGE_SUB left TITLE_COLON left CASE_COLON left FARROW left COMMA nonassoc RENDER_EXPR nonassoc RENDER_STRING left LOW preclow rule # Produces [Model::BlockExpression, Model::Expression, nil] depending on multiple statements, single statement or empty program : statements { result = create_program(Factory.block_or_expression(*val[0])) } | epp_expression { result = create_program(Factory.block_or_expression(*val[0])) } | nil # Produces a semantic model (non validated, but semantically adjusted). statements : syntactic_statements { result = transform_calls(val[0]) } # Change may have issues with nil; i.e. program is a sequence of nils/nops # Simplified from original which had validation for top level constructs - see statement rule # Produces Array syntactic_statements : syntactic_statement { result = [val[0]]} | syntactic_statements SEMIC syntactic_statement { result = val[0].push val[2] } | syntactic_statements syntactic_statement { result = val[0].push val[1] } # Produce a single expression or Array of expression syntactic_statement : any_expression { result = val[0] } | syntactic_statement COMMA any_expression { result = aryfy(val[0]).push val[2] } any_expression : relationship_expression relationship_expression : resource_expression =LOW { result = val[0] } | relationship_expression IN_EDGE relationship_expression { result = val[0].relop(val[1][:value], val[2]); loc result, val[1] } | relationship_expression IN_EDGE_SUB relationship_expression { result = val[0].relop(val[1][:value], val[2]); loc result, val[1] } | relationship_expression OUT_EDGE relationship_expression { result = val[0].relop(val[1][:value], val[2]); loc result, val[1] } | relationship_expression OUT_EDGE_SUB relationship_expression { result = val[0].relop(val[1][:value], val[2]); loc result, val[1] } #---EXPRESSION # # Produces Model::Expression expression : higher_precedence | expression LBRACK expressions RBRACK =LBRACK { result = val[0][*val[2]] ; loc result, val[0], val[3] } | expression IN expression { result = val[0].in val[2] ; loc result, val[1] } | expression MATCH expression { result = val[0] =~ val[2] ; loc result, val[1] } | expression NOMATCH expression { result = val[0].mne val[2] ; loc result, val[1] } | expression PLUS expression { result = val[0] + val[2] ; loc result, val[1] } | expression MINUS expression { result = val[0] - val[2] ; loc result, val[1] } | expression DIV expression { result = val[0] / val[2] ; loc result, val[1] } | expression TIMES expression { result = val[0] * val[2] ; loc result, val[1] } | expression MODULO expression { result = val[0] % val[2] ; loc result, val[1] } | expression LSHIFT expression { result = val[0] << val[2] ; loc result, val[1] } | expression RSHIFT expression { result = val[0] >> val[2] ; loc result, val[1] } | MINUS expression =UMINUS { result = val[1].minus() ; loc result, val[0] } | TIMES expression =SPLAT { result = val[1].unfold() ; loc result, val[0] } | expression NOTEQUAL expression { result = val[0].ne val[2] ; loc result, val[1] } | expression ISEQUAL expression { result = val[0] == val[2] ; loc result, val[1] } | expression GREATERTHAN expression { result = val[0] > val[2] ; loc result, val[1] } | expression GREATEREQUAL expression { result = val[0] >= val[2] ; loc result, val[1] } | expression LESSTHAN expression { result = val[0] < val[2] ; loc result, val[1] } | expression LESSEQUAL expression { result = val[0] <= val[2] ; loc result, val[1] } | NOT expression { result = val[1].not ; loc result, val[0] } | expression AND expression { result = val[0].and val[2] ; loc result, val[1] } | expression OR expression { result = val[0].or val[2] ; loc result, val[1] } | expression EQUALS expression { result = val[0].set(val[2]) ; loc result, val[1] } | expression APPENDS expression { result = val[0].plus_set(val[2]) ; loc result, val[1] } | expression DELETES expression { result = val[0].minus_set(val[2]); loc result, val[1] } | expression QMARK selector_entries { result = val[0].select(*val[2]) ; loc result, val[0] } | LPAREN expression RPAREN { result = val[1].paren() ; loc result, val[0] } #---EXPRESSIONS # (e.g. argument list) # # This expression list can not contain function calls without parentheses around arguments # Produces Array expressions : expression { result = [val[0]] } | expressions COMMA expression { result = val[0].push(val[2]) } # These go through a chain of left recursion, ending with primary_expression higher_precedence : call_function_expression primary_expression : literal_expression | variable | call_method_with_lambda_expression | collection_expression | case_expression | if_expression | unless_expression | definition_expression | hostclass_expression | node_definition_expression | epp_render_expression | reserved_word # Allways have the same value literal_expression : array | boolean | default | hash | regex | text_or_name | number | type | undef text_or_name : name { result = val[0] } | quotedtext { result = val[0] } #---CALL FUNCTION # # Produces Model::CallNamedFunction call_function_expression : primary_expression LPAREN expressions endcomma RPAREN { result = Factory.CALL_NAMED(val[0], true, val[2]) loc result, val[0], val[4] } | primary_expression LPAREN RPAREN { result = Factory.CALL_NAMED(val[0], true, []) loc result, val[0], val[2] } | primary_expression LPAREN expressions endcomma RPAREN lambda { result = Factory.CALL_NAMED(val[0], true, val[2]) loc result, val[0], val[4] result.lambda = val[5] } | primary_expression LPAREN RPAREN lambda { result = Factory.CALL_NAMED(val[0], true, []) loc result, val[0], val[2] result.lambda = val[3] } | primary_expression = LOW { result = val[0] } #---CALL METHOD # call_method_with_lambda_expression : call_method_expression =LOW { result = val[0] } | call_method_expression lambda { result = val[0]; val[0].lambda = val[1] } call_method_expression : named_access LPAREN expressions RPAREN { result = Factory.CALL_METHOD(val[0], val[2]); loc result, val[1], val[3] } | named_access LPAREN RPAREN { result = Factory.CALL_METHOD(val[0], []); loc result, val[1], val[3] } | named_access =LOW { result = Factory.CALL_METHOD(val[0], []); loc result, val[0] } # TODO: It may be of value to access named elements of types too named_access : expression DOT NAME { result = val[0].dot(Factory.fqn(val[2][:value])) loc result, val[1], val[2] } #---LAMBDA # # This is a temporary switch while experimenting with concrete syntax # One should be picked for inclusion in puppet. # Lambda with parameters to the left of the body lambda : lambda_parameter_list lambda_rest { result = Factory.LAMBDA(val[0], val[1]) # loc result, val[1] # TODO } lambda_rest : LBRACE statements RBRACE { result = val[1] } | LBRACE RBRACE { result = nil } # Produces Array lambda_parameter_list : PIPE PIPE { result = [] } | PIPE parameters endcomma PIPE { result = val[1] } #---CONDITIONALS # #--IF # # Produces Model::IfExpression if_expression : IF if_part { result = val[1] loc(result, val[0], val[1]) } # Produces Model::IfExpression if_part : expression LBRACE statements RBRACE else { result = Factory.IF(val[0], Factory.block_or_expression(*val[2]), val[4]) loc(result, val[0], (val[4] ? val[4] : val[3])) } | expression LBRACE RBRACE else { result = Factory.IF(val[0], nil, val[3]) loc(result, val[0], (val[3] ? val[3] : val[2])) } # Produces [Model::Expression, nil] - nil if there is no else or elsif part else : # nothing | ELSIF if_part { result = val[1] loc(result, val[0], val[1]) } | ELSE LBRACE statements RBRACE { result = Factory.block_or_expression(*val[2]) loc result, val[0], val[3] } | ELSE LBRACE RBRACE { result = nil # don't think a nop is needed here either } #--UNLESS # # Changed from Puppet 3x where there is no else part on unless # unless_expression : UNLESS expression LBRACE statements RBRACE unless_else { result = Factory.UNLESS(val[1], Factory.block_or_expression(*val[3]), val[5]) loc result, val[0], val[4] } | UNLESS expression LBRACE RBRACE unless_else { result = Factory.UNLESS(val[1], nil, nil) loc result, val[0], val[4] } # Different from else part of if, since "elsif" is not supported, but 'else' is # # Produces [Model::Expression, nil] - nil if there is no else or elsif part unless_else : # nothing | ELSE LBRACE statements RBRACE { result = Factory.block_or_expression(*val[2]) loc result, val[0], val[3] } | ELSE LBRACE RBRACE { result = nil # don't think a nop is needed here either } #--- CASE EXPRESSION # # Produces Model::CaseExpression case_expression : CASE expression LBRACE case_options RBRACE { result = Factory.CASE(val[1], *val[3]) loc result, val[0], val[4] } # Produces Array case_options : case_option { result = [val[0]] } | case_options case_option { result = val[0].push val[1] } # Produced Model::CaseOption (aka When) case_option : expressions case_colon LBRACE statements RBRACE { result = Factory.WHEN(val[0], val[3]) loc result, val[1], val[4] } | expressions case_colon LBRACE RBRACE = LOW { result = Factory.WHEN(val[0], nil) loc result, val[1], val[3] } case_colon: COLON =CASE_COLON { result = val[0] } # This special construct is required or racc will produce the wrong result when the selector entry # LHS is generalized to any expression (LBRACE looks like a hash). Thus it is not possible to write # a selector with a single entry where the entry LHS is a hash. # The SELBRACE token is a LBRACE that follows a QMARK, and this is produced by the lexer with a lookback # Produces Array # selector_entries : selector_entry | SELBRACE selector_entry_list endcomma RBRACE { result = val[1] } # Produces Array selector_entry_list : selector_entry { result = [val[0]] } | selector_entry_list COMMA selector_entry { result = val[0].push val[2] } # Produces a Model::SelectorEntry # This FARROW wins over FARROW in Hash selector_entry : expression FARROW expression { result = Factory.MAP(val[0], val[2]) ; loc result, val[1] } #---RESOURCE # # Produces [Model::ResourceExpression, Model::ResourceDefaultsExpression] # The resource expression parses a generalized syntax and then selects the correct # resulting model based on the combinatoin of the LHS and what follows. # It also handled exported and virtual resources, and the class case # resource_expression : expression =LOW { result = val[0] } | at expression LBRACE resourceinstances endsemi RBRACE { result = case Factory.resource_shape(val[1]) when :resource, :class tmp = Factory.RESOURCE(Factory.fqn(token_text(val[1])), val[3]) tmp.form = val[0] tmp when :defaults error val[1], "A resource default can not be virtual or exported" when :override error val[1], "A resource override can not be virtual or exported" else error val[1], "Expression is not valid as a resource, resource-default, or resource-override" end loc result, val[1], val[4] } | at expression LBRACE attribute_operations endcomma RBRACE { result = case Factory.resource_shape(val[1]) when :resource, :class, :defaults, :override error val[1], "Defaults are not virtualizable" else error val[1], "Expression is not valid as a resource, resource-default, or resource-override" end } | expression LBRACE resourceinstances endsemi RBRACE { result = case Factory.resource_shape(val[0]) when :resource, :class Factory.RESOURCE(Factory.fqn(token_text(val[0])), val[2]) when :defaults error val[1], "A resource default can not specify a resource name" when :override error val[1], "A resource override does not allow override of name of resource" else error val[1], "Expression is not valid as a resource, resource-default, or resource-override" end loc result, val[0], val[4] } | expression LBRACE attribute_operations endcomma RBRACE { result = case Factory.resource_shape(val[0]) when :resource, :class # This catches deprecated syntax. # If the attribute operations does not include +>, then the found expression # is actually a LEFT followed by LITERAL_HASH # unless tmp = transform_resource_wo_title(val[0], val[2]) error val[1], "Syntax error resource body without title or hash with +>" end tmp when :defaults Factory.RESOURCE_DEFAULTS(val[0], val[2]) when :override # This was only done for override in original - TODO shuld it be here at all Factory.RESOURCE_OVERRIDE(val[0], val[2]) else error val[0], "Expression is not valid as a resource, resource-default, or resource-override" end loc result, val[0], val[4] } | at CLASS LBRACE resourceinstances endsemi RBRACE { result = Factory.RESOURCE(Factory.fqn(token_text(val[1])), val[3]) result.form = val[0] loc result, val[1], val[5] } | CLASS LBRACE resourceinstances endsemi RBRACE { result = Factory.RESOURCE(Factory.fqn(token_text(val[0])), val[2]) loc result, val[0], val[4] } resourceinst : expression title_colon attribute_operations endcomma { result = Factory.RESOURCE_BODY(val[0], val[2]) } title_colon : COLON =TITLE_COLON { result = val[0] } resourceinstances : resourceinst { result = [val[0]] } | resourceinstances SEMIC resourceinst { result = val[0].push val[2] } # Produces Symbol corresponding to resource form # at : AT { result = :virtual } | AT AT { result = :exported } | ATAT { result = :exported } #---COLLECTION # # A Collection is a predicate applied to a set of objects with an implied context (used variables are # attributes of the object. # i.e. this is equivalent for source.select(QUERY).apply(ATTRIBUTE_OPERATIONS) # # Produces Model::CollectExpression # collection_expression : expression collect_query LBRACE attribute_operations endcomma RBRACE { result = Factory.COLLECT(val[0], val[1], val[3]) loc result, val[0], val[5] } | expression collect_query =LOW { result = Factory.COLLECT(val[0], val[1], []) loc result, val[0], val[1] } collect_query : LCOLLECT optional_query RCOLLECT { result = Factory.VIRTUAL_QUERY(val[1]) ; loc result, val[0], val[2] } | LLCOLLECT optional_query RRCOLLECT { result = Factory.EXPORTED_QUERY(val[1]) ; loc result, val[0], val[2] } optional_query : nil | expression #---ATTRIBUTE OPERATIONS # # (Not an expression) # # Produces Array # attribute_operations : { result = [] } | attribute_operation { result = [val[0]] } | attribute_operations COMMA attribute_operation { result = val[0].push(val[2]) } # Produces String # QUESTION: Why is BOOLEAN valid as an attribute name? # attribute_name : NAME | keyword | BOOLEAN # In this version, illegal combinations are validated instead of producing syntax errors # (Can give nicer error message "+> is not applicable to...") # Produces Model::AttributeOperation # attribute_operation : attribute_name FARROW expression { result = Factory.ATTRIBUTE_OP(val[0][:value], :'=>', val[2]) loc result, val[0], val[2] } | attribute_name PARROW expression { result = Factory.ATTRIBUTE_OP(val[0][:value], :'+>', val[2]) loc result, val[0], val[2] } #---DEFINE # # Produces Model::Definition # definition_expression : DEFINE classname parameter_list LBRACE opt_statements RBRACE { result = add_definition(Factory.DEFINITION(classname(val[1][:value]), val[2], val[4])) loc result, val[0], val[5] # New lexer does not keep track of this, this is done in validation if @lexer.respond_to?(:'indefine=') @lexer.indefine = false end } #---HOSTCLASS # # Produces Model::HostClassDefinition # hostclass_expression : CLASS stacked_classname parameter_list classparent LBRACE opt_statements RBRACE { # Remove this class' name from the namestack as all nested classes have been parsed namepop result = add_definition(Factory.HOSTCLASS(classname(val[1][:value]), val[2], token_text(val[3]), val[5])) loc result, val[0], val[6] } # Record the classname so nested classes gets a fully qualified name at parse-time # This is a separate rule since racc does not support intermediate actions. # stacked_classname : classname { namestack(val[0][:value]) ; result = val[0] } opt_statements : statements | nil # Produces String, name or nil result classparent : nil | INHERITS classnameordefault { result = val[1] } # Produces String (this construct allows a class to be named "default" and to be referenced as # the parent class. # TODO: Investigate the validity # Produces a String (classname), or a token (DEFAULT). # classnameordefault : classname | DEFAULT #---NODE # # Produces Model::NodeDefinition # node_definition_expression : NODE hostnames nodeparent LBRACE statements RBRACE { result = add_definition(Factory.NODE(val[1], val[2], val[4])) loc result, val[0], val[5] } | NODE hostnames nodeparent LBRACE RBRACE { result = add_definition(Factory.NODE(val[1], val[2], nil)) loc result, val[0], val[4] } # Hostnames is not a list of names, it is a list of name matchers (including a Regexp). # (The old implementation had a special "Hostname" object with some minimal validation) # # Produces Array # hostnames : hostname { result = [result] } | hostnames COMMA hostname { result = val[0].push(val[2]) } # Produces a LiteralExpression (string, :default, or regexp) # String with interpolation is validated for better error message hostname : dotted_name { result = val[0] } | quotedtext { result = val[0] } | DEFAULT { result = Factory.literal(:default); loc result, val[0] } | regex dotted_name : name_or_number { result = Factory.literal(val[0][:value]); loc result, val[0] } | dotted_name DOT name_or_number { result = Factory.concat(val[0], '.', val[2][:value]); loc result, val[0], val[2] } name_or_number : NAME | NUMBER # Produces Expression, since hostname is an Expression nodeparent : nil | INHERITS hostname { result = val[1] } #---FUNCTION DEFINITION # #function_definition # For now the function word will just be reserved, in the future it will # produce a function definition # FUNCTION classname parameter_list LBRACE opt_statements RBRACE { # result = add_definition(Factory.FUNCTION(val[1][:value], val[2], val[4])) # loc result, val[0], val[5] # } #---NAMES AND PARAMETERS COMMON TO SEVERAL RULES # Produces String # TODO: The error that "class" is not a valid classname is bad - classname rule is also used for other things classname : NAME { result = val[0] } | CLASS { error val[0], "'class' is not a valid classname" } # Produces Array parameter_list : nil { result = [] } | LPAREN RPAREN { result = [] } | LPAREN parameters endcomma RPAREN { result = val[1] } # Produces Array parameters : parameter { result = [val[0]] } | parameters COMMA parameter { result = val[0].push(val[2]) } # Produces Model::Parameter parameter + : untyped_parameter + | typed_parameter + +untyped_parameter + : regular_parameter + | splat_parameter + +regular_parameter : VARIABLE EQUALS expression { result = Factory.PARAM(val[0][:value], val[2]) ; loc result, val[0] } | VARIABLE { result = Factory.PARAM(val[0][:value]); loc result, val[0] } +splat_parameter + : TIMES regular_parameter { result = val[1]; val[1].captures_rest() } + +typed_parameter + : parameter_type untyped_parameter { val[1].type_expr(val[0]) ; result = val[1] } + +parameter_type + : type { result = val[0] } + | type LBRACK expressions RBRACK { result = val[0][*val[2]] ; loc result, val[0], val[3] } + #--RESTRICTED EXPRESSIONS # i.e. where one could have expected an expression, but the set is limited ## What is allowed RHS of match operators (see expression) #match_rvalue # : regex # | text_or_name #--VARIABLE # variable : VARIABLE { result = Factory.fqn(val[0][:value]).var ; loc result, val[0] } #---RESERVED WORDS # reserved_word : FUNCTION { result = Factory.RESERVED(val[0][:value]) ; loc result, val[0] } | PRIVATE { result = Factory.RESERVED(val[0][:value]) ; loc result, val[0] } | TYPE { result = Factory.RESERVED(val[0][:value]) ; loc result, val[0] } | ATTR { result = Factory.RESERVED(val[0][:value]) ; loc result, val[0] } #---LITERALS (dynamic and static) # array : LBRACK expressions RBRACK { result = Factory.LIST(val[1]); loc result, val[0], val[2] } | LBRACK expressions COMMA RBRACK { result = Factory.LIST(val[1]); loc result, val[0], val[3] } | LBRACK RBRACK { result = Factory.literal([]) ; loc result, val[0] } | LISTSTART expressions RBRACK { result = Factory.LIST(val[1]); loc result, val[0], val[2] } | LISTSTART expressions COMMA RBRACK { result = Factory.LIST(val[1]); loc result, val[0], val[3] } | LISTSTART RBRACK { result = Factory.literal([]) ; loc result, val[0] } hash : LBRACE hashpairs RBRACE { result = Factory.HASH(val[1]); loc result, val[0], val[2] } | LBRACE hashpairs COMMA RBRACE { result = Factory.HASH(val[1]); loc result, val[0], val[3] } | LBRACE RBRACE { result = Factory.literal({}) ; loc result, val[0], val[3] } hashpairs : hashpair { result = [val[0]] } | hashpairs COMMA hashpair { result = val[0].push val[2] } hashpair : expression FARROW expression { result = Factory.KEY_ENTRY(val[0], val[2]); loc result, val[1] } quotedtext : string | dq_string | heredoc string : STRING { result = Factory.literal(val[0][:value]) ; loc result, val[0] } | WORD { result = Factory.literal(val[0][:value]) ; loc result, val[0] } dq_string : dqpre dqrval { result = Factory.string(val[0], *val[1]) ; loc result, val[0], val[1][-1] } dqpre : DQPRE { result = Factory.literal(val[0][:value]); loc result, val[0] } dqpost : DQPOST { result = Factory.literal(val[0][:value]); loc result, val[0] } dqmid : DQMID { result = Factory.literal(val[0][:value]); loc result, val[0] } dqrval : text_expression dqtail { result = [val[0]] + val[1] } text_expression : expression { result = Factory.TEXT(val[0]) } dqtail : dqpost { result = [val[0]] } | dqmid dqrval { result = [val[0]] + val[1] } heredoc : HEREDOC sublocated_text { result = Factory.HEREDOC(val[0][:value], val[1]); loc result, val[0] } sublocated_text : SUBLOCATE string { result = Factory.SUBLOCATE(val[0], val[1]); loc result, val[0] } | SUBLOCATE dq_string { result = Factory.SUBLOCATE(val[0], val[1]); loc result, val[0] } epp_expression : EPP_START epp_parameters_list statements { result = Factory.EPP(val[1], val[2]); loc result, val[0] } epp_parameters_list : =LOW{ result = nil } | PIPE PIPE { result = [] } | PIPE parameters endcomma PIPE { result = val[1] } epp_render_expression : RENDER_STRING { result = Factory.RENDER_STRING(val[0][:value]); loc result, val[0] } | RENDER_EXPR expression epp_end { result = Factory.RENDER_EXPR(val[1]); loc result, val[0], val[2] } | RENDER_EXPR LBRACE statements RBRACE epp_end { result = Factory.RENDER_EXPR(Factory.block_or_expression(*val[2])); loc result, val[0], val[4] } epp_end : EPP_END | EPP_END_TRIM number : NUMBER { result = Factory.NUMBER(val[0][:value]) ; loc result, val[0] } name : NAME { result = Factory.QNAME_OR_NUMBER(val[0][:value]) ; loc result, val[0] } type : CLASSREF { result = Factory.QREF(val[0][:value]) ; loc result, val[0] } undef : UNDEF { result = Factory.literal(:undef); loc result, val[0] } default : DEFAULT { result = Factory.literal(:default); loc result, val[0] } # Assumes lexer produces a Boolean value for booleans, or this will go wrong and produce a literal string # with the text 'true'. #TODO: could be changed to a specific boolean literal factory method to prevent this possible glitch. boolean : BOOLEAN { result = Factory.literal(val[0][:value]) ; loc result, val[0] } regex : REGEX { result = Factory.literal(val[0][:value]); loc result, val[0] } #---MARKERS, SPECIAL TOKENS, SYNTACTIC SUGAR, etc. endcomma : # | COMMA { result = nil } endsemi : # | SEMIC keyword : AND | CASE | CLASS | DEFAULT | DEFINE | ELSE | ELSIF | IF | IN | INHERITS | NODE | OR | UNDEF | UNLESS nil : { result = nil} end ---- header ---- require 'puppet' require 'puppet/pops' module Puppet class ParseError < Puppet::Error; end class ImportError < Racc::ParseError; end class AlreadyImportedError < ImportError; end end ---- inner ---- # Make emacs happy # Local Variables: # mode: ruby # End: diff --git a/lib/puppet/pops/parser/eparser.rb b/lib/puppet/pops/parser/eparser.rb index 0baa81078..509f9c852 100644 --- a/lib/puppet/pops/parser/eparser.rb +++ b/lib/puppet/pops/parser/eparser.rb @@ -1,2715 +1,2776 @@ # # DO NOT MODIFY!!!! # This file is automatically generated by Racc 1.4.9 # from Racc grammer file "". # require 'racc/parser.rb' require 'puppet' require 'puppet/pops' module Puppet class ParseError < Puppet::Error; end class ImportError < Racc::ParseError; end class AlreadyImportedError < ImportError; end end module Puppet module Pops module Parser class Parser < Racc::Parser -module_eval(<<'...end egrammar.ra/module_eval...', 'egrammar.ra', 779) +module_eval(<<'...end egrammar.ra/module_eval...', 'egrammar.ra', 797) # Make emacs happy # Local Variables: # mode: ruby # End: ...end egrammar.ra/module_eval... ##### State transition tables begin ### clist = [ -'63,66,275,-134,64,57,248,59,285,86,-225,-132,323,144,-234,255,308,260', -'259,275,254,234,234,109,15,113,234,108,363,134,43,250,50,133,52,47,373', -'51,76,72,369,45,75,48,49,-134,145,73,14,112,286,74,-225,-132,12,13,-234', -'268,130,93,92,231,77,134,88,89,241,133,44,274,338,87,71,67,275,69,70', -'68,270,271,53,54,56,55,63,66,134,58,64,57,133,59,405,94,134,253,306', -'86,133,134,252,63,66,133,257,64,134,258,15,284,133,109,341,113,43,108', -'50,282,52,47,134,51,76,72,133,45,75,48,49,343,81,73,14,345,358,74,357', -'112,12,13,82,84,83,85,63,66,77,298,64,57,299,59,44,358,300,357,71,67', -'284,69,70,251,350,351,53,54,56,55,15,352,234,58,245,355,43,122,50,359', -'52,47,361,51,76,72,79,45,75,48,49,245,230,73,14,282,284,74,282,325,12', -'13,370,306,294,216,293,217,77,291,219,295,289,288,44,290,292,306,71', -'67,221,69,70,220,215,296,53,54,56,55,63,66,197,58,64,57,81,59,403,86', -'164,218,297,380,307,161,382,159,284,282,135,385,122,109,15,113,123,108', -'322,122,43,389,50,361,52,47,391,51,76,72,392,45,75,48,49,393,394,73', -'14,112,395,74,119,397,12,13,398,399,326,93,92,81,77,78,88,89,406,407', -'44,408,409,87,71,67,,69,70,,,,53,54,56,55,63,66,,58,64,57,,59,401,94', -',,,86,,,,,,,,,,,15,,,109,,113,43,108,50,,52,47,,51,76,72,,45,75,48,49', -',,73,14,,,74,,112,12,13,,,,,63,66,77,,64,57,,59,44,,,,71,67,,69,70,', -',,53,54,56,55,15,,,58,,,43,,50,,52,115,,51,76,72,,45,75,,,,,73,14,,', -'74,,,12,13,,,,,63,66,77,,64,57,,59,44,,,,71,67,,69,70,,,,53,54,56,55', -'15,,,58,,,43,,50,,52,115,,51,76,72,,45,75,,,,,73,14,,,74,,,12,13,,,', -',63,66,77,,64,57,,59,44,,,,71,67,,69,70,,,,53,54,56,55,15,,,58,,,43', -',50,,52,115,,51,76,72,,45,75,,,,,73,14,,,74,,,12,13,,,,,63,66,77,,64', +'63,66,282,292,64,57,333,59,-233,86,-134,-132,238,144,-242,238,318,282', +'63,66,277,278,64,109,15,113,374,108,73,248,43,396,50,239,52,47,239,51', +'76,72,252,45,75,48,49,293,145,73,14,112,-233,74,-134,-132,12,13,-242', +'134,130,93,92,133,77,134,88,89,307,133,44,255,384,87,71,67,262,69,70', +'68,351,261,53,54,56,55,63,66,134,58,64,57,133,59,417,94,257,260,238', +'86,398,238,259,281,264,282,306,265,282,353,15,73,230,109,73,113,43,108', +'50,239,52,47,239,51,76,72,355,45,75,48,49,134,305,73,14,133,231,74,313', +'112,12,13,238,267,266,134,63,66,77,133,64,57,134,59,44,73,133,252,71', +'67,134,69,70,239,133,291,53,54,56,55,15,258,369,58,368,360,43,275,50', +'361,52,47,362,51,76,72,79,45,75,48,49,122,291,73,14,221,369,74,368,366', +'12,13,336,370,301,216,300,217,77,298,219,302,296,295,44,297,299,372', +'71,67,335,69,70,220,215,303,53,54,56,55,63,66,197,58,64,57,289,59,415', +'86,291,218,304,82,84,83,85,289,380,381,332,313,81,109,15,113,164,108', +'161,159,43,391,50,289,52,47,289,51,76,72,393,45,75,48,49,313,291,73', +'14,112,135,74,81,348,12,13,122,123,317,93,92,314,77,122,88,89,401,372', +'44,403,404,87,71,67,405,69,70,406,407,119,53,54,56,55,63,66,409,58,64', +'57,410,59,413,94,411,238,81,86,78,418,419,420,421,,,,,,15,,,109,,113', +'43,108,50,,52,47,,51,76,72,,45,75,48,49,,,73,14,,,74,,112,12,13,,,,', +'63,66,77,,64,57,,59,44,,,,71,67,,69,70,,,,53,54,56,55,15,,,58,,,43,', +'50,,52,115,,51,76,72,,45,75,,,,,73,14,,,74,,,12,13,,,,,63,66,77,,64', '57,,59,44,,,,71,67,,69,70,,,,53,54,56,55,15,,,58,,,43,,50,,52,115,,51', '76,72,,45,75,,,,,73,14,,,74,,,12,13,,,,,63,66,77,,64,57,,59,44,,,,71', '67,,69,70,,,,53,54,56,55,15,,,58,,,43,,50,,52,115,,51,76,72,,45,75,', ',,,73,14,,,74,,,12,13,,,,,63,66,77,,64,57,,59,44,,,,71,67,,69,70,,,', -'53,54,56,55,15,,,58,,,43,,50,,52,47,,51,76,72,,45,75,48,49,,,73,14,', -',74,,,12,13,,,,,63,66,77,,64,57,,59,44,,,,71,67,,69,70,,,,53,54,56,55', -'15,,,58,,,43,,50,,52,115,,51,76,72,,45,75,,,,,73,14,,,74,,,12,13,,,', -',63,66,77,,64,57,,59,44,,,,71,67,,69,70,,,,53,54,56,55,15,,,58,,,43', -',50,,52,115,,51,76,72,,45,75,,,,,73,14,,,74,,,12,13,,,,,63,66,77,,64', +'53,54,56,55,15,,,58,,,43,,50,,52,115,,51,76,72,,45,75,,,,,73,14,,,74', +',,12,13,,,,,63,66,77,,64,57,,59,44,,,,71,67,,69,70,,,,53,54,56,55,15', +',,58,,,43,,50,,52,115,,51,76,72,,45,75,,,,,73,14,,,74,,,12,13,,,,,63', +'66,77,,64,57,,59,44,,,,71,67,,69,70,,,,53,54,56,55,15,,,58,,,43,,50', +',52,47,,51,76,72,,45,75,48,49,,,73,14,,,74,,,12,13,,,,,63,66,77,,64', '57,,59,44,,,,71,67,,69,70,,,,53,54,56,55,15,,,58,,,43,,50,,52,115,,51', '76,72,,45,75,,,,,73,14,,,74,,,12,13,,,,,63,66,77,,64,57,,59,44,,,,71', '67,,69,70,,,,53,54,56,55,15,,,58,,,43,,50,,52,115,,51,76,72,,45,75,', ',,,73,14,,,74,,,12,13,,,,,63,66,77,,64,57,,59,44,,,,71,67,,69,70,,,', '53,54,56,55,15,,,58,,,43,,50,,52,115,,51,76,72,,45,75,,,,,73,14,,,74', ',,12,13,,,,,63,66,77,,64,57,,59,44,,,,71,67,,69,70,,,,53,54,56,55,15', -',,58,,,43,,50,,52,129,,51,76,72,,45,75,,,,,73,14,,,74,,,12,13,,,,,63', -'66,77,,64,57,,59,44,,,,71,67,,69,70,,,,53,54,56,55,15,,,58,,,43,,50', -',52,115,,51,76,72,,45,75,,,,,73,14,,,74,,,12,13,,,,,63,66,77,,64,57', -',59,44,,,,71,67,,69,70,,,,53,54,56,55,15,,,58,,,43,,50,,52,115,,51,76', -'72,,45,75,,,,,73,14,,,74,,,12,13,,,,,63,66,77,,64,57,,59,44,,,,71,67', -',69,70,,,,53,54,56,55,15,,,58,,,43,,50,,52,115,,51,76,72,,45,75,,,,', -'73,14,,,74,,,12,13,,,,,63,66,77,,64,57,,59,44,,,,71,67,,69,70,,,,53', -'54,56,55,15,,,58,,,199,216,210,217,52,211,219,212,208,206,,201,214,', -',,,73,14,220,215,213,,,12,13,,,,,,,77,,,,,218,200,,,,71,67,,69,70,,', -',53,54,56,55,63,66,,58,64,57,148,59,,86,,,,,,,,,,,,,,109,15,113,,108', -',,43,,50,,52,115,,51,76,72,,45,75,,,,,73,14,112,,74,,,12,13,,,,,63,66', -'77,,64,57,150,59,44,,,87,71,67,,69,70,,,,53,54,56,55,15,,,58,,,43,,50', -',52,115,,51,76,72,,45,75,,,,,73,14,,,74,,86,12,13,,,,,,,77,,,,,109,44', -'113,,108,71,67,,69,70,,,,53,54,56,55,63,66,,58,64,57,,59,153,112,,,', -',,,,,,,,,,,15,,,,,,43,87,50,,52,115,,51,76,72,,45,75,,,,,73,14,,,74', -',,12,13,,,,,63,66,77,,64,57,,59,44,,,,71,67,,69,70,,,,53,54,56,55,15', ',,58,,,43,,50,,52,115,,51,76,72,,45,75,,,,,73,14,,,74,,,12,13,,,,,63', '66,77,,64,57,,59,44,,,,71,67,,69,70,,,,53,54,56,55,15,,,58,,,43,,50', ',52,115,,51,76,72,,45,75,,,,,73,14,,,74,,,12,13,,,,,63,66,77,,64,57', -',59,44,,,,71,67,,69,70,,,,53,54,56,55,15,,,58,,,43,,50,,52,115,,51,76', -'72,,45,75,,,,,73,14,,,74,,,12,13,,,,,63,66,77,,64,57,,163,44,,,,71,67', +',59,44,,,,71,67,,69,70,,,,53,54,56,55,15,,,58,,,43,,50,,52,129,,51,76', +'72,,45,75,,,,,73,14,,,74,,,12,13,,,,,63,66,77,,64,57,,59,44,,,,71,67', ',69,70,,,,53,54,56,55,15,,,58,,,43,,50,,52,115,,51,76,72,,45,75,,,,', '73,14,,,74,,,12,13,,,,,63,66,77,,64,57,,59,44,,,,71,67,,69,70,,,,53', '54,56,55,15,,,58,,,43,,50,,52,115,,51,76,72,,45,75,,,,,73,14,,,74,,', '12,13,,,,,63,66,77,,64,57,,59,44,,,,71,67,,69,70,,,,53,54,56,55,15,', -',58,,,43,,50,,52,47,,51,76,72,,45,75,48,49,,,73,14,,,74,,86,12,13,,', -',,,,77,,,,,109,44,113,,108,71,67,,69,70,,,,53,54,56,55,63,66,,58,64', -'57,,59,153,112,,,,86,,,,,,,,,,,15,,,109,,113,43,108,50,,52,47,,51,76', -'72,,45,75,48,49,,,73,14,,,74,,112,12,13,,,,,63,66,77,,64,57,,59,44,', -',,71,67,,69,70,,,,53,54,56,55,15,,,58,,,43,,50,,52,47,,51,76,72,,45', -'75,48,49,,,73,14,,,74,,,12,13,,,,,63,66,77,,64,57,,59,44,,,,71,67,,69', -'70,,,,53,54,56,55,15,,,58,,,43,,50,,52,47,,51,76,72,,45,75,48,49,,,73', -'14,,,74,,,12,13,,,,,63,66,77,,64,57,,59,44,,,,71,67,,69,70,,,,53,54', -'56,55,15,,,58,,,43,,50,,52,47,,51,76,72,,45,75,48,49,,,73,14,,,74,,', -'12,13,,,,,63,66,77,,64,57,,59,44,,,,71,67,,69,70,,,,53,54,56,55,15,', -',58,,,43,,50,,52,47,,51,76,72,,45,75,48,49,,,73,14,,,74,,,12,13,,,,', -'63,66,77,,64,57,,59,44,,,,71,67,,69,70,,,,53,54,56,55,15,,,58,,,43,', -'50,,52,47,,51,76,72,,45,75,48,49,,,73,14,,,74,,,12,13,,,,,63,66,77,', -'64,57,,59,44,,,,71,67,,69,70,,,,53,54,56,55,15,,,58,,,43,,50,,52,115', +',58,,,43,,50,,52,115,,51,76,72,,45,75,,,,,73,14,,,74,,86,12,13,,,,,', +',77,,,,,109,44,113,,108,71,67,,69,70,,,,53,54,56,55,63,66,,58,64,57', +',59,311,112,,,,,,,,,,,,,,,15,,,,,,43,87,50,,52,47,,51,76,72,,45,75,48', +'49,,,73,14,,,74,,,12,13,,,,,63,66,77,,64,57,148,59,44,,,,71,67,,69,70', +',,,53,54,56,55,15,,,58,,,43,,50,,52,115,,51,76,72,,45,75,,,,,73,14,', +',74,,,12,13,,,,,63,66,77,,64,57,150,59,44,,,,71,67,,69,70,,,,53,54,56', +'55,15,,,58,,,43,,50,,52,115,,51,76,72,,45,75,,,,,73,14,,,74,,86,12,13', +',,,,,,77,,,,,109,44,113,,108,71,67,,69,70,,,,53,54,56,55,63,66,,58,64', +'57,,59,153,112,,,,,,,,,,,,,,,15,,,,,,43,87,50,,52,115,,51,76,72,,45', +'75,,,,,73,14,,,74,,,12,13,,,,,63,66,77,,64,57,,59,44,,,,71,67,,69,70', +',,,53,54,56,55,15,,,58,,,43,,50,,52,115,,51,76,72,,45,75,,,,,73,14,', +',74,,,12,13,,,301,216,300,217,77,298,219,302,296,295,44,297,299,,71', +'67,,69,70,220,215,303,53,54,56,55,63,66,,58,64,57,,59,320,86,,218,304', +',,,,,,,,,,109,15,113,,108,,,43,,50,,52,47,,51,76,72,,45,75,48,49,,,73', +'14,112,,74,,,12,13,,90,91,93,92,,77,,88,89,,,44,,,87,71,67,,69,70,,', +',53,54,56,55,63,66,,58,64,57,,59,153,94,,,,86,,,,,,,,,,,15,,,109,,113', +'43,108,50,,52,47,,51,76,72,,45,75,48,49,,,73,14,,,74,,112,12,13,,,,', +'63,66,77,,64,57,,163,44,,,,71,67,,69,70,,,,53,54,56,55,15,,,58,,,43', +',50,,52,115,,51,76,72,,45,75,,,,,73,14,,,74,,86,12,13,,,,,,,77,,,,,109', +'44,113,,108,71,67,,69,70,,,,53,54,56,55,63,66,,58,64,57,,59,390,112', +',,,,,,,,,,,,,,15,,,,,,43,,50,,52,47,,51,76,72,,45,75,48,49,,,73,14,', +',74,,,12,13,,,,,63,66,77,,64,57,342,59,44,,,,71,67,,69,70,,,,53,54,56', +'55,15,,,58,,,43,,50,,52,115,,51,76,72,,45,75,,,,,73,14,,,74,,,12,13', +',,,,63,66,77,,64,57,,59,44,,,,71,67,,69,70,,,,53,54,56,55,15,,,58,,', +'43,,50,,52,47,,51,76,72,,45,75,48,49,,,73,14,,,74,,,12,13,,,,,63,66', +'77,,64,57,,59,44,,,,71,67,,69,70,,,,53,54,56,55,15,,,58,,,43,,50,,52', +'47,,51,76,72,,45,75,48,49,,,73,14,,,74,,,12,13,,,,,63,66,77,,64,57,', +'59,44,,,,71,67,,69,70,,,,53,54,56,55,15,,,58,,,43,,50,,52,47,,51,76', +'72,,45,75,48,49,,,73,14,,,74,,,12,13,,,,,63,66,77,,64,57,,59,44,,,,71', +'67,,69,70,,,,53,54,56,55,15,,,58,,,43,,50,,52,47,,51,76,72,,45,75,48', +'49,,,73,14,,,74,,,12,13,,,,,63,66,77,,64,57,,59,44,,,,71,67,,69,70,', +',,53,54,56,55,15,,,58,,,43,,50,,52,47,,51,76,72,,45,75,48,49,,,73,14', +',,74,,,12,13,,,,,63,66,77,,64,57,,59,44,,,,71,67,,69,70,,,,53,54,56', +'55,15,,,58,,,43,,50,,52,47,,51,76,72,,45,75,48,49,,,73,14,,,74,,,12', +'13,,,,,63,66,77,,64,57,,59,44,,,,71,67,,69,70,,,,53,54,56,55,15,,,58', +',,43,,50,,52,115,,51,76,72,,45,75,,,,,73,14,,,74,,,12,13,,,,,63,66,77', +',64,57,,59,44,,,,71,67,,69,70,,,,53,54,56,55,15,,,58,,,43,,50,,52,115', ',51,76,72,,45,75,,,,,73,14,,,74,,,12,13,,,,,63,66,77,,64,57,,59,44,', ',,71,67,,69,70,,,,53,54,56,55,15,,,58,,,43,,50,,52,115,,51,76,72,,45', '75,,,,,73,14,,,74,,,12,13,,,,,63,66,77,,64,57,,59,44,,,,71,67,,69,70', ',,,53,54,56,55,15,,,58,,,43,,50,,52,115,,51,76,72,,45,75,,,,,73,14,', ',74,,,12,13,,,,,63,66,77,,64,57,,59,44,,,,71,67,,69,70,,,,53,54,56,55', '15,,,58,,,43,,50,,52,115,,51,76,72,,45,75,,,,,73,14,,,74,,,12,13,,,', ',63,66,77,,64,57,,59,44,,,,71,67,,69,70,,,,53,54,56,55,15,,,58,,,43', ',50,,52,115,,51,76,72,,45,75,,,,,73,14,,,74,,,12,13,,,,,63,66,77,,64', '57,,59,44,,,,71,67,,69,70,,,,53,54,56,55,15,,,58,,,43,,50,,52,115,,51', '76,72,,45,75,,,,,73,14,,,74,,,12,13,,,,,63,66,77,,64,57,,59,44,,,,71', '67,,69,70,,,,53,54,56,55,15,,,58,,,43,,50,,52,115,,51,76,72,,45,75,', ',,,73,14,,,74,,,12,13,,,,,63,66,77,,64,57,,59,44,,,,71,67,,69,70,,,', '53,54,56,55,15,,,58,,,43,,50,,52,115,,51,76,72,,45,75,,,,,73,14,,,74', ',,12,13,,,,,63,66,77,,64,57,,59,44,,,,71,67,,69,70,,,,53,54,56,55,15', ',,58,,,43,,50,,52,115,,51,76,72,,45,75,,,,,73,14,,,74,,,12,13,,,,,63', '66,77,,64,57,,59,44,,,,71,67,,69,70,,,,53,54,56,55,15,,,58,,,43,,50', ',52,115,,51,76,72,,45,75,,,,,73,14,,,74,,,12,13,,,,,63,66,77,,64,57', ',59,44,,,,71,67,,69,70,,,,53,54,56,55,15,,,58,,,43,,50,,52,115,,51,76', '72,,45,75,,,,,73,14,,,74,,,12,13,,,,,63,66,77,,64,57,,59,44,,,,71,67', ',69,70,,,,53,54,56,55,15,,,58,,,43,,50,,52,115,,51,76,72,,45,75,,,,', '73,14,,,74,,,12,13,,,,,63,66,77,,64,57,,59,44,,,,71,67,,69,70,,,,53', '54,56,55,15,,,58,,,43,,50,,52,115,,51,76,72,,45,75,,,,,73,14,,,74,,', '12,13,,,,,63,66,77,,64,57,,59,44,,,,71,67,,69,70,,,,53,54,56,55,15,', ',58,,,43,,50,,52,115,,51,76,72,,45,75,,,,,73,14,,,74,,,12,13,,,,,63', '66,77,,64,57,,59,44,,,,71,67,,69,70,,,,53,54,56,55,15,,,58,,,43,,50', ',52,115,,51,76,72,,45,75,,,,,73,14,,,74,,,12,13,,,,,63,66,77,,64,57', ',59,44,,,,71,67,,69,70,,,,53,54,56,55,15,,,58,,,43,,50,,52,115,,51,76', '72,,45,75,,,,,73,14,,,74,,,12,13,,,,,63,66,77,,64,57,,59,44,,,,71,67', ',69,70,,,,53,54,56,55,15,,,58,,,43,,50,,52,115,,51,76,72,,45,75,,,,', '73,14,,,74,,,12,13,,,,,63,66,77,,64,57,,59,44,,,,71,67,,69,70,,,,53', '54,56,55,15,,,58,,,43,,50,,52,115,,51,76,72,,45,75,,,,,73,14,,,74,,', '12,13,,,,,63,66,77,,64,57,,59,44,,,,71,67,,69,70,,,,53,54,56,55,15,', ',58,,,43,,50,,52,115,,51,76,72,,45,75,,,,,73,14,,,74,,,12,13,,,,,63', '66,77,,64,57,,59,44,,,,71,67,,69,70,,,,53,54,56,55,15,,,58,,,43,,50', ',52,115,,51,76,72,,45,75,,,,,73,14,,,74,,,12,13,,,,,63,66,77,,64,57', ',59,44,,,,71,67,,69,70,,,,53,54,56,55,15,,,58,,,43,,50,,52,115,,51,76', '72,,45,75,,,,,73,14,,,74,,,12,13,,,,,63,66,77,,64,57,,59,44,,,,71,67', ',69,70,,,,53,54,56,55,15,,,58,,,43,,50,,52,115,,51,76,72,,45,75,,,,', -'73,14,,,74,,,12,13,,,,,63,66,77,,64,57,,59,44,,,,71,67,,69,70,,,,53', -'54,56,55,15,,,58,,,43,,50,,52,115,,51,76,72,,45,75,,,,,73,14,,,74,,', -'12,13,,,294,216,293,217,77,291,219,295,289,288,44,290,292,196,71,67', -',69,70,220,215,296,53,54,56,55,63,66,,58,64,57,,59,379,,,218,297,,,', -',,,,,,,,15,,,,,,43,,50,,52,47,,51,76,72,,45,75,48,49,,,73,14,,,74,,', -'12,13,,,,,63,66,77,,64,57,,59,44,,,,71,67,,69,70,,,,53,54,56,55,15,', -',58,,,199,216,210,217,52,211,219,212,208,206,,201,214,,,,,73,14,220', +'73,14,,,74,,,12,13,,,301,216,300,217,77,298,219,302,296,295,44,297,299', +'196,71,67,,69,70,220,215,303,53,54,56,55,63,66,,58,64,57,,59,375,,,218', +'304,,,,,,,,,,,,15,,,,,,43,,50,,52,115,,51,76,72,,45,75,,,,,73,14,,,74', +',,12,13,,,,,63,66,77,,64,57,,59,44,,,,71,67,,69,70,,,,53,54,56,55,15', +',,58,,,199,216,210,217,52,211,219,212,208,206,,201,214,,,,,73,14,220', '215,213,,,12,13,,,,,,,77,,,,,218,200,,,,71,67,,69,70,,,,53,54,56,55', -'63,66,,58,64,57,,59,294,216,293,217,,291,219,295,289,288,,290,292,,', -',15,,,220,215,296,43,,50,,52,47,,51,76,72,,45,75,48,49,218,297,73,14', +'63,66,,58,64,57,,59,,,,,,,,,,,,,,,,,15,,,,,,43,,50,,52,115,,51,76,72', +',45,75,,,,,73,14,,,74,,,12,13,,,,,63,66,77,,64,57,,59,44,,,,71,67,,69', +'70,,,,53,54,56,55,15,,,58,,,43,,50,,52,115,,51,76,72,,45,75,,,,,73,14', ',,74,,,12,13,,,,,63,66,77,,64,57,,59,44,,,,71,67,,69,70,,,,53,54,56', '55,15,,,58,,,43,,50,,52,115,,51,76,72,,45,75,,,,,73,14,,,74,,,12,13', ',,,,63,66,77,,64,57,,59,44,,,,71,67,,69,70,,,,53,54,56,55,15,,,58,,', '43,,50,,52,115,,51,76,72,,45,75,,,,,73,14,,,74,,,12,13,,,,,63,66,77', ',64,57,,59,44,,,,71,67,,69,70,,,,53,54,56,55,15,,,58,,,43,,50,,52,115', -',51,76,72,,45,75,,,,,73,14,,,74,,,12,13,,,,,,,77,,,,,,44,,,,71,67,,69', -'70,,,,53,54,56,55,63,66,,58,64,57,,59,312,86,,,,,,,,,,,,,,109,15,113', -',108,,,43,,50,,52,47,,51,76,72,,45,75,48,49,,,73,14,112,,74,,,12,13', -',90,91,93,92,,77,,88,89,,,44,,,87,71,67,,69,70,,,,53,54,56,55,63,66', -',58,64,57,,59,310,94,,,,,,,,,,,,,,,15,,,,,,43,,50,,52,47,,51,76,72,', -'45,75,48,49,,,73,14,,,74,,,12,13,,,,,63,66,77,,64,57,331,59,44,,,,71', -'67,,69,70,,,,53,54,56,55,15,,,58,,,43,,50,,52,115,,51,76,72,,45,75,', -',,,73,14,,,74,,,12,13,,,,,63,66,77,,64,57,,59,44,,,,71,67,,69,70,,,', -'53,54,56,55,15,,,58,,,43,,50,,52,115,,51,76,72,,45,75,,,,,73,14,,,74', -',,12,13,,,,,63,66,77,,64,57,,59,44,,,,71,67,,69,70,,,,53,54,56,55,15', -'228,,58,,,43,,50,,52,115,,51,76,72,,45,75,,,,,73,14,,,74,,,12,13,,,', -',,,77,,,,,,44,,,,71,67,,69,70,,,,53,54,56,55,63,66,,58,64,57,,59,364', -'86,,,,,,,,,,,,,,109,15,113,,108,,,43,,50,,52,115,,51,76,72,,45,75,,', -',,73,14,112,,74,,,12,13,,90,91,93,92,,77,,88,89,,,44,,,87,71,67,,69', -'70,,,,53,54,56,55,63,66,,58,64,57,,59,304,94,,,,,,,,,,,,,,,15,,,,,,43', -',50,,52,47,,51,76,72,,45,75,48,49,,,73,14,,,74,,,12,13,,,,,63,66,77', -',64,57,,59,44,,,,71,67,,69,70,,,,53,54,56,55,15,236,,58,,,43,,50,,52', +',51,76,72,,45,75,,,,,73,14,,,74,,,12,13,,,,,63,66,77,,64,57,,59,44,', +',,71,67,,69,70,,,,53,54,56,55,15,,,58,,,43,,50,,52,115,,51,76,72,,45', +'75,,,,,73,14,,,74,,,12,13,,,,,63,66,77,,64,57,,59,44,,,,71,67,,69,70', +',,,53,54,56,55,15,,,58,,,43,,50,,52,115,,51,76,72,,45,75,,,,,73,14,', +',74,,,12,13,,,,,,,77,,,,,,44,,,,71,67,,69,70,,,,53,54,56,55,63,66,,58', +'64,57,,59,322,,,,,,,,,,,,,,,,15,,,,,,43,,50,,52,47,,51,76,72,,45,75', +'48,49,,,73,14,,,74,,,12,13,,,,,63,66,77,,64,57,,59,44,,,,71,67,,69,70', +',,,53,54,56,55,15,228,,58,,,43,,50,,52,115,,51,76,72,,45,75,,,,,73,14', +',,74,,,12,13,,,,,63,66,77,,64,57,,59,44,,,,71,67,,69,70,,,,53,54,56', +'55,15,,,58,,,43,,50,,52,115,,51,76,72,,45,75,,,,,73,14,,,74,,,12,13', +',,,,63,66,77,,64,57,,59,44,,,,71,67,,69,70,,,,53,54,56,55,15,,,58,,', +'43,,50,,52,115,,51,76,72,,45,75,,,,,73,14,,,74,,,12,13,,,,,63,66,77', +',64,57,,59,44,,,,71,67,,69,70,,,,53,54,56,55,15,243,,58,,,43,,50,,52', '115,,51,76,72,,45,75,,,,,73,14,,,74,,,12,13,,,,,63,66,77,,64,57,,59', '44,,,,71,67,,69,70,,,,53,54,56,55,15,,,58,,,43,,50,,52,47,,51,76,72', ',45,75,48,49,,,73,14,,,74,,,12,13,,,,,63,66,77,,64,57,,59,44,,,,71,67', ',69,70,,,,53,54,56,55,15,,,58,,,43,,50,,52,115,,51,76,72,,45,75,,,,', '73,14,,,74,,,12,13,,,,,63,66,77,,64,57,,59,44,,,,71,67,,69,70,,,,53', -'54,56,55,15,,,58,,,43,,50,,52,115,,51,76,72,,45,75,,,,,73,14,,,74,,', -'12,13,,,,,63,66,77,,64,57,,59,44,,,,71,67,,69,70,,,,53,54,56,55,15,', -',58,,,43,,50,,52,115,,51,76,72,,45,75,,,,,73,14,,,74,,,12,13,,,,,63', -'66,77,,64,57,332,59,44,,,,71,67,,69,70,,,,53,54,56,55,15,,,58,,,43,', -'50,,52,115,,51,76,72,,45,75,,,,,73,14,,,74,,,12,13,,,,,63,66,77,,64', -'57,,59,44,,,,71,67,,69,70,,,,53,54,56,55,15,,,58,,,43,,50,,52,115,,51', -'76,72,,45,75,,,,,73,14,,,74,,,12,13,,,,,63,66,77,,64,57,,59,44,,,,71', -'67,,69,70,,,,53,54,56,55,15,,,58,,,43,,50,,52,115,,51,76,72,,45,75,', -',,,73,14,,,74,,,12,13,,,,,63,66,77,,64,57,,59,44,,,,71,67,,69,70,,,', -'53,54,56,55,15,,,58,,,43,,50,,52,115,,51,76,72,,45,75,,,,,73,14,,,74', -',,12,13,,,,,,,77,,,,,,44,,,,71,67,,69,70,,,,53,54,56,55,63,66,,58,64', -'57,,59,334,,,,,,,,,,,,,,,,15,,,,,,43,,50,,52,115,,51,76,72,,45,75,,', -',,73,14,,,74,,,12,13,,,,,63,66,77,,64,57,,59,44,,,,71,67,,69,70,,,,53', -'54,56,55,15,,,58,,,43,,50,,52,115,,51,76,72,,45,75,,,,,73,14,,,74,,', -'12,13,,,,,,,77,,,,,,44,,,,71,67,,69,70,,,86,53,54,56,55,,,,58,105,106', -'107,102,97,109,,113,,108,,,98,100,99,101,,,,,,,,,,,,,,,,112,,,,104,103', -',,90,91,93,92,95,96,,88,89,86,,240,,,87,,,,105,106,107,102,97,109,,113', -',108,,,98,100,99,101,,,94,,,,,,,,,,,,,112,,,,104,103,,,90,91,93,92,95', -'96,86,88,89,,,,,,87,105,106,107,102,97,109,,113,,108,,,98,100,99,101', -',,,,,94,,,,,,,,,,112,,,,104,103,,,90,91,93,92,95,96,86,88,89,,,256,', +'54,56,55,15,,,58,,,199,216,210,217,52,211,219,212,208,206,,201,214,', +',,,73,14,220,215,213,,,12,13,,,,,,,77,,,,,218,200,,,,71,67,,69,70,,', +',53,54,56,55,63,66,,58,64,57,,59,,,,,,,,,,,,,,,,,15,,,,,,43,,50,,52', +'115,,51,76,72,,45,75,,,,,73,14,,,74,,,12,13,,,,,63,66,77,,64,57,,59', +'44,,,,71,67,,69,70,,,,53,54,56,55,15,,,58,,,43,,50,,52,115,,51,76,72', +',45,75,,,,,73,14,,,74,,,12,13,,,,,63,66,77,,64,57,,59,44,,,,71,67,,69', +'70,,,,53,54,56,55,15,,,58,,,43,,50,,52,115,,51,76,72,,45,75,,,,,73,14', +',,74,,,12,13,,,,,63,66,77,,64,57,341,59,44,,,,71,67,,69,70,,,,53,54', +'56,55,15,,,58,,,43,,50,,52,115,,51,76,72,,45,75,,,,,73,14,,,74,,,12', +'13,,,,,63,66,77,,64,57,,59,44,,,,71,67,,69,70,,,,53,54,56,55,15,,,58', +',,43,,50,,52,115,,51,76,72,,45,75,,,,,73,14,,,74,,,12,13,,,,,,,77,,', +',,,44,,,,71,67,,69,70,,,,53,54,56,55,63,66,,58,64,57,,59,344,,,,,,,', +',,,,,,,,15,,,,,,43,,50,,52,115,,51,76,72,,45,75,,,,,73,14,,,74,,,12', +'13,,,,,63,66,77,,64,57,,59,44,,,,71,67,,69,70,,,,53,54,56,55,15,,,58', +',,43,,50,,52,115,,51,76,72,,45,75,,,,,73,14,,,74,,,12,13,,,,,63,66,77', +',64,57,,59,44,,,,71,67,,69,70,,,,53,54,56,55,15,,,58,,,43,,50,,52,47', +',51,76,72,,45,75,48,49,,,73,14,,,74,,,12,13,,,,,,,77,,,,,,44,,,,71,67', +',69,70,,,86,53,54,56,55,,,,58,105,106,107,102,97,109,,113,,108,,,98', +'100,99,101,,,,,,,,,,,,,,,,112,,,,104,103,,,90,91,93,92,95,96,,88,89', +',86,,110,,87,263,,,,105,106,107,102,97,109,,113,,108,,,98,100,99,101', +',94,,,,,,,,,,,,,,112,,,,104,103,,,90,91,93,92,95,96,86,88,89,,,263,', ',87,105,106,107,102,97,109,,113,,108,,,98,100,99,101,,,,,,94,,,,,,,', -',,112,,,,104,103,,,90,91,93,92,95,96,,88,89,86,,239,,,87,,,,105,106', +',,112,,,,104,103,,,90,91,93,92,95,96,,88,89,86,,247,,,87,,,,105,106', '107,102,97,109,,113,,108,,,98,100,99,101,,,94,,,,,,,,,,,,,112,,,,104', '103,,,90,91,93,92,95,96,86,88,89,,,,,,87,105,106,107,102,97,109,,113', ',108,,,98,100,99,101,,,,,,94,,,,,,,,,,112,,,,104,103,,,90,91,93,92,95', -'96,,88,89,86,,238,,,87,,,,105,106,107,102,97,109,,113,,108,,,98,100', -'99,101,,,94,,,,,,,,,,,,,112,,,,104,103,,,90,91,93,92,95,96,,88,89,,86', -',110,,87,256,,,,105,106,107,102,97,109,,113,,108,,,98,100,99,101,,94', -',,,,,,,,,,,,,112,,,,104,103,,,90,91,93,92,95,96,,88,89,86,,237,,,87', -',,,105,106,107,102,97,109,,113,,108,,,98,100,99,101,,,94,,,,,,,,,,,', -',112,,,,104,103,,,90,91,93,92,95,96,86,88,89,,,,,,87,105,106,107,102', -'97,109,,113,,108,,226,98,100,99,101,,,,,,94,,,,,,,,,,112,,,,104,103', +'96,,88,89,86,,246,,,87,,,,105,106,107,102,97,109,,113,,108,,,98,100', +'99,101,,,94,,,,,,,,,,,,,112,,,,104,103,,,90,91,93,92,95,96,,88,89,86', +',245,,,87,,,,105,106,107,102,97,109,,113,,108,,,98,100,99,101,,,94,', +',,,,,,,,,,,112,,,,104,103,,,90,91,93,92,95,96,,88,89,86,,244,,,87,,', +',105,106,107,102,97,109,,113,,108,,,98,100,99,101,,,94,,,,,,,,,,,,,112', +',,,104,103,,,90,91,93,92,95,96,86,88,89,,,,,,87,105,106,107,102,97,109', +',113,,108,,226,98,100,99,101,,,,,,94,,,,,,,,,,112,,,,104,103,,,90,91', +'93,92,95,96,86,88,89,,,,,,87,105,106,107,102,97,109,,113,,108,,,98,100', +'99,101,,,,,,94,,,,,,,,,,112,,,,104,103,,,90,91,93,92,95,96,86,88,89', +',,,,,87,105,106,107,102,97,109,,113,,108,277,278,98,100,99,101,,,,,', +'94,,,,,,,,,,112,,,,104,103,,,90,91,93,92,95,96,86,88,89,,,,,,87,105', +'106,107,102,97,109,,113,,108,,,98,100,99,101,,,,,,94,,,,,,,,,,112,,', +',104,103,,,90,91,93,92,95,96,86,88,89,,,,,,87,105,106,107,102,97,109', +',113,,108,,,98,100,99,101,,,,,,94,,,,,,,,,,112,,,,104,103,,,90,91,93', +'92,95,96,86,88,89,,,,,,87,105,106,107,102,97,109,,113,,108,,,98,100', +'99,101,,,,,,94,,,,,,,,,,112,,,,104,103,,,90,91,93,92,95,96,86,88,89', +',,,,,87,105,106,107,102,97,109,,113,,108,,,98,100,99,101,,,,,,94,,,', +',,,,,,112,,,,104,103,,,90,91,93,92,95,96,86,88,89,,,,,,87,105,106,107', +'102,97,109,,113,,108,,,98,100,99,101,,,,,,94,,,,,,,,,,112,,,,104,103', ',,90,91,93,92,95,96,86,88,89,,,,,,87,105,106,107,102,97,109,,113,,108', ',,98,100,99,101,,,,,,94,,,,,,,,,,112,,,,104,103,,,90,91,93,92,95,96', -'86,88,89,,,,,,87,105,106,107,102,97,109,,113,,108,270,271,98,100,99', -'101,,,,,,94,,,,,,,,,,112,,,,104,103,,,90,91,93,92,95,96,86,88,89,,,', -',,87,105,106,107,102,97,109,,113,,108,,,98,100,99,101,,,,,,94,,,,,,', -',,,112,,,,104,103,,,90,91,93,92,95,96,86,88,89,,,,,,87,105,106,107,102', -'97,109,,113,,108,,,98,100,99,101,,,,,,94,,,,,,,,,,112,,,,104,103,,,90', -'91,93,92,95,96,86,88,89,,,,,,87,105,106,107,102,97,109,,113,,108,,,98', -'100,99,101,,,,,,94,,,,,,,,,,112,,,,104,103,,,90,91,93,92,95,96,86,88', -'89,,,,,,87,105,106,107,102,97,109,,113,,108,,,98,100,99,101,,,,,,94', -',,,,,,,,,112,,,,104,103,,,90,91,93,92,95,96,86,88,89,,,,,,87,105,106', -'107,102,97,109,,113,,108,,,98,100,99,101,,,,,,94,,,,,,,,,,112,,,,104', -'103,,,90,91,93,92,95,96,86,88,89,,,,,,87,105,106,107,102,97,109,280', -'113,,108,,,98,100,99,101,,,,,,94,,,,,,,,,,112,,,,104,103,,,90,91,93', -'92,95,96,,88,89,86,,110,,,87,,,,105,106,107,102,97,109,,113,,108,,,98', -'100,99,101,,,94,,,,,,,,,,,,,112,,,,104,103,,,90,91,93,92,95,96,86,88', -'89,,,276,,,87,105,106,107,102,97,109,,113,86,108,,,98,100,99,101,,,', -',,94,109,,113,,108,,,,,112,,,,104,103,,,90,91,93,92,95,96,,88,89,112', -',,86,,87,,,90,91,93,92,95,96,,88,89,109,,113,86,108,87,,,,,94,,,,,,97', -'109,,113,,108,,,98,,112,94,,,,,,,90,91,93,92,95,96,,88,89,112,,,,,87', -',86,90,91,93,92,95,96,,88,89,,,,97,109,87,113,86,108,,94,98,,,,,,,,', -'97,109,,113,,108,,94,98,,112,63,66,,,64,,,90,91,93,92,95,96,,88,89,112', -',,,,87,,,90,91,93,92,95,96,,88,89,,,86,,,87,144,,,141,94,105,106,107', -'102,97,109,,113,,108,,,98,100,99,101,94,,77,,,,,,,,,,145,67,,112,,,', -'104,103,,86,90,91,93,92,95,96,,88,89,,,102,97,109,87,113,,108,,86,98', -'100,99,101,,,,,,,,102,97,109,,113,94,108,,112,98,100,99,101,,,,90,91', -'93,92,95,96,,88,89,,,,112,,87,,,103,,,90,91,93,92,95,96,,88,89,86,,', -',,87,,94,,105,106,107,102,97,109,,113,,108,,,98,100,99,101,,,94,,,,', -',,,,,,,,112,,,,104,103,,,90,91,93,92,95,96,86,88,89,,,,,,87,105,106', -'107,102,97,109,,113,,108,,86,98,100,99,101,63,66,,,64,94,,,97,109,,113', -',108,,112,98,,,104,103,,,90,91,93,92,95,96,,88,89,63,66,,112,64,87,', -'144,,,141,90,91,93,92,95,96,,88,89,86,,63,66,,87,64,94,,,77,,,,109,', -'113,86,108,144,145,67,141,,,,,94,,86,,109,,113,,108,,,,,112,144,77,109', -'141,113,,108,,,,,145,67,,88,89,112,,,,,87,,77,,,,,112,,,88,89,145,67', -',,,87,,,,,88,89,,,,,,87' ] - racc_action_table = arr = ::Array.new(6903, nil) +'86,88,89,,,,,,87,105,106,107,102,97,109,287,113,,108,,,98,100,99,101', +',,,,,94,,,,,,,,,,112,,,,104,103,,,90,91,93,92,95,96,,88,89,86,,110,', +',87,,,,105,106,107,102,97,109,,113,86,108,,,98,100,99,101,,,94,,,,109', +',113,,108,,,,,112,,,,104,103,,,90,91,93,92,95,96,,88,89,112,,,86,,87', +',,90,91,93,92,,,,88,89,109,,113,86,108,87,,,,,94,,,,,,,109,,113,,108', +',,,,112,94,,,,,,,90,91,93,92,95,96,,88,89,112,,,,,87,,86,90,91,93,92', +'95,96,,88,89,,,,97,109,87,113,86,108,,94,98,,,,,,,,,97,109,,113,,108', +',94,98,,112,,,,,,,,90,91,93,92,95,96,,88,89,112,,,,,87,,86,90,91,93', +'92,95,96,,88,89,,,,97,109,87,113,86,108,,94,98,,,,,,,,,97,109,,113,', +'108,,94,98,,112,,,,,,,,90,91,93,92,95,96,,88,89,112,,,,,87,,86,90,91', +'93,92,95,96,,88,89,,,102,97,109,87,113,,108,,94,98,100,99,101,,,,,,', +',,,,,,94,,,112,,,,,86,,,90,91,93,92,95,96,,88,89,102,97,109,,113,87', +'108,,86,98,100,99,101,,,,,105,106,107,102,97,109,,113,,108,94,112,98', +'100,99,101,103,,,90,91,93,92,95,96,,88,89,,,,112,,87,,104,103,,,90,91', +'93,92,95,96,,88,89,86,,,,,87,,94,,105,106,107,102,97,109,,113,,108,', +',98,100,99,101,,,94,,,,,,,,,,,,,112,,,,104,103,,,90,91,93,92,95,96,86', +'88,89,,,283,,,87,105,106,107,102,97,109,,113,,108,,,98,100,99,101,,', +',,,94,,,,,,,,,,112,,,,104,103,,,90,91,93,92,95,96,86,88,89,,,,,,87,105', +'106,107,102,97,109,,113,,108,,,98,100,99,101,63,66,63,66,64,94,64,63', +'66,63,66,64,,64,,112,,,,104,103,,,90,91,93,92,95,96,,88,89,,,,,86,87', +',144,,144,141,,141,,144,86,144,141,109,141,113,,108,,,,,94,,109,77,113', +'77,108,,,,77,86,77,145,67,145,67,112,,,145,67,145,67,,109,,113,112,108', +',,88,89,,,,,,87,,,,88,89,,,,,,87,112,,,,,,,,,,,,,,,88,89,,,,,,87' ] + racc_action_table = arr = ::Array.new(7002, nil) idx = 0 clist.each do |str| str.split(',', -1).each do |i| arr[idx] = i.to_i unless i.empty? idx += 1 end end clist = [ -'0,0,235,208,0,0,137,0,209,175,214,206,245,251,213,149,235,157,157,313', -'149,245,161,175,0,175,122,175,313,322,0,137,0,322,0,0,322,0,0,0,319', -'0,0,0,0,208,251,0,0,175,209,0,214,206,0,0,213,161,47,175,175,122,0,211', -'175,175,129,211,0,171,273,175,0,0,171,0,0,0,338,338,0,0,0,0,392,392', -'47,0,392,392,47,392,392,175,129,147,267,117,129,210,147,159,159,210', -'152,159,50,152,392,203,50,117,277,117,392,117,392,202,392,392,115,392', -'392,392,115,392,392,392,392,281,165,392,392,283,310,392,310,117,392', -'392,8,8,8,8,5,5,392,223,5,5,225,5,392,355,227,355,392,392,287,392,392', -'139,301,303,392,392,392,392,5,305,306,392,136,309,5,228,5,311,5,5,312', -'5,5,5,5,5,5,5,5,131,121,5,5,316,317,5,318,246,5,5,320,232,279,279,279', -'279,5,279,279,279,279,279,5,279,279,324,5,5,111,5,5,279,279,279,5,5', -'5,5,391,391,109,5,391,391,80,391,391,176,78,279,279,337,234,68,340,67', -'342,242,48,349,350,176,391,176,42,176,243,41,391,358,391,359,391,391', -'361,391,391,391,362,391,391,391,391,366,367,391,391,176,368,391,40,374', -'391,391,375,378,247,176,176,6,391,1,176,176,396,400,391,402,404,176', -'391,391,,391,391,,,,391,391,391,391,389,389,,391,389,389,,389,389,176', -',,,172,,,,,,,,,,,389,,,172,,172,389,172,389,,389,389,,389,389,389,,389', -'389,389,389,,,389,389,,,389,,172,389,389,,,,,196,196,389,,196,196,,196', -'389,,,,389,389,,389,389,,,,389,389,389,389,196,,,389,,,196,,196,,196', -'196,,196,196,196,,196,196,,,,,196,196,,,196,,,196,196,,,,,12,12,196', -',12,12,,12,196,,,,196,196,,196,196,,,,196,196,196,196,12,,,196,,,12', -',12,,12,12,,12,12,12,,12,12,,,,,12,12,,,12,,,12,12,,,,,13,13,12,,13', -'13,,13,12,,,,12,12,,12,12,,,,12,12,12,12,13,,,12,,,13,,13,,13,13,,13', -'13,13,,13,13,,,,,13,13,,,13,,,13,13,,,,,14,14,13,,14,14,,14,13,,,,13', -'13,,13,13,,,,13,13,13,13,14,,,13,,,14,,14,,14,14,,14,14,14,,14,14,,', -',,14,14,,,14,,,14,14,,,,,15,15,14,,15,15,,15,14,,,,14,14,,14,14,,,,14', -'14,14,14,15,,,14,,,15,,15,,15,15,,15,15,15,,15,15,,,,,15,15,,,15,,,15', -'15,,,,,370,370,15,,370,370,,370,15,,,,15,15,,15,15,,,,15,15,15,15,370', -',,15,,,370,,370,,370,370,,370,370,370,,370,370,370,370,,,370,370,,,370', -',,370,370,,,,,357,357,370,,357,357,,357,370,,,,370,370,,370,370,,,,370', -'370,370,370,357,,,370,,,357,,357,,357,357,,357,357,357,,357,357,,,,', -'357,357,,,357,,,357,357,,,,,199,199,357,,199,199,,199,357,,,,357,357', -',357,357,,,,357,357,357,357,199,,,357,,,199,,199,,199,199,,199,199,199', -',199,199,,,,,199,199,,,199,,,199,199,,,,,43,43,199,,43,43,,43,199,,', -',199,199,,199,199,,,,199,199,199,199,43,,,199,,,43,,43,,43,43,,43,43', -'43,,43,43,,,,,43,43,,,43,,,43,43,,,,,44,44,43,,44,44,,44,43,,,,43,43', -',43,43,,,,43,43,43,43,44,,,43,,,44,,44,,44,44,,44,44,44,,44,44,,,,,44', -'44,,,44,,,44,44,,,,,45,45,44,,45,45,,45,44,,,,44,44,,44,44,,,,44,44', -'44,44,45,,,44,,,45,,45,,45,45,,45,45,45,,45,45,,,,,45,45,,,45,,,45,45', -',,,,46,46,45,,46,46,,46,45,,,,45,45,,45,45,,,,45,45,45,45,46,,,45,,', -'46,,46,,46,46,,46,46,46,,46,46,,,,,46,46,,,46,,,46,46,,,,,241,241,46', -',241,241,,241,46,,,,46,46,,46,46,,,,46,46,46,46,241,,,46,,,241,,241', -',241,241,,241,241,241,,241,241,,,,,241,241,,,241,,,241,241,,,,,200,200', -'241,,200,200,,200,241,,,,241,241,,241,241,,,,241,241,241,241,200,,,241', -',,200,,200,,200,200,,200,200,200,,200,200,,,,,200,200,,,200,,,200,200', -',,,,201,201,200,,201,201,,201,200,,,,200,200,,200,200,,,,200,200,200', -'200,201,,,200,,,201,,201,,201,201,,201,201,201,,201,201,,,,,201,201', -',,201,,,201,201,,,,,240,240,201,,240,240,,240,201,,,,201,201,,201,201', -',,,201,201,201,201,240,,,201,,,240,240,240,240,240,240,240,240,240,240', -',240,240,,,,,240,240,240,240,240,,,240,240,,,,,,,240,,,,,240,240,,,', -'240,240,,240,240,,,,240,240,240,240,57,57,,240,57,57,57,57,,174,,,,', -',,,,,,,,,174,57,174,,174,,,57,,57,,57,57,,57,57,57,,57,57,,,,,57,57', -'174,,57,,,57,57,,,,,58,58,57,,58,58,58,58,57,,,174,57,57,,57,57,,,,57', -'57,57,57,58,,,57,,,58,,58,,58,58,,58,58,58,,58,58,,,,,58,58,,,58,,173', -'58,58,,,,,,,58,,,,,173,58,173,,173,58,58,,58,58,,,,58,58,58,58,59,59', -',58,59,59,,59,59,173,,,,,,,,,,,,,,,59,,,,,,59,173,59,,59,59,,59,59,59', -',59,59,,,,,59,59,,,59,,,59,59,,,,,65,65,59,,65,65,,65,59,,,,59,59,,59', -'59,,,,59,59,59,59,65,,,59,,,65,,65,,65,65,,65,65,65,,65,65,,,,,65,65', -',,65,,,65,65,,,,,341,341,65,,341,341,,341,65,,,,65,65,,65,65,,,,65,65', -'65,65,341,,,65,,,341,,341,,341,341,,341,341,341,,341,341,,,,,341,341', -',,341,,,341,341,,,,,276,276,341,,276,276,,276,341,,,,341,341,,341,341', -',,,341,341,341,341,276,,,341,,,276,,276,,276,276,,276,276,276,,276,276', -',,,,276,276,,,276,,,276,276,,,,,70,70,276,,70,70,,70,276,,,,276,276', -',276,276,,,,276,276,276,276,70,,,276,,,70,,70,,70,70,,70,70,70,,70,70', -',,,,70,70,,,70,,,70,70,,,,,282,282,70,,282,282,,282,70,,,,70,70,,70', -'70,,,,70,70,70,70,282,,,70,,,282,,282,,282,282,,282,282,282,,282,282', -',,,,282,282,,,282,,,282,282,,,,,79,79,282,,79,79,,79,282,,,,282,282', -',282,282,,,,282,282,282,282,79,,,282,,,79,,79,,79,79,,79,79,79,,79,79', -'79,79,,,79,79,,,79,,116,79,79,,,,,,,79,,,,,116,79,116,,116,79,79,,79', -'79,,,,79,79,79,79,163,163,,79,163,163,,163,163,116,,,,114,,,,,,,,,,', -'163,,,114,,114,163,114,163,,163,163,,163,163,163,,163,163,163,163,,', -'163,163,,,163,,114,163,163,,,,,81,81,163,,81,81,,81,163,,,,163,163,', -'163,163,,,,163,163,163,163,81,,,163,,,81,,81,,81,81,,81,81,81,,81,81', -'81,81,,,81,81,,,81,,,81,81,,,,,82,82,81,,82,82,,82,81,,,,81,81,,81,81', -',,,81,81,81,81,82,,,81,,,82,,82,,82,82,,82,82,82,,82,82,82,82,,,82,82', -',,82,,,82,82,,,,,83,83,82,,83,83,,83,82,,,,82,82,,82,82,,,,82,82,82', -'82,83,,,82,,,83,,83,,83,83,,83,83,83,,83,83,83,83,,,83,83,,,83,,,83', -'83,,,,,84,84,83,,84,84,,84,83,,,,83,83,,83,83,,,,83,83,83,83,84,,,83', -',,84,,84,,84,84,,84,84,84,,84,84,84,84,,,84,84,,,84,,,84,84,,,,,85,85', -'84,,85,85,,85,84,,,,84,84,,84,84,,,,84,84,84,84,85,,,84,,,85,,85,,85', -'85,,85,85,85,,85,85,85,85,,,85,85,,,85,,,85,85,,,,,86,86,85,,86,86,', -'86,85,,,,85,85,,85,85,,,,85,85,85,85,86,,,85,,,86,,86,,86,86,,86,86', -'86,,86,86,,,,,86,86,,,86,,,86,86,,,,,87,87,86,,87,87,,87,86,,,,86,86', -',86,86,,,,86,86,86,86,87,,,86,,,87,,87,,87,87,,87,87,87,,87,87,,,,,87', -'87,,,87,,,87,87,,,,,88,88,87,,88,88,,88,87,,,,87,87,,87,87,,,,87,87', -'87,87,88,,,87,,,88,,88,,88,88,,88,88,88,,88,88,,,,,88,88,,,88,,,88,88', -',,,,89,89,88,,89,89,,89,88,,,,88,88,,88,88,,,,88,88,88,88,89,,,88,,', -'89,,89,,89,89,,89,89,89,,89,89,,,,,89,89,,,89,,,89,89,,,,,90,90,89,', -'90,90,,90,89,,,,89,89,,89,89,,,,89,89,89,89,90,,,89,,,90,,90,,90,90', -',90,90,90,,90,90,,,,,90,90,,,90,,,90,90,,,,,91,91,90,,91,91,,91,90,', -',,90,90,,90,90,,,,90,90,90,90,91,,,90,,,91,,91,,91,91,,91,91,91,,91', -'91,,,,,91,91,,,91,,,91,91,,,,,92,92,91,,92,92,,92,91,,,,91,91,,91,91', -',,,91,91,91,91,92,,,91,,,92,,92,,92,92,,92,92,92,,92,92,,,,,92,92,,', -'92,,,92,92,,,,,93,93,92,,93,93,,93,92,,,,92,92,,92,92,,,,92,92,92,92', -'93,,,92,,,93,,93,,93,93,,93,93,93,,93,93,,,,,93,93,,,93,,,93,93,,,,', -'94,94,93,,94,94,,94,93,,,,93,93,,93,93,,,,93,93,93,93,94,,,93,,,94,', -'94,,94,94,,94,94,94,,94,94,,,,,94,94,,,94,,,94,94,,,,,95,95,94,,95,95', -',95,94,,,,94,94,,94,94,,,,94,94,94,94,95,,,94,,,95,,95,,95,95,,95,95', -'95,,95,95,,,,,95,95,,,95,,,95,95,,,,,96,96,95,,96,96,,96,95,,,,95,95', -',95,95,,,,95,95,95,95,96,,,95,,,96,,96,,96,96,,96,96,96,,96,96,,,,,96', -'96,,,96,,,96,96,,,,,97,97,96,,97,97,,97,96,,,,96,96,,96,96,,,,96,96', -'96,96,97,,,96,,,97,,97,,97,97,,97,97,97,,97,97,,,,,97,97,,,97,,,97,97', -',,,,98,98,97,,98,98,,98,97,,,,97,97,,97,97,,,,97,97,97,97,98,,,97,,', -'98,,98,,98,98,,98,98,98,,98,98,,,,,98,98,,,98,,,98,98,,,,,99,99,98,', -'99,99,,99,98,,,,98,98,,98,98,,,,98,98,98,98,99,,,98,,,99,,99,,99,99', -',99,99,99,,99,99,,,,,99,99,,,99,,,99,99,,,,,100,100,99,,100,100,,100', -'99,,,,99,99,,99,99,,,,99,99,99,99,100,,,99,,,100,,100,,100,100,,100', -'100,100,,100,100,,,,,100,100,,,100,,,100,100,,,,,101,101,100,,101,101', -',101,100,,,,100,100,,100,100,,,,100,100,100,100,101,,,100,,,101,,101', -',101,101,,101,101,101,,101,101,,,,,101,101,,,101,,,101,101,,,,,102,102', -'101,,102,102,,102,101,,,,101,101,,101,101,,,,101,101,101,101,102,,,101', -',,102,,102,,102,102,,102,102,102,,102,102,,,,,102,102,,,102,,,102,102', -',,,,103,103,102,,103,103,,103,102,,,,102,102,,102,102,,,,102,102,102', -'102,103,,,102,,,103,,103,,103,103,,103,103,103,,103,103,,,,,103,103', -',,103,,,103,103,,,,,104,104,103,,104,104,,104,103,,,,103,103,,103,103', -',,,103,103,103,103,104,,,103,,,104,,104,,104,104,,104,104,104,,104,104', -',,,,104,104,,,104,,,104,104,,,,,105,105,104,,105,105,,105,104,,,,104', -'104,,104,104,,,,104,104,104,104,105,,,104,,,105,,105,,105,105,,105,105', -'105,,105,105,,,,,105,105,,,105,,,105,105,,,,,106,106,105,,106,106,,106', -'105,,,,105,105,,105,105,,,,105,105,105,105,106,,,105,,,106,,106,,106', -'106,,106,106,106,,106,106,,,,,106,106,,,106,,,106,106,,,,,107,107,106', -',107,107,,107,106,,,,106,106,,106,106,,,,106,106,106,106,107,,,106,', -',107,,107,,107,107,,107,107,107,,107,107,,,,,107,107,,,107,,,107,107', -',,,,108,108,107,,108,108,,108,107,,,,107,107,,107,107,,,,107,107,107', -'107,108,,,107,,,108,,108,,108,108,,108,108,108,,108,108,,,,,108,108', -',,108,,,108,108,,,284,284,284,284,108,284,284,284,284,284,108,284,284', -'108,108,108,,108,108,284,284,284,108,108,108,108,326,326,,108,326,326', -',326,326,,,284,284,,,,,,,,,,,,326,,,,,,326,,326,,326,326,,326,326,326', -',326,326,326,326,,,326,326,,,326,,,326,326,,,,,110,110,326,,110,110', -',110,326,,,,326,326,,326,326,,,,326,326,326,326,110,,,326,,,110,110', -'110,110,110,110,110,110,110,110,,110,110,,,,,110,110,110,110,110,,,110', -'110,,,,,,,110,,,,,110,110,,,,110,110,,110,110,,,,110,110,110,110,325', -'325,,110,325,325,,325,221,221,221,221,,221,221,221,221,221,,221,221', -',,,325,,,221,221,221,325,,325,,325,325,,325,325,325,,325,325,325,325', -'221,221,325,325,,,325,,,325,325,,,,,112,112,325,,112,112,,112,325,,', -',325,325,,325,325,,,,325,325,325,325,112,,,325,,,112,,112,,112,112,', -'112,112,112,,112,112,,,,,112,112,,,112,,,112,112,,,,,113,113,112,,113', -'113,,113,112,,,,112,112,,112,112,,,,112,112,112,112,113,,,112,,,113', -',113,,113,113,,113,113,113,,113,113,,,,,113,113,,,113,,,113,113,,,,', -'239,239,113,,239,239,,239,113,,,,113,113,,113,113,,,,113,113,113,113', -'239,,,113,,,239,,239,,239,239,,239,239,239,,239,239,,,,,239,239,,,239', -',,239,239,,,,,,,239,,,,,,239,,,,239,239,,239,239,,,,239,239,239,239', -'238,238,,239,238,238,,238,238,180,,,,,,,,,,,,,,180,238,180,,180,,,238', -',238,,238,238,,238,238,238,,238,238,238,238,,,238,238,180,,238,,,238', -'238,,180,180,180,180,,238,,180,180,,,238,,,180,238,238,,238,238,,,,238', -'238,238,238,237,237,,238,237,237,,237,237,180,,,,,,,,,,,,,,,237,,,,', -',237,,237,,237,237,,237,237,237,,237,237,237,237,,,237,237,,,237,,,237', -'237,,,,,252,252,237,,252,252,252,252,237,,,,237,237,,237,237,,,,237', -'237,237,237,252,,,237,,,252,,252,,252,252,,252,252,252,,252,252,,,,', -'252,252,,,252,,,252,252,,,,,275,275,252,,275,275,,275,252,,,,252,252', -',252,252,,,,252,252,252,252,275,,,252,,,275,,275,,275,275,,275,275,275', -',275,275,,,,,275,275,,,275,,,275,275,,,,,119,119,275,,119,119,,119,275', -',,,275,275,,275,275,,,,275,275,275,275,119,119,,275,,,119,,119,,119', -'119,,119,119,119,,119,119,,,,,119,119,,,119,,,119,119,,,,,,,119,,,,', -',119,,,,119,119,,119,119,,,,119,119,119,119,314,314,,119,314,314,,314', -'314,181,,,,,,,,,,,,,,181,314,181,,181,,,314,,314,,314,314,,314,314,314', -',314,314,,,,,314,314,181,,314,,,314,314,,181,181,181,181,,314,,181,181', -',,314,,,181,314,314,,314,314,,,,314,314,314,314,230,230,,314,230,230', -',230,230,181,,,,,,,,,,,,,,,230,,,,,,230,,230,,230,230,,230,230,230,', -'230,230,230,230,,,230,230,,,230,,,230,230,,,,,123,123,230,,123,123,', -'123,230,,,,230,230,,230,230,,,,230,230,230,230,123,123,,230,,,123,,123', -',123,123,,123,123,123,,123,123,,,,,123,123,,,123,,,123,123,,,,,160,160', +'0,0,242,209,0,0,252,0,214,175,208,206,240,258,213,252,242,323,159,159', +'348,348,159,175,0,175,323,175,252,129,0,359,0,240,0,0,252,0,0,0,131', +'0,0,0,0,209,258,0,0,175,214,0,208,206,0,0,213,129,47,175,175,129,0,332', +'175,175,227,332,0,137,332,175,0,0,149,0,0,0,284,149,0,0,0,0,404,404', +'47,0,404,404,47,404,404,175,137,147,313,116,365,122,147,171,152,365', +'225,152,171,288,404,313,121,116,122,116,404,116,404,313,404,404,122', +'404,404,404,290,404,404,404,404,50,223,404,404,50,122,404,274,116,404', +'404,161,157,157,210,5,5,404,210,5,5,115,5,404,161,115,136,404,404,211', +'404,404,161,211,294,404,404,404,404,5,139,366,404,366,308,5,161,5,310', +'5,5,312,5,5,5,5,5,5,5,5,228,203,5,5,111,320,5,320,319,5,5,254,321,286', +'286,286,286,5,286,286,286,286,286,5,286,286,322,5,5,253,5,5,286,286', +'286,5,5,5,5,403,403,109,5,403,403,326,403,403,176,327,286,286,8,8,8', +'8,328,329,330,250,334,80,176,403,176,78,176,68,67,403,347,403,249,403', +'403,202,403,403,403,350,403,403,403,403,232,352,403,403,176,48,403,165', +'280,403,403,360,42,241,176,176,238,403,41,176,176,369,370,403,372,373', +'176,403,403,377,403,403,378,379,40,403,403,403,403,401,401,385,403,401', +'401,386,401,401,176,389,239,6,114,1,408,412,414,416,,,,,,401,,,114,', +'114,401,114,401,,401,401,,401,401,401,,401,401,401,401,,,401,401,,,401', +',114,401,401,,,,,196,196,401,,196,196,,196,401,,,,401,401,,401,401,', +',,401,401,401,401,196,,,401,,,196,,196,,196,196,,196,196,196,,196,196', +',,,,196,196,,,196,,,196,196,,,,,12,12,196,,12,12,,12,196,,,,196,196', +',196,196,,,,196,196,196,196,12,,,196,,,12,,12,,12,12,,12,12,12,,12,12', +',,,,12,12,,,12,,,12,12,,,,,13,13,12,,13,13,,13,12,,,,12,12,,12,12,,', +',12,12,12,12,13,,,12,,,13,,13,,13,13,,13,13,13,,13,13,,,,,13,13,,,13', +',,13,13,,,,,14,14,13,,14,14,,14,13,,,,13,13,,13,13,,,,13,13,13,13,14', +',,13,,,14,,14,,14,14,,14,14,14,,14,14,,,,,14,14,,,14,,,14,14,,,,,15', +'15,14,,15,15,,15,14,,,,14,14,,14,14,,,,14,14,14,14,15,,,14,,,15,,15', +',15,15,,15,15,15,,15,15,,,,,15,15,,,15,,,15,15,,,,,381,381,15,,381,381', +',381,15,,,,15,15,,15,15,,,,15,15,15,15,381,,,15,,,381,,381,,381,381', +',381,381,381,,381,381,381,381,,,381,381,,,381,,,381,381,,,,,368,368', +'381,,368,368,,368,381,,,,381,381,,381,381,,,,381,381,381,381,368,,,381', +',,368,,368,,368,368,,368,368,368,,368,368,,,,,368,368,,,368,,,368,368', +',,,,199,199,368,,199,199,,199,368,,,,368,368,,368,368,,,,368,368,368', +'368,199,,,368,,,199,,199,,199,199,,199,199,199,,199,199,,,,,199,199', +',,199,,,199,199,,,,,43,43,199,,43,43,,43,199,,,,199,199,,199,199,,,', +'199,199,199,199,43,,,199,,,43,,43,,43,43,,43,43,43,,43,43,,,,,43,43', +',,43,,,43,43,,,,,44,44,43,,44,44,,44,43,,,,43,43,,43,43,,,,43,43,43', +'43,44,,,43,,,44,,44,,44,44,,44,44,44,,44,44,,,,,44,44,,,44,,,44,44,', +',,,45,45,44,,45,45,,45,44,,,,44,44,,44,44,,,,44,44,44,44,45,,,44,,,45', +',45,,45,45,,45,45,45,,45,45,,,,,45,45,,,45,,,45,45,,,,,46,46,45,,46', +'46,,46,45,,,,45,45,,45,45,,,,45,45,45,45,46,,,45,,,46,,46,,46,46,,46', +'46,46,,46,46,,,,,46,46,,,46,,,46,46,,,,,200,200,46,,200,200,,200,46', +',,,46,46,,46,46,,,,46,46,46,46,200,,,46,,,200,,200,,200,200,,200,200', +'200,,200,200,,,,,200,200,,,200,,,200,200,,,,,201,201,200,,201,201,,201', +'200,,,,200,200,,200,200,,,,200,200,200,200,201,,,200,,,201,,201,,201', +'201,,201,201,201,,201,201,,,,,201,201,,,201,,,201,201,,,,,351,351,201', +',351,351,,351,201,,,,201,201,,201,201,,,,201,201,201,201,351,,,201,', +',351,,351,,351,351,,351,351,351,,351,351,,,,,351,351,,,351,,174,351', +'351,,,,,,,351,,,,,174,351,174,,174,351,351,,351,351,,,,351,351,351,351', +'230,230,,351,230,230,,230,230,174,,,,,,,,,,,,,,,230,,,,,,230,174,230', +',230,230,,230,230,230,,230,230,230,230,,,230,230,,,230,,,230,230,,,', +',57,57,230,,57,57,57,57,230,,,,230,230,,230,230,,,,230,230,230,230,57', +',,230,,,57,,57,,57,57,,57,57,57,,57,57,,,,,57,57,,,57,,,57,57,,,,,58', +'58,57,,58,58,58,58,57,,,,57,57,,57,57,,,,57,57,57,57,58,,,57,,,58,,58', +',58,58,,58,58,58,,58,58,,,,,58,58,,,58,,173,58,58,,,,,,,58,,,,,173,58', +'173,,173,58,58,,58,58,,,,58,58,58,58,59,59,,58,59,59,,59,59,173,,,,', +',,,,,,,,,,59,,,,,,59,173,59,,59,59,,59,59,59,,59,59,,,,,59,59,,,59,', +',59,59,,,,,65,65,59,,65,65,,65,59,,,,59,59,,59,59,,,,59,59,59,59,65', +',,59,,,65,,65,,65,65,,65,65,65,,65,65,,,,,65,65,,,65,,,65,65,,,221,221', +'221,221,65,221,221,221,221,221,65,221,221,,65,65,,65,65,221,221,221', +'65,65,65,65,244,244,,65,244,244,,244,244,180,,221,221,,,,,,,,,,,180', +'244,180,,180,,,244,,244,,244,244,,244,244,244,,244,244,244,244,,,244', +'244,180,,244,,,244,244,,180,180,180,180,,244,,180,180,,,244,,,180,244', +'244,,244,244,,,,244,244,244,244,163,163,,244,163,163,,163,163,180,,', +',172,,,,,,,,,,,163,,,172,,172,163,172,163,,163,163,,163,163,163,,163', +'163,163,163,,,163,163,,,163,,172,163,163,,,,,70,70,163,,70,70,,70,163', +',,,163,163,,163,163,,,,163,163,163,163,70,,,163,,,70,,70,,70,70,,70', +'70,70,,70,70,,,,,70,70,,,70,,117,70,70,,,,,,,70,,,,,117,70,117,,117', +'70,70,,70,70,,,,70,70,70,70,336,336,,70,336,336,,336,336,117,,,,,,,', +',,,,,,,336,,,,,,336,,336,,336,336,,336,336,336,,336,336,336,336,,,336', +'336,,,336,,,336,336,,,,,261,261,336,,261,261,261,261,336,,,,336,336', +',336,336,,,,336,336,336,336,261,,,336,,,261,,261,,261,261,,261,261,261', +',261,261,,,,,261,261,,,261,,,261,261,,,,,335,335,261,,335,335,,335,261', +',,,261,261,,261,261,,,,261,261,261,261,335,,,261,,,335,,335,,335,335', +',335,335,335,,335,335,335,335,,,335,335,,,335,,,335,335,,,,,81,81,335', +',81,81,,81,335,,,,335,335,,335,335,,,,335,335,335,335,81,,,335,,,81', +',81,,81,81,,81,81,81,,81,81,81,81,,,81,81,,,81,,,81,81,,,,,82,82,81', +',82,82,,82,81,,,,81,81,,81,81,,,,81,81,81,81,82,,,81,,,82,,82,,82,82', +',82,82,82,,82,82,82,82,,,82,82,,,82,,,82,82,,,,,83,83,82,,83,83,,83', +'82,,,,82,82,,82,82,,,,82,82,82,82,83,,,82,,,83,,83,,83,83,,83,83,83', +',83,83,83,83,,,83,83,,,83,,,83,83,,,,,84,84,83,,84,84,,84,83,,,,83,83', +',83,83,,,,83,83,83,83,84,,,83,,,84,,84,,84,84,,84,84,84,,84,84,84,84', +',,84,84,,,84,,,84,84,,,,,85,85,84,,85,85,,85,84,,,,84,84,,84,84,,,,84', +'84,84,84,85,,,84,,,85,,85,,85,85,,85,85,85,,85,85,85,85,,,85,85,,,85', +',,85,85,,,,,86,86,85,,86,86,,86,85,,,,85,85,,85,85,,,,85,85,85,85,86', +',,85,,,86,,86,,86,86,,86,86,86,,86,86,,,,,86,86,,,86,,,86,86,,,,,87', +'87,86,,87,87,,87,86,,,,86,86,,86,86,,,,86,86,86,86,87,,,86,,,87,,87', +',87,87,,87,87,87,,87,87,,,,,87,87,,,87,,,87,87,,,,,88,88,87,,88,88,', +'88,87,,,,87,87,,87,87,,,,87,87,87,87,88,,,87,,,88,,88,,88,88,,88,88', +'88,,88,88,,,,,88,88,,,88,,,88,88,,,,,89,89,88,,89,89,,89,88,,,,88,88', +',88,88,,,,88,88,88,88,89,,,88,,,89,,89,,89,89,,89,89,89,,89,89,,,,,89', +'89,,,89,,,89,89,,,,,90,90,89,,90,90,,90,89,,,,89,89,,89,89,,,,89,89', +'89,89,90,,,89,,,90,,90,,90,90,,90,90,90,,90,90,,,,,90,90,,,90,,,90,90', +',,,,91,91,90,,91,91,,91,90,,,,90,90,,90,90,,,,90,90,90,90,91,,,90,,', +'91,,91,,91,91,,91,91,91,,91,91,,,,,91,91,,,91,,,91,91,,,,,92,92,91,', +'92,92,,92,91,,,,91,91,,91,91,,,,91,91,91,91,92,,,91,,,92,,92,,92,92', +',92,92,92,,92,92,,,,,92,92,,,92,,,92,92,,,,,93,93,92,,93,93,,93,92,', +',,92,92,,92,92,,,,92,92,92,92,93,,,92,,,93,,93,,93,93,,93,93,93,,93', +'93,,,,,93,93,,,93,,,93,93,,,,,94,94,93,,94,94,,94,93,,,,93,93,,93,93', +',,,93,93,93,93,94,,,93,,,94,,94,,94,94,,94,94,94,,94,94,,,,,94,94,,', +'94,,,94,94,,,,,95,95,94,,95,95,,95,94,,,,94,94,,94,94,,,,94,94,94,94', +'95,,,94,,,95,,95,,95,95,,95,95,95,,95,95,,,,,95,95,,,95,,,95,95,,,,', +'96,96,95,,96,96,,96,95,,,,95,95,,95,95,,,,95,95,95,95,96,,,95,,,96,', +'96,,96,96,,96,96,96,,96,96,,,,,96,96,,,96,,,96,96,,,,,97,97,96,,97,97', +',97,96,,,,96,96,,96,96,,,,96,96,96,96,97,,,96,,,97,,97,,97,97,,97,97', +'97,,97,97,,,,,97,97,,,97,,,97,97,,,,,98,98,97,,98,98,,98,97,,,,97,97', +',97,97,,,,97,97,97,97,98,,,97,,,98,,98,,98,98,,98,98,98,,98,98,,,,,98', +'98,,,98,,,98,98,,,,,99,99,98,,99,99,,99,98,,,,98,98,,98,98,,,,98,98', +'98,98,99,,,98,,,99,,99,,99,99,,99,99,99,,99,99,,,,,99,99,,,99,,,99,99', +',,,,100,100,99,,100,100,,100,99,,,,99,99,,99,99,,,,99,99,99,99,100,', +',99,,,100,,100,,100,100,,100,100,100,,100,100,,,,,100,100,,,100,,,100', +'100,,,,,101,101,100,,101,101,,101,100,,,,100,100,,100,100,,,,100,100', +'100,100,101,,,100,,,101,,101,,101,101,,101,101,101,,101,101,,,,,101', +'101,,,101,,,101,101,,,,,102,102,101,,102,102,,102,101,,,,101,101,,101', +'101,,,,101,101,101,101,102,,,101,,,102,,102,,102,102,,102,102,102,,102', +'102,,,,,102,102,,,102,,,102,102,,,,,103,103,102,,103,103,,103,102,,', +',102,102,,102,102,,,,102,102,102,102,103,,,102,,,103,,103,,103,103,', +'103,103,103,,103,103,,,,,103,103,,,103,,,103,103,,,,,104,104,103,,104', +'104,,104,103,,,,103,103,,103,103,,,,103,103,103,103,104,,,103,,,104', +',104,,104,104,,104,104,104,,104,104,,,,,104,104,,,104,,,104,104,,,,', +'105,105,104,,105,105,,105,104,,,,104,104,,104,104,,,,104,104,104,104', +'105,,,104,,,105,,105,,105,105,,105,105,105,,105,105,,,,,105,105,,,105', +',,105,105,,,,,106,106,105,,106,106,,106,105,,,,105,105,,105,105,,,,105', +'105,105,105,106,,,105,,,106,,106,,106,106,,106,106,106,,106,106,,,,', +'106,106,,,106,,,106,106,,,,,107,107,106,,107,107,,107,106,,,,106,106', +',106,106,,,,106,106,106,106,107,,,106,,,107,,107,,107,107,,107,107,107', +',107,107,,,,,107,107,,,107,,,107,107,,,,,108,108,107,,108,108,,108,107', +',,,107,107,,107,107,,,,107,107,107,107,108,,,107,,,108,,108,,108,108', +',108,108,108,,108,108,,,,,108,108,,,108,,,108,108,,,291,291,291,291', +'108,291,291,291,291,291,108,291,291,108,108,108,,108,108,291,291,291', +'108,108,108,108,324,324,,108,324,324,,324,324,,,291,291,,,,,,,,,,,,324', +',,,,,324,,324,,324,324,,324,324,324,,324,324,,,,,324,324,,,324,,,324', +'324,,,,,110,110,324,,110,110,,110,324,,,,324,324,,324,324,,,,324,324', +'324,324,110,,,324,,,110,110,110,110,110,110,110,110,110,110,,110,110', +',,,,110,110,110,110,110,,,110,110,,,,,,,110,,,,,110,110,,,,110,110,', +'110,110,,,,110,110,110,110,317,317,,110,317,317,,317,,,,,,,,,,,,,,,', +',317,,,,,,317,,317,,317,317,,317,317,317,,317,317,,,,,317,317,,,317', +',,317,317,,,,,112,112,317,,112,112,,112,317,,,,317,317,,317,317,,,,317', +'317,317,317,112,,,317,,,112,,112,,112,112,,112,112,112,,112,112,,,,', +'112,112,,,112,,,112,112,,,,,113,113,112,,113,113,,113,112,,,,112,112', +',112,112,,,,112,112,112,112,113,,,112,,,113,,113,,113,113,,113,113,113', +',113,113,,,,,113,113,,,113,,,113,113,,,,,314,314,113,,314,314,,314,113', +',,,113,113,,113,113,,,,113,113,113,113,314,,,113,,,314,,314,,314,314', +',314,314,314,,314,314,,,,,314,314,,,314,,,314,314,,,,,307,307,314,,307', +'307,,307,314,,,,314,314,,314,314,,,,314,314,314,314,307,,,314,,,307', +',307,,307,307,,307,307,307,,307,307,,,,,307,307,,,307,,,307,307,,,,', +'293,293,307,,293,293,,293,307,,,,307,307,,307,307,,,,307,307,307,307', +'293,,,307,,,293,,293,,293,293,,293,293,293,,293,293,,,,,293,293,,,293', +',,293,293,,,,,292,292,293,,292,292,,292,293,,,,293,293,,293,293,,,,293', +'293,293,293,292,,,293,,,292,,292,,292,292,,292,292,292,,292,292,,,,', +'292,292,,,292,,,292,292,,,,,,,292,,,,,,292,,,,292,292,,292,292,,,,292', +'292,292,292,245,245,,292,245,245,,245,245,,,,,,,,,,,,,,,,245,,,,,,245', +',245,,245,245,,245,245,245,,245,245,245,245,,,245,245,,,245,,,245,245', +',,,,119,119,245,,119,119,,119,245,,,,245,245,,245,245,,,,245,245,245', +'245,119,119,,245,,,119,,119,,119,119,,119,119,119,,119,119,,,,,119,119', +',,119,,,119,119,,,,,289,289,119,,289,289,,289,119,,,,119,119,,119,119', +',,,119,119,119,119,289,,,119,,,289,,289,,289,289,,289,289,289,,289,289', +',,,,289,289,,,289,,,289,289,,,,,283,283,289,,283,283,,283,289,,,,289', +'289,,289,289,,,,289,289,289,289,283,,,289,,,283,,283,,283,283,,283,283', +'283,,283,283,,,,,283,283,,,283,,,283,283,,,,,123,123,283,,123,123,,123', +'283,,,,283,283,,283,283,,,,283,283,283,283,123,123,,283,,,123,,123,', +'123,123,,123,123,123,,123,123,,,,,123,123,,,123,,,123,123,,,,,160,160', '123,,160,160,,160,123,,,,123,123,,123,123,,,,123,123,123,123,160,,,123', ',,160,,160,,160,160,,160,160,160,,160,160,160,160,,,160,160,,,160,,', -'160,160,,,,,285,285,160,,285,285,,285,160,,,,160,160,,160,160,,,,160', -'160,160,160,285,,,160,,,285,,285,,285,285,,285,285,285,,285,285,,,,', -'285,285,,,285,,,285,285,,,,,300,300,285,,300,300,,300,285,,,,285,285', -',285,285,,,,285,285,285,285,300,,,285,,,300,,300,,300,300,,300,300,300', -',300,300,,,,,300,300,,,300,,,300,300,,,,,263,263,300,,263,263,,263,300', -',,,300,300,,300,300,,,,300,300,300,300,263,,,300,,,263,,263,,263,263', -',263,263,263,,263,263,,,,,263,263,,,263,,,263,263,,,,,254,254,263,,254', -'254,254,254,263,,,,263,263,,263,263,,,,263,263,263,263,254,,,263,,,254', -',254,,254,254,,254,254,254,,254,254,,,,,254,254,,,254,,,254,254,,,,', -'130,130,254,,130,130,,130,254,,,,254,254,,254,254,,,,254,254,254,254', -'130,,,254,,,130,,130,,130,130,,130,130,130,,130,130,,,,,130,130,,,130', -',,130,130,,,,,256,256,130,,256,256,,256,130,,,,130,130,,130,130,,,,130', -'130,130,130,256,,,130,,,256,,256,,256,256,,256,256,256,,256,256,,,,', -'256,256,,,256,,,256,256,,,,,307,307,256,,307,307,,307,256,,,,256,256', -',256,256,,,,256,256,256,256,307,,,256,,,307,,307,,307,307,,307,307,307', -',307,307,,,,,307,307,,,307,,,307,307,,,,,,,307,,,,,,307,,,,307,307,', -'307,307,,,,307,307,307,307,258,258,,307,258,258,,258,258,,,,,,,,,,,', -',,,,258,,,,,,258,,258,,258,258,,258,258,258,,258,258,,,,,258,258,,,258', -',,258,258,,,,,286,286,258,,286,286,,286,258,,,,258,258,,258,258,,,,258', -'258,258,258,286,,,258,,,286,,286,,286,286,,286,286,286,,286,286,,,,', -'286,286,,,286,,,286,286,,,,,,,286,,,,,,286,,,,286,286,,286,286,,,146', -'286,286,286,286,,,,286,146,146,146,146,146,146,,146,,146,,,146,146,146', -'146,,,,,,,,,,,,,,,,146,,,,146,146,,,146,146,146,146,146,146,,146,146', -'128,,128,,,146,,,,128,128,128,128,128,128,,128,,128,,,128,128,128,128', -',,146,,,,,,,,,,,,,128,,,,128,128,,,128,128,128,128,128,128,222,128,128', -',,,,,128,222,222,222,222,222,222,,222,,222,,,222,222,222,222,,,,,,128', -',,,,,,,,,222,,,,222,222,,,222,222,222,222,222,222,151,222,222,,,151', -',,222,151,151,151,151,151,151,,151,,151,,,151,151,151,151,,,,,,222,', -',,,,,,,,151,,,,151,151,,,151,151,151,151,151,151,,151,151,127,,127,', -',151,,,,127,127,127,127,127,127,,127,,127,,,127,127,127,127,,,151,,', -',,,,,,,,,,127,,,,127,127,,,127,127,127,127,127,127,155,127,127,,,,,', -'127,155,155,155,155,155,155,,155,,155,,,155,155,155,155,,,,,,127,,,', -',,,,,,155,,,,155,155,,,155,155,155,155,155,155,,155,155,126,,126,,,155', -',,,126,126,126,126,126,126,,126,,126,,,126,126,126,126,,,155,,,,,,,', -',,,,,126,,,,126,126,,,126,126,126,126,126,126,,126,126,,272,,272,,126', -'272,,,,272,272,272,272,272,272,,272,,272,,,272,272,272,272,,126,,,,', -',,,,,,,,,272,,,,272,272,,,272,272,272,272,272,272,,272,272,124,,124', -',,272,,,,124,124,124,124,124,124,,124,,124,,,124,124,124,124,,,272,', -',,,,,,,,,,,124,,,,124,124,,,124,124,124,124,124,124,118,124,124,,,,', -',124,118,118,118,118,118,118,,118,,118,,118,118,118,118,118,,,,,,124', -',,,,,,,,,118,,,,118,118,,,118,118,118,118,118,118,162,118,118,,,,,,118', -'162,162,162,162,162,162,,162,,162,,,162,162,162,162,,,,,,118,,,,,,,', -',,162,,,,162,162,,,162,162,162,162,162,162,330,162,162,,,,,,162,330', -'330,330,330,330,330,,330,,330,162,162,330,330,330,330,,,,,,162,,,,,', -',,,,330,,,,330,330,,,330,330,330,330,330,330,333,330,330,,,,,,330,333', -'333,333,333,333,333,,333,,333,,,333,333,333,333,,,,,,330,,,,,,,,,,333', -',,,333,333,,,333,333,333,333,333,333,339,333,333,,,,,,333,339,339,339', -'339,339,339,,339,,339,,,339,339,339,339,,,,,,333,,,,,,,,,,339,,,,339', -'339,,,339,339,339,339,339,339,347,339,339,,,,,,339,347,347,347,347,347', -'347,,347,,347,,,347,347,347,347,,,,,,339,,,,,,,,,,347,,,,347,347,,,347', -'347,347,347,347,347,348,347,347,,,,,,347,348,348,348,348,348,348,,348', -',348,,,348,348,348,348,,,,,,347,,,,,,,,,,348,,,,348,348,,,348,348,348', -'348,348,348,354,348,348,,,,,,348,354,354,354,354,354,354,,354,,354,', -',354,354,354,354,,,,,,348,,,,,,,,,,354,,,,354,354,,,354,354,354,354', -'354,354,198,354,354,,,,,,354,198,198,198,198,198,198,198,198,,198,,', -'198,198,198,198,,,,,,354,,,,,,,,,,198,,,,198,198,,,198,198,198,198,198', -'198,,198,198,11,,11,,,198,,,,11,11,11,11,11,11,,11,,11,,,11,11,11,11', -',,198,,,,,,,,,,,,,11,,,,11,11,,,11,11,11,11,11,11,193,11,11,,,193,,', -'11,193,193,193,193,193,193,,193,182,193,,,193,193,193,193,,,,,,11,182', -',182,,182,,,,,193,,,,193,193,,,193,193,193,193,193,193,,193,193,182', -',,183,,193,,,182,182,182,182,182,182,,182,182,183,,183,184,183,182,', -',,,193,,,,,,184,184,,184,,184,,,184,,183,182,,,,,,,183,183,183,183,183', -'183,,183,183,184,,,,,183,,185,184,184,184,184,184,184,,184,184,,,,185', -'185,184,185,186,185,,183,185,,,,,,,,,186,186,,186,,186,,184,186,,185', -'248,248,,,248,,,185,185,185,185,185,185,,185,185,186,,,,,185,,,186,186', -'186,186,186,186,,186,186,,,192,,,186,248,,,248,185,192,192,192,192,192', -'192,,192,,192,,,192,192,192,192,186,,248,,,,,,,,,,248,248,,192,,,,192', -'192,,188,192,192,192,192,192,192,,192,192,,,188,188,188,192,188,,188', -',189,188,188,188,188,,,,,,,,189,189,189,,189,192,189,,188,189,189,189', -'189,,,,188,188,188,188,188,188,,188,188,,,,189,,188,,,189,,,189,189', -'189,189,189,189,,189,189,190,,,,,189,,188,,190,190,190,190,190,190,', -'190,,190,,,190,190,190,190,,,189,,,,,,,,,,,,,190,,,,190,190,,,190,190', -'190,190,190,190,191,190,190,,,,,,190,191,191,191,191,191,191,,191,,191', -',187,191,191,191,191,51,51,,,51,190,,,187,187,,187,,187,,191,187,,,191', -'191,,,191,191,191,191,191,191,,191,191,250,250,,187,250,191,,51,,,51', -'187,187,187,187,187,187,,187,187,177,,212,212,,187,212,191,,,51,,,,177', -',177,178,177,250,51,51,250,,,,,187,,179,,178,,178,,178,,,,,177,212,250', -'179,212,179,,179,,,,,250,250,,177,177,178,,,,,177,,212,,,,,179,,,178', -'178,212,212,,,,178,,,,,179,179,,,,,,179' ] - racc_action_check = arr = ::Array.new(6903, nil) +'160,160,,,,,246,246,160,,246,246,,246,160,,,,160,160,,160,160,,,,160', +'160,160,160,246,,,160,,,246,,246,,246,246,,246,246,246,,246,246,,,,', +'246,246,,,246,,,246,246,,,,,247,247,246,,247,247,,247,246,,,,246,246', +',246,246,,,,246,246,246,246,247,,,246,,,247,247,247,247,247,247,247', +'247,247,247,,247,247,,,,,247,247,247,247,247,,,247,247,,,,,,,247,,,', +',247,247,,,,247,247,,247,247,,,,247,247,247,247,248,248,,247,248,248', +',248,,,,,,,,,,,,,,,,,248,,,,,,248,,248,,248,248,,248,248,248,,248,248', +',,,,248,248,,,248,,,248,248,,,,,282,282,248,,282,282,,282,248,,,,248', +'248,,248,248,,,,248,248,248,248,282,,,248,,,282,,282,,282,282,,282,282', +'282,,282,282,,,,,282,282,,,282,,,282,282,,,,,130,130,282,,130,130,,130', +'282,,,,282,282,,282,282,,,,282,282,282,282,130,,,282,,,130,,130,,130', +'130,,130,130,130,,130,130,,,,,130,130,,,130,,,130,130,,,,,259,259,130', +',259,259,259,259,130,,,,130,130,,130,130,,,,130,130,130,130,259,,,130', +',,259,,259,,259,259,,259,259,259,,259,259,,,,,259,259,,,259,,,259,259', +',,,,270,270,259,,270,270,,270,259,,,,259,259,,259,259,,,,259,259,259', +'259,270,,,259,,,270,,270,,270,270,,270,270,270,,270,270,,,,,270,270', +',,270,,,270,270,,,,,,,270,,,,,,270,,,,270,270,,270,270,,,,270,270,270', +'270,265,265,,270,265,265,,265,265,,,,,,,,,,,,,,,,265,,,,,,265,,265,', +'265,265,,265,265,265,,265,265,,,,,265,265,,,265,,,265,265,,,,,263,263', +'265,,263,263,,263,265,,,,265,265,,265,265,,,,265,265,265,265,263,,,265', +',,263,,263,,263,263,,263,263,263,,263,263,,,,,263,263,,,263,,,263,263', +',,,,79,79,263,,79,79,,79,263,,,,263,263,,263,263,,,,263,263,263,263', +'79,,,263,,,79,,79,,79,79,,79,79,79,,79,79,79,79,,,79,79,,,79,,,79,79', +',,,,,,79,,,,,,79,,,,79,79,,79,79,,,146,79,79,79,79,,,,79,146,146,146', +'146,146,146,,146,,146,,,146,146,146,146,,,,,,,,,,,,,,,,146,,,,146,146', +',,146,146,146,146,146,146,,146,146,,279,,279,,146,279,,,,279,279,279', +'279,279,279,,279,,279,,,279,279,279,279,,146,,,,,,,,,,,,,,279,,,,279', +'279,,,279,279,279,279,279,279,151,279,279,,,151,,,279,151,151,151,151', +'151,151,,151,,151,,,151,151,151,151,,,,,,279,,,,,,,,,,151,,,,151,151', +',,151,151,151,151,151,151,,151,151,128,,128,,,151,,,,128,128,128,128', +'128,128,,128,,128,,,128,128,128,128,,,151,,,,,,,,,,,,,128,,,,128,128', +',,128,128,128,128,128,128,155,128,128,,,,,,128,155,155,155,155,155,155', +',155,,155,,,155,155,155,155,,,,,,128,,,,,,,,,,155,,,,155,155,,,155,155', +'155,155,155,155,,155,155,127,,127,,,155,,,,127,127,127,127,127,127,', +'127,,127,,,127,127,127,127,,,155,,,,,,,,,,,,,127,,,,127,127,,,127,127', +'127,127,127,127,,127,127,126,,126,,,127,,,,126,126,126,126,126,126,', +'126,,126,,,126,126,126,126,,,127,,,,,,,,,,,,,126,,,,126,126,,,126,126', +'126,126,126,126,,126,126,124,,124,,,126,,,,124,124,124,124,124,124,', +'124,,124,,,124,124,124,124,,,126,,,,,,,,,,,,,124,,,,124,124,,,124,124', +'124,124,124,124,118,124,124,,,,,,124,118,118,118,118,118,118,,118,,118', +',118,118,118,118,118,,,,,,124,,,,,,,,,,118,,,,118,118,,,118,118,118', +'118,118,118,162,118,118,,,,,,118,162,162,162,162,162,162,,162,,162,', +',162,162,162,162,,,,,,118,,,,,,,,,,162,,,,162,162,,,162,162,162,162', +'162,162,340,162,162,,,,,,162,340,340,340,340,340,340,,340,,340,162,162', +'340,340,340,340,,,,,,162,,,,,,,,,,340,,,,340,340,,,340,340,340,340,340', +'340,343,340,340,,,,,,340,343,343,343,343,343,343,,343,,343,,,343,343', +'343,343,,,,,,340,,,,,,,,,,343,,,,343,343,,,343,343,343,343,343,343,349', +'343,343,,,,,,343,349,349,349,349,349,349,,349,,349,,,349,349,349,349', +',,,,,343,,,,,,,,,,349,,,,349,349,,,349,349,349,349,349,349,222,349,349', +',,,,,349,222,222,222,222,222,222,,222,,222,,,222,222,222,222,,,,,,349', +',,,,,,,,,222,,,,222,222,,,222,222,222,222,222,222,357,222,222,,,,,,222', +'357,357,357,357,357,357,,357,,357,,,357,357,357,357,,,,,,222,,,,,,,', +',,357,,,,357,357,,,357,357,357,357,357,357,358,357,357,,,,,,357,358', +'358,358,358,358,358,,358,,358,,,358,358,358,358,,,,,,357,,,,,,,,,,358', +',,,358,358,,,358,358,358,358,358,358,364,358,358,,,,,,358,364,364,364', +'364,364,364,,364,,364,,,364,364,364,364,,,,,,358,,,,,,,,,,364,,,,364', +'364,,,364,364,364,364,364,364,198,364,364,,,,,,364,198,198,198,198,198', +'198,198,198,,198,,,198,198,198,198,,,,,,364,,,,,,,,,,198,,,,198,198', +',,198,198,198,198,198,198,,198,198,11,,11,,,198,,,,11,11,11,11,11,11', +',11,181,11,,,11,11,11,11,,,198,,,,181,,181,,181,,,,,11,,,,11,11,,,11', +'11,11,11,11,11,,11,11,181,,,182,,11,,,181,181,181,181,,,,181,181,182', +',182,183,182,181,,,,,11,,,,,,,183,,183,,183,,,,,182,181,,,,,,,182,182', +'182,182,182,182,,182,182,183,,,,,182,,184,183,183,183,183,183,183,,183', +'183,,,,184,184,183,184,185,184,,182,184,,,,,,,,,185,185,,185,,185,,183', +'185,,184,,,,,,,,184,184,184,184,184,184,,184,184,185,,,,,184,,186,185', +'185,185,185,185,185,,185,185,,,,186,186,185,186,187,186,,184,186,,,', +',,,,,187,187,,187,,187,,185,187,,186,,,,,,,,186,186,186,186,186,186', +',186,186,187,,,,,186,,188,187,187,187,187,187,187,,187,187,,,188,188', +'188,187,188,,188,,186,188,188,188,188,,,,,,,,,,,,,187,,,188,,,,,189', +',,188,188,188,188,188,188,,188,188,189,189,189,,189,188,189,,190,189', +'189,189,189,,,,,190,190,190,190,190,190,,190,,190,188,189,190,190,190', +'190,189,,,189,189,189,189,189,189,,189,189,,,,190,,189,,190,190,,,190', +'190,190,190,190,190,,190,190,191,,,,,190,,189,,191,191,191,191,191,191', +',191,,191,,,191,191,191,191,,,190,,,,,,,,,,,,,191,,,,191,191,,,191,191', +'191,191,191,191,193,191,191,,,193,,,191,193,193,193,193,193,193,,193', +',193,,,193,193,193,193,,,,,,191,,,,,,,,,,193,,,,193,193,,,193,193,193', +'193,193,193,192,193,193,,,,,,193,192,192,192,192,192,192,,192,,192,', +',192,192,192,192,255,255,257,257,255,193,257,51,51,212,212,51,,212,', +'192,,,,192,192,,,192,192,192,192,192,192,,192,192,,,,,177,192,,255,', +'257,255,,257,,51,178,212,51,177,212,177,,177,,,,,192,,178,255,178,257', +'178,,,,51,179,212,255,255,257,257,177,,,51,51,212,212,,179,,179,178', +'179,,,177,177,,,,,,177,,,,178,178,,,,,,178,179,,,,,,,,,,,,,,,179,179', +',,,,,179' ] + racc_action_check = arr = ::Array.new(7002, nil) idx = 0 clist.each do |str| str.split(',', -1).each do |i| arr[idx] = i.to_i unless i.empty? idx += 1 end end racc_action_pointer = [ - -2, 291, nil, nil, nil, 142, 276, nil, 75, nil, - nil, 6295, 430, 490, 550, 610, nil, nil, nil, nil, + -2, 326, nil, nil, nil, 142, 311, nil, 176, nil, + nil, 6355, 430, 490, 550, 610, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, - 254, 186, 228, 850, 910, 970, 1030, 49, 203, nil, - 69, 6757, nil, nil, nil, nil, nil, 1354, 1414, 1498, - nil, nil, nil, nil, nil, 1558, nil, 169, 172, nil, - 1738, nil, nil, nil, nil, nil, nil, nil, 238, 1858, - 221, 2002, 2062, 2122, 2182, 2242, 2302, 2362, 2422, 2482, - 2542, 2602, 2662, 2722, 2782, 2842, 2902, 2962, 3022, 3082, - 3142, 3202, 3262, 3322, 3382, 3442, 3502, 3562, 3622, 189, - 3766, 209, 3910, 3970, 1950, 83, 1906, 90, 5806, 4378, - nil, 181, -10, 4606, 5752, nil, 5637, 5526, 5361, 57, - 4966, 163, nil, nil, nil, nil, 146, -7, nil, 140, - nil, nil, nil, nil, nil, nil, 5304, 87, nil, 7, - nil, 5469, 94, nil, nil, 5580, nil, 13, nil, 99, - 4666, -14, 5860, 1942, nil, 117, nil, nil, nil, nil, - nil, 61, 318, 1462, 1358, 2, 230, 6804, 6821, 6833, - 4118, 4466, 6366, 6409, 6426, 6473, 6490, 6747, 6596, 6616, - 6673, 6727, 6549, 6349, nil, nil, 370, nil, 6238, 790, - 1150, 1210, 75, 96, nil, nil, -1, nil, -9, -4, - 62, 26, 6811, 2, -2, nil, nil, nil, nil, nil, - nil, 3828, 5415, 99, nil, 126, nil, 141, 104, nil, - 4546, nil, 188, nil, 226, -11, nil, 4198, 4114, 4030, - 1270, 1090, 205, 218, nil, -15, 188, 277, 6519, nil, - 6789, -28, 4258, nil, 4906, nil, 5026, nil, 5170, nil, - nil, nil, nil, 4846, nil, nil, nil, 83, nil, nil, - nil, nil, 5695, 60, nil, 4318, 1678, 99, nil, 170, - nil, 119, 1798, 123, 3650, 4726, 5230, 145, nil, nil, + 281, 220, 259, 850, 910, 970, 1030, 49, 233, nil, + 92, 6874, nil, nil, nil, nil, nil, 1354, 1414, 1498, + nil, nil, nil, nil, nil, 1558, nil, 181, 185, nil, + 1786, nil, nil, nil, nil, nil, nil, nil, 254, 5290, + 237, 2050, 2110, 2170, 2230, 2290, 2350, 2410, 2470, 2530, + 2590, 2650, 2710, 2770, 2830, 2890, 2950, 3010, 3070, 3130, + 3190, 3250, 3310, 3370, 3430, 3490, 3550, 3610, 3670, 189, + 3814, 184, 3958, 4018, 318, 113, 90, 1834, 5812, 4402, + nil, 101, 63, 4582, 5758, nil, 5701, 5644, 5533, 20, + 4966, 14, nil, nil, nil, nil, 129, 56, nil, 148, + nil, nil, nil, nil, nil, nil, 5364, 87, nil, 66, + nil, 5476, 92, nil, nil, 5587, nil, 137, nil, 16, + 4642, 104, 5866, 1726, nil, 267, nil, nil, nil, nil, + nil, 93, 1734, 1462, 1258, 2, 230, 6898, 6909, 6932, + 1646, 6372, 6415, 6432, 6479, 6496, 6543, 6560, 6607, 6652, + 6672, 6729, 6837, 6783, nil, nil, 370, nil, 6298, 790, + 1090, 1150, 222, 177, nil, nil, -1, nil, -2, -9, + 106, 121, 6876, 2, -4, nil, nil, nil, nil, nil, + nil, 1586, 6082, 82, nil, 80, nil, 53, 118, nil, + 1294, nil, 260, nil, nil, nil, nil, nil, 273, 287, + -24, 279, -11, nil, 1642, 4342, 4702, 4762, 4846, 219, + 210, nil, -21, 209, 191, 6867, nil, 6869, -28, 5026, + nil, 1930, nil, 5230, nil, 5170, nil, nil, nil, nil, + 5086, nil, nil, nil, 123, nil, nil, nil, nil, 5422, + 271, nil, 4906, 4522, 65, nil, 170, nil, 97, 4462, + 114, 3698, 4258, 4198, 150, nil, nil, nil, nil, nil, + nil, nil, nil, nil, nil, nil, nil, 4138, 146, nil, + 167, nil, 109, 60, 4078, nil, nil, 3898, nil, 187, + 161, 191, 182, 4, 3754, nil, 192, 225, 203, 236, + 238, nil, 26, nil, 236, 1990, 1870, nil, nil, nil, + 5920, nil, nil, 5974, nil, nil, nil, 188, -60, 6028, + 258, 1210, 261, nil, nil, nil, nil, 6136, 6190, 21, + 213, nil, nil, nil, 6244, 90, 137, nil, 730, 285, + 262, nil, 288, 289, nil, nil, nil, 292, 295, 296, + nil, 670, nil, nil, nil, 287, 308, nil, nil, 312, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, - 4786, 135, nil, 153, nil, 98, 134, 5086, nil, 163, - 101, 167, 147, 6, 4462, nil, 151, 181, 154, 30, - 191, nil, -8, nil, 202, 3850, 3706, nil, nil, nil, - 5914, nil, nil, 5968, nil, nil, nil, 170, -2, 6022, - 234, 1618, 233, nil, nil, nil, nil, 6076, 6130, 239, - 179, nil, nil, nil, 6184, 120, nil, 730, 250, 228, - nil, 255, 259, nil, nil, nil, 263, 264, 268, nil, - 670, nil, nil, nil, 254, 274, nil, nil, 275, nil, - nil, nil, nil, nil, nil, nil, nil, nil, nil, 310, - nil, 226, 82, nil, nil, nil, 284, nil, nil, nil, - 285, nil, 287, nil, 288, nil, nil, nil, nil, nil ] + nil, 310, nil, 226, 82, nil, nil, nil, 317, nil, + nil, nil, 318, nil, 319, nil, 320, nil, nil, nil, + nil, nil ] racc_action_default = [ - -236, -237, -1, -2, -3, -4, -5, -8, -10, -11, - -16, -109, -237, -237, -237, -237, -46, -47, -48, -49, + -244, -245, -1, -2, -3, -4, -5, -8, -10, -11, + -16, -109, -245, -245, -245, -245, -46, -47, -48, -49, -50, -51, -52, -53, -54, -55, -56, -57, -58, -59, -60, -61, -62, -63, -64, -65, -66, -67, -68, -69, - -74, -75, -79, -237, -237, -237, -237, -237, -120, -122, - -237, -237, -169, -170, -171, -172, -173, -237, -237, -237, - -186, -187, -188, -189, -190, -237, -192, -237, -203, -206, - -237, -211, -212, -213, -214, -215, -216, -217, -237, -237, - -7, -237, -237, -237, -237, -237, -237, -237, -237, -237, - -237, -237, -237, -237, -237, -237, -237, -237, -237, -237, - -237, -237, -237, -237, -237, -237, -237, -237, -237, -237, - -129, -124, -236, -236, -28, -237, -29, -36, -237, -237, - -76, -237, -237, -237, -237, -86, -237, -237, -237, -237, - -237, -236, -139, -160, -161, -121, -236, -236, -148, -150, - -151, -152, -153, -154, -156, -157, -44, -237, -176, -237, - -179, -237, -237, -182, -183, -196, -191, -237, -199, -237, - -237, -237, -237, -237, 410, -6, -9, -12, -13, -14, - -15, -237, -18, -19, -20, -21, -22, -23, -24, -25, + -74, -75, -79, -245, -245, -245, -245, -245, -120, -122, + -245, -245, -177, -178, -179, -180, -181, -245, -245, -245, + -194, -195, -196, -197, -198, -245, -200, -245, -211, -214, + -245, -219, -220, -221, -222, -223, -224, -225, -245, -245, + -7, -245, -245, -245, -245, -245, -245, -245, -245, -245, + -245, -245, -245, -245, -245, -245, -245, -245, -245, -245, + -245, -245, -245, -245, -245, -245, -245, -245, -245, -245, + -129, -124, -244, -244, -28, -245, -29, -36, -245, -245, + -76, -245, -245, -245, -245, -86, -245, -245, -245, -245, + -245, -244, -139, -160, -161, -121, -244, -244, -148, -150, + -151, -152, -153, -154, -156, -157, -44, -245, -184, -245, + -187, -245, -245, -190, -191, -204, -199, -245, -207, -245, + -245, -245, -245, -245, 422, -6, -9, -12, -13, -14, + -15, -245, -18, -19, -20, -21, -22, -23, -24, -25, -26, -27, -30, -31, -32, -33, -34, -35, -37, -38, - -39, -40, -41, -237, -42, -104, -237, -80, -237, -229, - -235, -223, -220, -218, -118, -130, -212, -133, -216, -237, - -226, -224, -232, -214, -215, -222, -227, -228, -230, -231, - -233, -129, -128, -237, -127, -237, -43, -218, -71, -81, - -237, -84, -218, -165, -168, -237, -78, -237, -237, -237, - -129, -237, -220, -236, -162, -237, -237, -237, -237, -158, - -237, -237, -237, -174, -237, -177, -237, -180, -237, -193, - -194, -195, -197, -237, -200, -201, -202, -218, -204, -207, - -209, -210, -109, -237, -17, -237, -237, -218, -106, -129, - -117, -237, -221, -237, -219, -237, -237, -218, -132, -134, - -223, -224, -225, -226, -229, -232, -234, -235, -125, -126, - -219, -237, -73, -237, -83, -237, -219, -237, -77, -237, - -89, -237, -95, -237, -237, -99, -220, -218, -220, -237, - -237, -142, -237, -163, -218, -236, -237, -149, -159, -155, - -45, -175, -178, -185, -181, -184, -198, -237, -237, -108, - -237, -219, -218, -112, -119, -113, -131, -135, -136, -237, - -70, -82, -85, -166, -167, -89, -88, -237, -237, -95, - -94, -237, -237, -103, -98, -100, -237, -237, -237, -115, - -236, -143, -144, -145, -237, -237, -140, -141, -237, -147, - -205, -208, -105, -107, -116, -123, -72, -87, -90, -237, - -93, -237, -237, -110, -111, -114, -237, -164, -137, -146, - -237, -92, -237, -97, -237, -102, -138, -91, -96, -101 ] + -39, -40, -41, -245, -42, -104, -245, -80, -245, -237, + -243, -231, -228, -226, -118, -130, -220, -133, -224, -245, + -234, -232, -240, -222, -223, -230, -235, -236, -238, -239, + -241, -129, -128, -245, -127, -245, -43, -226, -71, -81, + -245, -84, -226, -165, -167, -168, -169, -170, -172, -245, + -245, -175, -245, -78, -245, -245, -245, -129, -245, -228, + -244, -162, -245, -245, -245, -245, -158, -245, -245, -245, + -182, -245, -185, -245, -188, -245, -201, -202, -203, -205, + -245, -208, -209, -210, -226, -212, -215, -217, -218, -109, + -245, -17, -245, -245, -226, -106, -129, -117, -245, -229, + -245, -227, -245, -245, -226, -132, -134, -231, -232, -233, + -234, -237, -240, -242, -243, -125, -126, -227, -245, -73, + -245, -83, -245, -227, -245, -173, -174, -245, -77, -245, + -89, -245, -95, -245, -245, -99, -228, -226, -228, -245, + -245, -142, -245, -163, -226, -244, -245, -149, -159, -155, + -45, -183, -186, -193, -189, -192, -206, -245, -245, -108, + -245, -227, -226, -112, -119, -113, -131, -135, -136, -245, + -70, -82, -85, -166, -171, -245, -89, -88, -245, -245, + -95, -94, -245, -245, -103, -98, -100, -245, -245, -245, + -115, -244, -143, -144, -145, -245, -245, -140, -141, -245, + -147, -213, -216, -105, -107, -116, -123, -72, -176, -87, + -90, -245, -93, -245, -245, -110, -111, -114, -245, -164, + -137, -146, -245, -92, -245, -97, -245, -102, -138, -91, + -96, -101 ] racc_goto_table = [ 2, 136, 4, 120, 114, 116, 117, 118, 142, 140, - 269, 156, 203, 202, 232, 281, 360, 243, 195, 375, - 356, 327, 246, 328, 223, 225, 315, 229, 166, 80, - 344, 279, 194, 242, 346, 124, 126, 127, 128, 167, - 168, 169, 170, 283, 362, 277, 147, 149, 314, 146, - 146, 151, 320, 267, 371, 319, 247, 155, 388, 329, - 353, 335, 162, 390, 396, 387, 264, 301, 265, 3, - 262, 263, 305, 261, 158, 171, 160, 1, 146, 172, + 147, 149, 156, 195, 241, 276, 232, 288, 371, 203, + 202, 367, 386, 250, 337, 325, 338, 330, 253, 223, + 225, 166, 80, 354, 286, 124, 126, 127, 128, 171, + 249, 167, 168, 169, 170, 284, 356, 229, 373, 146, + 146, 151, 324, 241, 194, 274, 382, 155, 254, 400, + 339, 363, 162, 316, 329, 315, 402, 399, 408, 345, + 271, 272, 227, 3, 269, 270, 242, 268, 146, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, - 193, 365, 198, 165, 222, 222, 278, 337, 227, nil, - nil, 146, 235, nil, nil, 146, nil, 340, nil, nil, - nil, nil, 198, 287, nil, nil, nil, 349, nil, 366, - nil, 368, nil, 244, nil, nil, nil, 324, 244, 249, - nil, nil, 317, 316, 318, nil, nil, nil, nil, nil, - nil, nil, nil, nil, nil, 272, nil, 367, nil, nil, - 266, 136, nil, 273, 374, nil, nil, nil, nil, 142, + 193, 285, 198, 376, 222, 222, 165, 158, 160, 1, + nil, 146, nil, nil, nil, 146, nil, nil, 290, nil, + nil, nil, 198, nil, nil, nil, nil, nil, nil, nil, + 294, nil, nil, 251, nil, nil, nil, nil, 251, 256, + nil, 377, 308, 379, 241, nil, 334, 312, nil, nil, + nil, nil, nil, nil, nil, 279, 327, 326, 328, nil, + 273, 136, nil, 280, nil, nil, nil, nil, nil, 142, 140, nil, nil, nil, nil, nil, nil, nil, nil, nil, - nil, 342, 384, nil, nil, nil, 381, nil, 193, nil, - 302, 124, 126, 127, nil, nil, nil, nil, nil, nil, - nil, nil, nil, nil, nil, 142, 140, 142, 140, 336, - nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, + nil, nil, nil, nil, nil, nil, nil, nil, 193, 347, + 309, 124, 126, 127, nil, 352, nil, nil, nil, 350, + nil, 392, nil, nil, nil, 241, nil, nil, nil, 359, + nil, nil, 142, 140, 142, 140, nil, 346, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, - 303, 146, 198, 198, nil, nil, nil, 309, 311, nil, - nil, nil, nil, nil, 330, 321, 330, nil, 333, nil, - 151, 383, nil, nil, nil, 155, nil, nil, nil, nil, - nil, nil, nil, nil, nil, nil, nil, 330, 339, nil, - nil, nil, nil, 372, 198, nil, nil, 347, 348, nil, - nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, - nil, nil, 330, nil, nil, nil, nil, nil, nil, 354, + 310, nil, nil, nil, nil, nil, nil, nil, 146, 198, + 198, nil, 378, nil, 319, 321, nil, nil, nil, 385, + nil, 340, 331, 340, nil, 343, 394, 151, nil, nil, + nil, nil, 155, nil, nil, nil, nil, 395, nil, nil, + 365, nil, nil, nil, 340, 349, nil, nil, nil, nil, + nil, 198, nil, 383, 357, 358, nil, nil, nil, nil, + nil, nil, nil, nil, nil, nil, nil, nil, nil, 340, + nil, nil, nil, nil, nil, nil, 364, nil, nil, 146, nil, nil, nil, nil, nil, nil, 146, nil, nil, nil, - nil, nil, 386, nil, nil, nil, nil, nil, nil, nil, - nil, nil, nil, nil, nil, nil, 378, 377, nil, nil, + nil, nil, 397, nil, nil, nil, nil, nil, nil, nil, + nil, nil, nil, nil, nil, nil, 389, 388, nil, nil, nil, nil, nil, 193, nil, nil, nil, nil, nil, nil, - nil, nil, nil, nil, nil, nil, nil, nil, nil, 124, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, + 124, nil, nil, nil, nil, nil, nil, nil, nil, nil, + nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, + nil, nil, nil, 388, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, - nil, nil, 377, nil, nil, nil, nil, nil, nil, nil, - nil, nil, nil, nil, nil, nil, nil, nil, nil, 400, - nil, 402, 404 ] + nil, 412, nil, 414, 416 ] racc_goto_check = [ 2, 65, 4, 40, 10, 10, 10, 10, 32, 38, - 90, 83, 57, 55, 45, 56, 48, 66, 52, 67, - 47, 73, 66, 73, 61, 61, 50, 44, 7, 6, - 58, 59, 13, 55, 62, 10, 10, 10, 10, 8, - 8, 8, 8, 39, 51, 53, 12, 12, 49, 10, - 10, 10, 69, 45, 70, 56, 72, 10, 46, 75, - 76, 78, 10, 48, 67, 47, 79, 39, 80, 3, - 84, 85, 39, 87, 88, 12, 89, 1, 10, 10, + 12, 12, 88, 52, 35, 95, 45, 56, 48, 57, + 55, 47, 67, 66, 73, 50, 73, 69, 66, 61, + 61, 7, 6, 58, 59, 10, 10, 10, 10, 12, + 55, 8, 8, 8, 8, 53, 62, 44, 51, 10, + 10, 10, 49, 35, 13, 45, 70, 10, 72, 46, + 75, 76, 10, 77, 56, 79, 48, 47, 67, 83, + 84, 85, 12, 3, 89, 90, 12, 92, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, - 10, 50, 10, 6, 10, 10, 52, 39, 12, nil, - nil, 10, 12, nil, nil, 10, nil, 39, nil, nil, - nil, nil, 10, 57, nil, nil, nil, 39, nil, 56, - nil, 56, nil, 4, nil, nil, nil, 45, 4, 4, - nil, nil, 57, 55, 55, nil, nil, nil, nil, nil, - nil, nil, nil, nil, nil, 10, nil, 39, nil, nil, - 2, 65, nil, 2, 39, nil, nil, nil, nil, 32, + 10, 52, 10, 50, 10, 10, 6, 93, 94, 1, + nil, 10, nil, nil, nil, 10, nil, nil, 39, nil, + nil, nil, 10, nil, nil, nil, nil, nil, nil, nil, + 57, nil, nil, 4, nil, nil, nil, nil, 4, 4, + nil, 56, 39, 56, 35, nil, 45, 39, nil, nil, + nil, nil, nil, nil, nil, 10, 57, 55, 55, nil, + 2, 65, nil, 2, nil, nil, nil, nil, nil, 32, 38, nil, nil, nil, nil, nil, nil, nil, nil, nil, - nil, 57, 39, nil, nil, nil, 90, nil, 10, nil, - 40, 10, 10, 10, nil, nil, nil, nil, nil, nil, - nil, nil, nil, nil, nil, 32, 38, 32, 38, 83, - nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, - nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, - 2, 10, 10, 10, nil, nil, nil, 2, 2, nil, - nil, nil, nil, nil, 10, 4, 10, nil, 10, nil, - 10, 52, nil, nil, nil, 10, nil, nil, nil, nil, - nil, nil, nil, nil, nil, nil, nil, 10, 10, nil, - nil, nil, nil, 65, 10, nil, nil, 10, 10, nil, + nil, nil, nil, nil, nil, nil, nil, nil, 10, 39, + 40, 10, 10, 10, nil, 57, nil, nil, nil, 39, + nil, 95, nil, nil, nil, 35, nil, nil, nil, 39, + nil, nil, 32, 38, 32, 38, nil, 88, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, - nil, nil, 10, nil, nil, nil, nil, nil, nil, 10, + 2, nil, nil, nil, nil, nil, nil, nil, 10, 10, + 10, nil, 39, nil, 2, 2, nil, nil, nil, 39, + nil, 10, 4, 10, nil, 10, 52, 10, nil, nil, + nil, nil, 10, nil, nil, nil, nil, 39, nil, nil, + 12, nil, nil, nil, 10, 10, nil, nil, nil, nil, + nil, 10, nil, 65, 10, 10, nil, nil, nil, nil, + nil, nil, nil, nil, nil, nil, nil, nil, nil, 10, + nil, nil, nil, nil, nil, nil, 10, nil, nil, 10, nil, nil, nil, nil, nil, nil, 10, nil, nil, nil, nil, nil, 40, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 2, 4, nil, nil, nil, nil, nil, 10, nil, nil, nil, nil, nil, nil, - nil, nil, nil, nil, nil, nil, nil, nil, nil, 10, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, + 10, nil, nil, nil, nil, nil, nil, nil, nil, nil, + nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, + nil, nil, nil, 4, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, - nil, nil, 4, nil, nil, nil, nil, nil, nil, nil, - nil, nil, nil, nil, nil, nil, nil, nil, nil, 2, - nil, 2, 2 ] + nil, 2, nil, 2, 2 ] racc_goto_pointer = [ - nil, 77, 0, 69, 2, nil, 24, -53, -43, nil, - -8, nil, -11, -76, nil, nil, nil, nil, nil, nil, + nil, 109, 0, 73, 2, nil, 27, -50, -41, nil, + -8, nil, -47, -54, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, - nil, nil, -43, nil, nil, nil, nil, nil, -42, -160, - -38, nil, nil, nil, -94, -108, -299, -290, -296, -191, - -213, -269, -90, -151, nil, -97, -187, -98, -252, -167, - nil, -88, -250, nil, nil, -49, -114, -306, nil, -191, - -268, nil, -81, -227, nil, -192, -246, nil, -197, -93, - -91, nil, nil, -54, -87, -86, nil, -84, 7, 8, - -152 ] + nil, nil, -43, nil, nil, -108, nil, nil, -42, -85, + -38, nil, nil, nil, -74, -106, -309, -299, -304, -194, + -221, -275, -95, -151, nil, -90, -185, -91, -256, -164, + nil, -83, -245, nil, nil, -49, -108, -313, nil, -223, + -276, nil, -79, -231, nil, -198, -252, -177, nil, -174, + nil, nil, nil, -196, -89, -88, nil, nil, -53, -83, + -82, nil, -80, 40, 40, -147 ] racc_goto_default = [ - nil, nil, 376, nil, 224, 5, 6, 7, 8, 9, - 11, 10, 313, nil, 16, 40, 17, 18, 19, 20, + nil, nil, 387, nil, 224, 5, 6, 7, 8, 9, + 11, 10, 323, nil, 16, 40, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, nil, nil, 41, 42, 121, nil, nil, 125, nil, nil, nil, nil, nil, nil, nil, 46, nil, nil, nil, 204, nil, 111, nil, 205, 209, 207, 132, nil, nil, 131, nil, - nil, 137, nil, 138, 139, 143, 233, 152, 154, 60, - 61, 62, 65, nil, nil, nil, 157, nil, nil, nil, - nil ] + nil, 137, nil, 138, 139, 143, 233, 234, 235, 236, + 237, 240, 152, 154, 60, 61, 62, 65, nil, nil, + nil, 157, nil, nil, nil, nil ] racc_reduce_table = [ 0, 0, :racc_error, 1, 95, :_reduce_1, 1, 95, :_reduce_2, 1, 95, :_reduce_none, 1, 96, :_reduce_4, 1, 99, :_reduce_5, 3, 99, :_reduce_6, 2, 99, :_reduce_7, 1, 100, :_reduce_8, 3, 100, :_reduce_9, 1, 101, :_reduce_none, 1, 102, :_reduce_11, 3, 102, :_reduce_12, 3, 102, :_reduce_13, 3, 102, :_reduce_14, 3, 102, :_reduce_15, 1, 104, :_reduce_none, 4, 104, :_reduce_17, 3, 104, :_reduce_18, 3, 104, :_reduce_19, 3, 104, :_reduce_20, 3, 104, :_reduce_21, 3, 104, :_reduce_22, 3, 104, :_reduce_23, 3, 104, :_reduce_24, 3, 104, :_reduce_25, 3, 104, :_reduce_26, 3, 104, :_reduce_27, 2, 104, :_reduce_28, 2, 104, :_reduce_29, 3, 104, :_reduce_30, 3, 104, :_reduce_31, 3, 104, :_reduce_32, 3, 104, :_reduce_33, 3, 104, :_reduce_34, 3, 104, :_reduce_35, 2, 104, :_reduce_36, 3, 104, :_reduce_37, 3, 104, :_reduce_38, 3, 104, :_reduce_39, 3, 104, :_reduce_40, 3, 104, :_reduce_41, 3, 104, :_reduce_42, 3, 104, :_reduce_43, 1, 106, :_reduce_44, 3, 106, :_reduce_45, 1, 105, :_reduce_none, 1, 109, :_reduce_none, 1, 109, :_reduce_none, 1, 109, :_reduce_none, 1, 109, :_reduce_none, 1, 109, :_reduce_none, 1, 109, :_reduce_none, 1, 109, :_reduce_none, 1, 109, :_reduce_none, 1, 109, :_reduce_none, 1, 109, :_reduce_none, 1, 109, :_reduce_none, 1, 109, :_reduce_none, 1, 110, :_reduce_none, 1, 110, :_reduce_none, 1, 110, :_reduce_none, 1, 110, :_reduce_none, 1, 110, :_reduce_none, 1, 110, :_reduce_none, 1, 110, :_reduce_none, 1, 110, :_reduce_none, 1, 110, :_reduce_none, 1, 127, :_reduce_68, 1, 127, :_reduce_69, 5, 108, :_reduce_70, 3, 108, :_reduce_71, 6, 108, :_reduce_72, 4, 108, :_reduce_73, 1, 108, :_reduce_74, 1, 112, :_reduce_75, 2, 112, :_reduce_76, 4, 135, :_reduce_77, 3, 135, :_reduce_78, 1, 135, :_reduce_79, 3, 136, :_reduce_80, 2, 134, :_reduce_81, 3, 138, :_reduce_82, 2, 138, :_reduce_83, 2, 137, :_reduce_84, 4, 137, :_reduce_85, 2, 115, :_reduce_86, 5, 140, :_reduce_87, 4, 140, :_reduce_88, 0, 141, :_reduce_none, 2, 141, :_reduce_90, 4, 141, :_reduce_91, 3, 141, :_reduce_92, 6, 116, :_reduce_93, 5, 116, :_reduce_94, 0, 142, :_reduce_none, 4, 142, :_reduce_96, 3, 142, :_reduce_97, 5, 114, :_reduce_98, 1, 143, :_reduce_99, 2, 143, :_reduce_100, 5, 144, :_reduce_101, 4, 144, :_reduce_102, 1, 145, :_reduce_103, 1, 107, :_reduce_none, 4, 107, :_reduce_105, 1, 147, :_reduce_106, 3, 147, :_reduce_107, 3, 146, :_reduce_108, 1, 103, :_reduce_109, 6, 103, :_reduce_110, 6, 103, :_reduce_111, 5, 103, :_reduce_112, 5, 103, :_reduce_113, 6, 103, :_reduce_114, 5, 103, :_reduce_115, 4, 152, :_reduce_116, 1, 153, :_reduce_117, 1, 149, :_reduce_118, 3, 149, :_reduce_119, 1, 148, :_reduce_120, 2, 148, :_reduce_121, 1, 148, :_reduce_122, 6, 113, :_reduce_123, 2, 113, :_reduce_124, 3, 154, :_reduce_125, 3, 154, :_reduce_126, 1, 155, :_reduce_none, 1, 155, :_reduce_none, 0, 151, :_reduce_129, 1, 151, :_reduce_130, 3, 151, :_reduce_131, 1, 157, :_reduce_none, 1, 157, :_reduce_none, 1, 157, :_reduce_none, 3, 156, :_reduce_135, 3, 156, :_reduce_136, 6, 117, :_reduce_137, 7, 118, :_reduce_138, 1, 162, :_reduce_139, 1, 161, :_reduce_none, 1, 161, :_reduce_none, 1, 163, :_reduce_none, 2, 163, :_reduce_143, 1, 164, :_reduce_none, 1, 164, :_reduce_none, 6, 119, :_reduce_146, 5, 119, :_reduce_147, 1, 165, :_reduce_148, 3, 165, :_reduce_149, 1, 167, :_reduce_150, 1, 167, :_reduce_151, 1, 167, :_reduce_152, 1, 167, :_reduce_none, 1, 168, :_reduce_154, 3, 168, :_reduce_155, 1, 169, :_reduce_none, 1, 169, :_reduce_none, 1, 166, :_reduce_none, 2, 166, :_reduce_159, 1, 159, :_reduce_160, 1, 159, :_reduce_161, 1, 160, :_reduce_162, 2, 160, :_reduce_163, 4, 160, :_reduce_164, 1, 139, :_reduce_165, 3, 139, :_reduce_166, - 3, 170, :_reduce_167, - 1, 170, :_reduce_168, - 1, 111, :_reduce_169, - 1, 121, :_reduce_170, - 1, 121, :_reduce_171, - 1, 121, :_reduce_172, - 1, 121, :_reduce_173, - 3, 122, :_reduce_174, - 4, 122, :_reduce_175, - 2, 122, :_reduce_176, - 3, 122, :_reduce_177, - 4, 122, :_reduce_178, - 2, 122, :_reduce_179, - 3, 125, :_reduce_180, - 4, 125, :_reduce_181, - 2, 125, :_reduce_182, - 1, 171, :_reduce_183, - 3, 171, :_reduce_184, - 3, 172, :_reduce_185, + 1, 170, :_reduce_none, + 1, 170, :_reduce_none, + 1, 171, :_reduce_none, + 1, 171, :_reduce_none, + 3, 173, :_reduce_171, + 1, 173, :_reduce_172, + 2, 174, :_reduce_173, + 2, 172, :_reduce_174, + 1, 175, :_reduce_175, + 4, 175, :_reduce_176, + 1, 111, :_reduce_177, + 1, 121, :_reduce_178, + 1, 121, :_reduce_179, + 1, 121, :_reduce_180, + 1, 121, :_reduce_181, + 3, 122, :_reduce_182, + 4, 122, :_reduce_183, + 2, 122, :_reduce_184, + 3, 122, :_reduce_185, + 4, 122, :_reduce_186, + 2, 122, :_reduce_187, + 3, 125, :_reduce_188, + 4, 125, :_reduce_189, + 2, 125, :_reduce_190, + 1, 176, :_reduce_191, + 3, 176, :_reduce_192, + 3, 177, :_reduce_193, 1, 132, :_reduce_none, 1, 132, :_reduce_none, 1, 132, :_reduce_none, - 1, 173, :_reduce_189, - 1, 173, :_reduce_190, - 2, 174, :_reduce_191, - 1, 176, :_reduce_192, - 1, 178, :_reduce_193, - 1, 179, :_reduce_194, - 2, 177, :_reduce_195, - 1, 180, :_reduce_196, - 1, 181, :_reduce_197, - 2, 181, :_reduce_198, - 2, 175, :_reduce_199, - 2, 182, :_reduce_200, - 2, 182, :_reduce_201, - 3, 97, :_reduce_202, - 0, 183, :_reduce_203, - 2, 183, :_reduce_204, - 4, 183, :_reduce_205, - 1, 120, :_reduce_206, - 3, 120, :_reduce_207, - 5, 120, :_reduce_208, - 1, 184, :_reduce_none, - 1, 184, :_reduce_none, - 1, 128, :_reduce_211, - 1, 131, :_reduce_212, - 1, 129, :_reduce_213, - 1, 130, :_reduce_214, - 1, 124, :_reduce_215, - 1, 123, :_reduce_216, - 1, 126, :_reduce_217, + 1, 178, :_reduce_197, + 1, 178, :_reduce_198, + 2, 179, :_reduce_199, + 1, 181, :_reduce_200, + 1, 183, :_reduce_201, + 1, 184, :_reduce_202, + 2, 182, :_reduce_203, + 1, 185, :_reduce_204, + 1, 186, :_reduce_205, + 2, 186, :_reduce_206, + 2, 180, :_reduce_207, + 2, 187, :_reduce_208, + 2, 187, :_reduce_209, + 3, 97, :_reduce_210, + 0, 188, :_reduce_211, + 2, 188, :_reduce_212, + 4, 188, :_reduce_213, + 1, 120, :_reduce_214, + 3, 120, :_reduce_215, + 5, 120, :_reduce_216, + 1, 189, :_reduce_none, + 1, 189, :_reduce_none, + 1, 128, :_reduce_219, + 1, 131, :_reduce_220, + 1, 129, :_reduce_221, + 1, 130, :_reduce_222, + 1, 124, :_reduce_223, + 1, 123, :_reduce_224, + 1, 126, :_reduce_225, 0, 133, :_reduce_none, - 1, 133, :_reduce_219, + 1, 133, :_reduce_227, 0, 150, :_reduce_none, 1, 150, :_reduce_none, 1, 158, :_reduce_none, 1, 158, :_reduce_none, 1, 158, :_reduce_none, 1, 158, :_reduce_none, 1, 158, :_reduce_none, 1, 158, :_reduce_none, 1, 158, :_reduce_none, 1, 158, :_reduce_none, 1, 158, :_reduce_none, 1, 158, :_reduce_none, 1, 158, :_reduce_none, 1, 158, :_reduce_none, 1, 158, :_reduce_none, 1, 158, :_reduce_none, - 0, 98, :_reduce_236 ] + 0, 98, :_reduce_244 ] -racc_reduce_n = 237 +racc_reduce_n = 245 -racc_shift_n = 410 +racc_shift_n = 422 racc_token_table = { false => 0, :error => 1, :STRING => 2, :DQPRE => 3, :DQMID => 4, :DQPOST => 5, :WORD => 6, :LBRACK => 7, :RBRACK => 8, :LBRACE => 9, :RBRACE => 10, :SYMBOL => 11, :FARROW => 12, :COMMA => 13, :TRUE => 14, :FALSE => 15, :EQUALS => 16, :APPENDS => 17, :DELETES => 18, :LESSEQUAL => 19, :NOTEQUAL => 20, :DOT => 21, :COLON => 22, :LLCOLLECT => 23, :RRCOLLECT => 24, :QMARK => 25, :LPAREN => 26, :RPAREN => 27, :ISEQUAL => 28, :GREATEREQUAL => 29, :GREATERTHAN => 30, :LESSTHAN => 31, :IF => 32, :ELSE => 33, :DEFINE => 34, :ELSIF => 35, :VARIABLE => 36, :CLASS => 37, :INHERITS => 38, :NODE => 39, :BOOLEAN => 40, :NAME => 41, :SEMIC => 42, :CASE => 43, :DEFAULT => 44, :AT => 45, :ATAT => 46, :LCOLLECT => 47, :RCOLLECT => 48, :CLASSREF => 49, :NOT => 50, :OR => 51, :AND => 52, :UNDEF => 53, :PARROW => 54, :PLUS => 55, :MINUS => 56, :TIMES => 57, :DIV => 58, :LSHIFT => 59, :RSHIFT => 60, :UMINUS => 61, :MATCH => 62, :NOMATCH => 63, :REGEX => 64, :IN_EDGE => 65, :OUT_EDGE => 66, :IN_EDGE_SUB => 67, :OUT_EDGE_SUB => 68, :IN => 69, :UNLESS => 70, :PIPE => 71, :LAMBDA => 72, :SELBRACE => 73, :NUMBER => 74, :HEREDOC => 75, :SUBLOCATE => 76, :RENDER_STRING => 77, :RENDER_EXPR => 78, :EPP_START => 79, :EPP_END => 80, :EPP_END_TRIM => 81, :FUNCTION => 82, :PRIVATE => 83, :ATTR => 84, :TYPE => 85, :LOW => 86, :HIGH => 87, :CALL => 88, :LISTSTART => 89, :SPLAT => 90, :MODULO => 91, :TITLE_COLON => 92, :CASE_COLON => 93 } racc_nt_base = 94 racc_use_result_var = true Racc_arg = [ racc_action_table, racc_action_check, racc_action_default, racc_action_pointer, racc_goto_table, racc_goto_check, racc_goto_default, racc_goto_pointer, racc_nt_base, racc_reduce_table, racc_token_table, racc_shift_n, racc_reduce_n, racc_use_result_var ] Racc_token_to_s_table = [ "$end", "error", "STRING", "DQPRE", "DQMID", "DQPOST", "WORD", "LBRACK", "RBRACK", "LBRACE", "RBRACE", "SYMBOL", "FARROW", "COMMA", "TRUE", "FALSE", "EQUALS", "APPENDS", "DELETES", "LESSEQUAL", "NOTEQUAL", "DOT", "COLON", "LLCOLLECT", "RRCOLLECT", "QMARK", "LPAREN", "RPAREN", "ISEQUAL", "GREATEREQUAL", "GREATERTHAN", "LESSTHAN", "IF", "ELSE", "DEFINE", "ELSIF", "VARIABLE", "CLASS", "INHERITS", "NODE", "BOOLEAN", "NAME", "SEMIC", "CASE", "DEFAULT", "AT", "ATAT", "LCOLLECT", "RCOLLECT", "CLASSREF", "NOT", "OR", "AND", "UNDEF", "PARROW", "PLUS", "MINUS", "TIMES", "DIV", "LSHIFT", "RSHIFT", "UMINUS", "MATCH", "NOMATCH", "REGEX", "IN_EDGE", "OUT_EDGE", "IN_EDGE_SUB", "OUT_EDGE_SUB", "IN", "UNLESS", "PIPE", "LAMBDA", "SELBRACE", "NUMBER", "HEREDOC", "SUBLOCATE", "RENDER_STRING", "RENDER_EXPR", "EPP_START", "EPP_END", "EPP_END_TRIM", "FUNCTION", "PRIVATE", "ATTR", "TYPE", "LOW", "HIGH", "CALL", "LISTSTART", "SPLAT", "MODULO", "TITLE_COLON", "CASE_COLON", "$start", "program", "statements", "epp_expression", "nil", "syntactic_statements", "syntactic_statement", "any_expression", "relationship_expression", "resource_expression", "expression", "higher_precedence", "expressions", "selector_entries", "call_function_expression", "primary_expression", "literal_expression", "variable", "call_method_with_lambda_expression", "collection_expression", "case_expression", "if_expression", "unless_expression", "definition_expression", "hostclass_expression", "node_definition_expression", "epp_render_expression", "reserved_word", "array", "boolean", "default", "hash", "regex", "text_or_name", "number", "type", "undef", "name", "quotedtext", "endcomma", "lambda", "call_method_expression", "named_access", "lambda_parameter_list", "lambda_rest", "parameters", "if_part", "else", "unless_else", "case_options", "case_option", "case_colon", "selector_entry", "selector_entry_list", "at", "resourceinstances", "endsemi", "attribute_operations", "resourceinst", "title_colon", "collect_query", "optional_query", "attribute_operation", "attribute_name", "keyword", "classname", "parameter_list", "opt_statements", "stacked_classname", "classparent", "classnameordefault", "hostnames", "nodeparent", "hostname", "dotted_name", "name_or_number", "parameter", + "untyped_parameter", + "typed_parameter", + "regular_parameter", + "splat_parameter", + "parameter_type", "hashpairs", "hashpair", "string", "dq_string", "heredoc", "dqpre", "dqrval", "dqpost", "dqmid", "text_expression", "dqtail", "sublocated_text", "epp_parameters_list", "epp_end" ] Racc_debug_parser = false ##### State transition tables end ##### # reduce 0 omitted module_eval(<<'.,.,', 'egrammar.ra', 68) def _reduce_1(val, _values, result) result = create_program(Factory.block_or_expression(*val[0])) result end .,., module_eval(<<'.,.,', 'egrammar.ra', 69) def _reduce_2(val, _values, result) result = create_program(Factory.block_or_expression(*val[0])) result end .,., # reduce 3 omitted module_eval(<<'.,.,', 'egrammar.ra', 74) def _reduce_4(val, _values, result) result = transform_calls(val[0]) result end .,., module_eval(<<'.,.,', 'egrammar.ra', 80) def _reduce_5(val, _values, result) result = [val[0]] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 81) def _reduce_6(val, _values, result) result = val[0].push val[2] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 82) def _reduce_7(val, _values, result) result = val[0].push val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 86) def _reduce_8(val, _values, result) result = val[0] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 87) def _reduce_9(val, _values, result) result = aryfy(val[0]).push val[2] result end .,., # reduce 10 omitted module_eval(<<'.,.,', 'egrammar.ra', 93) def _reduce_11(val, _values, result) result = val[0] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 94) def _reduce_12(val, _values, result) result = val[0].relop(val[1][:value], val[2]); loc result, val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 95) def _reduce_13(val, _values, result) result = val[0].relop(val[1][:value], val[2]); loc result, val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 96) def _reduce_14(val, _values, result) result = val[0].relop(val[1][:value], val[2]); loc result, val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 97) def _reduce_15(val, _values, result) result = val[0].relop(val[1][:value], val[2]); loc result, val[1] result end .,., # reduce 16 omitted module_eval(<<'.,.,', 'egrammar.ra', 104) def _reduce_17(val, _values, result) result = val[0][*val[2]] ; loc result, val[0], val[3] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 105) def _reduce_18(val, _values, result) result = val[0].in val[2] ; loc result, val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 106) def _reduce_19(val, _values, result) result = val[0] =~ val[2] ; loc result, val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 107) def _reduce_20(val, _values, result) result = val[0].mne val[2] ; loc result, val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 108) def _reduce_21(val, _values, result) result = val[0] + val[2] ; loc result, val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 109) def _reduce_22(val, _values, result) result = val[0] - val[2] ; loc result, val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 110) def _reduce_23(val, _values, result) result = val[0] / val[2] ; loc result, val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 111) def _reduce_24(val, _values, result) result = val[0] * val[2] ; loc result, val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 112) def _reduce_25(val, _values, result) result = val[0] % val[2] ; loc result, val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 113) def _reduce_26(val, _values, result) result = val[0] << val[2] ; loc result, val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 114) def _reduce_27(val, _values, result) result = val[0] >> val[2] ; loc result, val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 115) def _reduce_28(val, _values, result) result = val[1].minus() ; loc result, val[0] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 116) def _reduce_29(val, _values, result) result = val[1].unfold() ; loc result, val[0] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 117) def _reduce_30(val, _values, result) result = val[0].ne val[2] ; loc result, val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 118) def _reduce_31(val, _values, result) result = val[0] == val[2] ; loc result, val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 119) def _reduce_32(val, _values, result) result = val[0] > val[2] ; loc result, val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 120) def _reduce_33(val, _values, result) result = val[0] >= val[2] ; loc result, val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 121) def _reduce_34(val, _values, result) result = val[0] < val[2] ; loc result, val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 122) def _reduce_35(val, _values, result) result = val[0] <= val[2] ; loc result, val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 123) def _reduce_36(val, _values, result) result = val[1].not ; loc result, val[0] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 124) def _reduce_37(val, _values, result) result = val[0].and val[2] ; loc result, val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 125) def _reduce_38(val, _values, result) result = val[0].or val[2] ; loc result, val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 126) def _reduce_39(val, _values, result) result = val[0].set(val[2]) ; loc result, val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 127) def _reduce_40(val, _values, result) result = val[0].plus_set(val[2]) ; loc result, val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 128) def _reduce_41(val, _values, result) result = val[0].minus_set(val[2]); loc result, val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 129) def _reduce_42(val, _values, result) result = val[0].select(*val[2]) ; loc result, val[0] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 130) def _reduce_43(val, _values, result) result = val[1].paren() ; loc result, val[0] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 138) def _reduce_44(val, _values, result) result = [val[0]] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 139) def _reduce_45(val, _values, result) result = val[0].push(val[2]) result end .,., # reduce 46 omitted # reduce 47 omitted # reduce 48 omitted # reduce 49 omitted # reduce 50 omitted # reduce 51 omitted # reduce 52 omitted # reduce 53 omitted # reduce 54 omitted # reduce 55 omitted # reduce 56 omitted # reduce 57 omitted # reduce 58 omitted # reduce 59 omitted # reduce 60 omitted # reduce 61 omitted # reduce 62 omitted # reduce 63 omitted # reduce 64 omitted # reduce 65 omitted # reduce 66 omitted # reduce 67 omitted module_eval(<<'.,.,', 'egrammar.ra', 172) def _reduce_68(val, _values, result) result = val[0] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 173) def _reduce_69(val, _values, result) result = val[0] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 181) def _reduce_70(val, _values, result) result = Factory.CALL_NAMED(val[0], true, val[2]) loc result, val[0], val[4] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 185) def _reduce_71(val, _values, result) result = Factory.CALL_NAMED(val[0], true, []) loc result, val[0], val[2] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 189) def _reduce_72(val, _values, result) result = Factory.CALL_NAMED(val[0], true, val[2]) loc result, val[0], val[4] result.lambda = val[5] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 194) def _reduce_73(val, _values, result) result = Factory.CALL_NAMED(val[0], true, []) loc result, val[0], val[2] result.lambda = val[3] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 198) def _reduce_74(val, _values, result) result = val[0] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 203) def _reduce_75(val, _values, result) result = val[0] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 204) def _reduce_76(val, _values, result) result = val[0]; val[0].lambda = val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 207) def _reduce_77(val, _values, result) result = Factory.CALL_METHOD(val[0], val[2]); loc result, val[1], val[3] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 208) def _reduce_78(val, _values, result) result = Factory.CALL_METHOD(val[0], []); loc result, val[1], val[3] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 209) def _reduce_79(val, _values, result) result = Factory.CALL_METHOD(val[0], []); loc result, val[0] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 214) def _reduce_80(val, _values, result) result = val[0].dot(Factory.fqn(val[2][:value])) loc result, val[1], val[2] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 226) def _reduce_81(val, _values, result) result = Factory.LAMBDA(val[0], val[1]) # loc result, val[1] # TODO result end .,., module_eval(<<'.,.,', 'egrammar.ra', 231) def _reduce_82(val, _values, result) result = val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 232) def _reduce_83(val, _values, result) result = nil result end .,., module_eval(<<'.,.,', 'egrammar.ra', 236) def _reduce_84(val, _values, result) result = [] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 237) def _reduce_85(val, _values, result) result = val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 247) def _reduce_86(val, _values, result) result = val[1] loc(result, val[0], val[1]) result end .,., module_eval(<<'.,.,', 'egrammar.ra', 254) def _reduce_87(val, _values, result) result = Factory.IF(val[0], Factory.block_or_expression(*val[2]), val[4]) loc(result, val[0], (val[4] ? val[4] : val[3])) result end .,., module_eval(<<'.,.,', 'egrammar.ra', 258) def _reduce_88(val, _values, result) result = Factory.IF(val[0], nil, val[3]) loc(result, val[0], (val[3] ? val[3] : val[2])) result end .,., # reduce 89 omitted module_eval(<<'.,.,', 'egrammar.ra', 266) def _reduce_90(val, _values, result) result = val[1] loc(result, val[0], val[1]) result end .,., module_eval(<<'.,.,', 'egrammar.ra', 270) def _reduce_91(val, _values, result) result = Factory.block_or_expression(*val[2]) loc result, val[0], val[3] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 274) def _reduce_92(val, _values, result) result = nil # don't think a nop is needed here either result end .,., module_eval(<<'.,.,', 'egrammar.ra', 283) def _reduce_93(val, _values, result) result = Factory.UNLESS(val[1], Factory.block_or_expression(*val[3]), val[5]) loc result, val[0], val[4] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 287) def _reduce_94(val, _values, result) result = Factory.UNLESS(val[1], nil, nil) loc result, val[0], val[4] result end .,., # reduce 95 omitted module_eval(<<'.,.,', 'egrammar.ra', 297) def _reduce_96(val, _values, result) result = Factory.block_or_expression(*val[2]) loc result, val[0], val[3] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 301) def _reduce_97(val, _values, result) result = nil # don't think a nop is needed here either result end .,., module_eval(<<'.,.,', 'egrammar.ra', 309) def _reduce_98(val, _values, result) result = Factory.CASE(val[1], *val[3]) loc result, val[0], val[4] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 315) def _reduce_99(val, _values, result) result = [val[0]] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 316) def _reduce_100(val, _values, result) result = val[0].push val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 321) def _reduce_101(val, _values, result) result = Factory.WHEN(val[0], val[3]) loc result, val[1], val[4] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 325) def _reduce_102(val, _values, result) result = Factory.WHEN(val[0], nil) loc result, val[1], val[3] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 329) def _reduce_103(val, _values, result) result = val[0] result end .,., # reduce 104 omitted module_eval(<<'.,.,', 'egrammar.ra', 340) def _reduce_105(val, _values, result) result = val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 345) def _reduce_106(val, _values, result) result = [val[0]] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 346) def _reduce_107(val, _values, result) result = val[0].push val[2] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 351) def _reduce_108(val, _values, result) result = Factory.MAP(val[0], val[2]) ; loc result, val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 363) def _reduce_109(val, _values, result) result = val[0] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 366) def _reduce_110(val, _values, result) result = case Factory.resource_shape(val[1]) when :resource, :class tmp = Factory.RESOURCE(Factory.fqn(token_text(val[1])), val[3]) tmp.form = val[0] tmp when :defaults error val[1], "A resource default can not be virtual or exported" when :override error val[1], "A resource override can not be virtual or exported" else error val[1], "Expression is not valid as a resource, resource-default, or resource-override" end loc result, val[1], val[4] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 381) def _reduce_111(val, _values, result) result = case Factory.resource_shape(val[1]) when :resource, :class, :defaults, :override error val[1], "Defaults are not virtualizable" else error val[1], "Expression is not valid as a resource, resource-default, or resource-override" end result end .,., module_eval(<<'.,.,', 'egrammar.ra', 389) def _reduce_112(val, _values, result) result = case Factory.resource_shape(val[0]) when :resource, :class Factory.RESOURCE(Factory.fqn(token_text(val[0])), val[2]) when :defaults error val[1], "A resource default can not specify a resource name" when :override error val[1], "A resource override does not allow override of name of resource" else error val[1], "Expression is not valid as a resource, resource-default, or resource-override" end loc result, val[0], val[4] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 402) def _reduce_113(val, _values, result) result = case Factory.resource_shape(val[0]) when :resource, :class # This catches deprecated syntax. # If the attribute operations does not include +>, then the found expression # is actually a LEFT followed by LITERAL_HASH # unless tmp = transform_resource_wo_title(val[0], val[2]) error val[1], "Syntax error resource body without title or hash with +>" end tmp when :defaults Factory.RESOURCE_DEFAULTS(val[0], val[2]) when :override # This was only done for override in original - TODO shuld it be here at all Factory.RESOURCE_OVERRIDE(val[0], val[2]) else error val[0], "Expression is not valid as a resource, resource-default, or resource-override" end loc result, val[0], val[4] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 423) def _reduce_114(val, _values, result) result = Factory.RESOURCE(Factory.fqn(token_text(val[1])), val[3]) result.form = val[0] loc result, val[1], val[5] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 428) def _reduce_115(val, _values, result) result = Factory.RESOURCE(Factory.fqn(token_text(val[0])), val[2]) loc result, val[0], val[4] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 433) def _reduce_116(val, _values, result) result = Factory.RESOURCE_BODY(val[0], val[2]) result end .,., module_eval(<<'.,.,', 'egrammar.ra', 435) def _reduce_117(val, _values, result) result = val[0] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 438) def _reduce_118(val, _values, result) result = [val[0]] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 439) def _reduce_119(val, _values, result) result = val[0].push val[2] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 444) def _reduce_120(val, _values, result) result = :virtual result end .,., module_eval(<<'.,.,', 'egrammar.ra', 445) def _reduce_121(val, _values, result) result = :exported result end .,., module_eval(<<'.,.,', 'egrammar.ra', 446) def _reduce_122(val, _values, result) result = :exported result end .,., module_eval(<<'.,.,', 'egrammar.ra', 458) def _reduce_123(val, _values, result) result = Factory.COLLECT(val[0], val[1], val[3]) loc result, val[0], val[5] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 462) def _reduce_124(val, _values, result) result = Factory.COLLECT(val[0], val[1], []) loc result, val[0], val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 467) def _reduce_125(val, _values, result) result = Factory.VIRTUAL_QUERY(val[1]) ; loc result, val[0], val[2] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 468) def _reduce_126(val, _values, result) result = Factory.EXPORTED_QUERY(val[1]) ; loc result, val[0], val[2] result end .,., # reduce 127 omitted # reduce 128 omitted module_eval(<<'.,.,', 'egrammar.ra', 481) def _reduce_129(val, _values, result) result = [] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 482) def _reduce_130(val, _values, result) result = [val[0]] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 483) def _reduce_131(val, _values, result) result = val[0].push(val[2]) result end .,., # reduce 132 omitted # reduce 133 omitted # reduce 134 omitted module_eval(<<'.,.,', 'egrammar.ra', 499) def _reduce_135(val, _values, result) result = Factory.ATTRIBUTE_OP(val[0][:value], :'=>', val[2]) loc result, val[0], val[2] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 503) def _reduce_136(val, _values, result) result = Factory.ATTRIBUTE_OP(val[0][:value], :'+>', val[2]) loc result, val[0], val[2] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 513) def _reduce_137(val, _values, result) result = add_definition(Factory.DEFINITION(classname(val[1][:value]), val[2], val[4])) loc result, val[0], val[5] # New lexer does not keep track of this, this is done in validation if @lexer.respond_to?(:'indefine=') @lexer.indefine = false end result end .,., module_eval(<<'.,.,', 'egrammar.ra', 527) def _reduce_138(val, _values, result) # Remove this class' name from the namestack as all nested classes have been parsed namepop result = add_definition(Factory.HOSTCLASS(classname(val[1][:value]), val[2], token_text(val[3]), val[5])) loc result, val[0], val[6] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 537) def _reduce_139(val, _values, result) namestack(val[0][:value]) ; result = val[0] result end .,., # reduce 140 omitted # reduce 141 omitted # reduce 142 omitted module_eval(<<'.,.,', 'egrammar.ra', 546) def _reduce_143(val, _values, result) result = val[1] result end .,., # reduce 144 omitted # reduce 145 omitted module_eval(<<'.,.,', 'egrammar.ra', 563) def _reduce_146(val, _values, result) result = add_definition(Factory.NODE(val[1], val[2], val[4])) loc result, val[0], val[5] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 567) def _reduce_147(val, _values, result) result = add_definition(Factory.NODE(val[1], val[2], nil)) loc result, val[0], val[4] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 577) def _reduce_148(val, _values, result) result = [result] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 578) def _reduce_149(val, _values, result) result = val[0].push(val[2]) result end .,., module_eval(<<'.,.,', 'egrammar.ra', 583) def _reduce_150(val, _values, result) result = val[0] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 584) def _reduce_151(val, _values, result) result = val[0] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 585) def _reduce_152(val, _values, result) result = Factory.literal(:default); loc result, val[0] result end .,., # reduce 153 omitted module_eval(<<'.,.,', 'egrammar.ra', 589) def _reduce_154(val, _values, result) result = Factory.literal(val[0][:value]); loc result, val[0] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 590) def _reduce_155(val, _values, result) result = Factory.concat(val[0], '.', val[2][:value]); loc result, val[0], val[2] result end .,., # reduce 156 omitted # reduce 157 omitted # reduce 158 omitted module_eval(<<'.,.,', 'egrammar.ra', 599) def _reduce_159(val, _values, result) result = val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 615) def _reduce_160(val, _values, result) result = val[0] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 616) def _reduce_161(val, _values, result) error val[0], "'class' is not a valid classname" result end .,., module_eval(<<'.,.,', 'egrammar.ra', 620) def _reduce_162(val, _values, result) result = [] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 621) def _reduce_163(val, _values, result) result = [] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 622) def _reduce_164(val, _values, result) result = val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 626) def _reduce_165(val, _values, result) result = [val[0]] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 627) def _reduce_166(val, _values, result) result = val[0].push(val[2]) result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 631) - def _reduce_167(val, _values, result) +# reduce 167 omitted + +# reduce 168 omitted + +# reduce 169 omitted + +# reduce 170 omitted + +module_eval(<<'.,.,', 'egrammar.ra', 639) + def _reduce_171(val, _values, result) result = Factory.PARAM(val[0][:value], val[2]) ; loc result, val[0] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 632) - def _reduce_168(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 640) + def _reduce_172(val, _values, result) result = Factory.PARAM(val[0][:value]); loc result, val[0] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 645) - def _reduce_169(val, _values, result) - result = Factory.fqn(val[0][:value]).var ; loc result, val[0] +module_eval(<<'.,.,', 'egrammar.ra', 643) + def _reduce_173(val, _values, result) + result = val[1]; val[1].captures_rest() + result + end +.,., + +module_eval(<<'.,.,', 'egrammar.ra', 646) + def _reduce_174(val, _values, result) + val[1].type_expr(val[0]) ; result = val[1] + result + end +.,., + +module_eval(<<'.,.,', 'egrammar.ra', 649) + def _reduce_175(val, _values, result) + result = val[0] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 650) - def _reduce_170(val, _values, result) + def _reduce_176(val, _values, result) + result = val[0][*val[2]] ; loc result, val[0], val[3] + result + end +.,., + +module_eval(<<'.,.,', 'egrammar.ra', 663) + def _reduce_177(val, _values, result) + result = Factory.fqn(val[0][:value]).var ; loc result, val[0] + result + end +.,., + +module_eval(<<'.,.,', 'egrammar.ra', 668) + def _reduce_178(val, _values, result) result = Factory.RESERVED(val[0][:value]) ; loc result, val[0] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 651) - def _reduce_171(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 669) + def _reduce_179(val, _values, result) result = Factory.RESERVED(val[0][:value]) ; loc result, val[0] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 652) - def _reduce_172(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 670) + def _reduce_180(val, _values, result) result = Factory.RESERVED(val[0][:value]) ; loc result, val[0] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 653) - def _reduce_173(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 671) + def _reduce_181(val, _values, result) result = Factory.RESERVED(val[0][:value]) ; loc result, val[0] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 659) - def _reduce_174(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 677) + def _reduce_182(val, _values, result) result = Factory.LIST(val[1]); loc result, val[0], val[2] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 660) - def _reduce_175(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 678) + def _reduce_183(val, _values, result) result = Factory.LIST(val[1]); loc result, val[0], val[3] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 661) - def _reduce_176(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 679) + def _reduce_184(val, _values, result) result = Factory.literal([]) ; loc result, val[0] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 662) - def _reduce_177(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 680) + def _reduce_185(val, _values, result) result = Factory.LIST(val[1]); loc result, val[0], val[2] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 663) - def _reduce_178(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 681) + def _reduce_186(val, _values, result) result = Factory.LIST(val[1]); loc result, val[0], val[3] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 664) - def _reduce_179(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 682) + def _reduce_187(val, _values, result) result = Factory.literal([]) ; loc result, val[0] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 667) - def _reduce_180(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 685) + def _reduce_188(val, _values, result) result = Factory.HASH(val[1]); loc result, val[0], val[2] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 668) - def _reduce_181(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 686) + def _reduce_189(val, _values, result) result = Factory.HASH(val[1]); loc result, val[0], val[3] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 669) - def _reduce_182(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 687) + def _reduce_190(val, _values, result) result = Factory.literal({}) ; loc result, val[0], val[3] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 672) - def _reduce_183(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 690) + def _reduce_191(val, _values, result) result = [val[0]] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 673) - def _reduce_184(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 691) + def _reduce_192(val, _values, result) result = val[0].push val[2] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 676) - def _reduce_185(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 694) + def _reduce_193(val, _values, result) result = Factory.KEY_ENTRY(val[0], val[2]); loc result, val[1] result end .,., -# reduce 186 omitted +# reduce 194 omitted -# reduce 187 omitted +# reduce 195 omitted -# reduce 188 omitted +# reduce 196 omitted -module_eval(<<'.,.,', 'egrammar.ra', 684) - def _reduce_189(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 702) + def _reduce_197(val, _values, result) result = Factory.literal(val[0][:value]) ; loc result, val[0] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 685) - def _reduce_190(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 703) + def _reduce_198(val, _values, result) result = Factory.literal(val[0][:value]) ; loc result, val[0] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 687) - def _reduce_191(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 705) + def _reduce_199(val, _values, result) result = Factory.string(val[0], *val[1]) ; loc result, val[0], val[1][-1] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 688) - def _reduce_192(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 706) + def _reduce_200(val, _values, result) result = Factory.literal(val[0][:value]); loc result, val[0] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 689) - def _reduce_193(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 707) + def _reduce_201(val, _values, result) result = Factory.literal(val[0][:value]); loc result, val[0] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 690) - def _reduce_194(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 708) + def _reduce_202(val, _values, result) result = Factory.literal(val[0][:value]); loc result, val[0] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 691) - def _reduce_195(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 709) + def _reduce_203(val, _values, result) result = [val[0]] + val[1] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 692) - def _reduce_196(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 710) + def _reduce_204(val, _values, result) result = Factory.TEXT(val[0]) result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 695) - def _reduce_197(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 713) + def _reduce_205(val, _values, result) result = [val[0]] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 696) - def _reduce_198(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 714) + def _reduce_206(val, _values, result) result = [val[0]] + val[1] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 699) - def _reduce_199(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 717) + def _reduce_207(val, _values, result) result = Factory.HEREDOC(val[0][:value], val[1]); loc result, val[0] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 702) - def _reduce_200(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 720) + def _reduce_208(val, _values, result) result = Factory.SUBLOCATE(val[0], val[1]); loc result, val[0] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 703) - def _reduce_201(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 721) + def _reduce_209(val, _values, result) result = Factory.SUBLOCATE(val[0], val[1]); loc result, val[0] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 706) - def _reduce_202(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 724) + def _reduce_210(val, _values, result) result = Factory.EPP(val[1], val[2]); loc result, val[0] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 709) - def _reduce_203(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 727) + def _reduce_211(val, _values, result) result = nil result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 710) - def _reduce_204(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 728) + def _reduce_212(val, _values, result) result = [] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 711) - def _reduce_205(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 729) + def _reduce_213(val, _values, result) result = val[1] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 714) - def _reduce_206(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 732) + def _reduce_214(val, _values, result) result = Factory.RENDER_STRING(val[0][:value]); loc result, val[0] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 715) - def _reduce_207(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 733) + def _reduce_215(val, _values, result) result = Factory.RENDER_EXPR(val[1]); loc result, val[0], val[2] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 716) - def _reduce_208(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 734) + def _reduce_216(val, _values, result) result = Factory.RENDER_EXPR(Factory.block_or_expression(*val[2])); loc result, val[0], val[4] result end .,., -# reduce 209 omitted +# reduce 217 omitted -# reduce 210 omitted +# reduce 218 omitted -module_eval(<<'.,.,', 'egrammar.ra', 722) - def _reduce_211(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 740) + def _reduce_219(val, _values, result) result = Factory.NUMBER(val[0][:value]) ; loc result, val[0] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 723) - def _reduce_212(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 741) + def _reduce_220(val, _values, result) result = Factory.QNAME_OR_NUMBER(val[0][:value]) ; loc result, val[0] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 724) - def _reduce_213(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 742) + def _reduce_221(val, _values, result) result = Factory.QREF(val[0][:value]) ; loc result, val[0] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 725) - def _reduce_214(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 743) + def _reduce_222(val, _values, result) result = Factory.literal(:undef); loc result, val[0] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 726) - def _reduce_215(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 744) + def _reduce_223(val, _values, result) result = Factory.literal(:default); loc result, val[0] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 731) - def _reduce_216(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 749) + def _reduce_224(val, _values, result) result = Factory.literal(val[0][:value]) ; loc result, val[0] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 734) - def _reduce_217(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 752) + def _reduce_225(val, _values, result) result = Factory.literal(val[0][:value]); loc result, val[0] result end .,., -# reduce 218 omitted +# reduce 226 omitted -module_eval(<<'.,.,', 'egrammar.ra', 740) - def _reduce_219(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 758) + def _reduce_227(val, _values, result) result = nil result end .,., -# reduce 220 omitted - -# reduce 221 omitted - -# reduce 222 omitted - -# reduce 223 omitted - -# reduce 224 omitted - -# reduce 225 omitted - -# reduce 226 omitted - -# reduce 227 omitted - # reduce 228 omitted # reduce 229 omitted # reduce 230 omitted # reduce 231 omitted # reduce 232 omitted # reduce 233 omitted # reduce 234 omitted # reduce 235 omitted -module_eval(<<'.,.,', 'egrammar.ra', 763) - def _reduce_236(val, _values, result) +# reduce 236 omitted + +# reduce 237 omitted + +# reduce 238 omitted + +# reduce 239 omitted + +# reduce 240 omitted + +# reduce 241 omitted + +# reduce 242 omitted + +# reduce 243 omitted + +module_eval(<<'.,.,', 'egrammar.ra', 781) + def _reduce_244(val, _values, result) result = nil result end .,., def _reduce_none(val, _values, result) val[0] end end # class Parser end # module Parser end # module Pops end # module Puppet diff --git a/lib/puppet/pops/types/type_calculator.rb b/lib/puppet/pops/types/type_calculator.rb index 5df01a82a..46de35b7b 100644 --- a/lib/puppet/pops/types/type_calculator.rb +++ b/lib/puppet/pops/types/type_calculator.rb @@ -1,1597 +1,1601 @@ # The TypeCalculator can answer questions about puppet types. # # The Puppet type system is primarily based on sub-classing. When asking the type calculator to infer types from Ruby in general, it # may not provide the wanted answer; it does not for instance take module inclusions and extensions into account. In general the type # system should be unsurprising for anyone being exposed to the notion of type. The type `Data` may require a bit more explanation; this # is an abstract type that includes all scalar types, as well as Array with an element type compatible with Data, and Hash with key # compatible with scalar and elements compatible with Data. Expressed differently; Data is what you typically express using JSON (with # the exception that the Puppet type system also includes Pattern (regular expression) as a scalar. # # Inference # --------- # The `infer(o)` method infers a Puppet type for scalar Ruby objects, and for Arrays and Hashes. # The inference result is instance specific for single typed collections # and allows answering questions about its embedded type. It does not however preserve multiple types in # a collection, and can thus not answer questions like `[1,a].infer() =~ Array[Integer, String]` since the inference # computes the common type Scalar when combining Integer and String. # # The `infer_generic(o)` method infers a generic Puppet type for scalar Ruby object, Arrays and Hashes. # This inference result does not contain instance specific information; e.g. Array[Integer] where the integer # range is the generic default. Just `infer` it also combines types into a common type. # # The `infer_set(o)` method works like `infer` but preserves all type information. It does not do any # reduction into common types or ranges. This method of inference is best suited for answering questions # about an object being an instance of a type. It correctly answers: `[1,a].infer_set() =~ Array[Integer, String]` # # The `generalize!(t)` method modifies an instance specific inference result to a generic. The method mutates # the given argument. Basically, this removes string instances from String, and range from Integer and Float. # # Assignability # ------------- # The `assignable?(t1, t2)` method answers if t2 conforms to t1. The type t2 may be an instance, in which case # its type is inferred, or a type. # # Instance? # --------- # The `instance?(t, o)` method answers if the given object (instance) is an instance that is assignable to the given type. # # String # ------ # Creates a string representation of a type. # # Creation of Type instances # -------------------------- # Instance of the classes in the {Puppet::Pops::Types type model} are used to denote a specific type. It is most convenient # to use the {Puppet::Pops::Types::TypeFactory TypeFactory} when creating instances. # # @note # In general, new instances of the wanted type should be created as they are assigned to models using containment, and a # contained object can only be in one container at a time. Also, the type system may include more details in each type # instance, such as if it may be nil, be empty, contain a certain count etc. Or put differently, the puppet types are not # singletons. # # All types support `copy` which should be used when assigning a type where it is unknown if it is bound or not # to a parent type. A check can be made with `t.eContainer().nil?` # # Equality and Hash # ----------------- # Type instances are equal in terms of Ruby eql? and `==` if they describe the same type, but they are not `equal?` if they are not # the same type instance. Two types that describe the same type have identical hash - this makes them usable as hash keys. # # Types and Subclasses # -------------------- # In general, the type calculator should be used to answer questions if a type is a subtype of another (using {#assignable?}, or # {#instance?} if the question is if a given object is an instance of a given type (or is a subtype thereof). # Many of the types also have a Ruby subtype relationship; e.g. PHashType and PArrayType are both subtypes of PCollectionType, and # PIntegerType, PFloatType, PStringType,... are subtypes of PScalarType. Even if it is possible to answer certain questions about # type by looking at the Ruby class of the types this is considered an implementation detail, and such checks should in general # be performed by the type_calculator which implements the type system semantics. # # The PRubyType # ------------- # The PRubyType corresponds to a Ruby Class, except for the puppet types that are specialized (i.e. PRubyType should not be # used for Integer, String, etc. since there are specialized types for those). # When the type calculator deals with PRubyTypes and checks for assignability, it determines the "common ancestor class" of two classes. # This check is made based on the superclasses of the two classes being compared. In order to perform this, the classes must be present # (i.e. they are resolved from the string form in the PRubyType to a loaded, instantiated Ruby Class). In general this is not a problem, # since the question to produce the common super type for two objects means that the classes must be present or there would have been # no instances present in the first place. If however the classes are not present, the type calculator will fall back and state that # the two types at least have Object in common. # # @see Puppet::Pops::Types::TypeFactory TypeFactory for how to create instances of types # @see Puppet::Pops::Types::TypeParser TypeParser how to construct a type instance from a String # @see Puppet::Pops::Types Types for details about the type model # # Using the Type Calculator # ----- # The type calculator can be directly used via its class methods. If doing time critical work and doing many # calls to the type calculator, it is more performant to create an instance and invoke the corresponding # instance methods. Note that inference is an expensive operation, rather than infering the same thing # several times, it is in general better to infer once and then copy the result if mutation to a more generic form is # required. # # @api public # class Puppet::Pops::Types::TypeCalculator Types = Puppet::Pops::Types TheInfinity = 1.0 / 0.0 # because the Infinity symbol is not defined # @api public def self.assignable?(t1, t2) singleton.assignable?(t1,t2) end # Answers, does the given callable accept the arguments given in args (an array or a tuple) # @param callable [Puppet::Pops::Types::PCallableType] - the callable # @param args [Puppet::Pops::Types::PArrayType, Puppet::Pops::Types::PTupleType] args optionally including a lambda callable at the end # @return [Boolan] true if the callable accepts the arguments # # @api public def self.callable?(callable, args) singleton.callable?(callable, args) end # Produces a String representation of the given type. # @param t [Puppet::Pops::Types::PAbstractType] the type to produce a string form # @return [String] the type in string form # # @api public # def self.string(t) singleton.string(t) end # @api public def self.infer(o) singleton.infer(o) end # @api public def self.generalize!(o) singleton.generalize!(o) end # @api public def self.infer_set(o) singleton.infer_set(o) end # @api public def self.debug_string(t) singleton.debug_string(t) end # @api public def self.enumerable(t) singleton.enumerable(t) end # @api private def self.singleton() @tc_instance ||= new end # @api public # def initialize @@assignable_visitor ||= Puppet::Pops::Visitor.new(nil,"assignable",1,1) @@infer_visitor ||= Puppet::Pops::Visitor.new(nil,"infer",0,0) @@infer_set_visitor ||= Puppet::Pops::Visitor.new(nil,"infer_set",0,0) @@instance_of_visitor ||= Puppet::Pops::Visitor.new(nil,"instance_of",1,1) @@string_visitor ||= Puppet::Pops::Visitor.new(nil,"string",0,0) @@inspect_visitor ||= Puppet::Pops::Visitor.new(nil,"debug_string",0,0) @@enumerable_visitor ||= Puppet::Pops::Visitor.new(nil,"enumerable",0,0) @@extract_visitor ||= Puppet::Pops::Visitor.new(nil,"extract",0,0) @@generalize_visitor ||= Puppet::Pops::Visitor.new(nil,"generalize",0,0) @@callable_visitor ||= Puppet::Pops::Visitor.new(nil,"callable",1,1) da = Types::PArrayType.new() da.element_type = Types::PDataType.new() @data_array = da h = Types::PHashType.new() h.element_type = Types::PDataType.new() h.key_type = Types::PScalarType.new() @data_hash = h @data_t = Types::PDataType.new() @scalar_t = Types::PScalarType.new() @numeric_t = Types::PNumericType.new() @t = Types::PObjectType.new() # Data accepts a Tuple that has 0-infinity Data compatible entries (e.g. a Tuple equivalent to Array). data_tuple = Types::PTupleType.new() data_tuple.addTypes(Types::PDataType.new()) data_tuple.size_type = Types::PIntegerType.new() data_tuple.size_type.from = 0 data_tuple.size_type.to = nil # infinity @data_tuple_t = data_tuple # Variant type compatible with Data data_variant = Types::PVariantType.new() data_variant.addTypes(@data_hash.copy) data_variant.addTypes(@data_array.copy) data_variant.addTypes(Types::PScalarType.new) data_variant.addTypes(Types::PNilType.new) data_variant.addTypes(@data_tuple_t.copy) @data_variant_t = data_variant collection_default_size = Types::PIntegerType.new() collection_default_size.from = 0 collection_default_size.to = nil # infinity @collection_default_size_t = collection_default_size non_empty_string = Types::PStringType.new non_empty_string.size_type = Types::PIntegerType.new() non_empty_string.size_type.from = 1 non_empty_string.size_type.to = nil # infinity @non_empty_string_t = non_empty_string @nil_t = Types::PNilType.new end # Convenience method to get a data type for comparisons # @api private the returned value may not be contained in another element # def data @data_t end # Convenience method to get a variant compatible with the Data type. # @api private the returned value may not be contained in another element # def data_variant @data_variant_t end def self.data_variant singleton.data_variant end # Answers the question 'is it possible to inject an instance of the given class' # A class is injectable if it has a special *assisted inject* class method called `inject` taking # an injector and a scope as argument, or if it has a zero args `initialize` method. # # @param klazz [Class, PRubyType] the class/type to check if it is injectable # @return [Class, nil] the injectable Class, or nil if not injectable # @api public # def injectable_class(klazz) # Handle case when we get a PType instead of a class if klazz.is_a?(Types::PRubyType) klazz = Puppet::Pops::Types::ClassLoader.provide(klazz) end # data types can not be injected (check again, it is not safe to assume that given RubyType klazz arg was ok) return false unless type(klazz).is_a?(Types::PRubyType) if (klazz.respond_to?(:inject) && klazz.method(:inject).arity() == -4) || klazz.instance_method(:initialize).arity() == 0 klazz else nil end end # Answers 'can an instance of type t2 be assigned to a variable of type t'. # Does not accept nil/undef unless the type accepts it. # # @api public # def assignable?(t, t2) if t.is_a?(Class) t = type(t) end if t2.is_a?(Class) t2 = type(t2) end @@assignable_visitor.visit_this_1(self, t, t2) end # Returns an enumerable if the t represents something that can be iterated def enumerable(t) @@enumerable_visitor.visit_this_0(self, t) end # Answers, does the given callable accept the arguments given in args (an array or a tuple) # def callable?(callable, args) return false if !callable.is_a?(Types::PCallableType) # Note that polymorphism is for the args type, the callable is always a callable @@callable_visitor.visit_this_1(self, args, callable) end # Answers if the two given types describe the same type def equals(left, right) return false unless left.is_a?(Types::PAbstractType) && right.is_a?(Types::PAbstractType) # Types compare per class only - an extra test must be made if the are mutually assignable # to find all types that represent the same type of instance # left == right || (assignable?(right, left) && assignable?(left, right)) end # Answers 'what is the Puppet Type corresponding to the given Ruby class' # @param c [Class] the class for which a puppet type is wanted # @api public # def type(c) raise ArgumentError, "Argument must be a Class" unless c.is_a? Class # Can't use a visitor here since we don't have an instance of the class case when c <= Integer type = Types::PIntegerType.new() when c == Float type = Types::PFloatType.new() when c == Numeric type = Types::PNumericType.new() when c == String type = Types::PStringType.new() when c == Regexp type = Types::PRegexpType.new() when c == NilClass type = Types::PNilType.new() when c == FalseClass, c == TrueClass type = Types::PBooleanType.new() when c == Class type = Types::PType.new() when c == Array # Assume array of data values type = Types::PArrayType.new() type.element_type = Types::PDataType.new() when c == Hash # Assume hash with scalar keys and data values type = Types::PHashType.new() type.key_type = Types::PScalarType.new() type.element_type = Types::PDataType.new() else type = Types::PRubyType.new() type.ruby_class = c.name end type end # Generalizes value specific types. The given type is mutated and returned. # @api public def generalize!(o) @@generalize_visitor.visit_this_0(self, o) o.eAllContents.each { |x| @@generalize_visitor.visit_this_0(self, x) } o end def generalize_Object(o) # do nothing, there is nothing to change for most types end def generalize_PStringType(o) o.values = [] o.size_type = nil [] end def generalize_PCollectionType(o) # erase the size constraint from Array and Hash (if one exists, it is transformed to -Infinity - + Infinity, which is # not desirable. o.size_type = nil end def generalize_PFloatType(o) o.to = nil o.from = nil end def generalize_PIntegerType(o) o.to = nil o.from = nil end # Answers 'what is the single common Puppet Type describing o', or if o is an Array or Hash, what is the # single common type of the elements (or keys and elements for a Hash). # @api public # def infer(o) @@infer_visitor.visit_this_0(self, o) end def infer_generic(o) result = generalize!(infer(o)) result end # Answers 'what is the set of Puppet Types of o' # @api public # def infer_set(o) @@infer_set_visitor.visit_this_0(self, o) end def instance_of(t, o) @@instance_of_visitor.visit_this_1(self, t, o) end def instance_of_Object(t, o) # Undef is Undef and Object, but nothing else when checking instance? return false if (o.nil? || o == :undef) && t.class != Types::PObjectType assignable?(t, infer(o)) end def instance_of_PArrayType(t, o) return false unless o.is_a?(Array) return false unless o.all? {|element| instance_of(t.element_type, element) } size_t = t.size_type || @collection_default_size_t size_t2 = size_as_type(o) assignable?(size_t, size_t2) end def instance_of_PTupleType(t, o) return false unless o.is_a?(Array) # compute the tuple's min/max size, and check if that size matches size_t = t.size_type || Puppet::Pops::Types::TypeFactory.range(*t.size_range) # compute the array's size as type size_t2 = size_as_type(o) return false unless assignable?(size_t, size_t2) o.each_with_index do |element, index| return false unless instance_of(t.types[index] || t.types[-1], element) end true end def instance_of_PStructType(t, o) return false unless o.is_a?(Hash) h = t.hashed_elements # all keys must be present and have a value (even if nil/undef) (o.keys - h.keys).empty? && h.all? { |k,v| instance_of(v, o[k]) } end def instance_of_PHashType(t, o) return false unless o.is_a?(Hash) key_t = t.key_type element_t = t.element_type return false unless o.keys.all? {|key| instance_of(key_t, key) } && o.values.all? {|value| instance_of(element_t, value) } size_t = t.size_type || @collection_default_size_t size_t2 = size_as_type(o) assignable?(size_t, size_t2) end def instance_of_PDataType(t, o) instance_of(@data_variant_t, o) end def instance_of_PNilType(t, o) return o.nil? || o == :undef end def instance_of_POptionalType(t, o) return true if (o.nil? || o == :undef) instance_of(t.optional_type, o) end def instance_of_PVariantType(t, o) # instance of variant if o is instance? of any of variant's types t.types.any? { |option_t| instance_of(option_t, o) } end # Answers 'is o an instance of type t' # @api public # def self.instance?(t, o) singleton.instance_of(t,o) end # Answers 'is o an instance of type t' # @api public # def instance?(t, o) instance_of(t,o) end # Answers if t is a puppet type # @api public # def is_ptype?(t) return t.is_a?(Types::PAbstractType) end # Answers if t represents the puppet type PNilType # @api public # def is_pnil?(t) return t.nil? || t.is_a?(Types::PNilType) end # Answers, 'What is the common type of t1 and t2?' # # TODO: The current implementation should be optimized for performance # # @api public # def common_type(t1, t2) raise ArgumentError, 'two types expected' unless (is_ptype?(t1) || is_pnil?(t1)) && (is_ptype?(t2) || is_pnil?(t2)) # if either is nil, the common type is the other if is_pnil?(t1) return t2 elsif is_pnil?(t2) return t1 end # Simple case, one is assignable to the other if assignable?(t1, t2) return t1 elsif assignable?(t2, t1) return t2 end # when both are arrays, return an array with common element type if t1.is_a?(Types::PArrayType) && t2.is_a?(Types::PArrayType) type = Types::PArrayType.new() type.element_type = common_type(t1.element_type, t2.element_type) return type end # when both are hashes, return a hash with common key- and element type if t1.is_a?(Types::PHashType) && t2.is_a?(Types::PHashType) type = Types::PHashType.new() type.key_type = common_type(t1.key_type, t2.key_type) type.element_type = common_type(t1.element_type, t2.element_type) return type end # when both are host-classes, reduce to PHostClass[] (since one was not assignable to the other) if t1.is_a?(Types::PHostClassType) && t2.is_a?(Types::PHostClassType) return Types::PHostClassType.new() end # when both are resources, reduce to Resource[T] or Resource[] (since one was not assignable to the other) if t1.is_a?(Types::PResourceType) && t2.is_a?(Types::PResourceType) result = Types::PResourceType.new() # only Resource[] unless the type name is the same if t1.type_name == t2.type_name then result.type_name = t1.type_name end # the cross assignability test above has already determined that they do not have the same type and title return result end # Integers have range, expand the range to the common range if t1.is_a?(Types::PIntegerType) && t2.is_a?(Types::PIntegerType) t1range = from_to_ordered(t1.from, t1.to) t2range = from_to_ordered(t2.from, t2.to) t = Types::PIntegerType.new() from = [t1range[0], t2range[0]].min to = [t1range[1], t2range[1]].max t.from = from unless from == TheInfinity t.to = to unless to == TheInfinity return t end # Floats have range, expand the range to the common range if t1.is_a?(Types::PFloatType) && t2.is_a?(Types::PFloatType) t1range = from_to_ordered(t1.from, t1.to) t2range = from_to_ordered(t2.from, t2.to) t = Types::PFloatType.new() from = [t1range[0], t2range[0]].min to = [t1range[1], t2range[1]].max t.from = from unless from == TheInfinity t.to = to unless to == TheInfinity return t end if t1.is_a?(Types::PStringType) && t2.is_a?(Types::PStringType) t = Types::PStringType.new() t.values = t1.values | t2.values return t end if t1.is_a?(Types::PPatternType) && t2.is_a?(Types::PPatternType) t = Types::PPatternType.new() # must make copies since patterns are contained types, not data-types t.patterns = (t1.patterns | t2.patterns).map {|p| p.copy } return t end if t1.is_a?(Types::PEnumType) && t2.is_a?(Types::PEnumType) # The common type is one that complies with either set t = Types::PEnumType.new t.values = t1.values | t2.values return t end if t1.is_a?(Types::PVariantType) && t2.is_a?(Types::PVariantType) # The common type is one that complies with either set t = Types::PVariantType.new t.types = (t1.types | t2.types).map {|opt_t| opt_t.copy } return t end if t1.is_a?(Types::PRegexpType) && t2.is_a?(Types::PRegexpType) # if they were identical, the general rule would return a parameterized regexp # since they were not, the result is a generic regexp type return Types::PPatternType.new() end if t1.is_a?(Types::PCallableType) && t2.is_a?(Types::PCallableType) # They do not have the same signature, and one is not assignable to the other, # what remains is the most general form of Callable return Types::PCallableType.new() end # Common abstract types, from most specific to most general if common_numeric?(t1, t2) return Types::PNumericType.new() end if common_scalar?(t1, t2) return Types::PScalarType.new() end if common_data?(t1,t2) return Types::PDataType.new() end # Meta types Type[Integer] + Type[String] => Type[Data] if t1.is_a?(Types::PType) && t2.is_a?(Types::PType) type = Types::PType.new() type.type = common_type(t1.type, t2.type) return type end if t1.is_a?(Types::PRubyType) && t2.is_a?(Types::PRubyType) if t1.ruby_class == t2.ruby_class return t1 end # finding the common super class requires that names are resolved to class c1 = Types::ClassLoader.provide_from_type(t1) c2 = Types::ClassLoader.provide_from_type(t2) if c1 && c2 c2_superclasses = superclasses(c2) superclasses(c1).each do|c1_super| c2_superclasses.each do |c2_super| if c1_super == c2_super result = Types::PRubyType.new() result.ruby_class = c1_super.name return result end end end end end # If both are RubyObjects if common_pobject?(t1, t2) return Types::PObjectType.new() end end # Produces the superclasses of the given class, including the class def superclasses(c) result = [c] while s = c.superclass result << s c = s end result end # Produces a string representing the type # @api public # def string(t) @@string_visitor.visit_this_0(self, t) end # Produces a debug string representing the type (possibly with more information that the regular string format) # @api public # def debug_string(t) @@inspect_visitor.visit_this_0(self, t) end # Reduces an enumerable of types to a single common type. # @api public # def reduce_type(enumerable) enumerable.reduce(nil) {|memo, t| common_type(memo, t) } end # Reduce an enumerable of objects to a single common type # @api public # def infer_and_reduce_type(enumerable) reduce_type(enumerable.collect() {|o| infer(o) }) end # The type of all classes is PType # @api private # def infer_Class(o) Types::PType.new() end # @api private def infer_Closure(o) o.type() end # @api private def infer_Function(o) o.class.dispatcher.to_type end # @api private def infer_Object(o) type = Types::PRubyType.new() type.ruby_class = o.class.name type end # The type of all types is PType # @api private # def infer_PAbstractType(o) type = Types::PType.new() type.type = o.copy type end # The type of all types is PType # This is the metatype short circuit. # @api private # def infer_PType(o) type = Types::PType.new() type.type = o.copy type end # @api private def infer_String(o) t = Types::PStringType.new() t.addValues(o) t.size_type = size_as_type(o) t end # @api private def infer_Float(o) t = Types::PFloatType.new() t.from = o t.to = o t end # @api private def infer_Integer(o) t = Types::PIntegerType.new() t.from = o t.to = o t end # @api private def infer_Regexp(o) t = Types::PRegexpType.new() t.pattern = o.source t end # @api private def infer_NilClass(o) Types::PNilType.new() end # Inference of :undef as PNilType, all other are Ruby[Symbol] # @api private def infer_Symbol(o) o == :undef ? infer_NilClass(o) : infer_Object(o) end # @api private def infer_TrueClass(o) Types::PBooleanType.new() end # @api private def infer_FalseClass(o) Types::PBooleanType.new() end # @api private # A Puppet::Parser::Resource, or Puppet::Resource # def infer_Resource(o) t = Types::PResourceType.new() - t.type_name = o.type.to_s + t.type_name = o.type.to_s.downcase # Only Puppet::Resource can have a title that is a symbol :undef, a PResource cannot. # A mapping must be made to empty string. A nil value will result in an error later title = o.title t.title = (title == :undef ? '' : title) - t + type = Types::PType.new() + type.type = t + type end # @api private def infer_Array(o) type = Types::PArrayType.new() type.element_type = if o.empty? Types::PNilType.new() else infer_and_reduce_type(o) end type.size_type = size_as_type(o) type end # @api private def infer_Hash(o) type = Types::PHashType.new() if o.empty? ktype = Types::PNilType.new() etype = Types::PNilType.new() else ktype = infer_and_reduce_type(o.keys()) etype = infer_and_reduce_type(o.values()) end type.key_type = ktype type.element_type = etype type.size_type = size_as_type(o) type end def size_as_type(collection) size = collection.size t = Types::PIntegerType.new() t.from = size t.to = size t end # Common case for everything that intrinsically only has a single type def infer_set_Object(o) infer(o) end def infer_set_Array(o) if o.empty? type = Types::PArrayType.new() type.element_type = Types::PNilType.new() type.size_type = size_as_type(o) else type = Types::PTupleType.new() type.types = o.map() {|x| infer_set(x) } end type end def infer_set_Hash(o) type = Types::PHashType.new() if o.empty? ktype = Types::PNilType.new() etype = Types::PNilType.new() else ktype = Types::PVariantType.new() ktype.types = o.keys.map() {|k| infer_set(k) } etype = Types::PVariantType.new() etype.types = o.values.map() {|e| infer_set(e) } end type.key_type = unwrap_single_variant(ktype) type.element_type = unwrap_single_variant(vtype) type.size_type = size_as_type(o) type end def unwrap_single_variant(possible_variant) if possible_variant.is_a?(Types::PVariantType) && possible_variant.types.size == 1 possible_variant.types[0] else possible_variant end end # False in general type calculator # @api private def assignable_Object(t, t2) false end # @api private def assignable_PObjectType(t, t2) t2.is_a?(Types::PObjectType) end # @api private def assignable_PNilType(t, t2) # Only undef/nil is assignable to nil type t2.is_a?(Types::PNilType) end # @api private def assignable_PScalarType(t, t2) t2.is_a?(Types::PScalarType) end # @api private def assignable_PNumericType(t, t2) t2.is_a?(Types::PNumericType) end # @api private def assignable_PIntegerType(t, t2) return false unless t2.is_a?(Types::PIntegerType) trange = from_to_ordered(t.from, t.to) t2range = from_to_ordered(t2.from, t2.to) # If t2 min and max are within the range of t trange[0] <= t2range[0] && trange[1] >= t2range[1] end # Transform int range to a size constraint # if range == nil the constraint is 1,1 # if range.from == nil min size = 1 # if range.to == nil max size == Infinity # def size_range(range) return [1,1] if range.nil? from = range.from to = range.to x = from.nil? ? 1 : from y = to.nil? ? TheInfinity : to if x < y [x, y] else [y, x] end end # @api private def from_to_ordered(from, to) x = (from.nil? || from == :default) ? -TheInfinity : from y = (to.nil? || to == :default) ? TheInfinity : to if x < y [x, y] else [y, x] end end # @api private def assignable_PVariantType(t, t2) # Data is a specific variant t2 = @data_variant_t if t2.is_a?(Types::PDataType) if t2.is_a?(Types::PVariantType) # A variant is assignable if all of its options are assignable to one of this type's options return true if t == t2 t2.types.all? do |other| # if the other is a Variant, all if its options, but be assignable to one of this type's options other = other.is_a?(Types::PDataType) ? @data_variant_t : other if other.is_a?(Types::PVariantType) assignable?(t, other) else t.types.any? {|option_t| assignable?(option_t, other) } end end else # A variant is assignable if t2 is assignable to any of its types t.types.any? { |option_t| assignable?(option_t, t2) } end end # Catch all not callable combinations def callable_Object(o, callable_t) false end def callable_PTupleType(args_tuple, callable_t) if args_tuple.size_type raise ArgumentError, "Callable tuple may not have a size constraint when used as args" end # Assume no block was given - i.e. it is nil, and its type is PNilType block_t = @nil_t if args_tuple.types.last.is_a?(Types::PCallableType) # a split is needed to make it possible to use required, optional, and varargs semantics # of the tuple type. # args_tuple = args_tuple.copy # to drop the callable, it must be removed explicitly since this is an rgen array args_tuple.removeTypes(block_t = args_tuple.types.last()) else # no block was given, if it is required, the below will fail end # unless argument types match parameter types return false unless assignable?(callable_t.param_types, args_tuple) # unless given block (or no block) matches expected block (or no block) assignable?(callable_t.block_type || @nil_t, block_t) end def callable_PArrayType(args_array, callable_t) return false unless assignable?(callable_t.param_types, args_array) # does not support calling with a block, but have to check that callable expects it assignable?(callable_t.block_type || @nil_t, @nil_t) end def max(a,b) a >=b ? a : b end def min(a,b) a <= b ? a : b end def assignable_PTupleType(t, t2) return true if t == t2 || t.types.empty? && (t2.is_a?(Types::PArrayType)) size_t = t.size_type || Puppet::Pops::Types::TypeFactory.range(*t.size_range) if t2.is_a?(Types::PTupleType) size_t2 = t2.size_type || Puppet::Pops::Types::TypeFactory.range(*t2.size_range) # not assignable if the number of types in t2 is outside number of types in t1 - return false unless assignable?(size_t, size_t2) - max(t.types.size, t2.types.size).times do |index| - return false unless assignable?((t.types[index] || t.types[-1]), (t2.types[index] || t2.types[-1])) + if assignable?(size_t, size_t2) + t2.types.size.times do |index| + return false unless assignable?((t.types[index] || t.types[-1]), t2.types[index]) + end + return true + else + return false end - true - elsif t2.is_a?(Types::PArrayType) t2_entry = t2.element_type # Array of anything can not be assigned (unless tuple is tuple of anything) - this case # was handled at the top of this method. # return false if t2_entry.nil? size_t = t.size_type || Puppet::Pops::Types::TypeFactory.range(*t.size_range) size_t2 = t2.size_type || @collection_default_size_t return false unless assignable?(size_t, size_t2) min(t.types.size, size_t2.range()[1]).times do |index| return false unless assignable?((t.types[index] || t.types[-1]), t2_entry) end true else false end end # Produces the tuple entry at the given index given a tuple type, its from/to constraints on the last # type, and an index. # Produces nil if the index is out of bounds # from must be less than to, and from may not be less than 0 # # @api private # def tuple_entry_at(tuple_t, from, to, index) regular = (tuple_t.types.size - 1) if index < regular tuple_t.types[index] elsif index < regular + to # in the varargs part tuple_t.types[-1] else nil end end # @api private # def assignable_PStructType(t, t2) return true if t == t2 || t.elements.empty? && (t2.is_a?(Types::PHashType)) h = t.hashed_elements if t2.is_a?(Types::PStructType) h2 = t2.hashed_elements h.size == h2.size && h.all? {|k, v| assignable?(v, h2[k]) } elsif t2.is_a?(Types::PHashType) size_t2 = t2.size_type || @collection_default_size_t size_t = Types::PIntegerType.new size_t.from = size_t.to = h.size # compatible size # hash key type must be string of min 1 size # hash value t must be assignable to each key element_type = t2.element_type assignable?(size_t, size_t2) && assignable?(@non_empty_string_t, t2.key_type) && h.all? {|k,v| assignable?(v, element_type) } else false end end # @api private def assignable_POptionalType(t, t2) return true if t2.is_a?(Types::PNilType) if t2.is_a?(Types::POptionalType) assignable?(t.optional_type, t2.optional_type) else assignable?(t.optional_type, t2) end end # @api private def assignable_PEnumType(t, t2) return true if t == t2 || (t.values.empty? && (t2.is_a?(Types::PStringType) || t2.is_a?(Types::PEnumType))) if t2.is_a?(Types::PStringType) # if the set of strings are all found in the set of enums t2.values.all? { |s| t.values.any? { |e| e == s }} else false end end # @api private def assignable_PStringType(t, t2) if t.values.empty? # A general string is assignable by any other string or pattern restricted string # if the string has a size constraint it does not match since there is no reasonable way # to compute the min/max length a pattern will match. For enum, it is possible to test that # each enumerator value is within range size_t = t.size_type || @collection_default_size_t case t2 when Types::PStringType # true if size compliant size_t2 = t2.size_type || @collection_default_size_t assignable?(size_t, size_t2) when Types::PPatternType # true if size constraint is at least 0 to +Infinity (which is the same as the default) assignable?(size_t, @collection_default_size_t) when Types::PEnumType if t2.values # true if all enum values are within range min, max = t2.values.map(&:size).minmax trange = from_to_ordered(size_t.from, size_t.to) t2range = [min, max] # If t2 min and max are within the range of t trange[0] <= t2range[0] && trange[1] >= t2range[1] else # no string can match this enum anyway since it does not accept anything false end else # no other type matches string false end elsif t2.is_a?(Types::PStringType) # A specific string acts as a set of strings - must have exactly the same strings # In this case, size does not matter since the definition is very precise anyway Set.new(t.values) == Set.new(t2.values) else # All others are false, since no other type describes the same set of specific strings false end end # @api private def assignable_PPatternType(t, t2) return true if t == t2 return false unless t2.is_a?(Types::PStringType) || t2.is_a?(Types::PEnumType) if t2.values.empty? # Strings / Enums (unknown which ones) cannot all match a pattern, but if there is no pattern it is ok # (There should really always be a pattern, but better safe than sorry). return t.patterns.empty? ? true : false end # all strings in String/Enum type must match one of the patterns in Pattern type regexps = t.patterns.map {|p| p.regexp } t2.values.all? { |v| regexps.any? {|re| re.match(v) } } end # @api private def assignable_PFloatType(t, t2) return false unless t2.is_a?(Types::PFloatType) trange = from_to_ordered(t.from, t.to) t2range = from_to_ordered(t2.from, t2.to) # If t2 min and max are within the range of t trange[0] <= t2range[0] && trange[1] >= t2range[1] end # @api private def assignable_PBooleanType(t, t2) t2.is_a?(Types::PBooleanType) end # @api private def assignable_PRegexpType(t, t2) t2.is_a?(Types::PRegexpType) && (t.pattern.nil? || t.pattern == t2.pattern) end # @api private def assignable_PCallableType(t, t2) return false unless t2.is_a?(Types::PCallableType) # nil param_types means, any other Callable is assignable return true if t.param_types.nil? return false unless assignable?(t.param_types, t2.param_types) # names are ignored, they are just information # Blocks must be compatible this_block_t = t.block_type || @nil_t that_block_t = t2.block_type || @nil_t assignable?(this_block_t, that_block_t) end # @api private def assignable_PCollectionType(t, t2) size_t = t.size_type || @collection_default_size_t case t2 when Types::PCollectionType size_t2 = t2.size_type || @collection_default_size_t assignable?(size_t, size_t2) when Types::PTupleType # compute the tuple's min/max size, and check if that size matches from, to = size_range(t2.size_type) t2s = Types::PIntegerType.new() t2s.from = t2.types.size - 1 + from t2s.to = t2.types.size - 1 + to assignable?(size_t, t2s) when Types::PStructType from = to = t2.elements.size t2s = Types::PIntegerType.new() t2s.from = from t2s.to = to assignable?(size_t, t2s) else false end end # @api private def assignable_PType(t, t2) return false unless t2.is_a?(Types::PType) return true if t.type.nil? # wide enough to handle all types return false if t2.type.nil? # wider than t assignable?(t.type, t2.type) end # Array is assignable if t2 is an Array and t2's element type is assignable, or if t2 is a Tuple # where # @api private def assignable_PArrayType(t, t2) if t2.is_a?(Types::PArrayType) return false unless assignable?(t.element_type, t2.element_type) assignable_PCollectionType(t, t2) elsif t2.is_a?(Types::PTupleType) return false unless t2.types.all? {|t2_element| assignable?(t.element_type, t2_element) } t2_regular = t2.types[0..-2] t2_ranged = t2.types[-1] t2_from, t2_to = size_range(t2.size_type) t2_required = t2_regular.size + t2_from t_entry = t.element_type # Tuple of anything can not be assigned (unless array is tuple of anything) - this case # was handled at the top of this method. # return false if t_entry.nil? # array type may be size constrained size_t = t.size_type || @collection_default_size_t min, max = size_t.range # Tuple with fewer min entries can not be assigned return false if t2_required < min # Tuple with more optionally available entries can not be assigned return false if t2_regular.size + t2_to > max # each tuple type must be assignable to the element type t2_required.times do |index| t2_entry = tuple_entry_at(t2, t2_from, t2_to, index) return false unless assignable?(t_entry, t2_entry) end # ... and so must the last, possibly optional (ranged) type return assignable?(t_entry, t2_ranged) else false end end # Hash is assignable if t2 is a Hash and t2's key and element types are assignable # @api private def assignable_PHashType(t, t2) case t2 when Types::PHashType return false unless assignable?(t.key_type, t2.key_type) && assignable?(t.element_type, t2.element_type) assignable_PCollectionType(t, t2) when Types::PStructType # hash must accept String as key type # hash must accept all value types # hash must accept the size of the struct size_t = t.size_type || @collection_default_size_t min, max = size_t.range struct_size = t2.elements.size element_type = t.element_type ( struct_size >= min && struct_size <= max && assignable?(t.key_type, @non_empty_string_t) && t2.hashed_elements.all? {|k,v| assignable?(element_type, v) }) else false end end # @api private def assignable_PCatalogEntryType(t1, t2) t2.is_a?(Types::PCatalogEntryType) end # @api private def assignable_PHostClassType(t1, t2) return false unless t2.is_a?(Types::PHostClassType) # Class = Class[name}, Class[name] != Class return true if t1.class_name.nil? # Class[name] = Class[name] return t1.class_name == t2.class_name end # @api private def assignable_PResourceType(t1, t2) return false unless t2.is_a?(Types::PResourceType) return true if t1.type_name.nil? return false if t1.type_name != t2.type_name return true if t1.title.nil? return t1.title == t2.title end # Data is assignable by other Data and by Array[Data] and Hash[Scalar, Data] # @api private def assignable_PDataType(t, t2) t2.is_a?(Types::PDataType) || assignable?(@data_variant_t, t2) end # Assignable if t2's ruby class is same or subclass of t1's ruby class # @api private def assignable_PRubyType(t1, t2) return false unless t2.is_a?(Types::PRubyType) return true if t1.ruby_class.nil? # t1 is wider return false if t2.ruby_class.nil? # t1 not nil, so t2 can not be wider c1 = class_from_string(t1.ruby_class) c2 = class_from_string(t2.ruby_class) return false unless c1.is_a?(Class) && c2.is_a?(Class) !!(c2 <= c1) end # @api private def debug_string_Object(t) string(t) end # @api private def string_PType(t) if t.type.nil? "Type" else "Type[#{string(t.type)}]" end end # @api private def string_NilClass(t) ; '?' ; end # @api private def string_String(t) ; t ; end # @api private def string_PObjectType(t) ; "Object" ; end # @api private def string_PNilType(t) ; 'Undef' ; end # @api private def string_PBooleanType(t) ; "Boolean" ; end # @api private def string_PScalarType(t) ; "Scalar" ; end # @api private def string_PDataType(t) ; "Data" ; end # @api private def string_PNumericType(t) ; "Numeric" ; end # @api private def string_PIntegerType(t) range = range_array_part(t) unless range.empty? "Integer[#{range.join(', ')}]" else "Integer" end end # Produces a string from an Integer range type that is used inside other type strings # @api private def range_array_part(t) return [] if t.nil? || (t.from.nil? && t.to.nil?) [t.from.nil? ? 'default' : t.from , t.to.nil? ? 'default' : t.to ] end # @api private def string_PFloatType(t) range = range_array_part(t) unless range.empty? "Float[#{range.join(', ')}]" else "Float" end end # @api private def string_PRegexpType(t) t.pattern.nil? ? "Regexp" : "Regexp[#{t.regexp.inspect}]" end # @api private def string_PStringType(t) # skip values in regular output - see debug_string range = range_array_part(t.size_type) unless range.empty? "String[#{range.join(', ')}]" else "String" end end # @api private def debug_string_PStringType(t) range = range_array_part(t.size_type) range_part = range.empty? ? '' : '[' << range.join(' ,') << '], ' "String[" << range_part << (t.values.map {|s| "'#{s}'" }).join(', ') << ']' end # @api private def string_PEnumType(t) return "Enum" if t.values.empty? "Enum[" << t.values.map {|s| "'#{s}'" }.join(', ') << ']' end # @api private def string_PVariantType(t) return "Variant" if t.types.empty? "Variant[" << t.types.map {|t2| string(t2) }.join(', ') << ']' end # @api private def string_PTupleType(t) range = range_array_part(t.size_type) return "Tuple" if t.types.empty? s = "Tuple[" << t.types.map {|t2| string(t2) }.join(', ') unless range.empty? s << ", " << range.join(', ') end s << "]" s end # @api private def string_PCallableType(t) # generic return "Callable" if t.param_types.nil? if t.param_types.types.empty? range = [0, 0] else range = range_array_part(t.param_types.size_type) end types = t.param_types.types.map {|t2| string(t2) } params_part= types.join(', ') s = "Callable[" << types.join(', ') unless range.empty? (s << ', ') unless types.empty? s << range.join(', ') end # Add block T last (after min, max) if present) # unless t.block_type.nil? (s << ', ') unless types.empty? && range.empty? s << string(t.block_type) end s << "]" s end # @api private def string_PStructType(t) return "Struct" if t.elements.empty? "Struct[{" << t.elements.map {|element| string(element) }.join(', ') << "}]" end def string_PStructElement(t) "'#{t.name}'=>#{string(t.type)}" end # @api private def string_PPatternType(t) return "Pattern" if t.patterns.empty? "Pattern[" << t.patterns.map {|s| "#{s.regexp.inspect}" }.join(', ') << ']' end # @api private def string_PCollectionType(t) range = range_array_part(t.size_type) unless range.empty? "Collection[#{range.join(', ')}]" else "Collection" end end # @api private def string_PRubyType(t) ; "Ruby[#{string(t.ruby_class)}]" ; end # @api private def string_PArrayType(t) parts = [string(t.element_type)] + range_array_part(t.size_type) "Array[#{parts.join(', ')}]" end # @api private def string_PHashType(t) parts = [string(t.key_type), string(t.element_type)] + range_array_part(t.size_type) "Hash[#{parts.join(', ')}]" end # @api private def string_PCatalogEntryType(t) "CatalogEntry" end # @api private def string_PHostClassType(t) if t.class_name "Class[#{t.class_name}]" else "Class" end end # @api private def string_PResourceType(t) if t.type_name if t.title "#{t.type_name.capitalize}['#{t.title}']" else "#{t.type_name.capitalize}" end else "Resource" end end def string_POptionalType(t) if t.optional_type.nil? "Optional" else "Optional[#{string(t.optional_type)}]" end end # Catches all non enumerable types # @api private def enumerable_Object(o) nil end # @api private def enumerable_PIntegerType(t) # Not enumerable if representing an infinite range return nil if t.size == TheInfinity t end def self.copy_as_tuple(t) case t when Types::PTupleType t.copy when Types::PArrayType # transform array to tuple result = Types::PTupleType.new result.addTypes(t.element_type.copy) result.size_type = t.size_type.nil? ? nil : t.size_type.copy result else raise ArgumentError, "Internal Error: Only Array and Tuple can be given to copy_as_tuple" end end private def class_from_string(str) begin str.split('::').inject(Object) do |memo, name_segment| memo.const_get(name_segment) end rescue NameError return nil end end def common_data?(t1, t2) assignable?(@data_t, t1) && assignable?(@data_t, t2) end def common_scalar?(t1, t2) assignable?(@scalar_t, t1) && assignable?(@scalar_t, t2) end def common_numeric?(t1, t2) assignable?(@numeric_t, t1) && assignable?(@numeric_t, t2) end def common_pobject?(t1, t2) assignable?(@t, t1) && assignable?(@t, t2) end end diff --git a/lib/puppet/pops/types/type_factory.rb b/lib/puppet/pops/types/type_factory.rb index e01494f8f..916a8e73b 100644 --- a/lib/puppet/pops/types/type_factory.rb +++ b/lib/puppet/pops/types/type_factory.rb @@ -1,406 +1,401 @@ # Helper module that makes creation of type objects simpler. # @api public # module Puppet::Pops::Types::TypeFactory @type_calculator = Puppet::Pops::Types::TypeCalculator.new() Types = Puppet::Pops::Types # Produces the Integer type # @api public # def self.integer() Types::PIntegerType.new() end # Produces an Integer range type # @api public # def self.range(from, to) t = Types::PIntegerType.new() t.from = from unless (from == :default || from == 'default') t.to = to unless (to == :default || to == 'default') t end # Produces a Float range type # @api public # def self.float_range(from, to) t = Types::PFloatType.new() t.from = Float(from) unless from == :default || from.nil? t.to = Float(to) unless to == :default || to.nil? t end # Produces the Float type # @api public # def self.float() Types::PFloatType.new() end # Produces the Numeric type # @api public # def self.numeric() Types::PNumericType.new() end # Produces a string representation of the type # @api public # def self.label(t) @type_calculator.string(t) end # Produces the String type, optionally with specific string values # @api public # def self.string(*values) t = Types::PStringType.new() values.each {|v| t.addValues(v) } t end # Produces the Optional type, i.e. a short hand for Variant[T, Undef] def self.optional(optional_type = nil) t = Types::POptionalType.new t.optional_type = type_of(optional_type) t end - # Convenience method to produce an Optional[Object] type - def self.optional_object() - optional(object()) - end - # Produces the Enum type, optionally with specific string values # @api public # def self.enum(*values) t = Types::PEnumType.new() values.each {|v| t.addValues(v) } t end # Produces the Variant type, optionally with the "one of" types # @api public # def self.variant(*types) t = Types::PVariantType.new() types.each {|v| t.addTypes(type_of(v)) } t end # Produces the Struct type, either a non parameterized instance representing all structs (i.e. all hashes) # or a hash with a given set of keys of String type (names), bound to a value of a given type. Type may be # a Ruby Class, a Puppet Type, or an instance from which the type is inferred. # def self.struct(name_type_hash = {}) t = Types::PStructType.new name_type_hash.map do |name, type| elem = Types::PStructElement.new if name.is_a?(String) && name.empty? raise ArgumentError, "An empty String can not be used where a String[1, default] is expected" end elem.name = name elem.type = type_of(type) elem end.each {|elem| t.addElements(elem) } t end def self.tuple(*types) t = Types::PTupleType.new types.each {|elem| t.addTypes(type_of(elem)) } t end # Produces the Boolean type # @api public # def self.boolean() Types::PBooleanType.new() end # Produces the Object type # @api public # def self.object() Types::PObjectType.new() end # Produces the Regexp type # @param pattern [Regexp, String, nil] (nil) The regular expression object or a regexp source string, or nil for bare type # @api public # def self.regexp(pattern = nil) t = Types::PRegexpType.new() if pattern t.pattern = pattern.is_a?(Regexp) ? pattern.inspect[1..-2] : pattern end t.regexp() unless pattern.nil? # compile pattern to catch errors t end def self.pattern(*regular_expressions) t = Types::PPatternType.new() regular_expressions.each do |re| case re when String re_T = Types::PRegexpType.new() re_T.pattern = re re_T.regexp() # compile it to catch errors t.addPatterns(re_T) when Regexp re_T = Types::PRegexpType.new() # Regep.to_s includes options user did not enter and does not escape source # to work either as a string or as a // regexp. The inspect method does a better # job, but includes the // re_T.pattern = re.inspect[1..-2] t.addPatterns(re_T) when Types::PRegexpType t.addPatterns(re.copy) when Types::PPatternType re.patterns.each do |p| t.addPatterns(p.copy) end else raise ArgumentError, "Only String, Regexp, Pattern-Type, and Regexp-Type are allowed: got '#{re.class}" end end t end # Produces the Literal type # @api public # def self.scalar() Types::PScalarType.new() end # Produces a CallableType matching all callables # @api public # def self.all_callables() return Puppet::Pops::Types::PCallableType.new end # Produces a Callable type with one signature without support for a block # Use #with_block, or #with_optional_block to add a block to the callable # If no parameters are given, the Callable will describe a signature # that does not accept parameters. To create a Callable that matches all callables # use {#all_callables}. # # The params is a list of types, where the three last entries may be # optionally followed by min, max count, and a Callable which is taken as the block_type. # If neither min or max are specified the parameters must match exactly. # A min < params.size means that the difference are optional. # If max > params.size means that the last type repeats. # if max is :default, the max value is unbound (infinity). - # + # # Params are given as a sequence of arguments to {#type_of}. # def self.callable(*params) case params.last when Types::PCallableType last_callable = true when Types::POptionalType last_callable = true if params.last.optional_type.is_a?(Types::PCallableType) end block_t = last_callable ? params.pop : nil # compute a size_type for the signature based on the two last parameters if is_range_parameter?(params[-2]) && is_range_parameter?(params[-1]) size_type = range(params[-2], params[-1]) params = params[0, params.size - 2] elsif is_range_parameter?(params[-1]) size_type = range(params[-1], :default) params = params[0, params.size - 1] end types = params.map {|p| type_of(p) } # create a signature callable_t = Types::PCallableType.new() tuple_t = tuple(*types) tuple_t.size_type = size_type unless size_type.nil? callable_t.param_types = tuple_t callable_t.block_type = block_t callable_t end def self.with_block(callable, *block_params) callable.block_type = callable(*block_params) callable end def self.with_optional_block(callable, *block_params) callable.block_type = optional(callable(*block_params)) callable end # Produces the abstract type Collection # @api public # def self.collection() Types::PCollectionType.new() end # Produces the Data type # @api public # def self.data() Types::PDataType.new() end # Creates an instance of the Undef type # @api public def self.undef() Types::PNilType.new() end # Produces an instance of the abstract type PCatalogEntryType def self.catalog_entry() Types::PCatalogEntryType.new() end # Produces a PResourceType with a String type_name # A PResourceType with a nil or empty name is compatible with any other PResourceType. # A PResourceType with a given name is only compatible with a PResourceType with the same name. # (There is no resource-type subtyping in Puppet (yet)). # def self.resource(type_name = nil, title = nil) type = Types::PResourceType.new() type_name = type_name.type_name if type_name.is_a?(Types::PResourceType) type.type_name = type_name.downcase unless type_name.nil? type.title = title type end # Produces PHostClassType with a string class_name. # A PHostClassType with nil or empty name is compatible with any other PHostClassType. # A PHostClassType with a given name is only compatible with a PHostClassType with the same name. # def self.host_class(class_name = nil) type = Types::PHostClassType.new() unless class_name.nil? type.class_name = class_name.sub(/^::/, '') end type end # Produces a type for Array[o] where o is either a type, or an instance for which a type is inferred. # @api public # def self.array_of(o) type = Types::PArrayType.new() type.element_type = type_of(o) type end # Produces a type for Hash[Scalar, o] where o is either a type, or an instance for which a type is inferred. # @api public # def self.hash_of(value, key = scalar()) type = Types::PHashType.new() type.key_type = type_of(key) type.element_type = type_of(value) type end # Produces a type for Array[Data] # @api public # def self.array_of_data() type = Types::PArrayType.new() type.element_type = data() type end # Produces a type for Hash[Scalar, Data] # @api public # def self.hash_of_data() type = Types::PHashType.new() type.key_type = scalar() type.element_type = data() type end # Produces a type for Type[T] # @api public # def self.type_type(inst_type = nil) type = Types::PType.new() type.type = inst_type type end # Produce a type corresponding to the class of given unless given is a String, Class or a PAbstractType. # When a String is given this is taken as a classname. # def self.type_of(o) if o.is_a?(Class) @type_calculator.type(o) elsif o.is_a?(Types::PAbstractType) o elsif o.is_a?(String) type = Types::PRubyType.new() type.ruby_class = o type else @type_calculator.infer_generic(o) end end # Produces a type for a class or infers a type for something that is not a class # @note # To get the type for the class' class use `TypeCalculator.infer(c)` # # @overload ruby(o) # @param o [Class] produces the type corresponding to the class (e.g. Integer becomes PIntegerType) # @overload ruby(o) # @param o [Object] produces the type corresponding to the instance class (e.g. 3 becomes PIntegerType) # # @api public # def self.ruby(o) if o.is_a?(Class) @type_calculator.type(o) else type = Types::PRubyType.new() type.ruby_class = o.class.name type end end # Generic creator of a RubyType - allows creating the Ruby type with nil name, or String name. # Also see ruby(o) which performs inference, or mapps a Ruby Class to its name. # def self.ruby_type(class_name = nil) type = Types::PRubyType.new() type.ruby_class = class_name type end # Sets the accepted size range of a collection if something other than the default 0 to Infinity # is wanted. The semantics for from/to are the same as for #range # def self.constrain_size(collection_t, from, to) collection_t.size_type = range(from, to) collection_t end # Returns true if the given type t is of valid range parameter type (integer or literal default). def self.is_range_parameter?(t) t.is_a?(Integer) || t == 'default' || t == :default end end diff --git a/lib/puppet/pops/types/types.rb b/lib/puppet/pops/types/types.rb index 9bfe8d5e0..1545e55fb 100644 --- a/lib/puppet/pops/types/types.rb +++ b/lib/puppet/pops/types/types.rb @@ -1,506 +1,505 @@ require 'rgen/metamodel_builder' # The Types model is a model of Puppet Language types. # # The exact relationship between types is not visible in this model wrt. the PDataType which is an abstraction # of Scalar, Array[Data], and Hash[Scalar, Data] nested to any depth. This means it is not possible to # infer the type by simply looking at the inheritance hierarchy. The {Puppet::Pops::Types::TypeCalculator} should # be used to answer questions about types. The {Puppet::Pops::Types::TypeFactory} should be used to create an instance # of a type whenever one is needed. # # The implementation of the Types model contains methods that are required for the type objects to behave as # expected when comparing them and using them as keys in hashes. (No other logic is, or should be included directly in # the model's classes). # # @api public # module Puppet::Pops::Types # Used as end in a range INFINITY = 1.0 / 0.0 NEGATIVE_INFINITY = -INFINITY class PAbstractType < Puppet::Pops::Model::PopsObject abstract module ClassModule # Produce a deep copy of the type def copy Marshal.load(Marshal.dump(self)) end def hash self.class.hash end def ==(o) self.class == o.class end alias eql? == def to_s Puppet::Pops::Types::TypeCalculator.string(self) end end end # Base type for all types except {Puppet::Pops::Types::PType PType}, the type of types. # @api public class PObjectType < PAbstractType module ClassModule end end # The type of types. # @api public class PType < PObjectType contains_one_uni 'type', PAbstractType module ClassModule def hash [self.class, type].hash end def ==(o) self.class == o.class && type == o.type end end end # @api public class PNilType < PObjectType end # A flexible data type, being assignable to its subtypes as well as PArrayType and PHashType with element type assignable to PDataType. # # @api public class PDataType < PObjectType module ClassModule def ==(o) self.class == o.class || o.class == PVariantType && o == Puppet::Pops::Types::TypeCalculator.data_variant() end end end # A flexible type describing an any? of other types # @api public class PVariantType < PObjectType contains_many_uni 'types', PAbstractType, :lowerBound => 1 module ClassModule def hash [self.class, Set.new(self.types)].hash end def ==(o) (self.class == o.class && Set.new(types) == Set.new(o.types)) || (o.class == PDataType && self == Puppet::Pops::Types::TypeCalculator.data_variant()) end end end # Type that is PDataType compatible, but is not a PCollectionType. # @api public class PScalarType < PObjectType end # A string type describing the set of strings having one of the given values # class PEnumType < PScalarType has_many_attr 'values', String, :lowerBound => 1 module ClassModule def hash [self.class, Set.new(self.values)].hash end def ==(o) self.class == o.class && Set.new(values) == Set.new(o.values) end end end # @api public class PNumericType < PScalarType end # @api public class PIntegerType < PNumericType has_attr 'from', Integer, :lowerBound => 0 has_attr 'to', Integer, :lowerBound => 0 module ClassModule # The integer type is enumerable when it defines a range include Enumerable # Returns Float.Infinity if one end of the range is unbound def size return INFINITY if from.nil? || to.nil? 1+(to-from).abs end # Returns the range as an array ordered so the smaller number is always first. # The number may be Infinity or -Infinity. def range f = from || NEGATIVE_INFINITY t = to || INFINITY if f < t [f, t] else [t,f] end end # Returns Enumerator if no block is given # Returns self if size is infinity (does not yield) def each return self.to_enum unless block_given? return nil if from.nil? || to.nil? if to < from from.downto(to) {|x| yield x } else from.upto(to) {|x| yield x } end end def hash [self.class, from, to].hash end def ==(o) self.class == o.class && from == o.from && to == o.to end end end # @api public class PFloatType < PNumericType has_attr 'from', Float, :lowerBound => 0 has_attr 'to', Float, :lowerBound => 0 module ClassModule def hash [self.class, from, to].hash end def ==(o) self.class == o.class && from == o.from && to == o.to end end end # @api public class PStringType < PScalarType has_many_attr 'values', String, :lowerBound => 0, :upperBound => -1, :unique => true contains_one_uni 'size_type', PIntegerType module ClassModule def hash [self.class, self.size_type, Set.new(self.values)].hash end def ==(o) self.class == o.class && self.size_type == o.size_type && Set.new(values) == Set.new(o.values) end end end # @api public class PRegexpType < PScalarType has_attr 'pattern', String, :lowerBound => 1 has_attr 'regexp', Object, :derived => true module ClassModule def regexp_derived @_regexp = Regexp.new(pattern) unless @_regexp && @_regexp.source == pattern @_regexp end def hash [self.class, pattern].hash end def ==(o) self.class == o.class && pattern == o.pattern end end end # Represents a subtype of String that narrows the string to those matching the patterns # If specified without a pattern it is basically the same as the String type. # # @api public class PPatternType < PScalarType contains_many_uni 'patterns', PRegexpType module ClassModule def hash [self.class, Set.new(patterns)].hash end def ==(o) self.class == o.class && Set.new(patterns) == Set.new(o.patterns) end end end # @api public class PBooleanType < PScalarType end # @api public class PCollectionType < PObjectType contains_one_uni 'element_type', PAbstractType contains_one_uni 'size_type', PIntegerType module ClassModule # Returns an array with from (min) size to (max) size - # A negative range value in from is def size_range return [0, INFINITY] if size_type.nil? f = size_type.from || 0 t = size_type.to || INFINITY if f < t [f, t] else [t,f] end end def hash [self.class, element_type, size_type].hash end def ==(o) self.class == o.class && element_type == o.element_type && size_type == o.size_type end end end class PStructElement < Puppet::Pops::Model::PopsObject has_attr 'name', String, :lowerBound => 1 contains_one_uni 'type', PAbstractType module ClassModule def hash [self.class, type, name].hash end def ==(o) self.class == o.class && type == o.type && name == o.name end end end # @api public class PStructType < PObjectType contains_many_uni 'elements', PStructElement, :lowerBound => 1 has_attr 'hashed_elements', Object, :derived => true module ClassModule def hashed_elements_derived @_hashed ||= elements.reduce({}) {|memo, e| memo[e.name] = e.type; memo } @_hashed end def clear_hashed_elements @_hashed = nil end def hash [self.class, Set.new(elements)].hash end def ==(o) self.class == o.class && hashed_elements == o.hashed_elements end end end # @api public class PTupleType < PObjectType contains_many_uni 'types', PAbstractType, :lowerBound => 1 # If set, describes min and max required of the given types - if max > size of # types, the last type entry repeats # contains_one_uni 'size_type', PIntegerType, :lowerBound => 0 module ClassModule # Returns the number of elements accepted [min, max] in the tuple def size_range types_size = types.size size_type.nil? ? [types_size, types_size] : size_type.range end # Returns the number of accepted occurrences [min, max] of the last type in the tuple # The defaults is [1,1] # def repeat_last_range types_size = types.size if size_type.nil? return [1, 1] end from, to = size_type.range() min = from - (types_size-1) min = min <= 0 ? 0 : min max = to - (types_size-1) [min, max] end def hash [self.class, size_type, Set.new(types)].hash end def ==(o) self.class == o.class && types == o.types && size_type == o.size_type end end end class PCallableType < PObjectType # Types of parameters and required/optional count contains_one_uni 'param_types', PTupleType, :lowerBound => 1 # Although being an abstract type reference, only PAbstractCallable, and Optional[Callable] are supported # If not set, the meaning is that block is not supported. # contains_one_uni 'block_type', PAbstractType, :lowerBound => 0 module ClassModule # Returns the number of accepted arguments [min, max] def size_range param_types.size_range end # Returns the number of accepted arguments for the last parameter type [min, max] # def last_range param_types.repeat_last_range end # Range [0,0], [0,1], or [1,1] for the block # def block_range case block_type when Puppet::Pops::Types::POptionalType [0,1] when Puppet::Pops::Types::PVariantType, Puppet::Pops::Types::PCallableType [1,1] else [0,0] end end def hash [self.class, Set.new(param_types), block_type].hash end def ==(o) self.class == o.class && args_type == o.args_type && block_type == o.block_type end end end # @api public class PArrayType < PCollectionType module ClassModule def hash [self.class, self.element_type, self.size_type].hash end def ==(o) self.class == o.class && self.element_type == o.element_type && self.size_type == o.size_type end end end # @api public class PHashType < PCollectionType contains_one_uni 'key_type', PAbstractType module ClassModule def hash [self.class, key_type, self.element_type, self.size_type].hash end def ==(o) self.class == o.class && key_type == o.key_type && self.element_type == o.element_type && self.size_type == o.size_type end end end # @api public class PRubyType < PObjectType has_attr 'ruby_class', String module ClassModule def hash [self.class, ruby_class].hash end def ==(o) self.class == o.class && ruby_class == o.ruby_class end end end # Abstract representation of a type that can be placed in a Catalog. # @api public # class PCatalogEntryType < PObjectType end # Represents a (host-) class in the Puppet Language. # @api public # class PHostClassType < PCatalogEntryType has_attr 'class_name', String # contains_one_uni 'super_type', PHostClassType module ClassModule def hash [self.class, class_name].hash end def ==(o) self.class == o.class && class_name == o.class_name end end end # Represents a Resource Type in the Puppet Language # @api public # class PResourceType < PCatalogEntryType has_attr 'type_name', String has_attr 'title', String module ClassModule def hash [self.class, type_name, title].hash end def ==(o) self.class == o.class && type_name == o.type_name && title == o.title end end end # Represents a type that accept PNilType instead of the type parameter # required_type - is a short hand for Variant[T, Undef] # class POptionalType < PAbstractType contains_one_uni 'optional_type', PAbstractType module ClassModule def hash [self.class, optional_type].hash end def ==(o) self.class == o.class && optional_type == o.optional_type end end end end diff --git a/lib/puppet/pops/validation/checker4_0.rb b/lib/puppet/pops/validation/checker4_0.rb index 5e47d9c04..416015c94 100644 --- a/lib/puppet/pops/validation/checker4_0.rb +++ b/lib/puppet/pops/validation/checker4_0.rb @@ -1,655 +1,689 @@ # A Validator validates a model. # # Validation is performed on each model element in isolation. Each method should validate the model element's state # but not validate its referenced/contained elements except to check their validity in their respective role. # The intent is to drive the validation with a tree iterator that visits all elements in a model. # # # TODO: Add validation of multiplicities - this is a general validation that can be checked for all # Model objects via their metamodel. (I.e an extra call to multiplicity check in polymorph check). # This is however mostly valuable when validating model to model transformations, and is therefore T.B.D # class Puppet::Pops::Validation::Checker4_0 Issues = Puppet::Pops::Issues Model = Puppet::Pops::Model attr_reader :acceptor # Initializes the validator with a diagnostics producer. This object must respond to # `:will_accept?` and `:accept`. # def initialize(diagnostics_producer) @@check_visitor ||= Puppet::Pops::Visitor.new(nil, "check", 0, 0) @@rvalue_visitor ||= Puppet::Pops::Visitor.new(nil, "rvalue", 0, 0) @@hostname_visitor ||= Puppet::Pops::Visitor.new(nil, "hostname", 1, 2) @@assignment_visitor ||= Puppet::Pops::Visitor.new(nil, "assign", 0, 1) @@query_visitor ||= Puppet::Pops::Visitor.new(nil, "query", 0, 0) @@top_visitor ||= Puppet::Pops::Visitor.new(nil, "top", 1, 1) @@relation_visitor ||= Puppet::Pops::Visitor.new(nil, "relation", 0, 0) @@idem_visitor ||= Puppet::Pops::Visitor.new(self, "idem", 0, 0) @acceptor = diagnostics_producer end # Validates the entire model by visiting each model element and calling `check`. # The result is collected (or acted on immediately) by the configured diagnostic provider/acceptor # given when creating this Checker. # def validate(model) # tree iterate the model, and call check for each element check(model) model.eAllContents.each {|m| check(m) } end # Performs regular validity check def check(o) @@check_visitor.visit_this_0(self, o) end # Performs check if this is a vaid hostname expression # @param single_feature_name [String, nil] the name of a single valued hostname feature of the value's container. e.g. 'parent' def hostname(o, semantic, single_feature_name = nil) @@hostname_visitor.visit_this_2(self, o, semantic, single_feature_name) end # Performs check if this is valid as a query def query(o) @@query_visitor.visit_this_0(self, o) end # Performs check if this is valid as a relationship side def relation(o) @@relation_visitor.visit_this_0(self, o) end # Performs check if this is valid as a rvalue def rvalue(o) @@rvalue_visitor.visit_this_0(self, o) end # Performs check if this is valid as a container of a definition (class, define, node) def top(o, definition) @@top_visitor.visit_this_1(self, o, definition) end # Checks the LHS of an assignment (is it assignable?). # If args[0] is true, assignment via index is checked. # def assign(o, via_index = false) @@assignment_visitor.visit_this_1(self, o, via_index) end # Checks if the expression has side effect ('idem' is latin for 'the same', here meaning that the evaluation state # is known to be unchanged after the expression has been evaluated). The result is not 100% authoritative for # negative answers since analysis of function behavior is not possible. # @return [Boolean] true if expression is known to have no effect on evaluation state # def idem(o) @@idem_visitor.visit_this_0(self, o) end # Returns the last expression in a block, or the expression, if that expression is idem def ends_with_idem(o) if o.is_a?(Puppet::Pops::Model::BlockExpression) last = o.statements[-1] idem(last) ? last : nil else idem(o) ? o : nil end end #---ASSIGNMENT CHECKS def assign_VariableExpression(o, via_index) varname_string = varname_to_s(o.expr) if varname_string =~ Puppet::Pops::Patterns::NUMERIC_VAR_NAME acceptor.accept(Issues::ILLEGAL_NUMERIC_ASSIGNMENT, o, :varname => varname_string) end # Can not assign to something in another namespace (i.e. a '::' in the name is not legal) if acceptor.will_accept? Issues::CROSS_SCOPE_ASSIGNMENT if varname_string =~ /::/ acceptor.accept(Issues::CROSS_SCOPE_ASSIGNMENT, o, :name => varname_string) end end # TODO: Could scan for reassignment of the same variable if done earlier in the same container # Or if assigning to a parameter (more work). # TODO: Investigate if there are invalid cases for += assignment end def assign_AccessExpression(o, via_index) # Are indexed assignments allowed at all ? $x[x] = '...' if acceptor.will_accept? Issues::ILLEGAL_INDEXED_ASSIGNMENT acceptor.accept(Issues::ILLEGAL_INDEXED_ASSIGNMENT, o) else # Then the left expression must be assignable-via-index assign(o.left_expr, true) end end def assign_Object(o, via_index) # Can not assign to anything else (differentiate if this is via index or not) # i.e. 10 = 'hello' vs. 10['x'] = 'hello' (the root is reported as being in error in both cases) # acceptor.accept(via_index ? Issues::ILLEGAL_ASSIGNMENT_VIA_INDEX : Issues::ILLEGAL_ASSIGNMENT, o) end #---CHECKS def check_Object(o) end def check_Factory(o) check(o.current) end def check_AccessExpression(o) # Only min range is checked, all other checks are RT checks as they depend on the resulting type # of the LHS. if o.keys.size < 1 acceptor.accept(Issues::MISSING_INDEX, o) end end def check_AssignmentExpression(o) acceptor.accept(Issues::UNSUPPORTED_OPERATOR, o, {:operator => o.operator}) unless [:'=', :'+=', :'-='].include? o.operator assign(o.left_expr) rvalue(o.right_expr) end # Checks that operation with :+> is contained in a ResourceOverride or Collector. # # Parent of an AttributeOperation can be one of: # * CollectExpression # * ResourceOverride # * ResourceBody (ILLEGAL this is a regular resource expression) # * ResourceDefaults (ILLEGAL) # def check_AttributeOperation(o) if o.operator == :'+>' # Append operator use is constrained parent = o.eContainer unless parent.is_a?(Model::CollectExpression) || parent.is_a?(Model::ResourceOverrideExpression) acceptor.accept(Issues::ILLEGAL_ATTRIBUTE_APPEND, o, {:name=>o.attribute_name, :parent=>parent}) end end rvalue(o.value_expr) end def check_BinaryExpression(o) rvalue(o.left_expr) rvalue(o.right_expr) end def check_BlockExpression(o) o.statements[0..-2].each do |statement| if idem(statement) acceptor.accept(Issues::IDEM_EXPRESSION_NOT_LAST, statement) break # only flag the first end end end def check_CallNamedFunctionExpression(o) case o.functor_expr when Puppet::Pops::Model::QualifiedName # ok nil when Puppet::Pops::Model::RenderStringExpression # helpful to point out this easy to make Epp error acceptor.accept(Issues::ILLEGAL_EPP_PARAMETERS, o) else acceptor.accept(Issues::ILLEGAL_EXPRESSION, o.functor_expr, {:feature=>'function name', :container => o}) end end + def check_EppExpression(o) + if o.eContainer.is_a?(Puppet::Pops::Model::LambdaExpression) + internal_check_no_capture(o.eContainer, o) + end + end + def check_MethodCallExpression(o) unless o.functor_expr.is_a? Model::QualifiedName acceptor.accept(Issues::ILLEGAL_EXPRESSION, o.functor_expr, :feature => 'function name', :container => o) end end def check_CaseExpression(o) rvalue(o.test) # There should only be one LiteralDefault case option value # TODO: Implement this check end def check_CaseOption(o) o.values.each { |v| rvalue(v) } end def check_CollectExpression(o) unless o.type_expr.is_a? Model::QualifiedReference acceptor.accept(Issues::ILLEGAL_EXPRESSION, o.type_expr, :feature=> 'type name', :container => o) end # If a collect expression tries to collect exported resources and storeconfigs is not on # then it will not work... This was checked in the parser previously. This is a runtime checking # thing as opposed to a language thing. if acceptor.will_accept?(Issues::RT_NO_STORECONFIGS) && o.query.is_a?(Model::ExportedQuery) acceptor.accept(Issues::RT_NO_STORECONFIGS, o) end end # Only used for function names, grammar should not be able to produce something faulty, but # check anyway if model is created programatically (it will fail in transformation to AST for sure). def check_NamedAccessExpression(o) name = o.right_expr unless name.is_a? Model::QualifiedName acceptor.accept(Issues::ILLEGAL_EXPRESSION, name, :feature=> 'function name', :container => o.eContainer) end end # for 'class', 'define', and function def check_NamedDefinition(o) top(o.eContainer, o) if o.name !~ Puppet::Pops::Patterns::CLASSREF acceptor.accept(Issues::ILLEGAL_DEFINITION_NAME, o, {:name=>o.name}) end if violator = ends_with_idem(o.body) acceptor.accept(Issues::IDEM_NOT_ALLOWED_LAST, violator, {:container => o}) end end + def check_HostClassDefinition(o) + check_NamedDefinition(o) + internal_check_no_capture(o) + end + + def check_ResourceTypeDefinition(o) + check_NamedDefinition(o) + internal_check_no_capture(o) + end + + def internal_check_capture_last(o) + accepted_index = o.parameters.size() -1 + o.parameters.each_with_index do |p, index| + if p.captures_rest && index != accepted_index + acceptor.accept(Issues::CAPTURES_REST_NOT_LAST, p, {:param_name => p.name}) + end + end + end + + def internal_check_no_capture(o, container = o) + o.parameters.each_with_index do |p, index| + if p.captures_rest + acceptor.accept(Issues::CAPTURES_REST_NOT_SUPPORTED, p, {:container => container, :param_name => p.name}) + end + end + end + def check_IfExpression(o) rvalue(o.test) end def check_KeyedEntry(o) rvalue(o.key) rvalue(o.value) # In case there are additional things to forbid than non-rvalues # acceptor.accept(Issues::ILLEGAL_EXPRESSION, o.key, :feature => 'hash key', :container => o.eContainer) end def check_LambdaExpression(o) + internal_check_capture_last(o) end def check_LiteralList(o) o.values.each {|v| rvalue(v) } end def check_NodeDefinition(o) # Check that hostnames are valid hostnames (or regular expressions) hostname(o.host_matches, o) hostname(o.parent, o, 'parent') unless o.parent.nil? top(o.eContainer, o) if violator = ends_with_idem(o.body) acceptor.accept(Issues::IDEM_NOT_ALLOWED_LAST, violator, {:container => o}) end end # No checking takes place - all expressions using a QualifiedName need to check. This because the # rules are slightly different depending on the container (A variable allows a numeric start, but not # other names). This means that (if the lexer/parser so chooses) a QualifiedName # can be anything when it represents a Bare Word and evaluates to a String. # def check_QualifiedName(o) end # Checks that the value is a valid UpperCaseWord (a CLASSREF), and optionally if it contains a hypen. # DOH: QualifiedReferences are created with LOWER CASE NAMES at parse time def check_QualifiedReference(o) # Is this a valid qualified name? if o.value !~ Puppet::Pops::Patterns::CLASSREF acceptor.accept(Issues::ILLEGAL_CLASSREF, o, {:name=>o.value}) end end def check_QueryExpression(o) query(o.expr) if o.expr # is optional end def relation_Object(o) rvalue(o) end def relation_CollectExpression(o); end def relation_RelationshipExpression(o); end def check_Parameter(o) if o.name =~ /^[0-9]+$/ acceptor.accept(Issues::ILLEGAL_NUMERIC_PARAMETER, o, :name => o.name) end end #relationship_side: resource # | resourceref # | collection # | variable # | quotedtext # | selector # | casestatement # | hasharrayaccesses def check_RelationshipExpression(o) relation(o.left_expr) relation(o.right_expr) end def check_ResourceExpression(o) # A resource expression must have a lower case NAME as its type e.g. 'file { ... }' unless o.type_name.is_a? Model::QualifiedName acceptor.accept(Issues::ILLEGAL_EXPRESSION, o.type_name, :feature => 'resource type', :container => o) end # This is a runtime check - the model is valid, but will have runtime issues when evaluated # and storeconfigs is not set. if acceptor.will_accept?(Issues::RT_NO_STORECONFIGS) && o.exported acceptor.accept(Issues::RT_NO_STORECONFIGS_EXPORT, o) end end def check_ResourceDefaultsExpression(o) if o.form && o.form != :regular acceptor.accept(Issues::NOT_VIRTUALIZEABLE, o) end end def check_ReservedWord(o) acceptor.accept(Issues::RESERVED_WORD, o, :word => o.word) end def check_SelectorExpression(o) rvalue(o.left_expr) end def check_SelectorEntry(o) rvalue(o.matching_expr) end def check_UnaryExpression(o) rvalue(o.expr) end def check_UnlessExpression(o) rvalue(o.test) # TODO: Unless may not have an else part that is an IfExpression (grammar denies this though) end # Checks that variable is either strictly 0, or a non 0 starting decimal number, or a valid VAR_NAME def check_VariableExpression(o) # The expression must be a qualified name if !o.expr.is_a?(Model::QualifiedName) acceptor.accept(Issues::ILLEGAL_EXPRESSION, o, :feature => 'name', :container => o) else # name must be either a decimal value, or a valid NAME name = o.expr.value if name[0,1] =~ /[0-9]/ unless name =~ Puppet::Pops::Patterns::NUMERIC_VAR_NAME acceptor.accept(Issues::ILLEGAL_NUMERIC_VAR_NAME, o, :name => name) end else unless name =~ Puppet::Pops::Patterns::VAR_NAME acceptor.accept(Issues::ILLEGAL_VAR_NAME, o, :name => name) end end end end #--- HOSTNAME CHECKS # Transforms Array of host matching expressions into a (Ruby) array of AST::HostName def hostname_Array(o, semantic, single_feature_name) if single_feature_name acceptor.accept(Issues::ILLEGAL_EXPRESSION, o, {:feature=>single_feature_name, :container=>semantic}) end o.each {|x| hostname(x, semantic, false) } end def hostname_String(o, semantic, single_feature_name) # The 3.x checker only checks for illegal characters - if matching /[^-\w.]/ the name is invalid, # but this allows pathological names like "a..b......c", "----" # TODO: Investigate if more illegal hostnames should be flagged. # if o =~ Puppet::Pops::Patterns::ILLEGAL_HOSTNAME_CHARS acceptor.accept(Issues::ILLEGAL_HOSTNAME_CHARS, semantic, :hostname => o) end end def hostname_LiteralValue(o, semantic, single_feature_name) hostname_String(o.value.to_s, o, single_feature_name) end def hostname_ConcatenatedString(o, semantic, single_feature_name) # Puppet 3.1. only accepts a concatenated string without interpolated expressions if the_expr = o.segments.index {|s| s.is_a?(Model::TextExpression) } acceptor.accept(Issues::ILLEGAL_HOSTNAME_INTERPOLATION, o.segments[the_expr].expr) elsif o.segments.size() != 1 # corner case, bad model, concatenation of several plain strings acceptor.accept(Issues::ILLEGAL_HOSTNAME_INTERPOLATION, o) else # corner case, may be ok, but lexer may have replaced with plain string, this is # here if it does not hostname_String(o.segments[0], o.segments[0], false) end end def hostname_QualifiedName(o, semantic, single_feature_name) hostname_String(o.value.to_s, o, single_feature_name) end def hostname_QualifiedReference(o, semantic, single_feature_name) hostname_String(o.value.to_s, o, single_feature_name) end def hostname_LiteralNumber(o, semantic, single_feature_name) # always ok end def hostname_LiteralDefault(o, semantic, single_feature_name) # always ok end def hostname_LiteralRegularExpression(o, semantic, single_feature_name) # always ok end def hostname_Object(o, semantic, single_feature_name) acceptor.accept(Issues::ILLEGAL_EXPRESSION, o, {:feature=> single_feature_name || 'hostname', :container=>semantic}) end #---QUERY CHECKS # Anything not explicitly allowed is flagged as error. def query_Object(o) acceptor.accept(Issues::ILLEGAL_QUERY_EXPRESSION, o) end # Puppet AST only allows == and != # def query_ComparisonExpression(o) acceptor.accept(Issues::ILLEGAL_QUERY_EXPRESSION, o) unless [:'==', :'!='].include? o.operator end # Allows AND, OR, and checks if left/right are allowed in query. def query_BooleanExpression(o) query o.left_expr query o.right_expr end def query_ParenthesizedExpression(o) query(o.expr) end def query_VariableExpression(o); end def query_QualifiedName(o); end def query_LiteralNumber(o); end def query_LiteralString(o); end def query_LiteralBoolean(o); end #---RVALUE CHECKS # By default, all expressions are reported as being rvalues # Implement specific rvalue checks for those that are not. # def rvalue_Expression(o); end def rvalue_ResourceDefaultsExpression(o); acceptor.accept(Issues::NOT_RVALUE, o) ; end def rvalue_ResourceOverrideExpression(o); acceptor.accept(Issues::NOT_RVALUE, o) ; end def rvalue_CollectExpression(o) ; acceptor.accept(Issues::NOT_RVALUE, o) ; end def rvalue_Definition(o) ; acceptor.accept(Issues::NOT_RVALUE, o) ; end def rvalue_NodeDefinition(o) ; acceptor.accept(Issues::NOT_RVALUE, o) ; end def rvalue_UnaryExpression(o) ; rvalue o.expr ; end #---TOP CHECK def top_NilClass(o, definition) # ok, reached the top, no more parents end def top_Object(o, definition) # fail, reached a container that is not top level acceptor.accept(Issues::NOT_TOP_LEVEL, definition) end def top_BlockExpression(o, definition) # ok, if this is a block representing the body of a class, or is top level top o.eContainer, definition end def top_HostClassDefinition(o, definition) # ok, stop scanning parents end def top_Program(o, definition) # ok end # A LambdaExpression is a BlockExpression, and this method is needed to prevent the polymorph method for BlockExpression # to accept a lambda. # A lambda can not iteratively create classes, nodes or defines as the lambda does not have a closure. # def top_LambdaExpression(o, definition) # fail, stop scanning parents acceptor.accept(Issues::NOT_TOP_LEVEL, definition) end #--IDEM CHECK def idem_Object(o) false end def idem_Nop(o) true end def idem_NilClass(o) true end def idem_Literal(o) true end def idem_LiteralList(o) true end def idem_LiteralHash(o) true end def idem_Factory(o) idem(o.current) end def idem_AccessExpression(o) true end def idem_BinaryExpression(o) true end def idem_RelationshipExpression(o) # Always side effect false end def idem_AssignmentExpression(o) # Always side effect false end # Handles UnaryMinusExpression, NotExpression, VariableExpression def idem_UnaryExpression(o) true end # Allow (no-effect parentheses) to be used around a productive expression def idem_ParenthesizedExpression(o) idem(o.expr) end def idem_RenderExpression(o) false end def idem_RenderStringExpression(o) false end def idem_BlockExpression(o) # productive if there is at least one productive expression ! o.statements.any? {|expr| !idem(expr) } end # Returns true even though there may be interpolated expressions that have side effect. # Report as idem anyway, as it is very bad design to evaluate an interpolated string for its # side effect only. def idem_ConcatenatedString(o) true end # Heredoc is just a string, but may contain interpolated string (which may have side effects). # This is still bad design and should be reported as idem. def idem_HeredocExpression(o) true end # May technically have side effects inside the Selector, but this is bad design - treat as idem def idem_SelectorExpression(o) true end def idem_IfExpression(o) [o.test, o.then_expr, o.else_expr].all? {|e| idem(e) } end # Case expression is idem, if test, and all options are idem def idem_CaseExpression(o) return false if !idem(o.test) ! o.options.any? {|opt| !idem(opt) } end # An option is idem if values and the then_expression are idem def idem_CaseOption(o) return false if o.values.any? { |value| !idem(value) } idem(o.then_expr) end #--- NON POLYMORPH, NON CHECKING CODE # Produces string part of something named, or nil if not a QualifiedName or QualifiedReference # def varname_to_s(o) case o when Model::QualifiedName o.value when Model::QualifiedReference o.value else nil end end end diff --git a/lib/puppet/resource.rb b/lib/puppet/resource.rb index 5a3d95334..7e1231844 100644 --- a/lib/puppet/resource.rb +++ b/lib/puppet/resource.rb @@ -1,557 +1,572 @@ require 'puppet' require 'puppet/util/tagging' require 'puppet/util/pson' require 'puppet/parameter' # The simplest resource class. Eventually it will function as the # base class for all resource-like behaviour. # # @api public class Puppet::Resource # This stub class is only needed for serialization compatibility with 0.25.x. # Specifically, it exists to provide a compatibility API when using YAML # serialized objects loaded from StoreConfigs. Reference = Puppet::Resource include Puppet::Util::Tagging extend Puppet::Util::Pson include Enumerable attr_accessor :file, :line, :catalog, :exported, :virtual, :validate_parameters, :strict attr_reader :type, :title require 'puppet/indirector' extend Puppet::Indirector indirects :resource, :terminus_class => :ral ATTRIBUTES = [:file, :line, :exported] def self.from_data_hash(data) raise ArgumentError, "No resource type provided in serialized data" unless type = data['type'] raise ArgumentError, "No resource title provided in serialized data" unless title = data['title'] resource = new(type, title) if params = data['parameters'] params.each { |param, value| resource[param] = value } end if tags = data['tags'] tags.each { |tag| resource.tag(tag) } end ATTRIBUTES.each do |a| if value = data[a.to_s] resource.send(a.to_s + "=", value) end end resource end def self.from_pson(pson) Puppet.deprecation_warning("from_pson is being removed in favour of from_data_hash.") self.from_data_hash(pson) end def inspect "#{@type}[#{@title}]#{to_hash.inspect}" end def to_data_hash data = ([:type, :title, :tags] + ATTRIBUTES).inject({}) do |hash, param| next hash unless value = self.send(param) hash[param.to_s] = value hash end data["exported"] ||= false params = self.to_hash.inject({}) do |hash, ary| param, value = ary # Don't duplicate the title as the namevar next hash if param == namevar and value == title hash[param] = Puppet::Resource.value_to_pson_data(value) hash end data["parameters"] = params unless params.empty? data end # This doesn't include document type as it is part of a catalog def to_pson_data_hash to_data_hash end def self.value_to_pson_data(value) if value.is_a? Array value.map{|v| value_to_pson_data(v) } elsif value.is_a? Puppet::Resource value.to_s else value end end def yaml_property_munge(x) case x when Hash x.inject({}) { |h,kv| k,v = kv h[k] = self.class.value_to_pson_data(v) h } else self.class.value_to_pson_data(x) end end YAML_ATTRIBUTES = [:@file, :@line, :@exported, :@type, :@title, :@tags, :@parameters] # Explicitly list the instance variables that should be serialized when # converting to YAML. # # @api private # @return [Array] The intersection of our explicit variable list and # all of the instance variables defined on this class. def to_yaml_properties YAML_ATTRIBUTES & super end def to_pson(*args) to_data_hash.to_pson(*args) end # Proxy these methods to the parameters hash. It's likely they'll # be overridden at some point, but this works for now. %w{has_key? keys length delete empty? <<}.each do |method| define_method(method) do |*args| parameters.send(method, *args) end end # Set a given parameter. Converts all passed names # to lower-case symbols. def []=(param, value) validate_parameter(param) if validate_parameters parameters[parameter_name(param)] = value end # Return a given parameter's value. Converts all passed names # to lower-case symbols. def [](param) parameters[parameter_name(param)] end def ==(other) return false unless other.respond_to?(:title) and self.type == other.type and self.title == other.title return false unless to_hash == other.to_hash true end # Compatibility method. def builtin? builtin_type? end # Is this a builtin resource type? def builtin_type? resource_type.is_a?(Class) end # Iterate over each param/value pair, as required for Enumerable. def each parameters.each { |p,v| yield p, v } end def include?(parameter) super || parameters.keys.include?( parameter_name(parameter) ) end %w{exported virtual strict}.each do |m| define_method(m+"?") do self.send(m) end end def class? @is_class ||= @type == "Class" end def stage? @is_stage ||= @type.to_s.downcase == "stage" end # Cache to reduce respond_to? lookups @@nondeprecating_type = {} # Create our resource. def initialize(type, title = nil, attributes = {}) @parameters = {} # Set things like strictness first. attributes.each do |attr, value| next if attr == :parameters send(attr.to_s + "=", value) end @type, @title = extract_type_and_title(type, title) @type = munge_type_name(@type) if self.class? @title = :main if @title == "" @title = munge_type_name(@title) end if params = attributes[:parameters] extract_parameters(params) end if resource_type and ! @@nondeprecating_type[resource_type] if resource_type.respond_to?(:deprecate_params) resource_type.deprecate_params(title, attributes[:parameters]) else @@nondeprecating_type[resource_type] = true end end tag(self.type) tag(self.title) if valid_tag?(self.title) @reference = self # for serialization compatibility with 0.25.x if strict? and ! resource_type if self.class? raise ArgumentError, "Could not find declared class #{title}" else raise ArgumentError, "Invalid resource type #{type}" end end end def ref to_s end # Find our resource. def resolve catalog ? catalog.resource(to_s) : nil end # The resource's type implementation # @return [Puppet::Type, Puppet::Resource::Type] # @api private def resource_type @rstype ||= case type when "Class"; environment.known_resource_types.hostclass(title == :main ? "" : title) when "Node"; environment.known_resource_types.node(title) else Puppet::Type.type(type) || environment.known_resource_types.definition(type) end end # Set the resource's type implementation # @param type [Puppet::Type, Puppet::Resource::Type] # @api private def resource_type=(type) @rstype = type end def environment @environment ||= if catalog catalog.environment_instance else Puppet::Node::Environment::NONE end end def environment=(environment) @environment = environment end # Produce a simple hash of our parameters. def to_hash parse_title.merge parameters end def to_s "#{type}[#{title}]" end def uniqueness_key # Temporary kludge to deal with inconsistant use patters h = self.to_hash h[namevar] ||= h[:name] h[:name] ||= h[namevar] h.values_at(*key_attributes.sort_by { |k| k.to_s }) end def key_attributes resource_type.respond_to?(:key_attributes) ? resource_type.key_attributes : [:name] end # Convert our resource to Puppet code. def to_manifest # Collect list of attributes to align => and move ensure first attr = parameters.keys attr_max = attr.inject(0) { |max,k| k.to_s.length > max ? k.to_s.length : max } attr.sort! if attr.first != :ensure && attr.include?(:ensure) attr.delete(:ensure) attr.unshift(:ensure) end attributes = attr.collect { |k| v = parameters[k] " %-#{attr_max}s => %s,\n" % [k, Puppet::Parameter.format_value_for_display(v)] }.join "%s { '%s':\n%s}" % [self.type.to_s.downcase, self.title, attributes] end def to_ref ref end # Convert our resource to a RAL resource instance. Creates component # instances for resource types that don't exist. def to_ral typeklass = Puppet::Type.type(self.type) || Puppet::Type.type(:component) typeklass.new(self) end def name # this is potential namespace conflict # between the notion of an "indirector name" # and a "resource name" [ type, title ].join('/') end def missing_arguments resource_type.arguments.select do |param, default| param = param.to_sym parameters[param].nil? || parameters[param].value == :undef end end private :missing_arguments # Consult external data bindings for class parameter values which must be # namespaced in the backend. # # Example: # # class foo($port=0){ ... } # # We make a request to the backend for the key 'foo::port' not 'foo' # def lookup_external_default_for(param, scope) # Only lookup parameters for host classes return nil unless resource_type.type == :hostclass name = "#{resource_type.name}::#{param}" lookup_with_databinding(name, scope) end private :lookup_external_default_for def lookup_with_databinding(name, scope) begin Puppet::DataBinding.indirection.find( name, :environment => scope.environment.to_s, :variables => scope) rescue Puppet::DataBinding::LookupError => e raise Puppet::Error.new("Error from DataBinding '#{Puppet[:data_binding_terminus]}' while looking up '#{name}': #{e.message}", e) end end private :lookup_with_databinding def set_default_parameters(scope) return [] unless resource_type and resource_type.respond_to?(:arguments) unless is_a?(Puppet::Parser::Resource) fail Puppet::DevError, "Cannot evaluate default parameters for #{self} - not a parser resource" end missing_arguments.collect do |param, default| external_value = lookup_external_default_for(param, scope) if external_value.nil? && default.nil? next elsif external_value.nil? value = default.safeevaluate(scope) else value = external_value end self[param.to_sym] = value param end.compact end def copy_as_resource result = Puppet::Resource.new(type, title) result.file = self.file result.line = self.line result.exported = self.exported result.virtual = self.virtual result.tag(*self.tags) result.environment = environment result.instance_variable_set(:@rstype, resource_type) to_hash.each do |p, v| if v.is_a?(Puppet::Resource) v = Puppet::Resource.new(v.type, v.title) elsif v.is_a?(Array) # flatten resource references arrays v = v.flatten if v.flatten.find { |av| av.is_a?(Puppet::Resource) } v = v.collect do |av| av = Puppet::Resource.new(av.type, av.title) if av.is_a?(Puppet::Resource) av end end # If the value is an array with only one value, then # convert it to a single value. This is largely so that # the database interaction doesn't have to worry about # whether it returns an array or a string. result[p] = if v.is_a?(Array) and v.length == 1 v[0] else v end end result end def valid_parameter?(name) resource_type.valid_parameter?(name) end # Verify that all required arguments are either present or # have been provided with defaults. # Must be called after 'set_default_parameters'. We can't join the methods # because Type#set_parameters needs specifically ordered behavior. def validate_complete return unless resource_type and resource_type.respond_to?(:arguments) resource_type.arguments.each do |param, default| param = param.to_sym fail Puppet::ParseError, "Must pass #{param} to #{self}" unless parameters.include?(param) end + + # Perform optional type checking + if Puppet[:parser] == 'future' + # Perform type checking + arg_types = resource_type.argument_types + # Parameters is a map from name, to parameter, and the parameter again has name and value + parameters.each do |name, value| + next unless t = arg_types[name.to_s] # untyped, and parameters are symbols here (aargh, strings in the type) + unless Puppet::Pops::Types::TypeCalculator.instance?(t, value.value) + inferred_type = Puppet::Pops::Types::TypeCalculator.infer(value.value) + actual = Puppet::Pops::Types::TypeCalculator.generalize!(inferred_type) + fail Puppet::ParseError, "Expected parameter '#{name}' of '#{self}' to have type #{t.to_s}, got #{actual.to_s}" + end + end + end end def validate_parameter(name) raise ArgumentError, "Invalid parameter #{name}" unless valid_parameter?(name) end def prune_parameters(options = {}) properties = resource_type.properties.map(&:name) dup.collect do |attribute, value| if value.to_s.empty? or Array(value).empty? delete(attribute) elsif value.to_s == "absent" and attribute.to_s != "ensure" delete(attribute) end parameters_to_include = options[:parameters_to_include] || [] delete(attribute) unless properties.include?(attribute) || parameters_to_include.include?(attribute) end self end private # Produce a canonical method name. def parameter_name(param) param = param.to_s.downcase.to_sym if param == :name and namevar param = namevar end param end # The namevar for our resource type. If the type doesn't exist, # always use :name. def namevar if builtin_type? and t = resource_type and t.key_attributes.length == 1 t.key_attributes.first else :name end end def extract_parameters(params) params.each do |param, value| validate_parameter(param) if strict? self[param] = value end end def extract_type_and_title(argtype, argtitle) if (argtitle || argtype) =~ /^([^\[\]]+)\[(.+)\]$/m then [ $1, $2 ] elsif argtitle then [ argtype, argtitle ] elsif argtype.is_a?(Puppet::Type) then [ argtype.class.name, argtype.title ] elsif argtype.is_a?(Hash) then raise ArgumentError, "Puppet::Resource.new does not take a hash as the first argument. "+ "Did you mean (#{(argtype[:type] || argtype["type"]).inspect}, #{(argtype[:title] || argtype["title"]).inspect }) ?" else raise ArgumentError, "No title provided and #{argtype.inspect} is not a valid resource reference" end end def munge_type_name(value) return :main if value == :main return "Class" if value == "" or value.nil? or value.to_s.downcase == "component" value.to_s.split("::").collect { |s| s.capitalize }.join("::") end def parse_title h = {} type = resource_type if type.respond_to? :title_patterns type.title_patterns.each { |regexp, symbols_and_lambdas| if captures = regexp.match(title.to_s) symbols_and_lambdas.zip(captures[1..-1]).each do |symbol_and_lambda,capture| symbol, proc = symbol_and_lambda # Many types pass "identity" as the proc; we might as well give # them a shortcut to delivering that without the extra cost. # # Especially because the global type defines title_patterns and # uses the identity patterns. # # This was worth about 8MB of memory allocation saved in my # testing, so is worth the complexity for the API. if proc then h[symbol] = proc.call(capture) else h[symbol] = capture end end return h end } # If we've gotten this far, then none of the provided title patterns # matched. Since there's no way to determine the title then the # resource should fail here. raise Puppet::Error, "No set of title patterns matched the title \"#{title}\"." else return { :name => title.to_s } end end def parameters # @parameters could have been loaded from YAML, causing it to be nil (by # bypassing initialize). @parameters ||= {} end end diff --git a/lib/puppet/resource/type.rb b/lib/puppet/resource/type.rb index 27884df9f..a1ae98830 100644 --- a/lib/puppet/resource/type.rb +++ b/lib/puppet/resource/type.rb @@ -1,386 +1,413 @@ require 'puppet/parser' require 'puppet/util/warnings' require 'puppet/util/errors' require 'puppet/util/inline_docs' require 'puppet/parser/ast/leaf' require 'puppet/parser/ast/block_expression' require 'puppet/dsl' # Puppet::Resource::Type represents nodes, classes and defined types. # # It has a standard format for external consumption, usable from the # resource_type indirection via rest and the resource_type face. See the # {file:api_docs/http_resource_type.md#Schema resource type schema # description}. # # @api public class Puppet::Resource::Type Puppet::ResourceType = self include Puppet::Util::InlineDocs include Puppet::Util::Warnings include Puppet::Util::Errors RESOURCE_KINDS = [:hostclass, :node, :definition] # Map the names used in our documentation to the names used internally RESOURCE_KINDS_TO_EXTERNAL_NAMES = { :hostclass => "class", :node => "node", :definition => "defined_type", } RESOURCE_EXTERNAL_NAMES_TO_KINDS = RESOURCE_KINDS_TO_EXTERNAL_NAMES.invert attr_accessor :file, :line, :doc, :code, :ruby_code, :parent, :resource_type_collection attr_reader :namespace, :arguments, :behaves_like, :module_name + # Map from argument (aka parameter) names to Puppet Type + # @return [Hash :parser def self.from_data_hash(data) name = data.delete('name') or raise ArgumentError, "Resource Type names must be specified" kind = data.delete('kind') || "definition" unless type = RESOURCE_EXTERNAL_NAMES_TO_KINDS[kind] raise ArgumentError, "Unsupported resource kind '#{kind}'" end data = data.inject({}) { |result, ary| result[ary[0].intern] = ary[1]; result } # External documentation uses "parameters" but the internal name # is "arguments" data[:arguments] = data.delete(:parameters) new(type, name, data) end def self.from_pson(data) Puppet.deprecation_warning("from_pson is being removed in favour of from_data_hash.") self.from_data_hash(data) end def to_data_hash data = [:doc, :line, :file, :parent].inject({}) do |hash, param| next hash unless (value = self.send(param)) and (value != "") hash[param.to_s] = value hash end # External documentation uses "parameters" but the internal name # is "arguments" data['parameters'] = arguments.dup unless arguments.empty? data['name'] = name unless RESOURCE_KINDS_TO_EXTERNAL_NAMES.has_key?(type) raise ArgumentError, "Unsupported resource kind '#{type}'" end data['kind'] = RESOURCE_KINDS_TO_EXTERNAL_NAMES[type] data end # Are we a child of the passed class? Do a recursive search up our # parentage tree to figure it out. def child_of?(klass) return false unless parent return(klass == parent_type ? true : parent_type.child_of?(klass)) end # Now evaluate the code associated with this class or definition. def evaluate_code(resource) static_parent = evaluate_parent_type(resource) scope = static_parent || resource.scope scope = scope.newscope(:namespace => namespace, :source => self, :resource => resource) unless resource.title == :main scope.compiler.add_class(name) unless definition? set_resource_parameters(resource, scope) resource.add_edge_to_stage if code if @match # Only bother setting up the ephemeral scope if there are match variables to add into it begin elevel = scope.ephemeral_level scope.ephemeral_from(@match, file, line) code.safeevaluate(scope) ensure scope.unset_ephemeral_var(elevel) end else code.safeevaluate(scope) end end evaluate_ruby_code(resource, scope) if ruby_code end def initialize(type, name, options = {}) @type = type.to_s.downcase.to_sym raise ArgumentError, "Invalid resource supertype '#{type}'" unless RESOURCE_KINDS.include?(@type) name = convert_from_ast(name) if name.is_a?(Puppet::Parser::AST::HostName) set_name_and_namespace(name) [:code, :doc, :line, :file, :parent].each do |param| next unless value = options[param] send(param.to_s + "=", value) end set_arguments(options[:arguments]) + set_argument_types(options[:argument_types]) @match = nil @module_name = options[:module_name] end # This is only used for node names, and really only when the node name # is a regexp. def match(string) return string.to_s.downcase == name unless name_is_regex? @match = @name.match(string) end # Add code from a new instance to our code. def merge(other) fail "#{name} is not a class; cannot add code to it" unless type == :hostclass fail "#{other.name} is not a class; cannot add code from it" unless other.type == :hostclass fail "Cannot have code outside of a class/node/define because 'freeze_main' is enabled" if name == "" and Puppet.settings[:freeze_main] if parent and other.parent and parent != other.parent fail "Cannot merge classes with different parent classes (#{name} => #{parent} vs. #{other.name} => #{other.parent})" end # We know they're either equal or only one is set, so keep whichever parent is specified. self.parent ||= other.parent if other.doc self.doc ||= "" self.doc += other.doc end # This might just be an empty, stub class. return unless other.code unless self.code self.code = other.code return end self.code = Puppet::Parser::ParserFactory.code_merger.concatenate([self, other]) # self.code = self.code.sequence_with(other.code) end # Make an instance of the resource type, and place it in the catalog # if it isn't in the catalog already. This is only possible for # classes and nodes. No parameters are be supplied--if this is a # parameterized class, then all parameters take on their default # values. def ensure_in_catalog(scope, parameters=nil) type == :definition and raise ArgumentError, "Cannot create resources for defined resource types" resource_type = type == :hostclass ? :class : :node # Do nothing if the resource already exists; this makes sure we don't # get multiple copies of the class resource, which helps provide the # singleton nature of classes. # we should not do this for classes with parameters # if parameters are passed, we should still try to create the resource # even if it exists so that we can fail # this prevents us from being able to combine param classes with include if resource = scope.catalog.resource(resource_type, name) and !parameters return resource end resource = Puppet::Parser::Resource.new(resource_type, name, :scope => scope, :source => self) assign_parameter_values(parameters, resource) instantiate_resource(scope, resource) scope.compiler.add_resource(scope, resource) resource end def instantiate_resource(scope, resource) # Make sure our parent class has been evaluated, if we have one. if parent && !scope.catalog.resource(resource.type, parent) parent_type(scope).ensure_in_catalog(scope) end if ['Class', 'Node'].include? resource.type scope.catalog.tag(*resource.tags) end end def name return @name unless @name.is_a?(Regexp) @name.source.downcase.gsub(/[^-\w:.]/,'').sub(/^\.+/,'') end def name_is_regex? @name.is_a?(Regexp) end def assign_parameter_values(parameters, resource) return unless parameters # It'd be nice to assign default parameter values here, # but we can't because they often rely on local variables # created during set_resource_parameters. parameters.each do |name, value| resource.set_parameter name, value end end # MQR TODO: # # The change(s) introduced by the fix for #4270 are mostly silly & should be # removed, though we didn't realize it at the time. If it can be established/ # ensured that nodes never call parent_type and that resource_types are always # (as they should be) members of exactly one resource_type_collection the # following method could / should be replaced with: # # def parent_type # @parent_type ||= parent && ( # resource_type_collection.find_or_load([name],parent,type.to_sym) || # fail Puppet::ParseError, "Could not find parent resource type '#{parent}' of type #{type} in #{resource_type_collection.environment}" # ) # end # # ...and then the rest of the changes around passing in scope reverted. # def parent_type(scope = nil) return nil unless parent unless @parent_type raise "Must pass scope to parent_type when called first time" unless scope unless @parent_type = scope.environment.known_resource_types.send("find_#{type}", [name], parent) fail Puppet::ParseError, "Could not find parent resource type '#{parent}' of type #{type} in #{scope.environment}" end end @parent_type end # Set any arguments passed by the resource as variables in the scope. def set_resource_parameters(resource, scope) set = {} resource.to_hash.each do |param, value| param = param.to_sym fail Puppet::ParseError, "#{resource.ref} does not accept attribute #{param}" unless valid_parameter?(param) exceptwrap { scope[param.to_s] = value } set[param] = true end if @type == :hostclass scope["title"] = resource.title.to_s.downcase unless set.include? :title scope["name"] = resource.name.to_s.downcase unless set.include? :name else scope["title"] = resource.title unless set.include? :title scope["name"] = resource.name unless set.include? :name end scope["module_name"] = module_name if module_name and ! set.include? :module_name if caller_name = scope.parent_module_name and ! set.include?(:caller_module_name) scope["caller_module_name"] = caller_name end scope.class_set(self.name,scope) if hostclass? or node? # Evaluate the default parameters, now that all other variables are set default_params = resource.set_default_parameters(scope) default_params.each { |param| scope[param] = resource[param] } # This has to come after the above parameters so that default values # can use their values resource.validate_complete end # Check whether a given argument is valid. def valid_parameter?(param) param = param.to_s return true if param == "name" return true if Puppet::Type.metaparam?(param) return false unless defined?(@arguments) return(arguments.include?(param) ? true : false) end def set_arguments(arguments) @arguments = {} return if arguments.nil? arguments.each do |arg, default| arg = arg.to_s warn_if_metaparam(arg, default) @arguments[arg] = default end end + # Sets the argument name to Puppet Type hash used for type checking. + # Names must correspond to available arguments (they must be defined first). + # Arguments not mentioned will not be type-checked. Only supported when parser == "future" + # + def set_argument_types(name_to_type_hash) + @argument_types = {} + # Stop here if not running under future parser, the rest requires pops to be initialized + # and that the type system is available + return unless Puppet[:parser] == 'future' && name_to_type_hash + name_to_type_hash.each do |name, t| + # catch internal errors + unless @arguments.include?(name) + raise Puppet::DevError, "Parameter '#{name}' is given a type, but is not a valid parameter." + end + unless t.is_a? Puppet::Pops::Types::PAbstractType + raise Puppet::DevError, "Parameter '#{name}' is given a type that is not a Puppet Type, got #{t.class}" + end + @argument_types[name] = t + end + end + private def convert_from_ast(name) value = name.value if value.is_a?(Puppet::Parser::AST::Regex) name = value.value else name = value end end def evaluate_parent_type(resource) return unless klass = parent_type(resource.scope) and parent_resource = resource.scope.compiler.catalog.resource(:class, klass.name) || resource.scope.compiler.catalog.resource(:node, klass.name) parent_resource.evaluate unless parent_resource.evaluated? parent_scope(resource.scope, klass) end def evaluate_ruby_code(resource, scope) Puppet::DSL::ResourceAPI.new(resource, scope, ruby_code).evaluate end # Split an fq name into a namespace and name def namesplit(fullname) ary = fullname.split("::") n = ary.pop || "" ns = ary.join("::") return ns, n end def parent_scope(scope, klass) scope.class_scope(klass) || raise(Puppet::DevError, "Could not find scope for #{klass.name}") end def set_name_and_namespace(name) if name.is_a?(Regexp) @name = name @namespace = "" else @name = name.to_s.downcase # Note we're doing something somewhat weird here -- we're setting # the class's namespace to its fully qualified name. This means # anything inside that class starts looking in that namespace first. @namespace, ignored_shortname = @type == :hostclass ? [@name, ''] : namesplit(@name) end end def warn_if_metaparam(param, default) return unless Puppet::Type.metaparamclass(param) if default warnonce "#{param} is a metaparam; this value will inherit to all contained resources in the #{self.name} definition" else raise Puppet::ParseError, "#{param} is a metaparameter; please choose another parameter name in the #{self.name} definition" end end end diff --git a/spec/integration/parser/catalog_spec.rb b/spec/integration/parser/catalog_spec.rb index e37eb591a..21f31d856 100644 --- a/spec/integration/parser/catalog_spec.rb +++ b/spec/integration/parser/catalog_spec.rb @@ -1,125 +1,125 @@ require 'spec_helper' require 'matchers/include_in_order' require 'puppet_spec/compiler' require 'puppet/indirector/catalog/compiler' describe "A catalog" do include PuppetSpec::Compiler shared_examples_for "when compiled" do context "when transmitted to the agent" do it "preserves the order in which the resources are added to the catalog" do resources_in_declaration_order = ["Class[First]", "Second[position]", "Class[Third]", "Fourth[position]"] master_catalog, agent_catalog = master_and_agent_catalogs_for(<<-EOM) define fourth() { } class third { } define second() { fourth { "position": } } class first { second { "position": } class { "third": } } include first EOM expect(resources_in(master_catalog)). to include_in_order(*resources_in_declaration_order) expect(resources_in(agent_catalog)). to include_in_order(*resources_in_declaration_order) end it "does not contain unrealized, virtual resources" do virtual_resources = ["Unrealized[unreal]", "Class[Unreal]"] master_catalog, agent_catalog = master_and_agent_catalogs_for(<<-EOM) class unreal { } define unrealized() { } class real { @unrealized { "unreal": } @class { "unreal": } } include real EOM expect(resources_in(master_catalog)).to_not include(*virtual_resources) expect(resources_in(agent_catalog)).to_not include(*virtual_resources) end it "does not contain unrealized, exported resources" do exported_resources = ["Unrealized[unreal]", "Class[Unreal]"] master_catalog, agent_catalog = master_and_agent_catalogs_for(<<-EOM) class unreal { } define unrealized() { } class real { @@unrealized { "unreal": } @@class { "unreal": } } include real EOM expect(resources_in(master_catalog)).to_not include(*exported_resources) expect(resources_in(agent_catalog)).to_not include(*exported_resources) end end it "compiles resource creation from appended array as two separate resources" do # moved here from acceptance test "jeff_append_to_array.rb" master_catalog = master_catalog_for(<<-EOM) class parent { $arr1 = [ "parent array element" ] } class parent::child inherits parent { $arr1 += ["child array element"] notify { $arr1: } } include parent::child EOM expect(resources_in(master_catalog)).to include('Notify[parent array element]', 'Notify[child array element]') end end describe 'using classic parser' do before :each do Puppet[:parser] = 'current' end it_behaves_like 'when compiled' do end end describe 'using future parser' do before :each do Puppet[:parser] = 'future' end it_behaves_like 'when compiled' do end end def master_catalog_for(manifest) master_catalog = Puppet::Resource::Catalog::Compiler.new.filter(compile_to_catalog(manifest)) end def master_and_agent_catalogs_for(manifest) - master_catalog = Puppet::Resource::Catalog::Compiler.new.filter(compile_to_catalog(manifest)) + compiler = Puppet::Resource::Catalog::Compiler.new + master_catalog = compiler.filter(compile_to_catalog(manifest)) agent_catalog = Puppet::Resource::Catalog.convert_from(:pson, master_catalog.render(:pson)) - [master_catalog, agent_catalog] end def resources_in(catalog) catalog.resources.map(&:ref) end end diff --git a/spec/integration/parser/future_compiler_spec.rb b/spec/integration/parser/future_compiler_spec.rb index 63cb1708c..57205ceaf 100644 --- a/spec/integration/parser/future_compiler_spec.rb +++ b/spec/integration/parser/future_compiler_spec.rb @@ -1,435 +1,722 @@ require 'spec_helper' require 'puppet/pops' require 'puppet/parser/parser_factory' require 'puppet_spec/compiler' require 'puppet_spec/pops' require 'puppet_spec/scope' require 'matchers/resource' require 'rgen/metamodel_builder' # Test compilation using the future evaluator describe "Puppet::Parser::Compiler" do include PuppetSpec::Compiler include Matchers::Resource before :each do Puppet[:parser] = 'future' end describe "the compiler when using future parser and evaluator" do it "should be able to determine the configuration version from a local version control repository" do pending("Bug #14071 about semantics of Puppet::Util::Execute on Windows", :if => Puppet.features.microsoft_windows?) do # This should always work, because we should always be # in the puppet repo when we run this. version = %x{git rev-parse HEAD}.chomp Puppet.settings[:config_version] = 'git rev-parse HEAD' compiler = Puppet::Parser::Compiler.new(Puppet::Node.new("testnode")) compiler.catalog.version.should == version end end it "should not create duplicate resources when a class is referenced both directly and indirectly by the node classifier (4792)" do node = Puppet::Node.new("testnodex") node.classes = ['foo', 'bar'] catalog = compile_to_catalog(<<-PP, node) class foo { notify { foo_notify: } include bar } class bar { notify { bar_notify: } } PP catalog = Puppet::Parser::Compiler.compile(node) expect(catalog).to have_resource("Notify[foo_notify]") expect(catalog).to have_resource("Notify[bar_notify]") end it 'applies defaults for defines with qualified names (PUP-2302)' do catalog = compile_to_catalog(<<-CODE) define my::thing($msg = 'foo') { notify {'check_me': message => $msg } } My::Thing { msg => 'evoe' } my::thing { 'name': } CODE expect(catalog).to have_resource("Notify[check_me]").with_parameter(:message, "evoe") end it 'does not apply defaults from dynamic scopes (PUP-867)' do catalog = compile_to_catalog(<<-CODE) class a { Notify { message => "defaulted" } include b notify { bye: } } class b { notify { hi: } } include a CODE expect(catalog).to have_resource("Notify[hi]").with_parameter(:message, nil) expect(catalog).to have_resource("Notify[bye]").with_parameter(:message, "defaulted") end it 'gets default from inherited class (PUP-867)' do catalog = compile_to_catalog(<<-CODE) class a { Notify { message => "defaulted" } include c notify { bye: } } class b { Notify { message => "inherited" } } class c inherits b { notify { hi: } } include a CODE expect(catalog).to have_resource("Notify[hi]").with_parameter(:message, "inherited") expect(catalog).to have_resource("Notify[bye]").with_parameter(:message, "defaulted") end describe "when resolving class references" do it "should not favor local scope (with class included in topscope)" do catalog = compile_to_catalog(<<-PP) class experiment { class baz { } notify {"x" : require => Class[Baz] } notify {"y" : require => Class[Experiment::Baz] } } class baz { } include baz include experiment include experiment::baz PP expect(catalog).to have_resource("Notify[x]").with_parameter(:require, be_resource("Class[Baz]")) expect(catalog).to have_resource("Notify[y]").with_parameter(:require, be_resource("Class[Experiment::Baz]")) end it "should not favor local scope, (with class not included in topscope)" do catalog = compile_to_catalog(<<-PP) class experiment { class baz { } notify {"x" : require => Class[Baz] } notify {"y" : require => Class[Experiment::Baz] } } class baz { } include experiment include experiment::baz PP expect(catalog).to have_resource("Notify[x]").with_parameter(:require, be_resource("Class[Baz]")) expect(catalog).to have_resource("Notify[y]").with_parameter(:require, be_resource("Class[Experiment::Baz]")) end end describe "(ticket #13349) when explicitly specifying top scope" do ["class {'::bar::baz':}", "include ::bar::baz"].each do |include| describe "with #{include}" do it "should find the top level class" do catalog = compile_to_catalog(<<-MANIFEST) class { 'foo::test': } class foo::test { #{include} } class bar::baz { notify { 'good!': } } class foo::bar::baz { notify { 'bad!': } } MANIFEST expect(catalog).to have_resource("Class[Bar::Baz]") expect(catalog).to have_resource("Notify[good!]") expect(catalog).to_not have_resource("Class[Foo::Bar::Baz]") expect(catalog).to_not have_resource("Notify[bad!]") end end end end it "should recompute the version after input files are re-parsed" do Puppet[:code] = 'class foo { }' Time.stubs(:now).returns(1) node = Puppet::Node.new('mynode') Puppet::Parser::Compiler.compile(node).version.should == 1 Time.stubs(:now).returns(2) Puppet::Parser::Compiler.compile(node).version.should == 1 # no change because files didn't change Puppet::Resource::TypeCollection.any_instance.stubs(:stale?).returns(true).then.returns(false) # pretend change Puppet::Parser::Compiler.compile(node).version.should == 2 end ['define', 'class', 'node'].each do |thing| it "'#{thing}' is not allowed inside evaluated conditional constructs" do expect do compile_to_catalog(<<-PP) if true { #{thing} foo { } notify { decoy: } } PP end.to raise_error(Puppet::Error, /Classes, definitions, and nodes may only appear at toplevel/) end it "'#{thing}' is not allowed inside un-evaluated conditional constructs" do expect do compile_to_catalog(<<-PP) if false { #{thing} foo { } notify { decoy: } } PP end.to raise_error(Puppet::Error, /Classes, definitions, and nodes may only appear at toplevel/) end end describe "relationships can be formed" do def extract_name(ref) ref.sub(/File\[(\w+)\]/, '\1') end def assert_creates_relationships(relationship_code, expectations) base_manifest = <<-MANIFEST file { [a,b,c]: mode => 0644, } file { [d,e]: mode => 0755, } MANIFEST catalog = compile_to_catalog(base_manifest + relationship_code) resources = catalog.resources.select { |res| res.type == 'File' } actual_relationships, actual_subscriptions = [:before, :notify].map do |relation| resources.map do |res| dependents = Array(res[relation]) dependents.map { |ref| [res.title, extract_name(ref)] } end.inject(&:concat) end actual_relationships.should =~ (expectations[:relationships] || []) actual_subscriptions.should =~ (expectations[:subscriptions] || []) end it "of regular type" do assert_creates_relationships("File[a] -> File[b]", :relationships => [['a','b']]) end it "of subscription type" do assert_creates_relationships("File[a] ~> File[b]", :subscriptions => [['a', 'b']]) end it "between multiple resources expressed as resource with multiple titles" do assert_creates_relationships("File[a,b] -> File[c,d]", :relationships => [['a', 'c'], ['b', 'c'], ['a', 'd'], ['b', 'd']]) end it "between collection expressions" do assert_creates_relationships("File <| mode == 0644 |> -> File <| mode == 0755 |>", :relationships => [['a', 'd'], ['b', 'd'], ['c', 'd'], ['a', 'e'], ['b', 'e'], ['c', 'e']]) end it "between resources expressed as Strings" do assert_creates_relationships("'File[a]' -> 'File[b]'", :relationships => [['a', 'b']]) end it "between resources expressed as variables" do assert_creates_relationships(<<-MANIFEST, :relationships => [['a', 'b']]) $var = File[a] $var -> File[b] MANIFEST end it "between resources expressed as case statements" do assert_creates_relationships(<<-MANIFEST, :relationships => [['s1', 't2']]) $var = 10 case $var { 10: { file { s1: } } 12: { file { s2: } } } -> case $var + 2 { 10: { file { t1: } } 12: { file { t2: } } } MANIFEST end it "using deep access in array" do assert_creates_relationships(<<-MANIFEST, :relationships => [['a', 'b']]) $var = [ [ [ File[a], File[b] ] ] ] $var[0][0][0] -> $var[0][0][1] MANIFEST end it "using deep access in hash" do assert_creates_relationships(<<-MANIFEST, :relationships => [['a', 'b']]) $var = {'foo' => {'bar' => {'source' => File[a], 'target' => File[b]}}} $var[foo][bar][source] -> $var[foo][bar][target] MANIFEST end it "using resource declarations" do assert_creates_relationships("file { l: } -> file { r: }", :relationships => [['l', 'r']]) end it "between entries in a chain of relationships" do assert_creates_relationships("File[a] -> File[b] ~> File[c] <- File[d] <~ File[e]", :relationships => [['a', 'b'], ['d', 'c']], :subscriptions => [['b', 'c'], ['e', 'd']]) end end context "when dealing with variable references" do it 'an initial underscore in a variable name is ok' do catalog = compile_to_catalog(<<-MANIFEST) class a { $_a = 10} include a notify { 'test': message => $a::_a } MANIFEST expect(catalog).to have_resource("Notify[test]").with_parameter(:message, 10) end it 'an initial underscore in not ok if elsewhere than last segment' do expect do catalog = compile_to_catalog(<<-MANIFEST) class a { $_a = 10} include a notify { 'test': message => $_a::_a } MANIFEST end.to raise_error(/Illegal variable name/) end it 'a missing variable as default value becomes undef' do catalog = compile_to_catalog(<<-MANIFEST) class a ($b=$x) { notify {$b: message=>'meh'} } include a MANIFEST expect(catalog).to have_resource("Notify[undef]").with_parameter(:message, "meh") end end context 'when working with the trusted data hash' do context 'and have opted in to hashed_node_data' do before :each do Puppet[:trusted_node_data] = true end it 'should make $trusted available' do node = Puppet::Node.new("testing") node.trusted_data = { "data" => "value" } catalog = compile_to_catalog(<<-MANIFEST, node) notify { 'test': message => $trusted[data] } MANIFEST expect(catalog).to have_resource("Notify[test]").with_parameter(:message, "value") end it 'should not allow assignment to $trusted' do node = Puppet::Node.new("testing") node.trusted_data = { "data" => "value" } expect do compile_to_catalog(<<-MANIFEST, node) $trusted = 'changed' notify { 'test': message => $trusted == 'changed' } MANIFEST end.to raise_error(Puppet::Error, /Attempt to assign to a reserved variable name: 'trusted'/) end end context 'and have not opted in to hashed_node_data' do before :each do Puppet[:trusted_node_data] = false end it 'should not make $trusted available' do node = Puppet::Node.new("testing") node.trusted_data = { "data" => "value" } catalog = compile_to_catalog(<<-MANIFEST, node) notify { 'test': message => ($trusted == undef) } MANIFEST expect(catalog).to have_resource("Notify[test]").with_parameter(:message, true) end it 'should allow assignment to $trusted' do catalog = compile_to_catalog(<<-MANIFEST) $trusted = 'changed' notify { 'test': message => $trusted == 'changed' } MANIFEST expect(catalog).to have_resource("Notify[test]").with_parameter(:message, true) end end end + + context 'when using typed parameters in definition' do + it 'accepts type compliant arguments' do + catalog = compile_to_catalog(<<-MANIFEST) + define foo(String $x) { } + foo { 'test': x =>'say friend' } + MANIFEST + expect(catalog).to have_resource("Foo[test]").with_parameter(:x, 'say friend') + end + + it 'accepts anything when parameters are untyped' do + expect do + catalog = compile_to_catalog(<<-MANIFEST) + define foo($a, $b, $c) { } + foo { 'test': a => String, b=>10, c=>undef } + MANIFEST + end.to_not raise_error() + end + + it 'denies non type compliant arguments' do + expect do + catalog = compile_to_catalog(<<-MANIFEST) + define foo(Integer $x) { } + foo { 'test': x =>'say friend' } + MANIFEST + end.to raise_error(/type Integer, got String/) + end + + it 'denies non type compliant default argument' do + expect do + catalog = compile_to_catalog(<<-MANIFEST) + define foo(Integer $x = 'pow') { } + foo { 'test': } + MANIFEST + end.to raise_error(/type Integer, got String/) + end + + it 'accepts a Resource as a Type' do + catalog = compile_to_catalog(<<-MANIFEST) + define foo(Type[Bar] $x) { + notify { 'test': message => $x[text] } + } + define bar($text) { } + bar { 'joke': text => 'knock knock' } + foo { 'test': x => Bar[joke] } + MANIFEST + expect(catalog).to have_resource("Notify[test]").with_parameter(:message, 'knock knock') + end + end + + context 'when using typed parameters in class' do + it 'accepts type compliant arguments' do + catalog = compile_to_catalog(<<-MANIFEST) + class foo(String $x) { } + class { 'foo': x =>'say friend' } + MANIFEST + expect(catalog).to have_resource("Class[Foo]").with_parameter(:x, 'say friend') + end + + it 'accepts anything when parameters are untyped' do + expect do + catalog = compile_to_catalog(<<-MANIFEST) + class foo($a, $b, $c) { } + class { 'foo': a => String, b=>10, c=>undef } + MANIFEST + end.to_not raise_error() + end + + it 'denies non type compliant arguments' do + expect do + catalog = compile_to_catalog(<<-MANIFEST) + class foo(Integer $x) { } + class { 'foo': x =>'say friend' } + MANIFEST + end.to raise_error(/type Integer, got String/) + end + + it 'denies non type compliant default argument' do + expect do + catalog = compile_to_catalog(<<-MANIFEST) + class foo(Integer $x = 'pow') { } + class { 'foo': } + MANIFEST + end.to raise_error(/type Integer, got String/) + end + + it 'accepts a Resource as a Type' do + catalog = compile_to_catalog(<<-MANIFEST) + class foo(Type[Bar] $x) { + notify { 'test': message => $x[text] } + } + define bar($text) { } + bar { 'joke': text => 'knock knock' } + class { 'foo': x => Bar[joke] } + MANIFEST + expect(catalog).to have_resource("Notify[test]").with_parameter(:message, 'knock knock') + end + end + + context 'when using typed parameters in lambdas' do + it 'accepts type compliant arguments' do + catalog = compile_to_catalog(<<-MANIFEST) + with('value') |String $x| { notify { "$x": } } + MANIFEST + expect(catalog).to have_resource("Notify[value]") + end + + it 'handles an array as a single argument' do + catalog = compile_to_catalog(<<-MANIFEST) + with(['value', 'second']) |$x| { notify { "${x[0]} ${x[1]}": } } + MANIFEST + expect(catalog).to have_resource("Notify[value second]") + end + + it 'denies when missing required arguments' do + expect do + compile_to_catalog(<<-MANIFEST) + with(1) |$x, $y| { } + MANIFEST + end.to raise_error(/Parameter \$y is required but no value was given/m) + end + + it 'accepts anything when parameters are untyped' do + catalog = compile_to_catalog(<<-MANIFEST) + ['value', 1, true, undef].each |$x| { notify { "value: $x": } } + MANIFEST + + expect(catalog).to have_resource("Notify[value: value]") + expect(catalog).to have_resource("Notify[value: 1]") + expect(catalog).to have_resource("Notify[value: true]") + expect(catalog).to have_resource("Notify[value: ]") + end + + it 'accepts type-compliant, slurped arguments' do + catalog = compile_to_catalog(<<-MANIFEST) + with(1, 2) |Integer *$x| { notify { "${$x[0] + $x[1]}": } } + MANIFEST + expect(catalog).to have_resource("Notify[3]") + end + + it 'denies non-type-compliant arguments' do + expect do + compile_to_catalog(<<-MANIFEST) + with(1) |String $x| { } + MANIFEST + end.to raise_error(/expected.*String.*actual.*Integer/m) + end + + it 'denies non-type-compliant, slurped arguments' do + expect do + compile_to_catalog(<<-MANIFEST) + with(1, "hello") |Integer *$x| { } + MANIFEST + end.to raise_error(/called with mis-matched arguments.*expected.*Integer.*actual.*Integer, String/m) + end + + it 'denies non-type-compliant default argument' do + expect do + compile_to_catalog(<<-MANIFEST) + with(1) |$x, String $defaulted = 1| { notify { "${$x + $defaulted}": }} + MANIFEST + end.to raise_error(/expected.*Object.*String.*actual.*Integer.*Integer/m) + end + + it 'raises an error when a default argument value is an incorrect type and there are no arguments passed' do + expect do + compile_to_catalog(<<-MANIFEST) + with() |String $defaulted = 1| {} + MANIFEST + end.to raise_error(/expected.*String.*actual.*Integer/m) + end + + it 'raises an error when the default argument for a slurped parameter is an incorrect type' do + expect do + compile_to_catalog(<<-MANIFEST) + with() |String *$defaulted = 1| {} + MANIFEST + end.to raise_error(/expected.*String.*actual.*Integer/m) + end + + it 'allows using an array as the default slurped value' do + catalog = compile_to_catalog(<<-MANIFEST) + with() |String *$defaulted = [hi]| { notify { $defaulted[0]: } } + MANIFEST + + expect(catalog).to have_resource('Notify[hi]') + end + + it 'allows using a value of the type as the default slurped value' do + catalog = compile_to_catalog(<<-MANIFEST) + with() |String *$defaulted = hi| { notify { $defaulted[0]: } } + MANIFEST + + expect(catalog).to have_resource('Notify[hi]') + end + + it 'allows specifying the type of a slurped parameter as an array' do + catalog = compile_to_catalog(<<-MANIFEST) + with() |Array[String] *$defaulted = hi| { notify { $defaulted[0]: } } + MANIFEST + + expect(catalog).to have_resource('Notify[hi]') + end + + it 'raises an error when the number of default values does not match the parameter\'s size specification' do + expect do + compile_to_catalog(<<-MANIFEST) + with() |Array[String, 2] *$defaulted = hi| { } + MANIFEST + end.to raise_error(/expected.*arg count \{2,\}.*actual.*arg count \{1\}/m) + end + + it 'raises an error when the number of passed values does not match the parameter\'s size specification' do + expect do + compile_to_catalog(<<-MANIFEST) + with(hi) |Array[String, 2] *$passed| { } + MANIFEST + end.to raise_error(/expected.*arg count \{2,\}.*actual.*arg count \{1\}/m) + end + + it 'matches when the number of arguments passed for a slurp parameter match the size specification' do + catalog = compile_to_catalog(<<-MANIFEST) + with(hi, bye) |Array[String, 2] *$passed| { + $passed.each |$n| { notify { $n: } } + } + MANIFEST + + expect(catalog).to have_resource('Notify[hi]') + expect(catalog).to have_resource('Notify[bye]') + end + + it 'raises an error when the number of allowed slurp parameters exceeds the size constraint' do + expect do + compile_to_catalog(<<-MANIFEST) + with(hi, bye) |Array[String, 1, 1] *$passed| { } + MANIFEST + end.to raise_error(/expected.*arg count \{1\}.*actual.*arg count \{2\}/m) + end + + it 'allows passing slurped arrays by specifying an array of arrays' do + catalog = compile_to_catalog(<<-MANIFEST) + with([hi], [bye]) |Array[Array[String, 1, 1]] *$passed| { + notify { $passed[0][0]: } + notify { $passed[1][0]: } + } + MANIFEST + + expect(catalog).to have_resource('Notify[hi]') + expect(catalog).to have_resource('Notify[bye]') + end + + it 'raises an error when a required argument follows an optional one' do + expect do + compile_to_catalog(<<-MANIFEST) + with() |$y = first, $x, Array[String, 1] *$passed = bye| {} + MANIFEST + end.to raise_error(/Parameter \$x is required/) + end + + it 'raises an error when the minimum size of a slurped argument makes it required and it follows an optional argument' do + expect do + compile_to_catalog(<<-MANIFEST) + with() |$x = first, Array[String, 1] *$passed| {} + MANIFEST + end.to raise_error(/Parameter \$passed is required/) + end + + it 'allows slurped arguments with a minimum size of 0 after an optional argument' do + catalog = compile_to_catalog(<<-MANIFEST) + with() |$x = first, Array[String, 0] *$passed| { + notify { $x: } + } + MANIFEST + + expect(catalog).to have_resource('Notify[first]') + end + + it 'accepts a Resource as a Type' do + catalog = compile_to_catalog(<<-MANIFEST) + define bar($text) { } + bar { 'joke': text => 'knock knock' } + + with(Bar[joke]) |Type[Bar] $joke| { notify { "${joke[text]}": } } + MANIFEST + expect(catalog).to have_resource("Notify[knock knock]") + end + end end context 'when evaluating collection' do it 'matches on container inherited tags' do Puppet[:code] = <<-MANIFEST class xport_test { tag('foo_bar') @notify { 'nbr1': message => 'explicitly tagged', tag => 'foo_bar' } @notify { 'nbr2': message => 'implicitly tagged' } Notify <| tag == 'foo_bar' |> { message => 'overridden' } } include xport_test MANIFEST catalog = Puppet::Parser::Compiler.compile(Puppet::Node.new("mynode")) expect(catalog).to have_resource("Notify[nbr1]").with_parameter(:message, 'overridden') expect(catalog).to have_resource("Notify[nbr2]").with_parameter(:message, 'overridden') end end end diff --git a/spec/unit/functions/assert_type_spec.rb b/spec/unit/functions/assert_type_spec.rb index 612d33d9d..4c13db64b 100644 --- a/spec/unit/functions/assert_type_spec.rb +++ b/spec/unit/functions/assert_type_spec.rb @@ -1,78 +1,78 @@ require 'spec_helper' require 'puppet/pops' require 'puppet/loaders' describe 'the assert_type function' do after(:all) { Puppet::Pops::Loaders.clear } around(:each) do |example| loaders = Puppet::Pops::Loaders.new(Puppet::Node::Environment.create(:testing, [])) Puppet.override({:loaders => loaders}, "test-example") do example.run end end let(:func) do Puppet.lookup(:loaders).puppet_system_loader.load(:function, 'assert_type') end it 'asserts compliant type by returning the value' do expect(func.call({}, type(String), 'hello world')).to eql('hello world') end it 'accepts type given as a String' do expect(func.call({}, 'String', 'hello world')).to eql('hello world') end it 'asserts non compliant type by raising an error' do expect do func.call({}, type(Integer), 'hello world') end.to raise_error(Puppet::ParseError, /does not match actual/) end it 'checks that first argument is a type' do expect do func.call({}, 10, 10) end.to raise_error(ArgumentError, Regexp.new(Regexp.escape( "function 'assert_type' called with mis-matched arguments expected one of: - assert_type(Type type, Optional[Object] value, Callable[Optional[Object], Optional[Object]] block {0,1}) - arg count {2,3} - assert_type(String type_string, Optional[Object] value, Callable[Optional[Object], Optional[Object]] block {0,1}) - arg count {2,3} + assert_type(Type type, Object value, Callable[Object, Object] block {0,1}) - arg count {2,3} + assert_type(String type_string, Object value, Callable[Object, Object] block {0,1}) - arg count {2,3} actual: assert_type(Integer, Integer) - arg count {2}"))) end it 'allows the second arg to be undef/nil)' do expect do func.call({}, optional(String), nil) end.to_not raise_error(ArgumentError) end it 'can be called with a callable' do expected, actual = func.call({}, optional(String), 1, create_callable_2_args_unit) expect(expected.to_s).to eql('Optional[String]') expect(actual.to_s).to eql('Integer') end def optional(type_ref) Puppet::Pops::Types::TypeFactory.optional(type(type_ref)) end def type(type_ref) Puppet::Pops::Types::TypeFactory.type_of(type_ref) end def create_callable_2_args_unit() Puppet::Functions.create_function(:func) do dispatch :func do param 'Type', 'expected' param 'Type', 'actual' end def func(expected, actual) [expected, actual] end end.new({}, nil) end end diff --git a/spec/unit/functions/with_spec.rb b/spec/unit/functions/with_spec.rb new file mode 100644 index 000000000..952b14412 --- /dev/null +++ b/spec/unit/functions/with_spec.rb @@ -0,0 +1,35 @@ +require 'spec_helper' + +require 'puppet_spec/compiler' +require 'matchers/resource' + +describe 'the with function' do + include PuppetSpec::Compiler + include Matchers::Resource + + before :each do + Puppet[:parser] = 'future' + end + + it 'calls a lambda passing no arguments' do + expect(compile_to_catalog("with() || { notify { testing: } }")).to have_resource('Notify[testing]') + end + + it 'calls a lambda passing a single argument' do + expect(compile_to_catalog('with(1) |$x| { notify { "testing$x": } }')).to have_resource('Notify[testing1]') + end + + it 'calls a lambda passing more than one argument' do + expect(compile_to_catalog('with(1, 2) |*$x| { notify { "testing${x[0]}, ${x[1]}": } }')).to have_resource('Notify[testing1, 2]') + end + + it 'passes a type reference to a lambda' do + expect(compile_to_catalog('notify { test: message => "data" } with(Notify[test]) |$x| { notify { "${x[message]}": } }')).to have_resource('Notify[data]') + end + + it 'errors when not given enough arguments for the lambda' do + expect do + compile_to_catalog('with(1) |$x, $y| { }') + end.to raise_error(/Parameter \$y is required but no value was given/m) + end +end diff --git a/spec/unit/parser/functions/epp_spec.rb b/spec/unit/parser/functions/epp_spec.rb index 962709703..f5a2e3592 100644 --- a/spec/unit/parser/functions/epp_spec.rb +++ b/spec/unit/parser/functions/epp_spec.rb @@ -1,103 +1,141 @@ require 'spec_helper' describe "the epp function" do include PuppetSpec::Files before :all do Puppet::Parser::Functions.autoloader.loadall end before :each do Puppet[:parser] = 'future' end let :node do Puppet::Node.new('localhost') end let :compiler do Puppet::Parser::Compiler.new(node) end let :scope do Puppet::Parser::Scope.new(compiler) end context "when accessing scope variables as $ variables" do it "looks up the value from the scope" do scope["what"] = "are belong" eval_template("all your base <%= $what %> to us").should == "all your base are belong to us" end it "get nil accessing a variable that does not exist" do eval_template("<%= $kryptonite == undef %>").should == "true" end it "get nil accessing a variable that is undef" do scope['undef_var'] = :undef eval_template("<%= $undef_var == undef %>").should == "true" end it "gets shadowed variable if args are given" do scope['phantom'] = 'of the opera' eval_template_with_args("<%= $phantom == dragos %>", 'phantom' => 'dragos').should == "true" end + it "can use values from the enclosing scope for defaults" do + scope['phantom'] = 'of the opera' + eval_template("<%- |$phantom = $phantom| -%><%= $phantom %>").should == "of the opera" + end + + it "uses the default value if the given value is undef/nil" do + eval_template_with_args("<%- |$phantom = 'inside your mind'| -%><%= $phantom %>", 'phantom' => nil).should == "inside your mind" + end + it "gets shadowed variable if args are given and parameters are specified" do scope['x'] = 'wrong one' eval_template_with_args("<%- |$x| -%><%= $x == correct %>", 'x' => 'correct').should == "true" end it "raises an error if required variable is not given" do scope['x'] = 'wrong one' - expect { + expect do eval_template_with_args("<%-| $x |-%><%= $x == correct %>", 'y' => 'correct') - }.to raise_error(/no value given for required parameters x/) + end.to raise_error(/no value given for required parameters x/) end it "raises an error if too many arguments are given" do scope['x'] = 'wrong one' - expect { + expect do eval_template_with_args("<%-| $x |-%><%= $x == correct %>", 'x' => 'correct', 'y' => 'surplus') - }.to raise_error(/Too many arguments: 2 for 1/) + end.to raise_error(/Too many arguments: 2 for 1/) end end + context "when using typed parameters" do + it "allows a passed value that matches the parameter's type" do + expect(eval_template_with_args("<%-|String $x|-%><%= $x == correct %>", 'x' => 'correct')).to eq("true") + end + + it "does not allow slurped parameters" do + expect do + eval_template_with_args("<%-|*$x|-%><%= $x %>", 'x' => 'incorrect') + end.to raise_error(/'captures rest' - not supported in an Epp Template/) + end + + it "raises an error when the passed value does not match the parameter's type" do + expect do + eval_template_with_args("<%-|Integer $x|-%><%= $x %>", 'x' => 'incorrect') + end.to raise_error(/expected.*Integer.*actual.*String/m) + end + + it "raises an error when the default value does not match the parameter's type" do + expect do + eval_template("<%-|Integer $x = 'nope'|-%><%= $x %>") + end.to raise_error(/expected.*Integer.*actual.*String/m) + end + + it "allows an parameter to default to undef" do + expect(eval_template("<%-|Optional[Integer] $x = undef|-%><%= $x == undef %>")).to eq("true") + end + end + + # although never a problem with epp it "is not interfered with by having a variable named 'string' (#14093)" do scope['string'] = "this output should not be seen" eval_template("some text that is static").should == "some text that is static" end it "has access to a variable named 'string' (#14093)" do scope['string'] = "the string value" eval_template("string was: <%= $string %>").should == "string was: the string value" end describe 'when loading from modules' do include PuppetSpec::Files it 'an epp template is found' do modules_dir = dir_containing('modules', { 'testmodule' => { 'templates' => { 'the_x.epp' => 'The x is <%= $x %>' } }}) Puppet.override({:current_environment => (env = Puppet::Node::Environment.create(:testload, [ modules_dir ]))}, "test") do node.environment = env expect(scope.function_epp([ 'testmodule/the_x.epp', { 'x' => '3'} ])).to eql("The x is 3") end end end def eval_template_with_args(content, args_hash) file_path = tmpdir('epp_spec_content') filename = File.join(file_path, "template.epp") File.open(filename, "w+") { |f| f.write(content) } Puppet::Parser::Files.stubs(:find_template).returns(filename) scope.function_epp(['template', args_hash]) end def eval_template(content) file_path = tmpdir('epp_spec_content') filename = File.join(file_path, "template.epp") File.open(filename, "w+") { |f| f.write(content) } Puppet::Parser::Files.stubs(:find_template).returns(filename) scope.function_epp(['template']) end end diff --git a/spec/unit/parser/methods/each_spec.rb b/spec/unit/parser/methods/each_spec.rb index 5e9ce4e0c..053d978e9 100644 --- a/spec/unit/parser/methods/each_spec.rb +++ b/spec/unit/parser/methods/each_spec.rb @@ -1,91 +1,106 @@ require 'puppet' require 'spec_helper' require 'puppet_spec/compiler' require 'rubygems' describe 'the each method' do include PuppetSpec::Compiler before :each do Puppet[:parser] = 'future' end context "should be callable as" do it 'each on an array selecting each value' do catalog = compile_to_catalog(<<-MANIFEST) $a = [1,2,3] $a.each |$v| { file { "/file_$v": ensure => present } } MANIFEST catalog.resource(:file, "/file_1")['ensure'].should == 'present' catalog.resource(:file, "/file_2")['ensure'].should == 'present' catalog.resource(:file, "/file_3")['ensure'].should == 'present' end it 'each on an array selecting each value - function call style' do catalog = compile_to_catalog(<<-MANIFEST) $a = [1,2,3] each ($a) |$index, $v| { file { "/file_$v": ensure => present } } MANIFEST catalog.resource(:file, "/file_1")['ensure'].should == 'present' catalog.resource(:file, "/file_2")['ensure'].should == 'present' catalog.resource(:file, "/file_3")['ensure'].should == 'present' end it 'each on an array with index' do catalog = compile_to_catalog(<<-MANIFEST) $a = [present, absent, present] $a.each |$k,$v| { file { "/file_${$k+1}": ensure => $v } } MANIFEST catalog.resource(:file, "/file_1")['ensure'].should == 'present' catalog.resource(:file, "/file_2")['ensure'].should == 'absent' catalog.resource(:file, "/file_3")['ensure'].should == 'present' end it 'each on a hash selecting entries' do catalog = compile_to_catalog(<<-MANIFEST) $a = {'a'=>'present','b'=>'absent','c'=>'present'} $a.each |$e| { file { "/file_${e[0]}": ensure => $e[1] } } MANIFEST catalog.resource(:file, "/file_a")['ensure'].should == 'present' catalog.resource(:file, "/file_b")['ensure'].should == 'absent' catalog.resource(:file, "/file_c")['ensure'].should == 'present' end + it 'each on a hash selecting key and value' do catalog = compile_to_catalog(<<-MANIFEST) $a = {'a'=>present,'b'=>absent,'c'=>present} $a.each |$k, $v| { file { "/file_$k": ensure => $v } } MANIFEST catalog.resource(:file, "/file_a")['ensure'].should == 'present' catalog.resource(:file, "/file_b")['ensure'].should == 'absent' catalog.resource(:file, "/file_c")['ensure'].should == 'present' end + + it 'each on a hash selecting key and value (using captures-last parameter)' do + catalog = compile_to_catalog(<<-MANIFEST) + $a = {'a'=>present,'b'=>absent,'c'=>present} + $a.each |*$kv| { + file { "/file_${kv[0]}": ensure => $kv[1] } + } + MANIFEST + + catalog.resource(:file, "/file_a")['ensure'].should == 'present' + catalog.resource(:file, "/file_b")['ensure'].should == 'absent' + catalog.resource(:file, "/file_c")['ensure'].should == 'present' + end end + context "should produce receiver" do it 'each checking produced value using single expression' do catalog = compile_to_catalog(<<-MANIFEST) $a = [1, 3, 2] $b = $a.each |$x| { "unwanted" } file { "/file_${b[1]}": ensure => present } MANIFEST catalog.resource(:file, "/file_3")['ensure'].should == 'present' end end end diff --git a/spec/unit/parser/methods/filter_spec.rb b/spec/unit/parser/methods/filter_spec.rb index c9fed31d2..5639faff7 100644 --- a/spec/unit/parser/methods/filter_spec.rb +++ b/spec/unit/parser/methods/filter_spec.rb @@ -1,135 +1,149 @@ require 'puppet' require 'spec_helper' require 'puppet_spec/compiler' +require 'matchers/resource' require 'unit/parser/methods/shared' describe 'the filter method' do include PuppetSpec::Compiler + include Matchers::Resource before :each do Puppet[:parser] = 'future' end it 'should filter on an array (all berries)' do catalog = compile_to_catalog(<<-MANIFEST) $a = ['strawberry','blueberry','orange'] $a.filter |$x|{ $x =~ /berry$/}.each |$v|{ file { "/file_$v": ensure => present } } MANIFEST - catalog.resource(:file, "/file_strawberry")['ensure'].should == 'present' - catalog.resource(:file, "/file_blueberry")['ensure'].should == 'present' + expect(catalog).to have_resource("File[/file_strawberry]").with_parameter(:ensure, 'present') + expect(catalog).to have_resource("File[/file_blueberry]").with_parameter(:ensure, 'present') end it 'should filter on enumerable type (Integer)' do catalog = compile_to_catalog(<<-MANIFEST) $a = Integer[1,10] $a.filter |$x|{ $x % 3 == 0}.each |$v|{ file { "/file_$v": ensure => present } } MANIFEST - catalog.resource(:file, "/file_3")['ensure'].should == 'present' - catalog.resource(:file, "/file_6")['ensure'].should == 'present' - catalog.resource(:file, "/file_9")['ensure'].should == 'present' + expect(catalog).to have_resource("File[/file_3]").with_parameter(:ensure, 'present') + expect(catalog).to have_resource("File[/file_6]").with_parameter(:ensure, 'present') + expect(catalog).to have_resource("File[/file_9]").with_parameter(:ensure, 'present') end it 'should filter on enumerable type (Integer) using two args index/value' do catalog = compile_to_catalog(<<-MANIFEST) $a = Integer[10,18] $a.filter |$i, $x|{ $i % 3 == 0}.each |$v|{ file { "/file_$v": ensure => present } } MANIFEST - catalog.resource(:file, "/file_10")['ensure'].should == 'present' - catalog.resource(:file, "/file_13")['ensure'].should == 'present' - catalog.resource(:file, "/file_16")['ensure'].should == 'present' + expect(catalog).to have_resource("File[/file_10]").with_parameter(:ensure, 'present') + expect(catalog).to have_resource("File[/file_13]").with_parameter(:ensure, 'present') + expect(catalog).to have_resource("File[/file_16]").with_parameter(:ensure, 'present') end it 'should produce an array when acting on an array' do catalog = compile_to_catalog(<<-MANIFEST) $a = ['strawberry','blueberry','orange'] $b = $a.filter |$x|{ $x =~ /berry$/} file { "/file_${b[0]}": ensure => present } file { "/file_${b[1]}": ensure => present } MANIFEST - catalog.resource(:file, "/file_strawberry")['ensure'].should == 'present' - catalog.resource(:file, "/file_blueberry")['ensure'].should == 'present' + expect(catalog).to have_resource("File[/file_strawberry]").with_parameter(:ensure, 'present') + expect(catalog).to have_resource("File[/file_blueberry]").with_parameter(:ensure, 'present') end it 'can filter array using index and value' do catalog = compile_to_catalog(<<-MANIFEST) $a = ['strawberry','blueberry','orange'] $b = $a.filter |$index, $x|{ $index == 0 or $index ==2} file { "/file_${b[0]}": ensure => present } file { "/file_${b[1]}": ensure => present } MANIFEST - catalog.resource(:file, "/file_strawberry")['ensure'].should == 'present' - catalog.resource(:file, "/file_orange")['ensure'].should == 'present' + expect(catalog).to have_resource("File[/file_strawberry]").with_parameter(:ensure, 'present') + expect(catalog).to have_resource("File[/file_orange]").with_parameter(:ensure, 'present') + end + + it 'can filter array using index and value (using captures-rest)' do + catalog = compile_to_catalog(<<-MANIFEST) + $a = ['strawberry','blueberry','orange'] + $b = $a.filter |*$ix|{ $ix[0] == 0 or $ix[0] ==2} + file { "/file_${b[0]}": ensure => present } + file { "/file_${b[1]}": ensure => present } + MANIFEST + + expect(catalog).to have_resource("File[/file_strawberry]").with_parameter(:ensure, 'present') + expect(catalog).to have_resource("File[/file_orange]").with_parameter(:ensure, 'present') end it 'filters on a hash (all berries) by key' do catalog = compile_to_catalog(<<-MANIFEST) $a = {'strawberry'=>'red','blueberry'=>'blue','orange'=>'orange'} $a.filter |$x|{ $x[0] =~ /berry$/}.each |$v|{ file { "/file_${v[0]}": ensure => present } } MANIFEST - catalog.resource(:file, "/file_strawberry")['ensure'].should == 'present' - catalog.resource(:file, "/file_blueberry")['ensure'].should == 'present' + expect(catalog).to have_resource("File[/file_strawberry]").with_parameter(:ensure, 'present') + expect(catalog).to have_resource("File[/file_blueberry]").with_parameter(:ensure, 'present') end it 'should produce a hash when acting on a hash' do catalog = compile_to_catalog(<<-MANIFEST) $a = {'strawberry'=>'red','blueberry'=>'blue','orange'=>'orange'} $b = $a.filter |$x|{ $x[0] =~ /berry$/} file { "/file_${b['strawberry']}": ensure => present } file { "/file_${b['blueberry']}": ensure => present } file { "/file_${b['orange']}": ensure => present } MANIFEST - catalog.resource(:file, "/file_red")['ensure'].should == 'present' - catalog.resource(:file, "/file_blue")['ensure'].should == 'present' - catalog.resource(:file, "/file_")['ensure'].should == 'present' + expect(catalog).to have_resource("File[/file_red]").with_parameter(:ensure, 'present') + expect(catalog).to have_resource("File[/file_blue]").with_parameter(:ensure, 'present') + expect(catalog).to have_resource("File[/file_]").with_parameter(:ensure, 'present') end it 'filters on a hash (all berries) by value' do catalog = compile_to_catalog(<<-MANIFEST) $a = {'strawb'=>'red berry','blueb'=>'blue berry','orange'=>'orange fruit'} $a.filter |$x|{ $x[1] =~ /berry$/}.each |$v|{ file { "/file_${v[0]}": ensure => present } } MANIFEST - catalog.resource(:file, "/file_strawb")['ensure'].should == 'present' - catalog.resource(:file, "/file_blueb")['ensure'].should == 'present' + expect(catalog).to have_resource("File[/file_strawb]").with_parameter(:ensure, 'present') + expect(catalog).to have_resource("File[/file_blueb]").with_parameter(:ensure, 'present') end context 'filter checks arguments and' do it 'raises an error when block has more than 2 argument' do expect do compile_to_catalog(<<-MANIFEST) [1].filter |$indexm, $x, $yikes|{ } MANIFEST end.to raise_error(Puppet::Error, /block must define at most two parameters/) end it 'raises an error when block has fewer than 1 argument' do expect do compile_to_catalog(<<-MANIFEST) [1].filter || { } MANIFEST end.to raise_error(Puppet::Error, /block must define at least one parameter/) end end it_should_behave_like 'all iterative functions argument checks', 'filter' it_should_behave_like 'all iterative functions hash handling', 'filter' end diff --git a/spec/unit/parser/methods/map_spec.rb b/spec/unit/parser/methods/map_spec.rb index 08f4cd124..34c5ac7b9 100644 --- a/spec/unit/parser/methods/map_spec.rb +++ b/spec/unit/parser/methods/map_spec.rb @@ -1,196 +1,209 @@ require 'puppet' require 'spec_helper' require 'puppet_spec/compiler' +require 'matchers/resource' require 'unit/parser/methods/shared' describe 'the map method' do include PuppetSpec::Compiler + include Matchers::Resource before :each do Puppet[:parser] = "future" end context "using future parser" do it 'map on an array (multiplying each value by 2)' do catalog = compile_to_catalog(<<-MANIFEST) $a = [1,2,3] $a.map |$x|{ $x*2}.each |$v|{ file { "/file_$v": ensure => present } } MANIFEST - catalog.resource(:file, "/file_2")['ensure'].should == 'present' - catalog.resource(:file, "/file_4")['ensure'].should == 'present' - catalog.resource(:file, "/file_6")['ensure'].should == 'present' + expect(catalog).to have_resource("File[/file_2]").with_parameter(:ensure, 'present') + expect(catalog).to have_resource("File[/file_4]").with_parameter(:ensure, 'present') + expect(catalog).to have_resource("File[/file_6]").with_parameter(:ensure, 'present') end it 'map on an enumerable type (multiplying each value by 2)' do catalog = compile_to_catalog(<<-MANIFEST) $a = Integer[1,3] $a.map |$x|{ $x*2}.each |$v|{ file { "/file_$v": ensure => present } } MANIFEST - catalog.resource(:file, "/file_2")['ensure'].should == 'present' - catalog.resource(:file, "/file_4")['ensure'].should == 'present' - catalog.resource(:file, "/file_6")['ensure'].should == 'present' + expect(catalog).to have_resource("File[/file_2]").with_parameter(:ensure, 'present') + expect(catalog).to have_resource("File[/file_4]").with_parameter(:ensure, 'present') + expect(catalog).to have_resource("File[/file_6]").with_parameter(:ensure, 'present') end it 'map on an integer (multiply each by 3)' do catalog = compile_to_catalog(<<-MANIFEST) 3.map |$x|{ $x*3}.each |$v|{ file { "/file_$v": ensure => present } } MANIFEST - catalog.resource(:file, "/file_0")['ensure'].should == 'present' - catalog.resource(:file, "/file_3")['ensure'].should == 'present' - catalog.resource(:file, "/file_6")['ensure'].should == 'present' + expect(catalog).to have_resource("File[/file_0]").with_parameter(:ensure, 'present') + expect(catalog).to have_resource("File[/file_3]").with_parameter(:ensure, 'present') + expect(catalog).to have_resource("File[/file_6]").with_parameter(:ensure, 'present') end it 'map on a string' do catalog = compile_to_catalog(<<-MANIFEST) $a = {a=>x, b=>y} "ab".map |$x|{$a[$x]}.each |$v|{ file { "/file_$v": ensure => present } } MANIFEST - catalog.resource(:file, "/file_x")['ensure'].should == 'present' - catalog.resource(:file, "/file_y")['ensure'].should == 'present' + expect(catalog).to have_resource("File[/file_x]").with_parameter(:ensure, 'present') + expect(catalog).to have_resource("File[/file_y]").with_parameter(:ensure, 'present') end it 'map on an array (multiplying value by 10 in even index position)' do catalog = compile_to_catalog(<<-MANIFEST) $a = [1,2,3] $a.map |$i, $x|{ if $i % 2 == 0 {$x} else {$x*10}}.each |$v|{ file { "/file_$v": ensure => present } } MANIFEST - catalog.resource(:file, "/file_1")['ensure'].should == 'present' - catalog.resource(:file, "/file_20")['ensure'].should == 'present' - catalog.resource(:file, "/file_3")['ensure'].should == 'present' + expect(catalog).to have_resource("File[/file_1]").with_parameter(:ensure, 'present') + expect(catalog).to have_resource("File[/file_20]").with_parameter(:ensure, 'present') + expect(catalog).to have_resource("File[/file_3]").with_parameter(:ensure, 'present') end it 'map on a hash selecting keys' do catalog = compile_to_catalog(<<-MANIFEST) $a = {'a'=>1,'b'=>2,'c'=>3} $a.map |$x|{ $x[0]}.each |$k|{ file { "/file_$k": ensure => present } } MANIFEST - catalog.resource(:file, "/file_a")['ensure'].should == 'present' - catalog.resource(:file, "/file_b")['ensure'].should == 'present' - catalog.resource(:file, "/file_c")['ensure'].should == 'present' + expect(catalog).to have_resource("File[/file_a]").with_parameter(:ensure, 'present') + expect(catalog).to have_resource("File[/file_b]").with_parameter(:ensure, 'present') + expect(catalog).to have_resource("File[/file_c]").with_parameter(:ensure, 'present') end it 'map on a hash selecting keys - using two block parameters' do catalog = compile_to_catalog(<<-MANIFEST) $a = {'a'=>1,'b'=>2,'c'=>3} $a.map |$k,$v|{ file { "/file_$k": ensure => present } } MANIFEST - catalog.resource(:file, "/file_a")['ensure'].should == 'present' - catalog.resource(:file, "/file_b")['ensure'].should == 'present' - catalog.resource(:file, "/file_c")['ensure'].should == 'present' + expect(catalog).to have_resource("File[/file_a]").with_parameter(:ensure, 'present') + expect(catalog).to have_resource("File[/file_b]").with_parameter(:ensure, 'present') + expect(catalog).to have_resource("File[/file_c]").with_parameter(:ensure, 'present') + end + + it 'map on a hash using captures-last parameter' do + catalog = compile_to_catalog(<<-MANIFEST) + $a = {'a'=>present,'b'=>absent,'c'=>present} + $a.map |*$kv|{ file { "/file_${kv[0]}": ensure => $kv[1] } } + MANIFEST + + expect(catalog).to have_resource("File[/file_a]").with_parameter(:ensure, 'present') + expect(catalog).to have_resource("File[/file_b]").with_parameter(:ensure, 'absent') + expect(catalog).to have_resource("File[/file_c]").with_parameter(:ensure, 'present') end it 'each on a hash selecting value' do catalog = compile_to_catalog(<<-MANIFEST) $a = {'a'=>1,'b'=>2,'c'=>3} $a.map |$x|{ $x[1]}.each |$k|{ file { "/file_$k": ensure => present } } MANIFEST - catalog.resource(:file, "/file_1")['ensure'].should == 'present' - catalog.resource(:file, "/file_2")['ensure'].should == 'present' - catalog.resource(:file, "/file_3")['ensure'].should == 'present' + expect(catalog).to have_resource("File[/file_1]").with_parameter(:ensure, 'present') + expect(catalog).to have_resource("File[/file_2]").with_parameter(:ensure, 'present') + expect(catalog).to have_resource("File[/file_3]").with_parameter(:ensure, 'present') end - it 'each on a hash selecting value - using two bloc parameters' do + it 'each on a hash selecting value - using two block parameters' do catalog = compile_to_catalog(<<-MANIFEST) $a = {'a'=>1,'b'=>2,'c'=>3} $a.map |$k,$v|{ file { "/file_$v": ensure => present } } MANIFEST - catalog.resource(:file, "/file_1")['ensure'].should == 'present' - catalog.resource(:file, "/file_2")['ensure'].should == 'present' - catalog.resource(:file, "/file_3")['ensure'].should == 'present' + expect(catalog).to have_resource("File[/file_1]").with_parameter(:ensure, 'present') + expect(catalog).to have_resource("File[/file_2]").with_parameter(:ensure, 'present') + expect(catalog).to have_resource("File[/file_3]").with_parameter(:ensure, 'present') end context "handles data type corner cases" do it "map gets values that are false" do catalog = compile_to_catalog(<<-MANIFEST) $a = [false,false] $a.map |$x| { $x }.each |$i, $v| { file { "/file_$i.$v": ensure => present } } MANIFEST - catalog.resource(:file, "/file_0.false")['ensure'].should == 'present' - catalog.resource(:file, "/file_1.false")['ensure'].should == 'present' + expect(catalog).to have_resource("File[/file_0.false]").with_parameter(:ensure, 'present') + expect(catalog).to have_resource("File[/file_1.false]").with_parameter(:ensure, 'present') end it "map gets values that are nil" do Puppet::Parser::Functions.newfunction(:nil_array, :type => :rvalue) do |args| [nil] end catalog = compile_to_catalog(<<-MANIFEST) $a = nil_array() $a.map |$x| { $x }.each |$i, $v| { file { "/file_$i.$v": ensure => present } } MANIFEST - catalog.resource(:file, "/file_0.")['ensure'].should == 'present' + expect(catalog).to have_resource("File[/file_0.]").with_parameter(:ensure, 'present') end it "map gets values that are undef" do pending "Test is flawed, but has good intentions - should be rewritten when map has moved to new func API" # The test is broken because: # - a bug caused the given value to always be overridden by a given lambda default # - non existing variable results in nil / undef, which is transformed to empty string in the 3x func API # - when lambda is called, it gets an empty string, and it is then expected to use the default value # # This is not the semantics we want (only missing argument should trigger the default value). # Finally, it is not possible to test missing arguments with the map function since the call adapts itself # to the number of lambda parameters. (There is testing of this elsewhere). # # TODO: Rewrite map function, then test that undef / nil values are passed correctly to the lambda # catalog = compile_to_catalog(<<-MANIFEST) $a = [$does_not_exist] $a.map |$x = "something"| { $x }.each |$i, $v| { file { "/file_$i.$v": ensure => present } } MANIFEST catalog.resource(:file, "/file_0.something")['ensure'].should == 'present' end end context 'map checks arguments and' do it 'raises an error when block has more than 2 argument' do expect do compile_to_catalog(<<-MANIFEST) [1].map |$index, $x, $yikes|{ } MANIFEST end.to raise_error(Puppet::Error, /block must define at most two parameters/) end it 'raises an error when block has fewer than 1 argument' do expect do compile_to_catalog(<<-MANIFEST) [1].map || { } MANIFEST end.to raise_error(Puppet::Error, /block must define at least one parameter/) end end it_should_behave_like 'all iterative functions argument checks', 'map' it_should_behave_like 'all iterative functions hash handling', 'map' end end diff --git a/spec/unit/parser/methods/reduce_spec.rb b/spec/unit/parser/methods/reduce_spec.rb index 4f0c14e5e..eb2eacac1 100644 --- a/spec/unit/parser/methods/reduce_spec.rb +++ b/spec/unit/parser/methods/reduce_spec.rb @@ -1,78 +1,92 @@ require 'puppet' require 'spec_helper' require 'puppet_spec/compiler' +require 'matchers/resource' describe 'the reduce method' do include PuppetSpec::Compiler + include Matchers::Resource before :all do # enable switching back @saved_parser = Puppet[:parser] # These tests only work with future parser end after :all do # switch back to original Puppet[:parser] = @saved_parser end before :each do node = Puppet::Node.new("floppy", :environment => 'production') @compiler = Puppet::Parser::Compiler.new(node) @scope = Puppet::Parser::Scope.new(@compiler) @topscope = @scope.compiler.topscope @scope.parent = @topscope Puppet[:parser] = 'future' end context "should be callable as" do it 'reduce on an array' do catalog = compile_to_catalog(<<-MANIFEST) $a = [1,2,3] $b = $a.reduce |$memo, $x| { $memo + $x } file { "/file_$b": ensure => present } MANIFEST - catalog.resource(:file, "/file_6")['ensure'].should == 'present' + expect(catalog).to have_resource("File[/file_6]").with_parameter(:ensure, 'present') + end + + it 'reduce on an array with captures rest in lambda' do + catalog = compile_to_catalog(<<-MANIFEST) + $a = [1,2,3] + $b = $a.reduce |*$mx| { $mx[0] + $mx[1] } + file { "/file_$b": ensure => present } + MANIFEST + + expect(catalog).to have_resource("File[/file_6]").with_parameter(:ensure, 'present') end it 'reduce on enumerable type' do catalog = compile_to_catalog(<<-MANIFEST) $a = Integer[1,3] $b = $a.reduce |$memo, $x| { $memo + $x } file { "/file_$b": ensure => present } MANIFEST - catalog.resource(:file, "/file_6")['ensure'].should == 'present' + expect(catalog).to have_resource("File[/file_6]").with_parameter(:ensure, 'present') end it 'reduce on an array with start value' do catalog = compile_to_catalog(<<-MANIFEST) $a = [1,2,3] $b = $a.reduce(4) |$memo, $x| { $memo + $x } file { "/file_$b": ensure => present } MANIFEST - catalog.resource(:file, "/file_10")['ensure'].should == 'present' + expect(catalog).to have_resource("File[/file_10]").with_parameter(:ensure, 'present') end + it 'reduce on a hash' do catalog = compile_to_catalog(<<-MANIFEST) $a = {a=>1, b=>2, c=>3} $start = [ignored, 4] $b = $a.reduce |$memo, $x| {['sum', $memo[1] + $x[1]] } file { "/file_${$b[0]}_${$b[1]}": ensure => present } MANIFEST - catalog.resource(:file, "/file_sum_6")['ensure'].should == 'present' + expect(catalog).to have_resource("File[/file_sum_6]").with_parameter(:ensure, 'present') end + it 'reduce on a hash with start value' do catalog = compile_to_catalog(<<-MANIFEST) $a = {a=>1, b=>2, c=>3} $start = ['ignored', 4] $b = $a.reduce($start) |$memo, $x| { ['sum', $memo[1] + $x[1]] } file { "/file_${$b[0]}_${$b[1]}": ensure => present } MANIFEST - catalog.resource(:file, "/file_sum_10")['ensure'].should == 'present' + expect(catalog).to have_resource("File[/file_sum_10]").with_parameter(:ensure, 'present') end end end diff --git a/spec/unit/parser/methods/slice_spec.rb b/spec/unit/parser/methods/slice_spec.rb index 1de1dd0f1..262537045 100644 --- a/spec/unit/parser/methods/slice_spec.rb +++ b/spec/unit/parser/methods/slice_spec.rb @@ -1,135 +1,149 @@ require 'puppet' require 'spec_helper' require 'puppet_spec/compiler' require 'rubygems' +require 'matchers/resource' describe 'methods' do include PuppetSpec::Compiler + include Matchers::Resource before :all do # enable switching back @saved_parser = Puppet[:parser] # These tests only work with future parser Puppet[:parser] = 'future' end after :all do # switch back to original Puppet[:parser] = @saved_parser end before :each do node = Puppet::Node.new("floppy", :environment => 'production') @compiler = Puppet::Parser::Compiler.new(node) @scope = Puppet::Parser::Scope.new(@compiler) @topscope = @scope.compiler.topscope @scope.parent = @topscope Puppet[:parser] = 'future' end context "should be callable on array as" do it 'slice with explicit parameters' do catalog = compile_to_catalog(<<-MANIFEST) $a = [1, present, 2, absent, 3, present] $a.slice(2) |$k,$v| { file { "/file_${$k}": ensure => $v } } MANIFEST - catalog.resource(:file, "/file_1")['ensure'].should == 'present' - catalog.resource(:file, "/file_2")['ensure'].should == 'absent' - catalog.resource(:file, "/file_3")['ensure'].should == 'present' + expect(catalog).to have_resource("File[/file_1]").with_parameter(:ensure, 'present') + expect(catalog).to have_resource("File[/file_2]").with_parameter(:ensure, 'absent') + expect(catalog).to have_resource("File[/file_3]").with_parameter(:ensure, 'present') + end + + it 'slice with captures last' do + catalog = compile_to_catalog(<<-MANIFEST) + $a = [1, present, 2, absent, 3, present] + $a.slice(2) |*$kv| { + file { "/file_${$kv[0]}": ensure => $kv[1] } + } + MANIFEST + + expect(catalog).to have_resource("File[/file_1]").with_parameter(:ensure, 'present') + expect(catalog).to have_resource("File[/file_2]").with_parameter(:ensure, 'absent') + expect(catalog).to have_resource("File[/file_3]").with_parameter(:ensure, 'present') end it 'slice with one parameter' do catalog = compile_to_catalog(<<-MANIFEST) $a = [1, present, 2, absent, 3, present] $a.slice(2) |$k| { file { "/file_${$k[0]}": ensure => $k[1] } } MANIFEST - catalog.resource(:file, "/file_1")['ensure'].should == 'present' - catalog.resource(:file, "/file_2")['ensure'].should == 'absent' - catalog.resource(:file, "/file_3")['ensure'].should == 'present' + expect(catalog).to have_resource("File[/file_1]").with_parameter(:ensure, 'present') + expect(catalog).to have_resource("File[/file_2]").with_parameter(:ensure, 'absent') + expect(catalog).to have_resource("File[/file_3]").with_parameter(:ensure, 'present') end it 'slice with shorter last slice' do catalog = compile_to_catalog(<<-MANIFEST) $a = [1, present, 2, present, 3, absent] $a.slice(4) |$a, $b, $c, $d| { file { "/file_$a.$c": ensure => $b } } MANIFEST - catalog.resource(:file, "/file_1.2")['ensure'].should == 'present' - catalog.resource(:file, "/file_3.")['ensure'].should == 'absent' + expect(catalog).to have_resource("File[/file_1.2]").with_parameter(:ensure, 'present') + expect(catalog).to have_resource("File[/file_3.]").with_parameter(:ensure, 'absent') end end context "should be callable on hash as" do it 'slice with explicit parameters, missing are empty' do catalog = compile_to_catalog(<<-MANIFEST) $a = {1=>present, 2=>present, 3=>absent} $a.slice(2) |$a,$b| { file { "/file_${a[0]}.${b[0]}": ensure => $a[1] } } MANIFEST - catalog.resource(:file, "/file_1.2")['ensure'].should == 'present' - catalog.resource(:file, "/file_3.")['ensure'].should == 'absent' + expect(catalog).to have_resource("File[/file_1.2]").with_parameter(:ensure, 'present') + expect(catalog).to have_resource("File[/file_3.]").with_parameter(:ensure, 'absent') end end context "should be callable on enumerable types as" do it 'slice with integer range' do catalog = compile_to_catalog(<<-MANIFEST) $a = Integer[1,4] $a.slice(2) |$a,$b| { file { "/file_${a}.${b}": ensure => present } } MANIFEST - catalog.resource(:file, "/file_1.2")['ensure'].should == 'present' - catalog.resource(:file, "/file_3.4")['ensure'].should == 'present' + expect(catalog).to have_resource("File[/file_1.2]").with_parameter(:ensure, 'present') + expect(catalog).to have_resource("File[/file_3.4]").with_parameter(:ensure, 'present') end it 'slice with integer' do catalog = compile_to_catalog(<<-MANIFEST) 4.slice(2) |$a,$b| { file { "/file_${a}.${b}": ensure => present } } MANIFEST - catalog.resource(:file, "/file_0.1")['ensure'].should == 'present' - catalog.resource(:file, "/file_2.3")['ensure'].should == 'present' + expect(catalog).to have_resource("File[/file_0.1]").with_parameter(:ensure, 'present') + expect(catalog).to have_resource("File[/file_2.3]").with_parameter(:ensure, 'present') end it 'slice with string' do catalog = compile_to_catalog(<<-MANIFEST) 'abcd'.slice(2) |$a,$b| { file { "/file_${a}.${b}": ensure => present } } MANIFEST - catalog.resource(:file, "/file_a.b")['ensure'].should == 'present' - catalog.resource(:file, "/file_c.d")['ensure'].should == 'present' + expect(catalog).to have_resource("File[/file_a.b]").with_parameter(:ensure, 'present') + expect(catalog).to have_resource("File[/file_c.d]").with_parameter(:ensure, 'present') end end context "when called without a block" do it "should produce an array with the result" do catalog = compile_to_catalog(<<-MANIFEST) $a = [1, present, 2, absent, 3, present] $a.slice(2).each |$k| { file { "/file_${$k[0]}": ensure => $k[1] } } MANIFEST - catalog.resource(:file, "/file_1")['ensure'].should == 'present' - catalog.resource(:file, "/file_2")['ensure'].should == 'absent' - catalog.resource(:file, "/file_3")['ensure'].should == 'present' - + expect(catalog).to have_resource("File[/file_1]").with_parameter(:ensure, 'present') + expect(catalog).to have_resource("File[/file_2]").with_parameter(:ensure, 'absent') + expect(catalog).to have_resource("File[/file_3]").with_parameter(:ensure, 'present') end end end diff --git a/spec/unit/pops/evaluator/evaluating_parser_spec.rb b/spec/unit/pops/evaluator/evaluating_parser_spec.rb index e5912d972..8477aa0f2 100644 --- a/spec/unit/pops/evaluator/evaluating_parser_spec.rb +++ b/spec/unit/pops/evaluator/evaluating_parser_spec.rb @@ -1,1211 +1,1214 @@ require 'spec_helper' require 'puppet/pops' require 'puppet/pops/evaluator/evaluator_impl' require 'puppet/loaders' require 'puppet_spec/pops' require 'puppet_spec/scope' require 'puppet/parser/e4_parser_adapter' # relative to this spec file (./) does not work as this file is loaded by rspec #require File.join(File.dirname(__FILE__), '/evaluator_rspec_helper') describe 'Puppet::Pops::Evaluator::EvaluatorImpl' do include PuppetSpec::Pops include PuppetSpec::Scope before(:each) do Puppet[:strict_variables] = true # These must be set since the is 3x logic that triggers on these even if the tests are explicit # about selection of parser and evaluator # Puppet[:parser] = 'future' Puppet[:evaluator] = 'future' # Puppetx cannot be loaded until the correct parser has been set (injector is turned off otherwise) require 'puppetx' end let(:parser) { Puppet::Pops::Parser::EvaluatingParser::Transitional.new } let(:node) { 'node.example.com' } let(:scope) { s = create_test_scope_for_node(node); s } types = Puppet::Pops::Types::TypeFactory context "When evaluator evaluates literals" do { "1" => 1, "010" => 8, "0x10" => 16, "3.14" => 3.14, "0.314e1" => 3.14, "31.4e-1" => 3.14, "'1'" => '1', "'banana'" => 'banana', '"banana"' => 'banana', "banana" => 'banana', "banana::split" => 'banana::split', "false" => false, "true" => true, "Array" => types.array_of_data(), "/.*/" => /.*/ }.each do |source, result| it "should parse and evaluate the expression '#{source}' to #{result}" do parser.evaluate_string(scope, source, __FILE__).should == result end end end context "When the evaluator evaluates Lists and Hashes" do { "[]" => [], "[1,2,3]" => [1,2,3], "[1,[2.0, 2.1, [2.2]],[3.0, 3.1]]" => [1,[2.0, 2.1, [2.2]],[3.0, 3.1]], "[2 + 2]" => [4], "[1,2,3] == [1,2,3]" => true, "[1,2,3] != [2,3,4]" => true, "[1,2,3] == [2,2,3]" => false, "[1,2,3] != [1,2,3]" => false, "[1,2,3][2]" => 3, "[1,2,3] + [4,5]" => [1,2,3,4,5], "[1,2,3] + [[4,5]]" => [1,2,3,[4,5]], "[1,2,3] + 4" => [1,2,3,4], "[1,2,3] << [4,5]" => [1,2,3,[4,5]], "[1,2,3] << {'a' => 1, 'b'=>2}" => [1,2,3,{'a' => 1, 'b'=>2}], "[1,2,3] << 4" => [1,2,3,4], "[1,2,3,4] - [2,3]" => [1,4], "[1,2,3,4] - [2,5]" => [1,3,4], "[1,2,3,4] - 2" => [1,3,4], "[1,2,3,[2],4] - 2" => [1,3,[2],4], "[1,2,3,[2,3],4] - [[2,3]]" => [1,2,3,4], "[1,2,3,3,2,4,2,3] - [2,3]" => [1,4], "[1,2,3,['a',1],['b',2]] - {'a' => 1, 'b'=>2}" => [1,2,3], "[1,2,3,{'a'=>1,'b'=>2}] - [{'a' => 1, 'b'=>2}]" => [1,2,3], }.each do |source, result| it "should parse and evaluate the expression '#{source}' to #{result}" do parser.evaluate_string(scope, source, __FILE__).should == result end end { "[1,2,3] + {'a' => 1, 'b'=>2}" => [1,2,3,['a',1],['b',2]], }.each do |source, result| it "should parse and evaluate the expression '#{source}' to #{result}" do # This test must be done with match_array since the order of the hash # is undefined and Ruby 1.8.7 and 1.9.3 produce different results. expect(parser.evaluate_string(scope, source, __FILE__)).to match_array(result) end end { "[1,2,3][a]" => :error, }.each do |source, result| it "should parse and raise error for '#{source}'" do expect { parser.evaluate_string(scope, source, __FILE__) }.to raise_error(Puppet::ParseError) end end { "{}" => {}, "{'a'=>1,'b'=>2}" => {'a'=>1,'b'=>2}, "{'a'=>1,'b'=>{'x'=>2.1,'y'=>2.2}}" => {'a'=>1,'b'=>{'x'=>2.1,'y'=>2.2}}, "{'a'=> 2 + 2}" => {'a'=> 4}, "{'a'=> 1, 'b'=>2} == {'a'=> 1, 'b'=>2}" => true, "{'a'=> 1, 'b'=>2} != {'x'=> 1, 'b'=>2}" => true, "{'a'=> 1, 'b'=>2} == {'a'=> 2, 'b'=>3}" => false, "{'a'=> 1, 'b'=>2} != {'a'=> 1, 'b'=>2}" => false, "{a => 1, b => 2}[b]" => 2, "{2+2 => sum, b => 2}[4]" => 'sum', "{'a'=>1, 'b'=>2} + {'c'=>3}" => {'a'=>1,'b'=>2,'c'=>3}, "{'a'=>1, 'b'=>2} + {'b'=>3}" => {'a'=>1,'b'=>3}, "{'a'=>1, 'b'=>2} + ['c', 3, 'b', 3]" => {'a'=>1,'b'=>3, 'c'=>3}, "{'a'=>1, 'b'=>2} + [['c', 3], ['b', 3]]" => {'a'=>1,'b'=>3, 'c'=>3}, "{'a'=>1, 'b'=>2} - {'b' => 3}" => {'a'=>1}, "{'a'=>1, 'b'=>2, 'c'=>3} - ['b', 'c']" => {'a'=>1}, "{'a'=>1, 'b'=>2, 'c'=>3} - 'c'" => {'a'=>1, 'b'=>2}, }.each do |source, result| it "should parse and evaluate the expression '#{source}' to #{result}" do parser.evaluate_string(scope, source, __FILE__).should == result end end { "{'a' => 1, 'b'=>2} << 1" => :error, }.each do |source, result| it "should parse and raise error for '#{source}'" do expect { parser.evaluate_string(scope, source, __FILE__) }.to raise_error(Puppet::ParseError) end end end context "When the evaluator perform comparisons" do { "'a' == 'a'" => true, "'a' == 'b'" => false, "'a' != 'a'" => false, "'a' != 'b'" => true, "'a' < 'b' " => true, "'a' < 'a' " => false, "'b' < 'a' " => false, "'a' <= 'b'" => true, "'a' <= 'a'" => true, "'b' <= 'a'" => false, "'a' > 'b' " => false, "'a' > 'a' " => false, "'b' > 'a' " => true, "'a' >= 'b'" => false, "'a' >= 'a'" => true, "'b' >= 'a'" => true, "'a' == 'A'" => true, "'a' != 'A'" => false, "'a' > 'A'" => false, "'a' >= 'A'" => true, "'A' < 'a'" => false, "'A' <= 'a'" => true, "1 == 1" => true, "1 == 2" => false, "1 != 1" => false, "1 != 2" => true, "1 < 2 " => true, "1 < 1 " => false, "2 < 1 " => false, "1 <= 2" => true, "1 <= 1" => true, "2 <= 1" => false, "1 > 2 " => false, "1 > 1 " => false, "2 > 1 " => true, "1 >= 2" => false, "1 >= 1" => true, "2 >= 1" => true, "1 == 1.0 " => true, "1 < 1.1 " => true, "'1' < 1.1" => true, "1.0 == 1 " => true, "1.0 < 2 " => true, "1.0 < 'a'" => true, "'1.0' < 1.1" => true, "'1.0' < 'a'" => true, "'1.0' < '' " => true, "'1.0' < ' '" => true, "'a' > '1.0'" => true, "/.*/ == /.*/ " => true, "/.*/ != /a.*/" => true, "true == true " => true, "false == false" => true, "true == false" => false, }.each do |source, result| it "should parse and evaluate the expression '#{source}' to #{result}" do parser.evaluate_string(scope, source, __FILE__).should == result end end { "'a' =~ /.*/" => true, "'a' =~ '.*'" => true, "/.*/ != /a.*/" => true, "'a' !~ /b.*/" => true, "'a' !~ 'b.*'" => true, '$x = a; a =~ "$x.*"' => true, "a =~ Pattern['a.*']" => true, "a =~ Regexp['a.*']" => false, # String is not subtype of Regexp. PUP-957 "$x = /a.*/ a =~ $x" => true, "$x = Pattern['a.*'] a =~ $x" => true, "1 =~ Integer" => true, "1 !~ Integer" => false, "[1,2,3] =~ Array[Integer[1,10]]" => true, }.each do |source, result| it "should parse and evaluate the expression '#{source}' to #{result}" do parser.evaluate_string(scope, source, __FILE__).should == result end end { "666 =~ /6/" => :error, "[a] =~ /a/" => :error, "{a=>1} =~ /a/" => :error, "/a/ =~ /a/" => :error, "Array =~ /A/" => :error, }.each do |source, result| it "should parse and raise error for '#{source}'" do expect { parser.evaluate_string(scope, source, __FILE__) }.to raise_error(Puppet::ParseError) end end { "1 in [1,2,3]" => true, "4 in [1,2,3]" => false, "a in {x=>1, a=>2}" => true, "z in {x=>1, a=>2}" => false, "ana in bananas" => true, "xxx in bananas" => false, "/ana/ in bananas" => true, "/xxx/ in bananas" => false, "ANA in bananas" => false, # ANA is a type, not a String "String[1] in bananas" => false, # Philosophically true though :-) "'ANA' in bananas" => true, "ana in 'BANANAS'" => true, "/ana/ in 'BANANAS'" => false, "/ANA/ in 'BANANAS'" => true, "xxx in 'BANANAS'" => false, "[2,3] in [1,[2,3],4]" => true, "[2,4] in [1,[2,3],4]" => false, "[a,b] in ['A',['A','B'],'C']" => true, "[x,y] in ['A',['A','B'],'C']" => false, "a in {a=>1}" => true, "x in {a=>1}" => false, "'A' in {a=>1}" => true, "'X' in {a=>1}" => false, "a in {'A'=>1}" => true, "x in {'A'=>1}" => false, "/xxx/ in {'aaaxxxbbb'=>1}" => true, "/yyy/ in {'aaaxxxbbb'=>1}" => false, "15 in [1, 0xf]" => true, "15 in [1, '0xf']" => true, "'15' in [1, 0xf]" => true, "15 in [1, 115]" => false, "1 in [11, '111']" => false, "'1' in [11, '111']" => false, "Array[Integer] in [2, 3]" => false, "Array[Integer] in [2, [3, 4]]" => true, "Array[Integer] in [2, [a, 4]]" => false, "Integer in { 2 =>'a'}" => true, "Integer[5,10] in [1,5,3]" => true, "Integer[5,10] in [1,2,3]" => false, "Integer in {'a'=>'a'}" => false, "Integer in {'a'=>1}" => false, }.each do |source, result| it "should parse and evaluate the expression '#{source}' to #{result}" do parser.evaluate_string(scope, source, __FILE__).should == result end end { 'Object' => ['Data', 'Scalar', 'Numeric', 'Integer', 'Float', 'Boolean', 'String', 'Pattern', 'Collection', 'Array', 'Hash', 'CatalogEntry', 'Resource', 'Class', 'Undef', 'File', 'NotYetKnownResourceType'], # Note, Data > Collection is false (so not included) 'Data' => ['Scalar', 'Numeric', 'Integer', 'Float', 'Boolean', 'String', 'Pattern', 'Array', 'Hash',], 'Scalar' => ['Numeric', 'Integer', 'Float', 'Boolean', 'String', 'Pattern'], 'Numeric' => ['Integer', 'Float'], 'CatalogEntry' => ['Class', 'Resource', 'File', 'NotYetKnownResourceType'], 'Integer[1,10]' => ['Integer[2,3]'], }.each do |general, specials| specials.each do |special | it "should compute that #{general} > #{special}" do parser.evaluate_string(scope, "#{general} > #{special}", __FILE__).should == true end it "should compute that #{special} < #{general}" do parser.evaluate_string(scope, "#{special} < #{general}", __FILE__).should == true end it "should compute that #{general} != #{special}" do parser.evaluate_string(scope, "#{special} != #{general}", __FILE__).should == true end end end { 'Integer[1,10] > Integer[2,3]' => true, 'Integer[1,10] == Integer[2,3]' => false, 'Integer[1,10] > Integer[0,5]' => false, 'Integer[1,10] > Integer[1,10]' => false, 'Integer[1,10] >= Integer[1,10]' => true, 'Integer[1,10] == Integer[1,10]' => true, }.each do |source, result| it "should parse and evaluate the integer range comparison expression '#{source}' to #{result}" do parser.evaluate_string(scope, source, __FILE__).should == result end end end context "When the evaluator performs arithmetic" do context "on Integers" do { "2+2" => 4, "2 + 2" => 4, "7 - 3" => 4, "6 * 3" => 18, "6 / 3" => 2, "6 % 3" => 0, "10 % 3" => 1, "-(6/3)" => -2, "-6/3 " => -2, "8 >> 1" => 4, "8 << 1" => 16, }.each do |source, result| it "should parse and evaluate the expression '#{source}' to #{result}" do parser.evaluate_string(scope, source, __FILE__).should == result end end context "on Floats" do { "2.2 + 2.2" => 4.4, "7.7 - 3.3" => 4.4, "6.1 * 3.1" => 18.91, "6.6 / 3.3" => 2.0, "-(6.0/3.0)" => -2.0, "-6.0/3.0 " => -2.0, }.each do |source, result| it "should parse and evaluate the expression '#{source}' to #{result}" do parser.evaluate_string(scope, source, __FILE__).should == result end end { "3.14 << 2" => :error, "3.14 >> 2" => :error, "6.6 % 3.3" => 0.0, "10.0 % 3.0" => 1.0, }.each do |source, result| it "should parse and raise error for '#{source}'" do expect { parser.evaluate_string(scope, source, __FILE__) }.to raise_error(Puppet::ParseError) end end end context "on strings requiring boxing to Numeric" do { "'2' + '2'" => 4, "'2.2' + '2.2'" => 4.4, "'0xF7' + '010'" => 0xFF, "'0xF7' + '0x8'" => 0xFF, "'0367' + '010'" => 0xFF, "'012.3' + '010'" => 20.3, }.each do |source, result| it "should parse and evaluate the expression '#{source}' to #{result}" do parser.evaluate_string(scope, source, __FILE__).should == result end end { "'0888' + '010'" => :error, "'0xWTF' + '010'" => :error, "'0x12.3' + '010'" => :error, "'0x12.3' + '010'" => :error, }.each do |source, result| it "should parse and raise error for '#{source}'" do expect { parser.evaluate_string(scope, source, __FILE__) }.to raise_error(Puppet::ParseError) end end end end end # arithmetic context "When the evaluator evaluates assignment" do { "$a = 5" => 5, "$a = 5; $a" => 5, "$a = 5; $b = 6; $a" => 5, "$a = $b = 5; $a == $b" => true, "$a = [1,2,3]; [x].map |$x| { $a += x; $a }" => [[1,2,3,'x']], "$a = [a,x,c]; [x].map |$x| { $a -= x; $a }" => [['a','c']], }.each do |source, result| it "should parse and evaluate the expression '#{source}' to #{result}" do parser.evaluate_string(scope, source, __FILE__).should == result end end { "[a,b,c] = [1,2,3]; $a == 1 and $b == 2 and $c == 3" => :error, "[a,b,c] = {b=>2,c=>3,a=>1}; $a == 1 and $b == 2 and $c == 3" => :error, "$a = [1,2,3]; [x].collect |$x| { [a] += x; $a }" => :error, "$a = [a,x,c]; [x].collect |$x| { [a] -= x; $a }" => :error, }.each do |source, result| it "should parse and evaluate the expression '#{source}' to #{result}" do expect { parser.evaluate_string(scope, source, __FILE__)}.to raise_error(Puppet::ParseError) end end end context "When the evaluator evaluates conditionals" do { "if true {5}" => 5, "if false {5}" => nil, "if false {2} else {5}" => 5, "if false {2} elsif true {5}" => 5, "if false {2} elsif false {5}" => nil, "unless false {5}" => 5, "unless true {5}" => nil, "unless true {2} else {5}" => 5, "$a = if true {5} $a" => 5, "$a = if false {5} $a" => nil, "$a = if false {2} else {5} $a" => 5, "$a = if false {2} elsif true {5} $a" => 5, "$a = if false {2} elsif false {5} $a" => nil, "$a = unless false {5} $a" => 5, "$a = unless true {5} $a" => nil, "$a = unless true {2} else {5} $a" => 5, }.each do |source, result| it "should parse and evaluate the expression '#{source}' to #{result}" do parser.evaluate_string(scope, source, __FILE__).should == result end end { "case 1 { 1 : { yes } }" => 'yes', "case 2 { 1,2,3 : { yes} }" => 'yes', "case 2 { 1,3 : { no } 2: { yes} }" => 'yes', "case 2 { 1,3 : { no } 5: { no } default: { yes }}" => 'yes', "case 2 { 1,3 : { no } 5: { no } }" => nil, "case 'banana' { 1,3 : { no } /.*ana.*/: { yes } }" => 'yes', "case 'banana' { /.*(ana).*/: { $1 } }" => 'ana', "case [1] { Array : { yes } }" => 'yes', "case [1] { Array[String] : { no } Array[Integer]: { yes } }" => 'yes', "case 1 { Integer : { yes } Type[Integer] : { no } }" => 'yes', "case Integer { Integer : { no } Type[Integer] : { yes } }" => 'yes', # supports unfold "case ringo { *[paul, john, ringo, george] : { 'beatle' } }" => 'beatle', }.each do |source, result| it "should parse and evaluate the expression '#{source}' to #{result}" do parser.evaluate_string(scope, source, __FILE__).should == result end end { "2 ? { 1 => no, 2 => yes}" => 'yes', "3 ? { 1 => no, 2 => no, default => yes }" => 'yes', "3 ? { 1 => no, default => yes, 3 => no }" => 'no', "3 ? { 1 => no, 3 => no, default => yes }" => 'no', "4 ? { 1 => no, default => yes, 3 => no }" => 'yes', "4 ? { 1 => no, 3 => no, default => yes }" => 'yes', "'banana' ? { /.*(ana).*/ => $1 }" => 'ana', "[2] ? { Array[String] => yes, Array => yes}" => 'yes', "ringo ? *[paul, john, ringo, george] => 'beatle'" => 'beatle', }.each do |source, result| it "should parse and evaluate the expression '#{source}' to #{result}" do parser.evaluate_string(scope, source, __FILE__).should == result end end it 'fails if a selector does not match' do expect{parser.evaluate_string(scope, "2 ? 3 => 4")}.to raise_error(/No matching entry for selector parameter with value '2'/) end end context "When evaluator evaluated unfold" do { "*[1,2,3]" => [1,2,3], "*1" => [1], "*'a'" => ['a'] }.each do |source, result| it "should parse and evaluate the expression '#{source}' to #{result}" do parser.evaluate_string(scope, source, __FILE__).should == result end end it "should parse and evaluate the expression '*{a=>10, b=>20} to [['a',10],['b',20]]" do result = parser.evaluate_string(scope, '*{a=>10, b=>20}', __FILE__) expect(result).to include(['a', 10]) expect(result).to include(['b', 20]) end end context "When evaluator performs [] operations" do { "[1,2,3][0]" => 1, "[1,2,3][2]" => 3, "[1,2,3][3]" => nil, "[1,2,3][-1]" => 3, "[1,2,3][-2]" => 2, "[1,2,3][-4]" => nil, "[1,2,3,4][0,2]" => [1,2], "[1,2,3,4][1,3]" => [2,3,4], "[1,2,3,4][-2,2]" => [3,4], "[1,2,3,4][-3,2]" => [2,3], "[1,2,3,4][3,5]" => [4], "[1,2,3,4][5,2]" => [], "[1,2,3,4][0,-1]" => [1,2,3,4], "[1,2,3,4][0,-2]" => [1,2,3], "[1,2,3,4][0,-4]" => [1], "[1,2,3,4][0,-5]" => [], "[1,2,3,4][-5,2]" => [1], "[1,2,3,4][-5,-3]" => [1,2], "[1,2,3,4][-6,-3]" => [1,2], "[1,2,3,4][2,-3]" => [], "[1,*[2,3],4]" => [1,2,3,4], "[1,*[2,3],4][1]" => 2, }.each do |source, result| it "should parse and evaluate the expression '#{source}' to #{result}" do parser.evaluate_string(scope, source, __FILE__).should == result end end { "{a=>1, b=>2, c=>3}[a]" => 1, "{a=>1, b=>2, c=>3}[c]" => 3, "{a=>1, b=>2, c=>3}[x]" => nil, "{a=>1, b=>2, c=>3}[c,b]" => [3,2], "{a=>1, b=>2, c=>3}[a,b,c]" => [1,2,3], "{a=>{b=>{c=>'it works'}}}[a][b][c]" => 'it works', "$a = {undef => 10} $a[free_lunch]" => nil, "$a = {undef => 10} $a[undef]" => 10, "$a = {undef => 10} $a[$a[free_lunch]]" => 10, "$a = {} $a[free_lunch] == undef" => true, }.each do |source, result| it "should parse and evaluate the expression '#{source}' to #{result}" do parser.evaluate_string(scope, source, __FILE__).should == result end end { "'abc'[0]" => 'a', "'abc'[2]" => 'c', "'abc'[-1]" => 'c', "'abc'[-2]" => 'b', "'abc'[-3]" => 'a', "'abc'[-4]" => '', "'abc'[3]" => '', "abc[0]" => 'a', "abc[2]" => 'c', "abc[-1]" => 'c', "abc[-2]" => 'b', "abc[-3]" => 'a', "abc[-4]" => '', "abc[3]" => '', "'abcd'[0,2]" => 'ab', "'abcd'[1,3]" => 'bcd', "'abcd'[-2,2]" => 'cd', "'abcd'[-3,2]" => 'bc', "'abcd'[3,5]" => 'd', "'abcd'[5,2]" => '', "'abcd'[0,-1]" => 'abcd', "'abcd'[0,-2]" => 'abc', "'abcd'[0,-4]" => 'a', "'abcd'[0,-5]" => '', "'abcd'[-5,2]" => 'a', "'abcd'[-5,-3]" => 'ab', "'abcd'[-6,-3]" => 'ab', "'abcd'[2,-3]" => '', }.each do |source, result| it "should parse and evaluate the expression '#{source}' to #{result}" do parser.evaluate_string(scope, source, __FILE__).should == result end end # Type operations (full set tested by tests covering type calculator) { "Array[Integer]" => types.array_of(types.integer), "Array[Integer,1]" => types.constrain_size(types.array_of(types.integer),1, :default), "Array[Integer,1,2]" => types.constrain_size(types.array_of(types.integer),1, 2), "Array[Integer,Integer[1,2]]" => types.constrain_size(types.array_of(types.integer),1, 2), "Array[Integer,Integer[1]]" => types.constrain_size(types.array_of(types.integer),1, :default), "Hash[Integer,Integer]" => types.hash_of(types.integer, types.integer), "Hash[Integer,Integer,1]" => types.constrain_size(types.hash_of(types.integer, types.integer),1, :default), "Hash[Integer,Integer,1,2]" => types.constrain_size(types.hash_of(types.integer, types.integer),1, 2), "Hash[Integer,Integer,Integer[1,2]]" => types.constrain_size(types.hash_of(types.integer, types.integer),1, 2), "Hash[Integer,Integer,Integer[1]]" => types.constrain_size(types.hash_of(types.integer, types.integer),1, :default), "Resource[File]" => types.resource('File'), "Resource['File']" => types.resource(types.resource('File')), "File[foo]" => types.resource('file', 'foo'), "File[foo, bar]" => [types.resource('file', 'foo'), types.resource('file', 'bar')], "Pattern[a, /b/, Pattern[c], Regexp[d]]" => types.pattern('a', 'b', 'c', 'd'), "String[1,2]" => types.constrain_size(types.string,1, 2), "String[Integer[1,2]]" => types.constrain_size(types.string,1, 2), "String[Integer[1]]" => types.constrain_size(types.string,1, :default), }.each do |source, result| it "should parse and evaluate the expression '#{source}' to #{result}" do parser.evaluate_string(scope, source, __FILE__).should == result end end # LHS where [] not supported, and missing key(s) { "Array[]" => :error, "'abc'[]" => :error, "Resource[]" => :error, "File[]" => :error, "String[]" => :error, "1[]" => :error, "3.14[]" => :error, "/.*/[]" => :error, "$a=[1] $a[]" => :error, }.each do |source, result| it "should parse and evaluate the expression '#{source}' to #{result}" do expect { parser.evaluate_string(scope, source, __FILE__)}.to raise_error(/Syntax error/) end end # Errors when wrong number/type of keys are used { "Array[0]" => 'Array-Type[] arguments must be types. Got Fixnum', "Hash[0]" => 'Hash-Type[] arguments must be types. Got Fixnum', "Hash[Integer, 0]" => 'Hash-Type[] arguments must be types. Got Fixnum', "Array[Integer,1,2,3]" => 'Array-Type[] accepts 1 to 3 arguments. Got 4', "Array[Integer,String]" => "A Type's size constraint arguments must be a single Integer type, or 1-2 integers (or default). Got a String-Type", "Hash[Integer,String, 1,2,3]" => 'Hash-Type[] accepts 1 to 4 arguments. Got 5', "'abc'[x]" => "The value 'x' cannot be converted to Numeric", "'abc'[1.0]" => "A String[] cannot use Float where Integer is expected", "'abc'[1,2,3]" => "String supports [] with one or two arguments. Got 3", "Resource[0]" => 'First argument to Resource[] must be a resource type or a String. Got Fixnum', "Resource[a, 0]" => 'Error creating type specialization of a Resource-Type, Cannot use Fixnum where String is expected', "File[0]" => 'Error creating type specialization of a File-Type, Cannot use Fixnum where String is expected', "String[a]" => "A Type's size constraint arguments must be a single Integer type, or 1-2 integers (or default). Got a String", "Pattern[0]" => 'Error creating type specialization of a Pattern-Type, Cannot use Fixnum where String or Regexp or Pattern-Type or Regexp-Type is expected', "Regexp[0]" => 'Error creating type specialization of a Regexp-Type, Cannot use Fixnum where String or Regexp is expected', "Regexp[a,b]" => 'A Regexp-Type[] accepts 1 argument. Got 2', "true[0]" => "Operator '[]' is not applicable to a Boolean", "1[0]" => "Operator '[]' is not applicable to an Integer", "3.14[0]" => "Operator '[]' is not applicable to a Float", "/.*/[0]" => "Operator '[]' is not applicable to a Regexp", "[1][a]" => "The value 'a' cannot be converted to Numeric", "[1][0.0]" => "An Array[] cannot use Float where Integer is expected", "[1]['0.0']" => "An Array[] cannot use Float where Integer is expected", "[1,2][1, 0.0]" => "An Array[] cannot use Float where Integer is expected", "[1,2][1.0, -1]" => "An Array[] cannot use Float where Integer is expected", "[1,2][1, -1.0]" => "An Array[] cannot use Float where Integer is expected", }.each do |source, result| it "should parse and evaluate the expression '#{source}' to #{result}" do expect { parser.evaluate_string(scope, source, __FILE__)}.to raise_error(Regexp.new(Regexp.quote(result))) end end context "on catalog types" do it "[n] gets resource parameter [n]" do source = "notify { 'hello': message=>'yo'} Notify[hello][message]" parser.evaluate_string(scope, source, __FILE__).should == 'yo' end it "[n] gets class parameter [n]" do source = "class wonka($produces='chocolate'){ } include wonka Class[wonka][produces]" # This is more complicated since it needs to run like 3.x and do an import_ast adapted_parser = Puppet::Parser::E4ParserAdapter.new adapted_parser.file = __FILE__ ast = adapted_parser.parse(source) - scope.known_resource_types.import_ast(ast, '') - ast.code.safeevaluate(scope).should == 'chocolate' + Puppet.override({:global_scope => scope}, "test") do + scope.known_resource_types.import_ast(ast, '') + ast.code.safeevaluate(scope).should == 'chocolate' + end end # Resource default and override expressions and resource parameter access with [] { # Properties "notify { id: message=>explicit} Notify[id][message]" => "explicit", "Notify { message=>by_default} notify {foo:} Notify[foo][message]" => "by_default", "notify {foo:} Notify[foo]{message =>by_override} Notify[foo][message]" => "by_override", # Parameters "notify { id: withpath=>explicit} Notify[id][withpath]" => "explicit", "Notify { withpath=>by_default } notify { foo: } Notify[foo][withpath]" => "by_default", "notify {foo:} Notify[foo]{withpath=>by_override} Notify[foo][withpath]" => "by_override", # Metaparameters "notify { foo: tag => evoe} Notify[foo][tag]" => "evoe", # Does not produce the defaults for tag parameter (title, type or names of scopes) "notify { foo: } Notify[foo][tag]" => nil, # But a default may be specified on the type "Notify { tag=>by_default } notify { foo: } Notify[foo][tag]" => "by_default", "Notify { tag=>by_default } notify { foo: } Notify[foo]{ tag=>by_override } Notify[foo][tag]" => "by_override", }.each do |source, result| it "should parse and evaluate the expression '#{source}' to #{result}" do parser.evaluate_string(scope, source, __FILE__).should == result end end # Virtual and realized resource default and overridden resource parameter access with [] { # Properties "@notify { id: message=>explicit } Notify[id][message]" => "explicit", "@notify { id: message=>explicit } realize Notify[id] Notify[id][message]" => "explicit", "Notify { message=>by_default } @notify { id: } Notify[id][message]" => "by_default", "Notify { message=>by_default } @notify { id: tag=>thisone } Notify <| tag == thisone |>; Notify[id][message]" => "by_default", "@notify { id: } Notify[id]{message=>by_override} Notify[id][message]" => "by_override", # Parameters "@notify { id: withpath=>explicit } Notify[id][withpath]" => "explicit", "Notify { withpath=>by_default } @notify { id: } Notify[id][withpath]" => "by_default", "@notify { id: } realize Notify[id] Notify[id]{withpath=>by_override} Notify[id][withpath]" => "by_override", # Metaparameters "@notify { id: tag=>explicit } Notify[id][tag]" => "explicit", }.each do |source, result| it "parses and evaluates virtual and realized resources in the expression '#{source}' to #{result}" do expect(parser.evaluate_string(scope, source, __FILE__)).to eq(result) end end # Exported resource attributes { "@@notify { id: message=>explicit } Notify[id][message]" => "explicit", "@@notify { id: message=>explicit, tag=>thisone } Notify <<| tag == thisone |>> Notify[id][message]" => "explicit", }.each do |source, result| it "parses and evaluates exported resources in the expression '#{source}' to #{result}" do expect(parser.evaluate_string(scope, source, __FILE__)).to eq(result) end end # Resource default and override expressions and resource parameter access error conditions { "notify { xid: message=>explicit} Notify[id][message]" => /Resource not found/, "notify { id: message=>explicit} Notify[id][mustard]" => /does not have a parameter called 'mustard'/, # NOTE: these meta-esque parameters are not recognized as such "notify { id: message=>explicit} Notify[id][title]" => /does not have a parameter called 'title'/, "notify { id: message=>explicit} Notify[id]['type']" => /does not have a parameter called 'type'/, "notify { id: message=>explicit } Notify[id]{message=>override}" => /'message' is already set on Notify\[id\]/ }.each do |source, result| it "should parse '#{source}' and raise error matching #{result}" do expect { parser.evaluate_string(scope, source, __FILE__)}.to raise_error(result) end end context 'with errors' do { "Class['fail-whale']" => /Illegal name/, "Class[0]" => /An Integer cannot be used where a String is expected/, "Class[/.*/]" => /A Regexp cannot be used where a String is expected/, "Class[4.1415]" => /A Float cannot be used where a String is expected/, "Class[Integer]" => /An Integer-Type cannot be used where a String is expected/, "Class[File['tmp']]" => /A File\['tmp'\] Resource-Reference cannot be used where a String is expected/, }.each do | source, error_pattern| it "an error is flagged for '#{source}'" do expect { parser.evaluate_string(scope, source, __FILE__)}.to raise_error(error_pattern) end end end end # end [] operations end context "When the evaluator performs boolean operations" do { "true and true" => true, "false and true" => false, "true and false" => false, "false and false" => false, "true or true" => true, "false or true" => true, "true or false" => true, "false or false" => false, "! true" => false, "!! true" => true, "!! false" => false, "! 'x'" => false, "! ''" => false, "! undef" => true, "! [a]" => false, "! []" => false, "! {a=>1}" => false, "! {}" => false, "true and false and '0xwtf' + 1" => false, "false or true or '0xwtf' + 1" => true, }.each do |source, result| it "should parse and evaluate the expression '#{source}' to #{result}" do parser.evaluate_string(scope, source, __FILE__).should == result end end { "false || false || '0xwtf' + 1" => :error, }.each do |source, result| it "should parse and raise error for '#{source}'" do expect { parser.evaluate_string(scope, source, __FILE__) }.to raise_error(Puppet::ParseError) end end end context "When evaluator performs operations on literal undef" do it "computes non existing hash lookup as undef" do parser.evaluate_string(scope, "{a => 1}[b] == undef", __FILE__).should == true parser.evaluate_string(scope, "undef == {a => 1}[b]", __FILE__).should == true end end context "When evaluator performs calls" do around(:each) do |example| Puppet.override(:loaders => Puppet::Pops::Loaders.new(Puppet::Node::Environment.create(:testing, []))) do example.run end end let(:populate) do parser.evaluate_string(scope, "$a = 10 $b = [1,2,3]") end { 'sprintf( "x%iy", $a )' => "x10y", # unfolds 'sprintf( *["x%iy", $a] )' => "x10y", '"x%iy".sprintf( $a )' => "x10y", '$b.reduce |$memo,$x| { $memo + $x }' => 6, 'reduce($b) |$memo,$x| { $memo + $x }' => 6, }.each do |source, result| it "should parse and evaluate the expression '#{source}' to #{result}" do populate parser.evaluate_string(scope, source, __FILE__).should == result end end { '"value is ${a*2} yo"' => :error, }.each do |source, result| it "should parse and raise error for '#{source}'" do expect { parser.evaluate_string(scope, source, __FILE__) }.to raise_error(Puppet::ParseError) end end it "provides location information on error in unparenthesized call logic" do expect{parser.evaluate_string(scope, "include non_existing_class", __FILE__)}.to raise_error(Puppet::ParseError, /line 1\:1/) end it 'defaults can be given in a lambda and used only when arg is missing' do env_loader = Puppet.lookup(:loaders).public_environment_loader fc = Puppet::Functions.create_function(:test) do dispatch :test do param 'Integer', 'count' required_block_param end def test(count, block) block.call({}, *[].fill(10, 0, count)) end end the_func = fc.new({}, env_loader) env_loader.add_entry(:function, 'test', the_func, __FILE__) expect(parser.evaluate_string(scope, "test(1) |$x, $y=20| { $x + $y}")).to eql(30) expect(parser.evaluate_string(scope, "test(2) |$x, $y=20| { $x + $y}")).to eql(20) end it 'a given undef does not select the default value' do env_loader = Puppet.lookup(:loaders).public_environment_loader fc = Puppet::Functions.create_function(:test) do dispatch :test do - param 'Optional[Object]', 'lambda_arg' + param 'Object', 'lambda_arg' required_block_param end def test(lambda_arg, block) block.call({}, lambda_arg) end end the_func = fc.new({}, env_loader) env_loader.add_entry(:function, 'test', the_func, __FILE__) + expect(parser.evaluate_string(scope, "test(undef) |$x=20| { $x == undef}")).to eql(true) end end context "When evaluator performs string interpolation" do let(:populate) do parser.evaluate_string(scope, "$a = 10 $b = [1,2,3]") end { '"value is $a yo"' => "value is 10 yo", '"value is \$a yo"' => "value is $a yo", '"value is ${a} yo"' => "value is 10 yo", '"value is \${a} yo"' => "value is ${a} yo", '"value is ${$a} yo"' => "value is 10 yo", '"value is ${$a*2} yo"' => "value is 20 yo", '"value is ${sprintf("x%iy",$a)} yo"' => "value is x10y yo", '"value is ${"x%iy".sprintf($a)} yo"' => "value is x10y yo", '"value is ${[1,2,3]} yo"' => "value is [1, 2, 3] yo", '"value is ${/.*/} yo"' => "value is /.*/ yo", '$x = undef "value is $x yo"' => "value is yo", '$x = default "value is $x yo"' => "value is default yo", '$x = Array[Integer] "value is $x yo"' => "value is Array[Integer] yo", '"value is ${Array[Integer]} yo"' => "value is Array[Integer] yo", }.each do |source, result| it "should parse and evaluate the expression '#{source}' to #{result}" do populate parser.evaluate_string(scope, source, __FILE__).should == result end end it "should parse and evaluate an interpolation of a hash" do source = '"value is ${{a=>1,b=>2}} yo"' # This test requires testing against two options because a hash to string # produces a result that is unordered hashstr = {'a' => 1, 'b' => 2}.to_s alt_results = ["value is {a => 1, b => 2} yo", "value is {b => 2, a => 1} yo" ] populate parse_result = parser.evaluate_string(scope, source, __FILE__) alt_results.include?(parse_result).should == true end it 'should accept a variable with leading underscore when used directly' do source = '$_x = 10; "$_x"' expect(parser.evaluate_string(scope, source, __FILE__)).to eql('10') end it 'should accept a variable with leading underscore when used as an expression' do source = '$_x = 10; "${_x}"' expect(parser.evaluate_string(scope, source, __FILE__)).to eql('10') end { '"value is ${a*2} yo"' => :error, }.each do |source, result| it "should parse and raise error for '#{source}'" do expect { parser.evaluate_string(scope, source, __FILE__) }.to raise_error(Puppet::ParseError) end end end context "When evaluating variables" do context "that are non existing an error is raised for" do it "unqualified variable" do expect { parser.evaluate_string(scope, "$quantum_gravity", __FILE__) }.to raise_error(/Unknown variable/) end it "qualified variable" do expect { parser.evaluate_string(scope, "$quantum_gravity::graviton", __FILE__) }.to raise_error(/Unknown variable/) end end it "a lex error should be raised for '$foo::::bar'" do expect { parser.evaluate_string(scope, "$foo::::bar") }.to raise_error(Puppet::LexError, /Illegal fully qualified name at line 1:7/) end { '$a = $0' => nil, '$a = $1' => nil, }.each do |source, value| it "it is ok to reference numeric unassigned variables '#{source}'" do parser.evaluate_string(scope, source, __FILE__).should == value end end { '$00 = 0' => /must be a decimal value/, '$0xf = 0' => /must be a decimal value/, '$0777 = 0' => /must be a decimal value/, '$123a = 0' => /must be a decimal value/, }.each do |source, error_pattern| it "should raise an error for '#{source}'" do expect { parser.evaluate_string(scope, source, __FILE__) }.to raise_error(error_pattern) end end context "an initial underscore in the last segment of a var name is allowed" do { '$_a = 1' => 1, '$__a = 1' => 1, }.each do |source, value| it "as in this example '#{source}'" do parser.evaluate_string(scope, source, __FILE__).should == value end end end end context "When evaluating relationships" do it 'should form a relation with File[a] -> File[b]' do source = "File[a] -> File[b]" parser.evaluate_string(scope, source, __FILE__) scope.compiler.should have_relationship(['File', 'a', '->', 'File', 'b']) end it 'should form a relation with resource -> resource' do source = "notify{a:} -> notify{b:}" parser.evaluate_string(scope, source, __FILE__) scope.compiler.should have_relationship(['Notify', 'a', '->', 'Notify', 'b']) end it 'should form a relation with [File[a], File[b]] -> [File[x], File[y]]' do source = "[File[a], File[b]] -> [File[x], File[y]]" parser.evaluate_string(scope, source, __FILE__) scope.compiler.should have_relationship(['File', 'a', '->', 'File', 'x']) scope.compiler.should have_relationship(['File', 'b', '->', 'File', 'x']) scope.compiler.should have_relationship(['File', 'a', '->', 'File', 'y']) scope.compiler.should have_relationship(['File', 'b', '->', 'File', 'y']) end it 'should tolerate (eliminate) duplicates in operands' do source = "[File[a], File[a]] -> File[x]" parser.evaluate_string(scope, source, __FILE__) scope.compiler.should have_relationship(['File', 'a', '->', 'File', 'x']) scope.compiler.relationships.size.should == 1 end it 'should form a relation with <-' do source = "File[a] <- File[b]" parser.evaluate_string(scope, source, __FILE__) scope.compiler.should have_relationship(['File', 'b', '->', 'File', 'a']) end it 'should form a relation with <-' do source = "File[a] <~ File[b]" parser.evaluate_string(scope, source, __FILE__) scope.compiler.should have_relationship(['File', 'b', '~>', 'File', 'a']) end end context "When evaluating heredoc" do it "evaluates plain heredoc" do src = "@(END)\nThis is\nheredoc text\nEND\n" parser.evaluate_string(scope, src).should == "This is\nheredoc text\n" end it "parses heredoc with margin" do src = [ "@(END)", " This is", " heredoc text", " | END", "" ].join("\n") parser.evaluate_string(scope, src).should == "This is\nheredoc text\n" end it "parses heredoc with margin and right newline trim" do src = [ "@(END)", " This is", " heredoc text", " |- END", "" ].join("\n") parser.evaluate_string(scope, src).should == "This is\nheredoc text" end it "parses escape specification" do src = <<-CODE @(END/t) Tex\\tt\\n |- END CODE parser.evaluate_string(scope, src).should == "Tex\tt\\n" end it "parses syntax checked specification" do src = <<-CODE @(END:json) ["foo", "bar"] |- END CODE parser.evaluate_string(scope, src).should == '["foo", "bar"]' end it "parses syntax checked specification with error and reports it" do src = <<-CODE @(END:json) ['foo', "bar"] |- END CODE expect { parser.evaluate_string(scope, src)}.to raise_error(/Cannot parse invalid JSON string/) end it "parses interpolated heredoc expression" do src = <<-CODE $name = 'Fjodor' @("END") Hello $name |- END CODE parser.evaluate_string(scope, src).should == "Hello Fjodor" end end context "Handles Deprecations and Discontinuations" do around(:each) do |example| Puppet.override({:loaders => Puppet::Pops::Loaders.new(Puppet::Node::Environment.create(:testing, []))}, 'test') do example.run end end it 'of import statements' do source = "\nimport foo" # Error references position 5 at the opening '{' # Set file to nil to make it easier to match with line number (no file name in output) expect { parser.evaluate_string(scope, source) }.to raise_error(/'import' has been discontinued.*line 2:1/) end end context "Detailed Error messages are reported" do it 'for illegal type references' do source = '1+1 { "title": }' # Error references position 5 at the opening '{' # Set file to nil to make it easier to match with line number (no file name in output) expect { parser.parse_string(source, nil) }.to raise_error(/Expression is not valid as a resource.*line 1:5/) end it 'for non r-value producing <| |>' do expect { parser.parse_string("$a = File <| |>", nil) }.to raise_error(/A Virtual Query does not produce a value at line 1:6/) end it 'for non r-value producing <<| |>>' do expect { parser.parse_string("$a = File <<| |>>", nil) }.to raise_error(/An Exported Query does not produce a value at line 1:6/) end it 'for non r-value producing define' do Puppet.expects(:err).with("Invalid use of expression. A 'define' expression does not produce a value at line 1:6") Puppet.expects(:err).with("Classes, definitions, and nodes may only appear at toplevel or inside other classes at line 1:6") expect { parser.parse_string("$a = define foo { }", nil) }.to raise_error(/2 errors/) end it 'for non r-value producing class' do Puppet.expects(:err).with("Invalid use of expression. A Host Class Definition does not produce a value at line 1:6") Puppet.expects(:err).with("Classes, definitions, and nodes may only appear at toplevel or inside other classes at line 1:6") expect { parser.parse_string("$a = class foo { }", nil) }.to raise_error(/2 errors/) end it 'for unclosed quote with indication of start position of string' do source = <<-SOURCE.gsub(/^ {6}/,'') $a = "xx yyy SOURCE # first char after opening " reported as being in error. expect { parser.parse_string(source) }.to raise_error(/Unclosed quote after '"' followed by 'xx\\nyy\.\.\.' at line 1:7/) end it 'for multiple errors with a summary exception' do Puppet.expects(:err).with("Invalid use of expression. A Node Definition does not produce a value at line 1:6") Puppet.expects(:err).with("Classes, definitions, and nodes may only appear at toplevel or inside other classes at line 1:6") expect { parser.parse_string("$a = node x { }",nil) }.to raise_error(/2 errors/) end it 'for a bad hostname' do expect { parser.parse_string("node 'macbook+owned+by+name' { }", nil) }.to raise_error(/The hostname 'macbook\+owned\+by\+name' contains illegal characters.*at line 1:6/) end it 'for a hostname with interpolation' do source = <<-SOURCE.gsub(/^ {6}/,'') $name = 'fred' node "macbook-owned-by$name" { } SOURCE expect { parser.parse_string(source, nil) }.to raise_error(/An interpolated expression is not allowed in a hostname of a node at line 2:23/) end end matcher :have_relationship do |expected| calc = Puppet::Pops::Types::TypeCalculator.new match do |compiler| op_name = {'->' => :relationship, '~>' => :subscription} compiler.relationships.any? do | relation | relation.source.type == expected[0] && relation.source.title == expected[1] && relation.type == op_name[expected[2]] && relation.target.type == expected[3] && relation.target.title == expected[4] end end failure_message_for_should do |actual| "Relationship #{expected[0]}[#{expected[1]}] #{expected[2]} #{expected[3]}[#{expected[4]}] but was unknown to compiler" end end end diff --git a/spec/unit/pops/parser/parsing_typed_parameters_spec.rb b/spec/unit/pops/parser/parsing_typed_parameters_spec.rb new file mode 100644 index 000000000..88a6c302d --- /dev/null +++ b/spec/unit/pops/parser/parsing_typed_parameters_spec.rb @@ -0,0 +1,73 @@ +require 'spec_helper' + +require 'puppet/pops' +require 'puppet/pops/evaluator/evaluator_impl' +require 'puppet_spec/pops' +require 'puppet_spec/scope' +require 'puppet/parser/e4_parser_adapter' + + +# relative to this spec file (./) does not work as this file is loaded by rspec +#require File.join(File.dirname(__FILE__), '/evaluator_rspec_helper') + +describe 'Puppet::Pops::Evaluator::EvaluatorImpl' do + include PuppetSpec::Pops + include PuppetSpec::Scope + before(:each) do + + # These must be set since the is 3x logic that triggers on these even if the tests are explicit + # about selection of parser and evaluator + # + Puppet[:parser] = 'future' + Puppet[:evaluator] = 'future' + end + + let(:parser) { Puppet::Pops::Parser::EvaluatingParser::Transitional.new } + + context "captures-rest parameter" do + it 'is allowed in lambda when placed last' do + source = <<-CODE + foo() |$a, *$b| { $a + $b[0] } + CODE + expect do + parser.parse_string(source, __FILE__) + end.to_not raise_error() + end + + it 'allows a type annotation' do + source = <<-CODE + foo() |$a, Integer *$b| { $a + $b[0] } + CODE + expect do + parser.parse_string(source, __FILE__) + end.to_not raise_error() + end + + it 'is not allowed in lambda except last' do + source = <<-CODE + foo() |*$a, $b| { $a + $b[0] } + CODE + expect do + parser.parse_string(source, __FILE__) + end.to raise_error(Puppet::ParseError, /Parameter \$a is not last, and has 'captures rest'/) + end + + it 'is not allowed in define' do + source = <<-CODE + define foo(*$a) { } + CODE + expect do + parser.parse_string(source, __FILE__) + end.to raise_error(Puppet::ParseError, /Parameter \$a has 'captures rest' - not supported in a 'define'/) + end + + it 'is not allowed in class' do + source = <<-CODE + class foo(*$a) { } + CODE + expect do + parser.parse_string(source, __FILE__) + end.to raise_error(Puppet::ParseError, /Parameter \$a has 'captures rest' - not supported in a Host Class Definition/) + end + end +end diff --git a/spec/unit/pops/types/type_calculator_spec.rb b/spec/unit/pops/types/type_calculator_spec.rb index 8388d1855..f2e7e6800 100644 --- a/spec/unit/pops/types/type_calculator_spec.rb +++ b/spec/unit/pops/types/type_calculator_spec.rb @@ -1,1604 +1,1630 @@ require 'spec_helper' require 'puppet/pops' describe 'The type calculator' do let(:calculator) { Puppet::Pops::Types::TypeCalculator.new() } def range_t(from, to) t = Puppet::Pops::Types::PIntegerType.new t.from = from t.to = to t end def pattern_t(*patterns) Puppet::Pops::Types::TypeFactory.pattern(*patterns) end def regexp_t(pattern) Puppet::Pops::Types::TypeFactory.regexp(pattern) end def string_t(*strings) Puppet::Pops::Types::TypeFactory.string(*strings) end def callable_t(*params) Puppet::Pops::Types::TypeFactory.callable(*params) end def all_callables_t(*params) Puppet::Pops::Types::TypeFactory.all_callables() end def with_block_t(callable_t, *params) Puppet::Pops::Types::TypeFactory.with_block(callable_t, *params) end def with_optional_block_t(callable_t, *params) Puppet::Pops::Types::TypeFactory.with_optional_block(callable_t, *params) end def enum_t(*strings) Puppet::Pops::Types::TypeFactory.enum(*strings) end def variant_t(*types) Puppet::Pops::Types::TypeFactory.variant(*types) end def integer_t() Puppet::Pops::Types::TypeFactory.integer() end def array_t(t) Puppet::Pops::Types::TypeFactory.array_of(t) end def hash_t(k,v) Puppet::Pops::Types::TypeFactory.hash_of(v, k) end def data_t() Puppet::Pops::Types::TypeFactory.data() end def factory() Puppet::Pops::Types::TypeFactory end def collection_t() Puppet::Pops::Types::TypeFactory.collection() end def tuple_t(*types) Puppet::Pops::Types::TypeFactory.tuple(*types) end def struct_t(type_hash) Puppet::Pops::Types::TypeFactory.struct(type_hash) end - def optional_object_t - Puppet::Pops::Types::TypeFactory.optional_object() + def object_t + Puppet::Pops::Types::TypeFactory.object() end def types Puppet::Pops::Types end shared_context "types_setup" do def all_types [ Puppet::Pops::Types::PObjectType, Puppet::Pops::Types::PNilType, Puppet::Pops::Types::PDataType, Puppet::Pops::Types::PScalarType, Puppet::Pops::Types::PStringType, Puppet::Pops::Types::PNumericType, Puppet::Pops::Types::PIntegerType, Puppet::Pops::Types::PFloatType, Puppet::Pops::Types::PRegexpType, Puppet::Pops::Types::PBooleanType, Puppet::Pops::Types::PCollectionType, Puppet::Pops::Types::PArrayType, Puppet::Pops::Types::PHashType, Puppet::Pops::Types::PRubyType, Puppet::Pops::Types::PHostClassType, Puppet::Pops::Types::PResourceType, Puppet::Pops::Types::PPatternType, Puppet::Pops::Types::PEnumType, Puppet::Pops::Types::PVariantType, Puppet::Pops::Types::PStructType, Puppet::Pops::Types::PTupleType, Puppet::Pops::Types::PCallableType, Puppet::Pops::Types::PType, ] end def scalar_types # PVariantType is also scalar, if its types are all Scalar [ Puppet::Pops::Types::PScalarType, Puppet::Pops::Types::PStringType, Puppet::Pops::Types::PNumericType, Puppet::Pops::Types::PIntegerType, Puppet::Pops::Types::PFloatType, Puppet::Pops::Types::PRegexpType, Puppet::Pops::Types::PBooleanType, Puppet::Pops::Types::PPatternType, Puppet::Pops::Types::PEnumType, ] end def numeric_types # PVariantType is also numeric, if its types are all numeric [ Puppet::Pops::Types::PNumericType, Puppet::Pops::Types::PIntegerType, Puppet::Pops::Types::PFloatType, ] end def string_types # PVariantType is also string type, if its types are all compatible [ Puppet::Pops::Types::PStringType, Puppet::Pops::Types::PPatternType, Puppet::Pops::Types::PEnumType, ] end def collection_types # PVariantType is also string type, if its types are all compatible [ Puppet::Pops::Types::PCollectionType, Puppet::Pops::Types::PHashType, Puppet::Pops::Types::PArrayType, Puppet::Pops::Types::PStructType, Puppet::Pops::Types::PTupleType, ] end def data_compatible_types result = scalar_types result << Puppet::Pops::Types::PDataType result << array_t(types::PDataType.new) result << types::TypeFactory.hash_of_data result << Puppet::Pops::Types::PNilType tmp = tuple_t(types::PDataType.new) result << (tmp) tmp.size_type = range_t(0, nil) result end def type_from_class(c) c.is_a?(Class) ? c.new : c end end context 'when inferring ruby' do it 'fixnum translates to PIntegerType' do calculator.infer(1).class.should == Puppet::Pops::Types::PIntegerType end it 'large fixnum (or bignum depending on architecture) translates to PIntegerType' do calculator.infer(2**33).class.should == Puppet::Pops::Types::PIntegerType end it 'float translates to PFloatType' do calculator.infer(1.3).class.should == Puppet::Pops::Types::PFloatType end it 'string translates to PStringType' do calculator.infer('foo').class.should == Puppet::Pops::Types::PStringType end it 'inferred string type knows the string value' do t = calculator.infer('foo') t.class.should == Puppet::Pops::Types::PStringType t.values.should == ['foo'] end it 'boolean true translates to PBooleanType' do calculator.infer(true).class.should == Puppet::Pops::Types::PBooleanType end it 'boolean false translates to PBooleanType' do calculator.infer(false).class.should == Puppet::Pops::Types::PBooleanType end it 'regexp translates to PRegexpType' do calculator.infer(/^a regular expression$/).class.should == Puppet::Pops::Types::PRegexpType end it 'nil translates to PNilType' do calculator.infer(nil).class.should == Puppet::Pops::Types::PNilType end it ':undef translates to PNilType' do calculator.infer(:undef).class.should == Puppet::Pops::Types::PNilType end it 'an instance of class Foo translates to PRubyType[Foo]' do class Foo end t = calculator.infer(Foo.new) t.class.should == Puppet::Pops::Types::PRubyType t.ruby_class.should == 'Foo' end context 'array' do it 'translates to PArrayType' do calculator.infer([1,2]).class.should == Puppet::Pops::Types::PArrayType end it 'with fixnum values translates to PArrayType[PIntegerType]' do calculator.infer([1,2]).element_type.class.should == Puppet::Pops::Types::PIntegerType end it 'with 32 and 64 bit integer values translates to PArrayType[PIntegerType]' do calculator.infer([1,2**33]).element_type.class.should == Puppet::Pops::Types::PIntegerType end it 'Range of integer values are computed' do t = calculator.infer([-3,0,42]).element_type t.class.should == Puppet::Pops::Types::PIntegerType t.from.should == -3 t.to.should == 42 end it "Compound string values are computed" do t = calculator.infer(['a','b', 'c']).element_type t.class.should == Puppet::Pops::Types::PStringType t.values.should == ['a', 'b', 'c'] end it 'with fixnum and float values translates to PArrayType[PNumericType]' do calculator.infer([1,2.0]).element_type.class.should == Puppet::Pops::Types::PNumericType end it 'with fixnum and string values translates to PArrayType[PScalarType]' do calculator.infer([1,'two']).element_type.class.should == Puppet::Pops::Types::PScalarType end it 'with float and string values translates to PArrayType[PScalarType]' do calculator.infer([1.0,'two']).element_type.class.should == Puppet::Pops::Types::PScalarType end it 'with fixnum, float, and string values translates to PArrayType[PScalarType]' do calculator.infer([1, 2.0,'two']).element_type.class.should == Puppet::Pops::Types::PScalarType end it 'with fixnum and regexp values translates to PArrayType[PScalarType]' do calculator.infer([1, /two/]).element_type.class.should == Puppet::Pops::Types::PScalarType end it 'with string and regexp values translates to PArrayType[PScalarType]' do calculator.infer(['one', /two/]).element_type.class.should == Puppet::Pops::Types::PScalarType end it 'with string and symbol values translates to PArrayType[PObjectType]' do calculator.infer(['one', :two]).element_type.class.should == Puppet::Pops::Types::PObjectType end it 'with fixnum and nil values translates to PArrayType[PIntegerType]' do calculator.infer([1, nil]).element_type.class.should == Puppet::Pops::Types::PIntegerType end it 'with arrays of string values translates to PArrayType[PArrayType[PStringType]]' do et = calculator.infer([['first' 'array'], ['second','array']]) et.class.should == Puppet::Pops::Types::PArrayType et = et.element_type et.class.should == Puppet::Pops::Types::PArrayType et = et.element_type et.class.should == Puppet::Pops::Types::PStringType end it 'with array of string values and array of fixnums translates to PArrayType[PArrayType[PScalarType]]' do et = calculator.infer([['first' 'array'], [1,2]]) et.class.should == Puppet::Pops::Types::PArrayType et = et.element_type et.class.should == Puppet::Pops::Types::PArrayType et = et.element_type et.class.should == Puppet::Pops::Types::PScalarType end it 'with hashes of string values translates to PArrayType[PHashType[PStringType]]' do et = calculator.infer([{:first => 'first', :second => 'second' }, {:first => 'first', :second => 'second' }]) et.class.should == Puppet::Pops::Types::PArrayType et = et.element_type et.class.should == Puppet::Pops::Types::PHashType et = et.element_type et.class.should == Puppet::Pops::Types::PStringType end it 'with hash of string values and hash of fixnums translates to PArrayType[PHashType[PScalarType]]' do et = calculator.infer([{:first => 'first', :second => 'second' }, {:first => 1, :second => 2 }]) et.class.should == Puppet::Pops::Types::PArrayType et = et.element_type et.class.should == Puppet::Pops::Types::PHashType et = et.element_type et.class.should == Puppet::Pops::Types::PScalarType end end context 'hash' do it 'translates to PHashType' do calculator.infer({:first => 1, :second => 2}).class.should == Puppet::Pops::Types::PHashType end it 'with symbolic keys translates to PHashType[PRubyType[Symbol],value]' do k = calculator.infer({:first => 1, :second => 2}).key_type k.class.should == Puppet::Pops::Types::PRubyType k.ruby_class.should == 'Symbol' end it 'with string keys translates to PHashType[PStringType,value]' do calculator.infer({'first' => 1, 'second' => 2}).key_type.class.should == Puppet::Pops::Types::PStringType end it 'with fixnum values translates to PHashType[key,PIntegerType]' do calculator.infer({:first => 1, :second => 2}).element_type.class.should == Puppet::Pops::Types::PIntegerType end end end context 'patterns' do it "constructs a PPatternType" do t = pattern_t('a(b)c') t.class.should == Puppet::Pops::Types::PPatternType t.patterns.size.should == 1 t.patterns[0].class.should == Puppet::Pops::Types::PRegexpType t.patterns[0].pattern.should == 'a(b)c' t.patterns[0].regexp.match('abc')[1].should == 'b' end it "constructs a PStringType with multiple strings" do t = string_t('a', 'b', 'c', 'abc') t.values.should == ['a', 'b', 'c', 'abc'] end end # Deal with cases not covered by computing common type context 'when computing common type' do it 'computes given resource type commonality' do r1 = Puppet::Pops::Types::PResourceType.new() r1.type_name = 'File' r2 = Puppet::Pops::Types::PResourceType.new() r2.type_name = 'File' calculator.string(calculator.common_type(r1, r2)).should == "File" r2 = Puppet::Pops::Types::PResourceType.new() r2.type_name = 'File' r2.title = '/tmp/foo' calculator.string(calculator.common_type(r1, r2)).should == "File" r1 = Puppet::Pops::Types::PResourceType.new() r1.type_name = 'File' r1.title = '/tmp/foo' calculator.string(calculator.common_type(r1, r2)).should == "File['/tmp/foo']" r1 = Puppet::Pops::Types::PResourceType.new() r1.type_name = 'File' r1.title = '/tmp/bar' calculator.string(calculator.common_type(r1, r2)).should == "File" r2 = Puppet::Pops::Types::PResourceType.new() r2.type_name = 'Package' r2.title = 'apache' calculator.string(calculator.common_type(r1, r2)).should == "Resource" end it 'computes given hostclass type commonality' do r1 = Puppet::Pops::Types::PHostClassType.new() r1.class_name = 'foo' r2 = Puppet::Pops::Types::PHostClassType.new() r2.class_name = 'foo' calculator.string(calculator.common_type(r1, r2)).should == "Class[foo]" r2 = Puppet::Pops::Types::PHostClassType.new() r2.class_name = 'bar' calculator.string(calculator.common_type(r1, r2)).should == "Class" r2 = Puppet::Pops::Types::PHostClassType.new() calculator.string(calculator.common_type(r1, r2)).should == "Class" r1 = Puppet::Pops::Types::PHostClassType.new() calculator.string(calculator.common_type(r1, r2)).should == "Class" end it 'computes pattern commonality' do t1 = pattern_t('abc') t2 = pattern_t('xyz') common_t = calculator.common_type(t1,t2) common_t.class.should == Puppet::Pops::Types::PPatternType common_t.patterns.map { |pr| pr.pattern }.should == ['abc', 'xyz'] calculator.string(common_t).should == "Pattern[/abc/, /xyz/]" end it 'computes enum commonality to value set sum' do t1 = enum_t('a', 'b', 'c') t2 = enum_t('x', 'y', 'z') common_t = calculator.common_type(t1, t2) common_t.should == enum_t('a', 'b', 'c', 'x', 'y', 'z') end it 'computed variant commonality to type union where added types are not sub-types' do a_t1 = integer_t() a_t2 = enum_t('b') v_a = variant_t(a_t1, a_t2) b_t1 = enum_t('a') v_b = variant_t(b_t1) common_t = calculator.common_type(v_a, v_b) common_t.class.should == Puppet::Pops::Types::PVariantType Set.new(common_t.types).should == Set.new([a_t1, a_t2, b_t1]) end it 'computed variant commonality to type union where added types are sub-types' do a_t1 = integer_t() a_t2 = string_t() v_a = variant_t(a_t1, a_t2) b_t1 = enum_t('a') v_b = variant_t(b_t1) common_t = calculator.common_type(v_a, v_b) common_t.class.should == Puppet::Pops::Types::PVariantType Set.new(common_t.types).should == Set.new([a_t1, a_t2]) end context "of callables" do it 'incompatible instances => generic callable' do t1 = callable_t(String) t2 = callable_t(Integer) common_t = calculator.common_type(t1, t2) expect(common_t.class).to be(Puppet::Pops::Types::PCallableType) expect(common_t.param_types).to be_nil expect(common_t.block_type).to be_nil end it 'compatible instances => the least specific' do t1 = callable_t(String) scalar_t = Puppet::Pops::Types::PScalarType.new t2 = callable_t(scalar_t) common_t = calculator.common_type(t1, t2) expect(common_t.class).to be(Puppet::Pops::Types::PCallableType) expect(common_t.param_types.class).to be(Puppet::Pops::Types::PTupleType) expect(common_t.param_types.types).to eql([scalar_t]) expect(common_t.block_type).to be_nil end it 'block_type is included in the check (incompatible block)' do t1 = with_block_t(callable_t(String), String) t2 = with_block_t(callable_t(String), Integer) common_t = calculator.common_type(t1, t2) expect(common_t.class).to be(Puppet::Pops::Types::PCallableType) expect(common_t.param_types).to be_nil expect(common_t.block_type).to be_nil end it 'block_type is included in the check (compatible block)' do t1 = with_block_t(callable_t(String), String) scalar_t = Puppet::Pops::Types::PScalarType.new t2 = with_block_t(callable_t(String), scalar_t) common_t = calculator.common_type(t1, t2) expect(common_t.param_types.class).to be(Puppet::Pops::Types::PTupleType) expect(common_t.block_type).to eql(callable_t(scalar_t)) end end end context 'computes assignability' do include_context "types_setup" context "for Object, such that" do it 'all types are assignable to Object' do t = Puppet::Pops::Types::PObjectType.new() all_types.each { |t2| t2.new.should be_assignable_to(t) } end it 'Object is not assignable to anything but Object' do tested_types = all_types() - [Puppet::Pops::Types::PObjectType] t = Puppet::Pops::Types::PObjectType.new() tested_types.each { |t2| t.should_not be_assignable_to(t2.new) } end end context "for Data, such that" do it 'all scalars + array and hash are assignable to Data' do t = Puppet::Pops::Types::PDataType.new() data_compatible_types.each { |t2| type_from_class(t2).should be_assignable_to(t) } end it 'a Variant of scalar, hash, or array is assignable to Data' do t = Puppet::Pops::Types::PDataType.new() data_compatible_types.each { |t2| variant_t(type_from_class(t2)).should be_assignable_to(t) } end it 'Data is not assignable to any of its subtypes' do t = Puppet::Pops::Types::PDataType.new() types_to_test = data_compatible_types- [Puppet::Pops::Types::PDataType] types_to_test.each {|t2| t.should_not be_assignable_to(type_from_class(t2)) } end it 'Data is not assignable to a Variant of Data subtype' do t = Puppet::Pops::Types::PDataType.new() types_to_test = data_compatible_types- [Puppet::Pops::Types::PDataType] types_to_test.each { |t2| t.should_not be_assignable_to(variant_t(type_from_class(t2))) } end it 'Data is not assignable to any disjunct type' do tested_types = all_types - [Puppet::Pops::Types::PObjectType, Puppet::Pops::Types::PDataType] - scalar_types t = Puppet::Pops::Types::PDataType.new() tested_types.each {|t2| t.should_not be_assignable_to(t2.new) } end end context "for Scalar, such that" do it "all scalars are assignable to Scalar" do t = Puppet::Pops::Types::PScalarType.new() scalar_types.each {|t2| t2.new.should be_assignable_to(t) } end it 'Scalar is not assignable to any of its subtypes' do t = Puppet::Pops::Types::PScalarType.new() types_to_test = scalar_types - [Puppet::Pops::Types::PScalarType] types_to_test.each {|t2| t.should_not be_assignable_to(t2.new) } end it 'Scalar is not assignable to any disjunct type' do tested_types = all_types - [Puppet::Pops::Types::PObjectType, Puppet::Pops::Types::PDataType] - scalar_types t = Puppet::Pops::Types::PScalarType.new() tested_types.each {|t2| t.should_not be_assignable_to(t2.new) } end end context "for Numeric, such that" do it "all numerics are assignable to Numeric" do t = Puppet::Pops::Types::PNumericType.new() numeric_types.each {|t2| t2.new.should be_assignable_to(t) } end it 'Numeric is not assignable to any of its subtypes' do t = Puppet::Pops::Types::PNumericType.new() types_to_test = numeric_types - [Puppet::Pops::Types::PNumericType] types_to_test.each {|t2| t.should_not be_assignable_to(t2.new) } end it 'Numeric is not assignable to any disjunct type' do tested_types = all_types - [ Puppet::Pops::Types::PObjectType, Puppet::Pops::Types::PDataType, Puppet::Pops::Types::PScalarType, ] - numeric_types t = Puppet::Pops::Types::PNumericType.new() tested_types.each {|t2| t.should_not be_assignable_to(t2.new) } end end context "for Collection, such that" do it "all collections are assignable to Collection" do t = Puppet::Pops::Types::PCollectionType.new() collection_types.each {|t2| t2.new.should be_assignable_to(t) } end it 'Collection is not assignable to any of its subtypes' do t = Puppet::Pops::Types::PCollectionType.new() types_to_test = collection_types - [Puppet::Pops::Types::PCollectionType] types_to_test.each {|t2| t.should_not be_assignable_to(t2.new) } end it 'Collection is not assignable to any disjunct type' do tested_types = all_types - [Puppet::Pops::Types::PObjectType] - collection_types t = Puppet::Pops::Types::PCollectionType.new() tested_types.each {|t2| t.should_not be_assignable_to(t2.new) } end end context "for Array, such that" do it "Array is not assignable to non Array based Collection type" do t = Puppet::Pops::Types::PArrayType.new() tested_types = collection_types - [ Puppet::Pops::Types::PCollectionType, Puppet::Pops::Types::PArrayType, Puppet::Pops::Types::PTupleType] tested_types.each {|t2| t.should_not be_assignable_to(t2.new) } end it 'Array is not assignable to any disjunct type' do tested_types = all_types - [ Puppet::Pops::Types::PObjectType, Puppet::Pops::Types::PDataType] - collection_types t = Puppet::Pops::Types::PArrayType.new() tested_types.each {|t2| t.should_not be_assignable_to(t2.new) } end end context "for Hash, such that" do it "Hash is not assignable to any other Collection type" do t = Puppet::Pops::Types::PHashType.new() tested_types = collection_types - [ Puppet::Pops::Types::PCollectionType, Puppet::Pops::Types::PStructType, Puppet::Pops::Types::PHashType] tested_types.each {|t2| t.should_not be_assignable_to(t2.new) } end it 'Hash is not assignable to any disjunct type' do tested_types = all_types - [ Puppet::Pops::Types::PObjectType, Puppet::Pops::Types::PDataType] - collection_types t = Puppet::Pops::Types::PHashType.new() tested_types.each {|t2| t.should_not be_assignable_to(t2.new) } end end context "for Tuple, such that" do it "Tuple is not assignable to any other non Array based Collection type" do t = Puppet::Pops::Types::PTupleType.new() tested_types = collection_types - [ Puppet::Pops::Types::PCollectionType, Puppet::Pops::Types::PTupleType, Puppet::Pops::Types::PArrayType] tested_types.each {|t2| t.should_not be_assignable_to(t2.new) } end it 'Tuple is not assignable to any disjunct type' do tested_types = all_types - [ Puppet::Pops::Types::PObjectType, Puppet::Pops::Types::PDataType] - collection_types t = Puppet::Pops::Types::PTupleType.new() tested_types.each {|t2| t.should_not be_assignable_to(t2.new) } end end context "for Struct, such that" do it "Struct is not assignable to any other non Hashed based Collection type" do t = Puppet::Pops::Types::PStructType.new() tested_types = collection_types - [ Puppet::Pops::Types::PCollectionType, Puppet::Pops::Types::PStructType, Puppet::Pops::Types::PHashType] tested_types.each {|t2| t.should_not be_assignable_to(t2.new) } end it 'Struct is not assignable to any disjunct type' do tested_types = all_types - [ Puppet::Pops::Types::PObjectType, Puppet::Pops::Types::PDataType] - collection_types t = Puppet::Pops::Types::PStructType.new() tested_types.each {|t2| t.should_not be_assignable_to(t2.new) } end end context "for Callable, such that" do it "Callable is not assignable to any disjunct type" do t = Puppet::Pops::Types::PCallableType.new() tested_types = all_types - [ Puppet::Pops::Types::PCallableType, Puppet::Pops::Types::PObjectType] tested_types.each {|t2| t.should_not be_assignable_to(t2.new) } end end it 'should recognize mapped ruby types' do { Integer => Puppet::Pops::Types::PIntegerType.new, Fixnum => Puppet::Pops::Types::PIntegerType.new, Bignum => Puppet::Pops::Types::PIntegerType.new, Float => Puppet::Pops::Types::PFloatType.new, Numeric => Puppet::Pops::Types::PNumericType.new, NilClass => Puppet::Pops::Types::PNilType.new, TrueClass => Puppet::Pops::Types::PBooleanType.new, FalseClass => Puppet::Pops::Types::PBooleanType.new, String => Puppet::Pops::Types::PStringType.new, Regexp => Puppet::Pops::Types::PRegexpType.new, Regexp => Puppet::Pops::Types::PRegexpType.new, Array => Puppet::Pops::Types::TypeFactory.array_of_data(), Hash => Puppet::Pops::Types::TypeFactory.hash_of_data() }.each do |ruby_type, puppet_type | ruby_type.should be_assignable_to(puppet_type) end end context 'when dealing with integer ranges' do it 'should accept an equal range' do calculator.assignable?(range_t(2,5), range_t(2,5)).should == true end it 'should accept an equal reverse range' do calculator.assignable?(range_t(2,5), range_t(5,2)).should == true end it 'should accept a narrower range' do calculator.assignable?(range_t(2,10), range_t(3,5)).should == true end it 'should accept a narrower reverse range' do calculator.assignable?(range_t(2,10), range_t(5,3)).should == true end it 'should reject a wider range' do calculator.assignable?(range_t(3,5), range_t(2,10)).should == false end it 'should reject a wider reverse range' do calculator.assignable?(range_t(3,5), range_t(10,2)).should == false end it 'should reject a partially overlapping range' do calculator.assignable?(range_t(3,5), range_t(2,4)).should == false calculator.assignable?(range_t(3,5), range_t(4,6)).should == false end it 'should reject a partially overlapping reverse range' do calculator.assignable?(range_t(3,5), range_t(4,2)).should == false calculator.assignable?(range_t(3,5), range_t(6,4)).should == false end end context 'when dealing with patterns' do it 'should accept a string matching a pattern' do p_t = pattern_t('abc') p_s = string_t('XabcY') calculator.assignable?(p_t, p_s).should == true end it 'should accept a regexp matching a pattern' do p_t = pattern_t(/abc/) p_s = string_t('XabcY') calculator.assignable?(p_t, p_s).should == true end it 'should accept a pattern matching a pattern' do p_t = pattern_t(pattern_t('abc')) p_s = string_t('XabcY') calculator.assignable?(p_t, p_s).should == true end it 'should accept a regexp matching a pattern' do p_t = pattern_t(regexp_t('abc')) p_s = string_t('XabcY') calculator.assignable?(p_t, p_s).should == true end it 'should accept a string matching all patterns' do p_t = pattern_t('abc', 'ab', 'c') p_s = string_t('XabcY') calculator.assignable?(p_t, p_s).should == true end it 'should accept multiple strings if they all match any patterns' do p_t = pattern_t('X', 'Y', 'abc') p_s = string_t('Xa', 'aY', 'abc') calculator.assignable?(p_t, p_s).should == true end it 'should reject a string not matching any patterns' do p_t = pattern_t('abc', 'ab', 'c') p_s = string_t('XqqqY') calculator.assignable?(p_t, p_s).should == false end it 'should reject multiple strings if not all match any patterns' do p_t = pattern_t('abc', 'ab', 'c', 'q') p_s = string_t('X', 'Y', 'Z') calculator.assignable?(p_t, p_s).should == false end it 'should accept enum matching patterns as instanceof' do enum = enum_t('XS', 'S', 'M', 'L' 'XL', 'XXL') pattern = pattern_t('S', 'M', 'L') calculator.assignable?(pattern, enum).should == true end end context 'when dealing with tuples' do + it 'matches empty tuples' do + tuple1 = tuple_t() + tuple2 = tuple_t() + + calculator.assignable?(tuple1, tuple2).should == true + calculator.assignable?(tuple2, tuple1).should == true + end + + it 'accepts an empty tuple as assignable to a tuple with a min size of 0' do + tuple1 = tuple_t(Object) + factory.constrain_size(tuple1, 0, :default) + tuple2 = tuple_t() + + calculator.assignable?(tuple1, tuple2).should == true + calculator.assignable?(tuple2, tuple1).should == false + end + it 'should accept matching tuples' do tuple1 = tuple_t(1,2) tuple2 = tuple_t(Integer,Integer) calculator.assignable?(tuple1, tuple2).should == true calculator.assignable?(tuple2, tuple1).should == true end it 'should accept matching tuples where one is more general than the other' do tuple1 = tuple_t(1,2) tuple2 = tuple_t(Numeric,Numeric) calculator.assignable?(tuple1, tuple2).should == false calculator.assignable?(tuple2, tuple1).should == true end it 'should accept ranged tuples' do tuple1 = tuple_t(1) factory.constrain_size(tuple1, 5, 5) tuple2 = tuple_t(Integer,Integer, Integer, Integer, Integer) calculator.assignable?(tuple1, tuple2).should == true calculator.assignable?(tuple2, tuple1).should == true end it 'should reject ranged tuples when ranges does not match' do tuple1 = tuple_t(1) factory.constrain_size(tuple1, 4, 5) tuple2 = tuple_t(Integer,Integer, Integer, Integer, Integer) calculator.assignable?(tuple1, tuple2).should == true calculator.assignable?(tuple2, tuple1).should == false end it 'should reject ranged tuples when ranges does not match (using infinite upper bound)' do tuple1 = tuple_t(1) factory.constrain_size(tuple1, 4, :default) tuple2 = tuple_t(Integer,Integer, Integer, Integer, Integer) calculator.assignable?(tuple1, tuple2).should == true calculator.assignable?(tuple2, tuple1).should == false end it 'should accept matching tuples with optional entries by repeating last' do tuple1 = tuple_t(1,2) factory.constrain_size(tuple1, 0, :default) tuple2 = tuple_t(Numeric,Numeric) factory.constrain_size(tuple2, 0, :default) calculator.assignable?(tuple1, tuple2).should == false calculator.assignable?(tuple2, tuple1).should == true end it 'should accept matching tuples with optional entries' do tuple1 = tuple_t(Integer, Integer, String) factory.constrain_size(tuple1, 1, 3) array2 = factory.constrain_size(array_t(Integer),2,2) calculator.assignable?(tuple1, array2).should == true factory.constrain_size(tuple1, 3, 3) calculator.assignable?(tuple1, array2).should == false end it 'should accept matching array' do tuple1 = tuple_t(1,2) array = array_t(Integer) factory.constrain_size(array, 2, 2) calculator.assignable?(tuple1, array).should == true calculator.assignable?(array, tuple1).should == true end + + it 'should accept empty array when tuple allows min of 0' do + tuple1 = tuple_t(Integer) + factory.constrain_size(tuple1, 0, 1) + + array = array_t(Integer) + factory.constrain_size(array, 0, 0) + + calculator.assignable?(tuple1, array).should == true + calculator.assignable?(array, tuple1).should == false + end end context 'when dealing with structs' do it 'should accept matching structs' do struct1 = struct_t({'a'=>Integer, 'b'=>Integer}) struct2 = struct_t({'a'=>Integer, 'b'=>Integer}) calculator.assignable?(struct1, struct2).should == true calculator.assignable?(struct2, struct1).should == true end it 'should accept matching structs where one is more general than the other' do struct1 = struct_t({'a'=>Integer, 'b'=>Integer}) struct2 = struct_t({'a'=>Numeric, 'b'=>Numeric}) calculator.assignable?(struct1, struct2).should == false calculator.assignable?(struct2, struct1).should == true end it 'should accept matching hash' do struct1 = struct_t({'a'=>Integer, 'b'=>Integer}) non_empty_string = string_t() non_empty_string.size_type = range_t(1, nil) hsh = hash_t(non_empty_string, Integer) factory.constrain_size(hsh, 2, 2) calculator.assignable?(struct1, hsh).should == true calculator.assignable?(hsh, struct1).should == true end end it 'should recognize ruby type inheritance' do class Foo end class Bar < Foo end fooType = calculator.infer(Foo.new) barType = calculator.infer(Bar.new) calculator.assignable?(fooType, fooType).should == true calculator.assignable?(Foo, fooType).should == true calculator.assignable?(fooType, barType).should == true calculator.assignable?(Foo, barType).should == true calculator.assignable?(barType, fooType).should == false calculator.assignable?(Bar, fooType).should == false end it "should allow host class with same name" do hc1 = Puppet::Pops::Types::TypeFactory.host_class('the_name') hc2 = Puppet::Pops::Types::TypeFactory.host_class('the_name') calculator.assignable?(hc1, hc2).should == true end it "should allow host class with name assigned to hostclass without name" do hc1 = Puppet::Pops::Types::TypeFactory.host_class() hc2 = Puppet::Pops::Types::TypeFactory.host_class('the_name') calculator.assignable?(hc1, hc2).should == true end it "should reject host classes with different names" do hc1 = Puppet::Pops::Types::TypeFactory.host_class('the_name') hc2 = Puppet::Pops::Types::TypeFactory.host_class('another_name') calculator.assignable?(hc1, hc2).should == false end it "should reject host classes without name assigned to host class with name" do hc1 = Puppet::Pops::Types::TypeFactory.host_class('the_name') hc2 = Puppet::Pops::Types::TypeFactory.host_class() calculator.assignable?(hc1, hc2).should == false end it "should allow resource with same type_name and title" do r1 = Puppet::Pops::Types::TypeFactory.resource('file', 'foo') r2 = Puppet::Pops::Types::TypeFactory.resource('file', 'foo') calculator.assignable?(r1, r2).should == true end it "should allow more specific resource assignment" do r1 = Puppet::Pops::Types::TypeFactory.resource() r2 = Puppet::Pops::Types::TypeFactory.resource('file') calculator.assignable?(r1, r2).should == true r2 = Puppet::Pops::Types::TypeFactory.resource('file', '/tmp/foo') calculator.assignable?(r1, r2).should == true r1 = Puppet::Pops::Types::TypeFactory.resource('file') calculator.assignable?(r1, r2).should == true end it "should reject less specific resource assignment" do r1 = Puppet::Pops::Types::TypeFactory.resource('file', '/tmp/foo') r2 = Puppet::Pops::Types::TypeFactory.resource('file') calculator.assignable?(r1, r2).should == false r2 = Puppet::Pops::Types::TypeFactory.resource() calculator.assignable?(r1, r2).should == false end end context 'when testing if x is instance of type t' do include_context "types_setup" it 'should consider undef to be instance of Object and NilType' do calculator.instance?(Puppet::Pops::Types::PNilType.new(), nil).should == true calculator.instance?(Puppet::Pops::Types::PObjectType.new(), nil).should == true end it 'should not consider undef to be an instance of any other type than Object and NilType and Data' do types_to_test = all_types - [ Puppet::Pops::Types::PObjectType, Puppet::Pops::Types::PNilType, Puppet::Pops::Types::PDataType] types_to_test.each {|t| calculator.instance?(t.new, nil).should == false } types_to_test.each {|t| calculator.instance?(t.new, :undef).should == false } end it 'should consider fixnum instanceof PIntegerType' do calculator.instance?(Puppet::Pops::Types::PIntegerType.new(), 1).should == true end it 'should consider fixnum instanceof Fixnum' do calculator.instance?(Fixnum, 1).should == true end it 'should consider integer in range' do range = range_t(0,10) calculator.instance?(range, 1).should == true calculator.instance?(range, 10).should == true calculator.instance?(range, -1).should == false calculator.instance?(range, 11).should == false end it 'should consider string in length range' do range = factory.constrain_size(string_t, 1,3) calculator.instance?(range, 'a').should == true calculator.instance?(range, 'abc').should == true calculator.instance?(range, '').should == false calculator.instance?(range, 'abcd').should == false end it 'should consider array in length range' do range = factory.constrain_size(array_t(integer_t), 1,3) calculator.instance?(range, [1]).should == true calculator.instance?(range, [1,2,3]).should == true calculator.instance?(range, []).should == false calculator.instance?(range, [1,2,3,4]).should == false end it 'should consider hash in length range' do range = factory.constrain_size(hash_t(integer_t, integer_t), 1,2) calculator.instance?(range, {1=>1}).should == true calculator.instance?(range, {1=>1, 2=>2}).should == true calculator.instance?(range, {}).should == false calculator.instance?(range, {1=>1, 2=>2, 3=>3}).should == false end it 'should consider collection in length range for array ' do range = factory.constrain_size(collection_t, 1,3) calculator.instance?(range, [1]).should == true calculator.instance?(range, [1,2,3]).should == true calculator.instance?(range, []).should == false calculator.instance?(range, [1,2,3,4]).should == false end it 'should consider collection in length range for hash' do range = factory.constrain_size(collection_t, 1,2) calculator.instance?(range, {1=>1}).should == true calculator.instance?(range, {1=>1, 2=>2}).should == true calculator.instance?(range, {}).should == false calculator.instance?(range, {1=>1, 2=>2, 3=>3}).should == false end it 'should consider string matching enum as instanceof' do enum = enum_t('XS', 'S', 'M', 'L', 'XL', '0') calculator.instance?(enum, 'XS').should == true calculator.instance?(enum, 'S').should == true calculator.instance?(enum, 'XXL').should == false calculator.instance?(enum, '').should == false calculator.instance?(enum, '0').should == true calculator.instance?(enum, 0).should == false end it 'should consider array[string] as instance of Array[Enum] when strings are instance of Enum' do enum = enum_t('XS', 'S', 'M', 'L', 'XL', '0') array = array_t(enum) calculator.instance?(array, ['XS', 'S', 'XL']).should == true calculator.instance?(array, ['XS', 'S', 'XXL']).should == false end it 'should consider array[mixed] as instance of Variant[mixed] when mixed types are listed in Variant' do enum = enum_t('XS', 'S', 'M', 'L', 'XL') sizes = range_t(30, 50) array = array_t(variant_t(enum, sizes)) calculator.instance?(array, ['XS', 'S', 30, 50]).should == true calculator.instance?(array, ['XS', 'S', 'XXL']).should == false calculator.instance?(array, ['XS', 'S', 29]).should == false end it 'should consider array[seq] as instance of Tuple[seq] when elements of seq are instance of' do tuple = tuple_t(Integer, String, Float) calculator.instance?(tuple, [1, 'a', 3.14]).should == true calculator.instance?(tuple, [1.2, 'a', 3.14]).should == false calculator.instance?(tuple, [1, 1, 3.14]).should == false calculator.instance?(tuple, [1, 'a', 1]).should == false end it 'should consider hash[cont] as instance of Struct[cont-t]' do struct = struct_t({'a'=>Integer, 'b'=>String, 'c'=>Float}) calculator.instance?(struct, {'a'=>1, 'b'=>'a', 'c'=>3.14}).should == true calculator.instance?(struct, {'a'=>1.2, 'b'=>'a', 'c'=>3.14}).should == false calculator.instance?(struct, {'a'=>1, 'b'=>1, 'c'=>3.14}).should == false calculator.instance?(struct, {'a'=>1, 'b'=>'a', 'c'=>1}).should == false end context 'and t is Data' do it 'undef should be considered instance of Data' do calculator.instance?(data_t, :undef).should == true end it 'other symbols should not be considered instance of Data' do calculator.instance?(data_t, :love).should == false end it 'an empty array should be considered instance of Data' do calculator.instance?(data_t, []).should == true end it 'an empty hash should be considered instance of Data' do calculator.instance?(data_t, {}).should == true end it 'a hash with nil/undef data should be considered instance of Data' do calculator.instance?(data_t, {'a' => nil}).should == true calculator.instance?(data_t, {'a' => :undef}).should == true end it 'a hash with nil/undef key should not considered instance of Data' do calculator.instance?(data_t, {nil => 10}).should == false calculator.instance?(data_t, {:undef => 10}).should == false end it 'an array with undef entries should be considered instance of Data' do calculator.instance?(data_t, [:undef]).should == true calculator.instance?(data_t, [nil]).should == true end it 'an array with undef / data entries should be considered instance of Data' do calculator.instance?(data_t, [1, :undef, 'a']).should == true calculator.instance?(data_t, [1, nil, 'a']).should == true end end context "and t is something Callable" do it 'a Closure should be considered a Callable' do factory = Puppet::Pops::Model::Factory params = [factory.PARAM('a')] the_block = factory.LAMBDA(params,factory.literal(42)) the_closure = Puppet::Pops::Evaluator::Closure.new(:fake_evaluator, the_block, :fake_scope) expect(calculator.instance?(all_callables_t, the_closure)).to be_true - # TODO: lambdas are currently unttypes, anything can be given if arg count is correct - expect(calculator.instance?(callable_t(optional_object_t), the_closure)).to be_true - # Arg count is wrong - expect(calculator.instance?(callable_t(optional_object_t, optional_object_t), the_closure)).to be_false + expect(calculator.instance?(callable_t(object_t), the_closure)).to be_true + expect(calculator.instance?(callable_t(object_t, object_t), the_closure)).to be_false end it 'a Function instance should be considered a Callable' do fc = Puppet::Functions.create_function(:foo) do dispatch :foo do param 'String', 'a' end def foo(a) a end end f = fc.new(:closure_scope, :loader) # Any callable expect(calculator.instance?(all_callables_t, f)).to be_true # Callable[String] expect(calculator.instance?(callable_t(String), f)).to be_true end end end context 'when converting a ruby class' do it 'should yield \'PIntegerType\' for Integer, Fixnum, and Bignum' do [Integer,Fixnum,Bignum].each do |c| calculator.type(c).class.should == Puppet::Pops::Types::PIntegerType end end it 'should yield \'PFloatType\' for Float' do calculator.type(Float).class.should == Puppet::Pops::Types::PFloatType end it 'should yield \'PBooleanType\' for FalseClass and TrueClass' do [FalseClass,TrueClass].each do |c| calculator.type(c).class.should == Puppet::Pops::Types::PBooleanType end end it 'should yield \'PNilType\' for NilClass' do calculator.type(NilClass).class.should == Puppet::Pops::Types::PNilType end it 'should yield \'PStringType\' for String' do calculator.type(String).class.should == Puppet::Pops::Types::PStringType end it 'should yield \'PRegexpType\' for Regexp' do calculator.type(Regexp).class.should == Puppet::Pops::Types::PRegexpType end it 'should yield \'PArrayType[PDataType]\' for Array' do t = calculator.type(Array) t.class.should == Puppet::Pops::Types::PArrayType t.element_type.class.should == Puppet::Pops::Types::PDataType end it 'should yield \'PHashType[PScalarType,PDataType]\' for Hash' do t = calculator.type(Hash) t.class.should == Puppet::Pops::Types::PHashType t.key_type.class.should == Puppet::Pops::Types::PScalarType t.element_type.class.should == Puppet::Pops::Types::PDataType end end context 'when representing the type as string' do it 'should yield \'Type\' for PType' do calculator.string(Puppet::Pops::Types::PType.new()).should == 'Type' end it 'should yield \'Object\' for PObjectType' do calculator.string(Puppet::Pops::Types::PObjectType.new()).should == 'Object' end it 'should yield \'Scalar\' for PScalarType' do calculator.string(Puppet::Pops::Types::PScalarType.new()).should == 'Scalar' end it 'should yield \'Boolean\' for PBooleanType' do calculator.string(Puppet::Pops::Types::PBooleanType.new()).should == 'Boolean' end it 'should yield \'Data\' for PDataType' do calculator.string(Puppet::Pops::Types::PDataType.new()).should == 'Data' end it 'should yield \'Numeric\' for PNumericType' do calculator.string(Puppet::Pops::Types::PNumericType.new()).should == 'Numeric' end it 'should yield \'Integer\' and from/to for PIntegerType' do int_T = Puppet::Pops::Types::PIntegerType calculator.string(int_T.new()).should == 'Integer' int = int_T.new() int.from = 1 int.to = 1 calculator.string(int).should == 'Integer[1, 1]' int = int_T.new() int.from = 1 int.to = 2 calculator.string(int).should == 'Integer[1, 2]' int = int_T.new() int.from = nil int.to = 2 calculator.string(int).should == 'Integer[default, 2]' int = int_T.new() int.from = 2 int.to = nil calculator.string(int).should == 'Integer[2, default]' end it 'should yield \'Float\' for PFloatType' do calculator.string(Puppet::Pops::Types::PFloatType.new()).should == 'Float' end it 'should yield \'Regexp\' for PRegexpType' do calculator.string(Puppet::Pops::Types::PRegexpType.new()).should == 'Regexp' end it 'should yield \'Regexp[/pat/]\' for parameterized PRegexpType' do t = Puppet::Pops::Types::PRegexpType.new() t.pattern = ('a/b') calculator.string(Puppet::Pops::Types::PRegexpType.new()).should == 'Regexp' end it 'should yield \'String\' for PStringType' do calculator.string(Puppet::Pops::Types::PStringType.new()).should == 'String' end it 'should yield \'String\' for PStringType with multiple values' do calculator.string(string_t('a', 'b', 'c')).should == 'String' end it 'should yield \'String\' and from/to for PStringType' do string_T = Puppet::Pops::Types::PStringType calculator.string(factory.constrain_size(string_T.new(), 1,1)).should == 'String[1, 1]' calculator.string(factory.constrain_size(string_T.new(), 1,2)).should == 'String[1, 2]' calculator.string(factory.constrain_size(string_T.new(), :default, 2)).should == 'String[default, 2]' calculator.string(factory.constrain_size(string_T.new(), 2, :default)).should == 'String[2, default]' end it 'should yield \'Array[Integer]\' for PArrayType[PIntegerType]' do t = Puppet::Pops::Types::PArrayType.new() t.element_type = Puppet::Pops::Types::PIntegerType.new() calculator.string(t).should == 'Array[Integer]' end it 'should yield \'Collection\' and from/to for PCollectionType' do col = collection_t() calculator.string(factory.constrain_size(col.copy, 1,1)).should == 'Collection[1, 1]' calculator.string(factory.constrain_size(col.copy, 1,2)).should == 'Collection[1, 2]' calculator.string(factory.constrain_size(col.copy, :default, 2)).should == 'Collection[default, 2]' calculator.string(factory.constrain_size(col.copy, 2, :default)).should == 'Collection[2, default]' end it 'should yield \'Array\' and from/to for PArrayType' do arr = array_t(string_t) calculator.string(factory.constrain_size(arr.copy, 1,1)).should == 'Array[String, 1, 1]' calculator.string(factory.constrain_size(arr.copy, 1,2)).should == 'Array[String, 1, 2]' calculator.string(factory.constrain_size(arr.copy, :default, 2)).should == 'Array[String, default, 2]' calculator.string(factory.constrain_size(arr.copy, 2, :default)).should == 'Array[String, 2, default]' end it 'should yield \'Tuple[Integer]\' for PTupleType[PIntegerType]' do t = Puppet::Pops::Types::PTupleType.new() t.addTypes(Puppet::Pops::Types::PIntegerType.new()) calculator.string(t).should == 'Tuple[Integer]' end it 'should yield \'Tuple[T, T,..]\' for PTupleType[T, T, ...]' do t = Puppet::Pops::Types::PTupleType.new() t.addTypes(Puppet::Pops::Types::PIntegerType.new()) t.addTypes(Puppet::Pops::Types::PIntegerType.new()) t.addTypes(Puppet::Pops::Types::PStringType.new()) calculator.string(t).should == 'Tuple[Integer, Integer, String]' end it 'should yield \'Tuple\' and from/to for PTupleType' do tuple_t = tuple_t(string_t) calculator.string(factory.constrain_size(tuple_t.copy, 1,1)).should == 'Tuple[String, 1, 1]' calculator.string(factory.constrain_size(tuple_t.copy, 1,2)).should == 'Tuple[String, 1, 2]' calculator.string(factory.constrain_size(tuple_t.copy, :default, 2)).should == 'Tuple[String, default, 2]' calculator.string(factory.constrain_size(tuple_t.copy, 2, :default)).should == 'Tuple[String, 2, default]' end it 'should yield \'Struct\' and details for PStructType' do struct_t = struct_t({'a'=>Integer, 'b'=>String}) s = calculator.string(struct_t) # Ruby 1.8.7 - noone likes you... (s == "Struct[{'a'=>Integer, 'b'=>String}]" || s == "Struct[{'b'=>String, 'a'=>Integer}]").should == true struct_t = struct_t({}) calculator.string(struct_t).should == "Struct" end it 'should yield \'Hash[String, Integer]\' for PHashType[PStringType, PIntegerType]' do t = Puppet::Pops::Types::PHashType.new() t.key_type = Puppet::Pops::Types::PStringType.new() t.element_type = Puppet::Pops::Types::PIntegerType.new() calculator.string(t).should == 'Hash[String, Integer]' end it 'should yield \'Hash\' and from/to for PHashType' do hsh = hash_t(string_t, string_t) calculator.string(factory.constrain_size(hsh.copy, 1,1)).should == 'Hash[String, String, 1, 1]' calculator.string(factory.constrain_size(hsh.copy, 1,2)).should == 'Hash[String, String, 1, 2]' calculator.string(factory.constrain_size(hsh.copy, :default, 2)).should == 'Hash[String, String, default, 2]' calculator.string(factory.constrain_size(hsh.copy, 2, :default)).should == 'Hash[String, String, 2, default]' end it "should yield 'Class' for a PHostClassType" do t = Puppet::Pops::Types::PHostClassType.new() calculator.string(t).should == 'Class' end it "should yield 'Class[x]' for a PHostClassType[x]" do t = Puppet::Pops::Types::PHostClassType.new() t.class_name = 'x' calculator.string(t).should == 'Class[x]' end it "should yield 'Resource' for a PResourceType" do t = Puppet::Pops::Types::PResourceType.new() calculator.string(t).should == 'Resource' end it 'should yield \'File\' for a PResourceType[\'File\']' do t = Puppet::Pops::Types::PResourceType.new() t.type_name = 'File' calculator.string(t).should == 'File' end it "should yield 'File['/tmp/foo']' for a PResourceType['File', '/tmp/foo']" do t = Puppet::Pops::Types::PResourceType.new() t.type_name = 'File' t.title = '/tmp/foo' calculator.string(t).should == "File['/tmp/foo']" end it "should yield 'Enum[s,...]' for a PEnumType[s,...]" do t = enum_t('a', 'b', 'c') calculator.string(t).should == "Enum['a', 'b', 'c']" end it "should yield 'Pattern[/pat/,...]' for a PPatternType['pat',...]" do t = pattern_t('a') t2 = pattern_t('a', 'b', 'c') calculator.string(t).should == "Pattern[/a/]" calculator.string(t2).should == "Pattern[/a/, /b/, /c/]" end it "should escape special characters in the string for a PPatternType['pat',...]" do t = pattern_t('a/b') calculator.string(t).should == "Pattern[/a\\/b/]" end it "should yield 'Variant[t1,t2,...]' for a PVariantType[t1, t2,...]" do t1 = string_t() t2 = integer_t() t3 = pattern_t('a') t = variant_t(t1, t2, t3) calculator.string(t).should == "Variant[String, Integer, Pattern[/a/]]" end it "should yield 'Callable' for generic callable" do expect(calculator.string(all_callables_t)).to eql("Callable") end it "should yield 'Callable[0,0]' for callable without params" do expect(calculator.string(callable_t)).to eql("Callable[0, 0]") end it "should yield 'Callable[t,t]' for callable with typed parameters" do expect(calculator.string(callable_t(String, Integer))).to eql("Callable[String, Integer]") end it "should yield 'Callable[t,min.max]' for callable with size constraint (infinite max)" do expect(calculator.string(callable_t(String, 0))).to eql("Callable[String, 0, default]") end it "should yield 'Callable[t,min.max]' for callable with size constraint (capped max)" do expect(calculator.string(callable_t(String, 0, 3))).to eql("Callable[String, 0, 3]") end it "should yield 'Callable[Callable]' for callable with block" do expect(calculator.string(callable_t(all_callables_t))).to eql("Callable[0, 0, Callable]") expect(calculator.string(callable_t(string_t, all_callables_t))).to eql("Callable[String, Callable]") expect(calculator.string(callable_t(string_t, 1,1, all_callables_t))).to eql("Callable[String, 1, 1, Callable]") end end context 'when processing meta type' do it 'should infer PType as the type of all other types' do ptype = Puppet::Pops::Types::PType calculator.infer(Puppet::Pops::Types::PNilType.new() ).is_a?(ptype).should() == true calculator.infer(Puppet::Pops::Types::PDataType.new() ).is_a?(ptype).should() == true calculator.infer(Puppet::Pops::Types::PScalarType.new() ).is_a?(ptype).should() == true calculator.infer(Puppet::Pops::Types::PStringType.new() ).is_a?(ptype).should() == true calculator.infer(Puppet::Pops::Types::PNumericType.new() ).is_a?(ptype).should() == true calculator.infer(Puppet::Pops::Types::PIntegerType.new() ).is_a?(ptype).should() == true calculator.infer(Puppet::Pops::Types::PFloatType.new() ).is_a?(ptype).should() == true calculator.infer(Puppet::Pops::Types::PRegexpType.new() ).is_a?(ptype).should() == true calculator.infer(Puppet::Pops::Types::PBooleanType.new() ).is_a?(ptype).should() == true calculator.infer(Puppet::Pops::Types::PCollectionType.new()).is_a?(ptype).should() == true calculator.infer(Puppet::Pops::Types::PArrayType.new() ).is_a?(ptype).should() == true calculator.infer(Puppet::Pops::Types::PHashType.new() ).is_a?(ptype).should() == true calculator.infer(Puppet::Pops::Types::PRubyType.new() ).is_a?(ptype).should() == true calculator.infer(Puppet::Pops::Types::PHostClassType.new() ).is_a?(ptype).should() == true calculator.infer(Puppet::Pops::Types::PResourceType.new() ).is_a?(ptype).should() == true calculator.infer(Puppet::Pops::Types::PEnumType.new() ).is_a?(ptype).should() == true calculator.infer(Puppet::Pops::Types::PPatternType.new() ).is_a?(ptype).should() == true calculator.infer(Puppet::Pops::Types::PVariantType.new() ).is_a?(ptype).should() == true calculator.infer(Puppet::Pops::Types::PTupleType.new() ).is_a?(ptype).should() == true calculator.infer(Puppet::Pops::Types::POptionalType.new() ).is_a?(ptype).should() == true calculator.infer(Puppet::Pops::Types::PCallableType.new() ).is_a?(ptype).should() == true end it 'should infer PType as the type of all other types' do ptype = Puppet::Pops::Types::PType calculator.string(calculator.infer(Puppet::Pops::Types::PNilType.new() )).should == "Type[Undef]" calculator.string(calculator.infer(Puppet::Pops::Types::PDataType.new() )).should == "Type[Data]" calculator.string(calculator.infer(Puppet::Pops::Types::PScalarType.new() )).should == "Type[Scalar]" calculator.string(calculator.infer(Puppet::Pops::Types::PStringType.new() )).should == "Type[String]" calculator.string(calculator.infer(Puppet::Pops::Types::PNumericType.new() )).should == "Type[Numeric]" calculator.string(calculator.infer(Puppet::Pops::Types::PIntegerType.new() )).should == "Type[Integer]" calculator.string(calculator.infer(Puppet::Pops::Types::PFloatType.new() )).should == "Type[Float]" calculator.string(calculator.infer(Puppet::Pops::Types::PRegexpType.new() )).should == "Type[Regexp]" calculator.string(calculator.infer(Puppet::Pops::Types::PBooleanType.new() )).should == "Type[Boolean]" calculator.string(calculator.infer(Puppet::Pops::Types::PCollectionType.new())).should == "Type[Collection]" calculator.string(calculator.infer(Puppet::Pops::Types::PArrayType.new() )).should == "Type[Array[?]]" calculator.string(calculator.infer(Puppet::Pops::Types::PHashType.new() )).should == "Type[Hash[?, ?]]" calculator.string(calculator.infer(Puppet::Pops::Types::PRubyType.new() )).should == "Type[Ruby[?]]" calculator.string(calculator.infer(Puppet::Pops::Types::PHostClassType.new() )).should == "Type[Class]" calculator.string(calculator.infer(Puppet::Pops::Types::PResourceType.new() )).should == "Type[Resource]" calculator.string(calculator.infer(Puppet::Pops::Types::PEnumType.new() )).should == "Type[Enum]" calculator.string(calculator.infer(Puppet::Pops::Types::PVariantType.new() )).should == "Type[Variant]" calculator.string(calculator.infer(Puppet::Pops::Types::PPatternType.new() )).should == "Type[Pattern]" calculator.string(calculator.infer(Puppet::Pops::Types::PTupleType.new() )).should == "Type[Tuple]" calculator.string(calculator.infer(Puppet::Pops::Types::POptionalType.new() )).should == "Type[Optional]" calculator.string(calculator.infer(Puppet::Pops::Types::PCallableType.new() )).should == "Type[Callable]" end it "computes the common type of PType's type parameter" do int_t = Puppet::Pops::Types::PIntegerType.new() string_t = Puppet::Pops::Types::PStringType.new() calculator.string(calculator.infer([int_t])).should == "Array[Type[Integer], 1, 1]" calculator.string(calculator.infer([int_t, string_t])).should == "Array[Type[Scalar], 2, 2]" end it 'should infer PType as the type of ruby classes' do class Foo end [Object, Numeric, Integer, Fixnum, Bignum, Float, String, Regexp, Array, Hash, Foo].each do |c| calculator.infer(c).is_a?(Puppet::Pops::Types::PType).should() == true end end it 'should infer PType as the type of PType (meta regression short-circuit)' do calculator.infer(Puppet::Pops::Types::PType.new()).is_a?(Puppet::Pops::Types::PType).should() == true end it 'computes instance? to be true if parameterized and type match' do int_t = Puppet::Pops::Types::PIntegerType.new() type_t = Puppet::Pops::Types::TypeFactory.type_type(int_t) type_type_t = Puppet::Pops::Types::TypeFactory.type_type(type_t) calculator.instance?(type_type_t, type_t).should == true end it 'computes instance? to be false if parameterized and type do not match' do int_t = Puppet::Pops::Types::PIntegerType.new() string_t = Puppet::Pops::Types::PStringType.new() type_t = Puppet::Pops::Types::TypeFactory.type_type(int_t) type_t2 = Puppet::Pops::Types::TypeFactory.type_type(string_t) type_type_t = Puppet::Pops::Types::TypeFactory.type_type(type_t) # i.e. Type[Integer] =~ Type[Type[Integer]] # false calculator.instance?(type_type_t, type_t2).should == false end it 'computes instance? to be true if unparameterized and matched against a type[?]' do int_t = Puppet::Pops::Types::PIntegerType.new() type_t = Puppet::Pops::Types::TypeFactory.type_type(int_t) calculator.instance?(Puppet::Pops::Types::PType.new, type_t).should == true end end context "when asking for an enumerable " do it "should produce an enumerable for an Integer range that is not infinite" do t = Puppet::Pops::Types::PIntegerType.new() t.from = 1 t.to = 10 calculator.enumerable(t).respond_to?(:each).should == true end it "should not produce an enumerable for an Integer range that has an infinite side" do t = Puppet::Pops::Types::PIntegerType.new() t.from = nil t.to = 10 calculator.enumerable(t).should == nil t = Puppet::Pops::Types::PIntegerType.new() t.from = 1 t.to = nil calculator.enumerable(t).should == nil end it "all but Integer range are not enumerable" do [Object, Numeric, Float, String, Regexp, Array, Hash].each do |t| calculator.enumerable(calculator.type(t)).should == nil end end end context "when dealing with different types of inference" do it "an instance specific inference is produced by infer" do calculator.infer(['a','b']).element_type.values.should == ['a', 'b'] end it "a generic inference is produced using infer_generic" do calculator.infer_generic(['a','b']).element_type.values.should == [] end it "a generic result is created by generalize! given an instance specific result for an Array" do generic = calculator.infer(['a','b']) generic.element_type.values.should == ['a', 'b'] calculator.generalize!(generic) generic.element_type.values.should == [] end it "a generic result is created by generalize! given an instance specific result for a Hash" do generic = calculator.infer({'a' =>1,'b' => 2}) generic.key_type.values.sort.should == ['a', 'b'] generic.element_type.from.should == 1 generic.element_type.to.should == 2 calculator.generalize!(generic) generic.key_type.values.should == [] generic.element_type.from.should == nil generic.element_type.to.should == nil end it "does not reduce by combining types when using infer_set" do element_type = calculator.infer(['a','b',1,2]).element_type element_type.class.should == Puppet::Pops::Types::PScalarType inferred_type = calculator.infer_set(['a','b',1,2]) inferred_type.class.should == Puppet::Pops::Types::PTupleType element_types = inferred_type.types element_types[0].class.should == Puppet::Pops::Types::PStringType element_types[1].class.should == Puppet::Pops::Types::PStringType element_types[2].class.should == Puppet::Pops::Types::PIntegerType element_types[3].class.should == Puppet::Pops::Types::PIntegerType end it "does not reduce by combining types when using infer_set and values are undef" do element_type = calculator.infer(['a',nil]).element_type element_type.class.should == Puppet::Pops::Types::PStringType inferred_type = calculator.infer_set(['a',nil]) inferred_type.class.should == Puppet::Pops::Types::PTupleType element_types = inferred_type.types element_types[0].class.should == Puppet::Pops::Types::PStringType element_types[1].class.should == Puppet::Pops::Types::PNilType end end matcher :be_assignable_to do |type| calc = Puppet::Pops::Types::TypeCalculator.new match do |actual| calc.assignable?(type, actual) end failure_message_for_should do |actual| "#{calc.string(actual)} should be assignable to #{calc.string(type)}" end failure_message_for_should_not do |actual| "#{calc.string(actual)} is assignable to #{calc.string(type)} when it should not" end end end