diff --git a/lib/puppet/parser/functions/epp.rb b/lib/puppet/parser/functions/epp.rb index ee6abf05d..da6d5f3ff 100644 --- a/lib/puppet/parser/functions/epp.rb +++ b/lib/puppet/parser/functions/epp.rb @@ -1,53 +1,41 @@ -Puppet::Parser::Functions::newfunction(:epptemplate, :type => :rvalue, :arity => -2, :doc => -"Evaluates one or more Embedded Puppet Template (EPP) files and returns their concatenated result. +Puppet::Parser::Functions::newfunction(:epp, :type => :rvalue, :arity => -2, :doc => +"Evaluates an Embedded Puppet Template (EPP) file and returns the rendered text result as a String. EPP support the following tags: + * `<%= puppet expression %>` - This tag renders the value of the expression it contains. * `<% puppet expression(s) %>` - This tag will execute the expression(s) it contains, but renders nothing. * `<%# comment %>` - The tag and its content renders nothing. * `<%%` or `%%>` - Renders a literal `<%` or `%>` respectively. * `<%-` - Same as `<%` but suppresses any leading whitespace. * `-%>` - Same as `%>` but suppresses any trailing whitespace on the same line (including line break). +* `<%-( parameters )-%>` - When placed as the first tag declares the template's parameters. + +File based EPP supports the following visibilities of variables in scope: + +* Global scope (i.e. top + node scopes) - global scope is always visible +* Global + all given arguments - if the EPP template does not declare parameters, and arguments are given +* Global + declared parameters - if the EPP declares parameters, given argument names must match -EPP supports parameters by placing an optional parameter list as the very first element in the Epp. As an example, +EPP supports parameters by placing an optional parameter list as the very first element in the EPP. As an example, `<%- ($x, $y, $z='unicorn') -%>` when placed first in the EPP text declares that the parameters `x` and `y` must be -given as template arguments when calling `epptemplate`, and that `z` if not given as a template argument +given as template arguments when calling `inline_epp`, and that `z` if not given as a template argument defaults to `'unicorn'`. Template parameters are available as variables, e.g.arguments `$x`, `$y` and `$z` in the example. +Note that `<%-` must be used or any leading whitespace will be interpreted as text -Arguments are passed to the template by calling `epptemplate` with a Hash as the last argument, where parameters -are bound to values, e.g. `epptemplate('templatefile.epp', {'x'=>10, 'y'=>20})`. Excess arguments may be given -(i.e. undeclared parameters). Template parameters shadow variables in outer scopes. Template arguments may be -passed to any template; the template text does not have to have a declaration of parameters. - -Several files may be given as arguments to `epptemplate`, the result is the concatenation of each produced result. -If template arguments are given, they are used for each given file. -") do |arguments| - # accepts one or more arguments (each being a file), except an optional last argument being a hash - # of parameters to pass to each evaluation. - - if(arguments[-1].is_a? Hash) - template_args = arguments[-1] - arguments = arguments[0..-2] - else - template_args = {} +Arguments are passed to the template by calling `epp` with a Hash as the last argument, where parameters +are bound to values, e.g. `epp('...', {'x'=>10, 'y'=>20})`. Excess arguments may be given +(i.e. undeclared parameters) only if the EPP templates does not declare any parameters at all. +Template parameters shadow variables in outer scopes. File based epp does never have access to variables in the +scope where the `epp` function is called from. + +- See function inline_epp for examples of EPP +- Since 3.5 +- Requires Future Parser") do |arguments| + # Requires future parser + unless Puppet[:parser] == "future" + raise ArgumentError, "epp(): function is only available when --parser future is in effect" end - require 'puppet/parser/parser_factory' - require 'puppet/parser/ast' - - arguments.collect do |file| - if file.is_a?(Hash) - raise IllegalArgumentException, "A Hash may be given as the last argument only" - end - debug "Retrieving epp template #{file}" - template_file = Puppet::Parser::Files.find_template(file, self.compiler.environment.to_s) - unless template_file - raise Puppet::ParseError, "Could not find template '#{filename}'" - end - - parser = Puppet::Parser::ParserFactory.epp_parser(self.compiler.environment) - parser.file = template_file - result = parser.parse() - raise Puppet::ParseError, "Parsing #{template_file} did not produce an instance of Epp. Got: #{result.class}" unless result.is_a?(Puppet::Parser::AST::Epp) - result.call(self, template_args) - end.join("") + Puppet::Pops::Evaluator::EppEvaluator.epp(self, arguments[0], self.compiler.environment.to_s, arguments[1]) + end diff --git a/lib/puppet/parser/functions/inline_epp.rb b/lib/puppet/parser/functions/inline_epp.rb index ca0076712..cfc1597a1 100644 --- a/lib/puppet/parser/functions/inline_epp.rb +++ b/lib/puppet/parser/functions/inline_epp.rb @@ -1,58 +1,79 @@ -Puppet::Parser::Functions::newfunction(:inline_epptemplate, :type => :rvalue, :arity => -2, :doc => -"Evaluates one or more Embedded Puppet Template (EPP) strings and returns their concatenated result. +Puppet::Parser::Functions::newfunction(:inline_epp, :type => :rvalue, :arity => -2, :doc => +"Evaluates an Embedded Puppet Template (EPP) string and returns the rendered text result as a String. EPP support the following tags: + * `<%= puppet expression %>` - This tag renders the value of the expression it contains. * `<% puppet expression(s) %>` - This tag will execute the expression(s) it contains, but renders nothing. * `<%# comment %>` - The tag and its content renders nothing. * `<%%` or `%%>` - Renders a literal `<%` or `%>` respectively. * `<%-` - Same as `<%` but suppresses any leading whitespace. * `-%>` - Same as `%>` but suppresses any trailing whitespace on the same line (including line break). +* `<%-( parameters )-%>` - When placed as the first tag declares the template's parameters. -EPP supports parameters by placing an optional parameter list as the very first element in the Epp. As an example, -`<%- ($x, $y, $z='unicorn') -%>` when placed first in the EPP text declares that the parameters `x` and `y` must be -given as template arguments when calling `inline_epptemplate`, and that `z` if not given as a template argument -defaults to `'unicorn'`. Template parameters are available as variables, e.g.arguments `$x`, `$y` and `$z` in the example. +Inline EPP supports the following visibilities of variables in scope which depends on how EPP parameters +are used - see further below: + +* Global scope (i.e. top + node scopes) - global scope is always visible +* Global + Enclosing scope - if the EPP template does not declare parameters, and no arguments are given +* Global + all given arguments - if the EPP template does not declare parameters, and arguments are given +* Global + declared parameters - if the EPP declares parameters, given argument names must match -Arguments are passed to the template by calling `inline_epptemplate` with a Hash as the last argument, where parameters -are bound to values, e.g. `inline_epptemplate('...', {'x'=>10, 'y'=>20})`. Excess arguments may be given -(i.e. undeclared parameters). Template parameters shadow variables in outer scopes. Template arguments may be -passed to any template; the template text does not have to have a declaration of parameters. +EPP supports parameters by placing an optional parameter list as the very first element in the EPP. As an example, +`<%-( $x, $y, $z='unicorn' )-%>` when placed first in the EPP text declares that the parameters `x` and `y` must be +given as template arguments when calling `inline_epp`, and that `z` if not given as a template argument +defaults to `'unicorn'`. Template parameters are available as variables, e.g.arguments `$x`, `$y` and `$z` in the example. +Note that `<%-` must be used or any leading whitespace will be interpreted as text -Several strings may be given as arguments to `inline_epptemplate`, the result is the concatenation of each produced result. -If template arguments are given, they are used for each given template string. +Arguments are passed to the template by calling `inline_epp` with a Hash as the last argument, where parameters +are bound to values, e.g. `inline_epp('...', {'x'=>10, 'y'=>20})`. Excess arguments may be given +(i.e. undeclared parameters) only if the EPP templates does not declare any parameters at all. +Template parameters shadow variables in outer scopes. Note: An inline template is best stated using a single-quoted string, or a heredoc since a double-quoted string -is subject to expression interpolation before the string is parsed as an EPP template. Here is an example -using heredoc. +is subject to expression interpolation before the string is parsed as an EPP template. Here are examples +(using heredoc to define the EPP text): + + # produces 'Hello local variable world!' + $x ='local variable' + inline_epptemplate(@(END:epp)) + <%-( $x )-%> + Hello <%= $x %> world! + END - inline_epptemplate(@(END:epp), {'x'=>'epp template world'}) - <%- ($x) -%> + # produces 'Hello given argument world!' + $x ='local variable world' + inline_epptemplate(@(END:epp), { x =>'given argument'}) + <%-( $x )-%> + Hello <%= $x %> world! + END + + # produces 'Hello given argument world!' + $x ='local variable world' + inline_epptemplate(@(END:epp), { x =>'given argument'}) + <%-( $x )-%> + Hello <%= $x %>! + END + + # results in error, missing value for y + $x ='local variable world' + inline_epptemplate(@(END:epp), { x =>'given argument'}) + <%-( $x, $y )-%> Hello <%= $x %>! END -") do |arguments| - # accepts one or more arguments (each being a epp source string), except an optional last argument being a hash - # of parameters to pass to each evaluation. + # Produces 'Hello given argument planet' + $x ='local variable world' + inline_epptemplate(@(END:epp), { x =>'given argument'}) + <%-( $x, $y=planet)-%> + Hello <%= $x %> <%= $y %>! + END - if(arguments[-1].is_a? Hash) - template_args = arguments[-1] - arguments = arguments[0..-2] - else - template_args = {} +- Since 3.5 +- Requires Future Parser") do |arguments| + # Requires future parser + unless Puppet[:parser] == "future" + raise ArgumentError, "inline_epp(): function is only available when --parser future is in effect" end - require 'puppet/parser/parser_factory' - require 'puppet/parser/ast' - - arguments.collect do |text| - if text.is_a?(Hash) - raise IllegalArgumentException, "A Hash may be given as the last argument only" - end - - parser = Puppet::Parser::ParserFactory.epp_parser(self.compiler.environment) - parser.string = text - result = parser.parse() - raise Puppet::ParseError, "Parsing epp string did not produce an instance of Epp. Got: #{result.class}" unless result.is_a?(Puppet::Parser::AST::Epp) - result.call(self, template_args) - end.join("") + Puppet::Pops::Evaluator::EppEvaluator.inline_epp(self, arguments[0], arguments[1]) end diff --git a/lib/puppet/parser/scope.rb b/lib/puppet/parser/scope.rb index e043bff5a..f9a8efef4 100644 --- a/lib/puppet/parser/scope.rb +++ b/lib/puppet/parser/scope.rb @@ -1,817 +1,827 @@ # The scope class, which handles storing and retrieving variables and types and # such. require 'forwardable' require 'puppet/parser' require 'puppet/parser/templatewrapper' require 'puppet/resource/type_collection_helper' require 'puppet/util/methodhelper' # This class is part of the internal parser/evaluator/compiler functionality of Puppet. # It is passed between the various classes that participate in evaluation. # None of its methods are API except those that are clearly marked as such. # # @api public class Puppet::Parser::Scope extend Forwardable include Puppet::Util::MethodHelper include Puppet::Resource::TypeCollectionHelper require 'puppet/parser/resource' AST = Puppet::Parser::AST Puppet::Util.logmethods(self) include Puppet::Util::Errors attr_accessor :source, :resource attr_accessor :compiler attr_accessor :parent attr_reader :namespaces # Add some alias methods that forward to the compiler, since we reference # them frequently enough to justify the extra method call. def_delegators :compiler, :catalog, :environment # Abstract base class for LocalScope and MatchScope # class Ephemeral attr_reader :parent def initialize(parent = nil) @parent = parent end def is_local_scope? false end def [](name) if @parent @parent[name] end end def include?(name) (@parent and @parent.include?(name)) end def bound?(name) false end def add_entries_to(target = {}) @parent.add_entries_to(target) unless @parent.nil? # do not include match data ($0-$n) target end end class LocalScope < Ephemeral def initialize(parent=nil) super parent @symbols = {} end def [](name) if @symbols.include?(name) @symbols[name] else super end end def is_local_scope? true end def []=(name, value) @symbols[name] = value end def include?(name) bound?(name) || super end def delete(name) @symbols.delete(name) end def bound?(name) @symbols.include?(name) end def add_entries_to(target = {}) super @symbols.each do |k, v| if v == :undef target.delete(k) else target[ k ] = v end end target end end class MatchScope < Ephemeral attr_accessor :match_data def initialize(parent = nil, match_data = nil) super parent @match_data = match_data end def is_local_scope? false end def [](name) if bound?(name) @match_data[name.to_i] else super end end def include?(name) bound?(name) or super end def bound?(name) # A "match variables" scope reports all numeric variables to be bound if the scope has # match_data. Without match data the scope is transparent. # @match_data && name =~ /^\d+$/ end def []=(name, value) # TODO: Bad choice of exception raise Puppet::ParseError, "Numerical variables cannot be changed. Attempt to set $#{name}" end def delete(name) # TODO: Bad choice of exception raise Puppet::ParseError, "Numerical variables cannot be deleted: Attempt to delete: $#{name}" end def add_entries_to(target = {}) # do not include match data ($0-$n) super end end # Returns true if the variable of the given name has a non nil value. # TODO: This has vague semantics - does the variable exist or not? # use ['name'] to get nil or value, and if nil check with exist?('name') # this include? is only useful because of checking against the boolean value false. # def include?(name) ! self[name].nil? end # Returns true if the variable of the given name is set to any value (including nil) # def exist?(name) next_scope = inherited_scope || enclosing_scope effective_symtable(true).include?(name) || next_scope && next_scope.exist?(name) end # Returns true if the given name is bound in the current (most nested) scope for assignments. # def bound?(name) # Do not look in ephemeral (match scope), the semantics is to answer if an assignable variable is bound effective_symtable(false).bound?(name) end # Is the value true? This allows us to control the definition of truth # in one place. def self.true?(value) case value when '' false when :undef false else !!value end end # Coerce value to a number, or return `nil` if it isn't one. def self.number?(value) case value when Numeric value when /^-?\d+(:?\.\d+|(:?\.\d+)?e\d+)$/ value.to_f when /^0x[0-9a-f]+$/i value.to_i(16) when /^0[0-7]+$/ value.to_i(8) when /^-?\d+$/ value.to_i else nil end end # Add to our list of namespaces. def add_namespace(ns) return false if @namespaces.include?(ns) if @namespaces == [""] @namespaces = [ns] else @namespaces << ns end end def find_hostclass(name, options = {}) known_resource_types.find_hostclass(namespaces, name, options) end def find_definition(name) known_resource_types.find_definition(namespaces, name) end + def find_global_scope() + # walk upwards until first found node_scope or top_scope + if is_nodescope? || is_topscope? + self + else + next_scope = inherited_scope || enclosing_scope + next_scope.find_global_scope() + end + end + # This just delegates directly. def_delegator :compiler, :findresource # Initialize our new scope. Defaults to having no parent. def initialize(compiler, options = {}) if compiler.is_a? Puppet::Parser::Compiler self.compiler = compiler else raise Puppet::DevError, "you must pass a compiler instance to a new scope object" end if n = options.delete(:namespace) @namespaces = [n] else @namespaces = [""] end raise Puppet::DevError, "compiler passed in options" if options.include? :compiler set_options(options) extend_with_functions_module # The symbol table for this scope. This is where we store variables. # @symtable = Ephemeral.new(nil, true) @symtable = LocalScope.new(nil) @ephemeral = [ MatchScope.new(@symtable, nil) ] # All of the defaults set for types. It's a hash of hashes, # with the first key being the type, then the second key being # the parameter. @defaults = Hash.new { |dhash,type| dhash[type] = {} } # The table for storing class singletons. This will only actually # be used by top scopes and node scopes. @class_scopes = {} @enable_immutable_data = Puppet[:immutable_node_data] end # Store the fact that we've evaluated a class, and store a reference to # the scope in which it was evaluated, so that we can look it up later. def class_set(name, scope) if parent parent.class_set(name, scope) else @class_scopes[name] = scope end end # Return the scope associated with a class. This is just here so # that subclasses can set their parent scopes to be the scope of # their parent class, and it's also used when looking up qualified # variables. def class_scope(klass) # They might pass in either the class or class name k = klass.respond_to?(:name) ? klass.name : klass @class_scopes[k] || (parent && parent.class_scope(k)) end # Collect all of the defaults set at any higher scopes. # This is a different type of lookup because it's additive -- # it collects all of the defaults, with defaults in closer scopes # overriding those in later scopes. def lookupdefaults(type) values = {} # first collect the values from the parents if parent parent.lookupdefaults(type).each { |var,value| values[var] = value } end # then override them with any current values # this should probably be done differently if @defaults.include?(type) @defaults[type].each { |var,value| values[var] = value } end values end # Look up a defined type. def lookuptype(name) find_definition(name) || find_hostclass(name) end def undef_as(x,v) if v.nil? or v == :undef x else v end end # Lookup a variable within this scope using the Puppet language's # scoping rules. Variables can be qualified using just as in a # manifest. # # @param [String] name the variable name to lookup # # @return Object the value of the variable, or nil if it's not found # # @api public def lookupvar(name, options = {}) unless name.is_a? String raise Puppet::ParseError, "Scope variable name #{name.inspect} is a #{name.class}, not a string" end table = @ephemeral.last if name =~ /^(.*)::(.+)$/ class_name = $1 variable_name = $2 lookup_qualified_variable(class_name, variable_name, options) # TODO: optimize with an assoc instead, this searches through scopes twice for a hit elsif table.include?(name) table[name] else next_scope = inherited_scope || enclosing_scope if next_scope next_scope.lookupvar(name, options) else variable_not_found(name) end end end def variable_not_found(name, reason=nil) if Puppet[:strict_variables] if Puppet[:evaluator] == 'future' && Puppet[:parser] == 'future' throw :undefined_variable else reason_msg = reason.nil? ? '' : "; #{reason}" raise Puppet::ParseError, "Undefined variable #{name.inspect}#{reason_msg}" end else nil end end # Retrieves the variable value assigned to the name given as an argument. The name must be a String, # and namespace can be qualified with '::'. The value is looked up in this scope, its parent scopes, # or in a specific visible named scope. # # @param varname [String] the name of the variable (may be a qualified name using `(ns'::')*varname` # @param options [Hash] Additional options, not part of api. # @return [Object] the value assigned to the given varname # @see #[]= # @api public # def [](varname, options={}) lookupvar(varname, options) end # The scope of the inherited thing of this scope's resource. This could # either be a node that was inherited or the class. # # @return [Puppet::Parser::Scope] The scope or nil if there is not an inherited scope def inherited_scope if has_inherited_class? qualified_scope(resource.resource_type.parent) else nil end end # The enclosing scope (topscope or nodescope) of this scope. # The enclosing scopes are produced when a class or define is included at # some point. The parent scope of the included class or define becomes the # scope in which it was included. The chain of parent scopes is followed # until a node scope or the topscope is found # # @return [Puppet::Parser::Scope] The scope or nil if there is no enclosing scope def enclosing_scope if has_enclosing_scope? if parent.is_topscope? or parent.is_nodescope? parent else parent.enclosing_scope end else nil end end def is_classscope? resource and resource.type == "Class" end def is_nodescope? resource and resource.type == "Node" end def is_topscope? compiler and self == compiler.topscope end def lookup_qualified_variable(class_name, variable_name, position) begin if lookup_as_local_name?(class_name, variable_name) self[variable_name] else qualified_scope(class_name).lookupvar(variable_name, position) end rescue RuntimeError => e unless Puppet[:strict_variables] # Do not issue warning if strict variables are on, as an error will be raised by variable_not_found location = if position[:lineproc] " at #{position[:lineproc].call}" elsif position[:file] && position[:line] " at #{position[:file]}:#{position[:line]}" else "" end warning "Could not look up qualified variable '#{class_name}::#{variable_name}'; #{e.message}#{location}" end variable_not_found("#{class_name}::#{variable_name}", e.message) end end # Handles the special case of looking up fully qualified variable in not yet evaluated top scope # This is ok if the lookup request originated in topscope (this happens when evaluating # bindings; using the top scope to provide the values for facts. # @param class_name [String] the classname part of a variable name, may be special "" # @param variable_name [String] the variable name without the absolute leading '::' # @return [Boolean] true if the given variable name should be looked up directly in this scope # def lookup_as_local_name?(class_name, variable_name) # not a local if name has more than one segment return nil if variable_name =~ /::/ # partial only if the class for "" cannot be found return nil unless class_name == "" && klass = find_hostclass(class_name) && class_scope(klass).nil? is_topscope? end def has_inherited_class? is_classscope? and resource.resource_type.parent end private :has_inherited_class? def has_enclosing_scope? not parent.nil? end private :has_enclosing_scope? def qualified_scope(classname) raise "class #{classname} could not be found" unless klass = find_hostclass(classname) raise "class #{classname} has not been evaluated" unless kscope = class_scope(klass) kscope end private :qualified_scope # Returns a Hash containing all variables and their values, optionally (and # by default) including the values defined in parent. Local values # shadow parent values. Ephemeral scopes for match results ($0 - $n) are not included. # # This is currently a wrapper for to_hash_legacy or to_hash_future. # # @see to_hash_future # # @see to_hash_legacy def to_hash(recursive = true) @parser ||= Puppet[:parser] if @parser == 'future' to_hash_future(recursive) else to_hash_legacy(recursive) end end # Fixed version of to_hash that implements scoping correctly (i.e., with # dynamic scoping disabled #28200 / PUP-1220 # # @see to_hash def to_hash_future(recursive) if recursive and has_enclosing_scope? target = enclosing_scope.to_hash_future(recursive) else target = Hash.new end # add all local scopes @ephemeral.last.add_entries_to(target) target end # The old broken implementation of to_hash that retains the dynamic scoping # semantics # # @see to_hash def to_hash_legacy(recursive = true) if recursive and parent target = parent.to_hash_legacy(recursive) else target = Hash.new end # add all local scopes @ephemeral.last.add_entries_to(target) target end def namespaces @namespaces.dup end # Create a new scope and set these options. def newscope(options = {}) compiler.newscope(self, options) end def parent_module_name return nil unless @parent return nil unless @parent.source @parent.source.module_name end # Set defaults for a type. The typename should already be downcased, # so that the syntax is isolated. We don't do any kind of type-checking # here; instead we let the resource do it when the defaults are used. def define_settings(type, params) table = @defaults[type] # if we got a single param, it'll be in its own array params = [params] unless params.is_a?(Array) params.each { |param| if table.include?(param.name) raise Puppet::ParseError.new("Default already defined for #{type} { #{param.name} }; cannot redefine", param.line, param.file) end table[param.name] = param } end RESERVED_VARIABLE_NAMES = ['trusted', 'facts'].freeze # Set a variable in the current scope. This will override settings # in scopes above, but will not allow variables in the current scope # to be reassigned. # It's preferred that you use self[]= instead of this; only use this # when you need to set options. def setvar(name, value, options = {}) if name =~ /^[0-9]+$/ raise Puppet::ParseError.new("Cannot assign to a numeric match result variable '$#{name}'") # unless options[:ephemeral] end unless name.is_a? String raise Puppet::ParseError, "Scope variable name #{name.inspect} is a #{name.class}, not a string" end # Check for reserved variable names if @enable_immutable_data && !options[:privileged] && RESERVED_VARIABLE_NAMES.include?(name) raise Puppet::ParseError, "Attempt to assign to a reserved variable name: '#{name}'" end table = effective_symtable options[:ephemeral] if table.bound?(name) if options[:append] error = Puppet::ParseError.new("Cannot append, variable #{name} is defined in this scope") else error = Puppet::ParseError.new("Cannot reassign variable #{name}") end error.file = options[:file] if options[:file] error.line = options[:line] if options[:line] raise error end if options[:append] table[name] = append_value(undef_as('', self[name]), value) else table[name] = value end table[name] end def set_trusted(hash) setvar('trusted', deep_freeze(hash), :privileged => true) end def set_facts(hash) setvar('facts', deep_freeze(hash), :privileged => true) end # Deeply freezes the given object. The object and its content must be of the types: # Array, Hash, Numeric, Boolean, Symbol, Regexp, NilClass, or String. All other types raises an Error. # (i.e. if they are assignable to Puppet::Pops::Types::Data type). # def deep_freeze(object) case object when Array object.each {|v| deep_freeze(v) } object.freeze when Hash object.each {|k, v| deep_freeze(k); deep_freeze(v) } object.freeze when NilClass, Numeric, TrueClass, FalseClass # do nothing when String object.freeze else raise Puppet::Error, "Unsupported data type: '#{object.class}'" end object end private :deep_freeze # Return the effective "table" for setting variables. # This method returns the first ephemeral "table" that acts as a local scope, or this # scope's symtable. If the parameter `use_ephemeral` is true, the "top most" ephemeral "table" # will be returned (irrespective of it being a match scope or a local scope). # # @param use_ephemeral [Boolean] whether the top most ephemeral (of any kind) should be used or not def effective_symtable use_ephemeral s = @ephemeral.last return s || @symtable if use_ephemeral # Why check if ephemeral is a Hash ??? Not needed, a hash cannot be a parent scope ??? while s && !(s.is_a?(Hash) || s.is_local_scope?()) s = s.parent end s ? s : @symtable end # Sets the variable value of the name given as an argument to the given value. The value is # set in the current scope and may shadow a variable with the same name in a visible outer scope. # It is illegal to re-assign a variable in the same scope. It is illegal to set a variable in some other # scope/namespace than the scope passed to a method. # # @param varname [String] The variable name to which the value is assigned. Must not contain `::` # @param value [String] The value to assign to the given variable name. # @param options [Hash] Additional options, not part of api. # # @api public # def []=(varname, value, options = {}) setvar(varname, value, options = {}) end def append_value(bound_value, new_value) case new_value when Array bound_value + new_value when Hash bound_value.merge(new_value) else if bound_value.is_a?(Hash) raise ArgumentError, "Trying to append to a hash with something which is not a hash is unsupported" end bound_value + new_value end end private :append_value # Return the tags associated with this scope. def_delegator :resource, :tags # Used mainly for logging def to_s "Scope(#{@resource})" end # remove ephemeral scope up to level # TODO: Who uses :all ? Remove ?? # def unset_ephemeral_var(level=:all) if level == :all @ephemeral = [ MatchScope.new(@symtable, nil)] else @ephemeral.pop(@ephemeral.size - level) end end def ephemeral_level @ephemeral.size end # TODO: Who calls this? def new_ephemeral(local_scope = false) if local_scope @ephemeral.push(LocalScope.new(@ephemeral.last)) else @ephemeral.push(MatchScope.new(@ephemeral.last, nil)) end end # Sets match data in the most nested scope (which always is a MatchScope), it clobbers match data already set there # def set_match_data(match_data) @ephemeral.last.match_data = match_data end # Nests a match data scope def new_match_scope(match_data) @ephemeral.push(MatchScope.new(@ephemeral.last, match_data)) end def ephemeral_from(match, file = nil, line = nil) case match when Hash # Create local scope ephemeral and set all values from hash new_ephemeral(true) match.each {|k,v| setvar(k, v, :file => file, :line => line, :ephemeral => true) } # Must always have an inner match data scope (that starts out as transparent) # In 3x slightly wasteful, since a new nested scope is created for a match # (TODO: Fix that problem) new_ephemeral(false) else raise(ArgumentError,"Invalid regex match data. Got a #{match.class}") unless match.is_a?(MatchData) # Create a match ephemeral and set values from match data new_match_scope(match) end end def find_resource_type(type) # It still works fine without the type == 'class' short-cut, but it is a lot slower. return nil if ["class", "node"].include? type.to_s.downcase find_builtin_resource_type(type) || find_defined_resource_type(type) end def find_builtin_resource_type(type) Puppet::Type.type(type.to_s.downcase.to_sym) end def find_defined_resource_type(type) known_resource_types.find_definition(namespaces, type.to_s.downcase) end def method_missing(method, *args, &block) method.to_s =~ /^function_(.*)$/ name = $1 super unless name super unless Puppet::Parser::Functions.function(name) # In odd circumstances, this might not end up defined by the previous # method, so we might as well be certain. if respond_to? method send(method, *args) else raise Puppet::DevError, "Function #{name} not defined despite being loaded!" end end def resolve_type_and_titles(type, titles) raise ArgumentError, "titles must be an array" unless titles.is_a?(Array) case type.downcase when "class" # resolve the titles titles = titles.collect do |a_title| hostclass = find_hostclass(a_title) hostclass ? hostclass.name : a_title end when "node" # no-op else # resolve the type resource_type = find_resource_type(type) type = resource_type.name if resource_type end return [type, titles] end private def extend_with_functions_module root = Puppet.lookup(:root_environment) extend Puppet::Parser::Functions.environment_module(root) extend Puppet::Parser::Functions.environment_module(environment) if environment != root end end diff --git a/lib/puppet/pops.rb b/lib/puppet/pops.rb index 63ccf78ef..7aa812b59 100644 --- a/lib/puppet/pops.rb +++ b/lib/puppet/pops.rb @@ -1,98 +1,99 @@ module Puppet 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/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/runtime3_support' require 'puppet/pops/evaluator/evaluator_impl' + require 'puppet/pops/evaluator/epp_evaluator' end end require 'puppet/parser/ast/pops_bridge' require 'puppet/bindings' end diff --git a/lib/puppet/pops/evaluator/closure.rb b/lib/puppet/pops/evaluator/closure.rb index be582e546..547e15181 100644 --- a/lib/puppet/pops/evaluator/closure.rb +++ b/lib/puppet/pops/evaluator/closure.rb @@ -1,52 +1,57 @@ # 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. # class Puppet::Pops::Evaluator::Closure 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 def puppet_lambda() true end # compatible with 3x AST::Lambda def call(scope, *args) @evaluator.call(self, args, @enclosing_scope) 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) + end + # incompatible with 3x except that it is an array of the same size 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? } end def parameter_names @model.parameters.collect {|p| p.name } end end diff --git a/lib/puppet/pops/evaluator/epp_evaluator.rb b/lib/puppet/pops/evaluator/epp_evaluator.rb new file mode 100644 index 000000000..f8b341868 --- /dev/null +++ b/lib/puppet/pops/evaluator/epp_evaluator.rb @@ -0,0 +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 + raise ArgumentError, "inline_epp(): the first argument must be a String with the epp source text, got a #{file.class}" + end + + # Parse and validate the source + parser = Puppet::Pops::Parser::EvaluatingParser::EvaluatingEppParser.new + begin + 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 + 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}" + require 'debugger'; debugger + 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) + 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 + 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 + end + evaluated_result = parser.closure(body, closure_scope).call_by_name(scope, template_args, spill_over) + 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 ArgumentException, "#{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 diff --git a/lib/puppet/pops/evaluator/evaluator_impl.rb b/lib/puppet/pops/evaluator/evaluator_impl.rb index d2e1eee74..07be2a25a 100644 --- a/lib/puppet/pops/evaluator/evaluator_impl.rb +++ b/lib/puppet/pops/evaluator/evaluator_impl.rb @@ -1,1057 +1,1069 @@ 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 StandardError => e if e.is_a? Puppet::ParseError 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'). # # 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.size > parameters.size - raise ArgumentError, "Too many arguments: #{args.size} for #{parameters.size}" + 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 # associate values with parameters merged = parameters.zip(args) # calculate missing arguments missing = parameters.slice(args.size, parameters.size - args.size).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 evaluated = merged.collect do |m| # m can be one of # m = [Parameter{name => "name", value => nil], "given"] # | [Parameter{name => "name", value => Expression}, "given"] # # "given" is always an optional entry. If a parameter was provided then # the entry will be in the array, otherwise the m array will be a # single element.a = [] given_argument = m[1] argument_name = m[0].name default_expression = m[0].value value = if default_expression evaluate(default_expression, scope) else 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) 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 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 # 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 (assignable?) type. # # @example # x =~ /abc.*/ # @example # x =~ "abc.*/" # @example # y = "abc" # x =~ "${y}.*" # @example # [1,2,3] =~ Array[Integer[1,10]] # @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) if pattern.is_a?(Puppet::Pops::Types::PRegexpType) && pattern.pattern # A qualified PRegexpType, get its ruby regexp pattern = pattern.regexp else # evaluate as instance? matched = @@type_calculator.instance?(pattern, left) # convert match result to Boolean true, or false return o.operator == :'=~' ? !!matched : !matched end 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 # @return [Array] with the evaluated content # def eval_LiteralList o, scope o.values.collect {|expr| evaluate(expr, 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| the_default = co.then_expr if c.is_a? Puppet::Pops::Model::LiteralDefault is_match?(test, evaluate(c, scope), c, scope) 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() - unless o.functor_expr.is_a? Puppet::Pops::Model::QualifiedName + 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 assert_function_available(name, o, scope) evaluated_arguments = o.arguments.collect {|arg| evaluate(arg, 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) do |result| # prevent functions that are not r-value from leaking its return value rvalue_function?(name, o, scope) ? result : nil end 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 assert_function_available(name, o, scope) evaluated_arguments = [receiver] + (o.arguments || []).collect {|arg| evaluate(arg, scope) } evaluated_arguments << Puppet::Pops::Evaluator::Closure.new(self, o.lambda, scope) if o.lambda call_function(name, evaluated_arguments, o, scope) do |result| # prevent functions that are not r-value from leaking its return value rvalue_function?(name, o, scope) ? result : nil end 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) selected = o.selectors.find do |s| candidate = evaluate(s.matching_expr, scope) candidate == :default || is_match?(test, candidate, s.matching_expr, scope) end if selected evaluate(selected.value_expr, scope) else nil 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 end diff --git a/lib/puppet/pops/issues.rb b/lib/puppet/pops/issues.rb index 0613ba3a4..8dfe5d6c0 100644 --- a/lib/puppet/pops/issues.rb +++ b/lib/puppet/pops/issues.rb @@ -1,457 +1,461 @@ # 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 "#{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 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 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 end diff --git a/lib/puppet/pops/model/factory.rb b/lib/puppet/pops/model/factory.rb index 075332bad..452f6a654 100644 --- a/lib/puppet/pops/model/factory.rb +++ b/lib/puppet/pops/model/factory.rb @@ -1,929 +1,935 @@ # 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_KeyedEntry(o, k, v) o.key = build(k) o.value = build(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 = b.current if b + 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 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 # 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? 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.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 # 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) - new(Model::EppExpression, parameters, body) + see_scope = false + params = parameters + if parameters.nil? + params = [] + see_scope = true + end + LAMBDA(params, new(Model::EppExpression, see_scope, body)) 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 # 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 memo[-1] = Puppet::Pops::Model::Factory.CALL_NAMED(name, false, expr.is_a?(Array) ? expr : [expr]) 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 # 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, parameters, body) - parameters.each {|p| o.addParameters(build(p)) } + def build_EppExpression(o, see_scope, body) + o.see_scope = see_scope 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 7b65812f1..b186aca3f 100644 --- a/lib/puppet/pops/model/model.rb +++ b/lib/puppet/pops/model/model.rb @@ -1,606 +1,606 @@ # # 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 # 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 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 # 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 < Definition - contains_many_uni 'parameters', Parameter + class EppExpression < Expression + has_attr 'see_scope', 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 2e49092ce..1c4bf19cd 100644 --- a/lib/puppet/pops/model/model_tree_dumper.rb +++ b/lib/puppet/pops/model/model_tree_dumper.rb @@ -1,377 +1,377 @@ # 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 +# 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_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_ResourceTypeDefinition o result = ["define", 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_ResourceOverrideExpression o result = ["override", do_dump(o.resources), :indent] o.operations.each do |p| result << :break << do_dump(p) end result << :dedent result end # Produces parameters as name, or (= name value) def dump_Parameter o if o.value ["=", o.name, do_dump(o.value)] else o.name 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 b9ee391ef..69f0e3555 100644 --- a/lib/puppet/pops/parser/egrammar.ra +++ b/lib/puppet/pops/parser/egrammar.ra @@ -1,743 +1,741 @@ # vim: syntax=ruby # Parser using the Pops model, expression based class Puppet::Pops::Parser::Parser token STRING DQPRE DQMID DQPOST 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 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 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] } | 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 # Aleways 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. error val[1], "All resource specifications require names" 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 { result = Factory.literal(val[0][:value]); loc result, val[0] } | dotted_name DOT NAME { result = Factory.concat(val[0], '.', val[2][:value]); loc result, val[0], val[2] } # Produces Expression, since hostname is an Expression nodeparent : nil | INHERITS hostname { result = val[1] } #---NAMES AND PARAMETERS COMMON TO SEVERAL RULES # Produces String # 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 : 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] } #--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] } #---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] } 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] } -# TODO_HEREDOC 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] } -# TODO_EPP epp_expression : EPP_START epp_parameters_list statements { result = Factory.EPP(val[1], val[2]); loc result, val[0] } epp_parameters_list - : =LOW{ result = [] } + : =LOW{ result = nil } | LPAREN RPAREN { result = [] } | LPAREN parameters endcomma RPAREN { result = val[1] } epp_render_expression : RENDER_STRING { result = Factory.RENDER_STRING(val[0][:value]); loc result, val[0] } | RENDER_EXPR expression { result = Factory.RENDER_EXPR(val[1]); loc result, val[0], val[1] } 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 452c3eda6..a799180d5 100644 --- a/lib/puppet/pops/parser/eparser.rb +++ b/lib/puppet/pops/parser/eparser.rb @@ -1,2541 +1,2541 @@ # # 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', 739) +module_eval(<<'...end egrammar.ra/module_eval...', 'egrammar.ra', 737) # Make emacs happy # Local Variables: # mode: ruby # End: ...end egrammar.ra/module_eval... ##### State transition tables begin ### clist = [ '57,59,-132,269,51,259,53,-130,79,-213,-222,126,259,126,79,125,257,125', '307,292,223,346,102,14,106,223,101,223,102,41,106,48,101,50,45,234,49', '69,65,237,43,68,46,47,-132,270,66,13,105,-130,67,-213,-222,12,105,220', '57,59,249,248,51,70,53,387,239,126,234,42,79,125,80,64,60,122,62,63', '61,126,242,14,52,125,102,241,106,41,101,48,246,50,45,247,49,69,65,290', '43,68,46,47,126,126,66,13,125,125,67,356,105,12,57,59,240,244,51,258', '53,70,243,341,259,340,313,42,79,324,230,64,60,310,62,63,341,14,340,326', '52,219,102,41,106,48,101,50,45,328,49,69,65,72,43,68,46,47,126,309,66', '13,125,306,67,57,59,12,105,266,57,59,268,291,51,70,53,385,86,85,333', '42,79,81,82,64,60,334,62,63,80,335,223,14,52,210,102,338,106,41,101', '48,290,50,45,87,49,69,65,342,43,68,46,47,344,74,66,13,186,266,67,268', '105,12,266,352,57,59,353,114,51,70,53,383,290,74,153,42,151,149,363', '64,60,284,62,63,364,57,59,14,52,57,59,283,268,41,127,48,282,50,45,367', '49,69,65,114,43,68,46,47,115,268,66,13,114,371,67,344,373,12,57,59,374', '375,51,135,53,70,133,135,376,377,133,42,111,379,380,64,60,381,62,63', '266,14,74,71,52,388,70,41,389,48,70,50,108,390,49,69,65,60,43,68,391', '60,,,66,13,,,67,,,12,57,59,,,51,,53,70,75,77,76,78,,42,,,,64,60,,62', '63,,14,,,52,,,41,,48,,50,108,,49,69,65,,43,68,,,,,66,13,,,67,,,12,57', '59,,,51,,53,70,,,,,,42,,,,64,60,,62,63,,14,,,52,,,41,,48,,50,108,,49', '69,65,,43,68,,,,,66,13,,,67,,,12,57,59,,,51,,53,70,,,,,,42,,,,64,60', ',62,63,,14,,,52,,,41,,48,,50,108,,49,69,65,,43,68,,,,,66,13,,,67,,,12', '57,59,,,51,,53,70,,,,,,42,,,,64,60,,62,63,,14,,,52,,,41,,48,,50,45,', '49,69,65,,43,68,46,47,,,66,13,,,67,,,12,57,59,,,51,,53,70,,,,,,42,,', ',64,60,,62,63,,14,,,52,,,41,,48,,50,108,,49,69,65,,43,68,,,,,66,13,', ',67,,,12,57,59,,,51,,53,70,,,,,,42,,,,64,60,,62,63,,14,,,52,,,41,,48', ',50,108,,49,69,65,,43,68,,,,,66,13,,,67,,,12,57,59,,,51,,53,70,,,,,', '42,,,,64,60,,62,63,,14,,,52,,,41,,48,,50,108,,49,69,65,,43,68,,,,,66', '13,,,67,,,12,57,59,,,51,,53,70,,,,,,42,,,,64,60,,62,63,,14,,,52,,,41', ',48,,50,108,,49,69,65,,43,68,,,,,66,13,,,67,,,12,57,59,,,51,,53,70,', ',,,,42,,,,64,60,,62,63,,14,,,52,,,41,,48,,50,108,,49,69,65,,43,68,,', ',,66,13,,,67,,,12,57,59,,,51,,53,70,,,,,,42,,,,64,60,,62,63,,14,,,52', ',,41,,48,,50,121,,49,69,65,,43,68,,,,,66,13,,,67,,,12,57,59,,,51,,53', '70,,,,,,42,,,,64,60,,62,63,,14,,,52,,,41,,48,,50,108,,49,69,65,,43,68', ',,,,66,13,,,67,,,12,57,59,,,51,,53,70,,,,,,42,,,,64,60,,62,63,,14,,', '52,,,41,,48,,50,108,,49,69,65,,43,68,,,,,66,13,,,67,,,12,57,59,,,51', ',53,70,,,,,,42,79,,,64,60,,62,63,,14,,,52,,102,41,106,48,101,50,108', ',49,69,65,,43,68,,,,,66,13,,,67,,,12,105,,57,59,,,51,70,53,288,86,85', ',42,,81,82,64,60,,62,63,80,,,14,52,57,59,,,41,,48,,50,45,87,49,69,65', ',43,68,46,47,,,66,13,,,67,,,12,57,59,,,51,138,53,70,,135,,,133,42,,', ',64,60,,62,63,,14,,,52,,,41,,48,70,50,108,,49,69,65,,43,68,,60,,,66', '13,57,59,67,,,12,57,59,,,51,140,53,70,,,,,,42,,,,64,60,,62,63,,14,,', '52,,,41,,48,135,50,108,133,49,69,65,,43,68,,,,,66,13,,,67,,,12,,70,57', '59,,,51,70,53,143,,,60,42,,,,64,60,,62,63,,,,14,52,,,,,41,,48,,50,108', ',49,69,65,,43,68,,,,,66,13,,,67,,,12,57,59,,,51,,53,70,,,,,,42,79,,', '64,60,,62,63,,14,,,52,,102,41,106,48,101,50,108,,49,69,65,,43,68,,,', ',66,13,,,67,,,12,105,,57,59,,,51,70,53,294,,,,42,,81,82,64,60,,62,63', '80,,,14,52,,,,,41,,48,,50,45,,49,69,65,,43,68,46,47,,,66,13,,,67,,,12', '57,59,,,51,,53,70,,,,,,42,,,,64,60,,62,63,,14,,,52,,,41,,48,,50,45,', '49,69,65,,43,68,46,47,,,66,13,,,67,,,12,57,59,,,51,,53,70,,,,,,42,79', ',,64,60,,62,63,,14,,,52,,102,41,106,48,101,50,108,,49,69,65,,43,68,', ',,,66,13,,,67,,,12,105,,57,59,,,51,70,53,362,,,,42,,,,64,60,,62,63,80', ',,14,52,,,,,41,,48,,50,45,,49,69,65,,43,68,46,47,,,66,13,,,67,,,12,57', '59,,,51,,53,70,,,,,,42,,,,64,60,,62,63,,14,,,52,,,41,,48,,50,45,,49', '69,65,,43,68,46,47,,,66,13,,,67,,,12,57,59,,,51,,53,70,,,,,,42,,,,64', '60,,62,63,,14,,,52,,,41,,48,,50,45,,49,69,65,,43,68,46,47,,,66,13,,', '67,,,12,57,59,,,51,,53,70,,,,,,42,,,,64,60,,62,63,,14,,,52,,,41,,48', ',50,45,,49,69,65,,43,68,46,47,,,66,13,,,67,,,12,57,59,,,51,,53,70,,', ',,,42,,,,64,60,,62,63,,14,,,52,,,41,,48,,50,45,,49,69,65,,43,68,46,47', ',,66,13,,,67,,,12,57,59,,,51,,53,70,,,,,,42,,,,64,60,,62,63,,14,,,52', ',,41,,48,,50,45,,49,69,65,,43,68,46,47,,,66,13,,,67,,,12,57,59,,,51', ',53,70,,,,,,42,,,,64,60,,62,63,,14,,,52,,,41,,48,,50,45,,49,69,65,,43', '68,46,47,,,66,13,,,67,,,12,57,59,,,51,,53,70,,,,,,42,,,,64,60,,62,63', ',14,,,52,,,41,,48,,50,45,,49,69,65,,43,68,46,47,,,66,13,,,67,,,12,57', '59,,,51,,53,70,,,,,,42,,,,64,60,,62,63,,14,,,52,,,41,,48,,50,108,,49', '69,65,,43,68,,,,,66,13,,,67,,,12,57,59,,,51,,53,70,,,,,,42,,,,64,60', ',62,63,,14,,,52,,,41,,48,,50,108,,49,69,65,,43,68,,,,,66,13,,,67,,,12', '57,59,,,51,,53,70,,,,,,42,,,,64,60,,62,63,,14,,,52,,,41,,48,,50,108', ',49,69,65,,43,68,,,,,66,13,,,67,,,12,57,59,,,51,,53,70,,,,,,42,,,,64', '60,,62,63,,14,,,52,,,41,,48,,50,108,,49,69,65,,43,68,,,,,66,13,,,67', ',,12,57,59,,,51,,53,70,,,,,,42,,,,64,60,,62,63,,14,,,52,,,41,,48,,50', '108,,49,69,65,,43,68,,,,,66,13,,,67,,,12,57,59,,,51,,53,70,,,,,,42,', ',,64,60,,62,63,,14,,,52,,,41,,48,,50,108,,49,69,65,,43,68,,,,,66,13', ',,67,,,12,57,59,,,51,,53,70,,,,,,42,,,,64,60,,62,63,,14,,,52,,,41,,48', ',50,108,,49,69,65,,43,68,,,,,66,13,,,67,,,12,57,59,,,51,,53,70,,,,,', '42,,,,64,60,,62,63,,14,,,52,,,41,,48,,50,108,,49,69,65,,43,68,,,,,66', '13,,,67,,,12,57,59,,,51,,53,70,,,,,,42,,,,64,60,,62,63,,14,,,52,,,41', ',48,,50,108,,49,69,65,,43,68,,,,,66,13,,,67,,,12,57,59,,,51,,53,70,', ',,,,42,,,,64,60,,62,63,,14,,,52,,,41,,48,,50,108,,49,69,65,,43,68,,', ',,66,13,,,67,,,12,57,59,,,51,,53,70,,,,,,42,,,,64,60,,62,63,,14,,,52', ',,41,,48,,50,108,,49,69,65,,43,68,,,,,66,13,,,67,,,12,57,59,,,51,,53', '70,,,,,,42,,,,64,60,,62,63,,14,,,52,,,41,,48,,50,108,,49,69,65,,43,68', ',,,,66,13,,,67,,,12,57,59,,,51,,53,70,,,,,,42,,,,64,60,,62,63,,14,,', '52,,,41,,48,,50,108,,49,69,65,,43,68,,,,,66,13,,,67,,,12,57,59,,,51', ',53,70,,,,,,42,,,,64,60,,62,63,,14,,,52,,,41,,48,,50,108,,49,69,65,', '43,68,,,,,66,13,,,67,,,12,57,59,,,51,,53,70,,,,,,42,,,,64,60,,62,63', ',14,,,52,,,41,,48,,50,108,,49,69,65,,43,68,,,,,66,13,,,67,,,12,57,59', ',,51,,53,70,,,,,,42,,,,64,60,,62,63,,14,,,52,,,41,,48,,50,108,,49,69', '65,,43,68,,,,,66,13,,,67,,,12,57,59,,,51,,53,70,,,,,,42,,,,64,60,,62', '63,,14,,,52,,,41,,48,,50,108,,49,69,65,,43,68,,,,,66,13,,,67,,,12,57', '59,,,51,,53,70,,,,,,42,,,,64,60,,62,63,,14,,,52,,,41,,48,,50,108,,49', '69,65,,43,68,,,,,66,13,,,67,,,12,57,59,,,51,,53,70,,,,,,42,,,,64,60', ',62,63,,14,,,52,,,41,,48,,50,108,,49,69,65,,43,68,,,,,66,13,,,67,,,12', '57,59,,,51,,53,70,,,,,,42,,,,64,60,,62,63,,14,,,52,,,41,,48,,50,108', ',49,69,65,,43,68,,,,,66,13,,,67,,,12,57,59,,,51,,53,70,,,,,,42,,,,64', '60,,62,63,,14,,,52,,,41,,48,,50,108,,49,69,65,,43,68,,,,,66,13,,,67', ',,12,57,59,,,51,,53,70,,,,,,42,,,,64,60,,62,63,,14,,,52,,,41,,48,,50', '108,,49,69,65,,43,68,,,,,66,13,,,67,,,12,57,59,,,51,,53,70,,,,,,42,', ',,64,60,,62,63,,14,,,52,,,41,,48,,50,108,,49,69,65,,43,68,,,,,66,13', ',,67,,,12,,,57,59,,,51,70,53,347,,,,42,,,185,64,60,,62,63,,,,14,52,', ',,,41,,48,,50,108,,49,69,65,,43,68,,,,,66,13,,,67,,,12,57,59,,,51,,53', '70,,,,,,42,,,,64,60,,62,63,,14,,,52,,,188,205,199,206,50,200,208,201', '197,195,,190,203,,,,,66,13,209,204,202,,,12,57,59,,,51,,53,70,,,,,207', '189,,,,64,60,,62,63,,14,,,52,,,41,,48,,50,108,,49,69,65,,43,68,,,,,66', '13,,,67,,,12,57,59,,,51,,53,70,,,,,,42,,,,64,60,,62,63,,14,,,52,,,41', ',48,,50,108,,49,69,65,,43,68,,,,,66,13,,,67,,,12,57,59,,,51,,53,70,', ',,,,42,,,,64,60,,62,63,,14,,,52,,,41,,48,,50,108,,49,69,65,,43,68,,', ',,66,13,,,67,,,12,57,59,,,51,,53,70,,,,,,42,,,,64,60,,62,63,,14,,,52', ',,41,,48,,50,108,,49,69,65,,43,68,,,,,66,13,,,67,,,12,57,59,,,51,,53', '70,,,,,,42,,,,64,60,,62,63,,14,,,52,,,41,,48,,50,108,,49,69,65,,43,68', ',,,,66,13,,,67,,,12,57,59,,,51,,53,70,,,,,,42,79,,,64,60,,62,63,,14', ',,52,,102,41,106,48,101,50,108,,49,69,65,,43,68,,,,,66,13,,,67,,,12', '105,,57,59,,,51,70,53,296,,,,42,,81,82,64,60,,62,63,80,,,14,52,,,,,41', ',48,,50,45,,49,69,65,,43,68,46,47,,,66,13,,,67,,,12,57,59,,,51,,53,70', ',,,,,42,,,,64,60,,62,63,,14,217,,52,,,41,,48,,50,108,,49,69,65,,43,68', ',,,,66,13,,,67,,,12,57,59,,,51,,53,70,,,,,,42,,,,64,60,,62,63,,14,,', '52,,,41,,48,,50,108,,49,69,65,,43,68,,,,,66,13,,,67,,,12,57,59,,,51', ',53,70,,,,,,42,,,,64,60,,62,63,,14,,,52,,,41,,48,,50,108,,49,69,65,', '43,68,,,,,66,13,,,67,,,12,57,59,,,51,,53,70,,,,,,42,,,,64,60,,62,63', ',14,225,,52,,,41,,48,,50,108,,49,69,65,,43,68,,,,,66,13,,,67,,,12,57', '59,,,51,,53,70,,,,,,42,,,,64,60,,62,63,,14,,,52,,,41,,48,,50,108,,49', '69,65,,43,68,,,,,66,13,,,67,,,12,57,59,,,51,316,53,70,,,,,,42,,,,64', '60,,62,63,,14,,,52,,,41,,48,,50,108,,49,69,65,,43,68,,,,,66,13,,,67', ',,12,57,59,,,51,,53,70,,,,,,42,,,,64,60,,62,63,,14,,,52,,,41,,48,,50', '108,,49,69,65,,43,68,,,,,66,13,,,67,,,12,57,59,,,51,315,53,70,,,,,,42', ',,,64,60,,62,63,,14,,,52,,,41,,48,,50,108,,49,69,65,,43,68,,,,,66,13', ',,67,,,12,57,59,,,51,,53,70,,,,,,42,,,,64,60,,62,63,,14,,,52,,,41,,48', ',50,108,,49,69,65,,43,68,,,,,66,13,,,67,,,12,57,59,,,51,,53,70,,,,,', '42,,,,64,60,,62,63,,14,,,52,,,41,,48,,50,108,,49,69,65,,43,68,,,,,66', '13,,,67,,,12,57,59,,,51,,53,70,,,,,,42,79,,,64,60,,62,63,,14,,,52,,102', '41,106,48,101,50,108,,49,69,65,,43,68,,,,,66,13,,,67,,,12,105,,57,59', ',,51,70,53,318,,,,42,,81,82,64,60,,62,63,80,,,14,52,,,,,41,,48,,50,108', ',49,69,65,,43,68,,,,,66,13,,,67,,,12,57,59,,,51,,53,70,,,,,,42,,,,64', '60,,62,63,,14,,,52,,,41,,48,,50,108,,49,69,65,,43,68,,,,,66,13,,,67', ',,12,57,59,,,51,,53,70,,,,,,42,,,,64,60,,62,63,,14,,,52,,,188,205,199', '206,50,200,208,201,197,195,,190,203,,,,,66,13,209,204,202,,,12,,,,,', ',,70,,,,,207,189,,,,64,60,79,62,63,,,,,52,,98,99,100,95,90,102,,106', ',101,,,91,93,92,94,,,,,,,,,,,,,,,,105,,,,97,96,,,83,84,86,85,88,89,', '81,82,79,,229,,,80,,,,98,99,100,95,90,102,,106,,101,,87,91,93,92,94', ',,,,,,,,,,,,,,,105,,,,97,96,,,83,84,86,85,88,89,,81,82,79,,228,,,80', ',,,98,99,100,95,90,102,,106,,101,,87,91,93,92,94,,,,,,,,,,,,,,,,105', ',,,97,96,,,83,84,86,85,88,89,,81,82,,79,,,,80,245,,,,98,99,100,95,90', '102,,106,,101,87,,91,93,92,94,,,,,,,,,,,,,,,,105,,,,97,96,,,83,84,86', '85,88,89,,81,82,79,,227,,,80,,,,98,99,100,95,90,102,,106,,101,,87,91', '93,92,94,,,,,,,,,,,,,,,,105,,,,97,96,,,83,84,86,85,88,89,,81,82,79,', ',,,80,,,,98,99,100,95,90,102,,106,,101,,87,91,93,92,94,,,,,,,,,,,,,', ',,105,,,,97,96,,,83,84,86,85,88,89,,81,82,79,,226,,,80,,,,98,99,100', '95,90,102,,106,,101,,87,91,93,92,94,,,,,,,,,,,,,,,,105,,,,97,96,,,83', '84,86,85,88,89,,81,82,,79,,,,80,,,,,98,99,100,95,90,102,,106,,101,87', '215,91,93,92,94,,,,,,,,,,,,,,,,105,,,,97,96,,,83,84,86,85,88,89,,81', '82,79,,,,,80,,,,98,99,100,95,90,102,,106,,101,,87,91,93,92,94,,,,,,', ',,,,,,,,,105,,,,97,96,,,83,84,86,85,88,89,,81,82,79,,,,,80,,,,98,99', '100,95,90,102,,106,,101,,87,91,93,92,94,,,,,,,,,,,,,,,,105,,,,97,96', ',,83,84,86,85,88,89,,81,82,79,,,,,80,,,,98,99,100,95,90,102,,106,,101', ',87,91,93,92,94,,,,,,,,,,,,,,,,105,,,,97,96,,,83,84,86,85,88,89,,81', '82,79,,,,,80,,,,98,99,100,95,90,102,,106,,101,,87,91,93,92,94,,,,,,', ',,,,,,,,,105,,,,97,96,,,83,84,86,85,88,89,,81,82,79,,,,,80,,,,98,99', '100,95,90,102,,106,,101,,87,91,93,92,94,,,,,,,,,,,,,,,,105,,,,97,96', ',,83,84,86,85,88,89,,81,82,79,,,,,80,,,,98,99,100,95,90,102,,106,,101', ',87,91,93,92,94,,,,,,,,,,,,,,,,105,,,,97,96,,,83,84,86,85,88,89,,81', '82,79,,,,,80,,,,98,99,100,95,90,102,,106,,101,,87,91,93,92,94,,,,,,', ',,,,,,,,,105,,,,97,96,,,83,84,86,85,88,89,,81,82,79,,,,,80,,,,98,99', '100,95,90,102,,106,,101,,87,91,93,92,94,,,,,,,,,,,,,,,,105,,,,97,96', ',,83,84,86,85,88,89,,81,82,79,,,,,80,,,,98,99,100,95,90,102,264,106', ',101,,87,91,93,92,94,,,,,,,,,,,,,,,,105,,,,97,96,,,83,84,86,85,88,89', ',81,82,79,,103,,,80,,,,98,99,100,95,90,102,,106,,101,,87,91,93,92,94', ',,,,,,,,,,,,,,,105,,,,97,96,,,83,84,86,85,88,89,,81,82,,79,,,,80,260', ',,,98,99,100,95,90,102,,106,79,101,87,,91,93,92,94,,,,,,,102,,106,,101', ',,,,105,,,,97,96,,,83,84,86,85,88,89,,81,82,105,,,79,,80,,,83,84,86', '85,,,,81,82,102,,106,87,101,80,,,,79,,,,,,,,,,,87,,,102,,106,105,101', ',79,,,,,83,84,86,85,,,,81,82,102,,106,,101,80,105,,,,,,,,83,84,86,85', '88,89,87,81,82,,,,105,,80,,,79,,,83,84,86,85,88,89,,81,82,87,90,102', ',106,80,101,,79,91,,,,,,,,,,,87,90,102,,106,,101,,105,91,,,,,,,83,84', '86,85,88,89,,81,82,,,,105,,80,,,79,,,83,84,86,85,88,89,,81,82,87,90', '102,,106,80,101,,79,91,,,,,,,,,,,87,90,102,,106,,101,,105,91,,,,,,,83', '84,86,85,88,89,,81,82,,,,105,,80,,,,,79,83,84,86,85,88,89,,81,82,87', ',95,90,102,80,106,,101,,79,91,93,92,94,,,,,,87,,95,90,102,,106,,101', ',105,91,93,92,94,,,,83,84,86,85,88,89,,81,82,,,,105,,80,,,96,,,83,84', '86,85,88,89,,81,82,87,79,,,,80,,,,,98,99,100,95,90,102,,106,,101,87', ',91,93,92,94,,,,,,,,,,,,,,,,105,,,,97,96,,,83,84,86,85,88,89,,81,82', '79,,,,,80,,,,98,99,100,95,90,102,,106,,101,,87,91,93,92,94,,,,,,,,,', ',,,,,,105,,,,97,96,,,83,84,86,85,88,89,,81,82,79,,,,,80,,,,98,99,100', '95,90,102,,106,,101,,87,91,93,92,94,,,,,,,,,,,,,,,,105,,,,97,96,,,83', '84,86,85,88,89,,81,82,,,,,,80,278,205,277,206,,275,208,279,273,272,', '274,276,,87,,,,,209,204,280,278,205,277,206,,275,208,279,273,272,,274', '276,,,207,281,,,209,204,280,278,205,277,206,,275,208,279,273,272,,274', '276,,,207,281,,,209,204,280,,,,,,,,,,,,,,,,207,281' ] racc_action_table = arr = ::Array.new(6060, 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,197,198,0,224,0,195,163,203,202,200,297,108,161,200,151,108,234', '224,114,297,163,0,163,151,163,234,161,0,161,0,161,0,0,128,0,0,0,129', '0,0,0,0,197,198,0,0,163,195,0,203,202,0,161,114,374,374,147,147,374', '0,374,374,129,199,123,0,107,199,163,0,0,45,0,0,0,48,137,374,0,48,107', '137,107,374,107,374,142,374,374,142,374,374,374,256,374,374,374,374', '306,45,374,374,306,45,374,306,107,374,5,5,131,139,5,160,5,374,139,338', '160,338,240,374,164,261,121,374,374,236,374,374,294,5,294,265,374,113', '164,5,164,5,164,5,5,267,5,5,5,5,5,5,5,5,121,235,5,5,121,232,5,149,149', '5,164,231,373,373,271,223,373,5,373,373,164,164,285,5,109,164,164,5', '5,287,5,5,164,289,290,373,5,104,109,293,109,373,109,373,221,373,373', '164,373,373,373,295,373,373,373,373,296,154,373,373,102,300,373,301', '109,373,302,303,371,371,304,217,371,373,371,371,308,73,71,373,61,60', '321,373,373,216,373,373,323,49,49,371,373,239,239,214,325,371,46,371', '212,371,371,332,371,371,371,333,371,371,371,371,40,192,371,371,39,341', '371,342,344,371,185,185,345,349,185,49,185,371,49,239,350,351,239,371', '38,357,358,371,371,361,371,371,191,185,6,1,371,378,49,185,382,185,239', '185,185,384,185,185,185,49,185,185,386,239,,,185,185,,,185,,,185,12', '12,,,12,,12,185,8,8,8,8,,185,,,,185,185,,185,185,,12,,,185,,,12,,12', ',12,12,,12,12,12,,12,12,,,,,12,12,,,12,,,12,13,13,,,13,,13,12,,,,,,12', ',,,12,12,,12,12,,13,,,12,,,13,,13,,13,13,,13,13,13,,13,13,,,,,13,13', ',,13,,,13,14,14,,,14,,14,13,,,,,,13,,,,13,13,,13,13,,14,,,13,,,14,,14', ',14,14,,14,14,14,,14,14,,,,,14,14,,,14,,,14,353,353,,,353,,353,14,,', ',,,14,,,,14,14,,14,14,,353,,,14,,,353,,353,,353,353,,353,353,353,,353', '353,353,353,,,353,353,,,353,,,353,340,340,,,340,,340,353,,,,,,353,,', ',353,353,,353,353,,340,,,353,,,340,,340,,340,340,,340,340,340,,340,340', ',,,,340,340,,,340,,,340,188,188,,,188,,188,340,,,,,,340,,,,340,340,', '340,340,,188,,,340,,,188,,188,,188,188,,188,188,188,,188,188,,,,,188', '188,,,188,,,188,41,41,,,41,,41,188,,,,,,188,,,,188,188,,188,188,,41', ',,188,,,41,,41,,41,41,,41,41,41,,41,41,,,,,41,41,,,41,,,41,42,42,,,42', ',42,41,,,,,,41,,,,41,41,,41,41,,42,,,41,,,42,,42,,42,42,,42,42,42,,42', '42,,,,,42,42,,,42,,,42,43,43,,,43,,43,42,,,,,,42,,,,42,42,,42,42,,43', ',,42,,,43,,43,,43,43,,43,43,43,,43,43,,,,,43,43,,,43,,,43,44,44,,,44', ',44,43,,,,,,43,,,,43,43,,43,43,,44,,,43,,,44,,44,,44,44,,44,44,44,,44', '44,,,,,44,44,,,44,,,44,189,189,,,189,,189,44,,,,,,44,,,,44,44,,44,44', ',189,,,44,,,189,,189,,189,189,,189,189,189,,189,189,,,,,189,189,,,189', ',,189,190,190,,,190,,190,189,,,,,,189,,,,189,189,,189,189,,190,,,189', ',,190,,190,,190,190,,190,190,190,,190,190,,,,,190,190,,,190,,,190,324', '324,,,324,,324,190,,,,,,190,165,,,190,190,,190,190,,324,,,190,,165,324', '165,324,165,324,324,,324,324,324,,324,324,,,,,324,324,,,324,,,324,165', ',219,219,,,219,324,219,219,165,165,,324,,165,165,324,324,,324,324,165', ',,219,324,237,237,,,219,,219,,219,219,165,219,219,219,,219,219,219,219', ',,219,219,,,219,,,219,51,51,,,51,51,51,219,,237,,,237,219,,,,219,219', ',219,219,,51,,,219,,,51,,51,237,51,51,,51,51,51,,51,51,,237,,,51,51', '201,201,51,,,51,52,52,,,52,52,52,51,,,,,,51,,,,51,51,,51,51,,52,,,51', ',,52,,52,201,52,52,201,52,52,52,,52,52,,,,,52,52,,,52,,,52,,201,53,53', ',,53,52,53,53,,,201,52,,,,52,52,,52,52,,,,53,52,,,,,53,,53,,53,53,,53', '53,53,,53,53,,,,,53,53,,,53,,,53,58,58,,,58,,58,53,,,,,,53,168,,,53', '53,,53,53,,58,,,53,,168,58,168,58,168,58,58,,58,58,58,,58,58,,,,,58', '58,,,58,,,58,168,,226,226,,,226,58,226,226,,,,58,,168,168,58,58,,58', '58,168,,,226,58,,,,,226,,226,,226,226,,226,226,226,,226,226,226,226', ',,226,226,,,226,,,226,150,150,,,150,,150,226,,,,,,226,,,,226,226,,226', '226,,150,,,226,,,150,,150,,150,150,,150,150,150,,150,150,150,150,,,150', '150,,,150,,,150,63,63,,,63,,63,150,,,,,,150,162,,,150,150,,150,150,', '63,,,150,,162,63,162,63,162,63,63,,63,63,63,,63,63,,,,,63,63,,,63,,', '63,162,,310,310,,,310,63,310,310,,,,63,,,,63,63,,63,63,162,,,310,63', ',,,,310,,310,,310,310,,310,310,310,,310,310,310,310,,,310,310,,,310', ',,310,72,72,,,72,,72,310,,,,,,310,,,,310,310,,310,310,,72,,,310,,,72', ',72,,72,72,,72,72,72,,72,72,72,72,,,72,72,,,72,,,72,309,309,,,309,,309', '72,,,,,,72,,,,72,72,,72,72,,309,,,72,,,309,,309,,309,309,,309,309,309', ',309,309,309,309,,,309,309,,,309,,,309,74,74,,,74,,74,309,,,,,,309,', ',,309,309,,309,309,,74,,,309,,,74,,74,,74,74,,74,74,74,,74,74,74,74', ',,74,74,,,74,,,74,75,75,,,75,,75,74,,,,,,74,,,,74,74,,74,74,,75,,,74', ',,75,,75,,75,75,,75,75,75,,75,75,75,75,,,75,75,,,75,,,75,76,76,,,76', ',76,75,,,,,,75,,,,75,75,,75,75,,76,,,75,,,76,,76,,76,76,,76,76,76,,76', '76,76,76,,,76,76,,,76,,,76,77,77,,,77,,77,76,,,,,,76,,,,76,76,,76,76', ',77,,,76,,,77,,77,,77,77,,77,77,77,,77,77,77,77,,,77,77,,,77,,,77,78', '78,,,78,,78,77,,,,,,77,,,,77,77,,77,77,,78,,,77,,,78,,78,,78,78,,78', '78,78,,78,78,78,78,,,78,78,,,78,,,78,79,79,,,79,,79,78,,,,,,78,,,,78', '78,,78,78,,79,,,78,,,79,,79,,79,79,,79,79,79,,79,79,,,,,79,79,,,79,', ',79,80,80,,,80,,80,79,,,,,,79,,,,79,79,,79,79,,80,,,79,,,80,,80,,80', '80,,80,80,80,,80,80,,,,,80,80,,,80,,,80,81,81,,,81,,81,80,,,,,,80,,', ',80,80,,80,80,,81,,,80,,,81,,81,,81,81,,81,81,81,,81,81,,,,,81,81,,', '81,,,81,82,82,,,82,,82,81,,,,,,81,,,,81,81,,81,81,,82,,,81,,,82,,82', ',82,82,,82,82,82,,82,82,,,,,82,82,,,82,,,82,83,83,,,83,,83,82,,,,,,82', ',,,82,82,,82,82,,83,,,82,,,83,,83,,83,83,,83,83,83,,83,83,,,,,83,83', ',,83,,,83,84,84,,,84,,84,83,,,,,,83,,,,83,83,,83,83,,84,,,83,,,84,,84', ',84,84,,84,84,84,,84,84,,,,,84,84,,,84,,,84,85,85,,,85,,85,84,,,,,,84', ',,,84,84,,84,84,,85,,,84,,,85,,85,,85,85,,85,85,85,,85,85,,,,,85,85', ',,85,,,85,86,86,,,86,,86,85,,,,,,85,,,,85,85,,85,85,,86,,,85,,,86,,86', ',86,86,,86,86,86,,86,86,,,,,86,86,,,86,,,86,87,87,,,87,,87,86,,,,,,86', ',,,86,86,,86,86,,87,,,86,,,87,,87,,87,87,,87,87,87,,87,87,,,,,87,87', ',,87,,,87,88,88,,,88,,88,87,,,,,,87,,,,87,87,,87,87,,88,,,87,,,88,,88', ',88,88,,88,88,88,,88,88,,,,,88,88,,,88,,,88,89,89,,,89,,89,88,,,,,,88', ',,,88,88,,88,88,,89,,,88,,,89,,89,,89,89,,89,89,89,,89,89,,,,,89,89', ',,89,,,89,90,90,,,90,,90,89,,,,,,89,,,,89,89,,89,89,,90,,,89,,,90,,90', ',90,90,,90,90,90,,90,90,,,,,90,90,,,90,,,90,91,91,,,91,,91,90,,,,,,90', ',,,90,90,,90,90,,91,,,90,,,91,,91,,91,91,,91,91,91,,91,91,,,,,91,91', ',,91,,,91,92,92,,,92,,92,91,,,,,,91,,,,91,91,,91,91,,92,,,91,,,92,,92', ',92,92,,92,92,92,,92,92,,,,,92,92,,,92,,,92,93,93,,,93,,93,92,,,,,,92', ',,,92,92,,92,92,,93,,,92,,,93,,93,,93,93,,93,93,93,,93,93,,,,,93,93', ',,93,,,93,94,94,,,94,,94,93,,,,,,93,,,,93,93,,93,93,,94,,,93,,,94,,94', ',94,94,,94,94,94,,94,94,,,,,94,94,,,94,,,94,95,95,,,95,,95,94,,,,,,94', ',,,94,94,,94,94,,95,,,94,,,95,,95,,95,95,,95,95,95,,95,95,,,,,95,95', ',,95,,,95,96,96,,,96,,96,95,,,,,,95,,,,95,95,,95,95,,96,,,95,,,96,,96', ',96,96,,96,96,96,,96,96,,,,,96,96,,,96,,,96,97,97,,,97,,97,96,,,,,,96', ',,,96,96,,96,96,,97,,,96,,,97,,97,,97,97,,97,97,97,,97,97,,,,,97,97', ',,97,,,97,98,98,,,98,,98,97,,,,,,97,,,,97,97,,97,97,,98,,,97,,,98,,98', ',98,98,,98,98,98,,98,98,,,,,98,98,,,98,,,98,99,99,,,99,,99,98,,,,,,98', ',,,98,98,,98,98,,99,,,98,,,99,,99,,99,99,,99,99,99,,99,99,,,,,99,99', ',,99,,,99,100,100,,,100,,100,99,,,,,,99,,,,99,99,,99,99,,100,,,99,,', '100,,100,,100,100,,100,100,100,,100,100,,,,,100,100,,,100,,,100,101', '101,,,101,,101,100,,,,,,100,,,,100,100,,100,100,,101,,,100,,,101,,101', ',101,101,,101,101,101,,101,101,,,,,101,101,,,101,,,101,,,298,298,,,298', '101,298,298,,,,101,,,101,101,101,,101,101,,,,298,101,,,,,298,,298,,298', '298,,298,298,298,,298,298,,,,,298,298,,,298,,,298,103,103,,,103,,103', '298,,,,,,298,,,,298,298,,298,298,,103,,,298,,,103,103,103,103,103,103', '103,103,103,103,,103,103,,,,,103,103,103,103,103,,,103,291,291,,,291', ',291,103,,,,,103,103,,,,103,103,,103,103,,291,,,103,,,291,,291,,291', '291,,291,291,291,,291,291,,,,,291,291,,,291,,,291,105,105,,,105,,105', '291,,,,,,291,,,,291,291,,291,291,,105,,,291,,,105,,105,,105,105,,105', '105,105,,105,105,,,,,105,105,,,105,,,105,106,106,,,106,,106,105,,,,', ',105,,,,105,105,,105,105,,106,,,105,,,106,,106,,106,106,,106,106,106', ',106,106,,,,,106,106,,,106,,,106,284,284,,,284,,284,106,,,,,,106,,,', '106,106,,106,106,,284,,,106,,,284,,284,,284,284,,284,284,284,,284,284', ',,,,284,284,,,284,,,284,270,270,,,270,,270,284,,,,,,284,,,,284,284,', '284,284,,270,,,284,,,270,,270,,270,270,,270,270,270,,270,270,,,,,270', '270,,,270,,,270,269,269,,,269,,269,270,,,,,,270,167,,,270,270,,270,270', ',269,,,270,,167,269,167,269,167,269,269,,269,269,269,,269,269,,,,,269', '269,,,269,,,269,167,,227,227,,,227,269,227,227,,,,269,,167,167,269,269', ',269,269,167,,,227,269,,,,,227,,227,,227,227,,227,227,227,,227,227,227', '227,,,227,227,,,227,,,227,111,111,,,111,,111,227,,,,,,227,,,,227,227', ',227,227,,111,111,,227,,,111,,111,,111,111,,111,111,111,,111,111,,,', ',111,111,,,111,,,111,266,266,,,266,,266,111,,,,,,111,,,,111,111,,111', '111,,266,,,111,,,266,,266,,266,266,,266,266,266,,266,266,,,,,266,266', ',,266,,,266,260,260,,,260,,260,266,,,,,,266,,,,266,266,,266,266,,260', ',,266,,,260,,260,,260,260,,260,260,260,,260,260,,,,,260,260,,,260,,', '260,115,115,,,115,,115,260,,,,,,260,,,,260,260,,260,260,,115,115,,260', ',,115,,115,,115,115,,115,115,115,,115,115,,,,,115,115,,,115,,,115,228', '228,,,228,,228,115,,,,,,115,,,,115,115,,115,115,,228,,,115,,,228,,228', ',228,228,,228,228,228,,228,228,,,,,228,228,,,228,,,228,243,243,,,243', '243,243,228,,,,,,228,,,,228,228,,228,228,,243,,,228,,,243,,243,,243', '243,,243,243,243,,243,243,,,,,243,243,,,243,,,243,230,230,,,230,,230', '243,,,,,,243,,,,243,243,,243,243,,230,,,243,,,230,,230,,230,230,,230', '230,230,,230,230,,,,,230,230,,,230,,,230,241,241,,,241,241,241,230,', ',,,,230,,,,230,230,,230,230,,241,,,230,,,241,,241,,241,241,,241,241', '241,,241,241,,,,,241,241,,,241,,,241,259,259,,,259,,259,241,,,,,,241', ',,,241,241,,241,241,,259,,,241,,,259,,259,,259,259,,259,259,259,,259', '259,,,,,259,259,,,259,,,259,122,122,,,122,,122,259,,,,,,259,,,,259,259', ',259,259,,122,,,259,,,122,,122,,122,122,,122,122,122,,122,122,,,,,122', '122,,,122,,,122,252,252,,,252,,252,122,,,,,,122,166,,,122,122,,122,122', ',252,,,122,,166,252,166,252,166,252,252,,252,252,252,,252,252,,,,,252', '252,,,252,,,252,166,,247,247,,,247,252,247,247,,,,252,,166,166,252,252', ',252,252,166,,,247,252,,,,,247,,247,,247,247,,247,247,247,,247,247,', ',,,247,247,,,247,,,247,245,245,,,245,,245,247,,,,,,247,,,,247,247,,247', '247,,245,,,247,,,245,,245,,245,245,,245,245,245,,245,245,,,,,245,245', ',,245,,,245,229,229,,,229,,229,245,,,,,,245,,,,245,245,,245,245,,229', ',,245,,,229,229,229,229,229,229,229,229,229,229,,229,229,,,,,229,229', '229,229,229,,,229,,,,,,,,229,,,,,229,229,,,,229,229,136,229,229,,,,', '229,,136,136,136,136,136,136,,136,,136,,,136,136,136,136,,,,,,,,,,,', ',,,,136,,,,136,136,,,136,136,136,136,136,136,,136,136,120,,120,,,136', ',,,120,120,120,120,120,120,,120,,120,,136,120,120,120,120,,,,,,,,,,', ',,,,,120,,,,120,120,,,120,120,120,120,120,120,,120,120,119,,119,,,120', ',,,119,119,119,119,119,119,,119,,119,,120,119,119,119,119,,,,,,,,,,', ',,,,,119,,,,119,119,,,119,119,119,119,119,119,,119,119,,141,,,,119,141', ',,,141,141,141,141,141,141,,141,,141,119,,141,141,141,141,,,,,,,,,,', ',,,,,141,,,,141,141,,,141,141,141,141,141,141,,141,141,118,,118,,,141', ',,,118,118,118,118,118,118,,118,,118,,141,118,118,118,118,,,,,,,,,,', ',,,,,118,,,,118,118,,,118,118,118,118,118,118,,118,118,145,,,,,118,', ',,145,145,145,145,145,145,,145,,145,,118,145,145,145,145,,,,,,,,,,,', ',,,,145,,,,145,145,,,145,145,145,145,145,145,,145,145,116,,116,,,145', ',,,116,116,116,116,116,116,,116,,116,,145,116,116,116,116,,,,,,,,,,', ',,,,,116,,,,116,116,,,116,116,116,116,116,116,,116,116,,110,,,,116,', ',,,110,110,110,110,110,110,,110,,110,116,110,110,110,110,110,,,,,,,', ',,,,,,,,110,,,,110,110,,,110,110,110,110,110,110,,110,110,314,,,,,110', ',,,314,314,314,314,314,314,,314,,314,,110,314,314,314,314,,,,,,,,,,', ',,,,,314,,,,314,314,,,314,314,314,314,314,314,,314,314,317,,,,,314,', ',,317,317,317,317,317,317,,317,,317,,314,317,317,317,317,,,,,,,,,,,', ',,,,317,,,,317,317,,,317,317,317,317,317,317,,317,317,152,,,,,317,,', ',152,152,152,152,152,152,,152,,152,,317,152,152,152,152,,,,,,,,,,,,', ',,,152,,,,152,152,,,152,152,152,152,152,152,,152,152,322,,,,,152,,,', '322,322,322,322,322,322,,322,,322,,152,322,322,322,322,,,,,,,,,,,,,', ',,322,,,,322,322,,,322,322,322,322,322,322,,322,322,211,,,,,322,,,,211', '211,211,211,211,211,,211,,211,,322,211,211,211,211,,,,,,,,,,,,,,,,211', ',,,211,211,,,211,211,211,211,211,211,,211,211,330,,,,,211,,,,330,330', '330,330,330,330,,330,,330,,211,330,330,330,330,,,,,,,,,,,,,,,,330,,', ',330,330,,,330,330,330,330,330,330,,330,330,331,,,,,330,,,,331,331,331', '331,331,331,,331,,331,,330,331,331,331,331,,,,,,,,,,,,,,,,331,,,,331', '331,,,331,331,331,331,331,331,,331,331,337,,,,,331,,,,337,337,337,337', '337,337,,337,,337,,331,337,337,337,337,,,,,,,,,,,,,,,,337,,,,337,337', ',,337,337,337,337,337,337,,337,337,187,,,,,337,,,,187,187,187,187,187', '187,187,187,,187,,337,187,187,187,187,,,,,,,,,,,,,,,,187,,,,187,187', ',,187,187,187,187,187,187,,187,187,11,,11,,,187,,,,11,11,11,11,11,11', ',11,,11,,187,11,11,11,11,,,,,,,,,,,,,,,,11,,,,11,11,,,11,11,11,11,11', '11,,11,11,,182,,,,11,182,,,,182,182,182,182,182,182,,182,169,182,11', ',182,182,182,182,,,,,,,169,,169,,169,,,,,182,,,,182,182,,,182,182,182', '182,182,182,,182,182,169,,,170,,182,,,169,169,169,169,,,,169,169,170', ',170,182,170,169,,,,171,,,,,,,,,,,169,,,171,,171,170,171,,172,,,,,170', '170,170,170,,,,170,170,172,,172,,172,170,171,,,,,,,,171,171,171,171', '171,171,170,171,171,,,,172,,171,,,173,,,172,172,172,172,172,172,,172', '172,171,173,173,,173,172,173,,174,173,,,,,,,,,,,172,174,174,,174,,174', ',173,174,,,,,,,173,173,173,173,173,173,,173,173,,,,174,,173,,,175,,', '174,174,174,174,174,174,,174,174,173,175,175,,175,174,175,,176,175,', ',,,,,,,,,174,176,176,,176,,176,,175,176,,,,,,,175,175,175,175,175,175', ',175,175,,,,176,,175,,,,,177,176,176,176,176,176,176,,176,176,175,,177', '177,177,176,177,,177,,178,177,177,177,177,,,,,,176,,178,178,178,,178', ',178,,177,178,178,178,178,,,,177,177,177,177,177,177,,177,177,,,,178', ',177,,,178,,,178,178,178,178,178,178,,178,178,177,181,,,,178,,,,,181', '181,181,181,181,181,,181,,181,178,,181,181,181,181,,,,,,,,,,,,,,,,181', ',,,181,181,,,181,181,181,181,181,181,,181,181,180,,,,,181,,,,180,180', '180,180,180,180,,180,,180,,181,180,180,180,180,,,,,,,,,,,,,,,,180,,', ',180,180,,,180,180,180,180,180,180,,180,180,179,,,,,180,,,,179,179,179', '179,179,179,,179,,179,,180,179,179,179,179,,,,,,,,,,,,,,,,179,,,,179', '179,,,179,179,179,179,179,179,,179,179,,,,,,179,210,210,210,210,,210', '210,210,210,210,,210,210,,179,,,,,210,210,210,268,268,268,268,,268,268', '268,268,268,,268,268,,,210,210,,,268,268,268,263,263,263,263,,263,263', '263,263,263,,263,263,,,268,268,,,263,263,263,,,,,,,,,,,,,,,,263,263' ] racc_action_check = arr = ::Array.new(6060, 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, 301, nil, nil, nil, 108, 288, nil, 274, nil, nil, 5378, 328, 382, 436, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 265, 200, 241, 652, 706, 760, 814, 65, 208, nil, 41, 241, nil, 1086, 1140, 1196, nil, nil, nil, nil, 1250, nil, 160, 209, nil, 1414, nil, nil, nil, nil, nil, nil, nil, 232, 1524, 219, 1632, 1686, 1740, 1794, 1848, 1902, 1956, 2010, 2064, 2118, 2172, 2226, 2280, 2334, 2388, 2442, 2496, 2550, 2604, 2658, 2712, 2766, 2820, 2874, 2928, 2982, 3036, 3090, 174, 3200, 183, 3308, 3362, 62, -23, 172, 4808, 3634, nil, 129, -15, 3796, 4750, nil, 4636, 4521, 4464, 118, 4120, 41, nil, nil, nil, nil, 10, 27, nil, 92, nil, nil, nil, nil, 4407, 71, nil, 106, nil, 4579, 79, nil, nil, 4693, nil, 54, nil, 159, 1360, -10, 4979, nil, 199, nil, nil, nil, nil, nil, 108, 8, 1424, 2, 118, 986, 4184, 3534, 1260, 5453, 5496, 5519, 5539, 5584, 5604, 5649, 5669, 5716, 5736, 5908, 5851, 5794, 5436, nil, nil, 274, nil, 5321, 598, 868, 922, 257, 255, nil, nil, -4, nil, -9, -8, 29, -25, 1134, -1, -2, nil, nil, nil, nil, nil, nil, 5946, 5093, 207, nil, 226, nil, 227, 155, nil, 1032, nil, 186, nil, 154, -7, nil, 1306, 3580, 3850, 4338, 3958, 124, 122, nil, -8, 147, 121, 1057, nil, 245, 82, 4012, nil, 3904, nil, 4284, nil, 4230, nil, nil, nil, nil, 4174, nil, nil, nil, 83, nil, nil, 4066, 3742, 113, nil, 5990, nil, 126, 3688, 136, 5968, 3524, 3470, 156, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 3416, 150, nil, 174, nil, 117, 153, 3254, nil, 184, 100, 196, 178, 0, 3146, nil, 174, 205, 179, 212, 216, nil, 64, nil, 218, 1578, 1470, nil, nil, nil, 4865, nil, nil, 4922, nil, nil, nil, 210, 5036, 233, 976, 238, nil, nil, nil, nil, 5150, 5207, 248, 191, nil, nil, nil, 5264, 87, nil, 544, 263, 241, nil, 266, 270, nil, nil, nil, 270, 277, 278, nil, 490, nil, nil, nil, 265, 283, nil, nil, 286, nil, nil, nil, nil, nil, nil, nil, nil, nil, 220, nil, 164, 54, nil, nil, nil, 294, nil, nil, nil, 297, nil, 302, nil, 309, nil, nil, nil, nil, nil ] racc_action_default = [ -224, -225, -1, -2, -3, -4, -5, -8, -10, -11, -16, -107, -225, -225, -225, -45, -46, -47, -48, -49, -50, -51, -52, -53, -54, -55, -56, -57, -58, -59, -60, -61, -62, -63, -64, -65, -66, -67, -72, -73, -77, -225, -225, -225, -225, -225, -118, -120, -225, -225, -165, -225, -225, -225, -178, -179, -180, -181, -225, -183, -225, -194, -197, -225, -199, -200, -201, -202, -203, -204, -205, -225, -225, -7, -225, -225, -225, -225, -225, -225, -225, -225, -225, -225, -225, -225, -225, -225, -225, -225, -225, -225, -225, -225, -225, -225, -225, -225, -225, -225, -225, -225, -225, -127, -122, -224, -224, -28, -225, -35, -225, -225, -74, -225, -225, -225, -225, -84, -225, -225, -225, -225, -225, -224, -137, -156, -157, -119, -224, -224, -146, -148, -149, -150, -151, -152, -43, -225, -168, -225, -171, -225, -225, -174, -175, -187, -182, -225, -190, -225, -225, -225, -198, 392, -6, -9, -12, -13, -14, -15, -225, -18, -19, -20, -21, -22, -23, -24, -25, -26, -27, -29, -30, -31, -32, -33, -34, -36, -37, -38, -39, -40, -225, -41, -102, -225, -78, -225, -217, -223, -211, -208, -206, -116, -128, -200, -131, -204, -225, -214, -212, -220, -202, -203, -210, -215, -216, -218, -219, -221, -127, -126, -225, -125, -225, -42, -206, -69, -79, -225, -82, -206, -161, -164, -225, -76, -225, -225, -225, -127, -225, -208, -224, -158, -225, -225, -225, -225, -154, -225, -225, -225, -166, -225, -169, -225, -172, -225, -184, -185, -186, -188, -225, -191, -192, -193, -206, -195, -17, -225, -225, -206, -104, -127, -115, -225, -209, -225, -207, -225, -225, -206, -130, -132, -211, -212, -213, -214, -217, -220, -222, -223, -123, -124, -207, -225, -71, -225, -81, -225, -207, -225, -75, -225, -87, -225, -93, -225, -225, -97, -208, -206, -208, -225, -225, -140, -225, -159, -206, -224, -225, -147, -155, -153, -44, -167, -170, -177, -173, -176, -189, -225, -106, -225, -207, -206, -110, -117, -111, -129, -133, -134, -225, -68, -80, -83, -162, -163, -87, -86, -225, -225, -93, -92, -225, -225, -101, -96, -98, -225, -225, -225, -113, -224, -141, -142, -143, -225, -225, -138, -139, -225, -145, -196, -103, -105, -114, -121, -70, -85, -88, -225, -91, -225, -225, -108, -109, -112, -225, -160, -135, -144, -225, -90, -225, -95, -225, -100, -136, -89, -94, -99 ] racc_goto_table = [ 2, 112, 4, 144, 107, 109, 110, 128, 146, 192, 134, 132, 191, 265, 221, 184, 343, 232, 1, 339, 358, 311, 235, 312, 299, 156, 157, 158, 159, 212, 214, 231, 267, 116, 118, 119, 120, 73, 261, 327, 263, 345, 329, 136, 136, 141, 298, 370, 218, 304, 145, 256, 354, 303, 236, 152, 285, 183, 336, 142, 155, 289, 372, 369, 378, 253, 254, 3, 251, 252, 250, 136, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 348, 187, 321, 211, 211, 262, 148, 323, 150, 136, 154, nil, nil, 136, 137, 139, nil, 332, nil, nil, 187, nil, 271, nil, nil, nil, nil, nil, 349, nil, 351, 233, nil, nil, nil, nil, 233, 238, nil, nil, 308, 301, 160, nil, 300, 302, nil, 350, nil, nil, nil, nil, nil, nil, 357, nil, 255, nil, nil, nil, nil, nil, nil, nil, 128, nil, nil, nil, 134, 132, nil, 366, nil, nil, 216, 325, nil, nil, 224, nil, nil, nil, nil, 182, nil, 286, 116, 118, 119, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 319, 134, 132, 134, 132, 320, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 287, 136, 187, 187, nil, nil, nil, 293, 295, nil, nil, nil, nil, nil, 314, 305, 314, nil, 317, 365, 141, nil, nil, nil, nil, 145, nil, nil, nil, nil, nil, nil, 314, 322, nil, nil, nil, nil, nil, 187, nil, nil, 330, 331, nil, nil, 355, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 314, nil, nil, nil, nil, nil, nil, 337, nil, nil, nil, nil, nil, nil, 136, nil, nil, nil, nil, 368, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 361, 360, nil, nil, nil, nil, 182, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 116, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 360, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 382, nil, 384, 386 ] racc_goto_check = [ 2, 39, 4, 76, 10, 10, 10, 64, 81, 56, 31, 37, 54, 55, 44, 51, 47, 65, 1, 46, 66, 72, 65, 72, 49, 8, 8, 8, 8, 60, 60, 54, 38, 10, 10, 10, 10, 6, 52, 57, 58, 50, 61, 10, 10, 10, 48, 45, 43, 68, 10, 44, 69, 55, 71, 10, 38, 13, 74, 75, 7, 38, 47, 46, 66, 77, 78, 3, 82, 83, 85, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 49, 10, 38, 10, 10, 51, 86, 38, 87, 10, 6, nil, nil, 10, 12, 12, nil, 38, nil, nil, 10, nil, 56, nil, nil, nil, nil, nil, 55, nil, 55, 4, nil, nil, nil, nil, 4, 4, nil, nil, 44, 56, 12, nil, 54, 54, nil, 38, nil, nil, nil, nil, nil, nil, 38, nil, 2, nil, nil, nil, nil, nil, nil, nil, 64, nil, nil, nil, 31, 37, nil, 38, nil, nil, 12, 56, nil, nil, 12, nil, nil, nil, nil, 10, nil, 39, 10, 10, 10, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 76, 31, 37, 31, 37, 81, 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, 51, 10, nil, nil, nil, nil, 10, nil, nil, nil, nil, nil, nil, 10, 10, nil, nil, nil, nil, nil, 10, nil, nil, 10, 10, nil, nil, 64, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 10, nil, nil, nil, nil, nil, nil, 10, nil, nil, nil, nil, nil, nil, 10, nil, nil, nil, nil, 39, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 2, 4, 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, 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, 2, nil, 2, 2 ] racc_goto_pointer = [ nil, 18, 0, 67, 2, nil, 32, -14, -50, nil, -8, nil, 57, -44, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, -39, nil, nil, nil, nil, nil, -38, -160, -38, nil, nil, nil, -65, -100, -293, -275, -280, -182, -204, -256, -86, -147, nil, -91, -178, -94, -227, -147, nil, -76, -226, nil, nil, -41, -106, -289, nil, -183, -254, nil, -75, -216, nil, -232, 6, -50, -84, -83, nil, nil, -50, -79, -78, nil, -77, 40, 41 ] racc_goto_default = [ nil, nil, 359, nil, 213, 5, 6, 7, 8, 9, 11, 10, 297, nil, 15, 38, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, nil, nil, 39, 40, 113, nil, nil, 117, nil, nil, nil, nil, nil, nil, nil, 44, nil, nil, nil, 193, nil, 104, nil, 194, 198, 196, 124, nil, nil, 123, nil, nil, 129, nil, 130, 131, 222, nil, nil, 54, 55, 56, 58, nil, nil, nil, 147, nil, nil, nil ] racc_reduce_table = [ 0, 0, :racc_error, 1, 87, :_reduce_1, 1, 87, :_reduce_2, 1, 87, :_reduce_none, 1, 88, :_reduce_4, 1, 91, :_reduce_5, 3, 91, :_reduce_6, 2, 91, :_reduce_7, 1, 92, :_reduce_8, 3, 92, :_reduce_9, 1, 93, :_reduce_none, 1, 94, :_reduce_11, 3, 94, :_reduce_12, 3, 94, :_reduce_13, 3, 94, :_reduce_14, 3, 94, :_reduce_15, 1, 96, :_reduce_none, 4, 96, :_reduce_17, 3, 96, :_reduce_18, 3, 96, :_reduce_19, 3, 96, :_reduce_20, 3, 96, :_reduce_21, 3, 96, :_reduce_22, 3, 96, :_reduce_23, 3, 96, :_reduce_24, 3, 96, :_reduce_25, 3, 96, :_reduce_26, 3, 96, :_reduce_27, 2, 96, :_reduce_28, 3, 96, :_reduce_29, 3, 96, :_reduce_30, 3, 96, :_reduce_31, 3, 96, :_reduce_32, 3, 96, :_reduce_33, 3, 96, :_reduce_34, 2, 96, :_reduce_35, 3, 96, :_reduce_36, 3, 96, :_reduce_37, 3, 96, :_reduce_38, 3, 96, :_reduce_39, 3, 96, :_reduce_40, 3, 96, :_reduce_41, 3, 96, :_reduce_42, 1, 98, :_reduce_43, 3, 98, :_reduce_44, 1, 97, :_reduce_none, 1, 101, :_reduce_none, 1, 101, :_reduce_none, 1, 101, :_reduce_none, 1, 101, :_reduce_none, 1, 101, :_reduce_none, 1, 101, :_reduce_none, 1, 101, :_reduce_none, 1, 101, :_reduce_none, 1, 101, :_reduce_none, 1, 101, :_reduce_none, 1, 101, :_reduce_none, 1, 102, :_reduce_none, 1, 102, :_reduce_none, 1, 102, :_reduce_none, 1, 102, :_reduce_none, 1, 102, :_reduce_none, 1, 102, :_reduce_none, 1, 102, :_reduce_none, 1, 102, :_reduce_none, 1, 102, :_reduce_none, 1, 118, :_reduce_66, 1, 118, :_reduce_67, 5, 100, :_reduce_68, 3, 100, :_reduce_69, 6, 100, :_reduce_70, 4, 100, :_reduce_71, 1, 100, :_reduce_72, 1, 104, :_reduce_73, 2, 104, :_reduce_74, 4, 126, :_reduce_75, 3, 126, :_reduce_76, 1, 126, :_reduce_77, 3, 127, :_reduce_78, 2, 125, :_reduce_79, 3, 129, :_reduce_80, 2, 129, :_reduce_81, 2, 128, :_reduce_82, 4, 128, :_reduce_83, 2, 107, :_reduce_84, 5, 131, :_reduce_85, 4, 131, :_reduce_86, 0, 132, :_reduce_none, 2, 132, :_reduce_88, 4, 132, :_reduce_89, 3, 132, :_reduce_90, 6, 108, :_reduce_91, 5, 108, :_reduce_92, 0, 133, :_reduce_none, 4, 133, :_reduce_94, 3, 133, :_reduce_95, 5, 106, :_reduce_96, 1, 134, :_reduce_97, 2, 134, :_reduce_98, 5, 135, :_reduce_99, 4, 135, :_reduce_100, 1, 136, :_reduce_101, 1, 99, :_reduce_none, 4, 99, :_reduce_103, 1, 138, :_reduce_104, 3, 138, :_reduce_105, 3, 137, :_reduce_106, 1, 95, :_reduce_107, 6, 95, :_reduce_108, 6, 95, :_reduce_109, 5, 95, :_reduce_110, 5, 95, :_reduce_111, 6, 95, :_reduce_112, 5, 95, :_reduce_113, 4, 143, :_reduce_114, 1, 144, :_reduce_115, 1, 140, :_reduce_116, 3, 140, :_reduce_117, 1, 139, :_reduce_118, 2, 139, :_reduce_119, 1, 139, :_reduce_120, 6, 105, :_reduce_121, 2, 105, :_reduce_122, 3, 145, :_reduce_123, 3, 145, :_reduce_124, 1, 146, :_reduce_none, 1, 146, :_reduce_none, 0, 142, :_reduce_127, 1, 142, :_reduce_128, 3, 142, :_reduce_129, 1, 148, :_reduce_none, 1, 148, :_reduce_none, 1, 148, :_reduce_none, 3, 147, :_reduce_133, 3, 147, :_reduce_134, 6, 109, :_reduce_135, 7, 110, :_reduce_136, 1, 153, :_reduce_137, 1, 152, :_reduce_none, 1, 152, :_reduce_none, 1, 154, :_reduce_none, 2, 154, :_reduce_141, 1, 155, :_reduce_none, 1, 155, :_reduce_none, 6, 111, :_reduce_144, 5, 111, :_reduce_145, 1, 156, :_reduce_146, 3, 156, :_reduce_147, 1, 158, :_reduce_148, 1, 158, :_reduce_149, 1, 158, :_reduce_150, 1, 158, :_reduce_none, 1, 159, :_reduce_152, 3, 159, :_reduce_153, 1, 157, :_reduce_none, 2, 157, :_reduce_155, 1, 150, :_reduce_156, 1, 150, :_reduce_157, 1, 151, :_reduce_158, 2, 151, :_reduce_159, 4, 151, :_reduce_160, 1, 130, :_reduce_161, 3, 130, :_reduce_162, 3, 160, :_reduce_163, 1, 160, :_reduce_164, 1, 103, :_reduce_165, 3, 113, :_reduce_166, 4, 113, :_reduce_167, 2, 113, :_reduce_168, 3, 113, :_reduce_169, 4, 113, :_reduce_170, 2, 113, :_reduce_171, 3, 116, :_reduce_172, 4, 116, :_reduce_173, 2, 116, :_reduce_174, 1, 161, :_reduce_175, 3, 161, :_reduce_176, 3, 162, :_reduce_177, 1, 123, :_reduce_none, 1, 123, :_reduce_none, 1, 123, :_reduce_none, 1, 163, :_reduce_181, 2, 164, :_reduce_182, 1, 166, :_reduce_183, 1, 168, :_reduce_184, 1, 169, :_reduce_185, 2, 167, :_reduce_186, 1, 170, :_reduce_187, 1, 171, :_reduce_188, 2, 171, :_reduce_189, 2, 165, :_reduce_190, 2, 172, :_reduce_191, 2, 172, :_reduce_192, 3, 89, :_reduce_193, 0, 173, :_reduce_194, 2, 173, :_reduce_195, 4, 173, :_reduce_196, 1, 112, :_reduce_197, 2, 112, :_reduce_198, 1, 119, :_reduce_199, 1, 122, :_reduce_200, 1, 120, :_reduce_201, 1, 121, :_reduce_202, 1, 115, :_reduce_203, 1, 114, :_reduce_204, 1, 117, :_reduce_205, 0, 124, :_reduce_none, 1, 124, :_reduce_207, 0, 141, :_reduce_none, 1, 141, :_reduce_none, 1, 149, :_reduce_none, 1, 149, :_reduce_none, 1, 149, :_reduce_none, 1, 149, :_reduce_none, 1, 149, :_reduce_none, 1, 149, :_reduce_none, 1, 149, :_reduce_none, 1, 149, :_reduce_none, 1, 149, :_reduce_none, 1, 149, :_reduce_none, 1, 149, :_reduce_none, 1, 149, :_reduce_none, 1, 149, :_reduce_none, 1, 149, :_reduce_none, 0, 90, :_reduce_224 ] racc_reduce_n = 225 racc_shift_n = 392 racc_token_table = { false => 0, :error => 1, :STRING => 2, :DQPRE => 3, :DQMID => 4, :DQPOST => 5, :LBRACK => 6, :RBRACK => 7, :LBRACE => 8, :RBRACE => 9, :SYMBOL => 10, :FARROW => 11, :COMMA => 12, :TRUE => 13, :FALSE => 14, :EQUALS => 15, :APPENDS => 16, :DELETES => 17, :LESSEQUAL => 18, :NOTEQUAL => 19, :DOT => 20, :COLON => 21, :LLCOLLECT => 22, :RRCOLLECT => 23, :QMARK => 24, :LPAREN => 25, :RPAREN => 26, :ISEQUAL => 27, :GREATEREQUAL => 28, :GREATERTHAN => 29, :LESSTHAN => 30, :IF => 31, :ELSE => 32, :DEFINE => 33, :ELSIF => 34, :VARIABLE => 35, :CLASS => 36, :INHERITS => 37, :NODE => 38, :BOOLEAN => 39, :NAME => 40, :SEMIC => 41, :CASE => 42, :DEFAULT => 43, :AT => 44, :ATAT => 45, :LCOLLECT => 46, :RCOLLECT => 47, :CLASSREF => 48, :NOT => 49, :OR => 50, :AND => 51, :UNDEF => 52, :PARROW => 53, :PLUS => 54, :MINUS => 55, :TIMES => 56, :DIV => 57, :LSHIFT => 58, :RSHIFT => 59, :UMINUS => 60, :MATCH => 61, :NOMATCH => 62, :REGEX => 63, :IN_EDGE => 64, :OUT_EDGE => 65, :IN_EDGE_SUB => 66, :OUT_EDGE_SUB => 67, :IN => 68, :UNLESS => 69, :PIPE => 70, :LAMBDA => 71, :SELBRACE => 72, :NUMBER => 73, :HEREDOC => 74, :SUBLOCATE => 75, :RENDER_STRING => 76, :RENDER_EXPR => 77, :EPP_START => 78, :LOW => 79, :HIGH => 80, :CALL => 81, :LISTSTART => 82, :MODULO => 83, :TITLE_COLON => 84, :CASE_COLON => 85 } racc_nt_base = 86 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", "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", "LOW", "HIGH", "CALL", "LISTSTART", "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", "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", "parameter", "hashpairs", "hashpair", "string", "dq_string", "heredoc", "dqpre", "dqrval", "dqpost", "dqmid", "text_expression", "dqtail", "sublocated_text", "epp_parameters_list" ] Racc_debug_parser = false ##### State transition tables end ##### # reduce 0 omitted module_eval(<<'.,.,', 'egrammar.ra', 64) def _reduce_1(val, _values, result) result = create_program(Factory.block_or_expression(*val[0])) result end .,., module_eval(<<'.,.,', 'egrammar.ra', 65) def _reduce_2(val, _values, result) result = create_program(Factory.block_or_expression(*val[0])) result end .,., # reduce 3 omitted module_eval(<<'.,.,', 'egrammar.ra', 70) def _reduce_4(val, _values, result) result = transform_calls(val[0]) result end .,., module_eval(<<'.,.,', 'egrammar.ra', 76) def _reduce_5(val, _values, result) result = [val[0]] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 77) def _reduce_6(val, _values, result) result = val[0].push val[2] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 78) def _reduce_7(val, _values, result) result = val[0].push val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 82) def _reduce_8(val, _values, result) result = val[0] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 83) def _reduce_9(val, _values, result) result = aryfy(val[0]).push val[2] result end .,., # reduce 10 omitted module_eval(<<'.,.,', 'egrammar.ra', 89) def _reduce_11(val, _values, result) result = val[0] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 90) 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', 91) 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', 92) 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', 93) 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', 100) def _reduce_17(val, _values, result) result = val[0][*val[2]] ; loc result, val[0], val[3] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 101) def _reduce_18(val, _values, result) result = val[0].in val[2] ; loc result, val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 102) def _reduce_19(val, _values, result) result = val[0] =~ val[2] ; loc result, val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 103) def _reduce_20(val, _values, result) result = val[0].mne val[2] ; loc result, val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 104) def _reduce_21(val, _values, result) result = val[0] + val[2] ; loc result, val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 105) def _reduce_22(val, _values, result) result = val[0] - val[2] ; loc result, val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 106) def _reduce_23(val, _values, result) result = val[0] / val[2] ; loc result, val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 107) def _reduce_24(val, _values, result) result = val[0] * val[2] ; loc result, val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 108) def _reduce_25(val, _values, result) result = val[0] % val[2] ; loc result, val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 109) def _reduce_26(val, _values, result) result = val[0] << val[2] ; loc result, val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 110) def _reduce_27(val, _values, result) result = val[0] >> val[2] ; loc result, val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 111) def _reduce_28(val, _values, result) result = val[1].minus() ; loc result, val[0] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 112) def _reduce_29(val, _values, result) result = val[0].ne val[2] ; loc result, val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 113) def _reduce_30(val, _values, result) result = val[0] == val[2] ; loc result, val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 114) def _reduce_31(val, _values, result) result = val[0] > val[2] ; loc result, val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 115) def _reduce_32(val, _values, result) result = val[0] >= val[2] ; loc result, val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 116) def _reduce_33(val, _values, result) result = val[0] < val[2] ; loc result, val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 117) def _reduce_34(val, _values, result) result = val[0] <= val[2] ; loc result, val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 118) def _reduce_35(val, _values, result) result = val[1].not ; loc result, val[0] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 119) def _reduce_36(val, _values, result) result = val[0].and val[2] ; loc result, val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 120) def _reduce_37(val, _values, result) result = val[0].or val[2] ; loc result, val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 121) def _reduce_38(val, _values, result) result = val[0].set(val[2]) ; loc result, val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 122) def _reduce_39(val, _values, result) result = val[0].plus_set(val[2]) ; loc result, val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 123) def _reduce_40(val, _values, result) result = val[0].minus_set(val[2]); loc result, val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 124) def _reduce_41(val, _values, result) result = val[0].select(*val[2]) ; loc result, val[0] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 125) def _reduce_42(val, _values, result) result = val[1].paren() ; loc result, val[0] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 133) def _reduce_43(val, _values, result) result = [val[0]] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 134) def _reduce_44(val, _values, result) result = val[0].push(val[2]) result end .,., # reduce 45 omitted # 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 module_eval(<<'.,.,', 'egrammar.ra', 166) def _reduce_66(val, _values, result) result = val[0] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 167) def _reduce_67(val, _values, result) result = val[0] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 175) def _reduce_68(val, _values, result) result = Factory.CALL_NAMED(val[0], true, val[2]) loc result, val[0], val[4] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 179) def _reduce_69(val, _values, result) result = Factory.CALL_NAMED(val[0], true, []) loc result, val[0], val[2] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 183) def _reduce_70(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', 188) def _reduce_71(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', 192) def _reduce_72(val, _values, result) result = val[0] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 197) def _reduce_73(val, _values, result) result = val[0] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 198) def _reduce_74(val, _values, result) result = val[0]; val[0].lambda = val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 201) def _reduce_75(val, _values, result) result = Factory.CALL_METHOD(val[0], val[2]); loc result, val[1], val[3] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 202) def _reduce_76(val, _values, result) result = Factory.CALL_METHOD(val[0], []); loc result, val[1], val[3] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 203) def _reduce_77(val, _values, result) result = Factory.CALL_METHOD(val[0], []); loc result, val[0] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 208) def _reduce_78(val, _values, result) result = val[0].dot(Factory.fqn(val[2][:value])) loc result, val[1], val[2] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 220) def _reduce_79(val, _values, result) result = Factory.LAMBDA(val[0], val[1]) # loc result, val[1] # TODO result end .,., module_eval(<<'.,.,', 'egrammar.ra', 225) def _reduce_80(val, _values, result) result = val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 226) def _reduce_81(val, _values, result) result = nil result end .,., module_eval(<<'.,.,', 'egrammar.ra', 230) def _reduce_82(val, _values, result) result = [] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 231) def _reduce_83(val, _values, result) result = val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 241) def _reduce_84(val, _values, result) result = val[1] loc(result, val[0], val[1]) result end .,., module_eval(<<'.,.,', 'egrammar.ra', 248) def _reduce_85(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', 252) def _reduce_86(val, _values, result) result = Factory.IF(val[0], nil, val[3]) loc(result, val[0], (val[3] ? val[3] : val[2])) result end .,., # reduce 87 omitted module_eval(<<'.,.,', 'egrammar.ra', 260) def _reduce_88(val, _values, result) result = val[1] loc(result, val[0], val[1]) result end .,., module_eval(<<'.,.,', 'egrammar.ra', 264) def _reduce_89(val, _values, result) result = Factory.block_or_expression(*val[2]) loc result, val[0], val[3] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 268) def _reduce_90(val, _values, result) result = nil # don't think a nop is needed here either result end .,., module_eval(<<'.,.,', 'egrammar.ra', 277) def _reduce_91(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', 281) def _reduce_92(val, _values, result) result = Factory.UNLESS(val[1], nil, nil) loc result, val[0], val[4] result end .,., # reduce 93 omitted module_eval(<<'.,.,', 'egrammar.ra', 291) def _reduce_94(val, _values, result) result = Factory.block_or_expression(*val[2]) loc result, val[0], val[3] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 295) def _reduce_95(val, _values, result) result = nil # don't think a nop is needed here either result end .,., module_eval(<<'.,.,', 'egrammar.ra', 303) def _reduce_96(val, _values, result) result = Factory.CASE(val[1], *val[3]) loc result, val[0], val[4] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 309) def _reduce_97(val, _values, result) result = [val[0]] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 310) def _reduce_98(val, _values, result) result = val[0].push val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 315) def _reduce_99(val, _values, result) result = Factory.WHEN(val[0], val[3]) loc result, val[1], val[4] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 319) def _reduce_100(val, _values, result) result = Factory.WHEN(val[0], nil) loc result, val[1], val[3] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 323) def _reduce_101(val, _values, result) result = val[0] result end .,., # reduce 102 omitted module_eval(<<'.,.,', 'egrammar.ra', 334) def _reduce_103(val, _values, result) result = val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 339) def _reduce_104(val, _values, result) result = [val[0]] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 340) def _reduce_105(val, _values, result) result = val[0].push val[2] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 345) def _reduce_106(val, _values, result) result = Factory.MAP(val[0], val[2]) ; loc result, val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 357) def _reduce_107(val, _values, result) result = val[0] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 360) def _reduce_108(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', 375) def _reduce_109(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', 383) def _reduce_110(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', 396) def _reduce_111(val, _values, result) result = case Factory.resource_shape(val[0]) when :resource, :class # This catches deprecated syntax. error val[1], "All resource specifications require names" 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', 411) def _reduce_112(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', 416) def _reduce_113(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', 421) def _reduce_114(val, _values, result) result = Factory.RESOURCE_BODY(val[0], val[2]) result end .,., module_eval(<<'.,.,', 'egrammar.ra', 423) def _reduce_115(val, _values, result) result = val[0] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 426) def _reduce_116(val, _values, result) result = [val[0]] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 427) def _reduce_117(val, _values, result) result = val[0].push val[2] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 432) def _reduce_118(val, _values, result) result = :virtual result end .,., module_eval(<<'.,.,', 'egrammar.ra', 433) def _reduce_119(val, _values, result) result = :exported result end .,., module_eval(<<'.,.,', 'egrammar.ra', 434) def _reduce_120(val, _values, result) result = :exported result end .,., module_eval(<<'.,.,', 'egrammar.ra', 446) def _reduce_121(val, _values, result) result = Factory.COLLECT(val[0], val[1], val[3]) loc result, val[0], val[5] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 450) def _reduce_122(val, _values, result) result = Factory.COLLECT(val[0], val[1], []) loc result, val[0], val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 455) def _reduce_123(val, _values, result) result = Factory.VIRTUAL_QUERY(val[1]) ; loc result, val[0], val[2] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 456) def _reduce_124(val, _values, result) result = Factory.EXPORTED_QUERY(val[1]) ; loc result, val[0], val[2] result end .,., # reduce 125 omitted # reduce 126 omitted module_eval(<<'.,.,', 'egrammar.ra', 469) def _reduce_127(val, _values, result) result = [] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 470) def _reduce_128(val, _values, result) result = [val[0]] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 471) def _reduce_129(val, _values, result) result = val[0].push(val[2]) result end .,., # reduce 130 omitted # reduce 131 omitted # reduce 132 omitted module_eval(<<'.,.,', 'egrammar.ra', 487) def _reduce_133(val, _values, result) result = Factory.ATTRIBUTE_OP(val[0][:value], :'=>', val[2]) loc result, val[0], val[2] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 491) def _reduce_134(val, _values, result) result = Factory.ATTRIBUTE_OP(val[0][:value], :'+>', val[2]) loc result, val[0], val[2] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 501) def _reduce_135(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', 515) def _reduce_136(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', 525) def _reduce_137(val, _values, result) namestack(val[0][:value]) ; result = val[0] result end .,., # reduce 138 omitted # reduce 139 omitted # reduce 140 omitted module_eval(<<'.,.,', 'egrammar.ra', 534) def _reduce_141(val, _values, result) result = val[1] result end .,., # reduce 142 omitted # reduce 143 omitted module_eval(<<'.,.,', 'egrammar.ra', 551) def _reduce_144(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', 555) def _reduce_145(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', 565) def _reduce_146(val, _values, result) result = [result] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 566) def _reduce_147(val, _values, result) result = val[0].push(val[2]) result end .,., module_eval(<<'.,.,', 'egrammar.ra', 571) def _reduce_148(val, _values, result) result = val[0] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 572) def _reduce_149(val, _values, result) result = val[0] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 573) def _reduce_150(val, _values, result) result = Factory.literal(:default); loc result, val[0] result end .,., # reduce 151 omitted module_eval(<<'.,.,', 'egrammar.ra', 577) def _reduce_152(val, _values, result) result = Factory.literal(val[0][:value]); loc result, val[0] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 578) def _reduce_153(val, _values, result) result = Factory.concat(val[0], '.', val[2][:value]); loc result, val[0], val[2] result end .,., # reduce 154 omitted module_eval(<<'.,.,', 'egrammar.ra', 583) def _reduce_155(val, _values, result) result = val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 589) def _reduce_156(val, _values, result) result = val[0] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 590) def _reduce_157(val, _values, result) error val[0], "'class' is not a valid classname" result end .,., module_eval(<<'.,.,', 'egrammar.ra', 594) def _reduce_158(val, _values, result) result = [] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 595) def _reduce_159(val, _values, result) result = [] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 596) def _reduce_160(val, _values, result) result = val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 600) def _reduce_161(val, _values, result) result = [val[0]] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 601) def _reduce_162(val, _values, result) result = val[0].push(val[2]) result end .,., module_eval(<<'.,.,', 'egrammar.ra', 605) def _reduce_163(val, _values, result) result = Factory.PARAM(val[0][:value], val[2]) ; loc result, val[0] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 606) def _reduce_164(val, _values, result) result = Factory.PARAM(val[0][:value]); loc result, val[0] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 619) def _reduce_165(val, _values, result) result = Factory.fqn(val[0][:value]).var ; loc result, val[0] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 625) def _reduce_166(val, _values, result) result = Factory.LIST(val[1]); loc result, val[0], val[2] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 626) def _reduce_167(val, _values, result) result = Factory.LIST(val[1]); loc result, val[0], val[3] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 627) def _reduce_168(val, _values, result) result = Factory.literal([]) ; loc result, val[0] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 628) def _reduce_169(val, _values, result) result = Factory.LIST(val[1]); loc result, val[0], val[2] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 629) def _reduce_170(val, _values, result) result = Factory.LIST(val[1]); loc result, val[0], val[3] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 630) def _reduce_171(val, _values, result) result = Factory.literal([]) ; loc result, val[0] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 633) def _reduce_172(val, _values, result) result = Factory.HASH(val[1]); loc result, val[0], val[2] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 634) def _reduce_173(val, _values, result) result = Factory.HASH(val[1]); loc result, val[0], val[3] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 635) def _reduce_174(val, _values, result) result = Factory.literal({}) ; loc result, val[0], val[3] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 638) def _reduce_175(val, _values, result) result = [val[0]] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 639) def _reduce_176(val, _values, result) result = val[0].push val[2] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 642) def _reduce_177(val, _values, result) result = Factory.KEY_ENTRY(val[0], val[2]); loc result, val[1] result end .,., # reduce 178 omitted # reduce 179 omitted # reduce 180 omitted module_eval(<<'.,.,', 'egrammar.ra', 649) def _reduce_181(val, _values, result) result = Factory.literal(val[0][:value]) ; loc result, val[0] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 650) def _reduce_182(val, _values, result) result = Factory.string(val[0], *val[1]) ; loc result, val[0], val[1][-1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 651) def _reduce_183(val, _values, result) result = Factory.literal(val[0][:value]); loc result, val[0] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 652) def _reduce_184(val, _values, result) result = Factory.literal(val[0][:value]); loc result, val[0] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 653) def _reduce_185(val, _values, result) result = Factory.literal(val[0][:value]); loc result, val[0] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 654) def _reduce_186(val, _values, result) result = [val[0]] + val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 655) def _reduce_187(val, _values, result) result = Factory.TEXT(val[0]) result end .,., module_eval(<<'.,.,', 'egrammar.ra', 658) def _reduce_188(val, _values, result) result = [val[0]] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 659) def _reduce_189(val, _values, result) result = [val[0]] + val[1] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 663) +module_eval(<<'.,.,', 'egrammar.ra', 662) def _reduce_190(val, _values, result) result = Factory.HEREDOC(val[0][:value], val[1]); loc result, val[0] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 666) +module_eval(<<'.,.,', 'egrammar.ra', 665) def _reduce_191(val, _values, result) result = Factory.SUBLOCATE(val[0], val[1]); loc result, val[0] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 667) +module_eval(<<'.,.,', 'egrammar.ra', 666) def _reduce_192(val, _values, result) result = Factory.SUBLOCATE(val[0], val[1]); loc result, val[0] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 671) +module_eval(<<'.,.,', 'egrammar.ra', 669) def _reduce_193(val, _values, result) result = Factory.EPP(val[1], val[2]); loc result, val[0] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 674) +module_eval(<<'.,.,', 'egrammar.ra', 672) def _reduce_194(val, _values, result) - result = [] + result = nil result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 675) +module_eval(<<'.,.,', 'egrammar.ra', 673) def _reduce_195(val, _values, result) result = [] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 676) +module_eval(<<'.,.,', 'egrammar.ra', 674) def _reduce_196(val, _values, result) result = val[1] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 679) +module_eval(<<'.,.,', 'egrammar.ra', 677) def _reduce_197(val, _values, result) result = Factory.RENDER_STRING(val[0][:value]); loc result, val[0] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 680) +module_eval(<<'.,.,', 'egrammar.ra', 678) def _reduce_198(val, _values, result) result = Factory.RENDER_EXPR(val[1]); loc result, val[0], val[1] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 682) +module_eval(<<'.,.,', 'egrammar.ra', 680) def _reduce_199(val, _values, result) result = Factory.NUMBER(val[0][:value]) ; loc result, val[0] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 683) +module_eval(<<'.,.,', 'egrammar.ra', 681) def _reduce_200(val, _values, result) result = Factory.QNAME_OR_NUMBER(val[0][:value]) ; loc result, val[0] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 684) +module_eval(<<'.,.,', 'egrammar.ra', 682) def _reduce_201(val, _values, result) result = Factory.QREF(val[0][:value]) ; loc result, val[0] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 685) +module_eval(<<'.,.,', 'egrammar.ra', 683) def _reduce_202(val, _values, result) result = Factory.literal(:undef); loc result, val[0] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 686) +module_eval(<<'.,.,', 'egrammar.ra', 684) def _reduce_203(val, _values, result) result = Factory.literal(:default); loc result, val[0] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 691) +module_eval(<<'.,.,', 'egrammar.ra', 689) def _reduce_204(val, _values, result) result = Factory.literal(val[0][:value]) ; loc result, val[0] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 694) +module_eval(<<'.,.,', 'egrammar.ra', 692) def _reduce_205(val, _values, result) result = Factory.literal(val[0][:value]); loc result, val[0] result end .,., # reduce 206 omitted -module_eval(<<'.,.,', 'egrammar.ra', 700) +module_eval(<<'.,.,', 'egrammar.ra', 698) def _reduce_207(val, _values, result) result = nil result end .,., # reduce 208 omitted # reduce 209 omitted # reduce 210 omitted # reduce 211 omitted # reduce 212 omitted # reduce 213 omitted # reduce 214 omitted # reduce 215 omitted # reduce 216 omitted # reduce 217 omitted # reduce 218 omitted # reduce 219 omitted # reduce 220 omitted # reduce 221 omitted # reduce 222 omitted # reduce 223 omitted -module_eval(<<'.,.,', 'egrammar.ra', 723) +module_eval(<<'.,.,', 'egrammar.ra', 721) def _reduce_224(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/parser/evaluating_parser.rb b/lib/puppet/pops/parser/evaluating_parser.rb index 94ab47e03..ace5b8be0 100644 --- a/lib/puppet/pops/parser/evaluating_parser.rb +++ b/lib/puppet/pops/parser/evaluating_parser.rb @@ -1,184 +1,196 @@ # Does not support "import" and parsing ruby files # class Puppet::Pops::Parser::EvaluatingParser + attr_reader :parser + def initialize() @parser = Puppet::Pops::Parser::Parser.new() end def parse_string(s, file_source = 'unknown') @file_source = file_source clear() # Handling of syntax error can be much improved (in general), now it bails out of the parser # and does not have as rich information (when parsing a string), need to update it with the file source # (ideally, a syntax error should be entered as an issue, and not just thrown - but that is a general problem # and an improvement that can be made in the eparser (rather than here). # Also a possible improvement (if the YAML parser returns positions) is to provide correct output of position. # begin - assert_and_report(@parser.parse_string(s)) + assert_and_report(parser.parse_string(s)) rescue Puppet::ParseError => e # TODO: This is not quite right, why does not the exception have the correct file? e.file = @file_source unless e.file.is_a?(String) && !e.file.empty? raise e end end def parse_file(file) @file_source = file clear() - assert_and_report(@parser.parse_file(file)) + assert_and_report(parser.parse_file(file)) end def evaluate_string(scope, s, file_source='unknown') evaluate(scope, parse_string(s, file_source)) end def evaluate_file(file) evaluate(parse_file(file)) end def clear() @acceptor = nil end def evaluate(scope, model) return nil unless model ast = Puppet::Pops::Model::AstTransformer.new(@file_source, nil).transform(model) return nil unless ast ast.safeevaluate(scope) end def validate(parse_result) resulting_acceptor = acceptor() validator(resulting_acceptor).validate(parse_result) resulting_acceptor end def acceptor() Puppet::Pops::Validation::Acceptor.new end def validator(acceptor) Puppet::Pops::Validation::ValidatorFactory_3_1.new().validator(acceptor) end def assert_and_report(parse_result) return nil unless parse_result if parse_result.source_ref.nil? or parse_result.source_ref == '' parse_result.source_ref = @file_source end validation_result = validate(parse_result) max_errors = Puppet[:max_errors] max_warnings = Puppet[:max_warnings] + 1 max_deprecations = Puppet[:max_deprecations] + 1 # If there are warnings output them warnings = validation_result.warnings if warnings.size > 0 formatter = Puppet::Pops::Validation::DiagnosticFormatterPuppetStyle.new emitted_w = 0 emitted_dw = 0 validation_result.warnings.each {|w| if w.severity == :deprecation # Do *not* call Puppet.deprecation_warning it is for internal deprecation, not # deprecation of constructs in manifests! (It is not designed for that purpose even if # used throughout the code base). # Puppet.warning(formatter.format(w)) if emitted_dw < max_deprecations emitted_dw += 1 else Puppet.warning(formatter.format(w)) if emitted_w < max_warnings emitted_w += 1 end break if emitted_w > max_warnings && emitted_dw > max_deprecations # but only then } end # If there were errors, report the first found. Use a puppet style formatter. errors = validation_result.errors if errors.size > 0 formatter = Puppet::Pops::Validation::DiagnosticFormatterPuppetStyle.new if errors.size == 1 || max_errors <= 1 # raise immediately raise Puppet::ParseError.new(formatter.format(errors[0])) end emitted = 0 errors.each do |e| Puppet.err(formatter.format(e)) emitted += 1 break if emitted >= max_errors end warnings_message = warnings.size > 0 ? ", and #{warnings.size} warnings" : "" giving_up_message = "Found #{errors.size} errors#{warnings_message}. Giving up" exception = Puppet::ParseError.new(giving_up_message) exception.file = errors[0].file raise exception end parse_result end def quote(x) self.class.quote(x) end # Translates an already parsed string that contains control characters, quotes # and backslashes into a quoted string where all such constructs have been escaped. # Parsing the return value of this method using the puppet parser should yield # exactly the same string as the argument passed to this method # # The method makes an exception for the two character sequences \$ and \s. They # will not be escaped since they have a special meaning in puppet syntax. # # TODO: Handle \uXXXX characters ?? # # @param x [String] The string to quote and "unparse" # @return [String] The quoted string # def self.quote(x) escaped = '"' p = nil x.each_char do |c| case p when nil # do nothing when "\t" escaped << '\\t' when "\n" escaped << '\\n' when "\f" escaped << '\\f' # TODO: \cx is a range of characters - skip for now # when "\c" # escaped << '\\c' when '"' escaped << '\\"' when '\\' escaped << if c == '$' || c == 's'; p; else '\\\\'; end # don't escape \ when followed by s or $ else escaped << p end p = c end escaped << p unless p.nil? escaped << '"' end # This is a temporary solution to making it possible to use the new evaluator. The main class # will eventually have this behavior instead of using transformation to Puppet 3.x AST class Transitional < Puppet::Pops::Parser::EvaluatingParser def evaluate(scope, model) return nil unless model @@evaluator ||= Puppet::Pops::Evaluator::EvaluatorImpl.new() @@evaluator.evaluate(model, scope) end def validator(acceptor) Puppet::Pops::Validation::ValidatorFactory_4_0.new().validator(acceptor) end + # Create a closure that can be called in the given scope + def closure(model, scope) + Puppet::Pops::Evaluator::Closure.new(@@evaluator, model, scope) + end + end + + class EvaluatingEppParser < Transitional + def initialize() + @parser = Puppet::Pops::Parser::EppParser.new() + end end end diff --git a/lib/puppet/pops/validation/checker4_0.rb b/lib/puppet/pops/validation/checker4_0.rb index e162f42da..4b3271ebe 100644 --- a/lib/puppet/pops/validation/checker4_0.rb +++ b/lib/puppet/pops/validation/checker4_0.rb @@ -1,519 +1,526 @@ # 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) @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 #---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_CallNamedFunctionExpression(o) - unless o.functor_expr.is_a? Model::QualifiedName - acceptor.accept(Issues::ILLEGAL_EXPRESSION, o.functor_expr, :feature => 'function name', :container => 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_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' and 'define' 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 end def check_IfExpression(o) rvalue(o.test) end def check_ImportExpression(o) o.files.each do |f| unless f.is_a? Model::LiteralString acceptor.accept(Issues::ILLEGAL_EXPRESSION, f, :feature => 'file name', :container => o) end end 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 # A Lambda is a Definition, but it may appear in other scopes than top scope (Which check_Definition asserts). # def check_LambdaExpression(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) 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_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_ImportExpression(o) ; acceptor.accept(Issues::NOT_RVALUE, o) ; end def rvalue_BlockExpression(o) ; acceptor.accept(Issues::NOT_RVALUE, 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 #--- 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