diff --git a/lib/puppet/face/module/generate.rb b/lib/puppet/face/module/generate.rb index ca2a69f54..9b88a8c65 100644 --- a/lib/puppet/face/module/generate.rb +++ b/lib/puppet/face/module/generate.rb @@ -1,251 +1,251 @@ Puppet::Face.define(:module, '1.0.0') do action(:generate) do summary "Generate boilerplate for a new module." description <<-EOT Generates boilerplate for a new module by creating the directory structure and files recommended for the Puppet community's best practices. A module may need additional directories beyond this boilerplate if it provides plugins, files, or templates. EOT returns "Array of Pathname objects representing paths of generated files." examples <<-EOT Generate a new module in the current directory: $ puppet module generate puppetlabs-ssh We need to create a metadata.json file for this module. Please answer the following questions; if the question is not applicable to this module, feel free to leave it blank. Puppet uses Semantic Versioning (semver.org) to version modules. What version is this module? [0.1.0] --> Who wrote this module? [puppetlabs] --> What license does this module code fall under? [Apache 2.0] --> How would you describe this module in a single sentence? --> Where is this module's source code repository? --> Where can others go to learn more about this module? --> Where can others go to file issues about this module? --> ---------------------------------------- { "name": "puppetlabs-ssh", "version": "0.1.0", "author": "puppetlabs", "summary": null, "license": "Apache 2.0", "source": "", "project_page": null, "issues_url": null, "dependencies": [ { "name": "puppetlabs-stdlib", "version_requirement": ">= 1.0.0" } ] } ---------------------------------------- About to generate this metadata; continue? [n/Y] --> Notice: Generating module at /Users/username/Projects/puppet/puppetlabs-ssh... Notice: Populating ERB templates... Finished; module generated in puppetlabs-ssh. puppetlabs-ssh/manifests puppetlabs-ssh/manifests/init.pp puppetlabs-ssh/metadata.json puppetlabs-ssh/README.md puppetlabs-ssh/spec puppetlabs-ssh/spec/spec_helper.rb puppetlabs-ssh/tests puppetlabs-ssh/tests/init.pp EOT option "--skip-interview" do summary "Bypass the interactive metadata interview" description <<-EOT Do not attempt to perform a metadata interview. Primarily useful for automatic execution of `puppet module generate`. EOT end arguments "" when_invoked do |name, options| # Since we only want to interview if it's being rendered to the console # (i.e. when invoked with `puppet module generate`), we can't do any work # here in the when_invoked block. The result of this block is then # passed to each renderer, which will handle it appropriately; by # returning a simple message like this, every renderer will simply output # the string. # Our `when_rendering :console` handler will ignore this value and # actually generate the module. # # All this is necessary because it is not possible at this point in time # to know what the destination of the output is. "This format is not supported by this action." end when_rendering :console do |_, name, options| Puppet::ModuleTool.set_option_defaults options begin # A default dependency for all newly generated modules is being # introduced as a substitute for the comments we used to include in the # Modulefile. While introducing a default dependency is less than # perfectly desirable, the cost is low, and the syntax is obtuse enough # to justify its inclusion. metadata = Puppet::ModuleTool::Metadata.new.update( 'name' => name, 'version' => '0.1.0', 'dependencies' => [ - { :name => 'puppetlabs-stdlib', :version_requirement => '>= 1.0.0' } + { 'name' => 'puppetlabs-stdlib', 'version_requirement' => '>= 1.0.0' } ] ) rescue ArgumentError msg = "Could not generate directory #{name.inspect}, you must specify a dash-separated username and module name." raise ArgumentError, msg, $!.backtrace end dest = Puppet::ModuleTool::Generate.destination(metadata) result = Puppet::ModuleTool::Generate.generate(metadata, options[:skip_interview]) path = dest.relative_path_from(Pathname.pwd) puts "Finished; module generated in #{path}." result.join("\n") end end end module Puppet::ModuleTool::Generate module_function def generate(metadata, skip_interview = false) interview(metadata) unless skip_interview destination = duplicate_skeleton(metadata) all_files = destination.basename + '**/*' return Dir[all_files.to_s] end def interview(metadata) puts "We need to create a metadata.json file for this module. Please answer the" puts "following questions; if the question is not applicable to this module, feel free" puts "to leave it blank." begin puts puts "Puppet uses Semantic Versioning (semver.org) to version modules." puts "What version is this module? [#{metadata.version}]" metadata.update 'version' => user_input(metadata.version) rescue Puppet.err "We're sorry, we could not parse that as a Semantic Version." retry end puts puts "Who wrote this module? [#{metadata.author}]" metadata.update 'author' => user_input(metadata.author) puts puts "What license does this module code fall under? [#{metadata.license}]" metadata.update 'license' => user_input(metadata.license) puts puts "How would you describe this module in a single sentence?" metadata.update 'summary' => user_input(metadata.summary) puts puts "Where is this module's source code repository?" metadata.update 'source' => user_input(metadata.source) puts puts "Where can others go to learn more about this module?#{ metadata.project_page && " [#{metadata.project_page}]" }" metadata.update 'project_page' => user_input(metadata.project_page) puts puts "Where can others go to file issues about this module?#{ metadata.issues_url && " [#{metadata.issues_url}]" }" metadata.update 'issues_url' => user_input(metadata.issues_url) puts puts '-' * 40 puts metadata.to_json puts '-' * 40 puts puts "About to generate this metadata; continue? [n/Y]" if user_input('Y') !~ /^y(es)?$/i puts "Aborting..." exit 0 end end def user_input(default=nil) print '--> ' input = STDIN.gets.chomp.strip input = default if input == '' return input end def destination(metadata) return @dest if defined? @dest @dest = Pathname.pwd + metadata.dashed_name raise ArgumentError, "#{@dest} already exists." if @dest.exist? return @dest end def duplicate_skeleton(metadata) dest = destination(metadata) puts Puppet.notice "Generating module at #{dest}..." FileUtils.cp_r skeleton_path, dest populate_templates(metadata, dest) return dest end def populate_templates(metadata, destination) Puppet.notice "Populating templates..." formatters = { :erb => proc { |data, ctx| ERB.new(data).result(ctx) }, :template => proc { |data, _| data }, } formatters.each do |type, block| templates = destination + "**/*.#{type}" Dir.glob(templates.to_s, File::FNM_DOTMATCH).each do |erb| path = Pathname.new(erb) content = block[path.read, binding] target = path.parent + path.basename(".#{type}") target.open('w') { |f| f.write(content) } path.unlink end end end def skeleton_path return @path if defined? @path path = Pathname(Puppet.settings[:module_skeleton_dir]) path = Pathname(__FILE__).dirname + '../../module_tool/skeleton/templates/generator' unless path.directory? @path = path end end diff --git a/lib/puppet/face/parser.rb b/lib/puppet/face/parser.rb index 5761c5a8f..b3dec4ed7 100644 --- a/lib/puppet/face/parser.rb +++ b/lib/puppet/face/parser.rb @@ -1,74 +1,160 @@ require 'puppet/face' require 'puppet/parser' Puppet::Face.define(:parser, '0.0.1') do copyright "Puppet Labs", 2011 license "Apache 2 license; see COPYING" summary "Interact directly with the parser." action :validate do summary "Validate the syntax of one or more Puppet manifests." arguments "[] [ ...]" returns "Nothing, or the first syntax error encountered." description <<-'EOT' This action validates Puppet DSL syntax without compiling a catalog or syncing any resources. If no manifest files are provided, it will validate the default site manifest. When validating with --parser current, the validation stops after the first encountered issue. When validating with --parser future, multiple issues per file are reported up to the settings of max_error, and max_warnings. The processing stops after having reported issues for the first encountered file with errors. EOT examples <<-'EOT' Validate the default site manifest at /etc/puppet/manifests/site.pp: $ puppet parser validate Validate two arbitrary manifest files: $ puppet parser validate init.pp vhost.pp Validate from STDIN: $ cat init.pp | puppet parser validate EOT when_invoked do |*args| args.pop files = args if files.empty? if not STDIN.tty? Puppet[:code] = STDIN.read validate_manifest else manifest = Puppet.lookup(:current_environment).manifest files << manifest Puppet.notice "No manifest specified. Validating the default manifest #{manifest}" end end missing_files = [] files.each do |file| - missing_files << file if ! Puppet::FileSystem.exist?(file) - validate_manifest(file) + if Puppet::FileSystem.exist?(file) + validate_manifest(file) + else + missing_files << file + end + end + unless missing_files.empty? + raise Puppet::Error, "One or more file(s) specified did not exist:\n#{missing_files.collect {|f| " " * 3 + f + "\n"}}" end - raise Puppet::Error, "One or more file(s) specified did not exist:\n#{missing_files.collect {|f| " " * 3 + f + "\n"}}" if ! missing_files.empty? nil end end + + action (:dump) do + summary "Outputs a dump of the internal parse tree for debugging" + arguments "-e | [ ...] " + returns "A dump of the resulting AST model unless there are syntax or validation errors." + description <<-'EOT' + This action parses and validates the Puppet DSL syntax without compiling a catalog + or syncing any resources. It automatically turns on the future parser for the parsing. + + The command accepts one or more manifests (.pp) files, or an -e followed by the puppet + source text. + If no arguments are given, the stdin is read (unless it is attached to a terminal) + + The output format of the dumped tree is not API, it may change from time to time. + EOT + + option "--e " do + default_to { nil } + summary "dump one source expression given on the command line." + end + + option("--[no-]validate") do + summary "Whether or not to validate the parsed result, if no-validate only syntax errors are reported" + end + + when_invoked do |*args| + require 'puppet/pops' + options = args.pop + if options[:e] + dump_parse(options[:e], 'command-line-string', options, false) + elsif args.empty? + if ! STDIN.tty? + dump_parse(STDIN.read, 'stdin', options, false) + else + raise Puppet::Error, "No input to parse given on command line or stdin" + end + else + missing_files = [] + files = args + available_files = files.select do |file| + Puppet::FileSystem.exist?(file) + end + missing_files = files - available_files + + dumps = available_files.collect do |file| + dump_parse(File.read(file), file, options) + end.join("") + + if missing_files.empty? + dumps + else + dumps + "One or more file(s) specified did not exist:\n" + missing_files.collect { |f| " #{f}" }.join("\n") + end + end + end + end + + def dump_parse(source, filename, options, show_filename = true) + output = "" + dumper = Puppet::Pops::Model::ModelTreeDumper.new + evaluating_parser = Puppet::Pops::Parser::EvaluatingParser.new + begin + if options[:validate] + parse_result = evaluating_parser.parse_string(source, filename) + else + # side step the assert_and_report step + parse_result = evaluating_parser.parser.parse_string(source) + end + if show_filename + output << "--- #{filename}" + end + output << dumper.dump(parse_result) << "\n" + rescue Puppet::ParseError => detail + if show_filename + Puppet.err("--- #{filename}") + end + Puppet.err(detail.message) + "" + end + end + # @api private def validate_manifest(manifest = nil) env = Puppet.lookup(:current_environment) validation_environment = manifest ? env.override_with(:manifest => manifest) : env validation_environment.check_for_reparse validation_environment.known_resource_types.clear rescue => detail Puppet.log_exception(detail) exit(1) end end diff --git a/lib/puppet/forge.rb b/lib/puppet/forge.rb index d442bdeef..1dd95c41e 100644 --- a/lib/puppet/forge.rb +++ b/lib/puppet/forge.rb @@ -1,205 +1,213 @@ require 'puppet/vendor' Puppet::Vendor.load_vendored require 'net/http' require 'tempfile' require 'uri' require 'pathname' require 'json' require 'semantic' class Puppet::Forge < Semantic::Dependency::Source require 'puppet/forge/cache' require 'puppet/forge/repository' require 'puppet/forge/errors' include Puppet::Forge::Errors USER_AGENT = "PMT/1.1.1 (v3; Net::HTTP)".freeze attr_reader :host, :repository def initialize(host = Puppet[:module_repository]) @host = host @repository = Puppet::Forge::Repository.new(host, USER_AGENT) end # Return a list of module metadata hashes that match the search query. # This return value is used by the module_tool face install search, # and displayed to on the console. # # Example return value: # # [ # { # "author" => "puppetlabs", # "name" => "bacula", # "tag_list" => ["backup", "bacula"], # "releases" => [{"version"=>"0.0.1"}, {"version"=>"0.0.2"}], # "full_name" => "puppetlabs/bacula", # "version" => "0.0.2", # "project_url" => "http://github.com/puppetlabs/puppetlabs-bacula", # "desc" => "bacula" # } # ] # # @param term [String] search term # @return [Array] modules found # @raise [Puppet::Forge::Errors::CommunicationError] if there is a network # related error # @raise [Puppet::Forge::Errors::SSLVerifyError] if there is a problem # verifying the remote SSL certificate # @raise [Puppet::Forge::Errors::ResponseError] if the repository returns a # bad HTTP response def search(term) matches = [] uri = "/v3/modules?query=#{URI.escape(term)}" if Puppet[:module_groups] uri += "&module_groups=#{Puppet[:module_groups]}" end while uri response = make_http_request(uri) if response.code == '200' result = JSON.parse(response.body) uri = result['pagination']['next'] matches.concat result['results'] else raise ResponseError.new(:uri => URI.parse(@host).merge(uri) , :input => term, :response => response) end end matches.each do |mod| mod['author'] = mod['owner']['username'] mod['tag_list'] = mod['current_release']['tags'] mod['full_name'] = "#{mod['author']}/#{mod['name']}" mod['version'] = mod['current_release']['version'] mod['project_url'] = mod['homepage_url'] mod['desc'] = mod['current_release']['metadata']['summary'] || '' end end # Fetches {ModuleRelease} entries for each release of the named module. # # @param input [String] the module name to look up # @return [Array] a list of releases for # the given name # @see Semantic::Dependency::Source#fetch def fetch(input) name = input.tr('/', '-') uri = "/v3/releases?module=#{name}" if Puppet[:module_groups] uri += "&module_groups=#{Puppet[:module_groups]}" end releases = [] while uri response = make_http_request(uri) if response.code == '200' response = JSON.parse(response.body) else raise ResponseError.new(:uri => URI.parse(@host).merge(uri), :input => input, :response => response) end releases.concat(process(response['results'])) uri = response['pagination']['next'] end return releases end def make_http_request(*args) @repository.make_http_request(*args) end class ModuleRelease < Semantic::Dependency::ModuleRelease attr_reader :install_dir, :metadata def initialize(source, data) @data = data @metadata = meta = data['metadata'] name = meta['name'].tr('/', '-') version = Semantic::Version.parse(meta['version']) release = "#{name}@#{version}" - dependencies = (meta['dependencies'] || []) - dependencies.map! do |dep| - Puppet::ModuleTool.parse_module_dependency(release, dep)[0..1] + if meta['dependencies'] + dependencies = meta['dependencies'].collect do |dep| + begin + Puppet::ModuleTool::Metadata.new.add_dependency(dep['name'], dep['version_requirement'], dep['repository']) + Puppet::ModuleTool.parse_module_dependency(release, dep)[0..1] + rescue ArgumentError => e + Puppet.debug "Malformed dependency: #{dep['name']}. Exception was: #{e}" + end + end + else + dependencies = [] end super(source, name, version, Hash[dependencies]) end def install(dir) staging_dir = self.prepare module_dir = dir + name[/-(.*)/, 1] module_dir.rmtree if module_dir.exist? # Make sure unpacked module has the same ownership as the folder we are moving it into. Puppet::ModuleTool::Applications::Unpacker.harmonize_ownership(dir, staging_dir) FileUtils.mv(staging_dir, module_dir) @install_dir = dir # Return the Pathname object representing the directory where the # module release archive was unpacked the to. return module_dir ensure staging_dir.rmtree if staging_dir.exist? end def prepare return @unpacked_into if @unpacked_into download(@data['file_uri'], tmpfile) validate_checksum(tmpfile, @data['file_md5']) unpack(tmpfile, tmpdir) @unpacked_into = Pathname.new(tmpdir) end private # Obtain a suitable temporary path for unpacking tarballs # # @return [Pathname] path to temporary unpacking location def tmpdir @dir ||= Dir.mktmpdir(name, Puppet::Forge::Cache.base_path) end def tmpfile @file ||= Tempfile.new(name, Puppet::Forge::Cache.base_path).tap do |f| f.binmode end end def download(uri, destination) @source.make_http_request(uri, destination) destination.flush and destination.close end def validate_checksum(file, checksum) if Digest::MD5.file(file.path).hexdigest != checksum raise RuntimeError, "Downloaded release for #{name} did not match expected checksum" end end def unpack(file, destination) begin Puppet::ModuleTool::Applications::Unpacker.unpack(file.path, destination) rescue Puppet::ExecutionFailure => e raise RuntimeError, "Could not extract contents of module archive: #{e.message}" end end end private def process(list) list.map { |release| ModuleRelease.new(self, release) } end end diff --git a/lib/puppet/module_tool/dependency.rb b/lib/puppet/module_tool/dependency.rb index c213e55ce..2f0995e58 100644 --- a/lib/puppet/module_tool/dependency.rb +++ b/lib/puppet/module_tool/dependency.rb @@ -1,30 +1,42 @@ require 'puppet/module_tool' require 'puppet/network/format_support' module Puppet::ModuleTool class Dependency include Puppet::Network::FormatSupport alias :to_json :to_pson attr_reader :full_module_name, :username, :name, :version_requirement, :repository # Instantiates a new module dependency with a +full_module_name+ (e.g. # "myuser-mymodule"), and optional +version_requirement+ (e.g. "0.0.1") and # optional repository (a URL string). def initialize(full_module_name, version_requirement = nil, repository = nil) @full_module_name = full_module_name # TODO: add error checking, the next line raises ArgumentError when +full_module_name+ is invalid @username, @name = Puppet::ModuleTool.username_and_modname_from(full_module_name) @version_requirement = version_requirement @repository = repository ? Puppet::Forge::Repository.new(repository) : nil end + # We override Object's ==, eql, and hash so we can more easily find identical + # dependencies. + def ==(o) + self.hash == o.hash + end + + alias :eql? :== + + def hash + [@full_module_name, @version_requirement, @repository].hash + end + def to_data_hash result = { :name => @full_module_name } result[:version_requirement] = @version_requirement if @version_requirement && ! @version_requirement.nil? result[:repository] = @repository.to_s if @repository && ! @repository.nil? result end end end diff --git a/lib/puppet/module_tool/metadata.rb b/lib/puppet/module_tool/metadata.rb index 4ba40c147..0237555ad 100644 --- a/lib/puppet/module_tool/metadata.rb +++ b/lib/puppet/module_tool/metadata.rb @@ -1,167 +1,209 @@ require 'puppet/util/methodhelper' require 'puppet/module_tool' require 'puppet/network/format_support' require 'uri' require 'json' +require 'set' module Puppet::ModuleTool # This class provides a data structure representing a module's metadata. # @api private class Metadata include Puppet::Network::FormatSupport attr_accessor :module_name DEFAULTS = { 'name' => nil, 'version' => nil, 'author' => nil, 'summary' => nil, 'license' => 'Apache 2.0', 'source' => '', 'project_page' => nil, 'issues_url' => nil, - 'dependencies' => [].freeze, + 'dependencies' => Set.new.freeze, } def initialize @data = DEFAULTS.dup @data['dependencies'] = @data['dependencies'].dup end # Returns a filesystem-friendly version of this module name. def dashed_name @data['name'].tr('/', '-') if @data['name'] end # Returns a string that uniquely represents this version of this module. def release_name return nil unless @data['name'] && @data['version'] [ dashed_name, @data['version'] ].join('-') end alias :name :module_name alias :full_module_name :dashed_name # Merges the current set of metadata with another metadata hash. This # method also handles the validation of module names and versions, in an # effort to be proactive about module publishing constraints. def update(data) process_name(data) if data['name'] process_version(data) if data['version'] process_source(data) if data['source'] + merge_dependencies(data) if data['dependencies'] @data.merge!(data) return self end + # Validates the name and version_requirement for a dependency, then creates + # the Dependency and adds it. + # Returns the Dependency that was added. + def add_dependency(name, version_requirement=nil, repository=nil) + validate_name(name) + validate_version_range(version_requirement) if version_requirement + + if dup = @data['dependencies'].find { |d| d.full_module_name == name && d.version_requirement != version_requirement } + raise ArgumentError, "Dependency conflict for #{full_module_name}: Dependency #{name} was given conflicting version requirements #{version_requirement} and #{dup.version_requirement}. Verify that there are no duplicates in the metadata.json or the Modulefile." + end + + dep = Dependency.new(name, version_requirement, repository) + @data['dependencies'].add(dep) + + dep + end + # Provides an accessor for the now defunct 'description' property. This # addresses a regression in Puppet 3.6.x where previously valid templates # refering to the 'description' property were broken. # @deprecated def description @data['description'] end + def dependencies + @data['dependencies'].to_a + end + # Returns a hash of the module's metadata. Used by Puppet's automated # serialization routines. # # @see Puppet::Network::FormatSupport#to_data_hash def to_hash @data end alias :to_data_hash :to_hash def to_json + data = @data.dup.merge('dependencies' => dependencies) + # This is used to simulate an ordered hash. In particular, some keys # are promoted to the top of the serialized hash (while others are # demoted) for human-friendliness. # # This particularly works around the lack of ordered hashes in 1.8.7. promoted_keys = %w[ name version author summary license source ] demoted_keys = %w[ dependencies ] - keys = @data.keys + keys = data.keys keys -= promoted_keys keys -= demoted_keys contents = (promoted_keys + keys + demoted_keys).map do |k| - value = (JSON.pretty_generate(@data[k]) rescue @data[k].to_json) + value = (JSON.pretty_generate(data[k]) rescue data[k].to_json) "#{k.to_json}: #{value}" end "{\n" + contents.join(",\n").gsub(/^/, ' ') + "\n}\n" end # Expose any metadata keys as callable reader methods. def method_missing(name, *args) return @data[name.to_s] if @data.key? name.to_s super end private # Do basic validation and parsing of the name parameter. def process_name(data) validate_name(data['name']) author, @module_name = data['name'].split(/[-\/]/, 2) data['author'] ||= author if @data['author'] == DEFAULTS['author'] end # Do basic validation on the version parameter. def process_version(data) validate_version(data['version']) end # Do basic parsing of the source parameter. If the source is hosted on # GitHub, we can predict sensible defaults for both project_page and # issues_url. def process_source(data) if data['source'] =~ %r[://] source_uri = URI.parse(data['source']) else source_uri = URI.parse("http://#{data['source']}") end if source_uri.host =~ /^(www\.)?github\.com$/ source_uri.scheme = 'https' source_uri.path.sub!(/\.git$/, '') data['project_page'] ||= @data['project_page'] || source_uri.to_s data['issues_url'] ||= @data['issues_url'] || source_uri.to_s.sub(/\/*$/, '') + '/issues' end rescue URI::Error return end + # Validates and parses the dependencies. + def merge_dependencies(data) + data['dependencies'].each do |dep| + add_dependency(dep['name'], dep['version_requirement'], dep['repository']) + end + + # Clear dependencies so @data dependencies are not overwritten + data.delete 'dependencies' + end + # Validates that the given module name is both namespaced and well-formed. def validate_name(name) return if name =~ /\A[a-z0-9]+[-\/][a-z][a-z0-9_]*\Z/i namespace, modname = name.split(/[-\/]/, 2) modname = :namespace_missing if namespace == '' err = case modname when nil, '', :namespace_missing "the field must be a namespaced module name" when /[^a-z0-9_]/i "the module name contains non-alphanumeric (or underscore) characters" when /^[^a-z]/i "the module name must begin with a letter" else "the namespace contains non-alphanumeric characters" end raise ArgumentError, "Invalid 'name' field in metadata.json: #{err}" end # Validates that the version string can be parsed as per SemVer. def validate_version(version) return if SemVer.valid?(version) err = "version string cannot be parsed as a valid Semantic Version" raise ArgumentError, "Invalid 'version' field in metadata.json: #{err}" end + + # Validates that the version range can be parsed by Semantic. + def validate_version_range(version_range) + Semantic::VersionRange.parse(version_range) + rescue ArgumentError => e + raise ArgumentError, "Invalid 'version_range' field in metadata.json: #{e}" + end end end diff --git a/lib/puppet/module_tool/modulefile.rb b/lib/puppet/module_tool/modulefile.rb index 07f578312..31eb9c14c 100644 --- a/lib/puppet/module_tool/modulefile.rb +++ b/lib/puppet/module_tool/modulefile.rb @@ -1,78 +1,78 @@ require 'puppet/module_tool' require 'puppet/module_tool/dependency' module Puppet::ModuleTool # = Modulefile # # This class provides the DSL used for evaluating the module's 'Modulefile'. # These methods are used to concisely define this module's attributes, which # are later rendered as PSON into a 'metadata.json' file. class ModulefileReader # Read the +filename+ and eval its Ruby code to set values in the Metadata # +metadata+ instance. def self.evaluate(metadata, filename) builder = new(metadata) if File.file?(filename) builder.instance_eval(File.read(filename.to_s), filename.to_s, 1) else Puppet.warning "No Modulefile: #{filename}" end return builder end # Instantiate with the Metadata +metadata+ instance. def initialize(metadata) @metadata = metadata end # Set the +full_module_name+ (e.g. "myuser-mymodule"), which will also set the # +username+ and module +name+. Required. def name(name) @metadata.update('name' => name) end # Set the module +version+ (e.g., "0.1.0"). Required. def version(version) @metadata.update('version' => version) end # Add a dependency with the full_module_name +name+ (e.g. "myuser-mymodule"), an # optional +version_requirement+ (e.g. "0.1.0") and +repository+ (a URL # string). Optional. Can be called multiple times to add many dependencies. def dependency(name, version_requirement = nil, repository = nil) - @metadata.to_hash['dependencies'] << Dependency.new(name, version_requirement, repository) + @metadata.add_dependency(name, version_requirement, repository) end # Set the source def source(source) @metadata.update('source' => source) end # Set the author or default to +username+ def author(author) @metadata.update('author' => author) end # Set the license def license(license) @metadata.update('license' => license) end # Set the summary def summary(summary) @metadata.update('summary' => summary) end # Set the description def description(description) @metadata.update('description' => description) end # Set the project page def project_page(project_page) @metadata.update('project_page' => project_page) end end end diff --git a/lib/puppet/pops/evaluator/access_operator.rb b/lib/puppet/pops/evaluator/access_operator.rb index 1b1b7f731..e849a2c4e 100644 --- a/lib/puppet/pops/evaluator/access_operator.rb +++ b/lib/puppet/pops/evaluator/access_operator.rb @@ -1,599 +1,604 @@ # AccessOperator handles operator [] # This operator is part of evaluation. # class Puppet::Pops::Evaluator::AccessOperator # Provides access to the Puppet 3.x runtime (scope, etc.) # This separation has been made to make it easier to later migrate the evaluator to an improved runtime. # include Puppet::Pops::Evaluator::Runtime3Support Issues = Puppet::Pops::Issues TYPEFACTORY = Puppet::Pops::Types::TypeFactory EMPTY_STRING = ''.freeze attr_reader :semantic # Initialize with AccessExpression to enable reporting issues # @param access_expression [Puppet::Pops::Model::AccessExpression] the semantic object being evaluated # @return [void] # def initialize(access_expression) @@access_visitor ||= Puppet::Pops::Visitor.new(self, "access", 2, nil) @semantic = access_expression end def access (o, scope, *keys) @@access_visitor.visit_this_2(self, o, scope, keys) end protected def access_Object(o, scope, keys) fail(Issues::OPERATOR_NOT_APPLICABLE, @semantic.left_expr, :operator=>'[]', :left_value => o) end def access_String(o, scope, keys) keys.flatten! result = case keys.size when 0 fail(Puppet::Pops::Issues::BAD_STRING_SLICE_ARITY, @semantic.left_expr, {:actual => keys.size}) when 1 # Note that Ruby 1.8.7 requires a length of 1 to produce a String k1 = coerce_numeric(keys[0], @semantic.keys, scope) bad_access_key_type(o, 0, k1, Integer) unless k1.is_a?(Integer) k2 = 1 k1 = k1 < 0 ? o.length + k1 : k1 # abs pos # if k1 is outside, a length of 1 always produces an empty string if k1 < 0 EMPTY_STRING else o[ k1, k2 ] end when 2 k1 = coerce_numeric(keys[0], @semantic.keys, scope) k2 = coerce_numeric(keys[1], @semantic.keys, scope) [k1, k2].each_with_index { |k,i| bad_access_key_type(o, i, k, Integer) unless k.is_a?(Integer) } k1 = k1 < 0 ? o.length + k1 : k1 # abs pos (negative is count from end) k2 = k2 < 0 ? o.length - k1 + k2 + 1 : k2 # abs length (negative k2 is length from pos to end count) # if k1 is outside, adjust to first position, and adjust length if k1 < 0 k2 = k2 + k1 k1 = 0 end o[ k1, k2 ] else fail(Puppet::Pops::Issues::BAD_STRING_SLICE_ARITY, @semantic.left_expr, {:actual => keys.size}) end # Specified as: an index outside of range, or empty result == empty string (result.nil? || result.empty?) ? EMPTY_STRING : result end # Parameterizes a PRegexp Type with a pattern string or r ruby egexp # def access_PRegexpType(o, scope, keys) keys.flatten! unless keys.size == 1 blamed = keys.size == 0 ? @semantic : @semantic.keys[1] fail(Puppet::Pops::Issues::BAD_TYPE_SLICE_ARITY, blamed, :base_type => o, :min=>1, :actual => keys.size) end assert_keys(keys, o, 1, 1, String, Regexp) Puppet::Pops::Types::TypeFactory.regexp(*keys) end # Evaluates [] with 1 or 2 arguments. One argument is an index lookup, two arguments is a slice from/to. # def access_Array(o, scope, keys) keys.flatten! case keys.size when 0 fail(Puppet::Pops::Issues::BAD_ARRAY_SLICE_ARITY, @semantic.left_expr, {:actual => keys.size}) when 1 k = coerce_numeric(keys[0], @semantic.keys[0], scope) unless k.is_a?(Integer) bad_access_key_type(o, 0, k, Integer) end o[k] when 2 # A slice [from, to] with support for -1 to mean start, or end respectively. k1 = coerce_numeric(keys[0], @semantic.keys[0], scope) k2 = coerce_numeric(keys[1], @semantic.keys[1], scope) [k1, k2].each_with_index { |k,i| bad_access_key_type(o, i, k, Integer) unless k.is_a?(Integer) } # Help confused Ruby do the right thing (it truncates to the right, but negative index + length can never overlap # the available range. k1 = k1 < 0 ? o.length + k1 : k1 # abs pos (negative is count from end) k2 = k2 < 0 ? o.length - k1 + k2 + 1 : k2 # abs length (negative k2 is length from pos to end count) # if k1 is outside, adjust to first position, and adjust length if k1 < 0 k2 = k2 + k1 k1 = 0 end # Help ruby always return empty array when asking for a sub array result = o[ k1, k2 ] result.nil? ? [] : result else fail(Puppet::Pops::Issues::BAD_ARRAY_SLICE_ARITY, @semantic.left_expr, {:actual => keys.size}) end end # Evaluates [] with support for one or more arguments. If more than one argument is used, the result # is an array with each lookup. # @note # Does not flatten its keys to enable looking up with a structure # def access_Hash(o, scope, keys) # Look up key in hash, if key is nil, try alternate form (:undef) before giving up. # This is done because the hash may have been produced by 3x logic and may thus contain :undef. result = keys.collect do |k| o.fetch(k) { |key| key.nil? ? o[:undef] : nil } end case result.size when 0 fail(Puppet::Pops::Issues::BAD_HASH_SLICE_ARITY, @semantic.left_expr, {:actual => keys.size}) when 1 result.pop else # remove nil elements and return result.compact! result end end # Ruby does not have an infinity constant. TODO: Consider having one constant in Puppet. Now it is in several places. INFINITY = 1.0 / 0.0 def access_PEnumType(o, scope, keys) keys.flatten! assert_keys(keys, o, 1, INFINITY, String) Puppet::Pops::Types::TypeFactory.enum(*keys) end def access_PVariantType(o, scope, keys) keys.flatten! assert_keys(keys, o, 1, INFINITY, Puppet::Pops::Types::PAnyType) Puppet::Pops::Types::TypeFactory.variant(*keys) end def access_PTupleType(o, scope, keys) keys.flatten! if TYPEFACTORY.is_range_parameter?(keys[-2]) && TYPEFACTORY.is_range_parameter?(keys[-1]) size_type = TYPEFACTORY.range(keys[-2], keys[-1]) keys = keys[0, keys.size - 2] elsif TYPEFACTORY.is_range_parameter?(keys[-1]) size_type = TYPEFACTORY.range(keys[-1], :default) keys = keys[0, keys.size - 1] end assert_keys(keys, o, 1, INFINITY, Puppet::Pops::Types::PAnyType) t = Puppet::Pops::Types::TypeFactory.tuple(*keys) # set size type, or nil for default (exactly 1) t.size_type = size_type t end def access_PCallableType(o, scope, keys) TYPEFACTORY.callable(*keys) end def access_PStructType(o, scope, keys) assert_keys(keys, o, 1, 1, Hash) TYPEFACTORY.struct(keys[0]) end def access_PStringType(o, scope, keys) keys.flatten! case keys.size when 1 size_t = collection_size_t(0, keys[0]) when 2 size_t = collection_size_t(0, keys[0], keys[1]) else fail(Puppet::Pops::Issues::BAD_STRING_SLICE_ARITY, @semantic, {:actual => keys.size}) end string_t = Puppet::Pops::Types::TypeFactory.string() string_t.size_type = size_t string_t end # Asserts type of each key and calls fail with BAD_TYPE_SPECIFICATION # @param keys [Array] the evaluated keys # @param o [Object] evaluated LHS reported as :base_type # @param min [Integer] the minimum number of keys (typically 1) # @param max [Numeric] the maximum number of keys (use same as min, specific number, or INFINITY) # @param allowed_classes [Class] a variable number of classes that each key must be an instance of (any) # @api private # def assert_keys(keys, o, min, max, *allowed_classes) size = keys.size unless size.between?(min, max || INFINITY) fail(Puppet::Pops::Issues::BAD_TYPE_SLICE_ARITY, @semantic, :base_type => o, :min=>1, :max => max, :actual => keys.size) end keys.each_with_index do |k, i| unless allowed_classes.any? {|clazz| k.is_a?(clazz) } bad_type_specialization_key_type(o, i, k, *allowed_classes) end end end def bad_access_key_type(lhs, key_index, actual, *expected_classes) fail(Puppet::Pops::Issues::BAD_SLICE_KEY_TYPE, @semantic.keys[key_index], { :left_value => lhs, :actual => bad_key_type_name(actual), :expected_classes => expected_classes }) end def bad_key_type_name(actual) case actual when nil 'Undef' when :default 'Default' else actual.class.name end end def bad_type_specialization_key_type(type, key_index, actual, *expected_classes) label_provider = Puppet::Pops::Model::ModelLabelProvider.new() expected = expected_classes.map {|c| label_provider.label(c) }.join(' or ') fail(Puppet::Pops::Issues::BAD_TYPE_SPECIALIZATION, @semantic.keys[key_index], { :type => type, :message => "Cannot use #{bad_key_type_name(actual)} where #{expected} is expected" }) end def access_PPatternType(o, scope, keys) keys.flatten! assert_keys(keys, o, 1, INFINITY, String, Regexp, Puppet::Pops::Types::PPatternType, Puppet::Pops::Types::PRegexpType) Puppet::Pops::Types::TypeFactory.pattern(*keys) end def access_POptionalType(o, scope, keys) keys.flatten! if keys.size == 1 unless keys[0].is_a?(Puppet::Pops::Types::PAnyType) fail(Puppet::Pops::Issues::BAD_TYPE_SLICE_TYPE, @semantic.keys[0], {:base_type => 'Optional-Type', :actual => keys[0].class}) end result = Puppet::Pops::Types::POptionalType.new() result.optional_type = keys[0] result else fail(Puppet::Pops::Issues::BAD_TYPE_SLICE_ARITY, @semantic, {:base_type => 'Optional-Type', :min => 1, :actual => keys.size}) end end def access_PType(o, scope, keys) keys.flatten! if keys.size == 1 unless keys[0].is_a?(Puppet::Pops::Types::PAnyType) fail(Puppet::Pops::Issues::BAD_TYPE_SLICE_TYPE, @semantic.keys[0], {:base_type => 'Type-Type', :actual => keys[0].class}) end result = Puppet::Pops::Types::PType.new() result.type = keys[0] result else fail(Puppet::Pops::Issues::BAD_TYPE_SLICE_ARITY, @semantic, {:base_type => 'Type-Type', :min => 1, :actual => keys.size}) end end def access_PRuntimeType(o, scope, keys) keys.flatten! assert_keys(keys, o, 2, 2, String, String) # create runtime type based on runtime and name of class, (not inference of key's type) Puppet::Pops::Types::TypeFactory.runtime(*keys) end def access_PIntegerType(o, scope, keys) keys.flatten! unless keys.size.between?(1, 2) fail(Puppet::Pops::Issues::BAD_INTEGER_SLICE_ARITY, @semantic, {:actual => keys.size}) end keys.each_with_index do |x, index| fail(Puppet::Pops::Issues::BAD_INTEGER_SLICE_TYPE, @semantic.keys[index], {:actual => x.class}) unless (x.is_a?(Integer) || x == :default) end ranged_integer = Puppet::Pops::Types::PIntegerType.new() from, to = keys ranged_integer.from = from == :default ? nil : from ranged_integer.to = to == :default ? nil : to ranged_integer end def access_PFloatType(o, scope, keys) keys.flatten! unless keys.size.between?(1, 2) fail(Puppet::Pops::Issues::BAD_FLOAT_SLICE_ARITY, @semantic, {:actual => keys.size}) end keys.each_with_index do |x, index| fail(Puppet::Pops::Issues::BAD_FLOAT_SLICE_TYPE, @semantic.keys[index], {:actual => x.class}) unless (x.is_a?(Float) || x.is_a?(Integer) || x == :default) end ranged_float = Puppet::Pops::Types::PFloatType.new() from, to = keys ranged_float.from = from == :default || from.nil? ? nil : Float(from) ranged_float.to = to == :default || to.nil? ? nil : Float(to) ranged_float end # A Hash can create a new Hash type, one arg sets value type, two args sets key and value type in new type. # With 3 or 4 arguments, these are used to create a size constraint. # It is not possible to create a collection of Hash types directly. # def access_PHashType(o, scope, keys) keys.flatten! keys[0,2].each_with_index do |k, index| unless k.is_a?(Puppet::Pops::Types::PAnyType) fail(Puppet::Pops::Issues::BAD_TYPE_SLICE_TYPE, @semantic.keys[index], {:base_type => 'Hash-Type', :actual => k.class}) end end case keys.size when 1 result = Puppet::Pops::Types::PHashType.new() result.key_type = o.key_type.copy result.element_type = keys[0] result when 2 result = Puppet::Pops::Types::PHashType.new() result.key_type = keys[0] result.element_type = keys[1] result when 3 result = Puppet::Pops::Types::PHashType.new() result.key_type = keys[0] result.element_type = keys[1] size_t = collection_size_t(1, keys[2]) result when 4 result = Puppet::Pops::Types::PHashType.new() result.key_type = keys[0] result.element_type = keys[1] size_t = collection_size_t(1, keys[2], keys[3]) result else fail(Puppet::Pops::Issues::BAD_TYPE_SLICE_ARITY, @semantic, { :base_type => 'Hash-Type', :min => 1, :max => 4, :actual => keys.size }) end result.size_type = size_t if size_t result end # CollectionType is parameterized with a range def access_PCollectionType(o, scope, keys) keys.flatten! case keys.size when 1 size_t = collection_size_t(1, keys[0]) when 2 size_t = collection_size_t(1, keys[0], keys[1]) else fail(Puppet::Pops::Issues::BAD_TYPE_SLICE_ARITY, @semantic, {:base_type => 'Collection-Type', :min => 1, :max => 2, :actual => keys.size}) end result = Puppet::Pops::Types::PCollectionType.new() result.size_type = size_t result end # An Array can create a new Array type. It is not possible to create a collection of Array types. # def access_PArrayType(o, scope, keys) keys.flatten! case keys.size when 1 size_t = nil when 2 size_t = collection_size_t(1, keys[1]) when 3 size_t = collection_size_t(1, keys[1], keys[2]) else fail(Puppet::Pops::Issues::BAD_TYPE_SLICE_ARITY, @semantic, {:base_type => 'Array-Type', :min => 1, :max => 3, :actual => keys.size}) end unless keys[0].is_a?(Puppet::Pops::Types::PAnyType) fail(Puppet::Pops::Issues::BAD_TYPE_SLICE_TYPE, @semantic.keys[0], {:base_type => 'Array-Type', :actual => keys[0].class}) end result = Puppet::Pops::Types::PArrayType.new() result.element_type = keys[0] result.size_type = size_t result end # Produces an PIntegerType (range) given one or two keys. def collection_size_t(start_index, *keys) if keys.size == 1 && keys[0].is_a?(Puppet::Pops::Types::PIntegerType) keys[0].copy else keys.each_with_index do |x, index| fail(Puppet::Pops::Issues::BAD_COLLECTION_SLICE_TYPE, @semantic.keys[start_index + index], {:actual => x.class}) unless (x.is_a?(Integer) || x == :default) end ranged_integer = Puppet::Pops::Types::PIntegerType.new() from, to = keys ranged_integer.from = from == :default ? nil : from ranged_integer.to = to == :default ? nil : to ranged_integer end end # A Puppet::Resource represents either just a type (no title), or is a fully qualified type/title. # def access_Resource(o, scope, keys) # To access a Puppet::Resource as if it was a PResourceType, simply infer it, and take the type of # the parameterized meta type (i.e. Type[Resource[the_resource_type, the_resource_title]]) t = Puppet::Pops::Types::TypeCalculator.infer(o).type # must map "undefined title" from resource to nil t.title = nil if t.title == EMPTY_STRING access(t, scope, *keys) end # A Resource can create a new more specific Resource type, and/or an array of resource types # If the given type has title set, it can not be specified further. # @example # Resource[File] # => File # Resource[File, 'foo'] # => File[foo] # Resource[File. 'foo', 'bar'] # => [File[foo], File[bar]] # File['foo', 'bar'] # => [File[foo], File[bar]] # File['foo']['bar'] # => Value of the 'bar' parameter in the File['foo'] resource # Resource[File]['foo', 'bar'] # => [File[Foo], File[bar]] # Resource[File, 'foo', 'bar'] # => [File[foo], File[bar]] # Resource[File, 'foo']['bar'] # => Value of the 'bar' parameter in the File['foo'] resource # def access_PResourceType(o, scope, keys) blamed = keys.size == 0 ? @semantic : @semantic.keys[0] if keys.size == 0 fail(Puppet::Pops::Issues::BAD_TYPE_SLICE_ARITY, blamed, :base_type => Puppet::Pops::Types::TypeCalculator.new().string(o), :min => 1, :max => -1, :actual => 0) end # Must know which concrete resource type to operate on in all cases. # It is not allowed to specify the type in an array arg - e.g. Resource[[File, 'foo']] # type_name is LHS type_name if set, else the first given arg type_name = o.type_name || keys.shift type_name = case type_name when Puppet::Pops::Types::PResourceType type_name.type_name when String type_name.downcase else # blame given left expression if it defined the type, else the first given key expression blame = o.type_name.nil? ? @semantic.keys[0] : @semantic.left_expr fail(Puppet::Pops::Issues::ILLEGAL_RESOURCE_SPECIALIZATION, blame, {:actual => type_name.class}) end + # type name must conform + if type_name !~ Puppet::Pops::Patterns::CLASSREF + fail(Puppet::Pops::Issues::ILLEGAL_CLASSREF, blamed, {:name=>type_name}) + end + # The result is an array if multiple titles are given, or if titles are specified with an array # (possibly multiple arrays, and nested arrays). result_type_array = keys.size > 1 || keys[0].is_a?(Array) keys_orig_size = keys.size keys.flatten! keys.compact! # If given keys that were just a mix of empty/nil with empty array as a result. # As opposed to calling the function the wrong way (without any arguments), (configurable issue), # Return an empty array # if keys.empty? && keys_orig_size > 0 optionally_fail(Puppet::Pops::Issues::EMPTY_RESOURCE_SPECIALIZATION, blamed) return result_type_array ? [] : nil end if !o.title.nil? # lookup resource and return one or more parameter values resource = find_resource(scope, o.type_name, o.title) unless resource fail(Puppet::Pops::Issues::UNKNOWN_RESOURCE, @semantic, {:type_name => o.type_name, :title => o.title}) end result = keys.map do |k| unless is_parameter_of_resource?(scope, resource, k) fail(Puppet::Pops::Issues::UNKNOWN_RESOURCE_PARAMETER, @semantic, {:type_name => o.type_name, :title => o.title, :param_name=>k}) end get_resource_parameter_value(scope, resource, k) end return result_type_array ? result : result.pop end keys = [:no_title] if keys.size < 1 # if there was only a type_name and it was consumed result = keys.each_with_index.map do |t, i| unless t.is_a?(String) || t == :no_title type_to_report = case t when nil 'Undef' when :default 'Default' else t.class.name end index = keys_orig_size != keys.size ? i+1 : i fail(Puppet::Pops::Issues::BAD_TYPE_SPECIALIZATION, @semantic.keys[index], { :type => o, :message => "Cannot use #{type_to_report} where String is expected" }) end rtype = Puppet::Pops::Types::PResourceType.new() rtype.type_name = type_name rtype.title = (t == :no_title ? nil : t) rtype end # returns single type if request was for a single entity, else an array of types (possibly empty) return result_type_array ? result : result.pop end def access_PHostClassType(o, scope, keys) blamed = keys.size == 0 ? @semantic : @semantic.keys[0] keys_orig_size = keys.size if keys_orig_size == 0 fail(Puppet::Pops::Issues::BAD_TYPE_SLICE_ARITY, blamed, :base_type => Puppet::Pops::Types::TypeCalculator.new().string(o), :min => 1, :max => -1, :actual => 0) end # The result is an array if multiple classnames are given, or if classnames are specified with an array # (possibly multiple arrays, and nested arrays). result_type_array = keys.size > 1 || keys[0].is_a?(Array) keys.flatten! keys.compact! # If given keys that were just a mix of empty/nil with empty array as a result. # As opposed to calling the function the wrong way (without any arguments), (configurable issue), # Return an empty array # if keys.empty? && keys_orig_size > 0 optionally_fail(Puppet::Pops::Issues::EMPTY_RESOURCE_SPECIALIZATION, blamed) return result_type_array ? [] : nil end if o.class_name.nil? # The type argument may be a Resource Type - the Puppet Language allows a reference such as # Class[Foo], and this is interpreted as Class[Resource[Foo]] - which is ok as long as the resource # does not have a title. This should probably be deprecated. # result = keys.each_with_index.map do |c, i| name = if c.is_a?(Puppet::Pops::Types::PResourceType) && !c.type_name.nil? && c.title.nil? # type_name is already downcase. Don't waste time trying to downcase again c.type_name elsif c.is_a?(String) c.downcase else fail(Puppet::Pops::Issues::ILLEGAL_HOSTCLASS_NAME, @semantic.keys[i], {:name => c}) end if name =~ Puppet::Pops::Patterns::NAME ctype = Puppet::Pops::Types::PHostClassType.new() # Remove leading '::' since all references are global, and 3x runtime does the wrong thing ctype.class_name = name.sub(/^::/, EMPTY_STRING) ctype else fail(Issues::ILLEGAL_NAME, @semantic.keys[i], {:name=>c}) end end else # lookup class resource and return one or more parameter values resource = find_resource(scope, 'class', o.class_name) if resource result = keys.map do |k| if is_parameter_of_resource?(scope, resource, k) get_resource_parameter_value(scope, resource, k) else fail(Puppet::Pops::Issues::UNKNOWN_RESOURCE_PARAMETER, @semantic, {:type_name => 'Class', :title => o.class_name, :param_name=>k}) end end else fail(Puppet::Pops::Issues::UNKNOWN_RESOURCE, @semantic, {:type_name => 'Class', :title => o.class_name}) end end # returns single type as type, else an array of types return result_type_array ? result : result.pop end end diff --git a/lib/puppet/pops/evaluator/evaluator_impl.rb b/lib/puppet/pops/evaluator/evaluator_impl.rb index 15644bbff..b16a24df1 100644 --- a/lib/puppet/pops/evaluator/evaluator_impl.rb +++ b/lib/puppet/pops/evaluator/evaluator_impl.rb @@ -1,1039 +1,1134 @@ 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 EMPTY_STRING = ''.freeze COMMA_SEPARATOR = ', '.freeze # 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 + # Evaluates the given _target_ object in the given scope. # - # ## 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}) + # @overload evaluate(target, scope) # @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 + # @api public # def evaluate(target, scope) begin @@eval_visitor.visit_this_1(self, target, scope) rescue Puppet::Pops::SemanticError => e # a raised issue may not know the semantic target fail(e.issue, e.semantic || target, e.options, e) rescue StandardError => e if e.is_a? Puppet::ParseError # ParseError's are supposed to be fully configured with location information raise e end fail(Issues::RUNTIME_ERROR, target, {:detail => e.message}, e) end end - # Polymorphic assign - calls assign_TYPE - # - # ## Polymorphic assign - # Polymorphic assign calls a method on the format assign_TYPE where TYPE is the last - # part of the class of the given _target_. A search is performed starting with the actual class, continuing - # with each of the _target_ class's super classes until a matching method is found. - # - # # Description # Assigns the given _value_ to the given _target_. The additional argument _o_ is the instruction that # produced the target/value tuple and it is used to set the origin of the result. + # # @param target [Object] assignment target - see methods on the pattern assign_TYPE for actual supported types. # @param value [Object] the value to assign to `target` # @param o [Puppet::Pops::Model::PopsObject] originating instruction # @param scope [Object] the runtime specific scope where evaluation should take place # - # @api + # @api private # def assign(target, value, o, scope) @@assign_visitor.visit_this_3(self, target, value, o, scope) end + # Computes a value that can be used as the LHS in an assignment. + # @param o [Object] the expression to evaluate as a left (assignable) entity + # @param scope [Object] the runtime specific scope where evaluation should take place + # + # @api private + # def lvalue(o, scope) @@lvalue_visitor.visit_this_1(self, o, scope) end + # Produces a String representation of the given object _o_ as used in interpolation. + # @param o [Object] the expression of which a string representation is wanted + # @param scope [Object] the runtime specific scope where evaluation should take place + # + # @api public + # def string(o, scope) @@string_visitor.visit_this_1(self, o, scope) end # Evaluate a BlockExpression in a new scope with variables bound to the # given values. # # @param scope [Puppet::Parser::Scope] the parent scope # @param variable_bindings [Hash{String => Object}] the variable names and values to bind (names are keys, bound values are values) # @param block [Puppet::Pops::Model::BlockExpression] the sequence of expressions to evaluate in the new scope + # + # @api private + # def evaluate_block_with_bindings(scope, variable_bindings, block_expr) with_guarded_scope(scope) do # change to create local scope_from - cannot give it file and line - # that is the place of the call, not "here" create_local_scope_from(variable_bindings, scope) evaluate(block_expr, scope) end 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 def eval_NilClass(o, scope) nil end # Evaluates Nop to nil. def eval_Nop(o, scope) nil end # Captures all LiteralValues not handled elsewhere. # def eval_LiteralValue(o, scope) o.value end # Reserved Words fail to evaluate # def eval_ReservedWord(o, scope) fail(Puppet::Pops::Issues::RESERVED_WORD, o, {:word => o.word}) end def eval_LiteralDefault(o, scope) :default end def eval_LiteralUndef(o, scope) nil end # A QualifiedReference (i.e. a capitalized qualified name such as Foo, or Foo::Bar) evaluates to a PType # def eval_QualifiedReference(o, scope) @@type_parser.interpret(o) end def eval_NotExpression(o, scope) ! is_true?(evaluate(o.expr, scope)) end def eval_UnaryMinusExpression(o, scope) - coerce_numeric(evaluate(o.expr, scope), o, scope) end def eval_UnfoldExpression(o, scope) candidate = evaluate(o.expr, scope) case candidate when Array candidate when Hash candidate.to_a else # turns anything else into an array (so result can be unfolded) [candidate] end end # Abstract evaluation, returns array [left, right] with the evaluated result of left_expr and # right_expr # @return > array with result of evaluating left and right expressions # def eval_BinaryExpression o, scope [ evaluate(o.left_expr, scope), evaluate(o.right_expr, scope) ] end # Evaluates assignment with operators =, +=, -= and # # @example Puppet DSL # $a = 1 # $a += 1 # $a -= 1 # def eval_AssignmentExpression(o, scope) name = lvalue(o.left_expr, scope) value = evaluate(o.right_expr, scope) case o.operator when :'=' # regular assignment assign(name, value, o, scope) when :'+=' # if value does not exist and strict is on, looking it up fails, else it is nil or :undef existing_value = get_variable_value(name, o, scope) begin # Supports :undef as it may come from a 3x structure. 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 # Supports :undef as it may come from a 3x structure. if existing_value.nil? || existing_value == :undef assign(name, nil, 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 = evaluate(o.left_expr, scope) right = evaluate(o.right_expr, 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 = evaluate(o.left_expr, scope) right = evaluate(o.right_expr, scope) begin # Left is a type if left.is_a?(Puppet::Pops::Types::PAnyType) case o.operator when :'==' @@type_calculator.equals(left,right) when :'!=' !@@type_calculator.equals(left,right) when :'<' # left can be assigned to right, but they are not equal @@type_calculator.assignable?(right, left) && ! @@type_calculator.equals(left,right) when :'<=' # left can be assigned to right @@type_calculator.assignable?(right, left) when :'>' # right can be assigned to left, but they are not equal @@type_calculator.assignable?(left,right) && ! @@type_calculator.equals(left,right) when :'>=' # right can be assigned to left @@type_calculator.assignable?(left, right) else fail(Issues::UNSUPPORTED_OPERATOR, o, {:operator => o.operator}) end else case o.operator when :'==' @@compare_operator.equals(left,right) when :'!=' ! @@compare_operator.equals(left,right) when :'<' @@compare_operator.compare(left,right) < 0 when :'<=' @@compare_operator.compare(left,right) <= 0 when :'>' @@compare_operator.compare(left,right) > 0 when :'>=' @@compare_operator.compare(left,right) >= 0 else fail(Issues::UNSUPPORTED_OPERATOR, o, {:operator => o.operator}) end end rescue ArgumentError => e fail(Issues::COMPARISON_NOT_POSSIBLE, o, { :operator => o.operator, :left_value => left, :right_value => right, :detail => e.message}, e) end end # Evaluates matching expressions with type, string or regexp rhs expression. # If RHS is a type, the =~ matches compatible (instance? of) type. # # @example # x =~ /abc.*/ # @example # x =~ "abc.*/" # @example # y = "abc" # x =~ "${y}.*" # @example # [1,2,3] =~ Array[Integer[1,10]] # # Note that a string is not instance? of Regexp, only Regular expressions are. # The Pattern type should instead be used as it is specified as subtype of String. # # @return [Boolean] if a match was made or not. Also sets $0..$n to matchdata in current scope. # def eval_MatchExpression o, scope left = evaluate(o.left_expr, scope) pattern = evaluate(o.right_expr, scope) # matches RHS types as instance of for all types except a parameterized Regexp[R] if pattern.is_a?(Puppet::Pops::Types::PAnyType) # evaluate as instance? of type check matched = @@type_calculator.instance?(pattern, left) # convert match result to Boolean true, or false return o.operator == :'=~' ? !!matched : !matched end begin pattern = Regexp.new(pattern) unless pattern.is_a?(Regexp) rescue StandardError => e fail(Issues::MATCH_NOT_REGEXP, o.right_expr, {:detail => e.message}, e) end unless left.is_a?(String) fail(Issues::MATCH_NOT_STRING, o.left_expr, {:left_value => left}) end matched = pattern.match(left) # nil, or MatchData set_match_data(matched,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 = evaluate(o.left_expr, scope) right = evaluate(o.right_expr, scope) @@compare_operator.include?(right, left, scope) end # @example # $a and $b # b is only evaluated if a is true # def eval_AndExpression o, scope is_true?(evaluate(o.left_expr, scope)) ? is_true?(evaluate(o.right_expr, scope)) : false end # @example # a or b # b is only evaluated if a is false # def eval_OrExpression o, scope is_true?(evaluate(o.left_expr, scope)) ? true : is_true?(evaluate(o.right_expr, scope)) end # Evaluates each entry of the literal list and creates a new Array # Supports unfolding of entries # @return [Array] with the evaluated content # def eval_LiteralList o, scope unfold([], o.values, scope) end # Evaluates each entry of the literal hash and creates a new Hash. # @return [Hash] with the evaluated content # def eval_LiteralHash o, scope # optimized o.entries.reduce({}) {|h,entry| h[evaluate(entry.key, scope)] = evaluate(entry.value, scope); h } end # Evaluates all statements and produces the last evaluated value # def eval_BlockExpression o, scope r = nil o.statements.each {|s| r = evaluate(s, scope)} r end # Performs optimized search over case option values, lazily evaluating each # until there is a match. If no match is found, the case expression's default expression # is evaluated (it may be nil or Nop if there is no default, thus producing nil). # If an option matches, the result of evaluating that option is returned. # @return [Object, nil] what a matched option returns, or nil if nothing matched. # def eval_CaseExpression(o, scope) # memo scope level before evaluating test - don't want a match in the case test to leak $n match vars # to expressions after the case expression. # with_guarded_scope(scope) do test = evaluate(o.test, scope) result = nil the_default = nil if o.options.find do |co| # the first case option that matches if co.values.find do |c| case c when Puppet::Pops::Model::LiteralDefault the_default = co.then_expr is_match?(test, evaluate(c, scope), c, scope) when Puppet::Pops::Model::UnfoldExpression # not ideal for error reporting, since it is not known which unfolded result # that caused an error - the entire unfold expression is blamed (i.e. the var c, passed to is_match?) evaluate(c, scope).any? {|v| is_match?(test, v, c, scope) } else is_match?(test, evaluate(c, scope), c, scope) end end result = evaluate(co.then_expr, scope) true # the option was picked end end result # an option was picked, and produced a result else evaluate(the_default, scope) # evaluate the default (should be a nop/nil) if there is no default). end end end # Evaluates a CollectExpression by transforming it into a 3x AST::Collection and then evaluating that. # This is done because of the complex API between compiler, indirector, backends, and difference between # collecting virtual resources and exported resources. # def eval_CollectExpression o, scope # The Collect Expression and its contained query expressions are implemented in such a way in # 3x that it is almost impossible to do anything about them (the AST objects are lazily evaluated, # and the built structure consists of both higher order functions and arrays with query expressions # that are either used as a predicate filter, or given to an indirection terminus (such as the Puppet DB # resource terminus). Unfortunately, the 3x implementation has many inconsistencies that the implementation # below carries forward. # collect_3x = Puppet::Pops::Model::AstTransformer.new().transform(o) collected = collect_3x.evaluate(scope) # the 3x returns an instance of Parser::Collector (but it is only registered with the compiler at this # point and does not contain any valuable information (like the result) # Dilemma: If this object is returned, it is a first class value in the Puppet Language and we # need to be able to perform operations on it. We can forbid it from leaking by making CollectExpression # a non R-value. This makes it possible for the evaluator logic to make use of the Collector. collected end def eval_ParenthesizedExpression(o, scope) evaluate(o.expr, scope) end # This evaluates classes, nodes and resource type definitions to nil, since 3x: # instantiates them, and evaluates their parameters and body. This is achieved by # providing bridge AST classes in Puppet::Parser::AST::PopsBridge that bridges a # Pops Program and a Pops Expression. # # Since all Definitions are handled "out of band", they are treated as a no-op when # evaluated. # def eval_Definition(o, scope) nil end def eval_Program(o, scope) evaluate(o.body, scope) end # Produces Array[PAnyType], an array of resource references # def eval_ResourceExpression(o, scope) - case o.form - when :exported - exported = true - virtual = true - when :virtual - exported = false - virtual = true + exported = o.exported + virtual = o.virtual + + # Get the type name + type_name = + if (tmp_name = o.type_name).is_a?(Puppet::Pops::Model::QualifiedName) + tmp_name.value # already validated as a name else - exported = virtual = false + evaluated_name = evaluate(tmp_name, scope) + + # must be String or Resource Type + case evaluated_name + when String + resulting_name = evaluated_name.downcase + if resulting_name !~ Puppet::Pops::Patterns::CLASSREF + fail(Puppet::Pops::Issues::ILLEGAL_CLASSREF, o.type_name, {:name=>resulting_name}) + end + resulting_name + + when Puppet::Pops::Types::PHostClassType + unless evaluated_name.class_name.nil? + fail(Puppet::Pops::Issues::ILLEGAL_RESOURCE_TYPE, o.type_name, {:actual=> evaluated_name.to_s}) + end + 'class' + + when Puppet::Pops::Types::PResourceType + unless evaluated_name.title().nil? + fail(Puppet::Pops::Issues::ILLEGAL_RESOURCE_TYPE, o.type_name, {:actual=> evaluated_name.to_s}) + end + evaluated_name.type_name # assume validated + + else + actual = type_calculator.generalize!(type_calculator.infer(evaluated_name)).to_s + fail(Puppet::Pops::Issues::ILLEGAL_RESOURCE_TYPE, o.type_name, {:actual=>actual}) + end + end + + titles_to_body = {} + body_to_titles = {} + body_to_params = {} + + # titles are evaluated before attribute operations + o.bodies.map do | body | + titles = evaluate(body.title, scope) + + # Title may not be nil + # Titles may be given as an array, it is ok if it is empty, but not if it contains nil entries + # Titles may not be an empty String + # Titles must be unique in the same resource expression + # There may be a :default entry, its entries apply with lower precedence + # + if titles.nil? + fail(Puppet::Pops::Issues::MISSING_TITLE, body.title) + end + titles = [titles].flatten + + # Check types of evaluated titles and duplicate entries + titles.each_with_index do |title, index| + if title.nil? + fail(Puppet::Pops::Issues::MISSING_TITLE_AT, body.title, {:index => index}) + + elsif !title.is_a?(String) && title != :default + actual = type_calculator.generalize!(type_calculator.infer(title)).to_s + fail(Puppet::Pops::Issues::ILLEGAL_TITLE_TYPE_AT, body.title, {:index => index, :actual => actual}) + + elsif title == EMPTY_STRING + fail(Puppet::Pops::Issues::EMPTY_STRING_TITLE_AT, body.title, {:index => index}) + + elsif titles_to_body[title] + fail(Puppet::Pops::Issues::DUPLICATE_TITLE, o, {:title => title}) + end + titles_to_body[title] = body + end + + # Do not create a real instance from the :default case + titles.delete(:default) + + body_to_titles[body] = titles + + # Store evaluated parameters in a hash associated with the body, but do not yet create resource + # since the entry containing :defaults may appear later + body_to_params[body] = body.operations.reduce({}) do |param_memo, op| + params = evaluate(op, scope) + params = [params] unless params.is_a?(Array) + params.each { |p| param_memo[p.name] = p } + param_memo + end end - 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) + + # Titles and Operations have now been evaluated and resources can be created + # Each production is a PResource, and an array of all is produced as the result of + # evaluating the ResourceExpression. + # + defaults_hash = body_to_params[titles_to_body[:default]] || {} + o.bodies.map do | body | + titles = body_to_titles[body] + params = defaults_hash.merge(body_to_params[body] || {}) + create_resources(o, scope, virtual, exported, type_name, titles, params.values) 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 + # Produces 3x parameter def eval_AttributeOperation(o, scope) create_resource_parameter(o, scope, o.attribute_name, evaluate(o.value_expr, scope), o.operator) end + def eval_AttributesOperation(o, scope) + hashed_params = evaluate(o.expr, scope) + unless hashed_params.is_a?(Hash) + actual = type_calculator.generalize!(type_calculator.infer(hashed_params)).to_s + fail(Puppet::Pops::Issues::TYPE_MISMATCH, o.expr, {:expected => 'Hash', :actual => actual}) + end + hashed_params.map { |k,v| create_resource_parameter(o, scope, k, v, :'=>') } + 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 + type = evaluate(o.type_ref, scope) + type_name = + if type.is_a?(Puppet::Pops::Types::PResourceType) && !type.type_name.nil? && type.title.nil? + type.type_name # assume it is a valid name + else + actual = type_calculator.generalize!(type_calculator.infer(type)) + fail(Issues::ILLEGAL_RESOURCE_TYPE, o.type_ref, {:actual => actual}) + end 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) + type end # Evaluates function call by name. # def eval_CallNamedFunctionExpression(o, scope) # The functor expression is not evaluated, it is not possible to select the function to call # via an expression like $a() case o.functor_expr when Puppet::Pops::Model::QualifiedName # ok when Puppet::Pops::Model::RenderStringExpression # helpful to point out this easy to make Epp error fail(Issues::ILLEGAL_EPP_PARAMETERS, o) else fail(Issues::ILLEGAL_EXPRESSION, o.functor_expr, {:feature=>'function name', :container => o}) end name = o.functor_expr.value evaluated_arguments = unfold([], o.arguments, scope) # wrap lambda in a callable block if it is present evaluated_arguments << Puppet::Pops::Evaluator::Closure.new(self, o.lambda, scope) if o.lambda call_function(name, evaluated_arguments, o, scope) end # Evaluation of CallMethodExpression handles a NamedAccessExpression functor (receiver.function_name) # def eval_CallMethodExpression(o, scope) unless o.functor_expr.is_a? Puppet::Pops::Model::NamedAccessExpression fail(Issues::ILLEGAL_EXPRESSION, o.functor_expr, {:feature=>'function accessor', :container => o}) end receiver = evaluate(o.functor_expr.left_expr, scope) name = o.functor_expr.right_expr unless name.is_a? Puppet::Pops::Model::QualifiedName fail(Issues::ILLEGAL_EXPRESSION, o.functor_expr, {:feature=>'function name', :container => o}) end name = name.value # the string function name evaluated_arguments = unfold([receiver], o.arguments || [], scope) # wrap lambda in a callable block if it is present evaluated_arguments << Puppet::Pops::Evaluator::Closure.new(self, o.lambda, scope) if o.lambda call_function(name, evaluated_arguments, o, scope) end # @example # $x ? { 10 => true, 20 => false, default => 0 } # def eval_SelectorExpression o, scope # memo scope level before evaluating test - don't want a match in the case test to leak $n match vars # to expressions after the selector expression. # with_guarded_scope(scope) do test = evaluate(o.left_expr, scope) the_default = nil selected = o.selectors.find do |s| me = s.matching_expr case me when Puppet::Pops::Model::LiteralDefault the_default = s.value_expr false when Puppet::Pops::Model::UnfoldExpression # not ideal for error reporting, since it is not known which unfolded result # that caused an error - the entire unfold expression is blamed (i.e. the var c, passed to is_match?) evaluate(me, scope).any? {|v| is_match?(test, v, me, scope) } else is_match?(test, evaluate(me, scope), me, scope) end end if selected evaluate(selected.value_expr, scope) elsif the_default evaluate(the_default, scope) else fail(Issues::UNMATCHED_SELECTOR, o.left_expr, :param_value => test) end end end # SubLocatable is simply an expression that holds location information def eval_SubLocatedExpression o, scope evaluate(o.expr, scope) end # Evaluates Puppet DSL Heredoc def eval_HeredocExpression o, scope result = evaluate(o.text_expr, scope) assert_external_syntax(scope, result, o.syntax, o.text_expr) result end # Evaluates Puppet DSL `if` def eval_IfExpression o, scope with_guarded_scope(scope) do if is_true?(evaluate(o.test, scope)) evaluate(o.then_expr, scope) else evaluate(o.else_expr, scope) end end end # Evaluates Puppet DSL `unless` def eval_UnlessExpression o, scope with_guarded_scope(scope) do unless is_true?(evaluate(o.test, scope)) evaluate(o.then_expr, scope) else evaluate(o.else_expr, scope) end end end # Evaluates a variable (getting its value) # The evaluator is lenient; any expression producing a String is used as a name # of a variable. # def eval_VariableExpression o, scope # Evaluator is not too fussy about what constitutes a name as long as the result # is a String and a valid variable name # name = evaluate(o.expr, scope) # Should be caught by validation, but make this explicit here as well, or mysterious evaluation issues - # may occur. + # may occur for some evaluation use cases. 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) if :undef == o # optimized comparison 1.44 vs 1.95 EMPTY_STRING else o.to_s end end def string_Array(o, scope) "[#{o.map {|e| string(e, scope)}.join(COMMA_SEPARATOR)}]" end def string_Hash(o, scope) "{#{o.map {|k,v| "#{string(k, scope)} => #{string(v, scope)}"}.join(COMMA_SEPARATOR)}}" end def string_Regexp(o, scope) "/#{o.source}/" end def string_PAnyType(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, scope) # creates or clears ephemeral !!matched # convert to boolean elsif right.is_a?(Puppet::Pops::Types::PAnyType) # right is a type and left is not - check if left is an instance of the given type # (The reverse is not terribly meaningful - computing which of the case options that first produces # an instance of a given type). # @@type_calculator.instance?(right, left) else # Handle equality the same way as the language '==' operator (case insensitive etc.) @@compare_operator.equals(left,right) end end def with_guarded_scope(scope) scope_memo = get_scope_nesting_level(scope) begin yield ensure set_scope_nesting_level(scope, scope_memo) end end # Maps the expression in the given array to their product except for UnfoldExpressions which are first unfolded. # The result is added to the given result Array. # @param result [Array] Where to add the result (may contain information to add to) # @param array [Array[Puppet::Pops::Model::Expression] the expressions to map # @param scope [Puppet::Parser::Scope] the scope to evaluate in # @return [Array] the given result array with content added from the operation # def unfold(result, array, scope) array.each do |x| if x.is_a?(Puppet::Pops::Model::UnfoldExpression) result.concat(evaluate(x, scope)) else result << evaluate(x, scope) end end result end private :unfold end diff --git a/lib/puppet/pops/evaluator/relationship_operator.rb b/lib/puppet/pops/evaluator/relationship_operator.rb index 223c874b0..866afa8ef 100644 --- a/lib/puppet/pops/evaluator/relationship_operator.rb +++ b/lib/puppet/pops/evaluator/relationship_operator.rb @@ -1,158 +1,157 @@ # The RelationshipOperator implements the semantics of the -> <- ~> <~ operators creating relationships or notification # relationships between the left and right hand side's references to resources. # # This is separate class since a second level of evaluation is required that transforms string in left or right hand # to type references. The task of "making a relationship" is delegated to the "runtime support" class that is included. # This is done to separate the concerns of the new evaluator from the 3x runtime; messy logic goes into the runtime support # module. Later when more is cleaned up this can be simplified further. # class Puppet::Pops::Evaluator::RelationshipOperator # Provides access to the Puppet 3.x runtime (scope, etc.) # This separation has been made to make it easier to later migrate the evaluator to an improved runtime. # include Puppet::Pops::Evaluator::Runtime3Support Issues = Puppet::Pops::Issues class IllegalRelationshipOperandError < RuntimeError attr_reader :operand def initialize operand @operand = operand end end class NotCatalogTypeError < RuntimeError attr_reader :type def initialize type @type = type end end def initialize @type_transformer_visitor = Puppet::Pops::Visitor.new(self, "transform", 1, 1) @type_calculator = Puppet::Pops::Types::TypeCalculator.new() @type_parser = Puppet::Pops::Types::TypeParser.new() tf = Puppet::Pops::Types::TypeFactory @catalog_type = tf.variant(tf.catalog_entry, tf.type_type(tf.catalog_entry)) end def transform(o, scope) @type_transformer_visitor.visit_this_1(self, o, scope) end # Catch all non transformable objects # @api private def transform_Object(o, scope) raise IllegalRelationshipOperandError.new(o) end # A string must be a type reference in string format # @api private def transform_String(o, scope) assert_catalog_type(@type_parser.parse(o), scope) end # A qualified name is short hand for a class with this name # @api private def transform_QualifiedName(o, scope) Puppet::Pops::Types::TypeFactory.host_class(o.value) end # Types are what they are, just check the type # @api private def transform_PAnyType(o, scope) assert_catalog_type(o, scope) end # This transforms a 3x Collector (the result of evaluating a 3x AST::Collection). # It is passed through verbatim since it is evaluated late by the compiler. At the point # where the relationship is evaluated, it is simply recorded with the compiler for later evaluation. # If one of the sides of the relationship is a Collector it is evaluated before the actual # relationship is formed. (All of this happens at a later point in time. # def transform_Collector(o, scope) o end # Array content needs to be transformed def transform_Array(o, scope) o.map{|x| transform(x, scope) } end # Asserts (and returns) the type if it is a PCatalogEntryType # (A PCatalogEntryType is the base class of PHostClassType, and PResourceType). # def assert_catalog_type(o, scope) unless @type_calculator.assignable?(@catalog_type, o) - require 'debugger'; debugger raise NotCatalogTypeError.new(o) end # TODO must check if this is an abstract PResourceType (i.e. without a type_name) - which should fail ? # e.g. File -> File (and other similar constructs) - maybe the catalog protects against this since references # may be to future objects... o end RELATIONSHIP_OPERATORS = [:'->', :'~>', :'<-', :'<~'] REVERSE_OPERATORS = [:'<-', :'<~'] RELATION_TYPE = { :'->' => :relationship, :'<-' => :relationship, :'~>' => :subscription, :'<~' => :subscription } # Evaluate a relationship. # TODO: The error reporting is not fine grained since evaluation has already taken place # There is no references to the original source expressions at this point, only the overall # relationship expression. (e.g.. the expression may be ['string', func_call(), etc.] -> func_call()) # To implement this, the general evaluator needs to be able to track each evaluation result and associate # it with a corresponding expression. This structure should then be passed to the relationship operator. # def evaluate (left_right_evaluated, relationship_expression, scope) # assert operator (should have been validated, but this logic makes assumptions which would # screw things up royally). Better safe than sorry. unless RELATIONSHIP_OPERATORS.include?(relationship_expression.operator) fail(Issues::UNSUPPORTED_OPERATOR, relationship_expression, {:operator => relationship_expression.operator}) end begin # Turn each side into an array of types (this also asserts their type) # (note wrap in array first if value is not already an array) # # TODO: Later when objects are Puppet Runtime Objects and know their type, it will be more efficient to check/infer # the type first since a chained operation then does not have to visit each element again. This is not meaningful now # since inference needs to visit each object each time, and this is what the transformation does anyway). # # real is [left, right], and both the left and right may be a single value or an array. In each case all content # should be flattened, and then transformed to a type. left or right may also be a value that is transformed # into an array, and thus the resulting left and right must be flattened individually # Once flattened, the operands should be sets (to remove duplicate entries) # real = left_right_evaluated.collect {|x| [x].flatten.collect {|x| transform(x, scope) }} real[0].flatten! real[1].flatten! real[0].uniq! real[1].uniq! # reverse order if operator is Right to Left source, target = reverse_operator?(relationship_expression) ? real.reverse : real # Add the relationships to the catalog source.each {|s| target.each {|t| add_relationship(s, t, RELATION_TYPE[relationship_expression.operator], scope) }} # Produce the transformed source RHS (if this is a chain, this does not need to be done again) real.slice(1) rescue NotCatalogTypeError => e fail(Issues::ILLEGAL_RELATIONSHIP_OPERAND_TYPE, relationship_expression, {:type => @type_calculator.string(e.type)}) rescue IllegalRelationshipOperandError => e fail(Issues::ILLEGAL_RELATIONSHIP_OPERAND_TYPE, relationship_expression, {:operand => e.operand}) end end def reverse_operator?(o) REVERSE_OPERATORS.include?(o.operator) end end diff --git a/lib/puppet/pops/evaluator/runtime3_support.rb b/lib/puppet/pops/evaluator/runtime3_support.rb index fb16f958e..84c267775 100644 --- a/lib/puppet/pops/evaluator/runtime3_support.rb +++ b/lib/puppet/pops/evaluator/runtime3_support.rb @@ -1,550 +1,555 @@ # A module with bindings between the new evaluator and the 3x runtime. # The intention is to separate all calls into scope, compiler, resource, etc. in this module # to make it easier to later refactor the evaluator for better implementations of the 3x classes. # # @api private module Puppet::Pops::Evaluator::Runtime3Support NAME_SPACE_SEPARATOR = '::'.freeze # Fails the evaluation of _semantic_ with a given issue. # # @param issue [Puppet::Pops::Issue] the issue to report # @param semantic [Puppet::Pops::ModelPopsObject] the object for which evaluation failed in some way. Used to determine origin. # @param options [Hash] hash of optional named data elements for the given issue # @return [!] this method does not return # @raise [Puppet::ParseError] an evaluation error initialized from the arguments (TODO: Change to EvaluationError?) # def fail(issue, semantic, options={}, except=nil) optionally_fail(issue, semantic, options, except) # an error should have been raised since fail always fails raise ArgumentError, "Internal Error: Configuration of runtime error handling wrong: should have raised exception" end # Optionally (based on severity) Fails the evaluation of _semantic_ with a given issue # If the given issue is configured to be of severity < :error it is only reported, and the function returns. # # @param issue [Puppet::Pops::Issue] the issue to report # @param semantic [Puppet::Pops::ModelPopsObject] the object for which evaluation failed in some way. Used to determine origin. # @param options [Hash] hash of optional named data elements for the given issue # @return [!] this method does not return # @raise [Puppet::ParseError] an evaluation error initialized from the arguments (TODO: Change to EvaluationError?) # def optionally_fail(issue, semantic, options={}, except=nil) if except.nil? # Want a stacktrace, and it must be passed as an exception begin raise EvaluationError.new() rescue EvaluationError => e except = e end end diagnostic_producer.accept(issue, semantic, options, except) end # Binds the given variable name to the given value in the given scope. # The reference object `o` is intended to be used for origin information - the 3x scope implementation # only makes use of location when there is an error. This is now handled by other mechanisms; first a check # is made if a variable exists and an error is raised if attempting to change an immutable value. Errors # in name, numeric variable assignment etc. have also been validated prior to this call. In the event the # scope.setvar still raises an error, the general exception handling for evaluation of the assignment # expression knows about its location. Because of this, there is no need to extract the location for each # setting (extraction is somewhat expensive since 3x requires line instead of offset). # def set_variable(name, value, o, scope) # Scope also checks this but requires that location information are passed as options. # Those are expensive to calculate and a test is instead made here to enable failing with better information. # The error is not specific enough to allow catching it - need to check the actual message text. # TODO: Improve the messy implementation in Scope. # if scope.bound?(name) if Puppet::Parser::Scope::RESERVED_VARIABLE_NAMES.include?(name) fail(Puppet::Pops::Issues::ILLEGAL_RESERVED_ASSIGNMENT, o, {:name => name} ) else fail(Puppet::Pops::Issues::ILLEGAL_REASSIGNMENT, o, {:name => name} ) end end scope.setvar(name, value) end # Returns the value of the variable (nil is returned if variable has no value, or if variable does not exist) # def get_variable_value(name, o, scope) # Puppet 3x stores all variables as strings (then converts them back to numeric with a regexp... to see if it is a match variable) # Not ideal, scope should support numeric lookup directly instead. # TODO: consider fixing scope catch(:undefined_variable) { - return scope.lookupvar(name.to_s) + x = scope.lookupvar(name.to_s) + # Must convert :undef back to nil - this can happen when an undefined variable is used in a + # parameter's default value expression - there nil must be :undef to work with the rest of 3x. + # Now that the value comes back to 4x it is changed to nil. + return (x == :undef) ? nil : x } # It is always ok to reference numeric variables even if they are not assigned. They are always undef # if not set by a match expression. # unless name =~ Puppet::Pops::Patterns::NUMERIC_VAR_NAME fail(Puppet::Pops::Issues::UNKNOWN_VARIABLE, o, {:name => name}) end end # Returns true if the variable of the given name is set in the given most nested scope. True is returned even if # variable is bound to nil. # def variable_bound?(name, scope) scope.bound?(name.to_s) end # Returns true if the variable is bound to a value or nil, in the scope or it's parent scopes. # def variable_exists?(name, scope) scope.exist?(name.to_s) end def set_match_data(match_data, scope) # See set_variable for rationale for not passing file and line to ephemeral_from. # NOTE: The 3x scope adds one ephemeral(match) to its internal stack per match that succeeds ! It never # clears anything. Thus a context that performs many matches will get very deep (there simply is no way to # clear the match variables without rolling back the ephemeral stack.) # This implementation does not attempt to fix this, it behaves the same bad way. unless match_data.nil? scope.ephemeral_from(match_data) end end # Creates a local scope with vairalbes set from a hash of variable name to value # def create_local_scope_from(hash, scope) # two dummy values are needed since the scope tries to give an error message (can not happen in this # case - it is just wrong, the error should be reported by the caller who knows in more detail where it # is in the source. # raise ArgumentError, "Internal error - attempt to create a local scope without a hash" unless hash.is_a?(Hash) scope.ephemeral_from(hash) end # Creates a nested match scope def create_match_scope_from(scope) # Create a transparent match scope (for future matches) scope.new_match_scope(nil) end def get_scope_nesting_level(scope) scope.ephemeral_level end def set_scope_nesting_level(scope, level) # Yup, 3x uses this method to reset the level, it also supports passing :all to destroy all # ephemeral/local scopes - which is a sure way to create havoc. # scope.unset_ephemeral_var(level) end # Adds a relationship between the given `source` and `target` of the given `relationship_type` # @param source [Puppet:Pops::Types::PCatalogEntryType] the source end of the relationship (from) # @param target [Puppet:Pops::Types::PCatalogEntryType] the target end of the relationship (to) # @param relationship_type [:relationship, :subscription] the type of the relationship # def add_relationship(source, target, relationship_type, scope) # The 3x way is to record a Puppet::Parser::Relationship that is evaluated at the end of the compilation. # This means it is not possible to detect any duplicates at this point (and signal where an attempt is made to # add a duplicate. There is also no location information to signal the original place in the logic. The user will have # to go fish. # The 3.x implementation is based on Strings :-o, so the source and target must be transformed. The resolution is # done by Catalog#resource(type, title). To do that, it creates a Puppet::Resource since it is responsible for # translating the name/type/title and create index-keys used by the catalog. The Puppet::Resource has bizarre parsing of # the type and title (scan for [] that is interpreted as type/title (but it gets it wrong). # Moreover if the type is "" or "component", the type is Class, and if the type is :main, it is :main, all other cases # undergo capitalization of name-segments (foo::bar becomes Foo::Bar). (This was earlier done in the reverse by the parser). # Further, the title undergoes the same munging !!! # # That bug infested nest of messy logic needs serious Exorcism! # # Unfortunately it is not easy to simply call more intelligent methods at a lower level as the compiler evaluates the recorded # Relationship object at a much later point, and it is responsible for invoking all the messy logic. # # TODO: Revisit the below logic when there is a sane implementation of the catalog, compiler and resource. For now # concentrate on transforming the type references to what is expected by the wacky logic. # # HOWEVER, the Compiler only records the Relationships, and the only method it calls is @relationships.each{|x| x.evaluate(catalog) } # Which means a smarter Relationship class could do this right. Instead of obtaining the resource from the catalog using # the borked resource(type, title) which creates a resource for the purpose of looking it up, it needs to instead # scan the catalog's resources # # GAAAH, it is even worse! # It starts in the parser, which parses "File['foo']" into an AST::ResourceReference with type = File, and title = foo # This AST is evaluated by looking up the type/title in the scope - causing it to be loaded if it exists, and if not, the given # type name/title is used. It does not search for resource instances, only classes and types. It returns symbolic information # [type, [title, title]]. From this, instances of Puppet::Resource are created and returned. These only have type/title information # filled out. One or an array of resources are returned. # This set of evaluated (empty reference) Resource instances are then passed to the relationship operator. It creates a # Puppet::Parser::Relationship giving it a source and a target that are (empty reference) Resource instances. These are then remembered # until the relationship is evaluated by the compiler (at the end). When evaluation takes place, the (empty reference) Resource instances # are converted to String (!?! WTF) on the simple format "#{type}[#{title}]", and the catalog is told to find a resource, by giving # it this string. If it cannot find the resource it fails, else the before/notify parameter is appended with the target. # The search for the resource begin with (you guessed it) again creating an (empty reference) resource from type and title (WTF?!?!). # The catalog now uses the reference resource to compute a key [r.type, r.title.to_s] and also gets a uniqueness key from the # resource (This is only a reference type created from title and type). If it cannot find it with the first key, it uses the # uniqueness key to lookup. # # This is probably done to allow a resource type to munge/translate the title in some way (but it is quite unclear from the long # and convoluted path of evaluation. # In order to do this in a way that is similar to 3.x two resources are created to be used as keys. # # And if that is not enough, a source/target may be a Collector (a baked query that will be evaluated by the # compiler - it is simply passed through here for processing by the compiler at the right time). # if source.is_a?(Puppet::Parser::Collector) # use verbatim - behavior defined by 3x source_resource = source else # transform into the wonderful String representation in 3x type, title = catalog_type_to_split_type_title(source) source_resource = Puppet::Resource.new(type, title) end if target.is_a?(Puppet::Parser::Collector) # use verbatim - behavior defined by 3x target_resource = target else # transform into the wonderful String representation in 3x type, title = catalog_type_to_split_type_title(target) target_resource = Puppet::Resource.new(type, title) end # Add the relationship to the compiler for later evaluation. scope.compiler.add_relationship(Puppet::Parser::Relationship.new(source_resource, target_resource, relationship_type)) end # Coerce value `v` to numeric or fails. # The given value `v` is coerced to Numeric, and if that fails the operation # calls {#fail}. # @param v [Object] the value to convert # @param o [Object] originating instruction # @param scope [Object] the (runtime specific) scope where evaluation of o takes place # @return [Numeric] value `v` converted to Numeric. # def coerce_numeric(v, o, scope) unless n = Puppet::Pops::Utils.to_n(v) fail(Puppet::Pops::Issues::NOT_NUMERIC, o, {:value => v}) end n end def call_function(name, args, o, scope) # Call via 4x API if the function exists there loaders = scope.compiler.loaders # find the loader that loaded the code, or use the private_environment_loader (sees env + all modules) adapter = Puppet::Pops::Utils.find_adapter(o, Puppet::Pops::Adapters::LoaderAdapter) loader = adapter.nil? ? loaders.private_environment_loader : adapter.loader if loader && func = loader.load(:function, name) return func.call(scope, *args) end # Call via 3x API if function exists there fail(Puppet::Pops::Issues::UNKNOWN_FUNCTION, o, {:name => name}) unless Puppet::Parser::Functions.function(name) # Arguments must be mapped since functions are unaware of the new and magical creatures in 4x. # NOTE: Passing an empty string last converts nil/:undef to empty string mapped_args = args.map {|a| convert(a, scope, '') } result = scope.send("function_#{name}", mapped_args) # Prevent non r-value functions from leaking their result (they are not written to care about this) Puppet::Parser::Functions.rvalue?(name) ? result : nil end # The o is used for source reference def create_resource_parameter(o, scope, name, value, operator) file, line = extract_file_line(o) Puppet::Parser::Resource::Param.new( :name => name, # Here we must convert nil values to :undef for the 3x logic to work :value => convert(value, scope, :undef), # converted to 3x since 4x supports additional objects / types :source => scope.source, :line => line, :file => file, :add => operator == :'+>' ) end CLASS_STRING = 'class'.freeze def create_resources(o, scope, virtual, exported, type_name, resource_titles, evaluated_parameters) # TODO: Unknown resource causes creation of Resource to fail with ArgumentError, should give # a proper Issue. Now the result is "Error while evaluating a Resource Statement" with the message # from the raised exception. (It may be good enough). # resolve in scope. fully_qualified_type, resource_titles = scope.resolve_type_and_titles(type_name, resource_titles) # Not 100% accurate as this is the resource expression location and each title is processed separately # The titles are however the result of evaluation and they have no location at this point (an array # of positions for the source expressions are required for this to work). # TODO: Revisit and possible improve the accuracy. # file, line = extract_file_line(o) # Build a resource for each title resource_titles.map do |resource_title| resource = Puppet::Parser::Resource.new( fully_qualified_type, resource_title, :parameters => evaluated_parameters, :file => file, :line => line, :exported => exported, :virtual => virtual, # WTF is this? Which source is this? The file? The name of the context ? :source => scope.source, :scope => scope, :strict => true ) if resource.resource_type.is_a? Puppet::Resource::Type resource.resource_type.instantiate_resource(scope, resource) end scope.compiler.add_resource(scope, resource) scope.compiler.evaluate_classes([resource_title], scope, false, true) if fully_qualified_type == CLASS_STRING # Turn the resource into a PType (a reference to a resource type) # weed out nil's resource_to_ptype(resource) end end # Defines default parameters for a type with the given name. # def create_resource_defaults(o, scope, type_name, evaluated_parameters) # Note that name must be capitalized in this 3x call # The 3x impl creates a Resource instance with a bogus title and then asks the created resource # for the type of the name. # Note, locations are available per parameter. # scope.define_settings(capitalize_qualified_name(type_name), evaluated_parameters) end # Capitalizes each segment of a qualified name # def capitalize_qualified_name(name) name.split(/::/).map(&:capitalize).join(NAME_SPACE_SEPARATOR) end # Creates resource overrides for all resource type objects in evaluated_resources. The same set of # evaluated parameters are applied to all. # def create_resource_overrides(o, scope, evaluated_resources, evaluated_parameters) # Not 100% accurate as this is the resource expression location and each title is processed separately # The titles are however the result of evaluation and they have no location at this point (an array # of positions for the source expressions are required for this to work. # TODO: Revisit and possible improve the accuracy. # file, line = extract_file_line(o) evaluated_resources.each do |r| unless r.is_a?(Puppet::Pops::Types::PResourceType) && r.type_name != 'class' fail(Puppet::Pops::Issues::ILLEGAL_OVERRIDEN_TYPE, o, {:actual => r} ) end resource = Puppet::Parser::Resource.new( r.type_name, r.title, :parameters => evaluated_parameters, :file => file, :line => line, # WTF is this? Which source is this? The file? The name of the context ? :source => scope.source, :scope => scope ) scope.compiler.add_override(resource) end end # Finds a resource given a type and a title. # def find_resource(scope, type_name, title) scope.compiler.findresource(type_name, title) end # Returns the value of a resource's parameter by first looking up the parameter in the resource # and then in the defaults for the resource. Since the resource exists (it must in order to look up its # parameters, any overrides have already been applied). Defaults are not applied to a resource until it # has been finished (which typically has not taken place when this is evaluated; hence the dual lookup). # def get_resource_parameter_value(scope, resource, parameter_name) # This gets the parameter value, or nil (for both valid parameters and parameters that do not exist). val = resource[parameter_name] # The defaults must be looked up in the scope where the resource was created (not in the given # scope where the lookup takes place. resource_scope = resource.scope if val.nil? && resource_scope && defaults = resource_scope.lookupdefaults(resource.type) # NOTE: 3x resource keeps defaults as hash using symbol for name as key to Parameter which (again) holds # name and value. # NOTE: meta parameters that are unset ends up here, and there are no defaults for those encoded # in the defaults, they may receive hardcoded defaults later (e.g. 'tag'). param = defaults[parameter_name.to_sym] # Some parameters (meta parameters like 'tag') does not return a param from which the value can be obtained # at all times. Instead, they return a nil param until a value has been set. val = param.nil? ? nil : param.value end val end # Returns true, if the given name is the name of a resource parameter. # def is_parameter_of_resource?(scope, resource, name) resource.valid_parameter?(name) end def resource_to_ptype(resource) nil if resource.nil? - type_calculator.infer(resource) + # inference returns the meta type since the 3x Resource is an alternate way to describe a type + type_calculator.infer(resource).type end # This is the same type of "truth" as used in the current Puppet DSL. # def is_true? o # Is the value true? This allows us to control the definition of truth # in one place. case o # Support :undef since it may come from a 3x structure when :undef false else !!o end end # Utility method for TrueClass || FalseClass # @param x [Object] the object to test if it is instance of TrueClass or FalseClass def is_boolean? x x.is_a?(TrueClass) || x.is_a?(FalseClass) end def initialize @@convert_visitor ||= Puppet::Pops::Visitor.new(self, "convert", 2, 2) end # Converts 4x supported values to 3x values. This is required because # resources and other objects do not know about the new type system, and does not support # regular expressions. Unfortunately this has to be done for array and hash as well. # A complication is that catalog types needs to be resolved against the scope. # def convert(o, scope, undef_value) @@convert_visitor.visit_this_2(self, o, scope, undef_value) end def convert_NilClass(o, scope, undef_value) undef_value end def convert_Object(o, scope, undef_value) o end def convert_Array(o, scope, undef_value) o.map {|x| convert(x, scope, undef_value) } end def convert_Hash(o, scope, undef_value) result = {} o.each {|k,v| result[convert(k, scope, undef_value)] = convert(v, scope, undef_value) } result end def convert_Regexp(o, scope, undef_value) # Puppet 3x cannot handle parameter values that are reqular expressions. Turn into regexp string in # source form o.inspect end def convert_Symbol(o, scope, undef_value) case o # Support :undef since it may come from a 3x structure when :undef undef_value # 3x wants undef as either empty string or :undef else o # :default, and all others are verbatim since they are new in future evaluator end end def convert_PAnyType(o, scope, undef_value) o end def convert_PCatalogEntryType(o, scope, undef_value) # Since 4x does not support dynamic scoping, all names are absolute and can be # used as is (with some check/transformation/mangling between absolute/relative form # due to Puppet::Resource's idiosyncratic behavior where some references must be # absolute and others cannot be. # Thus there is no need to call scope.resolve_type_and_titles to do dynamic lookup. Puppet::Resource.new(*catalog_type_to_split_type_title(o)) end private # Produces an array with [type, title] from a PCatalogEntryType # This method is used to produce the arguments for creation of reference resource instances # (used when 3x is operating on a resource). # Ensures that resources are *not* absolute. # def catalog_type_to_split_type_title(catalog_type) split_type = catalog_type.is_a?(Puppet::Pops::Types::PType) ? catalog_type.type : catalog_type case split_type when Puppet::Pops::Types::PHostClassType class_name = split_type.class_name ['class', class_name.nil? ? nil : class_name.sub(/^::/, '')] when Puppet::Pops::Types::PResourceType type_name = split_type.type_name title = split_type.title if type_name =~ /^(::)?[Cc]lass/ ['class', title.nil? ? nil : title.sub(/^::/, '')] else # Ensure that title is '' if nil # Resources with absolute name always results in error because tagging does not support leading :: [type_name.nil? ? nil : type_name.sub(/^::/, ''), title.nil? ? '' : title] end else raise ArgumentError, "Cannot split the type #{catalog_type.class}, it represents neither a PHostClassType, nor a PResourceType." end end def extract_file_line(o) source_pos = Puppet::Pops::Utils.find_closest_positioned(o) return [nil, -1] unless source_pos [source_pos.locator.file, source_pos.line] end def find_closest_positioned(o) return nil if o.nil? || o.is_a?(Puppet::Pops::Model::Program) o.offset.nil? ? find_closest_positioned(o.eContainer) : Puppet::Pops::Adapters::SourcePosAdapter.adapt(o) end # Creates a diagnostic producer def diagnostic_producer Puppet::Pops::Validation::DiagnosticProducer.new( ExceptionRaisingAcceptor.new(), # Raises exception on all issues SeverityProducer.new(), # All issues are errors Puppet::Pops::Model::ModelLabelProvider.new()) end # Configure the severity of failures class SeverityProducer < Puppet::Pops::Validation::SeverityProducer Issues = Puppet::Pops::Issues def initialize super p = self # Issues triggering warning only if --debug is on if Puppet[:debug] p[Issues::EMPTY_RESOURCE_SPECIALIZATION] = :warning else p[Issues::EMPTY_RESOURCE_SPECIALIZATION] = :ignore end end end # An acceptor of diagnostics that immediately raises an exception. class ExceptionRaisingAcceptor < Puppet::Pops::Validation::Acceptor def accept(diagnostic) super Puppet::Pops::IssueReporter.assert_and_report(self, {:message => "Evaluation Error:", :emit_warnings => true }) if errors? raise ArgumentError, "Internal Error: Configuration of runtime error handling wrong: should have raised exception" end end end class EvaluationError < StandardError end end diff --git a/lib/puppet/pops/issues.rb b/lib/puppet/pops/issues.rb index 357167df4..a2ed3255d 100644 --- a/lib/puppet/pops/issues.rb +++ b/lib/puppet/pops/issues.rb @@ -1,518 +1,538 @@ # Defines classes to deal with issues, and message formatting and defines constants with Issues. # @api public # module Puppet::Pops::Issues # Describes an issue, and can produce a message for an occurrence of the issue. # class Issue # The issue code # @return [Symbol] attr_reader :issue_code # A block producing the message # @return [Proc] attr_reader :message_block # Names that must be bound in an occurrence of the issue to be able to produce a message. # These are the names in addition to requirements stipulated by the Issue formatter contract; i.e. :label`, # and `:semantic`. # attr_reader :arg_names # If this issue can have its severity lowered to :warning, :deprecation, or :ignored attr_writer :demotable # Configures the Issue with required arguments (bound by occurrence), and a block producing a message. def initialize issue_code, *args, &block @issue_code = issue_code @message_block = block @arg_names = args @demotable = true end # Returns true if it is allowed to demote this issue def demotable? @demotable end # Formats a message for an occurrence of the issue with argument bindings passed in a hash. # The hash must contain a LabelProvider bound to the key `label` and the semantic model element # bound to the key `semantic`. All required arguments as specified by `arg_names` must be bound # in the given `hash`. # @api public # def format(hash ={}) # Create a Message Data where all hash keys become methods for convenient interpolation # in issue text. msgdata = MessageData.new(*arg_names) begin # Evaluate the message block in the msg data's binding msgdata.format(hash, &message_block) rescue StandardError => e Puppet::Pops::Issues::MessageData raise RuntimeError, "Error while reporting issue: #{issue_code}. #{e.message}", caller end end end # Provides a binding of arguments passed to Issue.format to method names available # in the issue's message producing block. # @api private # class MessageData def initialize *argnames singleton = class << self; self end argnames.each do |name| singleton.send(:define_method, name) do @data[name] end end end def format(hash, &block) @data = hash instance_eval &block end # Returns the label provider given as a key in the hash passed to #format. # If given an argument, calls #label on the label provider (caller would otherwise have to # call label.label(it) # def label(it = nil) raise "Label provider key :label must be set to produce the text of the message!" unless @data[:label] it.nil? ? @data[:label] : @data[:label].label(it) end # Returns the label provider given as a key in the hash passed to #format. # def semantic raise "Label provider key :semantic must be set to produce the text of the message!" unless @data[:semantic] @data[:semantic] end end # Defines an issue with the given `issue_code`, additional required parameters, and a block producing a message. # The block is evaluated in the context of a MessageData which provides convenient access to all required arguments # via accessor methods. In addition to accessors for specified arguments, these are also available: # * `label` - a `LabelProvider` that provides human understandable names for model elements and production of article (a/an/the). # * `semantic` - the model element for which the issue is reported # # @param issue_code [Symbol] the issue code for the issue used as an identifier, should be the same as the constant # the issue is bound to. # @param args [Symbol] required arguments that must be passed when formatting the message, may be empty # @param block [Proc] a block producing the message string, evaluated in a MessageData scope. The produced string # should not end with a period as additional information may be appended. # # @see MessageData # @api public # def self.issue (issue_code, *args, &block) Issue.new(issue_code, *args, &block) end # Creates a non demotable issue. # @see Issue.issue # def self.hard_issue(issue_code, *args, &block) result = Issue.new(issue_code, *args, &block) result.demotable = false result end # @comment Here follows definitions of issues. The intent is to provide a list from which yardoc can be generated # containing more detailed information / explanation of the issue. # These issues are set as constants, but it is unfortunately not possible for the created object to easily know which # name it is bound to. Instead the constant has to be repeated. (Alternatively, it could be done by instead calling # #const_set on the module, but the extra work required to get yardoc output vs. the extra effort to repeat the name # twice makes it not worth it (if doable at all, since there is no tag to artificially construct a constant, and # the parse tag does not produce any result for a constant assignment). # This is allowed (3.1) and has not yet been deprecated. # @todo configuration # NAME_WITH_HYPHEN = issue :NAME_WITH_HYPHEN, :name do "#{label.a_an_uc(semantic)} may not have a name containing a hyphen. The name '#{name}' is not legal" end # When a variable name contains a hyphen and these are illegal. # It is possible to control if a hyphen is legal in a name or not using the setting TODO # @todo describe the setting # @api public # @todo configuration if this is error or warning # VAR_WITH_HYPHEN = issue :VAR_WITH_HYPHEN, :name do "A variable name may not contain a hyphen. The name '#{name}' is not legal" end # A class, definition, or node may only appear at top level or inside other classes # @todo Is this really true for nodes? Can they be inside classes? Isn't that too late? # @api public # NOT_TOP_LEVEL = hard_issue :NOT_TOP_LEVEL do "Classes, definitions, and nodes may only appear at toplevel or inside other classes" end CROSS_SCOPE_ASSIGNMENT = hard_issue :CROSS_SCOPE_ASSIGNMENT, :name do "Illegal attempt to assign to '#{name}'. Cannot assign to variables in other namespaces" end # Assignment can only be made to certain types of left hand expressions such as variables. ILLEGAL_ASSIGNMENT = hard_issue :ILLEGAL_ASSIGNMENT do "Illegal attempt to assign to '#{label.a_an(semantic)}'. Not an assignable reference" end # Variables are immutable, cannot reassign in the same assignment scope ILLEGAL_REASSIGNMENT = hard_issue :ILLEGAL_REASSIGNMENT, :name do "Cannot reassign variable #{name}" end ILLEGAL_RESERVED_ASSIGNMENT = hard_issue :ILLEGAL_RESERVED_ASSIGNMENT, :name do "Attempt to assign to a reserved variable name: '#{name}'" end # Assignment cannot be made to numeric match result variables ILLEGAL_NUMERIC_ASSIGNMENT = issue :ILLEGAL_NUMERIC_ASSIGNMENT, :varname do "Illegal attempt to assign to the numeric match result variable '$#{varname}'. Numeric variables are not assignable" end APPEND_FAILED = issue :APPEND_FAILED, :message do "Append assignment += failed with error: #{message}" end DELETE_FAILED = issue :DELETE_FAILED, :message do "'Delete' assignment -= failed with error: #{message}" end # parameters cannot have numeric names, clashes with match result variables ILLEGAL_NUMERIC_PARAMETER = issue :ILLEGAL_NUMERIC_PARAMETER, :name do "The numeric parameter name '$#{varname}' cannot be used (clashes with numeric match result variables)" end # In certain versions of Puppet it may be allowed to assign to a not already assigned key # in an array or a hash. This is an optional validation that may be turned on to prevent accidental # mutation. # ILLEGAL_INDEXED_ASSIGNMENT = issue :ILLEGAL_INDEXED_ASSIGNMENT do "Illegal attempt to assign via [index/key]. Not an assignable reference" end # When indexed assignment ($x[]=) is allowed, the leftmost expression must be # a variable expression. # ILLEGAL_ASSIGNMENT_VIA_INDEX = hard_issue :ILLEGAL_ASSIGNMENT_VIA_INDEX do "Illegal attempt to assign to #{label.a_an(semantic)} via [index/key]. Not an assignable reference" end # For unsupported operators (e.g. -= in puppet 3). # UNSUPPORTED_OPERATOR = hard_issue :UNSUPPORTED_OPERATOR, :operator do "The operator '#{operator}' in #{label.a_an(semantic)} is not supported." end # For non applicable operators (e.g. << on Hash). # OPERATOR_NOT_APPLICABLE = hard_issue :OPERATOR_NOT_APPLICABLE, :operator, :left_value do "Operator '#{operator}' is not applicable to #{label.a_an(left_value)}." end COMPARISON_NOT_POSSIBLE = hard_issue :COMPARISON_NOT_POSSIBLE, :operator, :left_value, :right_value, :detail do "Comparison of: #{label(left_value)} #{operator} #{label(right_value)}, is not possible. Caused by '#{detail}'." end MATCH_NOT_REGEXP = hard_issue :MATCH_NOT_REGEXP, :detail do "Can not convert right match operand to a regular expression. Caused by '#{detail}'." end MATCH_NOT_STRING = hard_issue :MATCH_NOT_STRING, :left_value do "Left match operand must result in a String value. Got #{label.a_an(left_value)}." end # Some expressions/statements may not produce a value (known as right-value, or rvalue). # This may vary between puppet versions. # NOT_RVALUE = issue :NOT_RVALUE do "Invalid use of expression. #{label.a_an_uc(semantic)} does not produce a value" end # Appending to attributes is only allowed in certain types of resource expressions. # ILLEGAL_ATTRIBUTE_APPEND = hard_issue :ILLEGAL_ATTRIBUTE_APPEND, :name, :parent do "Illegal +> operation on attribute #{name}. This operator can not be used in #{label.a_an(parent)}" end ILLEGAL_NAME = hard_issue :ILLEGAL_NAME, :name do "Illegal name. The given name #{name} does not conform to the naming rule /^((::)?[a-z_]\w*)(::[a-z]\w*)*$/" end ILLEGAL_VAR_NAME = hard_issue :ILLEGAL_VAR_NAME, :name do "Illegal variable name, The given name '#{name}' does not conform to the naming rule /^((::)?[a-z]\w*)*((::)?[a-z_]\w*)$/" end ILLEGAL_NUMERIC_VAR_NAME = hard_issue :ILLEGAL_NUMERIC_VAR_NAME, :name do "Illegal numeric variable name, The given name '#{name}' must be a decimal value if it starts with a digit 0-9" end # In case a model is constructed programmatically, it must create valid type references. # ILLEGAL_CLASSREF = hard_issue :ILLEGAL_CLASSREF, :name do "Illegal type reference. The given name '#{name}' does not conform to the naming rule" end # This is a runtime issue - storeconfigs must be on in order to collect exported. This issue should be # set to :ignore when just checking syntax. # @todo should be a :warning by default # RT_NO_STORECONFIGS = issue :RT_NO_STORECONFIGS do "You cannot collect exported resources without storeconfigs being set; the collection will be ignored" end # This is a runtime issue - storeconfigs must be on in order to export a resource. This issue should be # set to :ignore when just checking syntax. # @todo should be a :warning by default # RT_NO_STORECONFIGS_EXPORT = issue :RT_NO_STORECONFIGS_EXPORT do "You cannot collect exported resources without storeconfigs being set; the export is ignored" end # A hostname may only contain letters, digits, '_', '-', and '.'. # ILLEGAL_HOSTNAME_CHARS = hard_issue :ILLEGAL_HOSTNAME_CHARS, :hostname do "The hostname '#{hostname}' contains illegal characters (only letters, digits, '_', '-', and '.' are allowed)" end # A hostname may only contain letters, digits, '_', '-', and '.'. # ILLEGAL_HOSTNAME_INTERPOLATION = hard_issue :ILLEGAL_HOSTNAME_INTERPOLATION do "An interpolated expression is not allowed in a hostname of a node" end # Issues when an expression is used where it is not legal. # E.g. an arithmetic expression where a hostname is expected. # ILLEGAL_EXPRESSION = hard_issue :ILLEGAL_EXPRESSION, :feature, :container do "Illegal expression. #{label.a_an_uc(semantic)} is unacceptable as #{feature} in #{label.a_an(container)}" end # Issues when an expression is used where it is not legal. # E.g. an arithmetic expression where a hostname is expected. # ILLEGAL_VARIABLE_EXPRESSION = hard_issue :ILLEGAL_VARIABLE_EXPRESSION do "Illegal variable expression. #{label.a_an_uc(semantic)} did not produce a variable name (String or Numeric)." end # Issues when an expression is used illegaly in a query. # query only supports == and !=, and not <, > etc. # ILLEGAL_QUERY_EXPRESSION = hard_issue :ILLEGAL_QUERY_EXPRESSION do "Illegal query expression. #{label.a_an_uc(semantic)} cannot be used in a query" end # If an attempt is made to make a resource default virtual or exported. # NOT_VIRTUALIZEABLE = hard_issue :NOT_VIRTUALIZEABLE do "Resource Defaults are not virtualizable" end # When an attempt is made to use multiple keys (to produce a range in Ruby - e.g. $arr[2,-1]). # This is not supported in 3x, but it allowed in 4x. # UNSUPPORTED_RANGE = issue :UNSUPPORTED_RANGE, :count do "Attempt to use unsupported range in #{label.a_an(semantic)}, #{count} values given for max 1" end - DEPRECATED_NAME_AS_TYPE = issue :DEPRECATED_NAME_AS_TYPE, :name do - "Resource references should now be capitalized. The given '#{name}' does not have the correct form" - end - ILLEGAL_RELATIONSHIP_OPERAND_TYPE = issue :ILLEGAL_RELATIONSHIP_OPERAND_TYPE, :operand do "Illegal relationship operand, can not form a relationship with #{label.a_an(operand)}. A Catalog type is required." end NOT_CATALOG_TYPE = issue :NOT_CATALOG_TYPE, :type do "Illegal relationship operand, can not form a relationship with something of type #{type}. A Catalog type is required." end BAD_STRING_SLICE_ARITY = issue :BAD_STRING_SLICE_ARITY, :actual do "String supports [] with one or two arguments. Got #{actual}" end BAD_STRING_SLICE_TYPE = issue :BAD_STRING_SLICE_TYPE, :actual do "String-Type [] requires all arguments to be integers (or default). Got #{actual}" end BAD_ARRAY_SLICE_ARITY = issue :BAD_ARRAY_SLICE_ARITY, :actual do "Array supports [] with one or two arguments. Got #{actual}" end BAD_HASH_SLICE_ARITY = issue :BAD_HASH_SLICE_ARITY, :actual do "Hash supports [] with one or more arguments. Got #{actual}" end BAD_INTEGER_SLICE_ARITY = issue :BAD_INTEGER_SLICE_ARITY, :actual do "Integer-Type supports [] with one or two arguments (from, to). Got #{actual}" end BAD_INTEGER_SLICE_TYPE = issue :BAD_INTEGER_SLICE_TYPE, :actual do "Integer-Type [] requires all arguments to be integers (or default). Got #{actual}" end BAD_COLLECTION_SLICE_TYPE = issue :BAD_COLLECTION_SLICE_TYPE, :actual do "A Type's size constraint arguments must be a single Integer type, or 1-2 integers (or default). Got #{label.a_an(actual)}" end BAD_FLOAT_SLICE_ARITY = issue :BAD_INTEGER_SLICE_ARITY, :actual do "Float-Type supports [] with one or two arguments (from, to). Got #{actual}" end BAD_FLOAT_SLICE_TYPE = issue :BAD_INTEGER_SLICE_TYPE, :actual do "Float-Type [] requires all arguments to be floats, or integers (or default). Got #{actual}" end BAD_SLICE_KEY_TYPE = issue :BAD_SLICE_KEY_TYPE, :left_value, :expected_classes, :actual do expected_text = if expected_classes.size > 1 "one of #{expected_classes.join(', ')} are" else "#{expected_classes[0]} is" end "#{label.a_an_uc(left_value)}[] cannot use #{actual} where #{expected_text} expected" end BAD_TYPE_SLICE_TYPE = issue :BAD_TYPE_SLICE_TYPE, :base_type, :actual do "#{base_type}[] arguments must be types. Got #{actual}" end BAD_TYPE_SLICE_ARITY = issue :BAD_TYPE_SLICE_ARITY, :base_type, :min, :max, :actual do base_type_label = base_type.is_a?(String) ? base_type : label.a_an_uc(base_type) if max == -1 || max == 1.0 / 0.0 # Infinity "#{base_type_label}[] accepts #{min} or more arguments. Got #{actual}" elsif max && max != min "#{base_type_label}[] accepts #{min} to #{max} arguments. Got #{actual}" else "#{base_type_label}[] accepts #{min} #{label.plural_s(min, 'argument')}. Got #{actual}" end end BAD_TYPE_SPECIALIZATION = hard_issue :BAD_TYPE_SPECIALIZATION, :type, :message do "Error creating type specialization of #{label.a_an(type)}, #{message}" end ILLEGAL_TYPE_SPECIALIZATION = issue :ILLEGAL_TYPE_SPECIALIZATION, :kind do "Cannot specialize an already specialized #{kind} type" end ILLEGAL_RESOURCE_SPECIALIZATION = issue :ILLEGAL_RESOURCE_SPECIALIZATION, :actual do "First argument to Resource[] must be a resource type or a String. Got #{actual}." end EMPTY_RESOURCE_SPECIALIZATION = issue :EMPTY_RESOURCE_SPECIALIZATION do "Arguments to Resource[] are all empty/undefined" end ILLEGAL_HOSTCLASS_NAME = hard_issue :ILLEGAL_HOSTCLASS_NAME, :name do "Illegal Class name in class reference. #{label.a_an_uc(name)} cannot be used where a String is expected" end - # Issues when an expression is used where it is not legal. - # E.g. an arithmetic expression where a hostname is expected. - # ILLEGAL_DEFINITION_NAME = hard_issue :ILLEGAL_DEFINTION_NAME, :name do "Unacceptable name. The name '#{name}' is unacceptable as the name of #{label.a_an(semantic)}" end CAPTURES_REST_NOT_LAST = hard_issue :CAPTURES_REST_NOT_LAST, :param_name do "Parameter $#{param_name} is not last, and has 'captures rest'" end CAPTURES_REST_NOT_SUPPORTED = hard_issue :CAPTURES_REST_NOT_SUPPORTED, :container, :param_name do "Parameter $#{param_name} has 'captures rest' - not supported in #{label.a_an(container)}" end REQUIRED_PARAMETER_AFTER_OPTIONAL = hard_issue :REQUIRED_PARAMETER_AFTER_OPTIONAL, :param_name do "Parameter $#{param_name} is required but appears after optional parameters" end MISSING_REQUIRED_PARAMETER = hard_issue :MISSING_REQUIRED_PARAMETER, :param_name do "Parameter $#{param_name} is required but no value was given" end NOT_NUMERIC = issue :NOT_NUMERIC, :value do "The value '#{value}' cannot be converted to Numeric." end UNKNOWN_FUNCTION = issue :UNKNOWN_FUNCTION, :name do "Unknown function: '#{name}'." end UNKNOWN_VARIABLE = issue :UNKNOWN_VARIABLE, :name do "Unknown variable: '#{name}'." end RUNTIME_ERROR = issue :RUNTIME_ERROR, :detail do "Error while evaluating #{label.a_an(semantic)}, #{detail}" end UNKNOWN_RESOURCE_TYPE = issue :UNKNOWN_RESOURCE_TYPE, :type_name do "Resource type not found: #{type_name.capitalize}" end + ILLEGAL_RESOURCE_TYPE = hard_issue :ILLEGAL_RESOURCE_TYPE, :actual do + "Illegal Resource Type expression, expected result to be a type name, or untitled Resource, got #{actual}" + end + + DUPLICATE_TITLE = issue :DUPLICATE_TITLE, :title do + "The title '#{title}' has already been used in this resource expression" + end + + MISSING_TITLE = hard_issue :MISSING_TITLE do + "Missing title. The title expression resulted in undef" + end + + MISSING_TITLE_AT = hard_issue :MISSING_TITLE_AT, :index do + "Missing title at index #{index}. The title expression resulted in an undef title" + end + + ILLEGAL_TITLE_TYPE_AT = hard_issue :ILLEGAL_TITLE_TYPE_AT, :index, :actual do + "Illegal title type at index #{index}. Expected String, got #{actual}" + end + + EMPTY_STRING_TITLE_AT = hard_issue :EMPTY_STRING_TITLE_AT, :index do + "Empty string title at #{index}. Title strings must have a length greater than zero." + end + UNKNOWN_RESOURCE = issue :UNKNOWN_RESOURCE, :type_name, :title do "Resource not found: #{type_name.capitalize}['#{title}']" end UNKNOWN_RESOURCE_PARAMETER = issue :UNKNOWN_RESOURCE_PARAMETER, :type_name, :title, :param_name do "The resource #{type_name.capitalize}['#{title}'] does not have a parameter called '#{param_name}'" end DIV_BY_ZERO = hard_issue :DIV_BY_ZERO do "Division by 0" end RESULT_IS_INFINITY = hard_issue :RESULT_IS_INFINITY, :operator do "The result of the #{operator} expression is Infinity" end # TODO_HEREDOC EMPTY_HEREDOC_SYNTAX_SEGMENT = issue :EMPTY_HEREDOC_SYNTAX_SEGMENT, :syntax do "Heredoc syntax specification has empty segment between '+' : '#{syntax}'" end ILLEGAL_EPP_PARAMETERS = issue :ILLEGAL_EPP_PARAMETERS do "Ambiguous EPP parameter expression. Probably missing '<%-' before parameters to remove leading whitespace" end DISCONTINUED_IMPORT = hard_issue :DISCONTINUED_IMPORT do "Use of 'import' has been discontinued in favor of a manifest directory. See http://links.puppetlabs.com/puppet-import-deprecation" end IDEM_EXPRESSION_NOT_LAST = issue :IDEM_EXPRESSION_NOT_LAST do "This #{label.label(semantic)} is not productive. A non productive construct may only be placed last in a block/sequence" end IDEM_NOT_ALLOWED_LAST = hard_issue :IDEM_NOT_ALLOWED_LAST, :container do "This #{label.label(semantic)} is not productive. #{label.a_an_uc(container)} can not end with a non productive construct" end RESERVED_WORD = hard_issue :RESERVED_WORD, :word do "Use of reserved word: #{word}, must be quoted if intended to be a String value" end RESERVED_TYPE_NAME = hard_issue :RESERVED_TYPE_NAME, :name do "The name: '#{name}' is already defined by Puppet and can not be used as the name of #{label.a_an(semantic)}." end UNMATCHED_SELECTOR = hard_issue :UNMATCHED_SELECTOR, :param_value do "No matching entry for selector parameter with value '#{param_value}'" end ILLEGAL_NODE_INHERITANCE = issue :ILLEGAL_NODE_INHERITANCE do "Node inheritance is not supported in Puppet >= 4.0.0. See http://links.puppetlabs.com/puppet-node-inheritance-deprecation" end ILLEGAL_OVERRIDEN_TYPE = issue :ILLEGAL_OVERRIDEN_TYPE, :actual do "Resource Override can only operate on resources, got: #{label.label(actual)}" end RESERVED_PARAMETER = hard_issue :RESERVED_PARAMETER, :container, :param_name do "The parameter $#{param_name} redefines a built in parameter in #{label.the(container)}" end + TYPE_MISMATCH = hard_issue :TYPE_MISMATCH, :expected, :actual do + "Expected value of type #{expected}, got #{actual}" + end end diff --git a/lib/puppet/pops/loader/base_loader.rb b/lib/puppet/pops/loader/base_loader.rb index fd9bc53c5..cd916865b 100644 --- a/lib/puppet/pops/loader/base_loader.rb +++ b/lib/puppet/pops/loader/base_loader.rb @@ -1,103 +1,102 @@ # BaseLoader # === # An abstract implementation of Puppet::Pops::Loader::Loader # # A derived class should implement `find(typed_name)` and set entries, and possible handle "miss caching". # # @api private # class Puppet::Pops::Loader::BaseLoader < Puppet::Pops::Loader::Loader # The parent loader attr_reader :parent # An internal name used for debugging and error message purposes attr_reader :loader_name def initialize(parent_loader, loader_name) @parent = parent_loader # the higher priority loader to consult @named_values = {} # hash name => NamedEntry @last_name = nil # the last name asked for (optimization) @last_result = nil # the value of the last name (optimization) @loader_name = loader_name # the name of the loader (not the name-space it is a loader for) end # @api public # def load_typed(typed_name) # The check for "last queried name" is an optimization when a module searches. First it checks up its parent # chain, then itself, and then delegates to modules it depends on. # These modules are typically parented by the same # loader as the one initiating the search. It is inefficient to again try to search the same loader for # the same name. if typed_name == @last_name @last_result else @last_name = typed_name @last_result = internal_load(typed_name) end end # This method is final (subclasses should not override it) # # @api private # def get_entry(typed_name) @named_values[typed_name] end # @api private # def set_entry(typed_name, value, origin = nil) if entry = @named_values[typed_name] then fail_redefined(entry); end @named_values[typed_name] = Puppet::Pops::Loader::Loader::NamedEntry.new(typed_name, value, origin) end # @api private # def add_entry(type, name, value, origin) set_entry(Puppet::Pops::Loader::Loader::TypedName.new(type, name), value, origin) end # Promotes an already created entry (typically from another loader) to this loader # # @api private # def promote_entry(named_entry) typed_name = named_entry.typed_name if entry = @named_values[typed_name] then fail_redefine(entry); end @named_values[typed_name] = named_entry end private def fail_redefine(entry) - require 'debugger'; debugger origin_info = entry.origin ? " Originally set at #{origin_label(entry.origin)}." : "unknown location" raise ArgumentError, "Attempt to redefine entity '#{entry.typed_name}' originally set at #{origin_info}" end # TODO: Should not really be here?? - TODO: A Label provider ? semantics for the URI? # def origin_label(origin) if origin && origin.is_a?(URI) origin.to_s elsif origin.respond_to?(:uri) origin.uri.to_s else origin end end # loads in priority order: # 1. already loaded here # 2. load from parent # 3. find it here # 4. give up # def internal_load(typed_name) # avoid calling get_entry, by looking it up @named_values[typed_name] || parent.load_typed(typed_name) || find(typed_name) end end diff --git a/lib/puppet/pops/model/ast_transformer.rb b/lib/puppet/pops/model/ast_transformer.rb index 4016cc266..2898534dd 100644 --- a/lib/puppet/pops/model/ast_transformer.rb +++ b/lib/puppet/pops/model/ast_transformer.rb @@ -1,663 +1,643 @@ require 'puppet/parser/ast' # The receiver of `import(file)` calls; once per imported file, or nil if imports are ignored # # Transforms a Pops::Model to classic Puppet AST. # TODO: Documentation is currently skipped completely (it is only used for Rdoc) # class Puppet::Pops::Model::AstTransformer AST = Puppet::Parser::AST Model = Puppet::Pops::Model attr_reader :importer def initialize(source_file = "unknown-file", importer=nil) @@transform_visitor ||= Puppet::Pops::Visitor.new(nil,"transform",0,0) @@query_transform_visitor ||= Puppet::Pops::Visitor.new(nil,"query",0,0) @@hostname_transform_visitor ||= Puppet::Pops::Visitor.new(nil,"hostname",0,0) @importer = importer @source_file = source_file end # Initialize klass from o (location) and hash (options to created instance). # The object o is used to compute a source location. It may be nil. Source position is merged into # the given options (non surgically). If o is non-nil, the first found source position going up # the containment hierarchy is set. I.e. callers should pass nil if a source position is not wanted # or known to be unobtainable for the object. # # @param o [Object, nil] object from which source position / location is obtained, may be nil # @param klass [Class] the ast class to create an instance of # @param hash [Hash] hash with options for the class to create # def ast(o, klass, hash={}) # create and pass hash with file and line information klass.new(merge_location(hash, o)) end # THIS IS AN EXPENSIVE OPERATION # The 3x AST requires line, pos etc. to be recorded directly in the AST nodes and this information # must be computed. # (Newer implementation only computes the information that is actually needed; typically when raising an # exception). # def merge_location(hash, o) if o pos = {} source_pos = Puppet::Pops::Utils.find_closest_positioned(o) if source_pos pos[:line] = source_pos.line pos[:pos] = source_pos.pos end pos[:file] = @source_file if @source_file hash = hash.merge(pos) end hash end # Transforms pops expressions into AST 3.1 statements/expressions def transform(o) begin @@transform_visitor.visit_this(self,o) rescue StandardError => e loc_data = {} merge_location(loc_data, o) raise Puppet::ParseError.new("Error while transforming to Puppet 3 AST: #{e.message}", loc_data[:file], loc_data[:line], loc_data[:pos], e) end end # Transforms pops expressions into AST 3.1 query expressions def query(o) @@query_transform_visitor.visit_this(self, o) end # Transforms pops expressions into AST 3.1 hostnames def hostname(o) @@hostname_transform_visitor.visit_this(self, o) end def transform_LiteralFloat(o) # Numbers are Names in the AST !! (Name a.k.a BareWord) ast o, AST::Name, :value => o.value.to_s end def transform_LiteralInteger(o) s = 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 # Numbers are Names in the AST !! (Name a.k.a BareWord) ast o, AST::Name, :value => s end # Transforms all literal values to string (override for those that should not be AST::String) # def transform_LiteralValue(o) ast o, AST::String, :value => o.value.to_s end def transform_LiteralBoolean(o) ast o, AST::Boolean, :value => o.value end def transform_Factory(o) transform(o.current) end def transform_ArithmeticExpression(o) ast o, AST::ArithmeticOperator2, :lval => transform(o.left_expr), :rval=>transform(o.right_expr), :operator => o.operator.to_s end def transform_Array(o) ast nil, AST::ASTArray, :children => o.collect {|x| transform(x) } end # Puppet AST only allows: # * variable[expression] => Hasharray Access # * NAME [expressions] => Resource Reference(s) # * type [epxressions] => Resource Reference(s) # * HashArrayAccesses[expression] => HasharrayAccesses # # i.e. it is not possible to do `func()[3]`, `[1,2,3][$x]`, `{foo=>10, bar=>20}[$x]` etc. since # LHS is not an expression # # Validation for 3.x semantics should validate the illegal cases. This transformation may fail, # or ignore excess information if the expressions are not correct. # This means that the transformation does not have to evaluate the lhs to detect the target expression. # # Hm, this seems to have changed, the LHS (variable) is evaluated if evaluateable, else it is used as is. # def transform_AccessExpression(o) case o.left_expr when Model::QualifiedName ast o, AST::ResourceReference, :type => o.left_expr.value, :title => transform(o.keys) when Model::QualifiedReference ast o, AST::ResourceReference, :type => o.left_expr.value, :title => transform(o.keys) when Model::VariableExpression ast o, AST::HashOrArrayAccess, :variable => transform(o.left_expr), :key => transform(o.keys()[0]) else ast o, AST::HashOrArrayAccess, :variable => transform(o.left_expr), :key => transform(o.keys()[0]) end end # Puppet AST has a complicated structure # LHS can not be an expression, it must be a type (which is downcased). # type = a downcased QualifiedName # def transform_CollectExpression(o) raise "LHS is not a type" unless o.type_expr.is_a? Model::QualifiedReference type = o.type_expr.value().downcase() args = { :type => type } # This somewhat peculiar encoding is used by the 3.1 AST. query = transform(o.query) if query.is_a? Symbol args[:form] = query else args[:form] = query.form args[:query] = query query.type = type end if o.operations.size > 0 args[:override] = transform(o.operations) end ast o, AST::Collection, args end def transform_EppExpression(o) # TODO: Not supported in 3x TODO_EPP parameters = o.parameters.collect {|p| transform(p) } args = { :parameters => parameters } args[:children] = transform(o.body) unless is_nop?(o.body) Puppet::Parser::AST::Epp.new(merge_location(args, o)) end def transform_ExportedQuery(o) if is_nop?(o.expr) result = :exported else result = query(o.expr) result.form = :exported end result end def transform_VirtualQuery(o) if is_nop?(o.expr) result = :virtual else result = query(o.expr) result.form = :virtual end result end # Ensures transformation fails if a 3.1 non supported object is encountered in a query expression # def query_Object(o) raise "Not a valid expression in a collection query: "+o.class.name end # Puppet AST only allows == and !=, and left expr is restricted, but right value is an expression # def query_ComparisonExpression(o) if [:'==', :'!='].include? o.operator ast o, AST::CollExpr, :test1 => query(o.left_expr), :oper => o.operator.to_s, :test2 => transform(o.right_expr) else raise "Not a valid comparison operator in a collection query: " + o.operator.to_s end end def query_AndExpression(o) ast o, AST::CollExpr, :test1 => query(o.left_expr), :oper => 'and', :test2 => query(o.right_expr) end def query_OrExpression(o) ast o, AST::CollExpr, :test1 => query(o.left_expr), :oper => 'or', :test2 => query(o.right_expr) end def query_ParenthesizedExpression(o) result = query(o.expr) # produces CollExpr result.parens = true result end def query_VariableExpression(o) transform(o) end def query_QualifiedName(o) transform(o) end def query_LiteralNumber(o) transform(o) # number to string in correct radix end def query_LiteralString(o) transform(o) end def query_LiteralBoolean(o) transform(o) end def transform_QualifiedName(o) ast o, AST::Name, :value => o.value end def transform_QualifiedReference(o) ast o, AST::Type, :value => o.value end def transform_ComparisonExpression(o) ast o, AST::ComparisonOperator, :operator => o.operator.to_s, :lval => transform(o.left_expr), :rval => transform(o.right_expr) end def transform_AndExpression(o) ast o, AST::BooleanOperator, :operator => 'and', :lval => transform(o.left_expr), :rval => transform(o.right_expr) end def transform_OrExpression(o) ast o, AST::BooleanOperator, :operator => 'or', :lval => transform(o.left_expr), :rval => transform(o.right_expr) end def transform_InExpression(o) ast o, AST::InOperator, :lval => transform(o.left_expr), :rval => transform(o.right_expr) end # Assignment in AST 3.1 is to variable or hasharray accesses !!! See Bug #16116 def transform_AssignmentExpression(o) args = {:value => transform(o.right_expr) } case o.operator when :'+=' args[:append] = true when :'=' else raise "The operator #{o.operator} is not supported by Puppet 3." end args[:name] = case o.left_expr when Model::VariableExpression ast o, AST::Name, {:value => o.left_expr.expr.value } when Model::AccessExpression transform(o.left_expr) else raise "LHS is not an expression that can be assigned to" end ast o, AST::VarDef, args end # Produces (name => expr) or (name +> expr) def transform_AttributeOperation(o) args = { :value => transform(o.value_expr) } args[:add] = true if o.operator == :'+>' args[:param] = o.attribute_name ast o, AST::ResourceParam, args end def transform_LiteralList(o) # Uses default transform of Ruby Array to ASTArray transform(o.values) end # Literal hash has strange behavior in Puppet 3.1. See Bug #19426, and this implementation is bug # compatible def transform_LiteralHash(o) if o.entries.size == 0 ast o, AST::ASTHash, {:value=> {}} else value = {} o.entries.each {|x| value.merge! transform(x) } ast o, AST::ASTHash, {:value=> value} end end # Transforms entry into a hash (they are later merged with strange effects: Bug #19426). # Puppet 3.x only allows: # * NAME # * quotedtext # As keys (quoted text can be an interpolated string which is compared as a key in a less than satisfactory way). # def transform_KeyedEntry(o) value = transform(o.value) key = case o.key when Model::QualifiedName o.key.value when Model::LiteralString transform o.key when Model::LiteralNumber transform o.key when Model::ConcatenatedString transform o.key else raise "Illegal hash key expression of type (#{o.key.class})" end {key => value} end def transform_MatchExpression(o) ast o, AST::MatchOperator, :operator => o.operator.to_s, :lval => transform(o.left_expr), :rval => transform(o.right_expr) end def transform_LiteralString(o) ast o, AST::String, :value => o.value end def transform_LambdaExpression(o) astargs = { :parameters => o.parameters.collect {|p| transform(p) } } astargs.merge!({ :children => transform(o.body) }) if o.body # do not want children if it is nil/nop ast o, AST::Lambda, astargs end def transform_LiteralDefault(o) ast o, AST::Default, :value => :default end def transform_LiteralUndef(o) ast o, AST::Undef, :value => :undef end def transform_LiteralRegularExpression(o) ast o, AST::Regex, :value => o.value end def transform_Nop(o) ast o, AST::Nop end # In the 3.1. grammar this is a hash that is merged with other elements to form a method call # Also in 3.1. grammar there are restrictions on the LHS (that are only there for grammar issues). # def transform_NamedAccessExpression(o) receiver = transform(o.left_expr) name = o.right_expr raise "Unacceptable function/method name" unless name.is_a? Model::QualifiedName {:receiver => receiver, :name => name.value} end def transform_NilClass(o) ast o, AST::Nop, {} end def transform_NotExpression(o) ast o, AST::Not, :value => transform(o.expr) end def transform_VariableExpression(o) # assumes the expression is a QualifiedName ast o, AST::Variable, :value => o.expr.value end # In Puppet 3.1, the ConcatenatedString is responsible for the evaluation and stringification of # expression segments. Expressions and Strings are kept in an array. def transform_TextExpression(o) transform(o.expr) end def transform_UnaryMinusExpression(o) ast o, AST::Minus, :value => transform(o.expr) end # Puppet 3.1 representation of a BlockExpression is an AST::Array - this makes it impossible to differentiate # between a LiteralArray and a Sequence. (Should it return the collected array, or the last expression?) # (A BlockExpression has now been introduced in the AST to solve this). # def transform_BlockExpression(o) children = [] # remove nops resulting from import o.statements.each {|s| r = transform(s); children << r unless is_nop?(r) } ast o, AST::BlockExpression, :children => children # o.statements.collect {|s| transform(s) } end # Interpolated strings are kept in an array of AST (string or other expression). def transform_ConcatenatedString(o) ast o, AST::Concat, :value => o.segments.collect {|x| transform(x)} end def transform_HostClassDefinition(o) parameters = o.parameters.collect {|p| transform(p) } args = { :arguments => parameters, :parent => o.parent_class, } args[:code] = transform(o.body) unless is_nop?(o.body) Puppet::Parser::AST::Hostclass.new(o.name, merge_location(args, o)) end def transform_HeredocExpression(o) # TODO_HEREDOC Not supported in 3x args = {:syntax=> o.syntax(), :expr => transform(o.text_expr()) } Puppet::Parser::AST::Heredoc.new(merge_location(args, o)) end def transform_NodeDefinition(o) # o.host_matches are expressions, and 3.1 AST requires special object AST::HostName # where a HostName is one of NAME, STRING, DEFAULT or Regexp - all of these are strings except regexp # args = { :code => transform(o.body) } args[:parent] = hostname(o.parent) unless is_nop?(o.parent) if(args[:parent].is_a?(Array)) raise "Illegal expression - unacceptable as a node parent" end Puppet::Parser::AST::Node.new(hostname(o.host_matches), merge_location(args, o)) end # Transforms Array of host matching expressions into a (Ruby) array of AST::HostName def hostname_Array(o) o.collect {|x| ast x, AST::HostName, :value => hostname(x) } end def hostname_LiteralValue(o) return o.value end def hostname_QualifiedName(o) return o.value end def hostname_LiteralNumber(o) transform(o) # Number to string with correct radix end def hostname_LiteralDefault(o) return 'default' end def hostname_LiteralRegularExpression(o) ast o, AST::Regex, :value => o.value end def hostname_Object(o) raise "Illegal expression - unacceptable as a node name" end def transform_RelationshipExpression(o) Puppet::Parser::AST::Relationship.new(transform(o.left_expr), transform(o.right_expr), o.operator.to_s, merge_location({}, o)) end def transform_RenderStringExpression(o) # TODO_EPP Not supported in 3x ast o, AST::RenderString, :value => o.value end def transform_RenderExpression(o) # TODO_EPP Not supported in 3x ast o, AST::RenderExpression, :value => transform(o.expr) end def transform_ResourceTypeDefinition(o) parameters = o.parameters.collect {|p| transform(p) } args = { :arguments => parameters } args[:code] = transform(o.body) unless is_nop?(o.body) Puppet::Parser::AST::Definition.new(o.name, merge_location(args, o)) end # Transformation of ResourceOverrideExpression is slightly more involved than a straight forward # transformation. # A ResourceOverrideExppression has "resources" which should be an AccessExpression # on the form QualifiedName[expressions], or QualifiedReference[expressions] to be valid. # It also has a set of attribute operations. # # The AST equivalence is an AST::ResourceOverride with a ResourceReference as its LHS, and # a set of Parameters. # ResourceReference has type as a string, and the expressions representing # the "titles" to be an ASTArray. # def transform_ResourceOverrideExpression(o) - resource_ref = o.resources - raise "Unacceptable expression for resource override" unless resource_ref.is_a? Model::AccessExpression - - type = case resource_ref.left_expr - when Model::QualifiedName - # This is deprecated "Resource references should now be capitalized" - this is caught elsewhere - resource_ref.left_expr.value - when Model::QualifiedReference - resource_ref.left_expr.value - else - raise "Unacceptable expression for resource override; need NAME or CLASSREF" - end - - result_ref = ast o, AST::ResourceReference, :type => type, :title => transform(resource_ref.keys) - - # title is one or more expressions, if more than one it should be an ASTArray - ast o, AST::ResourceOverride, :object => result_ref, :parameters => transform(o.operations) + raise "Unsupported transformation - use the new evaluator" end # Parameter is a parameter in a definition of some kind. # It is transformed to an array on the form `[name]´, or `[name, value]´. def transform_Parameter(o) if o.value [o.name, transform(o.value)] else [o.name] end end # For non query expressions, parentheses can be dropped in the resulting AST. def transform_ParenthesizedExpression(o) transform(o.expr) end def transform_Program(o) transform(o.body) end def transform_IfExpression(o) args = { :test => transform(o.test), :statements => transform(o.then_expr) } args[:else] = transform(o.else_expr) # Tests say Nop should be there (unless is_nop? o.else_expr), probably not needed ast o, AST::IfStatement, args end # Unless is not an AST object, instead an AST::IfStatement is used with an AST::Not around the test # def transform_UnlessExpression(o) args = { :test => ast(o, AST::Not, :value => transform(o.test)), :statements => transform(o.then_expr) } # AST 3.1 does not allow else on unless in the grammar, but it is ok since unless is encoded as an if !x args.merge!({:else => transform(o.else_expr)}) unless is_nop?(o.else_expr) ast o, AST::IfStatement, args end # Puppet 3.1 AST only supports calling a function by name (it is not possible to produce a function # that is then called). # rval_required (for an expression) # functor_expr (lhs - the "name" expression) # arguments - list of arguments # def transform_CallNamedFunctionExpression(o) name = o.functor_expr raise "Unacceptable expression for name of function" unless name.is_a? Model::QualifiedName args = { :name => name.value, :arguments => transform(o.arguments), :ftype => o.rval_required ? :rvalue : :statement } args[:pblock] = transform(o.lambda) if o.lambda ast o, AST::Function, args end # Transformation of CallMethodExpression handles a NamedAccessExpression functor and # turns this into a 3.1 AST::MethodCall. # def transform_CallMethodExpression(o) name = o.functor_expr raise "Unacceptable expression for name of function" unless name.is_a? Model::NamedAccessExpression # transform of NamedAccess produces a hash, add arguments to it astargs = transform(name).merge(:arguments => transform(o.arguments)) astargs.merge!(:lambda => transform(o.lambda)) if o.lambda # do not want a Nop as the lambda ast o, AST::MethodCall, astargs end def transform_CaseExpression(o) # Expects expression, AST::ASTArray of AST ast o, AST::CaseStatement, :test => transform(o.test), :options => transform(o.options) end def transform_CaseOption(o) ast o, AST::CaseOpt, :value => transform(o.values), :statements => transform(o.then_expr) end def transform_ResourceBody(o) - # expects AST, AST::ASTArray of AST - ast o, AST::ResourceInstance, :title => transform(o.title), :parameters => transform(o.operations) + raise "Unsupported transformation - use the new evaluator" end def transform_ResourceDefaultsExpression(o) - ast o, AST::ResourceDefaults, :type => o.type_ref.value, :parameters => transform(o.operations) + raise "Unsupported transformation - use the new evaluator" end # Transformation of ResourceExpression requires calling a method on the resulting # AST::Resource if it is virtual or exported # def transform_ResourceExpression(o) - raise "Unacceptable type name expression" unless o.type_name.is_a? Model::QualifiedName - resource = ast o, AST::Resource, :type => o.type_name.value, :instances => transform(o.bodies) - resource.send("#{o.form}=", true) unless o.form == :regular - resource + raise "Unsupported transformation - use the new evaluator" end # Transformation of SelectorExpression is limited to certain types of expressions. # This is probably due to constraints in the old grammar rather than any real concerns. def transform_SelectorExpression(o) case o.left_expr when Model::CallNamedFunctionExpression when Model::AccessExpression when Model::VariableExpression when Model::ConcatenatedString else raise "Unacceptable select expression" unless o.left_expr.kind_of? Model::Literal end ast o, AST::Selector, :param => transform(o.left_expr), :values => transform(o.selectors) end def transform_SelectorEntry(o) ast o, AST::ResourceParam, :param => transform(o.matching_expr), :value => transform(o.value_expr) end def transform_Object(o) raise "Unacceptable transform - found an Object without a rule: #{o.class}" end # Nil, nop # Bee bopp a luh-lah, a bop bop boom. # def is_nop?(o) o.nil? || o.is_a?(Model::Nop) end end diff --git a/lib/puppet/pops/model/factory.rb b/lib/puppet/pops/model/factory.rb index 1059f29e0..cf77d9185 100644 --- a/lib/puppet/pops/model/factory.rb +++ b/lib/puppet/pops/model/factory.rb @@ -1,1005 +1,1040 @@ # 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_AttributesOperation(o, value) + o.expr = build(value) + o + end + def build_AccessExpression(o, left, *keys) o.left_expr = to_ops(left) keys.each {|expr| o.addKeys(to_ops(expr)) } o end def build_BinaryExpression(o, left, right) o.left_expr = to_ops(left) o.right_expr = to_ops(right) o end def build_BlockExpression(o, *args) args.each {|expr| o.addStatements(to_ops(expr)) } o end def build_CollectExpression(o, type_expr, query_expr, attribute_operations) o.type_expr = to_ops(type_expr) o.query = build(query_expr) attribute_operations.each {|op| o.addOperations(build(op)) } o end def build_ComparisonExpression(o, op, a, b) o.operator = op build_BinaryExpression(o, a, b) end def build_ConcatenatedString(o, *args) args.each {|expr| o.addSegments(build(expr)) } o end def build_CreateTypeExpression(o, name, super_name = nil) o.name = name o.super_name = super_name o end def build_CreateEnumExpression(o, *args) o.name = args.slice(0) if args.size == 2 o.values = build(args.last) o end def build_CreateAttributeExpression(o, name, datatype_expr) o.name = name o.type = to_ops(datatype_expr) o end def build_HeredocExpression(o, name, expr) o.syntax = name o.text_expr = build(expr) o end # @param name [String] a valid classname # @param parameters [Array] may be empty # @param parent_class_name [String, nil] a valid classname referencing a parent class, optional. # @param body [Array, Expression, nil] expression that constitute the body # @return [Model::HostClassDefinition] configured from the parameters # def build_HostClassDefinition(o, name, parameters, parent_class_name, body) build_NamedDefinition(o, name, parameters, body) o.parent_class = parent_class_name if parent_class_name o end def build_ResourceOverrideExpression(o, resources, attribute_operations) o.resources = build(resources) attribute_operations.each {|ao| o.addOperations(build(ao)) } o end def build_ReservedWord(o, name) o.word = name o end def build_KeyedEntry(o, k, v) o.key = to_ops(k) o.value = to_ops(v) o end def build_LiteralHash(o, *keyed_entries) keyed_entries.each {|entry| o.addEntries build(entry) } o end def build_LiteralList(o, *values) values.each {|v| o.addValues build(v) } o end def build_LiteralFloat(o, val) o.value = val o end def build_LiteralInteger(o, val, radix) o.value = val o.radix = radix o end def build_IfExpression(o, t, ift, els) o.test = build(t) o.then_expr = build(ift) o.else_expr= build(els) o end def build_MatchExpression(o, op, a, b) o.operator = op build_BinaryExpression(o, a, b) end # Builds body :) from different kinds of input # @overload f_build_body(nothing) # @param nothing [nil] unchanged, produces nil # @overload f_build_body(array) # @param array [Array] turns into a BlockExpression # @overload f_build_body(expr) # @param expr [Expression] produces the given expression # @overload f_build_body(obj) # @param obj [Object] produces the result of calling #build with body as argument def f_build_body(body) case body when NilClass nil when Array Puppet::Pops::Model::Factory.new(Model::BlockExpression, *body) else build(body) end end def build_LambdaExpression(o, parameters, body) parameters.each {|p| o.addParameters(build(p)) } b = f_build_body(body) o.body = to_ops(b) if b o end def build_NamedDefinition(o, name, parameters, body) parameters.each {|p| o.addParameters(build(p)) } b = f_build_body(body) o.body = b.current if b o.name = name o end # @param o [Model::NodeDefinition] # @param hosts [Array] host matches # @param parent [Expression] parent node matcher # @param body [Object] see {#f_build_body} def build_NodeDefinition(o, hosts, parent, body) hosts.each {|h| o.addHost_matches(build(h)) } o.parent = build(parent) if parent # no nop here b = f_build_body(body) o.body = b.current if b o end def build_Parameter(o, name, expr) o.name = name o.value = build(expr) if expr # don't build a nil/nop o end def build_QualifiedReference(o, name) o.value = name.to_s.downcase o end def build_RelationshipExpression(o, op, a, b) o.operator = op build_BinaryExpression(o, a, b) end def build_ResourceExpression(o, type_name, bodies) o.type_name = build(type_name) bodies.each {|b| o.addBodies(build(b)) } o end def build_RenderStringExpression(o, string) o.value = string; o end def build_ResourceBody(o, title_expression, attribute_operations) o.title = build(title_expression) attribute_operations.each {|ao| o.addOperations(build(ao)) } o end def build_ResourceDefaultsExpression(o, type_ref, attribute_operations) o.type_ref = build(type_ref) attribute_operations.each {|ao| o.addOperations(build(ao)) } o end def build_SelectorExpression(o, left, *selectors) o.left_expr = to_ops(left) selectors.each {|s| o.addSelectors(build(s)) } o end # Builds a SubLocatedExpression - this wraps the expression in a sublocation configured # from the given token # A SubLocated holds its own locator that is used for subexpressions holding positions relative # to what it describes. # def build_SubLocatedExpression(o, token, expression) o.expr = build(expression) o.offset = token.offset o.length = token.length locator = token.locator o.locator = locator o.leading_line_count = locator.leading_line_count o.leading_line_offset = locator.leading_line_offset # Index is held in sublocator's parent locator - needed to be able to reconstruct o.line_offsets = locator.locator.line_index o end def build_SelectorEntry(o, matching, value) o.matching_expr = build(matching) o.value_expr = build(value) o end def build_QueryExpression(o, expr) ops = to_ops(expr) o.expr = ops unless Puppet::Pops::Model::Factory.nop? ops o end def build_UnaryExpression(o, expr) ops = to_ops(expr) o.expr = ops unless Puppet::Pops::Model::Factory.nop? ops o end def build_Program(o, body, definitions, locator) o.body = to_ops(body) # non containment definitions.each { |d| o.addDefinitions(d) } o.source_ref = locator.file o.source_text = locator.string o.line_offsets = locator.line_index o.locator = locator o end def build_QualifiedName(o, name) o.value = name.to_s o end + def build_TokenValue(o) + raise "Factory can not deal with a Lexer Token. Got token: #{o}. Probably caused by wrong index in grammar val[n]." + end + # Puppet::Pops::Model::Factory helpers def f_build_unary(klazz, expr) Puppet::Pops::Model::Factory.new(build(klazz.new, expr)) end def f_build_binary_op(klazz, op, left, right) Puppet::Pops::Model::Factory.new(build(klazz.new, op, left, right)) end def f_build_binary(klazz, left, right) Puppet::Pops::Model::Factory.new(build(klazz.new, left, right)) end def f_build_vararg(klazz, left, *arg) Puppet::Pops::Model::Factory.new(build(klazz.new, left, *arg)) end def f_arithmetic(op, r) f_build_binary_op(Model::ArithmeticExpression, op, current, r) end def f_comparison(op, r) f_build_binary_op(Model::ComparisonExpression, op, current, r) end def f_match(op, r) f_build_binary_op(Model::MatchExpression, op, current, r) end # Operator helpers def in(r) f_build_binary(Model::InExpression, current, r); end def or(r) f_build_binary(Model::OrExpression, current, r); end def and(r) f_build_binary(Model::AndExpression, current, r); end def not(); f_build_unary(Model::NotExpression, self); end def minus(); f_build_unary(Model::UnaryMinusExpression, self); end def unfold(); f_build_unary(Model::UnfoldExpression, self); end def text(); f_build_unary(Model::TextExpression, self); end def var(); f_build_unary(Model::VariableExpression, self); end def [](*r); f_build_vararg(Model::AccessExpression, current, *r); end def dot r; f_build_binary(Model::NamedAccessExpression, current, r); end def + r; f_arithmetic(:+, r); end def - r; f_arithmetic(:-, r); end def / r; f_arithmetic(:/, r); end def * r; f_arithmetic(:*, r); end def % r; f_arithmetic(:%, r); end def << r; f_arithmetic(:<<, r); end def >> r; f_arithmetic(:>>, r); end def < r; f_comparison(:<, r); end def <= r; f_comparison(:<=, r); end def > r; f_comparison(:>, r); end def >= r; f_comparison(:>=, r); end def == r; f_comparison(:==, r); end def ne r; f_comparison(:'!=', r); end def =~ r; f_match(:'=~', r); end def mne r; f_match(:'!~', r); end def paren(); f_build_unary(Model::ParenthesizedExpression, current); end def relop op, r f_build_binary_op(Model::RelationshipExpression, op.to_sym, current, r) end def select *args Puppet::Pops::Model::Factory.new(build(Model::SelectorExpression, current, *args)) end # For CaseExpression, setting the default for an already build CaseExpression def default r current.addOptions(Puppet::Pops::Model::Factory.WHEN(:default, r).current) self end def lambda=(lambda) current.lambda = lambda.current self end # Assignment = def set(r) f_build_binary_op(Model::AssignmentExpression, :'=', current, r) end # Assignment += def plus_set(r) f_build_binary_op(Model::AssignmentExpression, :'+=', current, r) end # Assignment -= def minus_set(r) f_build_binary_op(Model::AssignmentExpression, :'-=', current, r) end def attributes(*args) args.each {|a| current.addAttributes(build(a)) } self end # Catch all delegation to current def method_missing(meth, *args, &block) if current.respond_to?(meth) current.send(meth, *args, &block) else super end end def respond_to?(meth, include_all=false) current.respond_to?(meth, include_all) || super end def self.record_position(o, start_locatable, end_locateable) new(o).record_position(start_locatable, end_locateable) end # Records the position (start -> end) and computes the resulting length. # def record_position(start_locatable, end_locatable) from = start_locatable.is_a?(Puppet::Pops::Model::Factory) ? start_locatable.current : start_locatable to = end_locatable.is_a?(Puppet::Pops::Model::Factory) ? end_locatable.current : end_locatable to = from if to.nil? || to.offset.nil? o = current # record information directly in the Model::Positioned object o.offset = from.offset o.length ||= to.offset - from.offset + to.length self end # @return [Puppet::Pops::Adapters::SourcePosAdapter] with location information def loc() Puppet::Pops::Adapters::SourcePosAdapter.adapt(current) end - # Returns symbolic information about an expected share of a resource expression given the LHS of a resource expr. + # Sets the form of the resource expression (:regular (the default), :virtual, or :exported). + # Produces true if the expression was a resource expression, false otherwise. + # + def self.set_resource_form(expr, form) + expr = expr.current if expr.is_a?(Puppet::Pops::Model::Factory) + # Note: Validation handles illegal combinations + return false unless expr.is_a?(Puppet::Pops::Model::AbstractResource) + expr.form = form + return true + end + + # Returns symbolic information about an expected shape 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 + # if Resource[e], then it is not resource specific + if expr.left_expr.is_a?(Model::QualifiedReference) && expr.left_expr.value == 'resource' && expr.keys.size == 1 + :defaults + else + :override + end when 'class' :class else :error end end # Factory starting points def self.literal(o); new(o); end def self.minus(o); new(o).minus; end def self.unfold(o); new(o).unfold; end def self.var(o); new(o).var; end def self.block(*args); new(Model::BlockExpression, *args); end def self.string(*args); new(Model::ConcatenatedString, *args); end def self.text(o); new(o).text; end def self.IF(test_e,then_e,else_e); new(Model::IfExpression, test_e, then_e, else_e); end def self.UNLESS(test_e,then_e,else_e); new(Model::UnlessExpression, test_e, then_e, else_e); end def self.CASE(test_e,*options); new(Model::CaseExpression, test_e, *options); end def self.WHEN(values_list, block); new(Model::CaseOption, values_list, block); end def self.MAP(match, value); new(Model::SelectorEntry, match, value); end def self.TYPE(name, super_name=nil); new(Model::CreateTypeExpression, name, super_name); end def self.ATTR(name, type_expr=nil); new(Model::CreateAttributeExpression, name, type_expr); end def self.ENUM(*args); new(Model::CreateEnumExpression, *args); end def self.KEY_ENTRY(key, val); new(Model::KeyedEntry, key, val); end def self.HASH(entries); new(Model::LiteralHash, *entries); end def self.HEREDOC(name, expr); new(Model::HeredocExpression, name, expr); end def self.SUBLOCATE(token, expr) new(Model::SubLocatedExpression, token, expr); end def self.LIST(entries); new(Model::LiteralList, *entries); end def self.PARAM(name, expr=nil); new(Model::Parameter, name, expr); end def self.NODE(hosts, parent, body); new(Model::NodeDefinition, hosts, parent, body); end # Parameters # Mark parameter as capturing the rest of arguments def captures_rest() current.captures_rest = true end # Set Expression that should evaluate to the parameter's type def type_expr(o) current.type_expr = to_ops(o) end # Creates a QualifiedName representation of o, unless o already represents a QualifiedName in which # case it is returned. # def self.fqn(o) o = o.current if o.is_a?(Puppet::Pops::Model::Factory) o = new(Model::QualifiedName, o) unless o.is_a? Model::QualifiedName o end # Creates a QualifiedName representation of o, unless o already represents a QualifiedName in which # case it is returned. # def self.fqr(o) o = o.current if o.is_a?(Puppet::Pops::Model::Factory) o = new(Model::QualifiedReference, o) unless o.is_a? Model::QualifiedReference o end def self.TEXT(expr) new(Model::TextExpression, new(expr).interpolate) end # TODO_EPP def self.RENDER_STRING(o) new(Model::RenderStringExpression, o) end def self.RENDER_EXPR(expr) new(Model::RenderExpression, expr) end def self.EPP(parameters, body) if parameters.nil? params = [] parameters_specified = false else params = parameters parameters_specified = true end LAMBDA(params, new(Model::EppExpression, parameters_specified, body)) end def self.RESERVED(name) new(Model::ReservedWord, name) end # TODO: This is the same a fqn factory method, don't know if callers to fqn and QNAME can live with the # same result or not yet - refactor into one method when decided. # def self.QNAME(name) new(Model::QualifiedName, name) end def self.NUMBER(name_or_numeric) if n_radix = Puppet::Pops::Utils.to_n_with_radix(name_or_numeric) val, radix = n_radix if val.is_a?(Float) new(Model::LiteralFloat, val) else new(Model::LiteralInteger, val, radix) end else # Bad number should already have been caught by lexer - this should never happen raise ArgumentError, "Internal Error, NUMBER token does not contain a valid number, #{name_or_numeric}" end end # Convert input string to either a qualified name, a LiteralInteger with radix, or a LiteralFloat # def self.QNAME_OR_NUMBER(name) if n_radix = Puppet::Pops::Utils.to_n_with_radix(name) val, radix = n_radix if val.is_a?(Float) new(Model::LiteralFloat, val) else new(Model::LiteralInteger, val, radix) end else new(Model::QualifiedName, name) end end def self.QREF(name) new(Model::QualifiedReference, name) end def self.VIRTUAL_QUERY(query_expr) new(Model::VirtualQuery, query_expr) end def self.EXPORTED_QUERY(query_expr) new(Model::ExportedQuery, query_expr) end def self.ATTRIBUTE_OP(name, op, expr) new(Model::AttributeOperation, name, op, expr) end + def self.ATTRIBUTES_OP(expr) + new(Model::AttributesOperation, expr) + end + def self.CALL_NAMED(name, rval_required, argument_list) unless name.kind_of?(Model::PopsObject) name = Puppet::Pops::Model::Factory.fqn(name) unless name.is_a?(Puppet::Pops::Model::Factory) end new(Model::CallNamedFunctionExpression, name, rval_required, *argument_list) end def self.CALL_METHOD(functor, argument_list) new(Model::CallMethodExpression, functor, true, nil, *argument_list) end def self.COLLECT(type_expr, query_expr, attribute_operations) new(Model::CollectExpression, type_expr, query_expr, attribute_operations) end def self.NAMED_ACCESS(type_name, bodies) new(Model::NamedAccessExpression, type_name, bodies) end def self.RESOURCE(type_name, bodies) new(Model::ResourceExpression, type_name, bodies) end def self.RESOURCE_DEFAULTS(type_name, attribute_operations) new(Model::ResourceDefaultsExpression, type_name, attribute_operations) end def self.RESOURCE_OVERRIDE(resource_ref, attribute_operations) new(Model::ResourceOverrideExpression, resource_ref, attribute_operations) end def self.RESOURCE_BODY(resource_title, attribute_operations) new(Model::ResourceBody, resource_title, attribute_operations) end def self.PROGRAM(body, definitions, locator) new(Model::Program, body, definitions, locator) end # Builds a BlockExpression if args size > 1, else the single expression/value in args def self.block_or_expression(*args) if args.size > 1 new(Model::BlockExpression, *args) else new(args[0]) end end def self.HOSTCLASS(name, parameters, parent, body) new(Model::HostClassDefinition, name, parameters, parent, body) end def self.DEFINITION(name, parameters, body) new(Model::ResourceTypeDefinition, name, parameters, body) end def self.LAMBDA(parameters, body) new(Model::LambdaExpression, parameters, body) end def self.nop? o o.nil? || o.is_a?(Puppet::Pops::Model::Nop) end STATEMENT_CALLS = { 'require' => true, 'realize' => true, 'include' => true, 'contain' => true, 'tag' => true, 'debug' => true, 'info' => true, 'notice' => true, 'warning' => true, 'error' => true, 'fail' => true, 'import' => true # discontinued, but transform it to make it call error reporting function } # Returns true if the given name is a "statement keyword" (require, include, contain, # error, notice, info, debug # def name_is_statement(name) STATEMENT_CALLS[name] end # Transforms an array of expressions containing literal name expressions to calls if followed by an # expression, or expression list. # def self.transform_calls(expressions) expressions.reduce([]) do |memo, expr| expr = expr.current if expr.is_a?(Puppet::Pops::Model::Factory) name = memo[-1] if name.is_a?(Model::QualifiedName) && STATEMENT_CALLS[name.value] the_call = Puppet::Pops::Model::Factory.CALL_NAMED(name, false, expr.is_a?(Array) ? expr : [expr]) # last positioned is last arg if there are several record_position(the_call, name, expr.is_a?(Array) ? expr[-1] : expr) memo[-1] = the_call if expr.is_a?(Model::CallNamedFunctionExpression) # Patch statement function call to expression style # This is needed because it is first parsed as a "statement" and the requirement changes as it becomes # an argument to the name to call transform above. expr.rval_required = true end else memo << expr if expr.is_a?(Model::CallNamedFunctionExpression) # Patch rvalue expression function call to statement style. # This is not really required but done to be AST model compliant expr.rval_required = false end end memo end end # Transforms a left expression followed by an untitled resource (in the form of attribute_operations) # @param left [Factory, Expression] the lhs followed what may be a hash def self.transform_resource_wo_title(left, attribute_ops) + # Returning nil means accepting the given as a potential resource expression return nil unless attribute_ops.is_a? Array + return nil unless left.current.is_a?(Puppet::Pops::Model::QualifiedName) keyed_entries = attribute_ops.map do |ao| return nil if ao.operator == :'+>' KEY_ENTRY(ao.attribute_name, ao.value_expr) end result = block_or_expression(*transform_calls([left, HASH(keyed_entries)])) result end # Building model equivalences of Ruby objects # Allows passing regular ruby objects to the factory to produce instructions # that when evaluated produce the same thing. def build_String(o) x = Model::LiteralString.new x.value = o; x end def build_NilClass(o) x = Model::Nop.new x end def build_TrueClass(o) x = Model::LiteralBoolean.new x.value = o x end def build_FalseClass(o) x = Model::LiteralBoolean.new x.value = o x end def build_Fixnum(o) x = Model::LiteralInteger.new x.value = o; x end def build_Float(o) x = Model::LiteralFloat.new x.value = o; x end def build_Regexp(o) x = Model::LiteralRegularExpression.new x.value = o; x end def build_EppExpression(o, parameters_specified, body) o.parameters_specified = parameters_specified b = f_build_body(body) o.body = b.current if b o end # If building a factory, simply unwrap the model oject contained in the factory. def build_Factory(o) o.current end # Creates a String literal, unless the symbol is one of the special :undef, or :default # which instead creates a LiterlUndef, or a LiteralDefault. # Supports :undef because nil creates a no-op instruction. 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 + + def to_s + Puppet::Pops::Model::ModelTreeDumper.new.dump(self) + end end diff --git a/lib/puppet/pops/model/model_meta.rb b/lib/puppet/pops/model/model_meta.rb index 89e611c22..246216aa9 100644 --- a/lib/puppet/pops/model/model_meta.rb +++ b/lib/puppet/pops/model/model_meta.rb @@ -1,567 +1,576 @@ # # 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 abstract end # A Positioned object has an offset measured in an opaque unit (representing characters) from the start # of a source text (starting # from 0), and a length measured in the same opaque unit. The resolution of the opaque unit requires the # aid of a Locator instance that knows about the measure. This information is stored in the model's # root node - a Program. # # The offset and length are optional if the source of the model is not from parsed text. # class Positioned < PopsObject abstract has_attr 'offset', Integer has_attr 'length', Integer end # @abstract base class for expressions class Expression < Positioned abstract end # A Nop - the "no op" expression. # @note not really needed since the evaluator can evaluate nil with the meaning of NoOp # @todo deprecate? May be useful if there is the need to differentiate between nil and Nop when transforming model. # class Nop < Expression end # A binary expression is abstract and has a left and a right expression. The order of evaluation # and semantics are determined by the concrete subclass. # class BinaryExpression < Expression abstract # # @!attribute [rw] left_expr # @return [Expression] contains_one_uni 'left_expr', Expression, :lowerBound => 1 contains_one_uni 'right_expr', Expression, :lowerBound => 1 end # An unary expression is abstract and contains one expression. The semantics are determined by # a concrete subclass. # class UnaryExpression < Expression abstract contains_one_uni 'expr', Expression, :lowerBound => 1 end # A class that simply evaluates to the contained expression. # It is of value in order to preserve user entered parentheses in transformations, and # transformations from model to source. # class ParenthesizedExpression < UnaryExpression; end # A boolean not expression, reversing the truth of the unary expr. # class NotExpression < UnaryExpression; end # An arithmetic expression reversing the polarity of the numeric unary expr. # class UnaryMinusExpression < UnaryExpression; end # Unfolds an array (a.k.a 'splat') class UnfoldExpression < UnaryExpression; end OpAssignment = RGen::MetamodelBuilder::DataTypes::Enum.new( :literals => [:'=', :'+=', :'-='], :name => 'OpAssignment') # An assignment expression assigns a value to the lval() of the left_expr. # class AssignmentExpression < BinaryExpression has_attr 'operator', OpAssignment, :lowerBound => 1 end OpArithmetic = RGen::MetamodelBuilder::DataTypes::Enum.new( :literals => [:'+', :'-', :'*', :'%', :'/', :'<<', :'>>' ], :name => 'OpArithmetic') # An arithmetic expression applies an arithmetic operator on left and right expressions. # class ArithmeticExpression < BinaryExpression has_attr 'operator', OpArithmetic, :lowerBound => 1 end OpRelationship = RGen::MetamodelBuilder::DataTypes::Enum.new( :literals => [:'->', :'<-', :'~>', :'<~'], :name => 'OpRelationship') # A relationship expression associates the left and right expressions # class RelationshipExpression < BinaryExpression has_attr 'operator', OpRelationship, :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 OpComparison = RGen::MetamodelBuilder::DataTypes::Enum.new( :literals => [:'==', :'!=', :'<', :'>', :'<=', :'>=' ], :name => 'OpComparison') # A comparison expression compares left and right using a comparison operator. # class ComparisonExpression < BinaryExpression has_attr 'operator', OpComparison, :lowerBound => 1 end OpMatch = RGen::MetamodelBuilder::DataTypes::Enum.new( :literals => [:'!~', :'=~'], :name => 'OpMatch') # A match expression matches left and right using a matching operator. # class MatchExpression < BinaryExpression has_attr 'operator', OpMatch, :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 OpAttribute = RGen::MetamodelBuilder::DataTypes::Enum.new( :literals => [:'=>', :'+>', ], :name => 'OpAttribute') + class AbstractAttributeOperation < Positioned + end + # An attribute operation sets or appends a value to a named attribute. # - class AttributeOperation < Positioned + class AttributeOperation < AbstractAttributeOperation has_attr 'attribute_name', String, :lowerBound => 1 has_attr 'operator', OpAttribute, :lowerBound => 1 contains_one_uni 'value_expr', Expression, :lowerBound => 1 end + # An attribute operation containing an expression that must evaluate to a Hash + # + class AttributesOperation < AbstractAttributeOperation + contains_one_uni 'expr', Expression, :lowerBound => 1 + end + # An object that collects stored objects from the central cache and returns # them to the current host. Operations may optionally be applied. # class CollectExpression < Expression contains_one_uni 'type_expr', Expression, :lowerBound => 1 contains_one_uni 'query', QueryExpression, :lowerBound => 1 contains_many_uni 'operations', AttributeOperation end class Parameter < Positioned has_attr 'name', String, :lowerBound => 1 contains_one_uni 'value', Expression contains_one_uni 'type_expr', Expression, :lowerBound => 0 has_attr 'captures_rest', Boolean end # Abstract base class for definitions. # class Definition < Expression abstract end # Abstract base class for named and parameterized definitions. class NamedDefinition < Definition abstract has_attr 'name', String, :lowerBound => 1 contains_many_uni 'parameters', Parameter contains_one_uni 'body', Expression end # A resource type definition (a 'define' in the DSL). # class ResourceTypeDefinition < NamedDefinition end # A node definition matches hosts using Strings, or Regular expressions. It may inherit from # a parent node (also using a String or Regular expression). # class NodeDefinition < Definition contains_one_uni 'parent', Expression contains_many_uni 'host_matches', Expression, :lowerBound => 1 contains_one_uni 'body', Expression end class LocatableExpression < Expression has_many_attr 'line_offsets', Integer has_attr 'locator', Object, :lowerBound => 1, :transient => true 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 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 end # A Literal String # class LiteralString < LiteralValue has_attr 'value', String, :lowerBound => 1 end class LiteralNumber < LiteralValue abstract end # A literal number has a radix of decimal (10), octal (8), or hex (16) to enable string conversion with the input radix. # By default, a radix of 10 is used. # class LiteralInteger < LiteralNumber has_attr 'radix', Integer, :lowerBound => 1, :defaultValueLiteral => "10" has_attr 'value', Integer, :lowerBound => 1 end class LiteralFloat < LiteralNumber has_attr 'value', Float, :lowerBound => 1 end # The DSL `undef`. # class LiteralUndef < Literal; end # The DSL `default` class LiteralDefault < Literal; end # DSL `true` or `false` class LiteralBoolean < LiteralValue has_attr 'value', Boolean, :lowerBound => 1 end # A text expression is an interpolation of an expression. If the embedded expression is # a QualifiedName, it is taken as a variable name and resolved. All other expressions are evaluated. # The result is transformed to a string. # class TextExpression < UnaryExpression; end # An interpolated/concatenated string. The contained segments are expressions. Verbatim sections # should be LiteralString instances, and interpolated expressions should either be # TextExpression instances (if QualifiedNames should be turned into variables), or any other expression # if such treatment is not needed. # class ConcatenatedString < Expression contains_many_uni 'segments', Expression end # A DSL NAME (one or multiple parts separated by '::'). # class QualifiedName < LiteralValue has_attr 'value', String, :lowerBound => 1 end # Represents a parsed reserved word class ReservedWord < LiteralValue has_attr 'word', String, :lowerBound => 1 end # A DSL CLASSREF (one or multiple parts separated by '::' where (at least) the first part starts with an upper case letter). # class QualifiedReference < LiteralValue has_attr 'value', String, :lowerBound => 1 end # A Variable expression looks up value of expr (some kind of name) in scope. # The expression is typically a QualifiedName, or QualifiedReference. # class VariableExpression < UnaryExpression; end # Epp start class EppExpression < Expression # EPP can be specified without giving any parameter specification. # However, the parameters of the lambda in that case are the empty # array, which is the same as when the parameters are explicity # specified as empty. This attribute tracks that difference. has_attr 'parameters_specified', Boolean contains_one_uni 'body', Expression end # A string to render class RenderStringExpression < LiteralString end # An expression to evluate and render class RenderExpression < UnaryExpression end # A resource body describes one resource instance # class ResourceBody < Positioned contains_one_uni 'title', Expression - contains_many_uni 'operations', AttributeOperation + contains_many_uni 'operations', AbstractAttributeOperation end ResourceFormEnum = RGen::MetamodelBuilder::DataTypes::Enum.new( :literals => [:regular, :virtual, :exported ], :name => 'ResourceFormEnum') # 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', ResourceFormEnum, :lowerBound => 1, :defaultValueLiteral => "regular" has_attr 'virtual', Boolean, :derived => true has_attr 'exported', Boolean, :derived => true 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 + contains_one_uni 'type_ref', Expression + contains_many_uni 'operations', AbstractAttributeOperation end # A resource override overrides already set values. # - class ResourceOverrideExpression < Expression + class ResourceOverrideExpression < AbstractResource contains_one_uni 'resources', Expression, :lowerBound => 1 - contains_many_uni 'operations', AttributeOperation + contains_many_uni 'operations', AbstractAttributeOperation 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 end end diff --git a/lib/puppet/pops/model/model_tree_dumper.rb b/lib/puppet/pops/model/model_tree_dumper.rb index 2791a01ba..4455414c1 100644 --- a/lib/puppet/pops/model/model_tree_dumper.rb +++ b/lib/puppet/pops/model/model_tree_dumper.rb @@ -1,398 +1,407 @@ # Dumps a Pops::Model in reverse polish notation; i.e. LISP style # The intention is to use this for debugging output # TODO: BAD NAME - A DUMP is a Ruby Serialization # class Puppet::Pops::Model::ModelTreeDumper < Puppet::Pops::Model::TreeDumper def dump_Array o o.collect {|e| do_dump(e) } end def dump_LiteralFloat o o.value.to_s end def dump_LiteralInteger o case o.radix when 10 o.value.to_s when 8 "0%o" % o.value when 16 "0x%X" % o.value else "bad radix:" + o.value.to_s end end def dump_LiteralValue o o.value.to_s end def dump_Factory o do_dump(o.current) end def dump_ArithmeticExpression o [o.operator.to_s, do_dump(o.left_expr), do_dump(o.right_expr)] end # x[y] prints as (slice x y) def dump_AccessExpression o if o.keys.size <= 1 ["slice", do_dump(o.left_expr), do_dump(o.keys[0])] else ["slice", do_dump(o.left_expr), do_dump(o.keys)] end end def dump_MatchesExpression o [o.operator.to_s, do_dump(o.left_expr), do_dump(o.right_expr)] end def dump_CollectExpression o result = ["collect", do_dump(o.type_expr), :indent, :break, do_dump(o.query), :indent] o.operations do |ao| result << :break << do_dump(ao) end result += [:dedent, :dedent ] result end def dump_EppExpression o result = ["epp"] # result << ["parameters"] + o.parameters.collect {|p| do_dump(p) } if o.parameters.size() > 0 if o.body result << do_dump(o.body) else result << [] end result end def dump_ExportedQuery o result = ["<<| |>>"] result += dump_QueryExpression(o) unless is_nop?(o.expr) result end def dump_VirtualQuery o result = ["<| |>"] result += dump_QueryExpression(o) unless is_nop?(o.expr) result end def dump_QueryExpression o [do_dump(o.expr)] end def dump_ComparisonExpression o [o.operator.to_s, do_dump(o.left_expr), do_dump(o.right_expr)] end def dump_AndExpression o ["&&", do_dump(o.left_expr), do_dump(o.right_expr)] end def dump_OrExpression o ["||", do_dump(o.left_expr), do_dump(o.right_expr)] end def dump_InExpression o ["in", do_dump(o.left_expr), do_dump(o.right_expr)] end def dump_AssignmentExpression o [o.operator.to_s, do_dump(o.left_expr), do_dump(o.right_expr)] end # Produces (name => expr) or (name +> expr) def dump_AttributeOperation o [o.attribute_name, o.operator, do_dump(o.value_expr)] end + def dump_AttributesOperation o + ['* =>', do_dump(o.expr)] + end + def dump_LiteralList o ["[]"] + o.values.collect {|x| do_dump(x)} end def dump_LiteralHash o ["{}"] + o.entries.collect {|x| do_dump(x)} end def dump_KeyedEntry o [do_dump(o.key), do_dump(o.value)] end def dump_MatchExpression o [o.operator.to_s, do_dump(o.left_expr), do_dump(o.right_expr)] end def dump_LiteralString o "'#{o.value}'" end def dump_LambdaExpression o result = ["lambda"] result << ["parameters"] + o.parameters.collect {|p| do_dump(p) } if o.parameters.size() > 0 if o.body result << do_dump(o.body) else result << [] end result end def dump_LiteralDefault o ":default" end def dump_LiteralUndef o ":undef" end def dump_LiteralRegularExpression o "/#{o.value.source}/" end def dump_Nop o ":nop" end def dump_NamedAccessExpression o [".", do_dump(o.left_expr), do_dump(o.right_expr)] end def dump_NilClass o "()" end def dump_NotExpression o ['!', dump(o.expr)] end def dump_VariableExpression o "$#{dump(o.expr)}" end # Interpolation (to string) shown as (str expr) def dump_TextExpression o ["str", do_dump(o.expr)] end def dump_UnaryMinusExpression o ['-', do_dump(o.expr)] end def dump_UnfoldExpression o ['unfold', do_dump(o.expr)] end def dump_BlockExpression o - ["block"] + o.statements.collect {|x| do_dump(x) } + result = ["block", :indent] + o.statements.each {|x| result << :break; result << do_dump(x) } + result << :dedent << :break + result end # Interpolated strings are shown as (cat seg0 seg1 ... segN) def dump_ConcatenatedString o ["cat"] + o.segments.collect {|x| do_dump(x)} end def dump_HeredocExpression(o) result = ["@(#{o.syntax})", :indent, :break, do_dump(o.text_expr), :dedent, :break] end def dump_HostClassDefinition o result = ["class", o.name] result << ["inherits", o.parent_class] if o.parent_class result << ["parameters"] + o.parameters.collect {|p| do_dump(p) } if o.parameters.size() > 0 if o.body result << do_dump(o.body) else result << [] end result end def dump_NodeDefinition o result = ["node"] result << ["matches"] + o.host_matches.collect {|m| do_dump(m) } result << ["parent", do_dump(o.parent)] if o.parent if o.body result << do_dump(o.body) else result << [] end result end def dump_NamedDefinition o # the nil must be replaced with a string result = [nil, o.name] result << ["parameters"] + o.parameters.collect {|p| do_dump(p) } if o.parameters.size() > 0 if o.body result << do_dump(o.body) else result << [] end result end def dump_ResourceTypeDefinition o result = dump_NamedDefinition(o) result[0] = 'define' result end def dump_ResourceOverrideExpression o - result = ["override", do_dump(o.resources), :indent] + form = o.form == :regular ? '' : o.form.to_s + "-" + result = [form+"override", do_dump(o.resources), :indent] o.operations.each do |p| result << :break << do_dump(p) end result << :dedent result end def dump_ReservedWord o [ 'reserved', o.word ] end # Produces parameters as name, or (= name value) def dump_Parameter o name_prefix = o.captures_rest ? '*' : '' name_part = "#{name_prefix}#{o.name}" if o.value && o.type_expr ["=t", do_dump(o.type_expr), name_part, do_dump(o.value)] elsif o.value ["=", name_part, do_dump(o.value)] elsif o.type_expr ["t", do_dump(o.type_expr), name_part] else name_part end end def dump_ParenthesizedExpression o do_dump(o.expr) end # Hides that Program exists in the output (only its body is shown), the definitions are just # references to contained classes, resource types, and nodes def dump_Program(o) dump(o.body) end def dump_IfExpression o result = ["if", do_dump(o.test), :indent, :break, ["then", :indent, do_dump(o.then_expr), :dedent]] result += [:break, ["else", :indent, do_dump(o.else_expr), :dedent], :dedent] unless is_nop? o.else_expr result end def dump_UnlessExpression o result = ["unless", do_dump(o.test), :indent, :break, ["then", :indent, do_dump(o.then_expr), :dedent]] result += [:break, ["else", :indent, do_dump(o.else_expr), :dedent], :dedent] unless is_nop? o.else_expr result end # Produces (invoke name args...) when not required to produce an rvalue, and # (call name args ... ) otherwise. # def dump_CallNamedFunctionExpression o result = [o.rval_required ? "call" : "invoke", do_dump(o.functor_expr)] o.arguments.collect {|a| result << do_dump(a) } result end # def dump_CallNamedFunctionExpression o # result = [o.rval_required ? "call" : "invoke", do_dump(o.functor_expr)] # o.arguments.collect {|a| result << do_dump(a) } # result # end def dump_CallMethodExpression o result = [o.rval_required ? "call-method" : "invoke-method", do_dump(o.functor_expr)] o.arguments.collect {|a| result << do_dump(a) } result << do_dump(o.lambda) if o.lambda result end def dump_CaseExpression o result = ["case", do_dump(o.test), :indent] o.options.each do |s| result << :break << do_dump(s) end result << :dedent end def dump_CaseOption o result = ["when"] result << o.values.collect {|x| do_dump(x) } result << ["then", do_dump(o.then_expr) ] result end def dump_RelationshipExpression o [o.operator.to_s, do_dump(o.left_expr), do_dump(o.right_expr)] end def dump_RenderStringExpression o ["render-s", " '#{o.value}'"] end def dump_RenderExpression o ["render", do_dump(o.expr)] end def dump_ResourceBody o result = [do_dump(o.title), :indent] o.operations.each do |p| result << :break << do_dump(p) end result << :dedent result end def dump_ResourceDefaultsExpression o - result = ["resource-defaults", do_dump(o.type_ref), :indent] + form = o.form == :regular ? '' : o.form.to_s + "-" + result = [form+"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 f56f50968..54ef2a9bc 100644 --- a/lib/puppet/pops/parser/egrammar.ra +++ b/lib/puppet/pops/parser/egrammar.ra @@ -1,809 +1,755 @@ # vim: syntax=ruby # Parser using the Pops model, expression based class Puppet::Pops::Parser::Parser token STRING DQPRE DQMID DQPOST token WORD token LBRACK RBRACK LBRACE RBRACE SYMBOL FARROW COMMA TRUE token FALSE EQUALS APPENDS DELETES LESSEQUAL NOTEQUAL DOT COLON LLCOLLECT RRCOLLECT token QMARK LPAREN RPAREN ISEQUAL GREATEREQUAL GREATERTHAN LESSTHAN token IF ELSE token DEFINE ELSIF VARIABLE CLASS INHERITS NODE BOOLEAN token NAME SEMIC CASE DEFAULT AT ATAT LCOLLECT RCOLLECT CLASSREF token NOT OR AND UNDEF PARROW PLUS MINUS TIMES DIV LSHIFT RSHIFT UMINUS token MATCH NOMATCH REGEX IN_EDGE OUT_EDGE IN_EDGE_SUB OUT_EDGE_SUB token IN UNLESS PIPE token LAMBDA SELBRACE token NUMBER token HEREDOC SUBLOCATE token RENDER_STRING RENDER_EXPR EPP_START EPP_END EPP_END_TRIM token FUNCTION token PRIVATE ATTR TYPE token LOW prechigh left HIGH left SEMIC left PIPE left LPAREN left RPAREN - left AT ATAT left DOT - left CALL nonassoc EPP_START left LBRACK LISTSTART left RBRACK left QMARK left LCOLLECT LLCOLLECT right NOT nonassoc SPLAT nonassoc UMINUS left IN left MATCH NOMATCH left TIMES DIV MODULO left MINUS PLUS left LSHIFT RSHIFT left NOTEQUAL ISEQUAL left GREATEREQUAL GREATERTHAN LESSTHAN LESSEQUAL left AND left OR - right APPENDS DELETES EQUALS left LBRACE left SELBRACE left RBRACE + right AT ATAT + right APPENDS DELETES EQUALS 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::Program] with a body containing what was parsed program : statements { result = create_program(Factory.block_or_expression(*val[0])) } | epp_expression { result = create_program(Factory.block_or_expression(*val[0])) } | { result = create_empty_program() } # 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 +# Collects sequence of elements into a list that the statements rule can transform +# (Needed because language supports function calls without parentheses around arguments). # 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 +# This exists to handle multiple arguments to non parenthesized function call. If e is expression, +# the a program can consists of e [e,e,e] where the first may be a name of a function to call. +# syntactic_statement - : any_expression { result = val[0] } - | syntactic_statement COMMA any_expression { result = aryfy(val[0]).push val[2] } + : assignment =LOW { result = val[0] } + | syntactic_statement COMMA assignment =LOW { result = aryfy(val[0]).push val[2] } + +# Assignment (is right recursive since assignment is right associative) +assignment + : relationship =LOW + | relationship EQUALS assignment { result = val[0].set(val[2]) ; loc result, val[1] } + | relationship APPENDS assignment { result = val[0].plus_set(val[2]) ; loc result, val[1] } + | relationship DELETES assignment { result = val[0].minus_set(val[2]); loc result, val[1] } + +assignments + : assignment { result = [val[0]] } + | assignments COMMA assignment { result = val[0].push(val[2]) } + +relationship + : resource =LOW + | relationship IN_EDGE resource { result = val[0].relop(val[1][:value], val[2]); loc result, val[1] } + | relationship IN_EDGE_SUB resource { result = val[0].relop(val[1][:value], val[2]); loc result, val[1] } + | relationship OUT_EDGE resource { result = val[0].relop(val[1][:value], val[2]); loc result, val[1] } + | relationship OUT_EDGE_SUB resource { result = val[0].relop(val[1][:value], val[2]); loc result, val[1] } + +#-- RESOURCE +# +resource + : expression = LOW + + #---VIRTUAL + | AT resource { + result = val[1] + unless Factory.set_resource_form(result, :virtual) + # This is equivalent to a syntax error - additional semantic restrictions apply + error val[0], "Virtual (@) can only be applied to a Resource Expression" + end + # relocate the result + loc result, val[0], val[1] + } + + #---EXPORTED + | ATAT resource { + result = val[1] + unless Factory.set_resource_form(result, :exported) + # This is equivalent to a syntax error - additional semantic restrictions apply + error val[0], "Exported (@@) can only be applied to a Resource Expression" + end + # relocate the result + loc result, val[0], val[1] + } + + #---RESOURCE TITLED 3x and 4x + | resource LBRACE expression COLON attribute_operations additional_resource_bodies RBRACE { + bodies = [Factory.RESOURCE_BODY(val[2], val[4])] + val[5] + result = Factory.RESOURCE(val[0], bodies) + loc result, val[0], val[6] + } + + #---CLASS RESOURCE + | CLASS LBRACE resource_bodies endsemi RBRACE { + result = Factory.RESOURCE(Factory.fqn(token_text(val[0])), val[2]) + loc result, val[0], val[4] + } + + # --RESOURCE 3X Expression + # Handles both 3x overrides and defaults (i.e. single resource_body without title colon) + # Slated for possible deprecation since it requires transformation and mix static/evaluation check + # + | resource LBRACE attribute_operations endcomma RBRACE { + result = case Factory.resource_shape(val[0]) + when :resource, :class + # This catches deprecated syntax. + # If the attribute operations does not include +>, then the found expression + # is actually a LEFT followed by LITERAL_HASH + # + unless tmp = transform_resource_wo_title(val[0], val[2]) + error val[1], "Syntax error resource body without title or hash with +>" + end + tmp + when :defaults + Factory.RESOURCE_DEFAULTS(val[0], val[2]) + when :override + # This was only done for override in original - TODO should 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] + } -any_expression - : relationship_expression + resource_body + : expression COLON attribute_operations endcomma { result = Factory.RESOURCE_BODY(val[0], val[2]) } -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] } + resource_bodies + : resource_body =HIGH { result = [val[0]] } + | resource_bodies SEMIC resource_body =HIGH { result = val[0].push val[2] } -#---EXPRESSION + # This is a rule for the intermediate state where RACC has seen enough tokens to understand that + # what is expressed is a Resource Expression, it now has to get to the finishing line + # + additional_resource_bodies + : endcomma { result = [] } + | endcomma SEMIC { result = [] } + | endcomma SEMIC resource_bodies endsemi { result = val[2] } + +#-- EXPRESSION # -# Produces Model::Expression expression - : higher_precedence + : primary_expression + | call_function_expression | expression LBRACK expressions RBRACK =LBRACK { result = val[0][*val[2]] ; loc result, val[0], val[3] } | expression IN expression { result = val[0].in val[2] ; loc result, val[1] } | expression MATCH expression { result = val[0] =~ val[2] ; loc result, val[1] } | expression NOMATCH expression { result = val[0].mne val[2] ; loc result, val[1] } | expression PLUS expression { result = val[0] + val[2] ; loc result, val[1] } | expression MINUS expression { result = val[0] - val[2] ; loc result, val[1] } | expression DIV expression { result = val[0] / val[2] ; loc result, val[1] } | expression TIMES expression { result = val[0] * val[2] ; loc result, val[1] } | expression MODULO expression { result = val[0] % val[2] ; loc result, val[1] } | expression LSHIFT expression { result = val[0] << val[2] ; loc result, val[1] } | expression RSHIFT expression { result = val[0] >> val[2] ; loc result, val[1] } | MINUS expression =UMINUS { result = val[1].minus() ; loc result, val[0] } | TIMES expression =SPLAT { result = val[1].unfold() ; loc result, val[0] } | expression NOTEQUAL expression { result = val[0].ne val[2] ; loc result, val[1] } | expression ISEQUAL expression { result = val[0] == val[2] ; loc result, val[1] } | expression GREATERTHAN expression { result = val[0] > val[2] ; loc result, val[1] } | expression GREATEREQUAL expression { result = val[0] >= val[2] ; loc result, val[1] } | expression LESSTHAN expression { result = val[0] < val[2] ; loc result, val[1] } | expression LESSEQUAL expression { result = val[0] <= val[2] ; loc result, val[1] } | NOT expression { result = val[1].not ; loc result, val[0] } | expression AND expression { result = val[0].and val[2] ; loc result, val[1] } | expression OR expression { result = val[0].or val[2] ; loc result, val[1] } - | expression EQUALS expression { result = val[0].set(val[2]) ; loc result, val[1] } - | expression APPENDS expression { result = val[0].plus_set(val[2]) ; loc result, val[1] } - | expression DELETES expression { result = val[0].minus_set(val[2]); loc result, val[1] } | expression QMARK selector_entries { result = val[0].select(*val[2]) ; loc result, val[0] } - | LPAREN expression RPAREN { result = val[1].paren() ; loc result, val[0] } + | LPAREN assignment RPAREN { result = val[1].paren() ; loc result, val[0] } + #---EXPRESSIONS -# (e.g. argument list) +# (i.e. "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 + : variable | call_method_with_lambda_expression | collection_expression | case_expression | if_expression | unless_expression | definition_expression | hostclass_expression | node_definition_expression | epp_render_expression | reserved_word - -# Allways have the same value -literal_expression - : array - | boolean - | default + | array | hash | regex - | text_or_name - | number + | quotedtext | type - | undef + | NUMBER { result = Factory.NUMBER(val[0][:value]) ; loc result, val[0] } + | BOOLEAN { result = Factory.literal(val[0][:value]) ; loc result, val[0] } + | DEFAULT { result = Factory.literal(:default) ; loc result, val[0] } + | UNDEF { result = Factory.literal(:undef) ; loc result, val[0] } + | NAME { result = Factory.QNAME_OR_NUMBER(val[0][:value]) ; loc result, val[0] } -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 { + : expression LPAREN assignments endcomma RPAREN { result = Factory.CALL_NAMED(val[0], true, val[2]) loc result, val[0], val[4] } - | primary_expression LPAREN RPAREN { + | expression LPAREN RPAREN { result = Factory.CALL_NAMED(val[0], true, []) loc result, val[0], val[2] } - | primary_expression LPAREN expressions endcomma RPAREN lambda { + | expression LPAREN assignments 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 { + | 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 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 assignments 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 + result = Factory.LAMBDA(val[0][:value], val[1][:value]) + loc result, val[0][:start], val[1][:end] } lambda_rest - : LBRACE statements RBRACE { result = val[1] } - | LBRACE RBRACE { result = nil } + : LBRACE statements RBRACE { result = {:end => val[2], :value =>val[1] } } + | LBRACE RBRACE { result = {:end => val[1], :value => nil } } + -# Produces Array lambda_parameter_list - : PIPE PIPE { result = [] } - | PIPE parameters endcomma PIPE { result = val[1] } + : PIPE PIPE { result = {:start => val[0], :value => [] } } + | PIPE parameters endcomma PIPE { result = {:start => val[0], :value => 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] } + | 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] + : expressions COLON LBRACE options_statements RBRACE { + result = Factory.WHEN(val[0], val[3]); loc result, val[1], val[4] } - case_colon: COLON =CASE_COLON { result = val[0] } + options_statements + : nil + | statements # This special construct is required or racc will produce the wrong result when the selector entry # LHS is generalized to any expression (LBRACE looks like a hash). Thus it is not possible to write # a selector with a single entry where the entry LHS is a hash. # The SELBRACE token is a LBRACE that follows a QMARK, and this is produced by the lexer with a lookback # Produces Array # selector_entries : selector_entry | SELBRACE selector_entry_list endcomma RBRACE { result = val[1] } # Produces Array selector_entry_list : selector_entry { result = [val[0]] } | selector_entry_list COMMA selector_entry { result = val[0].push val[2] } # Produces a Model::SelectorEntry # This FARROW wins over FARROW in Hash selector_entry : expression FARROW expression { result = Factory.MAP(val[0], val[2]) ; loc result, val[1] } -#---RESOURCE -# -# Produces [Model::ResourceExpression, Model::ResourceDefaultsExpression] - -# The resource expression parses a generalized syntax and then selects the correct -# resulting model based on the combinatoin of the LHS and what follows. -# It also handled exported and virtual resources, and the class case -# -resource_expression - : expression =LOW { - result = val[0] - } - | at expression LBRACE resourceinstances endsemi RBRACE { - result = case Factory.resource_shape(val[1]) - when :resource, :class - tmp = Factory.RESOURCE(Factory.fqn(token_text(val[1])), val[3]) - tmp.form = val[0] - tmp - when :defaults - error val[1], "A resource default can not be virtual or exported" - when :override - error val[1], "A resource override can not be virtual or exported" - else - error val[1], "Expression is not valid as a resource, resource-default, or resource-override" - end - loc result, val[1], val[4] - } - | at expression LBRACE attribute_operations endcomma RBRACE { - result = case Factory.resource_shape(val[1]) - when :resource, :class, :defaults, :override - error val[1], "Defaults are not virtualizable" - else - error val[1], "Expression is not valid as a resource, resource-default, or resource-override" - end - } - | expression LBRACE resourceinstances endsemi RBRACE { - result = case Factory.resource_shape(val[0]) - when :resource, :class - Factory.RESOURCE(Factory.fqn(token_text(val[0])), val[2]) - when :defaults - error val[1], "A resource default can not specify a resource name" - when :override - error val[1], "A resource override does not allow override of name of resource" - else - error val[1], "Expression is not valid as a resource, resource-default, or resource-override" - end - loc result, val[0], val[4] - } - | expression LBRACE attribute_operations endcomma RBRACE { - result = case Factory.resource_shape(val[0]) - when :resource, :class - # This catches deprecated syntax. - # If the attribute operations does not include +>, then the found expression - # is actually a LEFT followed by LITERAL_HASH - # - unless tmp = transform_resource_wo_title(val[0], val[2]) - error val[1], "Syntax error resource body without title or hash with +>" - end - tmp - when :defaults - Factory.RESOURCE_DEFAULTS(val[0], val[2]) - when :override - # This was only done for override in original - TODO shuld it be here at all - Factory.RESOURCE_OVERRIDE(val[0], val[2]) - else - error val[0], "Expression is not valid as a resource, resource-default, or resource-override" - end - loc result, val[0], val[4] - } - | at CLASS LBRACE resourceinstances endsemi RBRACE { - result = Factory.RESOURCE(Factory.fqn(token_text(val[1])), val[3]) - result.form = val[0] - loc result, val[1], val[5] - } - | CLASS LBRACE resourceinstances endsemi RBRACE { - result = Factory.RESOURCE(Factory.fqn(token_text(val[0])), val[2]) - loc result, val[0], val[4] - } - - resourceinst - : expression title_colon attribute_operations endcomma { result = Factory.RESOURCE_BODY(val[0], val[2]) } - - title_colon : COLON =TITLE_COLON { result = val[0] } - - resourceinstances - : resourceinst { result = [val[0]] } - | resourceinstances SEMIC resourceinst { result = val[0].push val[2] } - - # Produces Symbol corresponding to resource form - # - at - : AT { result = :virtual } - | AT AT { result = :exported } - | ATAT { result = :exported } - #---COLLECTION # # A Collection is a predicate applied to a set of objects with an implied context (used variables are # attributes of the object. -# i.e. this is equivalent for source.select(QUERY).apply(ATTRIBUTE_OPERATIONS) -# -# Produces Model::CollectExpression +# i.e. this is equivalent to source.select(QUERY).apply(ATTRIBUTE_OPERATIONS) # 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 (Not an expression) # attribute_operations : { result = [] } + | TIMES FARROW expression { result = [tmp = Factory.ATTRIBUTES_OP(val[2])] ; loc tmp, val[0], val[2] } | 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 +# | 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 endcomma nodeparent LBRACE statements RBRACE { result = add_definition(Factory.NODE(val[1], val[3], val[5])) loc result, val[0], val[6] } | NODE hostnames endcomma nodeparent LBRACE RBRACE { result = add_definition(Factory.NODE(val[1], val[3], nil)) loc result, val[0], val[5] } # 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] } + : dotted_name + | quotedtext | DEFAULT { result = Factory.literal(:default); loc result, val[0] } | regex dotted_name : name_or_number { result = Factory.literal(val[0][:value]); loc result, val[0] } | dotted_name DOT name_or_number { result = Factory.concat(val[0], '.', val[2][:value]); loc result, val[0], val[2] } name_or_number : NAME | NUMBER # Produces Expression, since hostname is an Expression nodeparent : nil | INHERITS hostname { result = val[1] } #---FUNCTION DEFINITION # #function_definition # For now the function word will just be reserved, in the future it will # produce a function definition # FUNCTION classname parameter_list LBRACE opt_statements RBRACE { # result = add_definition(Factory.FUNCTION(val[1][:value], val[2], val[4])) # loc result, val[0], val[5] # } #---NAMES AND PARAMETERS COMMON TO SEVERAL RULES # Produces String # TODO: The error that "class" is not a valid classname is bad - classname rule is also used for other things classname - : NAME { result = val[0] } + : NAME | CLASS { error val[0], "'class' is not a valid classname" } # Produces Array parameter_list : nil { result = [] } | LPAREN RPAREN { result = [] } | LPAREN parameters endcomma RPAREN { result = val[1] } # Produces Array parameters : parameter { result = [val[0]] } | parameters COMMA parameter { result = val[0].push(val[2]) } # Produces Model::Parameter parameter : untyped_parameter | typed_parameter untyped_parameter : regular_parameter | splat_parameter regular_parameter : VARIABLE EQUALS expression { result = Factory.PARAM(val[0][:value], val[2]) ; loc result, val[0] } | VARIABLE { result = Factory.PARAM(val[0][:value]); loc result, val[0] } splat_parameter : TIMES regular_parameter { result = val[1]; val[1].captures_rest() } typed_parameter : parameter_type untyped_parameter { val[1].type_expr(val[0]) ; result = val[1] } parameter_type : type { result = val[0] } | type LBRACK expressions RBRACK { result = val[0][*val[2]] ; loc result, val[0], val[3] } -#--RESTRICTED EXPRESSIONS -# i.e. where one could have expected an expression, but the set is limited - -## What is allowed RHS of match operators (see expression) -#match_rvalue -# : regex -# | text_or_name - #--VARIABLE # variable : VARIABLE { result = Factory.fqn(val[0][:value]).var ; loc result, val[0] } #---RESERVED WORDS # reserved_word : FUNCTION { result = Factory.RESERVED(val[0][:value]) ; loc result, val[0] } | PRIVATE { result = Factory.RESERVED(val[0][:value]) ; loc result, val[0] } | TYPE { result = Factory.RESERVED(val[0][:value]) ; loc result, val[0] } | ATTR { result = Factory.RESERVED(val[0][:value]) ; loc result, val[0] } #---LITERALS (dynamic and static) # array - : LBRACK expressions RBRACK { result = Factory.LIST(val[1]); loc result, val[0], val[2] } - | LBRACK expressions COMMA RBRACK { result = Factory.LIST(val[1]); loc result, val[0], val[3] } - | LBRACK RBRACK { result = Factory.literal([]) ; loc result, val[0] } - | LISTSTART expressions RBRACK { result = Factory.LIST(val[1]); loc result, val[0], val[2] } - | LISTSTART expressions COMMA RBRACK { result = Factory.LIST(val[1]); loc result, val[0], val[3] } - | LISTSTART RBRACK { result = Factory.literal([]) ; loc result, val[0] } + : LISTSTART assignments endcomma RBRACK { result = Factory.LIST(val[1]); loc result, val[0], val[3] } + | LISTSTART RBRACK { result = Factory.literal([]) ; loc result, val[0] } + | LBRACK assignments endcomma RBRACK { result = Factory.LIST(val[1]); loc result, val[0], val[3] } + | LBRACK 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] } + : assignment FARROW assignment { result = Factory.KEY_ENTRY(val[0], val[2]); loc result, val[1] } quotedtext : string | dq_string | heredoc string : STRING { result = Factory.literal(val[0][:value]) ; loc result, val[0] } | WORD { result = Factory.literal(val[0][:value]) ; loc result, val[0] } dq_string : dqpre dqrval { result = Factory.string(val[0], *val[1]) ; loc result, val[0], val[1][-1] } dqpre : DQPRE { result = Factory.literal(val[0][:value]); loc result, val[0] } dqpost : DQPOST { result = Factory.literal(val[0][:value]); loc result, val[0] } dqmid : DQMID { result = Factory.literal(val[0][:value]); loc result, val[0] } dqrval : text_expression dqtail { result = [val[0]] + val[1] } -text_expression : expression { result = Factory.TEXT(val[0]) } +text_expression : assignment { result = Factory.TEXT(val[0]) } dqtail : dqpost { result = [val[0]] } | dqmid dqrval { result = [val[0]] + val[1] } heredoc : HEREDOC sublocated_text { result = Factory.HEREDOC(val[0][:value], val[1]); loc result, val[0] } sublocated_text : SUBLOCATE string { result = Factory.SUBLOCATE(val[0], val[1]); loc result, val[0] } | SUBLOCATE dq_string { result = Factory.SUBLOCATE(val[0], val[1]); loc result, val[0] } epp_expression : EPP_START epp_parameters_list optional_statements { result = Factory.EPP(val[1], val[2]); loc result, val[0] } optional_statements : | statements epp_parameters_list : =LOW{ result = nil } | PIPE PIPE { result = [] } | PIPE parameters endcomma PIPE { result = val[1] } epp_render_expression : RENDER_STRING { result = Factory.RENDER_STRING(val[0][:value]); loc result, val[0] } | RENDER_EXPR expression epp_end { result = Factory.RENDER_EXPR(val[1]); loc result, val[0], val[2] } | RENDER_EXPR LBRACE statements RBRACE epp_end { result = Factory.RENDER_EXPR(Factory.block_or_expression(*val[2])); loc result, val[0], val[4] } epp_end : EPP_END | EPP_END_TRIM -number : NUMBER { result = Factory.NUMBER(val[0][:value]) ; loc result, val[0] } -name : NAME { result = Factory.QNAME_OR_NUMBER(val[0][:value]) ; loc result, val[0] } type : CLASSREF { result = Factory.QREF(val[0][:value]) ; loc result, val[0] } -undef : UNDEF { result = Factory.literal(:undef); loc result, val[0] } -default : DEFAULT { result = Factory.literal(:default); loc result, val[0] } - - # Assumes lexer produces a Boolean value for booleans, or this will go wrong and produce a literal string - # with the text 'true'. - #TODO: could be changed to a specific boolean literal factory method to prevent this possible glitch. -boolean : BOOLEAN { result = Factory.literal(val[0][:value]) ; loc result, val[0] } regex : REGEX { result = Factory.literal(val[0][:value]); loc result, val[0] } #---MARKERS, SPECIAL TOKENS, SYNTACTIC SUGAR, etc. endcomma : # | COMMA { result = nil } endsemi : # | SEMIC keyword : AND | CASE | CLASS | DEFAULT | DEFINE | ELSE | ELSIF | IF | IN | INHERITS | NODE | OR | UNDEF | UNLESS | TYPE | ATTR | FUNCTION | PRIVATE 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 e9448e978..b5087d973 100644 --- a/lib/puppet/pops/parser/eparser.rb +++ b/lib/puppet/pops/parser/eparser.rb @@ -1,2809 +1,2653 @@ # # 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', 805) +module_eval(<<'...end egrammar.ra/module_eval...', 'egrammar.ra', 751) # Make emacs happy # Local Variables: # mode: ruby # End: ...end egrammar.ra/module_eval... ##### State transition tables begin ### clist = [ -'62,65,134,294,63,56,284,58,-248,85,-134,-246,241,143,-132,-244,-235', -'284,133,-247,324,-249,132,108,14,112,381,107,279,280,42,263,49,242,51', -'46,262,50,75,71,291,44,74,47,48,295,144,72,13,111,-248,73,-134,-246', -'11,12,-132,-244,-235,92,91,-247,76,-249,87,88,129,265,43,80,266,86,70', -'66,251,68,69,67,268,267,52,53,55,54,62,65,358,57,63,56,405,58,425,93', -'133,284,241,85,132,241,133,376,133,375,132,261,132,391,14,72,260,108', -'72,112,42,107,49,242,51,46,242,50,75,71,283,44,74,47,48,284,319,72,13', -'339,234,73,360,111,11,12,241,293,241,133,62,65,76,132,63,56,133,58,43', -'72,132,72,70,66,133,68,69,242,132,242,52,53,55,54,14,62,65,57,362,63', -'42,277,49,323,51,46,311,50,75,71,78,44,74,47,48,312,293,72,13,259,376', -'73,375,367,11,12,81,83,82,84,368,369,76,258,255,255,373,313,43,377,379', -'121,70,66,233,68,69,291,293,291,52,53,55,54,62,65,387,57,63,56,388,58', -'423,85,344,319,341,396,224,196,80,398,319,163,400,160,293,108,14,112', -'158,107,338,403,42,121,49,355,51,46,291,50,75,71,320,44,74,47,48,122', -'408,72,13,111,379,73,410,411,11,12,412,89,90,92,91,413,76,414,87,88', -'121,416,43,417,118,86,70,66,241,68,69,80,77,426,52,53,55,54,62,65,427', -'57,63,56,428,58,421,93,429,430,,,,,,,,,,,,,14,,,,,,42,,49,,51,46,,50', -'75,71,,44,74,47,48,,,72,13,,,73,,85,11,12,,,,,,,76,,,,,108,43,112,,107', -'70,66,,68,69,,,,52,53,55,54,62,65,,57,63,56,,58,328,111,,,,,,,,,,,,', -',,14,,,,,,42,86,49,,51,46,,50,75,71,,44,74,47,48,,,72,13,,,73,,,11,12', -',,,,62,65,76,,63,56,,58,43,,,,70,66,,68,69,,,,52,53,55,54,14,,,57,,', -'42,,49,,51,114,,50,75,71,,44,74,,,,,72,13,,,73,,,11,12,,,,,62,65,76', -',63,56,,58,43,,,,70,66,,68,69,,,,52,53,55,54,14,,,57,,,42,,49,,51,114', -',50,75,71,,44,74,,,,,72,13,,,73,,,11,12,,,,,62,65,76,,63,56,,58,43,', -',,70,66,,68,69,,,,52,53,55,54,14,,,57,,,42,,49,,51,114,,50,75,71,,44', -'74,,,,,72,13,,,73,,,11,12,,,,,62,65,76,,63,56,,58,43,,,,70,66,,68,69', -',,,52,53,55,54,14,,,57,,,42,,49,,51,114,,50,75,71,,44,74,,,,,72,13,', -',73,,85,11,12,,,,,,,76,,,,,108,43,112,,107,70,66,,68,69,,,,52,53,55', -'54,62,65,,57,63,56,,58,419,111,,,,,,,,,,,,,,,14,,,,,,42,86,49,,51,46', -',50,75,71,,44,74,47,48,,,72,13,,,73,,,11,12,,,,,62,65,76,,63,56,,58', -'43,,,,70,66,,68,69,,,,52,53,55,54,14,,,57,,,42,,49,,51,46,,50,75,71', -',44,74,47,48,,,72,13,,,73,,,11,12,,,,,62,65,76,,63,56,,58,43,,,,70,66', -',68,69,,,,52,53,55,54,14,,,57,,,42,,49,,51,114,,50,75,71,,44,74,,,,', -'72,13,,,73,,,11,12,,,,,62,65,76,,63,56,,58,43,,,,70,66,,68,69,,,,52', -'53,55,54,14,,,57,,,42,,49,,51,114,,50,75,71,,44,74,,,,,72,13,,,73,,', -'11,12,,,,,62,65,76,,63,56,,58,43,,,,70,66,,68,69,,,,52,53,55,54,14,', -',57,,,42,,49,,51,114,,50,75,71,,44,74,,,,,72,13,,,73,,,11,12,,,,,62', -'65,76,,63,56,,58,43,,,,70,66,,68,69,,,,52,53,55,54,14,,,57,,,42,,49', -',51,114,,50,75,71,,44,74,,,,,72,13,,,73,,,11,12,,,,,62,65,76,,63,56', -',58,43,,,,70,66,,68,69,,,,52,53,55,54,14,,,57,,,42,,49,,51,128,,50,75', -'71,,44,74,,,,,72,13,,,73,,,11,12,,,,,62,65,76,,63,56,,58,43,,,,70,66', -',68,69,,,,52,53,55,54,14,,,57,,,42,,49,,51,114,,50,75,71,,44,74,,,,', -'72,13,,,73,,,11,12,,,,,62,65,76,,63,56,,58,43,,,,70,66,,68,69,,,,52', -'53,55,54,14,,,57,,,198,219,209,220,51,210,222,211,207,205,,200,217,', -',,,72,13,223,218,216,,,11,12,,,,,,,76,,,,,221,199,,,,70,66,,68,69,,', -',212,213,215,214,62,65,,57,63,56,,58,,85,,,,,,,,,,,,,,108,14,112,,107', -',,42,,49,,51,114,,50,75,71,,44,74,,,,,72,13,111,,73,,,11,12,,89,90,92', -'91,,76,,87,88,,,43,,,86,70,66,,68,69,,,,52,53,55,54,62,65,,57,63,56', -',58,317,93,,,,85,,,,,,,,,,,14,,,108,,112,42,107,49,,51,46,,50,75,71', -',44,74,47,48,,,72,13,,,73,,111,11,12,,,,,62,65,76,,63,56,147,58,43,', -',,70,66,,68,69,,,,52,53,55,54,14,,,57,,,42,,49,,51,114,,50,75,71,,44', -'74,,,,,72,13,,,73,,,11,12,,,,,62,65,76,,63,56,149,58,43,,,,70,66,,68', -'69,,,,52,53,55,54,14,,,57,,,42,,49,,51,114,,50,75,71,,44,74,,,,,72,13', -',,73,,85,11,12,,,,,,,76,,,,,108,43,112,,107,70,66,,68,69,,,,52,53,55', -'54,62,65,,57,63,56,,58,152,111,,,,85,,,,,,,,,,,14,,,108,,112,42,107', -'49,,51,114,,50,75,71,,44,74,,,,,72,13,,,73,,111,11,12,,,,,62,65,76,', -'63,56,,58,43,,,,70,66,,68,69,,,,52,53,55,54,14,,,57,,,42,,49,,51,114', -',50,75,71,,44,74,,,,,72,13,,,73,,,11,12,,,,,62,65,76,,63,56,348,58,43', -',,,70,66,,68,69,,,,52,53,55,54,14,,,57,,,42,,49,,51,114,,50,75,71,,44', -'74,,,,,72,13,,,73,,,11,12,,,,,62,65,76,,63,56,,58,43,,,,70,66,,68,69', -',,,52,53,55,54,14,,,57,,,42,,49,,51,114,,50,75,71,,44,74,,,,,72,13,', -',73,,,11,12,,,,,62,65,76,,63,56,,162,43,,,,70,66,,68,69,,,,52,53,55', -'54,14,,,57,,,42,,49,,51,114,,50,75,71,,44,74,,,,,72,13,,,73,,,11,12', -',,,,62,65,76,,63,56,349,58,43,,,,70,66,,68,69,,,,52,53,55,54,14,,,57', -',,42,,49,,51,114,,50,75,71,,44,74,,,,,72,13,,,73,,,11,12,,,,,62,65,76', -',63,56,,58,43,,,,70,66,,68,69,,,,52,53,55,54,14,,,57,,,42,,49,,51,46', -',50,75,71,,44,74,47,48,,,72,13,,,73,,,11,12,,,,,62,65,76,,63,56,,58', -'43,,,,70,66,,68,69,,,,52,53,55,54,14,,,57,,,42,,49,,51,114,,50,75,71', -',44,74,,,,,72,13,,,73,,,11,12,,,,,62,65,76,,63,56,,58,43,,,,70,66,,68', -'69,,,,52,53,55,54,14,,,57,,,42,,49,,51,46,,50,75,71,,44,74,47,48,,,72', -'13,,,73,,,11,12,,,,,62,65,76,,63,56,,58,43,,,,70,66,,68,69,,,,52,53', -'55,54,14,,,57,,,42,,49,,51,46,,50,75,71,,44,74,47,48,,,72,13,,,73,,', -'11,12,,,,,62,65,76,,63,56,,58,43,,,,70,66,,68,69,,,,52,53,55,54,14,', -',57,,,42,,49,,51,46,,50,75,71,,44,74,47,48,,,72,13,,,73,,,11,12,,,,', -'62,65,76,,63,56,,58,43,,,,70,66,,68,69,,,,52,53,55,54,14,,,57,,,42,', -'49,,51,46,,50,75,71,,44,74,47,48,,,72,13,,,73,,,11,12,,,,,62,65,76,', -'63,56,,58,43,,,,70,66,,68,69,,,,52,53,55,54,14,,,57,,,42,,49,,51,46', -',50,75,71,,44,74,47,48,,,72,13,,,73,,,11,12,,,,,62,65,76,,63,56,,58', -'43,,,,70,66,,68,69,,,,52,53,55,54,14,,,57,,,42,,49,,51,114,,50,75,71', -',44,74,,,,,72,13,,,73,,,11,12,,,,,62,65,76,,63,56,,58,43,,,,70,66,,68', -'69,,,,52,53,55,54,14,,,57,,,42,,49,,51,114,,50,75,71,,44,74,,,,,72,13', -',,73,,,11,12,,,,,62,65,76,,63,56,,58,43,,,,70,66,,68,69,,,,52,53,55', -'54,14,,,57,,,42,,49,,51,114,,50,75,71,,44,74,,,,,72,13,,,73,,,11,12', -',,,,62,65,76,,63,56,,58,43,,,,70,66,,68,69,,,,52,53,55,54,14,,,57,,', -'42,,49,,51,114,,50,75,71,,44,74,,,,,72,13,,,73,,,11,12,,,,,62,65,76', -',63,56,,58,43,,,,70,66,,68,69,,,,52,53,55,54,14,,,57,,,42,,49,,51,114', -',50,75,71,,44,74,,,,,72,13,,,73,,,11,12,,,,,62,65,76,,63,56,,58,43,', -',,70,66,,68,69,,,,52,53,55,54,14,,,57,,,42,,49,,51,114,,50,75,71,,44', -'74,,,,,72,13,,,73,,,11,12,,,,,62,65,76,,63,56,,58,43,,,,70,66,,68,69', -',,,52,53,55,54,14,,,57,,,42,,49,,51,114,,50,75,71,,44,74,,,,,72,13,', -',73,,,11,12,,,,,62,65,76,,63,56,,58,43,,,,70,66,,68,69,,,,52,53,55,54', -'14,,,57,,,42,,49,,51,114,,50,75,71,,44,74,,,,,72,13,,,73,,,11,12,,,', -',62,65,76,,63,56,,58,43,,,,70,66,,68,69,,,,52,53,55,54,14,,,57,,,42', -',49,,51,114,,50,75,71,,44,74,,,,,72,13,,,73,,,11,12,,,,,62,65,76,,63', -'56,,58,43,,,,70,66,,68,69,,,,52,53,55,54,14,,,57,,,42,,49,,51,114,,50', -'75,71,,44,74,,,,,72,13,,,73,,,11,12,,,,,62,65,76,,63,56,,58,43,,,,70', -'66,,68,69,,,,52,53,55,54,14,,,57,,,42,,49,,51,114,,50,75,71,,44,74,', -',,,72,13,,,73,,,11,12,,,,,62,65,76,,63,56,,58,43,,,,70,66,,68,69,,,', -'52,53,55,54,14,,,57,,,42,,49,,51,114,,50,75,71,,44,74,,,,,72,13,,,73', -',,11,12,,,,,62,65,76,,63,56,,58,43,,,,70,66,,68,69,,,,52,53,55,54,14', -',,57,,,42,,49,,51,114,,50,75,71,,44,74,,,,,72,13,,,73,,,11,12,,,,,62', -'65,76,,63,56,,58,43,,,,70,66,,68,69,,,,52,53,55,54,14,,,57,,,42,,49', -',51,114,,50,75,71,,44,74,,,,,72,13,,,73,,,11,12,,,,,62,65,76,,63,56', -',58,43,,,,70,66,,68,69,,,,52,53,55,54,14,,,57,,,42,,49,,51,114,,50,75', -'71,,44,74,,,,,72,13,,,73,,,11,12,,,,,62,65,76,,63,56,,58,43,,,,70,66', -',68,69,,,,52,53,55,54,14,,,57,,,42,,49,,51,114,,50,75,71,,44,74,,,,', -'72,13,,,73,,,11,12,,,,,62,65,76,,63,56,,58,43,,,,70,66,,68,69,,,,52', -'53,55,54,14,,,57,,,42,,49,,51,114,,50,75,71,,44,74,,,,,72,13,,,73,,', -'11,12,,,,,62,65,76,,63,56,,58,43,,,,70,66,,68,69,,,,52,53,55,54,14,', -',57,,,42,,49,,51,114,,50,75,71,,44,74,,,,,72,13,,,73,,,11,12,,,,,62', -'65,76,,63,56,,58,43,,,,70,66,,68,69,,,,52,53,55,54,14,,,57,,,42,,49', -',51,114,,50,75,71,,44,74,,,,,72,13,,,73,,,11,12,,,,,62,65,76,,63,56', -',58,43,,,,70,66,,68,69,,,,52,53,55,54,14,,,57,,,42,,49,,51,114,,50,75', -'71,,44,74,,,,,72,13,,,73,,,11,12,,,,,62,65,76,,63,56,,58,43,,,,70,66', -',68,69,,,,52,53,55,54,14,,,57,,,42,,49,,51,114,,50,75,71,,44,74,,,,', -'72,13,,,73,,,11,12,,,,,62,65,76,,63,56,,58,43,,,,70,66,,68,69,,,,52', -'53,55,54,14,,,57,,,42,,49,,51,114,,50,75,71,,44,74,,,,,72,13,,,73,,', -'11,12,,,,,62,65,76,,63,56,,58,43,,,,70,66,,68,69,,,,52,53,55,54,14,', -',57,,,42,,49,,51,114,,50,75,71,,44,74,,,,,72,13,,,73,,,11,12,,,,,,,76', -',,,,,43,,,195,70,66,,68,69,,,,52,53,55,54,62,65,,57,63,56,,58,351,,', -',,,,,,,,,,,,,14,,,,,,42,,49,,51,114,,50,75,71,,44,74,,,,,72,13,,,73', -',,11,12,,,,,62,65,76,,63,56,,58,43,,,,70,66,,68,69,,,,52,53,55,54,14', -',,57,,,198,219,209,220,51,210,222,211,207,205,,200,217,,,,,72,13,223', -'218,216,,,11,12,,,,,,,76,,,,,221,199,,,,70,66,,68,69,,,,212,213,215', -'214,62,65,,57,63,56,,58,,,,,,,,,,,,,,,,,14,,,,,,42,,49,,51,114,,50,75', -'71,,44,74,,,,,72,13,,,73,,,11,12,,,,,62,65,76,,63,56,,58,43,,,,70,66', -',68,69,,,,52,53,55,54,14,,,57,,,42,,49,,51,114,,50,75,71,,44,74,,,,', -'72,13,,,73,,,11,12,,,,,62,65,76,,63,56,,58,43,,,,70,66,,68,69,,,,52', -'53,55,54,14,,,57,,,42,,49,,51,114,,50,75,71,,44,74,,,,,72,13,,,73,,', -'11,12,,,,,62,65,76,,63,56,,58,43,,,,70,66,,68,69,,,,52,53,55,54,14,', -',57,,,42,,49,,51,114,,50,75,71,,44,74,,,,,72,13,,,73,,,11,12,,,,,62', -'65,76,,63,56,,58,43,,,,70,66,,68,69,,,,52,53,55,54,14,,,57,,,42,,49', -',51,46,,50,75,71,,44,74,47,48,,,72,13,,,73,,,11,12,,,,,62,65,76,,63', -'56,,58,43,,,,70,66,,68,69,,,,52,53,55,54,14,,,57,,,42,,49,,51,114,,50', -'75,71,,44,74,,,,,72,13,,,73,,,11,12,,,,,62,65,76,,63,56,,58,43,,,,70', -'66,,68,69,,,,52,53,55,54,14,,,57,,,42,,49,,51,114,,50,75,71,,44,74,', -',,,72,13,,,73,,,11,12,,,,,62,65,76,,63,56,,58,43,,,,70,66,,68,69,,,', -'52,53,55,54,14,,,57,,,42,,49,,51,114,,50,75,71,,44,74,,,,,72,13,,,73', -',,11,12,,,,,62,65,76,,63,56,,58,43,,,,70,66,,68,69,,,,52,53,55,54,14', -'231,,57,,,42,,49,,51,114,,50,75,71,,44,74,,,,,72,13,,,73,,,11,12,,,', -',,,76,,,,,,43,,,,70,66,,68,69,,,,52,53,55,54,62,65,,57,63,56,,58,382', -',,,,,,,,,,,,,,,14,,,,,,42,,49,,51,114,,50,75,71,,44,74,,,,,72,13,,,73', -',,11,12,,,,,62,65,76,,63,56,,58,43,,,,70,66,,68,69,,,,52,53,55,54,14', -',,57,,,42,,49,,51,114,,50,75,71,,44,74,,,,,72,13,,,73,,,11,12,,,,,62', -'65,76,,63,56,,58,43,,,,70,66,,68,69,,,,52,53,55,54,14,246,,57,,,42,', -'49,,51,114,,50,75,71,,44,74,,,,,72,13,,,73,,,11,12,,,,,62,65,76,,63', -'56,,58,43,,,,70,66,,68,69,,,,52,53,55,54,14,,,57,,,42,,49,,51,114,,50', -'75,71,,44,74,,,,,72,13,,,73,,,11,12,,,,,62,65,76,,63,56,,58,43,,,,70', -'66,,68,69,,,,52,53,55,54,14,,,57,,,42,,49,,51,114,,50,75,71,,44,74,', -',,,72,13,,,73,,,11,12,,,,,,,76,,,,,,43,,,,70,66,,68,69,,,,52,53,55,54', -'62,65,,57,63,56,,58,152,,,,,,,,,,,,,,,,14,,,,,,42,,49,,51,46,,50,75', -'71,,44,74,47,48,,,72,13,,,73,,,11,12,,,,,62,65,76,,63,56,,58,43,,,,70', -'66,,68,69,,,,52,53,55,54,14,,,57,,,42,,49,,51,46,,50,75,71,,44,74,47', -'48,,,72,13,,,73,,,11,12,,,,,62,65,76,,63,56,,58,43,,,,70,66,,68,69,', -',,52,53,55,54,14,,,57,,,42,,49,,51,114,,50,75,71,,44,74,,,,,72,13,,', -'73,,,11,12,,,,,62,65,76,,63,56,,58,43,,,,70,66,,68,69,,,,52,53,55,54', -'14,,,57,,,42,,49,,51,114,,50,75,71,,44,74,,,,,72,13,,,73,,,11,12,,,', -',62,65,76,,63,56,,58,43,,,,70,66,,68,69,,,,52,53,55,54,14,,,57,,,42', -',49,,51,114,,50,75,71,,44,74,,,,,72,13,,,73,,,11,12,,,,,62,65,76,,63', -'56,,58,43,,,,70,66,,68,69,,,,52,53,55,54,14,,,57,,,42,,49,,51,114,,50', -'75,71,,44,74,,,,,72,13,,,73,,,11,12,,,,,62,65,76,,63,56,,58,43,,,,70', -'66,,68,69,,,,52,53,55,54,14,,,57,,,42,,49,,51,114,,50,75,71,,44,74,', -',,,72,13,,,73,,,11,12,,,,,62,65,76,,63,56,,58,43,,,,70,66,,68,69,,,', -'52,53,55,54,14,,,57,,,42,,49,,51,114,,50,75,71,,44,74,,,,,72,13,,,73', -',,11,12,,,,,,,76,,,,,,43,,,,70,66,,68,69,,,,52,53,55,54,62,65,,57,63', -'56,,58,326,85,,,,,,,,,,,,,,108,14,112,,107,,,42,,49,,51,46,,50,75,71', -',44,74,47,48,,,72,13,111,,73,,,11,12,,,,,,,76,,87,88,,,43,,,86,70,66', -',68,69,,,85,52,53,55,54,,,,57,104,105,106,101,96,108,,112,,107,,,97', -'99,98,100,,,,,,,,,,,,,,,,111,,,,103,102,,85,89,90,92,91,94,95,,87,88', -',,101,96,108,86,112,,107,,85,97,99,98,100,264,,,,104,105,106,101,96', -'108,,112,93,107,,111,97,99,98,100,102,,,89,90,92,91,94,95,,87,88,,,', -'111,,86,,103,102,,,89,90,92,91,94,95,,87,88,85,,109,,,86,,93,,104,105', -'106,101,96,108,,112,,107,,,97,99,98,100,,,93,,,,,,,,,,,,,111,,,,103', -'102,,,89,90,92,91,94,95,85,87,88,,,,,,86,104,105,106,101,96,108,,112', -',107,,,97,99,98,100,,,,,,93,,,,,,,,,,111,,,,103,102,,,89,90,92,91,94', -'95,85,87,88,,,,,,86,104,105,106,101,96,108,,112,,107,,,97,99,98,100', -',,,,,93,,,,,,,,,,111,,,,103,102,,,89,90,92,91,94,95,85,87,88,,,,,,86', -'104,105,106,101,96,108,,112,,107,,,97,99,98,100,,,,,,93,,,,,,,,,,111', -',,,103,102,,,89,90,92,91,94,95,,87,88,85,,250,,,86,,,,104,105,106,101', -'96,108,,112,,107,,,97,99,98,100,,,93,,,,,,,,,,,,,111,,,,103,102,,,89', -'90,92,91,94,95,85,87,88,,,,,,86,104,105,106,101,96,108,,112,,107,,,97', -'99,98,100,,,,,,93,,,,,,,,,,111,,,,103,102,,,89,90,92,91,94,95,85,87', -'88,,,,,,86,104,105,106,101,96,108,,112,,107,,,97,99,98,100,,,,,,93,', -',,,,,,,,111,,,,103,102,,85,89,90,92,91,94,95,,87,88,,,,,108,86,112,', -'107,,85,,249,,,,279,280,,104,105,106,101,96,108,,112,93,107,,111,97', -'99,98,100,,,,89,90,92,91,94,95,,87,88,,,,111,,86,,103,102,,,89,90,92', -'91,94,95,,87,88,85,,248,,,86,,93,,104,105,106,101,96,108,,112,,107,', -',97,99,98,100,,,93,,,,,,,,,,,,,111,,,,103,102,,,89,90,92,91,94,95,,87', -'88,85,,247,,,86,,,,104,105,106,101,96,108,,112,,107,,,97,99,98,100,', -',93,,,,,,,,,,,,,111,,,,103,102,,,89,90,92,91,94,95,85,87,88,,,,,,86', -'104,105,106,101,96,108,,112,,107,,,97,99,98,100,,,,,,93,,,,,,,,,,111', -',,,103,102,,,89,90,92,91,94,95,85,87,88,,,,,,86,104,105,106,101,96,108', -',112,,107,,,97,99,98,100,,,,,,93,,,,,,,,,,111,,,,103,102,,,89,90,92', -'91,94,95,85,87,88,,,,,,86,104,105,106,101,96,108,,112,,107,,,97,99,98', -'100,,,,,,93,,,,,,,,,,111,,,,103,102,,,89,90,92,91,94,95,85,87,88,,,285', -',,86,104,105,106,101,96,108,,112,,107,,,97,99,98,100,,,,,,93,,,,,,,', -',,111,,,,103,102,,,89,90,92,91,94,95,85,87,88,,,,,,86,104,105,106,101', -'96,108,,112,,107,,,97,99,98,100,,,,,,93,,,,,,,,,,111,,,,103,102,,,89', -'90,92,91,94,95,,87,88,,85,,109,,86,264,,,,104,105,106,101,96,108,,112', -',107,,,97,99,98,100,,93,,,,,,,,,,,,,,111,,,,103,102,,,89,90,92,91,94', -'95,85,87,88,,,,,,86,104,105,106,101,96,108,289,112,85,107,,,97,99,98', -'100,,,,,,93,108,,112,,107,,,,,111,,,,103,102,,,89,90,92,91,94,95,,87', -'88,111,,,,,86,,85,89,90,92,91,94,95,,87,88,,,,96,108,86,112,85,107,', -'93,97,,,,,,,,,96,108,,112,,107,,93,97,,111,,,,,,,,89,90,92,91,94,95', -',87,88,111,,,,,86,,85,89,90,92,91,94,95,,87,88,,,,96,108,86,112,85,107', -',93,97,,,,,,,,,96,108,,112,,107,,93,97,,111,,,,,,,,89,90,92,91,94,95', -',87,88,111,,,,,86,,85,89,90,92,91,94,95,,87,88,,,101,96,108,86,112,', -'107,,93,97,99,98,100,,,,,,,,,,,,,93,,,111,,,,,,,,89,90,92,91,94,95,85', -'87,88,,,,,,86,104,105,106,101,96,108,,112,,107,,229,97,99,98,100,,,', -',,93,,,,,,,,,,111,,,,103,102,,,89,90,92,91,94,95,85,87,88,,,,,,86,104', -'105,106,101,96,108,,112,,107,,,97,99,98,100,,,,,,93,,,,,,,,,,111,,,', -'103,102,,,89,90,92,91,94,95,85,87,88,,,,,,86,104,105,106,101,96,108', -',112,,107,,,97,99,98,100,,,,,,93,,,,,,,,,,111,,,,103,102,,,89,90,92', -'91,94,95,85,87,88,,,,,,86,104,105,106,101,96,108,,112,85,107,,,97,99', -'98,100,,,,,,93,108,,112,,107,,,62,65,111,,63,,103,102,,,89,90,92,91', -'94,95,,87,88,111,,,62,65,86,,63,62,65,92,91,63,,,87,88,62,65,,143,63', -'86,140,,,,93,,,,,,,,,,,,,,,143,76,93,140,85,143,,,140,,,144,66,85,143', -',,140,108,,112,,107,76,,,,108,76,112,,107,,144,66,,,76,144,66,,,,,111', -',,144,66,,,,,111,,,,,,87,88,,,,,,86,,87,88,,303,219,302,220,86,300,222', -'304,298,297,,299,301,,,,,,,223,218,305,303,219,302,220,,300,222,304', -'298,297,,299,301,,,221,306,,,223,218,305,,,,,,,309,310,308,307,,,,,', -'221,306,,,,,,,,,,,,309,310,308,307,303,219,302,220,,300,222,304,298', -'297,,299,301,,,,,,,223,218,305,,,,,,,,,,,,,,,,221,306,,,,,,,,,,,,309', -'310,308,307' ] - racc_action_table = arr = ::Array.new(7093, nil) +'58,61,388,275,59,53,317,54,-236,80,-238,-237,236,133,-130,-234,-239', +'-225,256,255,318,392,278,101,18,104,278,99,100,333,42,373,45,237,47', +'12,111,46,36,39,110,44,37,10,11,276,134,66,17,103,-236,38,-238,-237', +'15,16,-130,-234,-239,-225,58,61,67,334,59,53,236,54,43,277,79,81,35', +'62,278,64,65,63,107,66,48,49,51,50,18,111,52,237,79,110,42,236,45,79', +'47,113,236,46,36,39,252,44,37,253,66,312,111,66,17,66,110,38,237,254', +'15,16,369,237,368,111,58,61,67,110,59,53,229,54,43,340,111,265,35,62', +'110,64,65,359,267,268,48,49,51,50,18,111,52,307,71,110,42,369,45,368', +'47,12,236,46,36,39,69,44,37,10,11,342,273,66,17,66,329,38,58,61,15,16', +'59,237,254,326,58,61,67,249,59,53,249,54,43,72,73,74,35,62,350,64,65', +'351,273,274,48,49,51,50,18,353,52,248,247,356,42,316,45,312,47,12,361', +'46,36,39,362,44,37,10,11,236,225,66,17,228,226,38,366,313,15,16,370', +'372,75,77,76,78,67,312,249,225,379,79,43,381,299,273,35,62,79,64,65', +'215,214,71,48,49,51,50,58,61,52,153,59,53,385,54,310,150,119,79,273', +'148,391,306,119,302,120,395,372,397,398,399,18,58,61,119,402,59,42,403', +'45,404,47,12,300,46,36,39,79,44,37,10,11,71,412,66,17,68,414,38,415', +'416,15,16,302,,,,,,67,,133,,,130,43,,,,35,62,,64,65,,,,48,49,51,50,58', +'61,52,67,59,53,,54,408,80,,,,134,62,,,,,,,,,101,18,104,,99,100,,42,', +'45,,47,12,,46,36,39,,44,37,10,11,,,66,17,103,,38,,,15,16,,,,,58,61,67', +',59,53,,54,43,,,81,35,62,,64,65,,,,48,49,51,50,18,,52,,,,42,,45,,47', +'113,,46,36,39,,44,37,,,,,66,17,,,38,,,15,16,,,,,58,61,67,,59,53,,54', +'43,,,,35,62,,64,65,,,,48,49,51,50,18,,52,,,,42,,45,,47,12,,46,36,39', +',44,37,10,11,,,66,17,,,38,,,15,16,,,,,58,61,67,,59,53,,54,43,,,,35,62', +',64,65,,,,48,49,51,50,18,,52,,,,42,,45,,47,12,,46,36,39,,44,37,10,11', +',,66,17,,,38,,,15,16,,,,,,,67,,,,,,43,,,,35,62,,64,65,,,,48,49,51,50', +'58,61,52,,59,53,,54,406,80,,,,,,,,,,,,,,101,18,104,,99,100,,42,,45,', +'47,12,,46,36,39,,44,37,10,11,,,66,17,103,,38,,,15,16,,,,,58,61,67,,59', +'53,,54,43,,,,35,62,,64,65,,,,48,49,51,50,18,,52,,,,42,,45,,47,113,,46', +'36,39,,44,37,,,,,66,17,,,38,,,15,16,,,,,58,61,67,,59,53,,54,43,,,,35', +'62,,64,65,,,,48,49,51,50,18,,52,,,,42,,45,,47,113,,46,36,39,,44,37,', +',,,66,17,,,38,,,15,16,,,,,58,61,67,,59,53,,54,43,,,,35,62,,64,65,,,', +'48,49,51,50,18,,52,,,,42,,45,,47,113,,46,36,39,,44,37,,,,,66,17,,,38', +',,15,16,,,,,58,61,67,,59,53,,54,43,,,,35,62,,64,65,,,,48,49,51,50,18', +',52,,,,42,,45,,47,12,,46,36,39,,44,37,10,11,,,66,17,,,38,,,15,16,,,', +',,,67,,,,,,43,,,,35,62,,64,65,,,,48,49,51,50,58,61,52,,59,53,,54,401', +'80,,,,,,,,,,,,,,101,18,104,,99,100,,42,,45,,47,12,,46,36,39,,44,37,10', +'11,,,66,17,103,,38,,,15,16,,,,,58,61,67,,59,53,,54,43,,,,35,62,,64,65', +',,,48,49,51,50,18,,52,,,,42,,45,,47,113,,46,36,39,,44,37,,,,,66,17,', +',38,,,15,16,,,,,58,61,67,,59,53,,54,43,,,,35,62,,64,65,,,,48,49,51,50', +'18,,52,,,,42,,45,,47,113,,46,36,39,,44,37,,,,,66,17,,,38,,,15,16,,,', +',58,61,67,,59,53,,54,43,,,,35,62,,64,65,,,,48,49,51,50,18,,52,,,,42', +',45,,47,113,,46,36,39,,44,37,,,,,66,17,,,38,,,15,16,,,,,58,61,67,,59', +'53,,54,43,,,,35,62,,64,65,,,,48,49,51,50,18,,52,,,,42,,45,,47,113,,46', +'36,39,,44,37,,,,,66,17,,,38,,,15,16,,,,,,,67,,,,,,43,,,,35,62,,64,65', +',,,48,49,51,50,58,61,52,,59,53,,54,320,,,,,,,,,,,,,,,,18,58,61,,,59', +'42,,45,,47,12,,46,36,39,,44,37,10,11,,,66,17,,,38,,,15,16,,,,,,,67,', +'133,,,130,43,,,,35,62,,64,65,,,,48,49,51,50,58,61,52,67,59,53,,54,322', +'80,,,,134,62,,,,,,,,,101,18,104,,99,100,,42,,45,,47,12,,46,36,39,,44', +'37,10,11,,,66,17,103,,38,,,15,16,,,,,58,61,67,,59,53,137,54,43,,,,35', +'62,,64,65,,,,48,49,51,50,18,,52,,,,42,,45,,47,12,,46,36,39,,44,37,10', +'11,,,66,17,,,38,,,15,16,,,,,58,61,67,,59,53,139,54,43,,,,35,62,,64,65', +',,,48,49,51,50,18,,52,,,,42,,45,,47,12,,46,36,39,,44,37,10,11,,,66,17', +',,38,,,15,16,,,,,,,67,,,,,,43,,,,35,62,,64,65,,,,48,49,51,50,58,61,52', +',59,53,,54,141,80,,,,,,,,,,,,,,101,18,104,,99,100,,42,,45,,47,12,,46', +'36,39,,44,37,10,11,,,66,17,103,,38,,,15,16,,,,,58,61,67,,59,53,,54,43', +',,,35,62,,64,65,,,,48,49,51,50,18,,52,,,,42,,45,,47,12,,46,36,39,,44', +'37,10,11,,,66,17,,,38,,,15,16,,,,,58,61,67,,59,53,,54,43,,,,35,62,,64', +'65,,,,48,49,51,50,18,,52,,,,42,,45,,47,12,,46,36,39,,44,37,10,11,,,66', +'17,,,38,,,15,16,,,,,58,61,67,,59,53,,54,43,,,,35,62,,64,65,,,,48,49', +'51,50,18,,52,,,,42,,45,,47,113,,46,36,39,,44,37,,,,,66,17,,,38,,,15', +'16,,,,,58,61,67,,59,53,,152,43,,,,35,62,,64,65,,,,48,49,51,50,18,,52', +',,,42,,45,,47,113,,46,36,39,,44,37,,,,,66,17,,,38,,,15,16,,,,,58,61', +'67,,59,53,,54,43,,,,35,62,,64,65,,,,48,49,51,50,18,,52,,,,42,,45,,47', +'113,,46,36,39,,44,37,,,,,66,17,,,38,,,15,16,,,,,58,61,67,,59,53,,54', +'43,,,,35,62,,64,65,,,,48,49,51,50,18,,52,,,,42,,45,,47,12,,46,36,39', +',44,37,10,11,,,66,17,,,38,,,15,16,,,,,58,61,67,,59,53,,54,43,,,,35,62', +',64,65,,,,48,49,51,50,18,,52,,,,42,,45,,47,113,,46,36,39,,44,37,,,,', +'66,17,,,38,,,15,16,,,,,58,61,67,,59,53,,54,43,,,,35,62,,64,65,,,,48', +'49,51,50,18,,52,,,,42,,45,,47,12,,46,36,39,,44,37,10,11,,,66,17,,,38', +',,15,16,,,,,58,61,67,,59,53,,54,43,,,,35,62,,64,65,,,,48,49,51,50,18', +',52,,,,42,,45,,47,12,,46,36,39,,44,37,10,11,,,66,17,,,38,,,15,16,,,', +',58,61,67,,59,53,,54,43,,,,35,62,,64,65,,,,48,49,51,50,18,,52,,,,42', +',45,,47,12,,46,36,39,,44,37,10,11,,,66,17,,,38,,,15,16,,,,,58,61,67', +',59,53,,54,43,,,,35,62,,64,65,,,,48,49,51,50,18,,52,,,,42,,45,,47,12', +',46,36,39,,44,37,10,11,,,66,17,,,38,,,15,16,,,,,58,61,67,,59,53,,54', +'43,,,,35,62,,64,65,,,,48,49,51,50,18,,52,,,,42,,45,,47,12,,46,36,39', +',44,37,10,11,,,66,17,,,38,,,15,16,,,,,58,61,67,,59,53,,54,43,,,,35,62', +',64,65,,,,48,49,51,50,18,,52,,,,42,,45,,47,12,,46,36,39,,44,37,10,11', +',,66,17,,,38,,,15,16,,,,,58,61,67,,59,53,,54,43,,,,35,62,,64,65,,,,48', +'49,51,50,18,,52,,,,42,,45,,47,12,,46,36,39,,44,37,10,11,,,66,17,,,38', +',,15,16,,,,,58,61,67,,59,53,,54,43,,,,35,62,,64,65,,,,48,49,51,50,18', +',52,,,,42,,45,,47,12,,46,36,39,,44,37,10,11,,,66,17,,,38,,,15,16,,,', +',58,61,67,,59,53,,54,43,,,,35,62,,64,65,,,,48,49,51,50,18,,52,,,,169', +'183,175,184,47,176,186,177,36,168,,171,166,,,,,66,17,187,182,167,,,15', +'165,,,,,,,67,,,,,185,170,,,,35,62,,64,65,,,,178,179,181,180,58,61,52', +',59,53,,54,,,,,,,,,,,,,,,,,18,,,,,,42,,45,,47,113,,46,36,39,,44,37,', +',,,66,17,,,38,,,15,16,,,,,58,61,67,,59,53,,54,43,,,,35,62,,64,65,,,', +'48,49,51,50,18,,52,,,,42,,45,,47,113,,46,36,39,,44,37,,,,,66,17,,,38', +',,15,16,,,,,58,61,67,,59,53,,54,43,,,,35,62,,64,65,,,,48,49,51,50,18', +',52,,,,42,,45,,47,113,,46,36,39,,44,37,,,,,66,17,,,38,,,15,16,,,,,58', +'61,67,,59,53,,54,43,,,,35,62,,64,65,,,,48,49,51,50,18,,52,,,,42,,45', +',47,113,,46,36,39,,44,37,,,,,66,17,,,38,,,15,16,,,,,58,61,67,,59,53', +',54,43,,,,35,62,,64,65,,,,48,49,51,50,18,,52,,,,42,,45,,47,113,,46,36', +'39,,44,37,,,,,66,17,,,38,,,15,16,,,,,58,61,67,,59,53,,54,43,,,,35,62', +',64,65,,,,48,49,51,50,18,,52,,,,42,,45,,47,113,,46,36,39,,44,37,,,,', +'66,17,,,38,,,15,16,,,,,58,61,67,,59,53,,54,43,,,,35,62,,64,65,,,,48', +'49,51,50,18,,52,,,,42,,45,,47,113,,46,36,39,,44,37,,,,,66,17,,,38,,', +'15,16,,,,,58,61,67,,59,53,,54,43,,,,35,62,,64,65,,,,48,49,51,50,18,', +'52,,,,42,,45,,47,113,,46,36,39,,44,37,,,,,66,17,,,38,,,15,16,,,,,58', +'61,67,,59,53,,54,43,,,,35,62,,64,65,,,,48,49,51,50,18,,52,,,,42,,45', +',47,113,,46,36,39,,44,37,,,,,66,17,,,38,,,15,16,,,,,58,61,67,,59,53', +',54,43,,,,35,62,,64,65,,,,48,49,51,50,18,,52,,,,42,,45,,47,113,,46,36', +'39,,44,37,,,,,66,17,,,38,,,15,16,,,,,58,61,67,,59,53,,54,43,,,,35,62', +',64,65,,,,48,49,51,50,18,,52,,,,42,,45,,47,113,,46,36,39,,44,37,,,,', +'66,17,,,38,,,15,16,,,,,58,61,67,,59,53,,54,43,,,,35,62,,64,65,,,,48', +'49,51,50,18,,52,,,,42,,45,,47,113,,46,36,39,,44,37,,,,,66,17,,,38,,', +'15,16,,,,,58,61,67,,59,53,,54,43,,,,35,62,,64,65,,,,48,49,51,50,18,', +'52,,,,42,,45,,47,113,,46,36,39,,44,37,,,,,66,17,,,38,,,15,16,,,,,58', +'61,67,,59,53,,54,43,,,,35,62,,64,65,,,,48,49,51,50,18,,52,,,,42,,45', +',47,113,,46,36,39,,44,37,,,,,66,17,,,38,,,15,16,,,,,58,61,67,,59,53', +',54,43,,,,35,62,,64,65,,,,48,49,51,50,18,,52,,,,42,,45,,47,113,,46,36', +'39,,44,37,,,,,66,17,,,38,,,15,16,,,,,58,61,67,,59,53,,54,43,,,,35,62', +',64,65,,,,48,49,51,50,18,,52,,,,42,,45,,47,113,,46,36,39,,44,37,,,,', +'66,17,,,38,,,15,16,,,,,58,61,67,,59,53,,54,43,,,,35,62,,64,65,,,,48', +'49,51,50,18,,52,,,,42,,45,,47,113,,46,36,39,,44,37,,,,,66,17,,,38,,', +'15,16,,,,,58,61,67,,59,53,,54,43,,,,35,62,,64,65,,,,48,49,51,50,18,', +'52,,,,42,,45,,47,113,,46,36,39,,44,37,,,,,66,17,,,38,,,15,16,,,,,58', +'61,67,,59,53,,54,43,,,,35,62,,64,65,,,,48,49,51,50,18,,52,,,,42,,45', +',47,113,,46,36,39,,44,37,,,,,66,17,,,38,,,15,16,,,,,58,61,67,,59,53', +',54,43,,,,35,62,,64,65,,,,48,49,51,50,18,,52,,,,42,,45,,47,113,,46,36', +'39,,44,37,,,,,66,17,,,38,,,15,16,,,,,58,61,67,,59,53,,54,43,,,211,35', +'62,,64,65,,,,48,49,51,50,18,213,52,,,,42,,45,,47,12,,46,36,39,,44,37', +'10,11,,,66,17,,,38,,,15,16,,,,,58,61,67,,59,53,,54,43,,,,35,62,,64,65', +',,,48,49,51,50,18,,52,,,,42,,45,,47,113,,46,36,39,,44,37,,,,,66,17,', +',38,,,15,16,,,,,58,61,67,,59,53,,54,43,,,,35,62,,64,65,,,,48,49,51,50', +'18,,52,,,,42,,45,,47,113,,46,36,39,,44,37,,,,,66,17,,,38,,,15,16,,,', +',58,61,67,,59,53,,54,43,,,,35,62,,64,65,,,,48,49,51,50,18,,52,,,,42', +',45,,47,113,,46,36,39,,44,37,,,,,66,17,,,38,,,15,16,,,,,58,61,67,,59', +'53,,54,43,,,,35,62,,64,65,,,,48,49,51,50,18,,52,,,,42,,45,,47,113,,46', +'36,39,,44,37,,,,,66,17,,,38,,,15,16,,,,,58,61,67,,59,53,,54,43,,274', +',35,62,,64,65,,,,48,49,51,50,18,,52,,,,42,,45,,47,113,,46,36,39,,44', +'37,,,,,66,17,,,38,,,15,16,,,,,58,61,67,,59,53,,54,43,,,,35,62,,64,65', +',,,48,49,51,50,18,,52,,,,42,,45,,47,12,,46,36,39,,44,37,10,11,,,66,17', +',,38,,,15,16,,,,,58,61,67,,59,53,,54,43,,,,35,62,,64,65,,,,48,49,51', +'50,18,,52,,,,42,,45,,47,113,,46,36,39,,44,37,,,,,66,17,,,38,,,15,16', +',,,,58,61,67,,59,53,,54,43,,,,35,62,,64,65,,,,48,49,51,50,18,,52,,,', +'42,,45,,47,12,,46,36,39,,44,37,10,11,,,66,17,,,38,,,15,16,,,,,,,67,', +',,,,43,,,,35,62,,64,65,,,,48,49,51,50,58,61,52,,59,53,,54,335,,,,,,', +',,,,,,,,,18,58,61,,,59,42,,45,,47,12,,46,36,39,,44,37,10,11,,,66,17', +',,38,,,15,16,,,,,,,67,,133,,,130,43,,,,35,62,,64,65,,,,48,49,51,50,58', +'61,52,67,59,53,,54,374,,,,,134,62,,,,,,,,,,18,,,,,,42,,45,,47,113,,46', +'36,39,,44,37,,,,,66,17,,,38,,,15,16,,,,,58,61,67,,59,53,,54,43,,,,35', +'62,,64,65,,,,48,49,51,50,18,,52,,,,42,,45,,47,12,,46,36,39,,44,37,10', +'11,,,66,17,,,38,,,15,16,,,,,58,61,67,,59,53,,54,43,,,,35,62,,64,65,', +',,48,49,51,50,18,,52,,,,42,,45,,47,12,,46,36,39,,44,37,10,11,,,66,17', +',,38,,,15,16,,,,,58,61,67,,59,53,,54,43,,,,35,62,,64,65,,,,48,49,51', +'50,18,,52,,,,42,,45,,47,12,,46,36,39,,44,37,10,11,,,66,17,,,38,,,15', +'16,,,,,58,61,67,,59,53,,54,43,,,,35,62,,64,65,,,,48,49,51,50,18,,52', +',,,42,,45,,47,113,,46,36,39,,44,37,,,,,66,17,,,38,,,15,16,,,,,,,67,', +',,,,43,,,,35,62,,64,65,,,,48,49,51,50,58,61,52,,59,53,,54,141,,,,,,', +',,,,,,,,,18,,,,,,42,,45,,47,12,,46,36,39,,44,37,10,11,,,66,17,,,38,', +',15,16,,,,,58,61,67,,59,53,,54,43,,,,35,62,,64,65,,,,48,49,51,50,18', +'241,52,,,,42,,45,,47,12,,46,36,39,,44,37,10,11,,,66,17,,,38,,,15,16', +',,,,58,61,67,,59,53,,54,43,,,,35,62,,64,65,,,,48,49,51,50,18,,52,,,', +'42,,45,,47,12,,46,36,39,,44,37,10,11,,,66,17,,,38,,,15,16,,,,,58,61', +'67,,59,53,,54,43,,,,35,62,,64,65,,,,48,49,51,50,18,,52,,,,42,,45,,47', +'113,,46,36,39,,44,37,,,,,66,17,,,38,,,15,16,,,,,58,61,67,,59,53,,54', +'43,,,,35,62,,64,65,,,,48,49,51,50,18,,52,,,,42,,45,,47,113,,46,36,39', +',44,37,,,,,66,17,,,38,,,15,16,,,,,58,61,67,,59,53,,54,43,,,,35,62,,64', +'65,,,,48,49,51,50,18,,52,,,,42,,45,,47,113,,46,36,39,,44,37,,,,,66,17', +',,38,,,15,16,,,,,58,61,67,,59,53,,54,43,,,,35,62,,64,65,,,,48,49,51', +'50,18,,52,,,,42,,45,,47,113,,46,36,39,,44,37,,,,,66,17,,,38,,,15,16', +',,,,58,61,67,,59,53,,54,43,,,,35,62,,64,65,,,,48,49,51,50,18,,52,,,', +'42,,45,,47,113,,46,36,39,,44,37,,,,,66,17,,,38,,,15,16,,,,,58,61,67', +',59,53,,54,43,,,,35,62,,64,65,,,,48,49,51,50,18,,52,,,,42,,45,,47,113', +',46,36,39,,44,37,,,,,66,17,,,38,,,15,16,,,,,58,61,67,,59,53,,54,43,', +',,35,62,,64,65,,,,48,49,51,50,18,,52,,,,42,,45,,47,113,,46,36,39,,44', +'37,,,,,66,17,,,38,,,15,16,,,,,,,67,,,,,,43,,,,35,62,,64,65,80,,,48,49', +'51,50,,,52,,,96,91,101,,104,,99,100,,92,94,93,95,,58,61,,,59,,,,,,,', +',,103,,,,98,97,,,84,85,87,86,89,90,,82,83,80,,244,,,81,,,133,,,130,96', +'91,101,,104,,99,100,,92,94,93,95,,88,,,,,67,,,,,,,,,103,134,62,,98,97', +',,84,85,87,86,89,90,,82,83,80,,243,,,81,,,,,,,96,91,101,,104,80,99,100', +',92,94,93,95,,88,,,,,101,,104,,99,100,,,,103,,,,98,97,,,84,85,87,86', +'89,90,,82,83,103,,,,,81,,,,,87,86,,,,82,83,80,,242,,,81,,,,88,,,96,91', +'101,,104,80,99,100,,92,94,93,95,,88,,,,,101,,104,,99,100,,,,103,,,,98', +'97,,80,84,85,87,86,89,90,,82,83,103,,96,91,101,81,104,,99,100,,92,94', +'93,95,82,83,,,,,,81,,,,88,,,,103,,,,98,97,,80,84,85,87,86,89,90,,82', +'83,,,96,91,101,81,104,,99,100,,92,94,93,95,,,,,,,,,,,,88,,,,103,,,,98', +'97,,,84,85,87,86,89,90,,82,83,,,,,,81,80,,,,,,,,,,267,268,96,91,101', +'303,104,80,99,100,88,92,94,93,95,,,,,,,101,,104,,99,100,,,,103,,,,98', +'97,,,84,85,87,86,89,90,,82,83,103,,,,,81,,,84,85,87,86,,,,82,83,80,', +',,,81,,,,88,,,96,91,101,,104,,99,100,,92,94,93,95,,88,,,,,,,,,,,,,,103', +',,,98,97,,,84,85,87,86,89,90,80,82,83,,,279,,,81,,,,96,91,101,,104,80', +'99,100,,92,94,93,95,,,,,88,,101,,104,,99,100,,,,103,,,,98,97,,80,84', +'85,87,86,89,90,,82,83,103,,96,91,101,81,104,,99,100,,92,94,93,95,82', +'83,,,,,,81,,,,88,,,,103,,,,,97,,80,84,85,87,86,89,90,,82,83,,,96,91', +'101,81,104,,99,100,,92,94,93,95,,,,,,,,,,,,88,,,,103,,,,98,97,,,84,85', +'87,86,89,90,80,82,83,,,,,,81,,,,96,91,101,271,104,80,99,100,,92,94,93', +'95,,,,,88,,101,,104,,99,100,,,,103,,,,98,97,,80,84,85,87,86,89,90,,82', +'83,103,,96,91,101,81,104,,99,100,,92,94,93,95,82,83,,,,,,81,,,,88,,', +',103,,,,98,97,,80,84,85,87,86,89,90,,82,83,,,96,91,101,81,104,,99,100', +',92,94,93,95,,,,,,,,,,,,88,,,,103,,,,98,97,,80,84,85,87,86,89,90,,82', +'83,,,96,91,101,81,104,,99,100,,92,94,93,95,80,,,,,,,,,,,88,,91,101,103', +'104,,99,100,80,92,,84,85,87,86,89,90,,82,83,,,101,,104,81,99,100,103', +',,,,,,,84,85,87,86,89,90,,82,83,,88,,103,,81,,,,,,84,85,87,86,80,,,82', +'83,,,,,,81,88,96,91,101,,104,,99,100,80,92,94,93,95,,,,,,,88,,,101,', +'104,,99,100,103,,,,98,97,,,84,85,87,86,89,90,,82,83,,,,103,,81,,,,,', +',,87,86,80,,,82,83,,,,,,81,88,96,91,101,,104,,99,100,80,92,94,93,95', +',,,,,,88,,91,101,,104,,99,100,103,92,,,98,97,,,84,85,87,86,89,90,,82', +'83,,,,103,,81,,,,,,84,85,87,86,89,90,80,82,83,,,,,,81,88,,,96,91,101', +',104,80,99,100,,92,94,93,95,,,,,88,,101,,104,,99,100,,,,103,,,,98,97', +',,84,85,87,86,89,90,,82,83,103,,,,,81,,,84,85,87,86,89,90,80,82,83,', +',,,,81,,,,88,,101,,104,80,99,100,,,,,,,,,,88,91,101,,104,,99,100,,92', +',103,,,,,,,,84,85,87,86,89,90,,82,83,103,,,,,81,,,84,85,87,86,89,90', +'80,82,83,,,,,,81,,,,88,91,101,,104,,99,100,,92,,,,,,,,88,,,,,,,,,,,103', +',,,,,,,84,85,87,86,89,90,,82,83,,,,,,81,,,291,183,290,184,,288,186,292', +',286,,287,289,,,,,,88,187,182,293,,,,285,,,,,,,,,,,,185,294,,,,,,,,', +',,,297,298,296,295,291,183,290,184,,288,186,292,,286,,287,289,,,,,,', +'187,182,293,291,183,290,184,,288,186,292,,286,,287,289,,,185,294,,,187', +'182,293,,,,285,,,297,298,296,295,,,,,,185,294,,,,,,,,,,,,297,298,296', +'295,291,183,290,184,,288,186,292,,286,,287,289,,,,,,,187,182,293,,,', +'285,,,,,,,,,,,,185,294,,,,,,,,,,,,297,298,296,295' ] + racc_action_table = arr = ::Array.new(6777, 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,47,208,0,0,245,0,212,174,207,214,243,259,205,216,217,329,114,215', -'245,213,114,174,0,174,329,174,355,355,0,148,0,243,0,0,148,0,0,0,201', -'0,0,0,0,208,259,0,0,174,212,0,207,214,0,0,205,216,217,174,174,215,0', -'213,174,174,46,151,0,164,151,174,0,0,128,0,0,0,156,156,0,0,0,0,411,411', -'286,0,411,411,372,411,411,174,46,372,319,171,46,121,338,326,128,326', -'338,146,128,338,411,319,146,171,121,171,411,171,411,319,411,411,121', -'411,411,411,170,411,411,411,411,170,276,411,411,255,121,411,290,171', -'411,411,160,202,255,49,4,4,411,49,4,4,209,4,411,160,209,255,411,411', -'210,411,411,160,210,255,411,411,411,411,4,158,158,411,292,158,4,160', -'4,244,4,4,226,4,4,4,4,4,4,4,4,228,296,4,4,138,373,4,373,314,4,4,7,7', -'7,7,316,318,4,136,135,130,325,230,4,327,328,231,4,4,120,4,4,332,333', -'334,4,4,4,4,410,410,335,4,410,410,336,410,410,179,257,340,256,342,110', -'108,79,354,235,77,357,67,359,179,410,179,66,179,253,366,410,367,410', -'282,410,410,252,410,410,410,241,410,410,410,410,41,376,410,410,179,377', -'410,379,380,410,410,384,179,179,179,179,385,410,386,179,179,40,392,410', -'393,39,179,410,410,242,410,410,5,1,415,410,410,410,410,408,408,418,410', -'408,408,420,408,408,179,422,424,,,,,,,,,,,,,408,,,,,,408,,408,,408,408', -',408,408,408,,408,408,408,408,,,408,408,,,408,,173,408,408,,,,,,,408', -',,,,173,408,173,,173,408,408,,408,408,,,,408,408,408,408,248,248,,408', -'248,248,,248,248,173,,,,,,,,,,,,,,,248,,,,,,248,173,248,,248,248,,248', -'248,248,,248,248,248,248,,,248,248,,,248,,,248,248,,,,,11,11,248,,11', -'11,,11,248,,,,248,248,,248,248,,,,248,248,248,248,11,,,248,,,11,,11', -',11,11,,11,11,11,,11,11,,,,,11,11,,,11,,,11,11,,,,,12,12,11,,12,12,', -'12,11,,,,11,11,,11,11,,,,11,11,11,11,12,,,11,,,12,,12,,12,12,,12,12', -'12,,12,12,,,,,12,12,,,12,,,12,12,,,,,13,13,12,,13,13,,13,12,,,,12,12', -',12,12,,,,12,12,12,12,13,,,12,,,13,,13,,13,13,,13,13,13,,13,13,,,,,13', -'13,,,13,,,13,13,,,,,14,14,13,,14,14,,14,13,,,,13,13,,13,13,,,,13,13', -'13,13,14,,,13,,,14,,14,,14,14,,14,14,14,,14,14,,,,,14,14,,,14,,172,14', -'14,,,,,,,14,,,,,172,14,172,,172,14,14,,14,14,,,,14,14,14,14,396,396', -',14,396,396,,396,396,172,,,,,,,,,,,,,,,396,,,,,,396,172,396,,396,396', -',396,396,396,,396,396,396,396,,,396,396,,,396,,,396,396,,,,,388,388', -'396,,388,388,,388,396,,,,396,396,,396,396,,,,396,396,396,396,388,,,396', -',,388,,388,,388,388,,388,388,388,,388,388,388,388,,,388,388,,,388,,', -'388,388,,,,,375,375,388,,375,375,,375,388,,,,388,388,,388,388,,,,388', -'388,388,388,375,,,388,,,375,,375,,375,375,,375,375,375,,375,375,,,,', -'375,375,,,375,,,375,375,,,,,42,42,375,,42,42,,42,375,,,,375,375,,375', -'375,,,,375,375,375,375,42,,,375,,,42,,42,,42,42,,42,42,42,,42,42,,,', -',42,42,,,42,,,42,42,,,,,43,43,42,,43,43,,43,42,,,,42,42,,42,42,,,,42', -'42,42,42,43,,,42,,,43,,43,,43,43,,43,43,43,,43,43,,,,,43,43,,,43,,,43', -'43,,,,,44,44,43,,44,44,,44,43,,,,43,43,,43,43,,,,43,43,43,43,44,,,43', -',,44,,44,,44,44,,44,44,44,,44,44,,,,,44,44,,,44,,,44,44,,,,,45,45,44', -',45,45,,45,44,,,,44,44,,44,44,,,,44,44,44,44,45,,,44,,,45,,45,,45,45', -',45,45,45,,45,45,,,,,45,45,,,45,,,45,45,,,,,249,249,45,,249,249,,249', -'45,,,,45,45,,45,45,,,,45,45,45,45,249,,,45,,,249,,249,,249,249,,249', -'249,249,,249,249,,,,,249,249,,,249,,,249,249,,,,,250,250,249,,250,250', -',250,249,,,,249,249,,249,249,,,,249,249,249,249,250,,,249,,,250,250', -'250,250,250,250,250,250,250,250,,250,250,,,,,250,250,250,250,250,,,250', -'250,,,,,,,250,,,,,250,250,,,,250,250,,250,250,,,,250,250,250,250,251', -'251,,250,251,251,,251,,180,,,,,,,,,,,,,,180,251,180,,180,,,251,,251', -',251,251,,251,251,251,,251,251,,,,,251,251,180,,251,,,251,251,,180,180', -'180,180,,251,,180,180,,,251,,,180,251,251,,251,251,,,,251,251,251,251', -'233,233,,251,233,233,,233,233,180,,,,116,,,,,,,,,,,233,,,116,,116,233', -'116,233,,233,233,,233,233,233,,233,233,233,233,,,233,233,,,233,,116', -'233,233,,,,,56,56,233,,56,56,56,56,233,,,,233,233,,233,233,,,,233,233', -'233,233,56,,,233,,,56,,56,,56,56,,56,56,56,,56,56,,,,,56,56,,,56,,,56', -'56,,,,,57,57,56,,57,57,57,57,56,,,,56,56,,56,56,,,,56,56,56,56,57,,', -'56,,,57,,57,,57,57,,57,57,57,,57,57,,,,,57,57,,,57,,115,57,57,,,,,,', -'57,,,,,115,57,115,,115,57,57,,57,57,,,,57,57,57,57,58,58,,57,58,58,', -'58,58,115,,,,113,,,,,,,,,,,58,,,113,,113,58,113,58,,58,58,,58,58,58', -',58,58,,,,,58,58,,,58,,113,58,58,,,,,64,64,58,,64,64,,64,58,,,,58,58', -',58,58,,,,58,58,58,58,64,,,58,,,64,,64,,64,64,,64,64,64,,64,64,,,,,64', -'64,,,64,,,64,64,,,,,260,260,64,,260,260,260,260,64,,,,64,64,,64,64,', -',,64,64,64,64,260,,,64,,,260,,260,,260,260,,260,260,260,,260,260,,,', -',260,260,,,260,,,260,260,,,,,358,358,260,,358,358,,358,260,,,,260,260', -',260,260,,,,260,260,260,260,358,,,260,,,358,,358,,358,358,,358,358,358', -',358,358,,,,,358,358,,,358,,,358,358,,,,,69,69,358,,69,69,,69,358,,', -',358,358,,358,358,,,,358,358,358,358,69,,,358,,,69,,69,,69,69,,69,69', -'69,,69,69,,,,,69,69,,,69,,,69,69,,,,,262,262,69,,262,262,262,262,69', -',,,69,69,,69,69,,,,69,69,69,69,262,,,69,,,262,,262,,262,262,,262,262', -'262,,262,262,,,,,262,262,,,262,,,262,262,,,,,78,78,262,,78,78,,78,262', -',,,262,262,,262,262,,,,262,262,262,262,78,,,262,,,78,,78,,78,78,,78', -'78,78,,78,78,78,78,,,78,78,,,78,,,78,78,,,,,264,264,78,,264,264,,264', -'78,,,,78,78,,78,78,,,,78,78,78,78,264,,,78,,,264,,264,,264,264,,264', -'264,264,,264,264,,,,,264,264,,,264,,,264,264,,,,,80,80,264,,80,80,,80', -'264,,,,264,264,,264,264,,,,264,264,264,264,80,,,264,,,80,,80,,80,80', -',80,80,80,,80,80,80,80,,,80,80,,,80,,,80,80,,,,,81,81,80,,81,81,,81', -'80,,,,80,80,,80,80,,,,80,80,80,80,81,,,80,,,81,,81,,81,81,,81,81,81', -',81,81,81,81,,,81,81,,,81,,,81,81,,,,,82,82,81,,82,82,,82,81,,,,81,81', -',81,81,,,,81,81,81,81,82,,,81,,,82,,82,,82,82,,82,82,82,,82,82,82,82', -',,82,82,,,82,,,82,82,,,,,83,83,82,,83,83,,83,82,,,,82,82,,82,82,,,,82', -'82,82,82,83,,,82,,,83,,83,,83,83,,83,83,83,,83,83,83,83,,,83,83,,,83', -',,83,83,,,,,84,84,83,,84,84,,84,83,,,,83,83,,83,83,,,,83,83,83,83,84', -',,83,,,84,,84,,84,84,,84,84,84,,84,84,84,84,,,84,84,,,84,,,84,84,,,', -',85,85,84,,85,85,,85,84,,,,84,84,,84,84,,,,84,84,84,84,85,,,84,,,85', -',85,,85,85,,85,85,85,,85,85,,,,,85,85,,,85,,,85,85,,,,,86,86,85,,86', -'86,,86,85,,,,85,85,,85,85,,,,85,85,85,85,86,,,85,,,86,,86,,86,86,,86', -'86,86,,86,86,,,,,86,86,,,86,,,86,86,,,,,87,87,86,,87,87,,87,86,,,,86', -'86,,86,86,,,,86,86,86,86,87,,,86,,,87,,87,,87,87,,87,87,87,,87,87,,', -',,87,87,,,87,,,87,87,,,,,88,88,87,,88,88,,88,87,,,,87,87,,87,87,,,,87', -'87,87,87,88,,,87,,,88,,88,,88,88,,88,88,88,,88,88,,,,,88,88,,,88,,,88', -'88,,,,,89,89,88,,89,89,,89,88,,,,88,88,,88,88,,,,88,88,88,88,89,,,88', -',,89,,89,,89,89,,89,89,89,,89,89,,,,,89,89,,,89,,,89,89,,,,,90,90,89', -',90,90,,90,89,,,,89,89,,89,89,,,,89,89,89,89,90,,,89,,,90,,90,,90,90', -',90,90,90,,90,90,,,,,90,90,,,90,,,90,90,,,,,91,91,90,,91,91,,91,90,', -',,90,90,,90,90,,,,90,90,90,90,91,,,90,,,91,,91,,91,91,,91,91,91,,91', -'91,,,,,91,91,,,91,,,91,91,,,,,92,92,91,,92,92,,92,91,,,,91,91,,91,91', -',,,91,91,91,91,92,,,91,,,92,,92,,92,92,,92,92,92,,92,92,,,,,92,92,,', -'92,,,92,92,,,,,93,93,92,,93,93,,93,92,,,,92,92,,92,92,,,,92,92,92,92', -'93,,,92,,,93,,93,,93,93,,93,93,93,,93,93,,,,,93,93,,,93,,,93,93,,,,', -'94,94,93,,94,94,,94,93,,,,93,93,,93,93,,,,93,93,93,93,94,,,93,,,94,', -'94,,94,94,,94,94,94,,94,94,,,,,94,94,,,94,,,94,94,,,,,95,95,94,,95,95', -',95,94,,,,94,94,,94,94,,,,94,94,94,94,95,,,94,,,95,,95,,95,95,,95,95', -'95,,95,95,,,,,95,95,,,95,,,95,95,,,,,96,96,95,,96,96,,96,95,,,,95,95', -',95,95,,,,95,95,95,95,96,,,95,,,96,,96,,96,96,,96,96,96,,96,96,,,,,96', -'96,,,96,,,96,96,,,,,97,97,96,,97,97,,97,96,,,,96,96,,96,96,,,,96,96', -'96,96,97,,,96,,,97,,97,,97,97,,97,97,97,,97,97,,,,,97,97,,,97,,,97,97', -',,,,98,98,97,,98,98,,98,97,,,,97,97,,97,97,,,,97,97,97,97,98,,,97,,', -'98,,98,,98,98,,98,98,98,,98,98,,,,,98,98,,,98,,,98,98,,,,,99,99,98,', -'99,99,,99,98,,,,98,98,,98,98,,,,98,98,98,98,99,,,98,,,99,,99,,99,99', -',99,99,99,,99,99,,,,,99,99,,,99,,,99,99,,,,,100,100,99,,100,100,,100', -'99,,,,99,99,,99,99,,,,99,99,99,99,100,,,99,,,100,,100,,100,100,,100', -'100,100,,100,100,,,,,100,100,,,100,,,100,100,,,,,101,101,100,,101,101', -',101,100,,,,100,100,,100,100,,,,100,100,100,100,101,,,100,,,101,,101', -',101,101,,101,101,101,,101,101,,,,,101,101,,,101,,,101,101,,,,,102,102', -'101,,102,102,,102,101,,,,101,101,,101,101,,,,101,101,101,101,102,,,101', -',,102,,102,,102,102,,102,102,102,,102,102,,,,,102,102,,,102,,,102,102', -',,,,103,103,102,,103,103,,103,102,,,,102,102,,102,102,,,,102,102,102', -'102,103,,,102,,,103,,103,,103,103,,103,103,103,,103,103,,,,,103,103', -',,103,,,103,103,,,,,104,104,103,,104,104,,104,103,,,,103,103,,103,103', -',,,103,103,103,103,104,,,103,,,104,,104,,104,104,,104,104,104,,104,104', -',,,,104,104,,,104,,,104,104,,,,,105,105,104,,105,105,,105,104,,,,104', -'104,,104,104,,,,104,104,104,104,105,,,104,,,105,,105,,105,105,,105,105', -'105,,105,105,,,,,105,105,,,105,,,105,105,,,,,106,106,105,,106,106,,106', -'105,,,,105,105,,105,105,,,,105,105,105,105,106,,,105,,,106,,106,,106', -'106,,106,106,106,,106,106,,,,,106,106,,,106,,,106,106,,,,,107,107,106', -',107,107,,107,106,,,,106,106,,106,106,,,,106,106,106,106,107,,,106,', -',107,,107,,107,107,,107,107,107,,107,107,,,,,107,107,,,107,,,107,107', -',,,,,,107,,,,,,107,,,107,107,107,,107,107,,,,107,107,107,107,266,266', -',107,266,266,,266,266,,,,,,,,,,,,,,,,266,,,,,,266,,266,,266,266,,266', -'266,266,,266,266,,,,,266,266,,,266,,,266,266,,,,,109,109,266,,109,109', -',109,266,,,,266,266,,266,266,,,,266,266,266,266,109,,,266,,,109,109', -'109,109,109,109,109,109,109,109,,109,109,,,,,109,109,109,109,109,,,109', -'109,,,,,,,109,,,,,109,109,,,,109,109,,109,109,,,,109,109,109,109,271', -'271,,109,271,271,,271,,,,,,,,,,,,,,,,,271,,,,,,271,,271,,271,271,,271', -'271,271,,271,271,,,,,271,271,,,271,,,271,271,,,,,111,111,271,,111,111', -',111,271,,,,271,271,,271,271,,,,271,271,271,271,111,,,271,,,111,,111', -',111,111,,111,111,111,,111,111,,,,,111,111,,,111,,,111,111,,,,,112,112', -'111,,112,112,,112,111,,,,111,111,,111,111,,,,111,111,111,111,112,,,111', -',,112,,112,,112,112,,112,112,112,,112,112,,,,,112,112,,,112,,,112,112', -',,,,200,200,112,,200,200,,200,112,,,,112,112,,112,112,,,,112,112,112', -'112,200,,,112,,,200,,200,,200,200,,200,200,200,,200,200,,,,,200,200', -',,200,,,200,200,,,,,341,341,200,,341,341,,341,200,,,,200,200,,200,200', -',,,200,200,200,200,341,,,200,,,341,,341,,341,341,,341,341,341,,341,341', -'341,341,,,341,341,,,341,,,341,341,,,,,199,199,341,,199,199,,199,341', -',,,341,341,,341,341,,,,341,341,341,341,199,,,341,,,199,,199,,199,199', -',199,199,199,,199,199,,,,,199,199,,,199,,,199,199,,,,,198,198,199,,198', -'198,,198,199,,,,199,199,,199,199,,,,199,199,199,199,198,,,199,,,198', -',198,,198,198,,198,198,198,,198,198,,,,,198,198,,,198,,,198,198,,,,', -'195,195,198,,195,195,,195,198,,,,198,198,,198,198,,,,198,198,198,198', -'195,,,198,,,195,,195,,195,195,,195,195,195,,195,195,,,,,195,195,,,195', -',,195,195,,,,,118,118,195,,118,118,,118,195,,,,195,195,,195,195,,,,195', -'195,195,195,118,118,,195,,,118,,118,,118,118,,118,118,118,,118,118,', -',,,118,118,,,118,,,118,118,,,,,,,118,,,,,,118,,,,118,118,,118,118,,', -',118,118,118,118,330,330,,118,330,330,,330,330,,,,,,,,,,,,,,,,330,,', -',,,330,,330,,330,330,,330,330,330,,330,330,,,,,330,330,,,330,,,330,330', -',,,,295,295,330,,295,295,,295,330,,,,330,330,,330,330,,,,330,330,330', -'330,295,,,330,,,295,,295,,295,295,,295,295,295,,295,295,,,,,295,295', -',,295,,,295,295,,,,,122,122,295,,122,122,,122,295,,,,295,295,,295,295', -',,,295,295,295,295,122,122,,295,,,122,,122,,122,122,,122,122,122,,122', -'122,,,,,122,122,,,122,,,122,122,,,,,284,284,122,,284,284,,284,122,,', -',122,122,,122,122,,,,122,122,122,122,284,,,122,,,284,,284,,284,284,', -'284,284,284,,284,284,,,,,284,284,,,284,,,284,284,,,,,285,285,284,,285', -'285,,285,284,,,,284,284,,284,284,,,,284,284,284,284,285,,,284,,,285', -',285,,285,285,,285,285,285,,285,285,,,,,285,285,,,285,,,285,285,,,,', -',,285,,,,,,285,,,,285,285,,285,285,,,,285,285,285,285,162,162,,285,162', -'162,,162,162,,,,,,,,,,,,,,,,162,,,,,,162,,162,,162,162,,162,162,162', -',162,162,162,162,,,162,162,,,162,,,162,162,,,,,159,159,162,,159,159', -',159,162,,,,162,162,,162,162,,,,162,162,162,162,159,,,162,,,159,,159', -',159,159,,159,159,159,,159,159,159,159,,,159,159,,,159,,,159,159,,,', -',291,291,159,,291,291,,291,159,,,,159,159,,159,159,,,,159,159,159,159', -'291,,,159,,,291,,291,,291,291,,291,291,291,,291,291,,,,,291,291,,,291', -',,291,291,,,,,129,129,291,,129,129,,129,291,,,,291,291,,291,291,,,,291', -'291,291,291,129,,,291,,,129,,129,,129,129,,129,129,129,,129,129,,,,', -'129,129,,,129,,,129,129,,,,,323,323,129,,323,323,,323,129,,,,129,129', -',129,129,,,,129,129,129,129,323,,,129,,,323,,323,,323,323,,323,323,323', -',323,323,,,,,323,323,,,323,,,323,323,,,,,320,320,323,,320,320,,320,323', -',,,323,323,,323,323,,,,323,323,323,323,320,,,323,,,320,,320,,320,320', -',320,320,320,,320,320,,,,,320,320,,,320,,,320,320,,,,,294,294,320,,294', -'294,,294,320,,,,320,320,,320,320,,,,320,320,320,320,294,,,320,,,294', -',294,,294,294,,294,294,294,,294,294,,,,,294,294,,,294,,,294,294,,,,', -'313,313,294,,313,313,,313,294,,,,294,294,,294,294,,,,294,294,294,294', -'313,,,294,,,313,,313,,313,313,,313,313,313,,313,313,,,,,313,313,,,313', -',,313,313,,,,,,,313,,,,,,313,,,,313,313,,313,313,,,,313,313,313,313', -'247,247,,313,247,247,,247,247,176,,,,,,,,,,,,,,176,247,176,,176,,,247', -',247,,247,247,,247,247,247,,247,247,247,247,,,247,247,176,,247,,,247', -'247,,,,,,,247,,176,176,,,247,,,176,247,247,,247,247,,,145,247,247,247', -'247,,,,247,145,145,145,145,145,145,,145,,145,,,145,145,145,145,,,,,', -',,,,,,,,,,145,,,,145,145,,188,145,145,145,145,145,145,,145,145,,,188', -'188,188,145,188,,188,,150,188,188,188,188,150,,,,150,150,150,150,150', -'150,,150,145,150,,188,150,150,150,150,188,,,188,188,188,188,188,188', -',188,188,,,,150,,188,,150,150,,,150,150,150,150,150,150,,150,150,10', -',10,,,150,,188,,10,10,10,10,10,10,,10,,10,,,10,10,10,10,,,150,,,,,,', -',,,,,,10,,,,10,10,,,10,10,10,10,10,10,154,10,10,,,,,,10,154,154,154', -'154,154,154,,154,,154,,,154,154,154,154,,,,,,10,,,,,,,,,,154,,,,154', -'154,,,154,154,154,154,154,154,371,154,154,,,,,,154,371,371,371,371,371', -'371,,371,,371,,,371,371,371,371,,,,,,154,,,,,,,,,,371,,,,371,371,,,371', -'371,371,371,371,371,365,371,371,,,,,,371,365,365,365,365,365,365,,365', -',365,,,365,365,365,365,,,,,,371,,,,,,,,,,365,,,,365,365,,,365,365,365', -'365,365,365,,365,365,127,,127,,,365,,,,127,127,127,127,127,127,,127', -',127,,,127,127,127,127,,,365,,,,,,,,,,,,,127,,,,127,127,,,127,127,127', -'127,127,127,364,127,127,,,,,,127,364,364,364,364,364,364,,364,,364,', -',364,364,364,364,,,,,,127,,,,,,,,,,364,,,,364,364,,,364,364,364,364', -'364,364,161,364,364,,,,,,364,161,161,161,161,161,161,,161,,161,,,161', -'161,161,161,,,,,,364,,,,,,,,,,161,,,,161,161,,181,161,161,161,161,161', -'161,,161,161,,,,,181,161,181,,181,,126,,126,,,,161,161,,126,126,126', -'126,126,126,,126,161,126,,181,126,126,126,126,,,,181,181,181,181,181', -'181,,181,181,,,,126,,181,,126,126,,,126,126,126,126,126,126,,126,126', -'125,,125,,,126,,181,,125,125,125,125,125,125,,125,,125,,,125,125,125', -'125,,,126,,,,,,,,,,,,,125,,,,125,125,,,125,125,125,125,125,125,,125', -'125,123,,123,,,125,,,,123,123,123,123,123,123,,123,,123,,,123,123,123', -'123,,,125,,,,,,,,,,,,,123,,,,123,123,,,123,123,123,123,123,123,356,123', -'123,,,,,,123,356,356,356,356,356,356,,356,,356,,,356,356,356,356,,,', -',,123,,,,,,,,,,356,,,,356,356,,,356,356,356,356,356,356,225,356,356', -',,,,,356,225,225,225,225,225,225,,225,,225,,,225,225,225,225,,,,,,356', -',,,,,,,,,225,,,,225,225,,,225,225,225,225,225,225,350,225,225,,,,,,225', -'350,350,350,350,350,350,,350,,350,,,350,350,350,350,,,,,,225,,,,,,,', -',,350,,,,350,350,,,350,350,350,350,350,350,192,350,350,,,192,,,350,192', -'192,192,192,192,192,,192,,192,,,192,192,192,192,,,,,,350,,,,,,,,,,192', -',,,192,192,,,192,192,192,192,192,192,347,192,192,,,,,,192,347,347,347', -'347,347,347,,347,,347,,,347,347,347,347,,,,,,192,,,,,,,,,,347,,,,347', -'347,,,347,347,347,347,347,347,,347,347,,281,,281,,347,281,,,,281,281', -'281,281,281,281,,281,,281,,,281,281,281,281,,347,,,,,,,,,,,,,,281,,', -',281,281,,,281,281,281,281,281,281,197,281,281,,,,,,281,197,197,197', -'197,197,197,197,197,182,197,,,197,197,197,197,,,,,,281,182,,182,,182', -',,,,197,,,,197,197,,,197,197,197,197,197,197,,197,197,182,,,,,197,,183', -'182,182,182,182,182,182,,182,182,,,,183,183,182,183,184,183,,197,183', -',,,,,,,,184,184,,184,,184,,182,184,,183,,,,,,,,183,183,183,183,183,183', -',183,183,184,,,,,183,,185,184,184,184,184,184,184,,184,184,,,,185,185', -'184,185,186,185,,183,185,,,,,,,,,186,186,,186,,186,,184,186,,185,,,', -',,,,185,185,185,185,185,185,,185,185,186,,,,,185,,187,186,186,186,186', -'186,186,,186,186,,,187,187,187,186,187,,187,,185,187,187,187,187,,,', -',,,,,,,,,186,,,187,,,,,,,,187,187,187,187,187,187,117,187,187,,,,,,187', -'117,117,117,117,117,117,,117,,117,,117,117,117,117,117,,,,,,187,,,,', -',,,,,117,,,,117,117,,,117,117,117,117,117,117,189,117,117,,,,,,117,189', -'189,189,189,189,189,,189,,189,,,189,189,189,189,,,,,,117,,,,,,,,,,189', -',,,189,189,,,189,189,189,189,189,189,190,189,189,,,,,,189,190,190,190', -'190,190,190,,190,,190,,,190,190,190,190,,,,,,189,,,,,,,,,,190,,,,190', -'190,,,190,190,190,190,190,190,191,190,190,,,,,,190,191,191,191,191,191', -'191,,191,175,191,,,191,191,191,191,,,,,,190,175,,175,,175,,,258,258', -'191,,258,,191,191,,,191,191,191,191,191,191,,191,191,175,,,50,50,191', -',50,344,344,175,175,344,,,175,175,211,211,,258,211,175,258,,,,191,,', -',,,,,,,,,,,,50,258,175,50,177,344,,,344,,,258,258,178,211,,,211,177', -',177,,177,50,,,,178,344,178,,178,,50,50,,,211,344,344,,,,,177,,,211', -'211,,,,,178,,,,,,177,177,,,,,,177,,178,178,,293,293,293,293,178,293', -'293,293,293,293,,293,293,,,,,,,293,293,293,224,224,224,224,,224,224', -'224,224,224,,224,224,,,293,293,,,224,224,224,,,,,,,293,293,293,293,', -',,,,224,224,,,,,,,,,,,,224,224,224,224,288,288,288,288,,288,288,288', -'288,288,,288,288,,,,,,,288,288,288,,,,,,,,,,,,,,,,288,288,,,,,,,,,,', -',288,288,288,288' ] - racc_action_check = arr = ::Array.new(7093, nil) +'0,0,352,174,0,0,240,0,180,191,178,181,238,248,168,167,179,166,145,145', +'240,365,323,191,0,191,365,191,191,250,0,323,0,238,0,0,113,0,0,0,113', +'0,0,0,0,174,248,0,0,191,180,0,178,181,0,0,168,167,179,166,403,403,0', +'251,403,403,312,403,0,189,161,191,0,0,189,0,0,0,12,312,0,0,0,0,403,45', +'0,312,160,45,403,119,403,159,403,403,150,403,403,403,140,403,403,140', +'119,264,12,403,403,150,12,403,119,269,403,403,320,150,320,175,4,4,403', +'175,4,4,119,4,403,270,306,150,403,403,306,403,403,306,340,340,403,403', +'403,403,4,176,403,225,154,176,4,366,4,366,4,4,225,4,4,4,4,4,4,4,4,272', +'164,4,4,225,246,4,148,148,4,4,148,225,143,245,398,398,4,138,398,398', +'136,398,4,7,7,7,4,4,280,4,4,282,284,285,4,4,4,4,398,301,4,128,126,304', +'398,239,398,308,398,398,309,398,398,398,311,398,398,398,398,237,125', +'398,398,118,116,398,319,236,398,398,321,322,7,7,7,7,398,230,212,108', +'327,106,398,339,217,341,398,398,105,398,398,102,101,70,398,398,398,398', +'228,228,398,68,228,228,349,228,228,63,351,162,355,62,360,223,213,220', +'41,369,370,372,373,376,228,177,177,40,383,177,228,384,228,390,228,228', +'219,228,228,228,8,228,228,228,228,5,400,228,228,1,405,228,407,409,228', +'228,413,,,,,,228,,177,,,177,228,,,,228,228,,228,228,,,,228,228,228,228', +'397,397,228,177,397,397,,397,397,192,,,,177,177,,,,,,,,,192,397,192', +',192,192,,397,,397,,397,397,,397,397,397,,397,397,397,397,,,397,397', +'192,,397,,,397,397,,,,,211,211,397,,211,211,,211,397,,,192,397,397,', +'397,397,,,,397,397,397,397,211,,397,,,,211,,211,,211,211,,211,211,211', +',211,211,,,,,211,211,,,211,,,211,211,,,,,10,10,211,,10,10,,10,211,,', +',211,211,,211,211,,,,211,211,211,211,10,,211,,,,10,,10,,10,10,,10,10', +'10,,10,10,10,10,,,10,10,,,10,,,10,10,,,,,11,11,10,,11,11,,11,10,,,,10', +'10,,10,10,,,,10,10,10,10,11,,10,,,,11,,11,,11,11,,11,11,11,,11,11,11', +'11,,,11,11,,,11,,,11,11,,,,,,,11,,,,,,11,,,,11,11,,11,11,,,,11,11,11', +'11,395,395,11,,395,395,,395,395,114,,,,,,,,,,,,,,114,395,114,,114,114', +',395,,395,,395,395,,395,395,395,,395,395,395,395,,,395,395,114,,395', +',,395,395,,,,,15,15,395,,15,15,,15,395,,,,395,395,,395,395,,,,395,395', +'395,395,15,,395,,,,15,,15,,15,15,,15,15,15,,15,15,,,,,15,15,,,15,,,15', +'15,,,,,16,16,15,,16,16,,16,15,,,,15,15,,15,15,,,,15,15,15,15,16,,15', +',,,16,,16,,16,16,,16,16,16,,16,16,,,,,16,16,,,16,,,16,16,,,,,17,17,16', +',17,17,,17,16,,,,16,16,,16,16,,,,16,16,16,16,17,,16,,,,17,,17,,17,17', +',17,17,17,,17,17,,,,,17,17,,,17,,,17,17,,,,,18,18,17,,18,18,,18,17,', +',,17,17,,17,17,,,,17,17,17,17,18,,17,,,,18,,18,,18,18,,18,18,18,,18', +'18,18,18,,,18,18,,,18,,,18,18,,,,,,,18,,,,,,18,,,,18,18,,18,18,,,,18', +'18,18,18,379,379,18,,379,379,,379,379,115,,,,,,,,,,,,,,115,379,115,', +'115,115,,379,,379,,379,379,,379,379,379,,379,379,379,379,,,379,379,115', +',379,,,379,379,,,,,368,368,379,,368,368,,368,379,,,,379,379,,379,379', +',,,379,379,379,379,368,,379,,,,368,,368,,368,368,,368,368,368,,368,368', +',,,,368,368,,,368,,,368,368,,,,,42,42,368,,42,42,,42,368,,,,368,368', +',368,368,,,,368,368,368,368,42,,368,,,,42,,42,,42,42,,42,42,42,,42,42', +',,,,42,42,,,42,,,42,42,,,,,43,43,42,,43,43,,43,42,,,,42,42,,42,42,,', +',42,42,42,42,43,,42,,,,43,,43,,43,43,,43,43,43,,43,43,,,,,43,43,,,43', +',,43,43,,,,,44,44,43,,44,44,,44,43,,,,43,43,,43,43,,,,43,43,43,43,44', +',43,,,,44,,44,,44,44,,44,44,44,,44,44,,,,,44,44,,,44,,,44,44,,,,,,,44', +',,,,,44,,,,44,44,,44,44,,,,44,44,44,44,242,242,44,,242,242,,242,242', +',,,,,,,,,,,,,,,242,46,46,,,46,242,,242,,242,242,,242,242,242,,242,242', +'242,242,,,242,242,,,242,,,242,242,,,,,,,242,,46,,,46,242,,,,242,242', +',242,242,,,,242,242,242,242,243,243,242,46,243,243,,243,243,190,,,,46', +'46,,,,,,,,,190,243,190,,190,190,,243,,243,,243,243,,243,243,243,,243', +'243,243,243,,,243,243,190,,243,,,243,243,,,,,52,52,243,,52,52,52,52', +'243,,,,243,243,,243,243,,,,243,243,243,243,52,,243,,,,52,,52,,52,52', +',52,52,52,,52,52,52,52,,,52,52,,,52,,,52,52,,,,,53,53,52,,53,53,53,53', +'52,,,,52,52,,52,52,,,,52,52,52,52,53,,52,,,,53,,53,,53,53,,53,53,53', +',53,53,53,53,,,53,53,,,53,,,53,53,,,,,,,53,,,,,,53,,,,53,53,,53,53,', +',,53,53,53,53,54,54,53,,54,54,,54,54,112,,,,,,,,,,,,,,112,54,112,,112', +'112,,54,,54,,54,54,,54,54,54,,54,54,54,54,,,54,54,112,,54,,,54,54,,', +',,60,60,54,,60,60,,60,54,,,,54,54,,54,54,,,,54,54,54,54,60,,54,,,,60', +',60,,60,60,,60,60,60,,60,60,60,60,,,60,60,,,60,,,60,60,,,,,356,356,60', +',356,356,,356,60,,,,60,60,,60,60,,,,60,60,60,60,356,,60,,,,356,,356', +',356,356,,356,356,356,,356,356,356,356,,,356,356,,,356,,,356,356,,,', +',350,350,356,,350,350,,350,356,,,,356,356,,356,356,,,,356,356,356,356', +'350,,356,,,,350,,350,,350,350,,350,350,350,,350,350,,,,,350,350,,,350', +',,350,350,,,,,65,65,350,,65,65,,65,350,,,,350,350,,350,350,,,,350,350', +'350,350,65,,350,,,,65,,65,,65,65,,65,65,65,,65,65,,,,,65,65,,,65,,,65', +'65,,,,,244,244,65,,244,244,,244,65,,,,65,65,,65,65,,,,65,65,65,65,244', +',65,,,,244,,244,,244,244,,244,244,244,,244,244,,,,,244,244,,,244,,,244', +'244,,,,,69,69,244,,69,69,,69,244,,,,244,244,,244,244,,,,244,244,244', +'244,69,,244,,,,69,,69,,69,69,,69,69,69,,69,69,69,69,,,69,69,,,69,,,69', +'69,,,,,171,171,69,,171,171,,171,69,,,,69,69,,69,69,,,,69,69,69,69,171', +',69,,,,171,,171,,171,171,,171,171,171,,171,171,,,,,171,171,,,171,,,171', +'171,,,,,71,71,171,,71,71,,71,171,,,,171,171,,171,171,,,,171,171,171', +'171,71,,171,,,,71,,71,,71,71,,71,71,71,,71,71,71,71,,,71,71,,,71,,,71', +'71,,,,,72,72,71,,72,72,,72,71,,,,71,71,,71,71,,,,71,71,71,71,72,,71', +',,,72,,72,,72,72,,72,72,72,,72,72,72,72,,,72,72,,,72,,,72,72,,,,,73', +'73,72,,73,73,,73,72,,,,72,72,,72,72,,,,72,72,72,72,73,,72,,,,73,,73', +',73,73,,73,73,73,,73,73,73,73,,,73,73,,,73,,,73,73,,,,,74,74,73,,74', +'74,,74,73,,,,73,73,,73,73,,,,73,73,73,73,74,,73,,,,74,,74,,74,74,,74', +'74,74,,74,74,74,74,,,74,74,,,74,,,74,74,,,,,75,75,74,,75,75,,75,74,', +',,74,74,,74,74,,,,74,74,74,74,75,,74,,,,75,,75,,75,75,,75,75,75,,75', +'75,75,75,,,75,75,,,75,,,75,75,,,,,76,76,75,,76,76,,76,75,,,,75,75,,75', +'75,,,,75,75,75,75,76,,75,,,,76,,76,,76,76,,76,76,76,,76,76,76,76,,,76', +'76,,,76,,,76,76,,,,,77,77,76,,77,77,,77,76,,,,76,76,,76,76,,,,76,76', +'76,76,77,,76,,,,77,,77,,77,77,,77,77,77,,77,77,77,77,,,77,77,,,77,,', +'77,77,,,,,78,78,77,,78,78,,78,77,,,,77,77,,77,77,,,,77,77,77,77,78,', +'77,,,,78,,78,,78,78,,78,78,78,,78,78,78,78,,,78,78,,,78,,,78,78,,,,', +'79,79,78,,79,79,,79,78,,,,78,78,,78,78,,,,78,78,78,78,79,,78,,,,79,79', +'79,79,79,79,79,79,79,79,,79,79,,,,,79,79,79,79,79,,,79,79,,,,,,,79,', +',,,79,79,,,,79,79,,79,79,,,,79,79,79,79,80,80,79,,80,80,,80,,,,,,,,', +',,,,,,,,80,,,,,,80,,80,,80,80,,80,80,80,,80,80,,,,,80,80,,,80,,,80,80', +',,,,81,81,80,,81,81,,81,80,,,,80,80,,80,80,,,,80,80,80,80,81,,80,,,', +'81,,81,,81,81,,81,81,81,,81,81,,,,,81,81,,,81,,,81,81,,,,,82,82,81,', +'82,82,,82,81,,,,81,81,,81,81,,,,81,81,81,81,82,,81,,,,82,,82,,82,82', +',82,82,82,,82,82,,,,,82,82,,,82,,,82,82,,,,,83,83,82,,83,83,,83,82,', +',,82,82,,82,82,,,,82,82,82,82,83,,82,,,,83,,83,,83,83,,83,83,83,,83', +'83,,,,,83,83,,,83,,,83,83,,,,,84,84,83,,84,84,,84,83,,,,83,83,,83,83', +',,,83,83,83,83,84,,83,,,,84,,84,,84,84,,84,84,84,,84,84,,,,,84,84,,', +'84,,,84,84,,,,,85,85,84,,85,85,,85,84,,,,84,84,,84,84,,,,84,84,84,84', +'85,,84,,,,85,,85,,85,85,,85,85,85,,85,85,,,,,85,85,,,85,,,85,85,,,,', +'86,86,85,,86,86,,86,85,,,,85,85,,85,85,,,,85,85,85,85,86,,85,,,,86,', +'86,,86,86,,86,86,86,,86,86,,,,,86,86,,,86,,,86,86,,,,,87,87,86,,87,87', +',87,86,,,,86,86,,86,86,,,,86,86,86,86,87,,86,,,,87,,87,,87,87,,87,87', +'87,,87,87,,,,,87,87,,,87,,,87,87,,,,,88,88,87,,88,88,,88,87,,,,87,87', +',87,87,,,,87,87,87,87,88,,87,,,,88,,88,,88,88,,88,88,88,,88,88,,,,,88', +'88,,,88,,,88,88,,,,,89,89,88,,89,89,,89,88,,,,88,88,,88,88,,,,88,88', +'88,88,89,,88,,,,89,,89,,89,89,,89,89,89,,89,89,,,,,89,89,,,89,,,89,89', +',,,,90,90,89,,90,90,,90,89,,,,89,89,,89,89,,,,89,89,89,89,90,,89,,,', +'90,,90,,90,90,,90,90,90,,90,90,,,,,90,90,,,90,,,90,90,,,,,91,91,90,', +'91,91,,91,90,,,,90,90,,90,90,,,,90,90,90,90,91,,90,,,,91,,91,,91,91', +',91,91,91,,91,91,,,,,91,91,,,91,,,91,91,,,,,92,92,91,,92,92,,92,91,', +',,91,91,,91,91,,,,91,91,91,91,92,,91,,,,92,,92,,92,92,,92,92,92,,92', +'92,,,,,92,92,,,92,,,92,92,,,,,93,93,92,,93,93,,93,92,,,,92,92,,92,92', +',,,92,92,92,92,93,,92,,,,93,,93,,93,93,,93,93,93,,93,93,,,,,93,93,,', +'93,,,93,93,,,,,94,94,93,,94,94,,94,93,,,,93,93,,93,93,,,,93,93,93,93', +'94,,93,,,,94,,94,,94,94,,94,94,94,,94,94,,,,,94,94,,,94,,,94,94,,,,', +'95,95,94,,95,95,,95,94,,,,94,94,,94,94,,,,94,94,94,94,95,,94,,,,95,', +'95,,95,95,,95,95,95,,95,95,,,,,95,95,,,95,,,95,95,,,,,96,96,95,,96,96', +',96,95,,,,95,95,,95,95,,,,95,95,95,95,96,,95,,,,96,,96,,96,96,,96,96', +'96,,96,96,,,,,96,96,,,96,,,96,96,,,,,97,97,96,,97,97,,97,96,,,,96,96', +',96,96,,,,96,96,96,96,97,,96,,,,97,,97,,97,97,,97,97,97,,97,97,,,,,97', +'97,,,97,,,97,97,,,,,98,98,97,,98,98,,98,97,,,,97,97,,97,97,,,,97,97', +'97,97,98,,97,,,,98,,98,,98,98,,98,98,98,,98,98,,,,,98,98,,,98,,,98,98', +',,,,99,99,98,,99,99,,99,98,,,,98,98,,98,98,,,,98,98,98,98,99,,98,,,', +'99,,99,,99,99,,99,99,99,,99,99,,,,,99,99,,,99,,,99,99,,,,,100,100,99', +',100,100,,100,99,,,99,99,99,,99,99,,,,99,99,99,99,100,100,99,,,,100', +',100,,100,100,,100,100,100,,100,100,100,100,,,100,100,,,100,,,100,100', +',,,,278,278,100,,278,278,,278,100,,,,100,100,,100,100,,,,100,100,100', +'100,278,,100,,,,278,,278,,278,278,,278,278,278,,278,278,,,,,278,278', +',,278,,,278,278,,,,,169,169,278,,169,169,,169,278,,,,278,278,,278,278', +',,,278,278,278,278,169,,278,,,,169,,169,,169,169,,169,169,169,,169,169', +',,,,169,169,,,169,,,169,169,,,,,103,103,169,,103,103,,103,169,,,,169', +'169,,169,169,,,,169,169,169,169,103,,169,,,,103,,103,,103,103,,103,103', +'103,,103,103,,,,,103,103,,,103,,,103,103,,,,,104,104,103,,104,104,,104', +'103,,,,103,103,,103,103,,,,103,103,103,103,104,,103,,,,104,,104,,104', +'104,,104,104,104,,104,104,,,,,104,104,,,104,,,104,104,,,,,165,165,104', +',165,165,,165,104,,165,,104,104,,104,104,,,,104,104,104,104,165,,104', +',,,165,,165,,165,165,,165,165,165,,165,165,,,,,165,165,,,165,,,165,165', +',,,,249,249,165,,249,249,,249,165,,,,165,165,,165,165,,,,165,165,165', +'165,249,,165,,,,249,,249,,249,249,,249,249,249,,249,249,249,249,,,249', +'249,,,249,,,249,249,,,,,107,107,249,,107,107,,107,249,,,,249,249,,249', +'249,,,,249,249,249,249,107,,249,,,,107,,107,,107,107,,107,107,107,,107', +'107,,,,,107,107,,,107,,,107,107,,,,,326,326,107,,326,326,,326,107,,', +',107,107,,107,107,,,,107,107,107,107,326,,107,,,,326,,326,,326,326,', +'326,326,326,,326,326,326,326,,,326,326,,,326,,,326,326,,,,,,,326,,,', +',,326,,,,326,326,,326,326,,,,326,326,326,326,253,253,326,,253,253,,253', +'253,,,,,,,,,,,,,,,,253,247,247,,,247,253,,253,,253,253,,253,253,253', +',253,253,253,253,,,253,253,,,253,,,253,253,,,,,,,253,,247,,,247,253', +',,,253,253,,253,253,,,,253,253,253,253,324,324,253,247,324,324,,324', +'324,,,,,247,247,,,,,,,,,,324,,,,,,324,,324,,324,324,,324,324,324,,324', +'324,,,,,324,324,,,324,,,324,324,,,,,254,254,324,,254,254,,254,324,,', +',324,324,,324,324,,,,324,324,324,324,254,,324,,,,254,,254,,254,254,', +'254,254,254,,254,254,254,254,,,254,254,,,254,,,254,254,,,,,259,259,254', +',259,259,,259,254,,,,254,254,,254,254,,,,254,254,254,254,259,,254,,', +',259,,259,,259,259,,259,259,259,,259,259,259,259,,,259,259,,,259,,,259', +'259,,,,,317,317,259,,317,317,,317,259,,,,259,259,,259,259,,,,259,259', +'259,259,317,,259,,,,317,,317,,317,317,,317,317,317,,317,317,317,317', +',,317,317,,,317,,,317,317,,,,,316,316,317,,316,316,,316,317,,,,317,317', +',317,317,,,,317,317,317,317,316,,317,,,,316,,316,,316,316,,316,316,316', +',316,316,,,,,316,316,,,316,,,316,316,,,,,,,316,,,,,,316,,,,316,316,', +'316,316,,,,316,316,316,316,152,152,316,,152,152,,152,152,,,,,,,,,,,', +',,,,152,,,,,,152,,152,,152,152,,152,152,152,,152,152,152,152,,,152,152', +',,152,,,152,152,,,,,120,120,152,,120,120,,120,152,,,,152,152,,152,152', +',,,152,152,152,152,120,120,152,,,,120,,120,,120,120,,120,120,120,,120', +'120,120,120,,,120,120,,,120,,,120,120,,,,,149,149,120,,149,149,,149', +'120,,,,120,120,,120,120,,,,120,120,120,120,149,,120,,,,149,,149,,149', +'149,,149,149,149,,149,149,149,149,,,149,149,,,149,,,149,149,,,,,274', +'274,149,,274,274,,274,149,,,,149,149,,149,149,,,,149,149,149,149,274', +',149,,,,274,,274,,274,274,,274,274,274,,274,274,,,,,274,274,,,274,,', +'274,274,,,,,275,275,274,,275,275,,275,274,,,,274,274,,274,274,,,,274', +'274,274,274,275,,274,,,,275,,275,,275,275,,275,275,275,,275,275,,,,', +'275,275,,,275,,,275,275,,,,,313,313,275,,313,313,,313,275,,,,275,275', +',275,275,,,,275,275,275,275,313,,275,,,,313,,313,,313,313,,313,313,313', +',313,313,,,,,313,313,,,313,,,313,313,,,,,276,276,313,,276,276,,276,313', +',,,313,313,,313,313,,,,313,313,313,313,276,,313,,,,276,,276,,276,276', +',276,276,276,,276,276,,,,,276,276,,,276,,,276,276,,,,,302,302,276,,302', +'302,,302,276,,,,276,276,,276,276,,,,276,276,276,276,302,,276,,,,302', +',302,,302,302,,302,302,302,,302,302,,,,,302,302,,,302,,,302,302,,,,', +'279,279,302,,279,279,,279,302,,,,302,302,,302,302,,,,302,302,302,302', +'279,,302,,,,279,,279,,279,279,,279,279,279,,279,279,,,,,279,279,,,279', +',,279,279,,,,,170,170,279,,170,170,,170,279,,,,279,279,,279,279,,,,279', +'279,279,279,170,,279,,,,170,,170,,170,170,,170,170,170,,170,170,,,,', +'170,170,,,170,,,170,170,,,,,,,170,,,,,,170,,,,170,170,,170,170,346,', +',170,170,170,170,,,170,,,346,346,346,,346,,346,346,,346,346,346,346', +',329,329,,,329,,,,,,,,,,346,,,,346,346,,,346,346,346,346,346,346,,346', +'346,124,,124,,,346,,,329,,,329,124,124,124,,124,,124,124,,124,124,124', +'124,,346,,,,,329,,,,,,,,,124,329,329,,124,124,,,124,124,124,124,124', +'124,,124,124,123,,123,,,124,,,,,,,123,123,123,,123,193,123,123,,123', +'123,123,123,,124,,,,,193,,193,,193,193,,,,123,,,,123,123,,,123,123,123', +'123,123,123,,123,123,193,,,,,123,,,,,193,193,,,,193,193,121,,121,,,193', +',,,123,,,121,121,121,,121,195,121,121,,121,121,121,121,,193,,,,,195', +',195,,195,195,,,,121,,,,121,121,,216,121,121,121,121,121,121,,121,121', +'195,,216,216,216,121,216,,216,216,,216,216,216,216,195,195,,,,,,195', +',,,121,,,,216,,,,216,216,,151,216,216,216,216,216,216,,216,216,,,151', +'151,151,216,151,,151,151,,151,151,151,151,,,,,,,,,,,,216,,,,151,,,,151', +'151,,,151,151,151,151,151,151,,151,151,,,,,,151,221,,,,,,,,,,151,151', +'221,221,221,221,221,199,221,221,151,221,221,221,221,,,,,,,199,,199,', +'199,199,,,,221,,,,221,221,,,221,221,221,221,221,221,,221,221,199,,,', +',221,,,199,199,199,199,,,,199,199,9,,,,,199,,,,221,,,9,9,9,,9,,9,9,', +'9,9,9,9,,199,,,,,,,,,,,,,,9,,,,9,9,,,9,9,9,9,9,9,208,9,9,,,208,,,9,', +',,208,208,208,,208,196,208,208,,208,208,208,208,,,,,9,,196,,196,,196', +'196,,,,208,,,,208,208,,207,208,208,208,208,208,208,,208,208,196,,207', +'207,207,208,207,,207,207,,207,207,207,207,196,196,,,,,,196,,,,208,,', +',207,,,,,207,,364,207,207,207,207,207,207,,207,207,,,364,364,364,207', +'364,,364,364,,364,364,364,364,,,,,,,,,,,,207,,,,364,,,,364,364,,,364', +'364,364,364,364,364,163,364,364,,,,,,364,,,,163,163,163,163,163,197', +'163,163,,163,163,163,163,,,,,364,,197,,197,,197,197,,,,163,,,,163,163', +',188,163,163,163,163,163,163,,163,163,197,,188,188,188,163,188,,188', +'188,,188,188,188,188,197,197,,,,,,197,,,,163,,,,188,,,,188,188,,344', +'188,188,188,188,188,188,,188,188,,,344,344,344,188,344,,344,344,,344', +'344,344,344,,,,,,,,,,,,188,,,,344,,,,344,344,,206,344,344,344,344,344', +'344,,344,344,,,206,206,206,344,206,,206,206,,206,206,206,206,205,,,', +',,,,,,,344,,205,205,206,205,,205,205,198,205,,206,206,206,206,206,206', +',206,206,,,198,,198,206,198,198,205,,,,,,,,205,205,205,205,205,205,', +'205,205,,206,,198,,205,,,,,,198,198,198,198,345,,,198,198,,,,,,198,205', +'345,345,345,,345,,345,345,194,345,345,345,345,,,,,,,198,,,194,,194,', +'194,194,345,,,,345,345,,,345,345,345,345,345,345,,345,345,,,,194,,345', +',,,,,,,194,194,347,,,194,194,,,,,,194,345,347,347,347,,347,,347,347', +'203,347,347,347,347,,,,,,,194,,203,203,,203,,203,203,347,203,,,347,347', +',,347,347,347,347,347,347,,347,347,,,,203,,347,,,,,,203,203,203,203', +'203,203,348,203,203,,,,,,203,347,,,348,348,348,,348,200,348,348,,348', +'348,348,348,,,,,203,,200,,200,,200,200,,,,348,,,,348,348,,,348,348,348', +'348,348,348,,348,348,200,,,,,348,,,200,200,200,200,200,200,201,200,200', +',,,,,200,,,,348,,201,,201,202,201,201,,,,,,,,,,200,202,202,,202,,202', +'202,,202,,201,,,,,,,,201,201,201,201,201,201,,201,201,202,,,,,201,,', +'202,202,202,202,202,202,204,202,202,,,,,,202,,,,201,204,204,,204,,204', +'204,,204,,,,,,,,202,,,,,,,,,,,204,,,,,,,,204,204,204,204,204,204,,204', +'204,,,,,,204,,,271,271,271,271,,271,271,271,,271,,271,271,,,,,,204,271', +'271,271,,,,271,,,,,,,,,,,,271,271,,,,,,,,,,,,271,271,271,271,273,273', +'273,273,,273,273,273,,273,,273,273,,,,,,,273,273,273,303,303,303,303', +',303,303,303,,303,,303,303,,,273,273,,,303,303,303,,,,303,,,273,273', +'273,273,,,,,,303,303,,,,,,,,,,,,303,303,303,303,215,215,215,215,,215', +'215,215,,215,,215,215,,,,,,,215,215,215,,,,215,,,,,,,,,,,,215,215,,', +',,,,,,,,,215,215,215,215' ] + racc_action_check = arr = ::Array.new(6777, 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, 306, nil, nil, 142, 292, nil, 135, nil, nil, - 5488, 454, 514, 574, 634, nil, nil, nil, nil, nil, + -2, 313, nil, nil, 118, 296, nil, 173, 295, 5793, + 466, 526, 69, nil, nil, 670, 730, 790, 850, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, - nil, nil, nil, nil, nil, nil, nil, nil, nil, 272, - 223, 247, 898, 958, 1018, 1078, 57, -43, nil, 106, - 6851, nil, nil, nil, nil, nil, 1426, 1486, 1570, nil, - nil, nil, nil, nil, 1630, nil, 178, 178, nil, 1810, - nil, nil, nil, nil, nil, nil, nil, 247, 1930, 231, - 2050, 2110, 2170, 2230, 2290, 2350, 2410, 2470, 2530, 2590, - 2650, 2710, 2770, 2830, 2890, 2950, 3010, 3070, 3130, 3190, - 3250, 3310, 3370, 3430, 3490, 3550, 3610, 3670, 202, 3814, - 233, 3958, 4018, 1578, -19, 1534, 1374, 6624, 4378, nil, - 209, 63, 4582, 5996, nil, 5939, 5882, 5707, 65, 4966, - 183, nil, nil, nil, nil, 182, 194, nil, 172, nil, - nil, nil, nil, nil, nil, 5364, 97, nil, 23, nil, - 5431, 57, nil, nil, 5542, nil, 74, nil, 167, 4846, - 104, 5815, 4786, nil, 56, nil, nil, nil, nil, nil, - 116, 90, 682, 358, 2, 6803, 5294, 6889, 6898, 230, - 1286, 5862, 6395, 6442, 6459, 6506, 6523, 6570, 5411, 6678, - 6732, 6786, 6212, nil, nil, 4318, nil, 6378, 4258, 4198, - 4078, -2, 128, nil, nil, 2, nil, -2, -9, 113, - 121, 6865, -4, 9, -1, 7, 3, 4, nil, nil, - nil, nil, nil, nil, 6953, 6104, 132, nil, 165, nil, - 198, 144, nil, 1366, nil, 233, nil, nil, nil, nil, - nil, 252, 266, -24, 170, -7, nil, 5290, 394, 1138, - 1198, 1282, 222, 218, nil, 106, 231, 200, 6829, -28, - 1690, nil, 1870, nil, 1990, nil, 3754, nil, nil, nil, - nil, 3898, nil, nil, nil, nil, 117, nil, nil, nil, - nil, 6324, 251, nil, 4642, 4702, 73, nil, 7007, nil, - 126, 4906, 162, 6931, 5146, 4522, 177, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, - nil, nil, nil, 5206, 170, nil, 194, nil, 134, 60, - 5086, nil, nil, 5026, nil, 200, 68, 203, 181, 4, - 4462, nil, 179, 209, 181, 220, 225, nil, 63, nil, - 226, 4138, 232, nil, 6856, nil, nil, 6266, nil, nil, - 6158, nil, nil, nil, 174, -52, 6050, 238, 1750, 237, - nil, nil, nil, nil, 5761, 5650, 247, 188, nil, nil, - nil, 5596, 82, 161, nil, 838, 265, 245, nil, 271, - 272, nil, nil, nil, 274, 279, 281, nil, 778, nil, - nil, nil, 268, 287, nil, nil, 718, nil, nil, nil, - nil, nil, nil, nil, nil, nil, nil, nil, 310, nil, - 226, 82, nil, nil, nil, 297, nil, nil, 304, nil, - 308, nil, 312, nil, 313, nil, nil, nil, nil, nil, - nil ] + 220, 256, 1054, 1114, 1174, 48, 1283, nil, nil, nil, + nil, nil, 1402, 1462, 1546, nil, nil, nil, nil, nil, + 1606, nil, 201, 202, nil, 1786, nil, nil, 267, 1906, + 246, 2026, 2086, 2146, 2206, 2266, 2326, 2386, 2446, 2506, + 2590, 2650, 2710, 2770, 2830, 2890, 2950, 3010, 3070, 3130, + 3190, 3250, 3310, 3370, 3430, 3490, 3550, 3610, 3670, 3730, + 3790, 217, 248, 3970, 4030, 245, 238, 4210, 219, nil, + nil, nil, 1550, -1, 614, 938, 203, nil, 220, 55, + 4822, 5562, nil, 5488, 5431, 200, 195, nil, 186, nil, + nil, nil, nil, nil, nil, nil, 173, nil, 170, nil, + 90, nil, nil, 166, nil, 14, nil, nil, 170, 4882, + 60, 5656, 4762, nil, 135, nil, nil, nil, nil, 84, + 79, 61, 266, 5995, 153, 4090, 5, 3, 2, 3910, + 5302, 1966, nil, nil, -9, 82, 108, 287, -2, 4, + -4, -1, nil, nil, nil, nil, nil, nil, 6042, 61, + 1346, 2, 350, 5505, 6253, 5579, 5864, 6012, 6181, 5736, + 6396, 6450, 6467, 6325, 6521, 6161, 6136, 5894, 5847, nil, + nil, 406, 231, 209, nil, 6691, 5609, 202, nil, 276, + 239, 5719, nil, 241, nil, 120, nil, nil, 262, nil, + 230, nil, nil, nil, nil, nil, 217, 189, -24, 204, + -7, nil, 1258, 1342, 1846, 170, 132, 4379, -28, 4150, + 21, 55, nil, 4354, 4498, nil, nil, nil, nil, 4558, + nil, nil, nil, nil, 92, nil, nil, nil, nil, 101, + 119, 6561, 155, 6615, 4942, 5002, 5122, nil, 3850, 5242, + 181, nil, 170, nil, 185, 187, nil, nil, nil, nil, + nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, + nil, 195, 5182, 6637, 200, nil, 93, nil, 200, 206, + nil, 149, 30, 5062, nil, nil, 4678, 4618, nil, 222, + 83, 226, 204, 9, 4438, nil, 4270, 237, nil, 5405, + nil, nil, nil, nil, nil, nil, nil, nil, nil, 178, + 58, 238, nil, nil, 6089, 6233, 5374, 6305, 6379, 260, + 1726, 203, -8, nil, nil, 263, 1666, nil, nil, nil, + 251, nil, nil, nil, 5941, 13, 118, nil, 994, 274, + 251, nil, 276, 277, nil, nil, 277, nil, nil, 934, + nil, nil, nil, 282, 253, nil, nil, nil, nil, nil, + 287, nil, nil, nil, nil, 610, nil, 346, 178, nil, + 300, nil, nil, 58, nil, 304, nil, 306, nil, 307, + nil, nil, nil, 278, nil, nil, nil, nil ] racc_action_default = [ - -3, -251, -1, -2, -4, -5, -8, -10, -11, -16, - -109, -251, -251, -251, -251, -46, -47, -48, -49, -50, - -51, -52, -53, -54, -55, -56, -57, -58, -59, -60, - -61, -62, -63, -64, -65, -66, -67, -68, -69, -74, - -75, -79, -251, -251, -251, -251, -251, -120, -122, -251, - -251, -177, -178, -179, -180, -181, -251, -251, -251, -194, - -195, -196, -197, -198, -251, -200, -251, -213, -216, -251, - -221, -222, -223, -224, -225, -226, -227, -251, -251, -7, - -251, -251, -251, -251, -251, -251, -251, -251, -251, -251, - -251, -251, -251, -251, -251, -251, -251, -251, -251, -251, - -251, -251, -251, -251, -251, -251, -251, -251, -251, -129, - -124, -250, -250, -28, -251, -29, -36, -251, -251, -76, - -251, -251, -251, -251, -86, -251, -251, -251, -251, -251, - -250, -139, -160, -161, -121, -250, -228, -148, -150, -151, - -152, -153, -154, -156, -157, -44, -251, -184, -251, -187, - -251, -251, -190, -191, -204, -199, -251, -207, -251, -211, - -251, -251, -251, 431, -6, -9, -12, -13, -14, -15, - -251, -18, -19, -20, -21, -22, -23, -24, -25, -26, - -27, -30, -31, -32, -33, -34, -35, -37, -38, -39, - -40, -41, -251, -42, -104, -251, -80, -251, -239, -245, - -233, -230, -228, -118, -130, -222, -133, -226, -251, -236, - -234, -242, -178, -179, -180, -181, -224, -225, -232, -237, - -238, -240, -241, -243, -129, -128, -251, -127, -251, -43, - -228, -71, -81, -251, -84, -228, -165, -167, -168, -169, - -170, -172, -251, -251, -175, -251, -78, -251, -251, -251, - -129, -251, -230, -250, -162, -251, -251, -250, -229, -251, - -251, -182, -251, -185, -251, -188, -251, -201, -202, -203, - -205, -251, -208, -209, -210, -212, -228, -214, -217, -219, - -220, -109, -251, -17, -251, -251, -228, -106, -129, -117, - -251, -231, -251, -229, -251, -251, -228, -132, -134, -233, - -234, -235, -236, -239, -242, -244, -245, -246, -247, -248, - -249, -125, -126, -229, -251, -73, -251, -83, -251, -229, - -251, -173, -174, -251, -77, -251, -89, -251, -95, -251, - -251, -99, -230, -228, -230, -251, -251, -142, -251, -163, - -228, -250, -251, -158, -251, -149, -155, -45, -183, -186, - -193, -189, -192, -206, -251, -251, -108, -251, -229, -228, - -112, -119, -113, -131, -135, -136, -251, -70, -82, -85, - -166, -171, -251, -89, -88, -251, -251, -95, -94, -251, - -251, -103, -98, -100, -251, -251, -251, -115, -250, -143, - -144, -145, -251, -251, -140, -141, -251, -159, -215, -218, - -105, -107, -116, -123, -72, -176, -87, -90, -251, -93, - -251, -251, -110, -111, -114, -251, -164, -137, -251, -147, - -251, -92, -251, -97, -251, -102, -138, -146, -91, -96, - -101 ] + -3, -241, -1, -2, -4, -5, -8, -10, -16, -21, + -241, -241, -241, -33, -34, -241, -241, -241, -241, -61, + -62, -63, -64, -65, -66, -67, -68, -69, -70, -71, + -72, -73, -74, -75, -76, -77, -78, -79, -80, -81, + -86, -90, -241, -241, -241, -241, -241, -174, -175, -176, + -177, -178, -241, -241, -241, -189, -190, -191, -192, -193, + -241, -195, -241, -208, -211, -241, -216, -217, -241, -241, + -7, -241, -241, -241, -241, -241, -241, -241, -241, -126, + -241, -241, -241, -241, -241, -241, -241, -241, -241, -241, + -241, -241, -241, -241, -241, -241, -241, -241, -241, -241, + -241, -241, -121, -240, -240, -22, -23, -241, -240, -136, + -157, -158, -46, -241, -47, -54, -241, -87, -241, -241, + -241, -241, -97, -241, -241, -240, -218, -145, -147, -148, + -149, -150, -151, -153, -154, -14, -218, -180, -218, -182, + -241, -185, -186, -241, -194, -241, -199, -202, -241, -206, + -241, -241, -241, 418, -6, -9, -11, -12, -13, -17, + -18, -19, -20, -241, -218, -241, -79, -80, -81, -229, + -235, -223, -128, -131, -241, -226, -224, -232, -175, -176, + -177, -178, -222, -227, -228, -230, -231, -233, -59, -241, + -36, -37, -38, -39, -40, -41, -42, -43, -44, -45, + -48, -49, -50, -51, -52, -53, -55, -56, -241, -57, + -115, -241, -218, -83, -91, -126, -125, -241, -124, -241, + -220, -241, -28, -240, -159, -241, -58, -92, -241, -95, + -218, -162, -164, -165, -166, -167, -169, -241, -241, -172, + -241, -89, -241, -241, -241, -241, -240, -219, -241, -219, + -241, -241, -183, -241, -241, -196, -197, -198, -200, -241, + -203, -204, -205, -207, -218, -209, -212, -214, -215, -8, + -241, -126, -241, -219, -241, -241, -241, -35, -241, -241, + -218, -117, -241, -85, -218, -241, -130, -223, -224, -225, + -226, -229, -232, -234, -235, -236, -237, -238, -239, -122, + -123, -241, -221, -126, -241, -139, -241, -160, -218, -241, + -94, -241, -219, -241, -170, -171, -241, -241, -88, -241, + -100, -241, -106, -241, -241, -110, -240, -241, -155, -241, + -146, -152, -15, -179, -181, -184, -187, -188, -201, -241, + -241, -218, -26, -129, -127, -132, -133, -60, -119, -241, + -219, -82, -241, -25, -29, -218, -240, -140, -141, -142, + -241, -93, -96, -163, -168, -241, -100, -99, -241, -241, + -106, -105, -241, -241, -109, -111, -241, -137, -138, -241, + -156, -210, -213, -241, -30, -116, -118, -84, -120, -27, + -241, -161, -173, -98, -101, -241, -104, -241, -240, -134, + -241, -144, -24, -31, -135, -241, -103, -241, -108, -241, + -113, -114, -143, -220, -102, -107, -112, -32 ] racc_goto_table = [ - 2, 113, 115, 116, 117, 119, 139, 141, 254, 135, - 155, 244, 202, 254, 278, 378, 201, 194, 290, 361, - 345, 235, 331, 393, 79, 374, 253, 166, 167, 168, - 169, 256, 123, 125, 126, 127, 252, 286, 146, 148, - 226, 228, 288, 380, 330, 363, 145, 145, 150, 407, - 244, 232, 193, 336, 154, 389, 342, 165, 346, 161, - 276, 370, 322, 321, 409, 352, 257, 170, 272, 335, - 415, 273, 406, 3, 270, 145, 171, 172, 173, 174, - 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, - 185, 186, 187, 188, 189, 190, 191, 192, 164, 197, - 230, 225, 225, 383, 245, 287, 397, 271, 145, 269, - 157, 159, 145, 274, 1, nil, nil, nil, nil, 197, - nil, nil, nil, nil, nil, nil, nil, 296, nil, nil, - nil, 337, 292, nil, nil, 343, nil, nil, nil, nil, - nil, nil, nil, nil, nil, 244, nil, nil, nil, 384, - nil, 386, 281, 333, nil, 340, nil, 332, 334, 275, - 314, nil, 282, nil, nil, 318, nil, 139, 141, 135, + 2, 112, 114, 115, 117, 116, 224, 220, 125, 129, + 131, 189, 210, 144, 301, 266, 330, 230, 367, 325, + 376, 239, 409, 224, 246, 164, 324, 70, 121, 123, + 124, 280, 223, 394, 250, 343, 251, 105, 106, 135, + 135, 143, 227, 136, 138, 209, 371, 146, 264, 245, + 390, 151, 239, 217, 219, 354, 304, 357, 155, 156, + 157, 158, 272, 327, 393, 163, 188, 190, 191, 192, + 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, + 203, 204, 205, 206, 207, 208, 383, 135, 331, 216, + 216, 212, 154, 221, 396, 363, 315, 314, 380, 375, + 336, 260, 159, 160, 161, 162, 261, 135, 3, 258, + 282, 240, 259, 257, 147, 149, 262, 1, nil, nil, + nil, 305, nil, 308, 281, nil, nil, 239, 311, nil, + nil, nil, nil, nil, nil, nil, nil, nil, 125, 269, + 129, 131, nil, nil, 328, nil, nil, nil, nil, 263, + nil, 114, 270, nil, nil, 121, 123, 124, nil, nil, + nil, 284, 339, nil, nil, nil, nil, nil, nil, nil, + nil, nil, nil, nil, nil, nil, nil, 283, 349, nil, + nil, nil, 352, nil, nil, nil, nil, nil, nil, nil, + nil, nil, nil, nil, nil, nil, nil, 208, nil, nil, + nil, nil, nil, nil, 382, nil, 360, 417, nil, nil, + 129, 131, 338, nil, 239, nil, nil, 341, nil, nil, + nil, nil, nil, nil, 378, nil, nil, nil, 309, nil, + 188, nil, nil, nil, nil, nil, 332, nil, nil, 384, + 143, 337, 319, 321, nil, nil, 146, 365, nil, 355, + nil, nil, nil, 389, 378, nil, nil, nil, nil, nil, + 344, 345, 346, 386, 347, 348, nil, nil, nil, 358, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, - nil, nil, nil, nil, nil, 192, nil, nil, 123, 125, - 126, 359, nil, nil, nil, nil, 315, nil, nil, nil, - nil, nil, nil, nil, nil, nil, 354, nil, 399, 244, - nil, nil, nil, nil, 139, 141, 357, 353, nil, 395, - nil, nil, nil, nil, nil, nil, 366, nil, nil, nil, - nil, nil, nil, 316, nil, nil, nil, nil, nil, 145, - 197, 197, nil, nil, nil, nil, nil, 325, 327, nil, - 347, nil, 347, nil, 350, nil, 150, nil, nil, nil, - nil, 154, nil, 385, nil, nil, 395, nil, 401, nil, - 392, nil, nil, nil, 347, 356, nil, nil, nil, nil, - nil, 197, nil, nil, 364, 365, nil, nil, nil, 402, - nil, nil, nil, nil, nil, nil, nil, nil, 390, nil, - 139, 141, nil, 347, nil, 372, nil, nil, nil, nil, - 371, nil, nil, 145, nil, nil, nil, nil, nil, nil, - 145, nil, nil, nil, nil, nil, nil, nil, nil, nil, - nil, nil, 404, nil, nil, nil, nil, nil, nil, nil, - nil, nil, nil, nil, nil, nil, nil, nil, 192, nil, + nil, nil, nil, nil, nil, nil, nil, nil, 221, nil, + nil, nil, 129, 131, nil, nil, 410, nil, nil, 364, + nil, nil, 188, 413, 332, nil, nil, nil, nil, nil, + 188, nil, nil, nil, nil, 387, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, - nil, nil, nil, nil, nil, 123, nil, nil, nil, nil, + nil, nil, nil, nil, nil, nil, 208, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, + nil, nil, nil, nil, 121, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, - nil, nil, nil, nil, nil, nil, 418, nil, nil, nil, - nil, nil, nil, nil, nil, nil, nil, nil, 420, nil, - 422, 424 ] + nil, nil, nil, nil, nil, nil, nil, nil, nil, 400, + nil, nil, nil, nil, nil, nil, nil, nil, nil, 221, + nil, nil, nil, nil, nil, 405, nil, 407, 411 ] racc_goto_check = [ - 2, 9, 9, 9, 9, 39, 37, 31, 61, 65, - 88, 34, 56, 61, 96, 47, 54, 51, 55, 57, - 73, 44, 49, 67, 5, 46, 66, 7, 7, 7, - 7, 66, 9, 9, 9, 9, 54, 52, 11, 11, - 60, 60, 58, 50, 48, 62, 9, 9, 9, 45, - 34, 43, 12, 69, 9, 70, 72, 6, 75, 9, - 44, 76, 77, 79, 47, 83, 38, 11, 84, 55, - 67, 85, 46, 3, 89, 9, 9, 9, 9, 9, - 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, - 9, 9, 9, 9, 9, 9, 9, 9, 5, 9, - 11, 9, 9, 49, 11, 51, 73, 90, 9, 92, - 93, 94, 9, 95, 1, nil, nil, nil, nil, 9, - nil, nil, nil, nil, nil, nil, nil, 56, nil, nil, - nil, 61, 38, nil, nil, 61, nil, nil, nil, nil, - nil, nil, nil, nil, nil, 34, nil, nil, nil, 55, - nil, 55, 9, 56, nil, 44, nil, 54, 54, 2, - 38, nil, 2, nil, nil, 38, nil, 37, 31, 65, + 2, 10, 10, 10, 37, 6, 49, 13, 57, 35, + 34, 19, 50, 80, 14, 88, 65, 42, 44, 47, + 59, 36, 48, 49, 15, 11, 46, 5, 10, 10, + 10, 51, 58, 43, 15, 54, 15, 9, 9, 6, + 6, 6, 41, 8, 8, 20, 45, 6, 42, 58, + 59, 10, 36, 53, 53, 16, 61, 62, 6, 6, + 6, 6, 15, 64, 44, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 12, 6, 67, 10, + 10, 8, 5, 10, 45, 68, 69, 71, 65, 47, + 75, 76, 9, 9, 9, 9, 77, 6, 3, 81, + 15, 8, 82, 84, 85, 86, 87, 1, nil, nil, + nil, 49, nil, 42, 50, nil, nil, 36, 15, nil, + nil, nil, nil, nil, nil, nil, nil, nil, 57, 6, + 35, 34, nil, nil, 49, nil, nil, nil, nil, 2, + nil, 10, 2, nil, nil, 10, 10, 10, nil, nil, + nil, 11, 15, nil, nil, nil, nil, nil, nil, nil, + nil, nil, nil, nil, nil, nil, nil, 37, 15, nil, + nil, nil, 15, nil, nil, nil, nil, nil, nil, nil, + nil, nil, nil, nil, nil, nil, nil, 10, nil, nil, + nil, nil, nil, nil, 88, nil, 15, 14, nil, nil, + 35, 34, 80, nil, 36, nil, nil, 11, nil, nil, + nil, nil, nil, nil, 49, nil, nil, nil, 2, nil, + 10, nil, nil, nil, nil, nil, 6, nil, nil, 15, + 6, 6, 2, 2, nil, nil, 6, 19, nil, 11, + nil, nil, nil, 15, 49, nil, nil, nil, nil, nil, + 10, 10, 10, 50, 10, 10, nil, nil, nil, 57, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, - nil, nil, nil, nil, nil, 9, nil, nil, 9, 9, - 9, 56, nil, nil, nil, nil, 39, nil, nil, nil, - nil, nil, nil, nil, nil, nil, 38, nil, 96, 34, - nil, nil, nil, nil, 37, 31, 38, 88, nil, 61, - nil, nil, nil, nil, nil, nil, 38, nil, nil, nil, - nil, nil, nil, 2, nil, nil, nil, nil, nil, 9, - 9, 9, nil, nil, nil, nil, nil, 2, 2, nil, - 9, nil, 9, nil, 9, nil, 9, nil, nil, nil, - nil, 9, nil, 38, nil, nil, 61, nil, 51, nil, - 38, nil, nil, nil, 9, 9, nil, nil, nil, nil, - nil, 9, nil, nil, 9, 9, nil, nil, nil, 38, - nil, nil, nil, nil, nil, nil, nil, nil, 65, nil, - 37, 31, nil, 9, nil, 11, nil, nil, nil, nil, - 9, nil, nil, 9, nil, nil, nil, nil, nil, nil, - 9, nil, nil, nil, nil, nil, nil, nil, nil, nil, - nil, nil, 39, nil, nil, nil, nil, nil, nil, nil, - nil, nil, nil, nil, nil, nil, nil, nil, 9, nil, + nil, nil, nil, nil, nil, nil, nil, nil, 10, nil, + nil, nil, 35, 34, nil, nil, 49, nil, nil, 10, + nil, nil, 10, 13, 6, nil, nil, nil, nil, nil, + 10, nil, nil, nil, nil, 37, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, - nil, nil, nil, nil, nil, 9, 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, 10, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, - nil, nil, nil, nil, nil, nil, 2, nil, nil, nil, - nil, nil, nil, nil, nil, nil, nil, nil, 2, nil, - 2, 2 ] + nil, nil, nil, nil, nil, nil, nil, nil, nil, 2, + nil, nil, nil, nil, nil, nil, nil, nil, nil, 10, + nil, nil, nil, nil, nil, 2, nil, 2, 2 ] racc_goto_pointer = [ - nil, 114, 0, 73, nil, 20, -23, -54, nil, -10, - nil, -18, -55, nil, nil, nil, nil, nil, nil, nil, - nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, - nil, -43, nil, nil, -110, nil, nil, -44, -70, -35, - nil, nil, nil, -69, -100, -326, -301, -313, -205, -227, - -286, -90, -158, nil, -93, -183, -97, -272, -155, nil, - -71, -122, -248, nil, nil, -40, -104, -318, nil, -200, - -283, nil, -201, -238, nil, -201, -258, -181, nil, -179, - nil, nil, nil, -201, -90, -87, nil, nil, -54, -82, - -49, nil, -47, 44, 44, -46, -147 ] + nil, 117, 0, 108, nil, 23, -13, nil, -9, 27, + -14, -54, -255, -100, -206, -102, -247, nil, nil, -69, + -54, nil, nil, nil, nil, nil, nil, nil, nil, nil, + nil, nil, nil, nil, -36, -37, -98, -36, nil, nil, + nil, -76, -102, -335, -302, -276, -218, -225, -376, -102, + -87, -180, nil, -50, -238, nil, nil, -37, -76, -306, + nil, -167, -249, nil, -183, -231, nil, -160, -217, -142, + nil, -140, nil, nil, nil, -153, -47, -42, nil, nil, + -47, -36, -33, nil, -32, 52, 52, -33, -136 ] racc_goto_default = [ - nil, nil, 394, nil, 4, 5, 6, 7, 8, 10, - 9, 329, nil, 15, 39, 16, 17, 18, 19, 20, - 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, - 31, 32, 33, 34, 35, 36, 37, 38, nil, nil, - 40, 41, 120, nil, nil, 124, nil, nil, nil, nil, - nil, nil, nil, 45, nil, nil, nil, 203, nil, 110, - nil, 227, 204, 208, 206, 131, nil, nil, 130, nil, - nil, 136, nil, 137, 138, 142, 236, 237, 238, 239, - 240, 243, 151, 153, 59, 60, 61, 64, nil, nil, - nil, 156, nil, nil, nil, nil, nil ] + nil, nil, 377, nil, 4, 5, 6, 7, nil, 8, + 9, nil, nil, nil, nil, nil, 222, 13, 14, 323, + nil, 19, 20, 21, 22, 23, 24, 25, 26, 27, + 28, 29, 30, 31, 32, 33, 34, nil, 40, 41, + 118, nil, nil, 122, nil, nil, nil, nil, nil, 218, + nil, nil, 102, nil, 172, 174, 173, 109, nil, nil, + 108, nil, nil, 126, nil, 127, 128, 132, 231, 232, + 233, 234, 235, 238, 140, 142, 55, 56, 57, 60, + nil, nil, nil, 145, nil, nil, nil, nil, nil ] racc_reduce_table = [ 0, 0, :racc_error, - 1, 95, :_reduce_1, - 1, 95, :_reduce_2, - 0, 95, :_reduce_3, - 1, 96, :_reduce_4, - 1, 98, :_reduce_5, - 3, 98, :_reduce_6, - 2, 98, :_reduce_7, - 1, 99, :_reduce_8, - 3, 99, :_reduce_9, + 1, 92, :_reduce_1, + 1, 92, :_reduce_2, + 0, 92, :_reduce_3, + 1, 93, :_reduce_4, + 1, 95, :_reduce_5, + 3, 95, :_reduce_6, + 2, 95, :_reduce_7, + 1, 96, :_reduce_8, + 3, 96, :_reduce_9, + 1, 97, :_reduce_none, + 3, 97, :_reduce_11, + 3, 97, :_reduce_12, + 3, 97, :_reduce_13, + 1, 99, :_reduce_14, + 3, 99, :_reduce_15, + 1, 98, :_reduce_none, + 3, 98, :_reduce_17, + 3, 98, :_reduce_18, + 3, 98, :_reduce_19, + 3, 98, :_reduce_20, 1, 100, :_reduce_none, - 1, 101, :_reduce_11, - 3, 101, :_reduce_12, - 3, 101, :_reduce_13, - 3, 101, :_reduce_14, - 3, 101, :_reduce_15, - 1, 103, :_reduce_none, - 4, 103, :_reduce_17, - 3, 103, :_reduce_18, - 3, 103, :_reduce_19, - 3, 103, :_reduce_20, - 3, 103, :_reduce_21, - 3, 103, :_reduce_22, - 3, 103, :_reduce_23, - 3, 103, :_reduce_24, - 3, 103, :_reduce_25, - 3, 103, :_reduce_26, - 3, 103, :_reduce_27, - 2, 103, :_reduce_28, - 2, 103, :_reduce_29, - 3, 103, :_reduce_30, - 3, 103, :_reduce_31, - 3, 103, :_reduce_32, - 3, 103, :_reduce_33, - 3, 103, :_reduce_34, - 3, 103, :_reduce_35, - 2, 103, :_reduce_36, - 3, 103, :_reduce_37, - 3, 103, :_reduce_38, - 3, 103, :_reduce_39, - 3, 103, :_reduce_40, - 3, 103, :_reduce_41, - 3, 103, :_reduce_42, - 3, 103, :_reduce_43, - 1, 105, :_reduce_44, - 3, 105, :_reduce_45, - 1, 104, :_reduce_none, + 2, 100, :_reduce_22, + 2, 100, :_reduce_23, + 7, 100, :_reduce_24, + 5, 100, :_reduce_25, + 5, 100, :_reduce_26, + 4, 107, :_reduce_27, + 1, 104, :_reduce_28, + 3, 104, :_reduce_29, + 1, 103, :_reduce_30, + 2, 103, :_reduce_31, + 4, 103, :_reduce_32, + 1, 101, :_reduce_none, + 1, 101, :_reduce_none, + 4, 101, :_reduce_35, + 3, 101, :_reduce_36, + 3, 101, :_reduce_37, + 3, 101, :_reduce_38, + 3, 101, :_reduce_39, + 3, 101, :_reduce_40, + 3, 101, :_reduce_41, + 3, 101, :_reduce_42, + 3, 101, :_reduce_43, + 3, 101, :_reduce_44, + 3, 101, :_reduce_45, + 2, 101, :_reduce_46, + 2, 101, :_reduce_47, + 3, 101, :_reduce_48, + 3, 101, :_reduce_49, + 3, 101, :_reduce_50, + 3, 101, :_reduce_51, + 3, 101, :_reduce_52, + 3, 101, :_reduce_53, + 2, 101, :_reduce_54, + 3, 101, :_reduce_55, + 3, 101, :_reduce_56, + 3, 101, :_reduce_57, + 3, 101, :_reduce_58, + 1, 110, :_reduce_59, + 3, 110, :_reduce_60, 1, 108, :_reduce_none, 1, 108, :_reduce_none, 1, 108, :_reduce_none, 1, 108, :_reduce_none, 1, 108, :_reduce_none, 1, 108, :_reduce_none, 1, 108, :_reduce_none, 1, 108, :_reduce_none, 1, 108, :_reduce_none, 1, 108, :_reduce_none, 1, 108, :_reduce_none, 1, 108, :_reduce_none, - 1, 109, :_reduce_none, - 1, 109, :_reduce_none, - 1, 109, :_reduce_none, - 1, 109, :_reduce_none, - 1, 109, :_reduce_none, - 1, 109, :_reduce_none, - 1, 109, :_reduce_none, - 1, 109, :_reduce_none, - 1, 109, :_reduce_none, - 1, 126, :_reduce_68, - 1, 126, :_reduce_69, - 5, 107, :_reduce_70, - 3, 107, :_reduce_71, - 6, 107, :_reduce_72, - 4, 107, :_reduce_73, - 1, 107, :_reduce_74, - 1, 111, :_reduce_75, - 2, 111, :_reduce_76, - 4, 134, :_reduce_77, - 3, 134, :_reduce_78, - 1, 134, :_reduce_79, - 3, 135, :_reduce_80, - 2, 133, :_reduce_81, - 3, 137, :_reduce_82, - 2, 137, :_reduce_83, - 2, 136, :_reduce_84, - 4, 136, :_reduce_85, - 2, 114, :_reduce_86, - 5, 139, :_reduce_87, - 4, 139, :_reduce_88, - 0, 140, :_reduce_none, - 2, 140, :_reduce_90, - 4, 140, :_reduce_91, - 3, 140, :_reduce_92, - 6, 115, :_reduce_93, - 5, 115, :_reduce_94, - 0, 141, :_reduce_none, - 4, 141, :_reduce_96, - 3, 141, :_reduce_97, - 5, 113, :_reduce_98, - 1, 142, :_reduce_99, - 2, 142, :_reduce_100, - 5, 143, :_reduce_101, - 4, 143, :_reduce_102, - 1, 144, :_reduce_103, - 1, 106, :_reduce_none, - 4, 106, :_reduce_105, - 1, 146, :_reduce_106, - 3, 146, :_reduce_107, - 3, 145, :_reduce_108, - 1, 102, :_reduce_109, - 6, 102, :_reduce_110, - 6, 102, :_reduce_111, - 5, 102, :_reduce_112, - 5, 102, :_reduce_113, - 6, 102, :_reduce_114, - 5, 102, :_reduce_115, - 4, 151, :_reduce_116, - 1, 152, :_reduce_117, - 1, 148, :_reduce_118, - 3, 148, :_reduce_119, - 1, 147, :_reduce_120, - 2, 147, :_reduce_121, - 1, 147, :_reduce_122, - 6, 112, :_reduce_123, - 2, 112, :_reduce_124, - 3, 153, :_reduce_125, - 3, 153, :_reduce_126, - 1, 154, :_reduce_none, - 1, 154, :_reduce_none, - 0, 150, :_reduce_129, - 1, 150, :_reduce_130, - 3, 150, :_reduce_131, - 1, 157, :_reduce_none, - 1, 157, :_reduce_none, - 1, 157, :_reduce_none, - 3, 156, :_reduce_135, - 3, 156, :_reduce_136, - 6, 116, :_reduce_137, - 7, 117, :_reduce_138, - 1, 162, :_reduce_139, - 1, 161, :_reduce_none, - 1, 161, :_reduce_none, - 1, 163, :_reduce_none, - 2, 163, :_reduce_143, - 1, 164, :_reduce_none, - 1, 164, :_reduce_none, - 7, 118, :_reduce_146, - 6, 118, :_reduce_147, - 1, 165, :_reduce_148, - 3, 165, :_reduce_149, - 1, 167, :_reduce_150, - 1, 167, :_reduce_151, - 1, 167, :_reduce_152, - 1, 167, :_reduce_none, - 1, 168, :_reduce_154, - 3, 168, :_reduce_155, - 1, 169, :_reduce_none, - 1, 169, :_reduce_none, - 1, 166, :_reduce_none, - 2, 166, :_reduce_159, - 1, 159, :_reduce_160, - 1, 159, :_reduce_161, - 1, 160, :_reduce_162, - 2, 160, :_reduce_163, - 4, 160, :_reduce_164, - 1, 138, :_reduce_165, - 3, 138, :_reduce_166, - 1, 170, :_reduce_none, - 1, 170, :_reduce_none, - 1, 171, :_reduce_none, - 1, 171, :_reduce_none, - 3, 173, :_reduce_171, - 1, 173, :_reduce_172, - 2, 174, :_reduce_173, - 2, 172, :_reduce_174, - 1, 175, :_reduce_175, - 4, 175, :_reduce_176, - 1, 110, :_reduce_177, - 1, 120, :_reduce_178, - 1, 120, :_reduce_179, - 1, 120, :_reduce_180, - 1, 120, :_reduce_181, - 3, 121, :_reduce_182, - 4, 121, :_reduce_183, - 2, 121, :_reduce_184, - 3, 121, :_reduce_185, - 4, 121, :_reduce_186, - 2, 121, :_reduce_187, - 3, 124, :_reduce_188, - 4, 124, :_reduce_189, - 2, 124, :_reduce_190, - 1, 176, :_reduce_191, - 3, 176, :_reduce_192, - 3, 177, :_reduce_193, - 1, 131, :_reduce_none, - 1, 131, :_reduce_none, - 1, 131, :_reduce_none, - 1, 178, :_reduce_197, - 1, 178, :_reduce_198, - 2, 179, :_reduce_199, - 1, 181, :_reduce_200, - 1, 183, :_reduce_201, - 1, 184, :_reduce_202, - 2, 182, :_reduce_203, - 1, 185, :_reduce_204, - 1, 186, :_reduce_205, - 2, 186, :_reduce_206, - 2, 180, :_reduce_207, - 2, 187, :_reduce_208, - 2, 187, :_reduce_209, - 3, 97, :_reduce_210, - 0, 189, :_reduce_none, - 1, 189, :_reduce_none, - 0, 188, :_reduce_213, - 2, 188, :_reduce_214, - 4, 188, :_reduce_215, - 1, 119, :_reduce_216, - 3, 119, :_reduce_217, - 5, 119, :_reduce_218, - 1, 190, :_reduce_none, - 1, 190, :_reduce_none, - 1, 127, :_reduce_221, - 1, 130, :_reduce_222, - 1, 128, :_reduce_223, - 1, 129, :_reduce_224, - 1, 123, :_reduce_225, - 1, 122, :_reduce_226, - 1, 125, :_reduce_227, - 0, 132, :_reduce_none, - 1, 132, :_reduce_229, - 0, 149, :_reduce_none, - 1, 149, :_reduce_none, - 1, 158, :_reduce_none, - 1, 158, :_reduce_none, - 1, 158, :_reduce_none, - 1, 158, :_reduce_none, - 1, 158, :_reduce_none, - 1, 158, :_reduce_none, - 1, 158, :_reduce_none, - 1, 158, :_reduce_none, - 1, 158, :_reduce_none, - 1, 158, :_reduce_none, - 1, 158, :_reduce_none, - 1, 158, :_reduce_none, - 1, 158, :_reduce_none, - 1, 158, :_reduce_none, - 1, 158, :_reduce_none, - 1, 158, :_reduce_none, + 1, 108, :_reduce_none, + 1, 108, :_reduce_none, + 1, 108, :_reduce_none, + 1, 108, :_reduce_none, + 1, 108, :_reduce_77, + 1, 108, :_reduce_78, + 1, 108, :_reduce_79, + 1, 108, :_reduce_80, + 1, 108, :_reduce_81, + 5, 109, :_reduce_82, + 3, 109, :_reduce_83, + 6, 109, :_reduce_84, + 4, 109, :_reduce_85, + 1, 113, :_reduce_86, + 2, 113, :_reduce_87, + 4, 129, :_reduce_88, + 3, 129, :_reduce_89, + 1, 129, :_reduce_90, + 3, 130, :_reduce_91, + 2, 128, :_reduce_92, + 3, 132, :_reduce_93, + 2, 132, :_reduce_94, + 2, 131, :_reduce_95, + 4, 131, :_reduce_96, + 2, 116, :_reduce_97, + 5, 134, :_reduce_98, + 4, 134, :_reduce_99, + 0, 135, :_reduce_none, + 2, 135, :_reduce_101, + 4, 135, :_reduce_102, + 3, 135, :_reduce_103, + 6, 117, :_reduce_104, + 5, 117, :_reduce_105, + 0, 136, :_reduce_none, + 4, 136, :_reduce_107, + 3, 136, :_reduce_108, + 5, 115, :_reduce_109, + 1, 137, :_reduce_110, + 2, 137, :_reduce_111, + 5, 138, :_reduce_112, + 1, 139, :_reduce_none, + 1, 139, :_reduce_none, + 1, 111, :_reduce_none, + 4, 111, :_reduce_116, + 1, 142, :_reduce_117, + 3, 142, :_reduce_118, + 3, 141, :_reduce_119, + 6, 114, :_reduce_120, + 2, 114, :_reduce_121, + 3, 143, :_reduce_122, + 3, 143, :_reduce_123, + 1, 144, :_reduce_none, + 1, 144, :_reduce_none, + 0, 102, :_reduce_126, + 3, 102, :_reduce_127, + 1, 102, :_reduce_128, + 3, 102, :_reduce_129, + 1, 146, :_reduce_none, + 1, 146, :_reduce_none, + 3, 145, :_reduce_132, + 3, 145, :_reduce_133, + 6, 118, :_reduce_134, + 7, 119, :_reduce_135, + 1, 151, :_reduce_136, + 1, 150, :_reduce_none, + 1, 150, :_reduce_none, + 1, 152, :_reduce_none, + 2, 152, :_reduce_140, + 1, 153, :_reduce_none, + 1, 153, :_reduce_none, + 7, 120, :_reduce_143, + 6, 120, :_reduce_144, + 1, 154, :_reduce_145, + 3, 154, :_reduce_146, + 1, 156, :_reduce_none, + 1, 156, :_reduce_none, + 1, 156, :_reduce_149, + 1, 156, :_reduce_none, + 1, 157, :_reduce_151, + 3, 157, :_reduce_152, 1, 158, :_reduce_none, 1, 158, :_reduce_none, - 0, 155, :_reduce_250 ] - -racc_reduce_n = 251 - -racc_shift_n = 431 + 1, 155, :_reduce_none, + 2, 155, :_reduce_156, + 1, 148, :_reduce_none, + 1, 148, :_reduce_158, + 1, 149, :_reduce_159, + 2, 149, :_reduce_160, + 4, 149, :_reduce_161, + 1, 133, :_reduce_162, + 3, 133, :_reduce_163, + 1, 159, :_reduce_none, + 1, 159, :_reduce_none, + 1, 160, :_reduce_none, + 1, 160, :_reduce_none, + 3, 162, :_reduce_168, + 1, 162, :_reduce_169, + 2, 163, :_reduce_170, + 2, 161, :_reduce_171, + 1, 164, :_reduce_172, + 4, 164, :_reduce_173, + 1, 112, :_reduce_174, + 1, 122, :_reduce_175, + 1, 122, :_reduce_176, + 1, 122, :_reduce_177, + 1, 122, :_reduce_178, + 4, 123, :_reduce_179, + 2, 123, :_reduce_180, + 4, 123, :_reduce_181, + 2, 123, :_reduce_182, + 3, 124, :_reduce_183, + 4, 124, :_reduce_184, + 2, 124, :_reduce_185, + 1, 165, :_reduce_186, + 3, 165, :_reduce_187, + 3, 166, :_reduce_188, + 1, 126, :_reduce_none, + 1, 126, :_reduce_none, + 1, 126, :_reduce_none, + 1, 167, :_reduce_192, + 1, 167, :_reduce_193, + 2, 168, :_reduce_194, + 1, 170, :_reduce_195, + 1, 172, :_reduce_196, + 1, 173, :_reduce_197, + 2, 171, :_reduce_198, + 1, 174, :_reduce_199, + 1, 175, :_reduce_200, + 2, 175, :_reduce_201, + 2, 169, :_reduce_202, + 2, 176, :_reduce_203, + 2, 176, :_reduce_204, + 3, 94, :_reduce_205, + 0, 178, :_reduce_none, + 1, 178, :_reduce_none, + 0, 177, :_reduce_208, + 2, 177, :_reduce_209, + 4, 177, :_reduce_210, + 1, 121, :_reduce_211, + 3, 121, :_reduce_212, + 5, 121, :_reduce_213, + 1, 179, :_reduce_none, + 1, 179, :_reduce_none, + 1, 127, :_reduce_216, + 1, 125, :_reduce_217, + 0, 106, :_reduce_none, + 1, 106, :_reduce_219, + 0, 105, :_reduce_none, + 1, 105, :_reduce_none, + 1, 147, :_reduce_none, + 1, 147, :_reduce_none, + 1, 147, :_reduce_none, + 1, 147, :_reduce_none, + 1, 147, :_reduce_none, + 1, 147, :_reduce_none, + 1, 147, :_reduce_none, + 1, 147, :_reduce_none, + 1, 147, :_reduce_none, + 1, 147, :_reduce_none, + 1, 147, :_reduce_none, + 1, 147, :_reduce_none, + 1, 147, :_reduce_none, + 1, 147, :_reduce_none, + 1, 147, :_reduce_none, + 1, 147, :_reduce_none, + 1, 147, :_reduce_none, + 1, 147, :_reduce_none, + 0, 140, :_reduce_240 ] + +racc_reduce_n = 241 + +racc_shift_n = 418 racc_token_table = { false => 0, :error => 1, :STRING => 2, :DQPRE => 3, :DQMID => 4, :DQPOST => 5, :WORD => 6, :LBRACK => 7, :RBRACK => 8, :LBRACE => 9, :RBRACE => 10, :SYMBOL => 11, :FARROW => 12, :COMMA => 13, :TRUE => 14, :FALSE => 15, :EQUALS => 16, :APPENDS => 17, :DELETES => 18, :LESSEQUAL => 19, :NOTEQUAL => 20, :DOT => 21, :COLON => 22, :LLCOLLECT => 23, :RRCOLLECT => 24, :QMARK => 25, :LPAREN => 26, :RPAREN => 27, :ISEQUAL => 28, :GREATEREQUAL => 29, :GREATERTHAN => 30, :LESSTHAN => 31, :IF => 32, :ELSE => 33, :DEFINE => 34, :ELSIF => 35, :VARIABLE => 36, :CLASS => 37, :INHERITS => 38, :NODE => 39, :BOOLEAN => 40, :NAME => 41, :SEMIC => 42, :CASE => 43, :DEFAULT => 44, :AT => 45, :ATAT => 46, :LCOLLECT => 47, :RCOLLECT => 48, :CLASSREF => 49, :NOT => 50, :OR => 51, :AND => 52, :UNDEF => 53, :PARROW => 54, :PLUS => 55, :MINUS => 56, :TIMES => 57, :DIV => 58, :LSHIFT => 59, :RSHIFT => 60, :UMINUS => 61, :MATCH => 62, :NOMATCH => 63, :REGEX => 64, :IN_EDGE => 65, :OUT_EDGE => 66, :IN_EDGE_SUB => 67, :OUT_EDGE_SUB => 68, :IN => 69, :UNLESS => 70, :PIPE => 71, :LAMBDA => 72, :SELBRACE => 73, :NUMBER => 74, :HEREDOC => 75, :SUBLOCATE => 76, :RENDER_STRING => 77, :RENDER_EXPR => 78, :EPP_START => 79, :EPP_END => 80, :EPP_END_TRIM => 81, :FUNCTION => 82, :PRIVATE => 83, :ATTR => 84, :TYPE => 85, :LOW => 86, :HIGH => 87, - :CALL => 88, - :LISTSTART => 89, - :SPLAT => 90, - :MODULO => 91, - :TITLE_COLON => 92, - :CASE_COLON => 93 } + :LISTSTART => 88, + :SPLAT => 89, + :MODULO => 90 } -racc_nt_base = 94 +racc_nt_base = 91 racc_use_result_var = true Racc_arg = [ racc_action_table, racc_action_check, racc_action_default, racc_action_pointer, racc_goto_table, racc_goto_check, racc_goto_default, racc_goto_pointer, racc_nt_base, racc_reduce_table, racc_token_table, racc_shift_n, racc_reduce_n, racc_use_result_var ] Racc_token_to_s_table = [ "$end", "error", "STRING", "DQPRE", "DQMID", "DQPOST", "WORD", "LBRACK", "RBRACK", "LBRACE", "RBRACE", "SYMBOL", "FARROW", "COMMA", "TRUE", "FALSE", "EQUALS", "APPENDS", "DELETES", "LESSEQUAL", "NOTEQUAL", "DOT", "COLON", "LLCOLLECT", "RRCOLLECT", "QMARK", "LPAREN", "RPAREN", "ISEQUAL", "GREATEREQUAL", "GREATERTHAN", "LESSTHAN", "IF", "ELSE", "DEFINE", "ELSIF", "VARIABLE", "CLASS", "INHERITS", "NODE", "BOOLEAN", "NAME", "SEMIC", "CASE", "DEFAULT", "AT", "ATAT", "LCOLLECT", "RCOLLECT", "CLASSREF", "NOT", "OR", "AND", "UNDEF", "PARROW", "PLUS", "MINUS", "TIMES", "DIV", "LSHIFT", "RSHIFT", "UMINUS", "MATCH", "NOMATCH", "REGEX", "IN_EDGE", "OUT_EDGE", "IN_EDGE_SUB", "OUT_EDGE_SUB", "IN", "UNLESS", "PIPE", "LAMBDA", "SELBRACE", "NUMBER", "HEREDOC", "SUBLOCATE", "RENDER_STRING", "RENDER_EXPR", "EPP_START", "EPP_END", "EPP_END_TRIM", "FUNCTION", "PRIVATE", "ATTR", "TYPE", "LOW", "HIGH", - "CALL", "LISTSTART", "SPLAT", "MODULO", - "TITLE_COLON", - "CASE_COLON", "$start", "program", "statements", "epp_expression", "syntactic_statements", "syntactic_statement", - "any_expression", - "relationship_expression", - "resource_expression", + "assignment", + "relationship", + "assignments", + "resource", "expression", - "higher_precedence", + "attribute_operations", + "additional_resource_bodies", + "resource_bodies", + "endsemi", + "endcomma", + "resource_body", + "primary_expression", + "call_function_expression", "expressions", "selector_entries", - "call_function_expression", - "primary_expression", - "literal_expression", "variable", "call_method_with_lambda_expression", "collection_expression", "case_expression", "if_expression", "unless_expression", "definition_expression", "hostclass_expression", "node_definition_expression", "epp_render_expression", "reserved_word", "array", - "boolean", - "default", "hash", "regex", - "text_or_name", - "number", - "type", - "undef", - "name", "quotedtext", - "endcomma", + "type", "lambda", "call_method_expression", "named_access", "lambda_parameter_list", "lambda_rest", "parameters", "if_part", "else", "unless_else", "case_options", "case_option", - "case_colon", + "options_statements", + "nil", "selector_entry", "selector_entry_list", - "at", - "resourceinstances", - "endsemi", - "attribute_operations", - "resourceinst", - "title_colon", "collect_query", "optional_query", - "nil", "attribute_operation", "attribute_name", "keyword", "classname", "parameter_list", "opt_statements", "stacked_classname", "classparent", "classnameordefault", "hostnames", "nodeparent", "hostname", "dotted_name", "name_or_number", "parameter", "untyped_parameter", "typed_parameter", "regular_parameter", "splat_parameter", "parameter_type", "hashpairs", "hashpair", "string", "dq_string", "heredoc", "dqpre", "dqrval", "dqpost", "dqmid", "text_expression", "dqtail", "sublocated_text", "epp_parameters_list", "optional_statements", "epp_end" ] Racc_debug_parser = false ##### State transition tables end ##### # reduce 0 omitted -module_eval(<<'.,.,', 'egrammar.ra', 68) +module_eval(<<'.,.,', 'egrammar.ra', 65) def _reduce_1(val, _values, result) result = create_program(Factory.block_or_expression(*val[0])) result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 69) +module_eval(<<'.,.,', 'egrammar.ra', 66) def _reduce_2(val, _values, result) result = create_program(Factory.block_or_expression(*val[0])) result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 70) +module_eval(<<'.,.,', 'egrammar.ra', 67) def _reduce_3(val, _values, result) result = create_empty_program() result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 74) +module_eval(<<'.,.,', 'egrammar.ra', 71) def _reduce_4(val, _values, result) result = transform_calls(val[0]) result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 80) +module_eval(<<'.,.,', 'egrammar.ra', 78) def _reduce_5(val, _values, result) result = [val[0]] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 81) +module_eval(<<'.,.,', 'egrammar.ra', 79) def _reduce_6(val, _values, result) result = val[0].push val[2] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 82) +module_eval(<<'.,.,', 'egrammar.ra', 80) def _reduce_7(val, _values, result) result = val[0].push val[1] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 86) +module_eval(<<'.,.,', 'egrammar.ra', 87) def _reduce_8(val, _values, result) result = val[0] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 87) +module_eval(<<'.,.,', 'egrammar.ra', 88) def _reduce_9(val, _values, result) result = aryfy(val[0]).push val[2] result end .,., # reduce 10 omitted module_eval(<<'.,.,', 'egrammar.ra', 93) def _reduce_11(val, _values, result) - result = val[0] + result = val[0].set(val[2]) ; loc result, val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 94) def _reduce_12(val, _values, result) - result = val[0].relop(val[1][:value], val[2]); loc result, val[1] + result = val[0].plus_set(val[2]) ; loc result, val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 95) def _reduce_13(val, _values, result) - result = val[0].relop(val[1][:value], val[2]); loc result, val[1] + result = val[0].minus_set(val[2]); loc result, val[1] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 96) +module_eval(<<'.,.,', 'egrammar.ra', 98) def _reduce_14(val, _values, result) - result = val[0].relop(val[1][:value], val[2]); loc result, val[1] + result = [val[0]] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 97) +module_eval(<<'.,.,', 'egrammar.ra', 99) def _reduce_15(val, _values, result) - result = val[0].relop(val[1][:value], val[2]); loc result, val[1] + result = val[0].push(val[2]) result end .,., # reduce 16 omitted -module_eval(<<'.,.,', 'egrammar.ra', 104) +module_eval(<<'.,.,', 'egrammar.ra', 103) def _reduce_17(val, _values, result) - result = val[0][*val[2]] ; loc result, val[0], val[3] + result = val[0].relop(val[1][:value], val[2]); loc result, val[1] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 105) +module_eval(<<'.,.,', 'egrammar.ra', 104) def _reduce_18(val, _values, result) - result = val[0].in val[2] ; loc result, val[1] + result = val[0].relop(val[1][:value], val[2]); loc result, val[1] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 106) +module_eval(<<'.,.,', 'egrammar.ra', 105) def _reduce_19(val, _values, result) - result = val[0] =~ val[2] ; loc result, val[1] + result = val[0].relop(val[1][:value], val[2]); loc result, val[1] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 107) +module_eval(<<'.,.,', 'egrammar.ra', 106) def _reduce_20(val, _values, result) - result = val[0].mne val[2] ; loc result, val[1] + result = val[0].relop(val[1][:value], val[2]); loc result, val[1] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 108) - def _reduce_21(val, _values, result) - result = val[0] + val[2] ; loc result, val[1] - result - end -.,., +# reduce 21 omitted -module_eval(<<'.,.,', 'egrammar.ra', 109) +module_eval(<<'.,.,', 'egrammar.ra', 115) def _reduce_22(val, _values, result) - result = val[0] - val[2] ; loc result, val[1] + result = val[1] + unless Factory.set_resource_form(result, :virtual) + # This is equivalent to a syntax error - additional semantic restrictions apply + error val[0], "Virtual (@) can only be applied to a Resource Expression" + end + # relocate the result + loc result, val[0], val[1] + result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 110) +module_eval(<<'.,.,', 'egrammar.ra', 126) def _reduce_23(val, _values, result) - result = val[0] / val[2] ; loc result, val[1] + result = val[1] + unless Factory.set_resource_form(result, :exported) + # This is equivalent to a syntax error - additional semantic restrictions apply + error val[0], "Exported (@@) can only be applied to a Resource Expression" + end + # relocate the result + loc result, val[0], val[1] + result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 111) +module_eval(<<'.,.,', 'egrammar.ra', 137) def _reduce_24(val, _values, result) - result = val[0] * val[2] ; loc result, val[1] + bodies = [Factory.RESOURCE_BODY(val[2], val[4])] + val[5] + result = Factory.RESOURCE(val[0], bodies) + loc result, val[0], val[6] + result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 112) +module_eval(<<'.,.,', 'egrammar.ra', 144) def _reduce_25(val, _values, result) - result = val[0] % val[2] ; loc result, val[1] + result = Factory.RESOURCE(Factory.fqn(token_text(val[0])), val[2]) + loc result, val[0], val[4] + result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 113) +module_eval(<<'.,.,', 'egrammar.ra', 153) def _reduce_26(val, _values, result) - result = val[0] << val[2] ; loc result, val[1] + result = case Factory.resource_shape(val[0]) + when :resource, :class + # This catches deprecated syntax. + # If the attribute operations does not include +>, then the found expression + # is actually a LEFT followed by LITERAL_HASH + # + unless tmp = transform_resource_wo_title(val[0], val[2]) + error val[1], "Syntax error resource body without title or hash with +>" + end + tmp + when :defaults + Factory.RESOURCE_DEFAULTS(val[0], val[2]) + when :override + # This was only done for override in original - TODO should 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', 114) +module_eval(<<'.,.,', 'egrammar.ra', 175) def _reduce_27(val, _values, result) - result = val[0] >> val[2] ; loc result, val[1] + result = Factory.RESOURCE_BODY(val[0], val[2]) result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 115) +module_eval(<<'.,.,', 'egrammar.ra', 178) def _reduce_28(val, _values, result) - result = val[1].minus() ; loc result, val[0] + result = [val[0]] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 116) +module_eval(<<'.,.,', 'egrammar.ra', 179) def _reduce_29(val, _values, result) - result = val[1].unfold() ; loc result, val[0] + result = val[0].push val[2] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 117) +module_eval(<<'.,.,', 'egrammar.ra', 185) def _reduce_30(val, _values, result) - result = val[0].ne val[2] ; loc result, val[1] + result = [] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 118) +module_eval(<<'.,.,', 'egrammar.ra', 186) def _reduce_31(val, _values, result) - result = val[0] == val[2] ; loc result, val[1] + result = [] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 119) +module_eval(<<'.,.,', 'egrammar.ra', 187) def _reduce_32(val, _values, result) - result = val[0] > val[2] ; loc result, val[1] + result = val[2] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 120) - def _reduce_33(val, _values, result) - result = val[0] >= val[2] ; loc result, val[1] - result - end -.,., +# reduce 33 omitted -module_eval(<<'.,.,', 'egrammar.ra', 121) - def _reduce_34(val, _values, result) - result = val[0] < val[2] ; loc result, val[1] - result - end -.,., +# reduce 34 omitted -module_eval(<<'.,.,', 'egrammar.ra', 122) +module_eval(<<'.,.,', 'egrammar.ra', 194) def _reduce_35(val, _values, result) - result = val[0] <= val[2] ; loc result, val[1] + result = val[0][*val[2]] ; loc result, val[0], val[3] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 123) +module_eval(<<'.,.,', 'egrammar.ra', 195) def _reduce_36(val, _values, result) - result = val[1].not ; loc result, val[0] + result = val[0].in val[2] ; loc result, val[1] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 124) +module_eval(<<'.,.,', 'egrammar.ra', 196) def _reduce_37(val, _values, result) - result = val[0].and val[2] ; loc result, val[1] + result = val[0] =~ val[2] ; loc result, val[1] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 125) +module_eval(<<'.,.,', 'egrammar.ra', 197) def _reduce_38(val, _values, result) - result = val[0].or val[2] ; loc result, val[1] + result = val[0].mne val[2] ; loc result, val[1] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 126) +module_eval(<<'.,.,', 'egrammar.ra', 198) def _reduce_39(val, _values, result) - result = val[0].set(val[2]) ; loc result, val[1] + result = val[0] + val[2] ; loc result, val[1] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 127) +module_eval(<<'.,.,', 'egrammar.ra', 199) def _reduce_40(val, _values, result) - result = val[0].plus_set(val[2]) ; loc result, val[1] + result = val[0] - val[2] ; loc result, val[1] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 128) +module_eval(<<'.,.,', 'egrammar.ra', 200) def _reduce_41(val, _values, result) - result = val[0].minus_set(val[2]); loc result, val[1] + result = val[0] / val[2] ; loc result, val[1] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 129) +module_eval(<<'.,.,', 'egrammar.ra', 201) def _reduce_42(val, _values, result) - result = val[0].select(*val[2]) ; loc result, val[0] + result = val[0] * val[2] ; loc result, val[1] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 130) +module_eval(<<'.,.,', 'egrammar.ra', 202) def _reduce_43(val, _values, result) - result = val[1].paren() ; loc result, val[0] + result = val[0] % val[2] ; loc result, val[1] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 138) +module_eval(<<'.,.,', 'egrammar.ra', 203) def _reduce_44(val, _values, result) - result = [val[0]] + result = val[0] << val[2] ; loc result, val[1] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 139) +module_eval(<<'.,.,', 'egrammar.ra', 204) def _reduce_45(val, _values, result) - result = val[0].push(val[2]) + result = val[0] >> val[2] ; loc result, val[1] result end .,., -# reduce 46 omitted +module_eval(<<'.,.,', 'egrammar.ra', 205) + def _reduce_46(val, _values, result) + result = val[1].minus() ; loc result, val[0] + result + end +.,., -# reduce 47 omitted +module_eval(<<'.,.,', 'egrammar.ra', 206) + def _reduce_47(val, _values, result) + result = val[1].unfold() ; loc result, val[0] + result + end +.,., -# reduce 48 omitted +module_eval(<<'.,.,', 'egrammar.ra', 207) + def _reduce_48(val, _values, result) + result = val[0].ne val[2] ; loc result, val[1] + result + end +.,., -# reduce 49 omitted +module_eval(<<'.,.,', 'egrammar.ra', 208) + def _reduce_49(val, _values, result) + result = val[0] == val[2] ; loc result, val[1] + result + end +.,., -# reduce 50 omitted +module_eval(<<'.,.,', 'egrammar.ra', 209) + def _reduce_50(val, _values, result) + result = val[0] > val[2] ; loc result, val[1] + result + end +.,., -# reduce 51 omitted +module_eval(<<'.,.,', 'egrammar.ra', 210) + def _reduce_51(val, _values, result) + result = val[0] >= val[2] ; loc result, val[1] + result + end +.,., -# reduce 52 omitted +module_eval(<<'.,.,', 'egrammar.ra', 211) + def _reduce_52(val, _values, result) + result = val[0] < val[2] ; loc result, val[1] + result + end +.,., -# reduce 53 omitted +module_eval(<<'.,.,', 'egrammar.ra', 212) + def _reduce_53(val, _values, result) + result = val[0] <= val[2] ; loc result, val[1] + result + end +.,., -# reduce 54 omitted +module_eval(<<'.,.,', 'egrammar.ra', 213) + def _reduce_54(val, _values, result) + result = val[1].not ; loc result, val[0] + result + end +.,., -# reduce 55 omitted +module_eval(<<'.,.,', 'egrammar.ra', 214) + def _reduce_55(val, _values, result) + result = val[0].and val[2] ; loc result, val[1] + result + end +.,., -# reduce 56 omitted +module_eval(<<'.,.,', 'egrammar.ra', 215) + def _reduce_56(val, _values, result) + result = val[0].or val[2] ; loc result, val[1] + result + end +.,., -# reduce 57 omitted +module_eval(<<'.,.,', 'egrammar.ra', 216) + def _reduce_57(val, _values, result) + result = val[0].select(*val[2]) ; loc result, val[0] + result + end +.,., -# reduce 58 omitted +module_eval(<<'.,.,', 'egrammar.ra', 217) + def _reduce_58(val, _values, result) + result = val[1].paren() ; loc result, val[0] + result + end +.,., -# reduce 59 omitted +module_eval(<<'.,.,', 'egrammar.ra', 227) + def _reduce_59(val, _values, result) + result = [val[0]] + result + end +.,., -# reduce 60 omitted +module_eval(<<'.,.,', 'egrammar.ra', 228) + def _reduce_60(val, _values, result) + result = val[0].push(val[2]) + result + end +.,., # reduce 61 omitted # reduce 62 omitted # reduce 63 omitted # reduce 64 omitted # reduce 65 omitted # reduce 66 omitted # reduce 67 omitted -module_eval(<<'.,.,', 'egrammar.ra', 172) - def _reduce_68(val, _values, result) - result = val[0] +# reduce 68 omitted + +# reduce 69 omitted + +# reduce 70 omitted + +# reduce 71 omitted + +# reduce 72 omitted + +# reduce 73 omitted + +# reduce 74 omitted + +# reduce 75 omitted + +# reduce 76 omitted + +module_eval(<<'.,.,', 'egrammar.ra', 247) + def _reduce_77(val, _values, result) + result = Factory.NUMBER(val[0][:value]) ; loc result, val[0] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 173) - def _reduce_69(val, _values, result) - result = val[0] +module_eval(<<'.,.,', 'egrammar.ra', 248) + def _reduce_78(val, _values, result) + result = Factory.literal(val[0][:value]) ; loc result, val[0] + result + end +.,., + +module_eval(<<'.,.,', 'egrammar.ra', 249) + def _reduce_79(val, _values, result) + result = Factory.literal(:default) ; loc result, val[0] + result + end +.,., + +module_eval(<<'.,.,', 'egrammar.ra', 250) + def _reduce_80(val, _values, result) + result = Factory.literal(:undef) ; loc result, val[0] + result + end +.,., + +module_eval(<<'.,.,', 'egrammar.ra', 251) + def _reduce_81(val, _values, result) + result = Factory.QNAME_OR_NUMBER(val[0][:value]) ; loc result, val[0] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 181) - def _reduce_70(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 260) + def _reduce_82(val, _values, result) result = Factory.CALL_NAMED(val[0], true, val[2]) loc result, val[0], val[4] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 185) - def _reduce_71(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 264) + def _reduce_83(val, _values, result) result = Factory.CALL_NAMED(val[0], true, []) loc result, val[0], val[2] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 189) - def _reduce_72(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 268) + def _reduce_84(val, _values, result) result = Factory.CALL_NAMED(val[0], true, val[2]) loc result, val[0], val[4] result.lambda = val[5] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 194) - def _reduce_73(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 273) + def _reduce_85(val, _values, result) result = Factory.CALL_NAMED(val[0], true, []) loc result, val[0], val[2] result.lambda = val[3] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 198) - def _reduce_74(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 281) + def _reduce_86(val, _values, result) result = val[0] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 203) - def _reduce_75(val, _values, result) - result = val[0] - result - end -.,., - -module_eval(<<'.,.,', 'egrammar.ra', 204) - def _reduce_76(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 282) + def _reduce_87(val, _values, result) result = val[0]; val[0].lambda = val[1] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 207) - def _reduce_77(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 285) + def _reduce_88(val, _values, result) result = Factory.CALL_METHOD(val[0], val[2]); loc result, val[1], val[3] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 208) - def _reduce_78(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 286) + def _reduce_89(val, _values, result) result = Factory.CALL_METHOD(val[0], []); loc result, val[1], val[3] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 209) - def _reduce_79(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 287) + def _reduce_90(val, _values, result) result = Factory.CALL_METHOD(val[0], []); loc result, val[0] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 214) - def _reduce_80(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 291) + def _reduce_91(val, _values, result) result = val[0].dot(Factory.fqn(val[2][:value])) loc result, val[1], val[2] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 226) - def _reduce_81(val, _values, result) - result = Factory.LAMBDA(val[0], val[1]) -# loc result, val[1] # TODO +module_eval(<<'.,.,', 'egrammar.ra', 299) + def _reduce_92(val, _values, result) + result = Factory.LAMBDA(val[0][:value], val[1][:value]) + loc result, val[0][:start], val[1][:end] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 231) - def _reduce_82(val, _values, result) - result = val[1] +module_eval(<<'.,.,', 'egrammar.ra', 304) + def _reduce_93(val, _values, result) + result = {:end => val[2], :value =>val[1] } result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 232) - def _reduce_83(val, _values, result) - result = nil +module_eval(<<'.,.,', 'egrammar.ra', 305) + def _reduce_94(val, _values, result) + result = {:end => val[1], :value => nil } result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 236) - def _reduce_84(val, _values, result) - result = [] +module_eval(<<'.,.,', 'egrammar.ra', 309) + def _reduce_95(val, _values, result) + result = {:start => val[0], :value => [] } result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 237) - def _reduce_85(val, _values, result) - result = val[1] +module_eval(<<'.,.,', 'egrammar.ra', 310) + def _reduce_96(val, _values, result) + result = {:start => val[0], :value => val[1] } result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 247) - def _reduce_86(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 318) + def _reduce_97(val, _values, result) result = val[1] loc(result, val[0], val[1]) result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 254) - def _reduce_87(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 325) + def _reduce_98(val, _values, result) result = Factory.IF(val[0], Factory.block_or_expression(*val[2]), val[4]) loc(result, val[0], (val[4] ? val[4] : val[3])) result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 258) - def _reduce_88(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 329) + def _reduce_99(val, _values, result) result = Factory.IF(val[0], nil, val[3]) loc(result, val[0], (val[3] ? val[3] : val[2])) result end .,., -# reduce 89 omitted +# reduce 100 omitted -module_eval(<<'.,.,', 'egrammar.ra', 266) - def _reduce_90(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 337) + def _reduce_101(val, _values, result) result = val[1] loc(result, val[0], val[1]) result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 270) - def _reduce_91(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 341) + def _reduce_102(val, _values, result) result = Factory.block_or_expression(*val[2]) loc result, val[0], val[3] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 274) - def _reduce_92(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 345) + def _reduce_103(val, _values, result) result = nil # don't think a nop is needed here either result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 283) - def _reduce_93(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 352) + def _reduce_104(val, _values, result) result = Factory.UNLESS(val[1], Factory.block_or_expression(*val[3]), val[5]) loc result, val[0], val[4] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 287) - def _reduce_94(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 356) + def _reduce_105(val, _values, result) result = Factory.UNLESS(val[1], nil, nil) loc result, val[0], val[4] result end .,., -# reduce 95 omitted +# reduce 106 omitted -module_eval(<<'.,.,', 'egrammar.ra', 297) - def _reduce_96(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 366) + def _reduce_107(val, _values, result) result = Factory.block_or_expression(*val[2]) loc result, val[0], val[3] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 301) - def _reduce_97(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 370) + def _reduce_108(val, _values, result) result = nil # don't think a nop is needed here either result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 309) - def _reduce_98(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 377) + def _reduce_109(val, _values, result) result = Factory.CASE(val[1], *val[3]) loc result, val[0], val[4] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 315) - def _reduce_99(val, _values, result) - result = [val[0]] - result - end -.,., - -module_eval(<<'.,.,', 'egrammar.ra', 316) - def _reduce_100(val, _values, result) - result = val[0].push val[1] - result - end -.,., - -module_eval(<<'.,.,', 'egrammar.ra', 321) - def _reduce_101(val, _values, result) - result = Factory.WHEN(val[0], val[3]) - loc result, val[1], val[4] - - result - end -.,., - -module_eval(<<'.,.,', 'egrammar.ra', 325) - def _reduce_102(val, _values, result) - result = Factory.WHEN(val[0], nil) - loc result, val[1], val[3] - - result - end -.,., - -module_eval(<<'.,.,', 'egrammar.ra', 329) - def _reduce_103(val, _values, result) - result = val[0] - result - end -.,., - -# reduce 104 omitted - -module_eval(<<'.,.,', 'egrammar.ra', 340) - def _reduce_105(val, _values, result) - result = val[1] - - result - end -.,., - -module_eval(<<'.,.,', 'egrammar.ra', 345) - def _reduce_106(val, _values, result) - result = [val[0]] - result - end -.,., - -module_eval(<<'.,.,', 'egrammar.ra', 346) - def _reduce_107(val, _values, result) - result = val[0].push val[2] - result - end -.,., - -module_eval(<<'.,.,', 'egrammar.ra', 351) - def _reduce_108(val, _values, result) - result = Factory.MAP(val[0], val[2]) ; loc result, val[1] - result - end -.,., - -module_eval(<<'.,.,', 'egrammar.ra', 363) - def _reduce_109(val, _values, result) - result = val[0] - - result - end -.,., - -module_eval(<<'.,.,', 'egrammar.ra', 366) +module_eval(<<'.,.,', 'egrammar.ra', 383) def _reduce_110(val, _values, result) - result = case Factory.resource_shape(val[1]) - when :resource, :class - tmp = Factory.RESOURCE(Factory.fqn(token_text(val[1])), val[3]) - tmp.form = val[0] - tmp - when :defaults - error val[1], "A resource default can not be virtual or exported" - when :override - error val[1], "A resource override can not be virtual or exported" - else - error val[1], "Expression is not valid as a resource, resource-default, or resource-override" - end - loc result, val[1], val[4] - + result = [val[0]] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 381) +module_eval(<<'.,.,', 'egrammar.ra', 384) def _reduce_111(val, _values, result) - result = case Factory.resource_shape(val[1]) - when :resource, :class, :defaults, :override - error val[1], "Defaults are not virtualizable" - else - error val[1], "Expression is not valid as a resource, resource-default, or resource-override" - end - + result = val[0].push val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 389) def _reduce_112(val, _values, result) - result = case Factory.resource_shape(val[0]) - when :resource, :class - Factory.RESOURCE(Factory.fqn(token_text(val[0])), val[2]) - when :defaults - error val[1], "A resource default can not specify a resource name" - when :override - error val[1], "A resource override does not allow override of name of resource" - else - error val[1], "Expression is not valid as a resource, resource-default, or resource-override" - end - loc result, val[0], val[4] - + result = Factory.WHEN(val[0], val[3]); loc result, val[1], val[4] + result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 402) - def _reduce_113(val, _values, result) - result = case Factory.resource_shape(val[0]) - when :resource, :class - # This catches deprecated syntax. - # If the attribute operations does not include +>, then the found expression - # is actually a LEFT followed by LITERAL_HASH - # - unless tmp = transform_resource_wo_title(val[0], val[2]) - error val[1], "Syntax error resource body without title or hash with +>" - end - tmp - when :defaults - Factory.RESOURCE_DEFAULTS(val[0], val[2]) - when :override - # This was only done for override in original - TODO shuld it be here at all - Factory.RESOURCE_OVERRIDE(val[0], val[2]) - else - error val[0], "Expression is not valid as a resource, resource-default, or resource-override" - end - loc result, val[0], val[4] - - result - end -.,., +# reduce 113 omitted -module_eval(<<'.,.,', 'egrammar.ra', 423) - def _reduce_114(val, _values, result) - result = Factory.RESOURCE(Factory.fqn(token_text(val[1])), val[3]) - result.form = val[0] - loc result, val[1], val[5] - - result - end -.,., +# reduce 114 omitted -module_eval(<<'.,.,', 'egrammar.ra', 428) - def _reduce_115(val, _values, result) - result = Factory.RESOURCE(Factory.fqn(token_text(val[0])), val[2]) - loc result, val[0], val[4] - - result - end -.,., +# reduce 115 omitted -module_eval(<<'.,.,', 'egrammar.ra', 433) +module_eval(<<'.,.,', 'egrammar.ra', 405) def _reduce_116(val, _values, result) - result = Factory.RESOURCE_BODY(val[0], val[2]) + result = val[1] + result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 435) +module_eval(<<'.,.,', 'egrammar.ra', 410) def _reduce_117(val, _values, result) - result = val[0] + result = [val[0]] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 438) +module_eval(<<'.,.,', 'egrammar.ra', 411) def _reduce_118(val, _values, result) - result = [val[0]] + result = val[0].push val[2] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 439) +module_eval(<<'.,.,', 'egrammar.ra', 416) def _reduce_119(val, _values, result) - result = val[0].push val[2] + result = Factory.MAP(val[0], val[2]) ; loc result, val[1] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 444) +module_eval(<<'.,.,', 'egrammar.ra', 426) def _reduce_120(val, _values, result) - result = :virtual - result - end -.,., - -module_eval(<<'.,.,', 'egrammar.ra', 445) - def _reduce_121(val, _values, result) - result = :exported - result - end -.,., - -module_eval(<<'.,.,', 'egrammar.ra', 446) - def _reduce_122(val, _values, result) - result = :exported - result - end -.,., - -module_eval(<<'.,.,', 'egrammar.ra', 458) - def _reduce_123(val, _values, result) result = Factory.COLLECT(val[0], val[1], val[3]) loc result, val[0], val[5] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 462) - def _reduce_124(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 430) + def _reduce_121(val, _values, result) result = Factory.COLLECT(val[0], val[1], []) loc result, val[0], val[1] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 467) - def _reduce_125(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 435) + def _reduce_122(val, _values, result) result = Factory.VIRTUAL_QUERY(val[1]) ; loc result, val[0], val[2] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 468) - def _reduce_126(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 436) + def _reduce_123(val, _values, result) result = Factory.EXPORTED_QUERY(val[1]) ; loc result, val[0], val[2] result end .,., -# reduce 127 omitted +# reduce 124 omitted -# reduce 128 omitted +# reduce 125 omitted -module_eval(<<'.,.,', 'egrammar.ra', 481) - def _reduce_129(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 445) + def _reduce_126(val, _values, result) result = [] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 482) - def _reduce_130(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 446) + def _reduce_127(val, _values, result) + result = [tmp = Factory.ATTRIBUTES_OP(val[2])] ; loc tmp, val[0], val[2] + result + end +.,., + +module_eval(<<'.,.,', 'egrammar.ra', 447) + def _reduce_128(val, _values, result) result = [val[0]] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 483) - def _reduce_131(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 448) + def _reduce_129(val, _values, result) result = val[0].push(val[2]) result end .,., -# reduce 132 omitted +# reduce 130 omitted -# reduce 133 omitted +# reduce 131 omitted -# reduce 134 omitted - -module_eval(<<'.,.,', 'egrammar.ra', 499) - def _reduce_135(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 464) + def _reduce_132(val, _values, result) result = Factory.ATTRIBUTE_OP(val[0][:value], :'=>', val[2]) loc result, val[0], val[2] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 503) - def _reduce_136(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 468) + 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', 513) - def _reduce_137(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 478) + def _reduce_134(val, _values, result) result = add_definition(Factory.DEFINITION(classname(val[1][:value]), val[2], val[4])) loc result, val[0], val[5] # New lexer does not keep track of this, this is done in validation if @lexer.respond_to?(:'indefine=') @lexer.indefine = false end result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 527) - def _reduce_138(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 492) + def _reduce_135(val, _values, result) # Remove this class' name from the namestack as all nested classes have been parsed namepop result = add_definition(Factory.HOSTCLASS(classname(val[1][:value]), val[2], token_text(val[3]), val[5])) loc result, val[0], val[6] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 537) - def _reduce_139(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 502) + def _reduce_136(val, _values, result) namestack(val[0][:value]) ; result = val[0] result end .,., -# reduce 140 omitted +# reduce 137 omitted -# reduce 141 omitted +# reduce 138 omitted -# reduce 142 omitted +# reduce 139 omitted -module_eval(<<'.,.,', 'egrammar.ra', 546) - def _reduce_143(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 511) + def _reduce_140(val, _values, result) result = val[1] result end .,., -# reduce 144 omitted +# reduce 141 omitted -# reduce 145 omitted +# reduce 142 omitted -module_eval(<<'.,.,', 'egrammar.ra', 563) - def _reduce_146(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 528) + def _reduce_143(val, _values, result) result = add_definition(Factory.NODE(val[1], val[3], val[5])) loc result, val[0], val[6] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 567) - def _reduce_147(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 532) + def _reduce_144(val, _values, result) result = add_definition(Factory.NODE(val[1], val[3], nil)) loc result, val[0], val[5] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 577) - def _reduce_148(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 542) + def _reduce_145(val, _values, result) result = [result] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 578) - def _reduce_149(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 543) + def _reduce_146(val, _values, result) result = val[0].push(val[2]) result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 583) - def _reduce_150(val, _values, result) - result = val[0] - result - end -.,., +# reduce 147 omitted -module_eval(<<'.,.,', 'egrammar.ra', 584) - def _reduce_151(val, _values, result) - result = val[0] - result - end -.,., +# reduce 148 omitted -module_eval(<<'.,.,', 'egrammar.ra', 585) - def _reduce_152(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 550) + def _reduce_149(val, _values, result) result = Factory.literal(:default); loc result, val[0] result end .,., -# reduce 153 omitted +# reduce 150 omitted -module_eval(<<'.,.,', 'egrammar.ra', 589) - def _reduce_154(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 554) + def _reduce_151(val, _values, result) result = Factory.literal(val[0][:value]); loc result, val[0] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 590) - def _reduce_155(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 555) + def _reduce_152(val, _values, result) result = Factory.concat(val[0], '.', val[2][:value]); loc result, val[0], val[2] result end .,., -# reduce 156 omitted +# reduce 153 omitted -# reduce 157 omitted +# reduce 154 omitted -# reduce 158 omitted +# reduce 155 omitted -module_eval(<<'.,.,', 'egrammar.ra', 599) - def _reduce_159(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 564) + def _reduce_156(val, _values, result) result = val[1] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 615) - def _reduce_160(val, _values, result) - result = val[0] - result - end -.,., +# reduce 157 omitted -module_eval(<<'.,.,', 'egrammar.ra', 616) - def _reduce_161(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 581) + def _reduce_158(val, _values, result) error val[0], "'class' is not a valid classname" result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 620) - def _reduce_162(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 585) + def _reduce_159(val, _values, result) result = [] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 621) - def _reduce_163(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 586) + def _reduce_160(val, _values, result) result = [] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 622) - def _reduce_164(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 587) + def _reduce_161(val, _values, result) result = val[1] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 626) - def _reduce_165(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 591) + def _reduce_162(val, _values, result) result = [val[0]] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 627) - def _reduce_166(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 592) + def _reduce_163(val, _values, result) result = val[0].push(val[2]) result end .,., -# reduce 167 omitted +# reduce 164 omitted -# reduce 168 omitted +# reduce 165 omitted -# reduce 169 omitted +# reduce 166 omitted -# reduce 170 omitted +# reduce 167 omitted -module_eval(<<'.,.,', 'egrammar.ra', 639) - def _reduce_171(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 604) + def _reduce_168(val, _values, result) result = Factory.PARAM(val[0][:value], val[2]) ; loc result, val[0] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 640) - def _reduce_172(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 605) + def _reduce_169(val, _values, result) result = Factory.PARAM(val[0][:value]); loc result, val[0] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 643) - def _reduce_173(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 608) + def _reduce_170(val, _values, result) result = val[1]; val[1].captures_rest() result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 646) - def _reduce_174(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 611) + def _reduce_171(val, _values, result) val[1].type_expr(val[0]) ; result = val[1] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 649) - def _reduce_175(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 614) + def _reduce_172(val, _values, result) result = val[0] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 650) - def _reduce_176(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 615) + def _reduce_173(val, _values, result) result = val[0][*val[2]] ; loc result, val[0], val[3] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 663) - def _reduce_177(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 620) + def _reduce_174(val, _values, result) result = Factory.fqn(val[0][:value]).var ; loc result, val[0] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 668) - def _reduce_178(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 625) + def _reduce_175(val, _values, result) result = Factory.RESERVED(val[0][:value]) ; loc result, val[0] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 669) - def _reduce_179(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 626) + def _reduce_176(val, _values, result) result = Factory.RESERVED(val[0][:value]) ; loc result, val[0] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 670) - def _reduce_180(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 627) + def _reduce_177(val, _values, result) result = Factory.RESERVED(val[0][:value]) ; loc result, val[0] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 671) - def _reduce_181(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 628) + def _reduce_178(val, _values, result) result = Factory.RESERVED(val[0][:value]) ; loc result, val[0] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 677) - def _reduce_182(val, _values, result) - result = Factory.LIST(val[1]); loc result, val[0], val[2] - result - end -.,., - -module_eval(<<'.,.,', 'egrammar.ra', 678) - def _reduce_183(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 634) + def _reduce_179(val, _values, result) result = Factory.LIST(val[1]); loc result, val[0], val[3] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 679) - def _reduce_184(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 635) + def _reduce_180(val, _values, result) result = Factory.literal([]) ; loc result, val[0] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 680) - def _reduce_185(val, _values, result) - result = Factory.LIST(val[1]); loc result, val[0], val[2] - result - end -.,., - -module_eval(<<'.,.,', 'egrammar.ra', 681) - def _reduce_186(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 636) + def _reduce_181(val, _values, result) result = Factory.LIST(val[1]); loc result, val[0], val[3] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 682) - def _reduce_187(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 637) + def _reduce_182(val, _values, result) result = Factory.literal([]) ; loc result, val[0] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 685) - def _reduce_188(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 640) + def _reduce_183(val, _values, result) result = Factory.HASH(val[1]); loc result, val[0], val[2] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 686) - def _reduce_189(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 641) + def _reduce_184(val, _values, result) result = Factory.HASH(val[1]); loc result, val[0], val[3] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 687) - def _reduce_190(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 642) + def _reduce_185(val, _values, result) result = Factory.literal({}) ; loc result, val[0], val[3] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 690) - def _reduce_191(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 645) + def _reduce_186(val, _values, result) result = [val[0]] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 691) - def _reduce_192(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 646) + def _reduce_187(val, _values, result) result = val[0].push val[2] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 694) - def _reduce_193(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 649) + def _reduce_188(val, _values, result) result = Factory.KEY_ENTRY(val[0], val[2]); loc result, val[1] result end .,., -# reduce 194 omitted +# reduce 189 omitted -# reduce 195 omitted +# reduce 190 omitted -# reduce 196 omitted +# reduce 191 omitted -module_eval(<<'.,.,', 'egrammar.ra', 702) - def _reduce_197(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 657) + def _reduce_192(val, _values, result) result = Factory.literal(val[0][:value]) ; loc result, val[0] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 703) - def _reduce_198(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 658) + def _reduce_193(val, _values, result) result = Factory.literal(val[0][:value]) ; loc result, val[0] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 705) - def _reduce_199(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 660) + def _reduce_194(val, _values, result) result = Factory.string(val[0], *val[1]) ; loc result, val[0], val[1][-1] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 706) - def _reduce_200(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 661) + def _reduce_195(val, _values, result) result = Factory.literal(val[0][:value]); loc result, val[0] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 707) - def _reduce_201(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 662) + def _reduce_196(val, _values, result) result = Factory.literal(val[0][:value]); loc result, val[0] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 708) - def _reduce_202(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 663) + def _reduce_197(val, _values, result) result = Factory.literal(val[0][:value]); loc result, val[0] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 709) - def _reduce_203(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 664) + def _reduce_198(val, _values, result) result = [val[0]] + val[1] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 710) - def _reduce_204(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 665) + def _reduce_199(val, _values, result) result = Factory.TEXT(val[0]) result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 713) - def _reduce_205(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 668) + def _reduce_200(val, _values, result) result = [val[0]] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 714) - def _reduce_206(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 669) + def _reduce_201(val, _values, result) result = [val[0]] + val[1] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 717) - def _reduce_207(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 672) + def _reduce_202(val, _values, result) result = Factory.HEREDOC(val[0][:value], val[1]); loc result, val[0] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 720) - def _reduce_208(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 675) + def _reduce_203(val, _values, result) result = Factory.SUBLOCATE(val[0], val[1]); loc result, val[0] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 721) - def _reduce_209(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 676) + def _reduce_204(val, _values, result) result = Factory.SUBLOCATE(val[0], val[1]); loc result, val[0] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 724) - def _reduce_210(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 679) + def _reduce_205(val, _values, result) result = Factory.EPP(val[1], val[2]); loc result, val[0] result end .,., -# reduce 211 omitted +# reduce 206 omitted -# reduce 212 omitted +# reduce 207 omitted -module_eval(<<'.,.,', 'egrammar.ra', 731) - def _reduce_213(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 686) + def _reduce_208(val, _values, result) result = nil result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 732) - def _reduce_214(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 687) + def _reduce_209(val, _values, result) result = [] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 733) - def _reduce_215(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 688) + def _reduce_210(val, _values, result) result = val[1] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 736) - def _reduce_216(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 691) + def _reduce_211(val, _values, result) result = Factory.RENDER_STRING(val[0][:value]); loc result, val[0] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 737) - def _reduce_217(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 692) + def _reduce_212(val, _values, result) result = Factory.RENDER_EXPR(val[1]); loc result, val[0], val[2] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 738) - def _reduce_218(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 693) + def _reduce_213(val, _values, result) result = Factory.RENDER_EXPR(Factory.block_or_expression(*val[2])); loc result, val[0], val[4] result end .,., -# reduce 219 omitted +# reduce 214 omitted -# reduce 220 omitted +# reduce 215 omitted -module_eval(<<'.,.,', 'egrammar.ra', 744) - def _reduce_221(val, _values, result) - result = Factory.NUMBER(val[0][:value]) ; loc result, val[0] +module_eval(<<'.,.,', 'egrammar.ra', 699) + def _reduce_216(val, _values, result) + result = Factory.QREF(val[0][:value]) ; loc result, val[0] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 745) - def _reduce_222(val, _values, result) - result = Factory.QNAME_OR_NUMBER(val[0][:value]) ; loc result, val[0] +module_eval(<<'.,.,', 'egrammar.ra', 702) + def _reduce_217(val, _values, result) + result = Factory.literal(val[0][:value]); loc result, val[0] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 746) - def _reduce_223(val, _values, result) - result = Factory.QREF(val[0][:value]) ; loc result, val[0] - result - end -.,., +# reduce 218 omitted -module_eval(<<'.,.,', 'egrammar.ra', 747) - def _reduce_224(val, _values, result) - result = Factory.literal(:undef); loc result, val[0] +module_eval(<<'.,.,', 'egrammar.ra', 708) + def _reduce_219(val, _values, result) + result = nil result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 748) - def _reduce_225(val, _values, result) - result = Factory.literal(:default); loc result, val[0] - result - end -.,., +# reduce 220 omitted -module_eval(<<'.,.,', 'egrammar.ra', 753) - def _reduce_226(val, _values, result) - result = Factory.literal(val[0][:value]) ; loc result, val[0] - result - end -.,., +# reduce 221 omitted -module_eval(<<'.,.,', 'egrammar.ra', 756) - def _reduce_227(val, _values, result) - result = Factory.literal(val[0][:value]); loc result, val[0] - result - end -.,., +# reduce 222 omitted + +# reduce 223 omitted + +# reduce 224 omitted + +# reduce 225 omitted + +# reduce 226 omitted + +# reduce 227 omitted # reduce 228 omitted -module_eval(<<'.,.,', 'egrammar.ra', 762) - def _reduce_229(val, _values, result) - result = nil - result - end -.,., +# reduce 229 omitted # reduce 230 omitted # reduce 231 omitted # reduce 232 omitted # reduce 233 omitted # reduce 234 omitted # reduce 235 omitted # reduce 236 omitted # reduce 237 omitted # reduce 238 omitted # reduce 239 omitted -# reduce 240 omitted - -# reduce 241 omitted - -# reduce 242 omitted - -# reduce 243 omitted - -# reduce 244 omitted - -# reduce 245 omitted - -# reduce 246 omitted - -# reduce 247 omitted - -# reduce 248 omitted - -# reduce 249 omitted - -module_eval(<<'.,.,', 'egrammar.ra', 789) - def _reduce_250(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 735) + def _reduce_240(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/lexer_support.rb b/lib/puppet/pops/parser/lexer_support.rb index c769255a5..5b296e49c 100644 --- a/lib/puppet/pops/parser/lexer_support.rb +++ b/lib/puppet/pops/parser/lexer_support.rb @@ -1,107 +1,113 @@ # This is an integral part of the Lexer. It is broken out into a separate module # for maintainability of the code, and making the various parts of the lexer focused. # module Puppet::Pops::Parser::LexerSupport # Formats given message by appending file, line and position if available. def positioned_message(msg, pos = nil) result = [msg] file = @locator.file line = @locator.line_for_offset(pos || @scanner.pos) pos = @locator.pos_on_line(pos || @scanner.pos) result << "in file #{file}" if file && file.is_a?(String) && !file.empty? result << "at line #{line}:#{pos}" result.join(" ") end # Returns "" if at end of input, else the following 5 characters with \n \r \t escaped def followed_by return "" if @scanner.eos? result = @scanner.rest[0,5] + "..." result.gsub!("\t", '\t') result.gsub!("\n", '\n') result.gsub!("\r", '\r') result end # Returns a quoted string using " or ' depending on the given a strings's content def format_quote(q) if q == "'" '"\'"' else "'#{q}'" end end # Raises a Puppet::LexError with the given message def lex_error_without_pos msg raise Puppet::LexError.new(msg) end # Raises a Puppet::LexError with the given message def lex_error(msg, pos=nil) raise Puppet::LexError.new(positioned_message(msg, pos)) end # Asserts that the given string value is a float, or an integer in decimal, octal or hex form. # An error is raised if the given value does not comply. # def assert_numeric(value, length) if value =~ /^0[xX].*$/ lex_error("Not a valid hex number #{value}", length) unless value =~ /^0[xX][0-9A-Fa-f]+$/ elsif value =~ /^0[^.].*$/ lex_error("Not a valid octal number #{value}", length) unless value =~ /^0[0-7]+$/ else lex_error("Not a valid decimal number #{value}", length) unless value =~ /0?\d+(?:\.\d+)?(?:[eE]-?\d+)?/ end end # A TokenValue keeps track of the token symbol, the lexed text for the token, its length # and its position in its source container. There is a cost associated with computing the # line and position on line information. # class TokenValue < Puppet::Pops::Parser::Locatable attr_reader :token_array attr_reader :offset attr_reader :locator def initialize(token_array, offset, locator) @token_array = token_array @offset = offset @locator = locator end def length @token_array[2] end def [](key) case key when :value @token_array[1] when :file @locator.file when :line @locator.line_for_offset(@offset) when :pos @locator.pos_on_line(@offset) when :length @token_array[2] when :locator @locator when :offset @offset else nil end end + def to_s + # This format is very compact and is intended for debugging output from racc parsser in + # debug mode. If this is made more elaborate the output from a debug run becomes very hard to read. + # + "'#{self[:value]} #{@token_array[0]}'" + end # TODO: Make this comparable for testing # vs symbolic, vs array with symbol and non hash, array with symbol and hash) # end end diff --git a/lib/puppet/pops/parser/makefile b/lib/puppet/pops/parser/makefile index 802382dd8..4613c6c7f 100644 --- a/lib/puppet/pops/parser/makefile +++ b/lib/puppet/pops/parser/makefile @@ -1,6 +1,10 @@ eparser.rb: egrammar.ra racc -o$@ egrammar.ra egrammar.output: egrammar.ra racc -v -o$@ egrammar.ra + +egrammar.debug: egrammar.ra + racc -t -oeparser.rb egrammar.ra + diff --git a/lib/puppet/pops/parser/parser_support.rb b/lib/puppet/pops/parser/parser_support.rb index b6bc7b8ad..cb1c83aa2 100644 --- a/lib/puppet/pops/parser/parser_support.rb +++ b/lib/puppet/pops/parser/parser_support.rb @@ -1,247 +1,213 @@ require 'puppet/parser/functions' require 'puppet/parser/files' require 'puppet/resource/type_collection' require 'puppet/resource/type_collection_helper' require 'puppet/resource/type' require 'monitor' # Supporting logic for the parser. # This supporting logic has slightly different responsibilities compared to the original Puppet::Parser::Parser. # It is only concerned with parsing. # class Puppet::Pops::Parser::Parser # Note that the name of the contained class and the file name (currently parser_support.rb) # needs to be different as the class is generated by Racc, and this file (parser_support.rb) is included as a mix in # # Simplify access to the Model factory # Note that the parser/parser support does not have direct knowledge about the Model. # All model construction/manipulation is made by the Factory. # Factory = Puppet::Pops::Model::Factory Model = Puppet::Pops::Model include Puppet::Resource::TypeCollectionHelper attr_accessor :lexer attr_reader :definitions # Returns the token text of the given lexer token, or nil, if token is nil def token_text t return t if t.nil? t = t.current if t.respond_to?(:current) return t.value if t.is_a? Model::QualifiedName # else it is a lexer token t[:value] end # Produces the fully qualified name, with the full (current) namespace for a given name. # # This is needed because class bodies are lazily evaluated and an inner class' container(s) may not # have been evaluated before some external reference is made to the inner class; its must therefore know its complete name # before evaluation-time. # def classname(name) - [namespace, name].join("::").sub(/^::/, '') + [namespace, name].join('::').sub(/^::/, '') end -# # Reinitializes variables (i.e. creates a new lexer instance -# # -# def clear -# initvars -# end - # Raises a Parse error. def error(value, message, options = {}) except = Puppet::ParseError.new(message) except.line = options[:line] || value[:line] - except.file = options[:file] || value[:file] # @lexer.file - except.pos = options[:pos] || value[:pos] # @lexer.pos + except.file = options[:file] || value[:file] + except.pos = options[:pos] || value[:pos] raise except end # Parses a file expected to contain pp DSL logic. def parse_file(file) unless Puppet::FileSystem.exist?(file) unless file =~ /\.pp$/ file = file + ".pp" end end @lexer.file = file _parse() end def initialize() - # Since the parser is not responsible for importing (removed), and does not perform linking, - # and there is no syntax that requires knowing if something referenced exists, it is safe - # to assume that no environment is needed when parsing. (All that comes later). - # @lexer = Puppet::Pops::Parser::Lexer2.new @namestack = [] @definitions = [] end -# # Initializes the parser support by creating a new instance of {Puppet::Pops::Parser::Lexer} -# # @return [void] -# # -# def initvars -# end - - # This is a callback from the generated grammar (when an error occurs while parsing) - # TODO Picks up origin information from the lexer, probably needs this from the caller instead - # (for code strings, and when start line is not line 1 in a code string (or file), etc.) + # This is a callback from the generated parser (when an error occurs while parsing) # def on_error(token,value,stack) if token == 0 # denotes end of file value_at = 'end of file' else value_at = "'#{value[:value]}'" end - error = "Syntax error at #{value_at}" + if @yydebug + error = "Syntax error at #{value_at}, token: #{token}" + else + error = "Syntax error at #{value_at}" + end + # Note, old parser had processing of "expected token here" - do not try to reinstate: # The 'expected' is only of value at end of input, otherwise any parse error involving a # start of a pair will be reported as expecting the close of the pair - e.g. "$x.each |$x {|", would # report that "seeing the '{', the '}' is expected. That would be wrong. # Real "expected" tokens are very difficult to compute (would require parsing of racc output data). Output of the stack # could help, but can require extensive backtracking and produce many options. # # The lexer should handle the "expected instead of end of file for strings, and interpolation", other expectancies # must be handled by the grammar. The lexer may have enqueued tokens far ahead - the lexer's opinion about this # is not trustworthy. # -# if token == 0 && brace = @lexer.expected -# error += "; expected '#{brace}'" -# end except = Puppet::ParseError.new(error) if token != 0 path = value[:file] except.line = value[:line] except.pos = value[:pos] else # At end of input, use what the lexer thinks is the source file path = lexer.file end except.file = path if path.is_a?(String) && !path.empty? raise except end # Parses a String of pp DSL code. # @todo make it possible to pass a given origin # def parse_string(code) @lexer.string = code _parse() end # Mark the factory wrapped model object with location information - # @todo the lexer produces :line for token, but no offset or length # @return [Puppet::Pops::Model::Factory] the given factory # @api private # def loc(factory, start_locateable, end_locateable = nil) factory.record_position(start_locateable, end_locateable) end - def heredoc_loc(factory, start_locateabke, end_locateable = nil) - factory.record_heredoc_position(start_locatable, end_locatable) - end - - # Associate documentation with the factory wrapped model object. + # Mark the factory wrapped heredoc model object with location information # @return [Puppet::Pops::Model::Factory] the given factory # @api private - def doc factory, doc_string - factory.doc = doc_string + # + def heredoc_loc(factory, start_locateabke, end_locateable = nil) + factory.record_heredoc_position(start_locatable, end_locatable) end def aryfy(o) o = [o] unless o.is_a?(Array) o end def namespace @namestack.join('::') end def namestack(name) @namestack << name end def namepop() @namestack.pop end def add_definition(definition) @definitions << definition.current definition end # Transforms an array of expressions containing literal name expressions to calls if followed by an # expression, or expression list # def transform_calls(expressions) Factory.transform_calls(expressions) end # Transforms a LEFT followed by the result of attribute_operations, this may be a call or an invalid sequence def transform_resource_wo_title(left, resource) Factory.transform_resource_wo_title(left, resource) end # Creates a program with the given body. # def create_program(body) locator = @lexer.locator Factory.PROGRAM(body, definitions, locator) end # Creates an empty program with a single No-op at the input's EOF offset with 0 length. # def create_empty_program() locator = @lexer.locator no_op = Factory.literal(nil) # Create a synthetic NOOP token at EOF offset with 0 size. The lexer does not produce an EOF token that is # visible to the grammar rules. Creating this token is mainly to reuse the positioning logic as it # expects a token decorated with location information. token_sym, token = @lexer.emit_completed([:NOOP,'',0], locator.string.bytesize) loc(no_op, token) # Program with a Noop program = Factory.PROGRAM(no_op, [], locator) program end # Performs the parsing and returns the resulting model. # The lexer holds state, and this is setup with {#parse_string}, or {#parse_file}. # - # TODO: Drop support for parsing a ruby file this way (should be done where it is decided - # which file to load/run (i.e. loaders), and initial file to run - # TODO: deal with options containing origin (i.e. parsing a string from externally known location). - # TODO: should return the model, not a Hostclass - # # @api private # def _parse() begin @yydebug = false main = yyparse(@lexer,:scan) - # #Commented out now because this hides problems in the racc grammar while developing - # # TODO include this when test coverage is good enough. - # rescue Puppet::ParseError => except - # except.line ||= @lexer.line - # except.file ||= @lexer.file - # except.pos ||= @lexer.pos - # raise except - # rescue => except - # raise Puppet::ParseError.new(except.message, @lexer.file, @lexer.line, @lexer.pos, except) end return main ensure @lexer.clear @namestack = [] @definitions = [] end end diff --git a/lib/puppet/pops/patterns.rb b/lib/puppet/pops/patterns.rb index ff174c6c6..aa46d9d06 100644 --- a/lib/puppet/pops/patterns.rb +++ b/lib/puppet/pops/patterns.rb @@ -1,46 +1,44 @@ # The Patterns module contains common regular expression patters for the Puppet DSL language module Puppet::Pops::Patterns # NUMERIC matches hex, octal, decimal, and floating point and captures several parts # 0 = entire matched number, leading and trailing whitespace and sign included # 1 = sign, +, - or nothing # 2 = entire numeric part # 3 = hexadecimal number # 4 = non hex integer portion, possibly with leading 0 (octal) # 5 = floating point part, starts with ".", decimals and optional exponent # # Thus, a hex number has group 3 value, an octal value has group 4 (if it starts with 0), and no group 3 # and a floating point value has group 4 and group 5. # NUMERIC = %r{\A[[:blank:]]*([-+]?)[[:blank:]]*((0[xX][0-9A-Fa-f]+)|(0?\d+)((?:\.\d+)?(?:[eE]-?\d+)?))[[:blank:]]*\z} # ILLEGAL_P3_1_HOSTNAME matches if a hostname contains illegal characters. # This check does not prevent pathological names like 'a....b', '.....', "---". etc. ILLEGAL_HOSTNAME_CHARS = %r{[^-\w.]} # NAME matches a name the same way as the lexer. NAME = %r{\A((::)?[a-z]\w*)(::[a-z]\w*)*\z} # CLASSREF_EXT matches a class reference the same way as the lexer - i.e. the external source form # where each part must start with a capital letter A-Z. - # This name includes hyphen, which may be illegal in some cases. # CLASSREF_EXT = %r{\A((::){0,1}[A-Z][\w]*)+\z} # CLASSREF matches a class reference the way it is represented internally in the # model (i.e. in lower case). - # This name includes hyphen, which may be illegal in some cases. # CLASSREF = %r{\A((::){0,1}[a-z][\w]*)+\z} # DOLLAR_VAR matches a variable name including the initial $ character DOLLAR_VAR = %r{\$(::)?(\w+::)*\w+} # VAR_NAME matches the name part of a variable (The $ character is not included) # Note, that only the final segment may start with an underscore. VAR_NAME = %r{\A(:?(::)?[a-z]\w*)*(:?(::)?[a-z_]\w*)\z} # A Numeric var name must be the decimal number 0, or a decimal number not starting with 0 NUMERIC_VAR_NAME = %r{\A(?:0|(?:[1-9][0-9]*))\z} end diff --git a/lib/puppet/pops/types/type_factory.rb b/lib/puppet/pops/types/type_factory.rb index c436d43c9..45979dd96 100644 --- a/lib/puppet/pops/types/type_factory.rb +++ b/lib/puppet/pops/types/type_factory.rb @@ -1,425 +1,432 @@ # Helper module that makes creation of type objects simpler. # @api public # module Puppet::Pops::Types::TypeFactory @type_calculator = Puppet::Pops::Types::TypeCalculator.new() Types = Puppet::Pops::Types # Produces the Integer type # @api public # def self.integer() Types::PIntegerType.new() end # Produces an Integer range type # @api public # def self.range(from, to) t = Types::PIntegerType.new() # optimize eq with symbol (faster when it is left) t.from = from unless (:default == from || from == 'default') t.to = to unless (:default == to || to == 'default') t end # Produces a Float range type # @api public # def self.float_range(from, to) t = Types::PFloatType.new() # optimize eq with symbol (faster when it is left) t.from = Float(from) unless :default == from || from.nil? t.to = Float(to) unless :default == to || to.nil? t end # Produces the Float type # @api public # def self.float() Types::PFloatType.new() end # Produces the Numeric type # @api public # def self.numeric() Types::PNumericType.new() end # Produces a string representation of the type # @api public # def self.label(t) @type_calculator.string(t) end # Produces the String type, optionally with specific string values # @api public # def self.string(*values) t = Types::PStringType.new() values.each {|v| t.addValues(v) } t end # Produces the Optional type, i.e. a short hand for Variant[T, Undef] def self.optional(optional_type = nil) t = Types::POptionalType.new t.optional_type = type_of(optional_type) t end # Produces the Enum type, optionally with specific string values # @api public # def self.enum(*values) t = Types::PEnumType.new() values.each {|v| t.addValues(v) } t end # Produces the Variant type, optionally with the "one of" types # @api public # def self.variant(*types) t = Types::PVariantType.new() types.each {|v| t.addTypes(type_of(v)) } t end # Produces the Struct type, either a non parameterized instance representing # all structs (i.e. all hashes) or a hash with a given set of keys of String # type (names), bound to a value of a given type. Type may be a Ruby Class, a # Puppet Type, or an instance from which the type is inferred. # def self.struct(name_type_hash = {}) t = Types::PStructType.new name_type_hash.map do |name, type| elem = Types::PStructElement.new if name.is_a?(String) && name.empty? raise ArgumentError, "An empty String can not be used where a String[1, default] is expected" end elem.name = name elem.type = type_of(type) elem end.each {|elem| t.addElements(elem) } t end def self.tuple(*types) t = Types::PTupleType.new types.each {|elem| t.addTypes(type_of(elem)) } t end # Produces the Boolean type # @api public # def self.boolean() Types::PBooleanType.new() end # Produces the Any type # @api public # def self.any() Types::PAnyType.new() end # Produces the Regexp type # @param pattern [Regexp, String, nil] (nil) The regular expression object or # a regexp source string, or nil for bare type # @api public # def self.regexp(pattern = nil) t = Types::PRegexpType.new() if pattern t.pattern = pattern.is_a?(Regexp) ? pattern.inspect[1..-2] : pattern end t.regexp() unless pattern.nil? # compile pattern to catch errors t end def self.pattern(*regular_expressions) t = Types::PPatternType.new() regular_expressions.each do |re| case re when String re_T = Types::PRegexpType.new() re_T.pattern = re re_T.regexp() # compile it to catch errors t.addPatterns(re_T) when Regexp re_T = Types::PRegexpType.new() # Regep.to_s includes options user did not enter and does not escape source # to work either as a string or as a // regexp. The inspect method does a better # job, but includes the // re_T.pattern = re.inspect[1..-2] t.addPatterns(re_T) when Types::PRegexpType t.addPatterns(re.copy) when Types::PPatternType re.patterns.each do |p| t.addPatterns(p.copy) end else raise ArgumentError, "Only String, Regexp, Pattern-Type, and Regexp-Type are allowed: got '#{re.class}" end end t end # Produces the Literal type # @api public # def self.scalar() Types::PScalarType.new() end # Produces a CallableType matching all callables # @api public # def self.all_callables() return Puppet::Pops::Types::PCallableType.new end # Produces a Callable type with one signature without support for a block # Use #with_block, or #with_optional_block to add a block to the callable # If no parameters are given, the Callable will describe a signature # that does not accept parameters. To create a Callable that matches all callables # use {#all_callables}. # # The params is a list of types, where the three last entries may be # optionally followed by min, max count, and a Callable which is taken as the # block_type. # If neither min or max are specified the parameters must match exactly. # A min < params.size means that the difference are optional. # If max > params.size means that the last type repeats. # if max is :default, the max value is unbound (infinity). # # Params are given as a sequence of arguments to {#type_of}. # def self.callable(*params) if Puppet::Pops::Types::TypeCalculator.is_kind_of_callable?(params.last) last_callable = true end block_t = last_callable ? params.pop : nil # compute a size_type for the signature based on the two last parameters if is_range_parameter?(params[-2]) && is_range_parameter?(params[-1]) size_type = range(params[-2], params[-1]) params = params[0, params.size - 2] elsif is_range_parameter?(params[-1]) size_type = range(params[-1], :default) params = params[0, params.size - 1] end types = params.map {|p| type_of(p) } # If the specification requires types, and none were given, a Unit type is used if types.empty? && !size_type.nil? && size_type.range[1] > 0 types << Types::PUnitType.new end # create a signature callable_t = Types::PCallableType.new() tuple_t = tuple(*types) tuple_t.size_type = size_type unless size_type.nil? callable_t.param_types = tuple_t callable_t.block_type = block_t callable_t end def self.with_block(callable, *block_params) callable.block_type = callable(*block_params) callable end def self.with_optional_block(callable, *block_params) callable.block_type = optional(callable(*block_params)) callable end # Produces the abstract type Collection # @api public # def self.collection() Types::PCollectionType.new() end # Produces the Data type # @api public # def self.data() Types::PDataType.new() end # Creates an instance of the Undef type # @api public def self.undef() Types::PNilType.new() end # Creates an instance of the Default type # @api public def self.default() Types::PDefaultType.new() end # Produces an instance of the abstract type PCatalogEntryType def self.catalog_entry() Types::PCatalogEntryType.new() end # Produces a PResourceType with a String type_name A PResourceType with a nil # or empty name is compatible with any other PResourceType. A PResourceType # with a given name is only compatible with a PResourceType with the same # name. (There is no resource-type subtyping in Puppet (yet)). # def self.resource(type_name = nil, title = nil) type = Types::PResourceType.new() type_name = type_name.type_name if type_name.is_a?(Types::PResourceType) - type.type_name = type_name.downcase unless type_name.nil? + type_name = type_name.downcase unless type_name.nil? + type.type_name = type_name + unless type_name.nil? || type_name =~ Puppet::Pops::Patterns::CLASSREF + raise ArgumentError, "Illegal type name '#{type.type_name}'" + end + if type_name.nil? && !title.nil? + raise ArgumentError, "The type name cannot be nil, if title is given" + end type.title = title type end # Produces PHostClassType with a string class_name. A PHostClassType with # nil or empty name is compatible with any other PHostClassType. A # PHostClassType with a given name is only compatible with a PHostClassType # with the same name. # def self.host_class(class_name = nil) type = Types::PHostClassType.new() unless class_name.nil? type.class_name = class_name.sub(/^::/, '') end type end # Produces a type for Array[o] where o is either a type, or an instance for # which a type is inferred. # @api public # def self.array_of(o) type = Types::PArrayType.new() type.element_type = type_of(o) type end # Produces a type for Hash[Scalar, o] where o is either a type, or an # instance for which a type is inferred. # @api public # def self.hash_of(value, key = scalar()) type = Types::PHashType.new() type.key_type = type_of(key) type.element_type = type_of(value) type end # Produces a type for Array[Data] # @api public # def self.array_of_data() type = Types::PArrayType.new() type.element_type = data() type end # Produces a type for Hash[Scalar, Data] # @api public # def self.hash_of_data() type = Types::PHashType.new() type.key_type = scalar() type.element_type = data() type end # Produces a type for Type[T] # @api public # def self.type_type(inst_type = nil) type = Types::PType.new() type.type = inst_type type end # Produce a type corresponding to the class of given unless given is a # String, Class or a PAnyType. When a String is given this is taken as # a classname. # def self.type_of(o) if o.is_a?(Class) @type_calculator.type(o) elsif o.is_a?(Types::PAnyType) o elsif o.is_a?(String) Types::PRuntimeType.new(:runtime => :ruby, :runtime_type_name => o) else @type_calculator.infer_generic(o) end end # Produces a type for a class or infers a type for something that is not a # class # @note # To get the type for the class' class use `TypeCalculator.infer(c)` # # @overload ruby(o) # @param o [Class] produces the type corresponding to the class (e.g. # Integer becomes PIntegerType) # @overload ruby(o) # @param o [Object] produces the type corresponding to the instance class # (e.g. 3 becomes PIntegerType) # # @api public # def self.ruby(o) if o.is_a?(Class) @type_calculator.type(o) else Types::PRuntimeType.new(:runtime => :ruby, :runtime_type_name => o.class.name) end end # Generic creator of a RuntimeType["ruby"] - allows creating the Ruby type # with nil name, or String name. Also see ruby(o) which performs inference, # or mapps a Ruby Class to its name. # def self.ruby_type(class_name = nil) Types::PRuntimeType.new(:runtime => :ruby, :runtime_type_name => class_name) end # Generic creator of a RuntimeType - allows creating the type with nil or # String runtime_type_name. Also see ruby_type(o) and ruby(o). # def self.runtime(runtime=nil, runtime_type_name = nil) runtime = runtime.to_sym if runtime.is_a?(String) Types::PRuntimeType.new(:runtime => runtime, :runtime_type_name => runtime_type_name) end # Sets the accepted size range of a collection if something other than the # default 0 to Infinity is wanted. The semantics for from/to are the same as # for #range # def self.constrain_size(collection_t, from, to) collection_t.size_type = range(from, to) collection_t end # Returns true if the given type t is of valid range parameter type (integer # or literal default). def self.is_range_parameter?(t) t.is_a?(Integer) || t == 'default' || :default == t end end diff --git a/lib/puppet/pops/validation/checker4_0.rb b/lib/puppet/pops/validation/checker4_0.rb index 666ff451d..512786e9a 100644 --- a/lib/puppet/pops/validation/checker4_0.rb +++ b/lib/puppet/pops/validation/checker4_0.rb @@ -1,734 +1,749 @@ # A Validator validates a model. # # Validation is performed on each model element in isolation. Each method should validate the model element's state # but not validate its referenced/contained elements except to check their validity in their respective role. # The intent is to drive the validation with a tree iterator that visits all elements in a model. # # # TODO: Add validation of multiplicities - this is a general validation that can be checked for all # Model objects via their metamodel. (I.e an extra call to multiplicity check in polymorph check). # This is however mostly valuable when validating model to model transformations, and is therefore T.B.D # class Puppet::Pops::Validation::Checker4_0 Issues = Puppet::Pops::Issues Model = Puppet::Pops::Model attr_reader :acceptor # Initializes the validator with a diagnostics producer. This object must respond to # `:will_accept?` and `:accept`. # def initialize(diagnostics_producer) @@check_visitor ||= Puppet::Pops::Visitor.new(nil, "check", 0, 0) @@rvalue_visitor ||= Puppet::Pops::Visitor.new(nil, "rvalue", 0, 0) @@hostname_visitor ||= Puppet::Pops::Visitor.new(nil, "hostname", 1, 2) @@assignment_visitor ||= Puppet::Pops::Visitor.new(nil, "assign", 0, 1) @@query_visitor ||= Puppet::Pops::Visitor.new(nil, "query", 0, 0) @@top_visitor ||= Puppet::Pops::Visitor.new(nil, "top", 1, 1) @@relation_visitor ||= Puppet::Pops::Visitor.new(nil, "relation", 0, 0) @@idem_visitor ||= Puppet::Pops::Visitor.new(self, "idem", 0, 0) @acceptor = diagnostics_producer end # Validates the entire model by visiting each model element and calling `check`. # The result is collected (or acted on immediately) by the configured diagnostic provider/acceptor # given when creating this Checker. # def validate(model) # tree iterate the model, and call check for each element check(model) model.eAllContents.each {|m| check(m) } end # Performs regular validity check def check(o) @@check_visitor.visit_this_0(self, o) end # Performs check if this is a vaid hostname expression # @param single_feature_name [String, nil] the name of a single valued hostname feature of the value's container. e.g. 'parent' def hostname(o, semantic, single_feature_name = nil) @@hostname_visitor.visit_this_2(self, o, semantic, single_feature_name) end # Performs check if this is valid as a query def query(o) @@query_visitor.visit_this_0(self, o) end # Performs check if this is valid as a relationship side def relation(o) @@relation_visitor.visit_this_0(self, o) end # Performs check if this is valid as a rvalue def rvalue(o) @@rvalue_visitor.visit_this_0(self, o) end # Performs check if this is valid as a container of a definition (class, define, node) def top(o, definition) @@top_visitor.visit_this_1(self, o, definition) end # Checks the LHS of an assignment (is it assignable?). # If args[0] is true, assignment via index is checked. # def assign(o, via_index = false) @@assignment_visitor.visit_this_1(self, o, via_index) end # Checks if the expression has side effect ('idem' is latin for 'the same', here meaning that the evaluation state # is known to be unchanged after the expression has been evaluated). The result is not 100% authoritative for # negative answers since analysis of function behavior is not possible. # @return [Boolean] true if expression is known to have no effect on evaluation state # def idem(o) @@idem_visitor.visit_this_0(self, o) end # Returns the last expression in a block, or the expression, if that expression is idem def ends_with_idem(o) if o.is_a?(Puppet::Pops::Model::BlockExpression) last = o.statements[-1] idem(last) ? last : nil else idem(o) ? o : nil end end #---ASSIGNMENT CHECKS def assign_VariableExpression(o, via_index) varname_string = varname_to_s(o.expr) if varname_string =~ Puppet::Pops::Patterns::NUMERIC_VAR_NAME acceptor.accept(Issues::ILLEGAL_NUMERIC_ASSIGNMENT, o, :varname => varname_string) end # Can not assign to something in another namespace (i.e. a '::' in the name is not legal) if acceptor.will_accept? Issues::CROSS_SCOPE_ASSIGNMENT if varname_string =~ /::/ acceptor.accept(Issues::CROSS_SCOPE_ASSIGNMENT, o, :name => varname_string) end end # TODO: Could scan for reassignment of the same variable if done earlier in the same container # Or if assigning to a parameter (more work). # TODO: Investigate if there are invalid cases for += assignment end def assign_AccessExpression(o, via_index) # Are indexed assignments allowed at all ? $x[x] = '...' if acceptor.will_accept? Issues::ILLEGAL_INDEXED_ASSIGNMENT acceptor.accept(Issues::ILLEGAL_INDEXED_ASSIGNMENT, o) else # Then the left expression must be assignable-via-index assign(o.left_expr, true) end end def assign_Object(o, via_index) # Can not assign to anything else (differentiate if this is via index or not) # i.e. 10 = 'hello' vs. 10['x'] = 'hello' (the root is reported as being in error in both cases) # acceptor.accept(via_index ? Issues::ILLEGAL_ASSIGNMENT_VIA_INDEX : Issues::ILLEGAL_ASSIGNMENT, o) end #---CHECKS def check_Object(o) end def check_Factory(o) check(o.current) end def check_AccessExpression(o) # Only min range is checked, all other checks are RT checks as they depend on the resulting type # of the LHS. if o.keys.size < 1 acceptor.accept(Issues::MISSING_INDEX, o) end end def check_AssignmentExpression(o) acceptor.accept(Issues::UNSUPPORTED_OPERATOR, o, {:operator => o.operator}) unless [:'=', :'+=', :'-='].include? o.operator assign(o.left_expr) rvalue(o.right_expr) end # Checks that operation with :+> is contained in a ResourceOverride or Collector. # # Parent of an AttributeOperation can be one of: # * CollectExpression # * ResourceOverride # * ResourceBody (ILLEGAL this is a regular resource expression) # * ResourceDefaults (ILLEGAL) # def check_AttributeOperation(o) if o.operator == :'+>' # Append operator use is constrained parent = o.eContainer unless parent.is_a?(Model::CollectExpression) || parent.is_a?(Model::ResourceOverrideExpression) acceptor.accept(Issues::ILLEGAL_ATTRIBUTE_APPEND, o, {:name=>o.attribute_name, :parent=>parent}) end end rvalue(o.value_expr) end + def check_AttributesOperation(o) + # Append operator use is constrained + parent = o.eContainer + parent = parent.eContainer unless parent.nil? + unless parent.is_a?(Model::ResourceExpression) + acceptor.accept(Issues::UNSUPPORTED_OPERATOR, o, :operator=>'* =>') + end + + rvalue(o.expr) + end + def check_BinaryExpression(o) rvalue(o.left_expr) rvalue(o.right_expr) end def check_BlockExpression(o) o.statements[0..-2].each do |statement| if idem(statement) acceptor.accept(Issues::IDEM_EXPRESSION_NOT_LAST, statement) break # only flag the first end end end def check_CallNamedFunctionExpression(o) case o.functor_expr when Puppet::Pops::Model::QualifiedName # ok nil when Puppet::Pops::Model::RenderStringExpression # helpful to point out this easy to make Epp error acceptor.accept(Issues::ILLEGAL_EPP_PARAMETERS, o) else acceptor.accept(Issues::ILLEGAL_EXPRESSION, o.functor_expr, {:feature=>'function name', :container => o}) end end def check_EppExpression(o) if o.eContainer.is_a?(Puppet::Pops::Model::LambdaExpression) internal_check_no_capture(o.eContainer, o) end end def check_MethodCallExpression(o) unless o.functor_expr.is_a? Model::QualifiedName acceptor.accept(Issues::ILLEGAL_EXPRESSION, o.functor_expr, :feature => 'function name', :container => o) end end def check_CaseExpression(o) rvalue(o.test) # There should only be one LiteralDefault case option value # TODO: Implement this check end def check_CaseOption(o) o.values.each { |v| rvalue(v) } end def check_CollectExpression(o) unless o.type_expr.is_a? Model::QualifiedReference acceptor.accept(Issues::ILLEGAL_EXPRESSION, o.type_expr, :feature=> 'type name', :container => o) end # If a collect expression tries to collect exported resources and storeconfigs is not on # then it will not work... This was checked in the parser previously. This is a runtime checking # thing as opposed to a language thing. if acceptor.will_accept?(Issues::RT_NO_STORECONFIGS) && o.query.is_a?(Model::ExportedQuery) acceptor.accept(Issues::RT_NO_STORECONFIGS, o) end end # Only used for function names, grammar should not be able to produce something faulty, but # check anyway if model is created programatically (it will fail in transformation to AST for sure). def check_NamedAccessExpression(o) name = o.right_expr unless name.is_a? Model::QualifiedName acceptor.accept(Issues::ILLEGAL_EXPRESSION, name, :feature=> 'function name', :container => o.eContainer) end end RESERVED_TYPE_NAMES = { 'type' => true, 'any' => true, 'unit' => true, 'scalar' => true, 'boolean' => true, 'numeric' => true, 'integer' => true, 'float' => true, 'collection' => true, 'array' => true, 'hash' => true, 'tuple' => true, 'struct' => true, 'variant' => true, 'optional' => true, 'enum' => true, 'regexp' => true, 'pattern' => true, 'runtime' => true, } # for 'class', 'define', and function def check_NamedDefinition(o) top(o.eContainer, o) if o.name !~ Puppet::Pops::Patterns::CLASSREF acceptor.accept(Issues::ILLEGAL_DEFINITION_NAME, o, {:name=>o.name}) end if RESERVED_TYPE_NAMES[o.name()] acceptor.accept(Issues::RESERVED_TYPE_NAME, o, {:name => o.name}) end if violator = ends_with_idem(o.body) acceptor.accept(Issues::IDEM_NOT_ALLOWED_LAST, violator, {:container => o}) end end def check_HostClassDefinition(o) check_NamedDefinition(o) internal_check_no_capture(o) internal_check_reserved_params(o) end def check_ResourceTypeDefinition(o) check_NamedDefinition(o) internal_check_no_capture(o) internal_check_reserved_params(o) end def internal_check_capture_last(o) accepted_index = o.parameters.size() -1 o.parameters.each_with_index do |p, index| if p.captures_rest && index != accepted_index acceptor.accept(Issues::CAPTURES_REST_NOT_LAST, p, {:param_name => p.name}) end end end def internal_check_no_capture(o, container = o) o.parameters.each do |p| if p.captures_rest acceptor.accept(Issues::CAPTURES_REST_NOT_SUPPORTED, p, {:container => container, :param_name => p.name}) end end end RESERVED_PARAMETERS = { 'name' => true, 'title' => true, } def internal_check_reserved_params(o) o.parameters.each do |p| if RESERVED_PARAMETERS[p.name] acceptor.accept(Issues::RESERVED_PARAMETER, p, {:container => o, :param_name => p.name}) end end end def check_IfExpression(o) rvalue(o.test) end def check_KeyedEntry(o) rvalue(o.key) rvalue(o.value) # In case there are additional things to forbid than non-rvalues # acceptor.accept(Issues::ILLEGAL_EXPRESSION, o.key, :feature => 'hash key', :container => o.eContainer) end def check_LambdaExpression(o) internal_check_capture_last(o) end def check_LiteralList(o) o.values.each {|v| rvalue(v) } end def check_NodeDefinition(o) # Check that hostnames are valid hostnames (or regular expressions) hostname(o.host_matches, o) hostname(o.parent, o, 'parent') unless o.parent.nil? top(o.eContainer, o) if violator = ends_with_idem(o.body) acceptor.accept(Issues::IDEM_NOT_ALLOWED_LAST, violator, {:container => o}) end unless o.parent.nil? acceptor.accept(Issues::ILLEGAL_NODE_INHERITANCE, o.parent) end end # No checking takes place - all expressions using a QualifiedName need to check. This because the # rules are slightly different depending on the container (A variable allows a numeric start, but not # other names). This means that (if the lexer/parser so chooses) a QualifiedName # can be anything when it represents a Bare Word and evaluates to a String. # def check_QualifiedName(o) end # Checks that the value is a valid UpperCaseWord (a CLASSREF), and optionally if it contains a hypen. # DOH: QualifiedReferences are created with LOWER CASE NAMES at parse time def check_QualifiedReference(o) # Is this a valid qualified name? if o.value !~ Puppet::Pops::Patterns::CLASSREF acceptor.accept(Issues::ILLEGAL_CLASSREF, o, {:name=>o.value}) end end def check_QueryExpression(o) query(o.expr) if o.expr # is optional end def relation_Object(o) rvalue(o) end def relation_CollectExpression(o); end def relation_RelationshipExpression(o); end def check_Parameter(o) if o.name =~ /^[0-9]+$/ acceptor.accept(Issues::ILLEGAL_NUMERIC_PARAMETER, o, :name => o.name) end end #relationship_side: resource # | resourceref # | collection # | variable # | quotedtext # | selector # | casestatement # | hasharrayaccesses def check_RelationshipExpression(o) relation(o.left_expr) relation(o.right_expr) end def check_ResourceExpression(o) - # A resource expression must have a lower case NAME as its type e.g. 'file { ... }' - unless o.type_name.is_a? Model::QualifiedName - acceptor.accept(Issues::ILLEGAL_EXPRESSION, o.type_name, :feature => 'resource type', :container => o) - end + # TODO: Can no longer be asserted + + ## 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_ResourceOverrideExpression(o) + if o.form && o.form != :regular + acceptor.accept(Issues::NOT_VIRTUALIZEABLE, o) + end + end + def check_ReservedWord(o) acceptor.accept(Issues::RESERVED_WORD, o, :word => o.word) end def check_SelectorExpression(o) rvalue(o.left_expr) end def check_SelectorEntry(o) rvalue(o.matching_expr) end def check_UnaryExpression(o) rvalue(o.expr) end def check_UnlessExpression(o) rvalue(o.test) # TODO: Unless may not have an else part that is an IfExpression (grammar denies this though) end # Checks that variable is either strictly 0, or a non 0 starting decimal number, or a valid VAR_NAME def check_VariableExpression(o) # The expression must be a qualified name if !o.expr.is_a?(Model::QualifiedName) acceptor.accept(Issues::ILLEGAL_EXPRESSION, o, :feature => 'name', :container => o) else # name must be either a decimal value, or a valid NAME name = o.expr.value if name[0,1] =~ /[0-9]/ unless name =~ Puppet::Pops::Patterns::NUMERIC_VAR_NAME acceptor.accept(Issues::ILLEGAL_NUMERIC_VAR_NAME, o, :name => name) end else unless name =~ Puppet::Pops::Patterns::VAR_NAME acceptor.accept(Issues::ILLEGAL_VAR_NAME, o, :name => name) end end end end #--- HOSTNAME CHECKS # Transforms Array of host matching expressions into a (Ruby) array of AST::HostName def hostname_Array(o, semantic, single_feature_name) if single_feature_name acceptor.accept(Issues::ILLEGAL_EXPRESSION, o, {:feature=>single_feature_name, :container=>semantic}) end o.each {|x| hostname(x, semantic, false) } end def hostname_String(o, semantic, single_feature_name) # The 3.x checker only checks for illegal characters - if matching /[^-\w.]/ the name is invalid, # but this allows pathological names like "a..b......c", "----" # TODO: Investigate if more illegal hostnames should be flagged. # if o =~ Puppet::Pops::Patterns::ILLEGAL_HOSTNAME_CHARS acceptor.accept(Issues::ILLEGAL_HOSTNAME_CHARS, semantic, :hostname => o) end end def hostname_LiteralValue(o, semantic, single_feature_name) hostname_String(o.value.to_s, o, single_feature_name) end def hostname_ConcatenatedString(o, semantic, single_feature_name) # Puppet 3.1. only accepts a concatenated string without interpolated expressions if the_expr = o.segments.index {|s| s.is_a?(Model::TextExpression) } acceptor.accept(Issues::ILLEGAL_HOSTNAME_INTERPOLATION, o.segments[the_expr].expr) elsif o.segments.size() != 1 # corner case, bad model, concatenation of several plain strings acceptor.accept(Issues::ILLEGAL_HOSTNAME_INTERPOLATION, o) else # corner case, may be ok, but lexer may have replaced with plain string, this is # here if it does not hostname_String(o.segments[0], o.segments[0], false) end end def hostname_QualifiedName(o, semantic, single_feature_name) hostname_String(o.value.to_s, o, single_feature_name) end def hostname_QualifiedReference(o, semantic, single_feature_name) hostname_String(o.value.to_s, o, single_feature_name) end def hostname_LiteralNumber(o, semantic, single_feature_name) # always ok end def hostname_LiteralDefault(o, semantic, single_feature_name) # always ok end def hostname_LiteralRegularExpression(o, semantic, single_feature_name) # always ok end def hostname_Object(o, semantic, single_feature_name) acceptor.accept(Issues::ILLEGAL_EXPRESSION, o, {:feature=> single_feature_name || 'hostname', :container=>semantic}) end #---QUERY CHECKS # Anything not explicitly allowed is flagged as error. def query_Object(o) acceptor.accept(Issues::ILLEGAL_QUERY_EXPRESSION, o) end # Puppet AST only allows == and != # def query_ComparisonExpression(o) acceptor.accept(Issues::ILLEGAL_QUERY_EXPRESSION, o) unless [:'==', :'!='].include? o.operator end # Allows AND, OR, and checks if left/right are allowed in query. def query_BooleanExpression(o) query o.left_expr query o.right_expr end def query_ParenthesizedExpression(o) query(o.expr) end def query_VariableExpression(o); end def query_QualifiedName(o); end def query_LiteralNumber(o); end def query_LiteralString(o); end def query_LiteralBoolean(o); end #---RVALUE CHECKS # By default, all expressions are reported as being rvalues # Implement specific rvalue checks for those that are not. # def rvalue_Expression(o); end - def rvalue_ResourceDefaultsExpression(o); acceptor.accept(Issues::NOT_RVALUE, o) ; end - - def rvalue_ResourceOverrideExpression(o); acceptor.accept(Issues::NOT_RVALUE, o) ; end - def rvalue_CollectExpression(o) ; acceptor.accept(Issues::NOT_RVALUE, o) ; end def rvalue_Definition(o) ; acceptor.accept(Issues::NOT_RVALUE, o) ; end def rvalue_NodeDefinition(o) ; acceptor.accept(Issues::NOT_RVALUE, o) ; end def rvalue_UnaryExpression(o) ; rvalue o.expr ; end #---TOP CHECK def top_NilClass(o, definition) # ok, reached the top, no more parents end def top_Object(o, definition) # fail, reached a container that is not top level acceptor.accept(Issues::NOT_TOP_LEVEL, definition) end def top_BlockExpression(o, definition) # ok, if this is a block representing the body of a class, or is top level top o.eContainer, definition end def top_HostClassDefinition(o, definition) # ok, stop scanning parents end def top_Program(o, definition) # ok end # A LambdaExpression is a BlockExpression, and this method is needed to prevent the polymorph method for BlockExpression # to accept a lambda. # A lambda can not iteratively create classes, nodes or defines as the lambda does not have a closure. # def top_LambdaExpression(o, definition) # fail, stop scanning parents acceptor.accept(Issues::NOT_TOP_LEVEL, definition) end #--IDEM CHECK def idem_Object(o) false end def idem_Nop(o) true end def idem_NilClass(o) true end def idem_Literal(o) true end def idem_LiteralList(o) true end def idem_LiteralHash(o) true end def idem_Factory(o) idem(o.current) end def idem_AccessExpression(o) true end def idem_BinaryExpression(o) true end def idem_RelationshipExpression(o) # Always side effect false end def idem_AssignmentExpression(o) # Always side effect false end # Handles UnaryMinusExpression, NotExpression, VariableExpression def idem_UnaryExpression(o) true end # Allow (no-effect parentheses) to be used around a productive expression def idem_ParenthesizedExpression(o) idem(o.expr) end def idem_RenderExpression(o) false end def idem_RenderStringExpression(o) false end def idem_BlockExpression(o) # productive if there is at least one productive expression ! o.statements.any? {|expr| !idem(expr) } end # Returns true even though there may be interpolated expressions that have side effect. # Report as idem anyway, as it is very bad design to evaluate an interpolated string for its # side effect only. def idem_ConcatenatedString(o) true end # Heredoc is just a string, but may contain interpolated string (which may have side effects). # This is still bad design and should be reported as idem. def idem_HeredocExpression(o) true end # May technically have side effects inside the Selector, but this is bad design - treat as idem def idem_SelectorExpression(o) true end def idem_IfExpression(o) [o.test, o.then_expr, o.else_expr].all? {|e| idem(e) } end # Case expression is idem, if test, and all options are idem def idem_CaseExpression(o) return false if !idem(o.test) ! o.options.any? {|opt| !idem(opt) } end # An option is idem if values and the then_expression are idem def idem_CaseOption(o) return false if o.values.any? { |value| !idem(value) } idem(o.then_expr) end #--- NON POLYMORPH, NON CHECKING CODE # Produces string part of something named, or nil if not a QualifiedName or QualifiedReference # def varname_to_s(o) case o when Model::QualifiedName o.value when Model::QualifiedReference o.value else nil end end end diff --git a/lib/puppet/pops/validation/validator_factory_4_0.rb b/lib/puppet/pops/validation/validator_factory_4_0.rb index 7eae59351..783164016 100644 --- a/lib/puppet/pops/validation/validator_factory_4_0.rb +++ b/lib/puppet/pops/validation/validator_factory_4_0.rb @@ -1,31 +1,30 @@ # Configures validation suitable for 4.0 # class Puppet::Pops::Validation::ValidatorFactory_4_0 < Puppet::Pops::Validation::Factory Issues = Puppet::Pops::Issues # Produces the checker to use def checker diagnostic_producer Puppet::Pops::Validation::Checker4_0.new(diagnostic_producer) end # Produces the label provider to use def label_provider Puppet::Pops::Model::ModelLabelProvider.new() end # Produces the severity producer to use def severity_producer p = super # Configure each issue that should **not** be an error # # Validate as per the current runtime configuration p[Issues::RT_NO_STORECONFIGS_EXPORT] = Puppet[:storeconfigs] ? :ignore : :warning p[Issues::RT_NO_STORECONFIGS] = Puppet[:storeconfigs] ? :ignore : :warning p[Issues::NAME_WITH_HYPHEN] = :error - p[Issues::DEPRECATED_NAME_AS_TYPE] = :error p[Issues::EMPTY_RESOURCE_SPECIALIZATION] = :ignore p end end diff --git a/lib/puppet/type/file.rb b/lib/puppet/type/file.rb index 68635c9d9..9b256d06b 100644 --- a/lib/puppet/type/file.rb +++ b/lib/puppet/type/file.rb @@ -1,934 +1,934 @@ require 'digest/md5' require 'cgi' require 'etc' require 'uri' require 'fileutils' require 'enumerator' require 'pathname' require 'puppet/parameter/boolean' require 'puppet/util/diff' require 'puppet/util/checksums' require 'puppet/util/backups' require 'puppet/util/symbolic_file_mode' Puppet::Type.newtype(:file) do include Puppet::Util::MethodHelper include Puppet::Util::Checksums include Puppet::Util::Backups include Puppet::Util::SymbolicFileMode @doc = "Manages files, including their content, ownership, and permissions. The `file` type can manage normal files, directories, and symlinks; the type should be specified in the `ensure` attribute. Note that symlinks cannot be managed on Windows systems. File contents can be managed directly with the `content` attribute, or downloaded from a remote source using the `source` attribute; the latter can also be used to recursively serve directories (when the `recurse` attribute is set to `true` or `local`). On Windows, note that file contents are managed in binary mode; Puppet never automatically translates line endings. **Autorequires:** If Puppet is managing the user or group that owns a file, the file resource will autorequire them. If Puppet is managing any parent directories of a file, the file resource will autorequire them." feature :manages_symlinks, "The provider can manage symbolic links." def self.title_patterns [ [ /^(.*?)\/*\Z/m, [ [ :path ] ] ] ] end newparam(:path) do desc <<-'EOT' The path to the file to manage. Must be fully qualified. On Windows, the path should include the drive letter and should use `/` as the separator character (rather than `\\`). EOT isnamevar validate do |value| unless Puppet::Util.absolute_path?(value) fail Puppet::Error, "File paths must be fully qualified, not '#{value}'" end end munge do |value| if value.start_with?('//') and ::File.basename(value) == "/" # This is a UNC path pointing to a share, so don't add a trailing slash ::File.expand_path(value) else ::File.join(::File.split(::File.expand_path(value))) end end end newparam(:backup) do desc <<-EOT Whether (and how) file content should be backed up before being replaced. This attribute works best as a resource default in the site manifest (`File { backup => main }`), so it can affect all file resources. * If set to `false`, file content won't be backed up. * If set to a string beginning with `.` (e.g., `.puppet-bak`), Puppet will use copy the file in the same directory with that value as the extension of the backup. (A value of `true` is a synonym for `.puppet-bak`.) * If set to any other string, Puppet will try to back up to a filebucket with that title. See the `filebucket` resource type for more details. (This is the preferred method for backup, since it can be centralized and queried.) Default value: `puppet`, which backs up to a filebucket of the same name. (Puppet automatically creates a **local** filebucket named `puppet` if one doesn't already exist.) Backing up to a local filebucket isn't particularly useful. If you want to make organized use of backups, you will generally want to use the puppet master server's filebucket service. This requires declaring a filebucket resource and a resource default for the `backup` attribute in site.pp: # /etc/puppet/manifests/site.pp filebucket { 'main': path => false, # This is required for remote filebuckets. server => 'puppet.example.com', # Optional; defaults to the configured puppet master. } File { backup => main, } If you are using multiple puppet master servers, you will want to centralize the contents of the filebucket. Either configure your load balancer to direct all filebucket traffic to a single master, or use something like an out-of-band rsync task to synchronize the content on all masters. EOT defaultto "puppet" munge do |value| # I don't really know how this is happening. value = value.shift if value.is_a?(Array) case value when false, "false", :false false when true, "true", ".puppet-bak", :true ".puppet-bak" when String value else self.fail "Invalid backup type #{value.inspect}" end end end newparam(:recurse) do desc "Whether to recursively manage the _contents_ of a directory. This attribute is only used when `ensure => directory` is set. The allowed values are: * `false` --- The default behavior. The contents of the directory will not be automatically managed. * `remote` --- If the `source` attribute is set, Puppet will automatically manage the contents of the source directory (or directories), ensuring that equivalent files and directories exist on the target system and that their contents match. Using `remote` will disable the `purge` attribute, but results in faster catalog application than `recurse => true`. The `source` attribute is mandatory when `recurse => remote`. * `true` --- If the `source` attribute is set, this behaves similarly to `recurse => remote`, automatically managing files from the source directory. This also enables the `purge` attribute, which can delete unmanaged files from a directory. See the description of `purge` for more details. The `source` attribute is not mandatory when using `recurse => true`, so you can enable purging in directories where all files are managed individually. - (Note: `inf` is an out-of-date synonym for `true`.) + (Note: `inf` is a deprecated synonym for `true`.) By default, setting recurse to `remote` or `true` will manage _all_ subdirectories. You can use the `recurselimit` attribute to limit the recursion depth. " newvalues(:true, :false, :inf, :remote) validate { |arg| } munge do |value| newval = super(value) case newval when :true, :inf; true when :false; false when :remote; :remote else self.fail "Invalid recurse value #{value.inspect}" end end end newparam(:recurselimit) do desc "How far Puppet should descend into subdirectories, when using `ensure => directory` and either `recurse => true` or `recurse => remote`. The recursion limit affects which files will be copied from the `source` directory, as well as which files can be purged when `purge => true`. Setting `recurselimit => 0` is the same as setting `recurse => false` --- Puppet will manage the directory, but all of its contents will be treated as unmanaged. Setting `recurselimit => 1` will manage files and directories that are directly inside the directory, but will not manage the contents of any subdirectories. Setting `recurselimit => 2` will manage the direct contents of the directory, as well as the contents of the _first_ level of subdirectories. And so on --- 3 will manage the contents of the second level of subdirectories, etc." newvalues(/^[0-9]+$/) munge do |value| newval = super(value) case newval when Integer, Fixnum, Bignum; value when /^\d+$/; Integer(value) else self.fail "Invalid recurselimit value #{value.inspect}" end end end newparam(:replace, :boolean => true, :parent => Puppet::Parameter::Boolean) do desc "Whether to replace a file or symlink that already exists on the local system but whose content doesn't match what the `source` or `content` attribute specifies. Setting this to false allows file resources to initialize files without overwriting future changes. Note that this only affects content; Puppet will still manage ownership and permissions. Defaults to `true`." defaultto :true end newparam(:force, :boolean => true, :parent => Puppet::Parameter::Boolean) do desc "Perform the file operation even if it will destroy one or more directories. You must use `force` in order to: * `purge` subdirectories * Replace directories with files or links * Remove a directory when `ensure => absent`" defaultto false end newparam(:ignore) do desc "A parameter which omits action on files matching specified patterns during recursion. Uses Ruby's builtin globbing engine, so shell metacharacters are fully supported, e.g. `[a-z]*`. Matches that would descend into the directory structure are ignored, e.g., `*/*`." validate do |value| unless value.is_a?(Array) or value.is_a?(String) or value == false self.devfail "Ignore must be a string or an Array" end end end newparam(:links) do desc "How to handle links during file actions. During file copying, `follow` will copy the target file instead of the link, `manage` will copy the link itself, and `ignore` will just pass it by. When not copying, `manage` and `ignore` behave equivalently (because you cannot really ignore links entirely during local recursion), and `follow` will manage the file to which the link points." newvalues(:follow, :manage) defaultto :manage end newparam(:purge, :boolean => true, :parent => Puppet::Parameter::Boolean) do desc "Whether unmanaged files should be purged. This option only makes sense when `ensure => directory` and `recurse => true`. * When recursively duplicating an entire directory with the `source` attribute, `purge => true` will automatically purge any files that are not in the source directory. * When managing files in a directory as individual resources, setting `purge => true` will purge any files that aren't being specifically managed. If you have a filebucket configured, the purged files will be uploaded, but if you do not, this will destroy data. Unless `force => true` is set, purging will **not** delete directories, although it will delete the files they contain. If `recurselimit` is set and you aren't using `force => true`, purging will obey the recursion limit; files in any subdirectories deeper than the limit will be treated as unmanaged and left alone." defaultto :false end newparam(:sourceselect) do desc "Whether to copy all valid sources, or just the first one. This parameter only affects recursive directory copies; by default, the first valid source is the only one used, but if this parameter is set to `all`, then all valid sources will have all of their contents copied to the local system. If a given file exists in more than one source, the version from the earliest source in the list will be used." defaultto :first newvalues(:first, :all) end newparam(:show_diff, :boolean => true, :parent => Puppet::Parameter::Boolean) do desc "Whether to display differences when the file changes, defaulting to true. This parameter is useful for files that may contain passwords or other secret data, which might otherwise be included in Puppet reports or other insecure outputs. If the global `show_diff` setting is false, then no diffs will be shown even if this parameter is true." defaultto :true end newparam(:validate_cmd) do desc "A command for validating the file's syntax before replacing it. If Puppet would need to rewrite a file due to new `source` or `content`, it will check the new content's validity first. If validation fails, the file resource will fail. This command must have a fully qualified path, and should contain a percent (`%`) token where it would expect an input file. It must exit `0` if the syntax is correct, and non-zero otherwise. The command will be run on the target system while applying the catalog, not on the puppet master. Example: file { '/etc/apache2/apache2.conf': content => 'example', validate_cmd => '/usr/sbin/apache2 -t -f %', } This would replace apache2.conf only if the test returned true. Note that if a validation command requires a `%` as part of its text, you can specify a different placeholder token with the `validate_replacement` attribute." end newparam(:validate_replacement) do desc "The replacement string in a `validate_cmd` that will be replaced with an input file name. Defaults to: `%`" defaultto '%' end # Autorequire the nearest ancestor directory found in the catalog. autorequire(:file) do req = [] path = Pathname.new(self[:path]) if !path.root? # Start at our parent, to avoid autorequiring ourself parents = path.parent.enum_for(:ascend) if found = parents.find { |p| catalog.resource(:file, p.to_s) } req << found.to_s end end # if the resource is a link, make sure the target is created first req << self[:target] if self[:target] req end # Autorequire the owner and group of the file. {:user => :owner, :group => :group}.each do |type, property| autorequire(type) do if @parameters.include?(property) # The user/group property automatically converts to IDs next unless should = @parameters[property].shouldorig val = should[0] if val.is_a?(Integer) or val =~ /^\d+$/ nil else val end end end end CREATORS = [:content, :source, :target] SOURCE_ONLY_CHECKSUMS = [:none, :ctime, :mtime] validate do creator_count = 0 CREATORS.each do |param| creator_count += 1 if self.should(param) end creator_count += 1 if @parameters.include?(:source) self.fail "You cannot specify more than one of #{CREATORS.collect { |p| p.to_s}.join(", ")}" if creator_count > 1 self.fail "You cannot specify a remote recursion without a source" if !self[:source] and self[:recurse] == :remote self.fail "You cannot specify source when using checksum 'none'" if self[:checksum] == :none && !self[:source].nil? SOURCE_ONLY_CHECKSUMS.each do |checksum_type| self.fail "You cannot specify content when using checksum '#{checksum_type}'" if self[:checksum] == checksum_type && !self[:content].nil? end self.warning "Possible error: recurselimit is set but not recurse, no recursion will happen" if !self[:recurse] and self[:recurselimit] provider.validate if provider.respond_to?(:validate) end def self.[](path) return nil unless path super(path.gsub(/\/+/, '/').sub(/\/$/, '')) end def self.instances return [] end # Determine the user to write files as. def asuser if self.should(:owner) and ! self.should(:owner).is_a?(Symbol) writeable = Puppet::Util::SUIDManager.asuser(self.should(:owner)) { FileTest.writable?(::File.dirname(self[:path])) } # If the parent directory is writeable, then we execute # as the user in question. Otherwise we'll rely on # the 'owner' property to do things. asuser = self.should(:owner) if writeable end asuser end def bucket return @bucket if @bucket backup = self[:backup] return nil unless backup return nil if backup =~ /^\./ unless catalog or backup == "puppet" fail "Can not find filebucket for backups without a catalog" end unless catalog and filebucket = catalog.resource(:filebucket, backup) or backup == "puppet" fail "Could not find filebucket #{backup} specified in backup" end return default_bucket unless filebucket @bucket = filebucket.bucket @bucket end def default_bucket Puppet::Type.type(:filebucket).mkdefaultbucket.bucket end # Does the file currently exist? Just checks for whether # we have a stat def exist? stat ? true : false end def present?(current_values) super && current_values[:ensure] != :false end # We have to do some extra finishing, to retrieve our bucket if # there is one. def finish # Look up our bucket, if there is one bucket super end # Create any children via recursion or whatever. def eval_generate return [] unless self.recurse? recurse end def ancestors ancestors = Pathname.new(self[:path]).enum_for(:ascend).map(&:to_s) ancestors.delete(self[:path]) ancestors end def flush # We want to make sure we retrieve metadata anew on each transaction. @parameters.each do |name, param| param.flush if param.respond_to?(:flush) end @stat = :needs_stat end def initialize(hash) # Used for caching clients @clients = {} super # If they've specified a source, we get our 'should' values # from it. unless self[:ensure] if self[:target] self[:ensure] = :link elsif self[:content] self[:ensure] = :file end end @stat = :needs_stat end # Configure discovered resources to be purged. def mark_children_for_purging(children) children.each do |name, child| next if child[:source] child[:ensure] = :absent end end # Create a new file or directory object as a child to the current # object. def newchild(path) full_path = ::File.join(self[:path], path) # Add some new values to our original arguments -- these are the ones # set at initialization. We specifically want to exclude any param # values set by the :source property or any default values. # LAK:NOTE This is kind of silly, because the whole point here is that # the values set at initialization should live as long as the resource # but values set by default or by :source should only live for the transaction # or so. Unfortunately, we don't have a straightforward way to manage # the different lifetimes of this data, so we kludge it like this. # The right-side hash wins in the merge. options = @original_parameters.merge(:path => full_path).reject { |param, value| value.nil? } # These should never be passed to our children. [:parent, :ensure, :recurse, :recurselimit, :target, :alias, :source].each do |param| options.delete(param) if options.include?(param) end self.class.new(options) end # Files handle paths specially, because they just lengthen their # path names, rather than including the full parent's title each # time. def pathbuilder # We specifically need to call the method here, so it looks # up our parent in the catalog graph. if parent = parent() # We only need to behave specially when our parent is also # a file if parent.is_a?(self.class) # Remove the parent file name list = parent.pathbuilder list.pop # remove the parent's path info return list << self.ref else return super end else return [self.ref] end end # Recursively generate a list of file resources, which will # be used to copy remote files, manage local files, and/or make links # to map to another directory. def recurse children = (self[:recurse] == :remote) ? {} : recurse_local if self[:target] recurse_link(children) elsif self[:source] recurse_remote(children) end # If we're purging resources, then delete any resource that isn't on the # remote system. mark_children_for_purging(children) if self.purge? # REVISIT: sort_by is more efficient? result = children.values.sort { |a, b| a[:path] <=> b[:path] } remove_less_specific_files(result) end # This is to fix bug #2296, where two files recurse over the same # set of files. It's a rare case, and when it does happen you're # not likely to have many actual conflicts, which is good, because # this is a pretty inefficient implementation. def remove_less_specific_files(files) # REVISIT: is this Windows safe? AltSeparator? mypath = self[:path].split(::File::Separator) other_paths = catalog.vertices. select { |r| r.is_a?(self.class) and r[:path] != self[:path] }. collect { |r| r[:path].split(::File::Separator) }. select { |p| p[0,mypath.length] == mypath } return files if other_paths.empty? files.reject { |file| path = file[:path].split(::File::Separator) other_paths.any? { |p| path[0,p.length] == p } } end # A simple method for determining whether we should be recursing. def recurse? self[:recurse] == true or self[:recurse] == :remote end # Recurse the target of the link. def recurse_link(children) perform_recursion(self[:target]).each do |meta| if meta.relative_path == "." self[:ensure] = :directory next end children[meta.relative_path] ||= newchild(meta.relative_path) if meta.ftype == "directory" children[meta.relative_path][:ensure] = :directory else children[meta.relative_path][:ensure] = :link children[meta.relative_path][:target] = meta.full_path end end children end # Recurse the file itself, returning a Metadata instance for every found file. def recurse_local result = perform_recursion(self[:path]) return {} unless result result.inject({}) do |hash, meta| next hash if meta.relative_path == "." hash[meta.relative_path] = newchild(meta.relative_path) hash end end # Recurse against our remote file. def recurse_remote(children) sourceselect = self[:sourceselect] total = self[:source].collect do |source| next unless result = perform_recursion(source) return if top = result.find { |r| r.relative_path == "." } and top.ftype != "directory" result.each { |data| data.source = "#{source}/#{data.relative_path}" } break result if result and ! result.empty? and sourceselect == :first result end.flatten.compact # This only happens if we have sourceselect == :all unless sourceselect == :first found = [] total.reject! do |data| result = found.include?(data.relative_path) found << data.relative_path unless found.include?(data.relative_path) result end end total.each do |meta| if meta.relative_path == "." parameter(:source).metadata = meta next end children[meta.relative_path] ||= newchild(meta.relative_path) children[meta.relative_path][:source] = meta.source if meta.ftype == "file" children[meta.relative_path][:checksum] = Puppet[:digest_algorithm].to_sym end children[meta.relative_path].parameter(:source).metadata = meta end children end def perform_recursion(path) Puppet::FileServing::Metadata.indirection.search( path, :links => self[:links], :recurse => (self[:recurse] == :remote ? true : self[:recurse]), :recurselimit => self[:recurselimit], :ignore => self[:ignore], :checksum_type => (self[:source] || self[:content]) ? self[:checksum] : :none, :environment => catalog.environment ) end # Back up and remove the file or directory at `self[:path]`. # # @param [Symbol] should The file type replacing the current content. # @return [Boolean] True if the file was removed, else False # @raises [fail???] If the current file isn't one of %w{file link directory} and can't be removed. def remove_existing(should) wanted_type = should.to_s current_type = read_current_type if current_type.nil? return false end if can_backup?(current_type) backup_existing end if wanted_type != "link" and current_type == wanted_type return false end case current_type when "directory" return remove_directory(wanted_type) when "link", "file" return remove_file(current_type, wanted_type) else self.fail "Could not back up files of type #{current_type}" end end def retrieve if source = parameter(:source) source.copy_source_values end super end # Set the checksum, from another property. There are multiple # properties that modify the contents of a file, and they need the # ability to make sure that the checksum value is in sync. def setchecksum(sum = nil) if @parameters.include? :checksum if sum @parameters[:checksum].checksum = sum else # If they didn't pass in a sum, then tell checksum to # figure it out. currentvalue = @parameters[:checksum].retrieve @parameters[:checksum].checksum = currentvalue end end end # Should this thing be a normal file? This is a relatively complex # way of determining whether we're trying to create a normal file, # and it's here so that the logic isn't visible in the content property. def should_be_file? return true if self[:ensure] == :file # I.e., it's set to something like "directory" return false if e = self[:ensure] and e != :present # The user doesn't really care, apparently if self[:ensure] == :present return true unless s = stat return(s.ftype == "file" ? true : false) end # If we've gotten here, then :ensure isn't set return true if self[:content] return true if stat and stat.ftype == "file" false end # Stat our file. Depending on the value of the 'links' attribute, we # use either 'stat' or 'lstat', and we expect the properties to use the # resulting stat object accordingly (mostly by testing the 'ftype' # value). # # We use the initial value :needs_stat to ensure we only stat the file once, # but can also keep track of a failed stat (@stat == nil). This also allows # us to re-stat on demand by setting @stat = :needs_stat. def stat return @stat unless @stat == :needs_stat method = :stat # Files are the only types that support links if (self.class.name == :file and self[:links] != :follow) or self.class.name == :tidy method = :lstat end @stat = begin Puppet::FileSystem.send(method, self[:path]) rescue Errno::ENOENT => error nil rescue Errno::ENOTDIR => error nil rescue Errno::EACCES => error warning "Could not stat; permission denied" nil end end def to_resource resource = super resource.delete(:target) if resource[:target] == :notlink resource end # Write out the file. Requires the property name for logging. # Write will be done by the content property, along with checksum computation def write(property) remove_existing(:file) mode = self.should(:mode) # might be nil mode_int = mode ? symbolic_mode_to_int(mode, Puppet::Util::DEFAULT_POSIX_MODE) : nil if write_temporary_file? Puppet::Util.replace_file(self[:path], mode_int) do |file| file.binmode content_checksum = write_content(file) file.flush fail_if_checksum_is_wrong(file.path, content_checksum) if validate_checksum? if self[:validate_cmd] output = Puppet::Util::Execution.execute(self[:validate_cmd].gsub(self[:validate_replacement], file.path), :failonfail => true, :combine => true) output.split(/\n/).each { |line| self.debug(line) } end end else umask = mode ? 000 : 022 Puppet::Util.withumask(umask) { ::File.open(self[:path], 'wb', mode_int ) { |f| write_content(f) } } end # make sure all of the modes are actually correct property_fix end private # @return [String] The type of the current file, cast to a string. def read_current_type stat_info = stat if stat_info stat_info.ftype.to_s else nil end end # @return [Boolean] If the current file can be backed up and needs to be backed up. def can_backup?(type) if type == "directory" and not force? # (#18110) Directories cannot be removed without :force, so it doesn't # make sense to back them up. false else true end end # @return [Boolean] True if the directory was removed # @api private def remove_directory(wanted_type) if force? debug "Removing existing directory for replacement with #{wanted_type}" FileUtils.rmtree(self[:path]) stat_needed true else notice "Not removing directory; use 'force' to override" false end end # @return [Boolean] if the file was removed (which is always true currently) # @api private def remove_file(current_type, wanted_type) debug "Removing existing #{current_type} for replacement with #{wanted_type}" Puppet::FileSystem.unlink(self[:path]) stat_needed true end def stat_needed @stat = :needs_stat end # Back up the existing file at a given prior to it being removed # @api private # @raise [Puppet::Error] if the file backup failed # @return [void] def backup_existing unless perform_backup raise Puppet::Error, "Could not back up; will not replace" end end # Should we validate the checksum of the file we're writing? def validate_checksum? self[:checksum] !~ /time/ end # Make sure the file we wrote out is what we think it is. def fail_if_checksum_is_wrong(path, content_checksum) newsum = parameter(:checksum).sum_file(path) return if [:absent, nil, content_checksum].include?(newsum) self.fail "File written to disk did not match checksum; discarding changes (#{content_checksum} vs #{newsum})" end # write the current content. Note that if there is no content property # simply opening the file with 'w' as done in write is enough to truncate # or write an empty length file. def write_content(file) (content = property(:content)) && content.write(file) end def write_temporary_file? # unfortunately we don't know the source file size before fetching it # so let's assume the file won't be empty (c = property(:content) and c.length) || @parameters[:source] end # There are some cases where all of the work does not get done on # file creation/modification, so we have to do some extra checking. def property_fix properties.each do |thing| next unless [:mode, :owner, :group, :seluser, :selrole, :seltype, :selrange].include?(thing.name) # Make sure we get a new stat objct @stat = :needs_stat currentvalue = thing.retrieve thing.sync unless thing.safe_insync?(currentvalue) end end end # We put all of the properties in separate files, because there are so many # of them. The order these are loaded is important, because it determines # the order they are in the property lit. require 'puppet/type/file/checksum' require 'puppet/type/file/content' # can create the file require 'puppet/type/file/source' # can create the file require 'puppet/type/file/target' # creates a different type of file require 'puppet/type/file/ensure' # can create the file require 'puppet/type/file/owner' require 'puppet/type/file/group' require 'puppet/type/file/mode' require 'puppet/type/file/type' require 'puppet/type/file/selcontext' # SELinux file context require 'puppet/type/file/ctime' require 'puppet/type/file/mtime' diff --git a/spec/integration/parser/future_compiler_spec.rb b/spec/integration/parser/future_compiler_spec.rb index e6cc08f38..4f8b35a8d 100644 --- a/spec/integration/parser/future_compiler_spec.rb +++ b/spec/integration/parser/future_compiler_spec.rb @@ -1,749 +1,750 @@ require 'spec_helper' require 'puppet/pops' require 'puppet/parser/parser_factory' require 'puppet_spec/compiler' require 'puppet_spec/pops' require 'puppet_spec/scope' require 'matchers/resource' require 'rgen/metamodel_builder' # Test compilation using the future evaluator describe "Puppet::Parser::Compiler" do include PuppetSpec::Compiler include Matchers::Resource before :each do Puppet[:parser] = 'future' end describe "the compiler when using future parser and evaluator" do it "should be able to determine the configuration version from a local version control repository" do pending("Bug #14071 about semantics of Puppet::Util::Execute on Windows", :if => Puppet.features.microsoft_windows?) do # This should always work, because we should always be # in the puppet repo when we run this. version = %x{git rev-parse HEAD}.chomp Puppet.settings[:config_version] = 'git rev-parse HEAD' compiler = Puppet::Parser::Compiler.new(Puppet::Node.new("testnode")) compiler.catalog.version.should == version end end it "should not create duplicate resources when a class is referenced both directly and indirectly by the node classifier (4792)" do node = Puppet::Node.new("testnodex") node.classes = ['foo', 'bar'] catalog = compile_to_catalog(<<-PP, node) class foo { notify { foo_notify: } include bar } class bar { notify { bar_notify: } } PP catalog = Puppet::Parser::Compiler.compile(node) expect(catalog).to have_resource("Notify[foo_notify]") expect(catalog).to have_resource("Notify[bar_notify]") end it 'applies defaults for defines with qualified names (PUP-2302)' do catalog = compile_to_catalog(<<-CODE) define my::thing($msg = 'foo') { notify {'check_me': message => $msg } } My::Thing { msg => 'evoe' } my::thing { 'name': } CODE expect(catalog).to have_resource("Notify[check_me]").with_parameter(:message, "evoe") end it 'does not apply defaults from dynamic scopes (PUP-867)' do catalog = compile_to_catalog(<<-CODE) class a { Notify { message => "defaulted" } include b notify { bye: } } class b { notify { hi: } } include a CODE expect(catalog).to have_resource("Notify[hi]").with_parameter(:message, nil) expect(catalog).to have_resource("Notify[bye]").with_parameter(:message, "defaulted") end it 'gets default from inherited class (PUP-867)' do catalog = compile_to_catalog(<<-CODE) class a { Notify { message => "defaulted" } include c notify { bye: } } class b { Notify { message => "inherited" } } class c inherits b { notify { hi: } } include a CODE expect(catalog).to have_resource("Notify[hi]").with_parameter(:message, "inherited") expect(catalog).to have_resource("Notify[bye]").with_parameter(:message, "defaulted") end it 'looks up default parameter values from inherited class (PUP-2532)' do catalog = compile_to_catalog(<<-CODE) class a { Notify { message => "defaulted" } include c notify { bye: } } class b { Notify { message => "inherited" } } class c inherits b { notify { hi: } } include a notify {hi_test: message => Notify[hi][message] } notify {bye_test: message => Notify[bye][message] } CODE expect(catalog).to have_resource("Notify[hi_test]").with_parameter(:message, "inherited") expect(catalog).to have_resource("Notify[bye_test]").with_parameter(:message, "defaulted") end it 'does not allow override of class parameters using a resource override expression' do expect do compile_to_catalog(<<-CODE) Class[a] { x => 2} CODE end.to raise_error(/Resource Override can only.*got: Class\[a\].*/) end describe "when resolving class references" do it "should not favor local scope (with class included in topscope)" do catalog = compile_to_catalog(<<-PP) class experiment { class baz { } notify {"x" : require => Class[Baz] } notify {"y" : require => Class[Experiment::Baz] } } class baz { } include baz include experiment include experiment::baz PP expect(catalog).to have_resource("Notify[x]").with_parameter(:require, be_resource("Class[Baz]")) expect(catalog).to have_resource("Notify[y]").with_parameter(:require, be_resource("Class[Experiment::Baz]")) end it "should not favor local scope, (with class not included in topscope)" do catalog = compile_to_catalog(<<-PP) class experiment { class baz { } notify {"x" : require => Class[Baz] } notify {"y" : require => Class[Experiment::Baz] } } class baz { } include experiment include experiment::baz PP expect(catalog).to have_resource("Notify[x]").with_parameter(:require, be_resource("Class[Baz]")) expect(catalog).to have_resource("Notify[y]").with_parameter(:require, be_resource("Class[Experiment::Baz]")) end end describe "(ticket #13349) when explicitly specifying top scope" do ["class {'::bar::baz':}", "include ::bar::baz"].each do |include| describe "with #{include}" do it "should find the top level class" do catalog = compile_to_catalog(<<-MANIFEST) class { 'foo::test': } class foo::test { #{include} } class bar::baz { notify { 'good!': } } class foo::bar::baz { notify { 'bad!': } } MANIFEST expect(catalog).to have_resource("Class[Bar::Baz]") expect(catalog).to have_resource("Notify[good!]") expect(catalog).to_not have_resource("Class[Foo::Bar::Baz]") expect(catalog).to_not have_resource("Notify[bad!]") end end end end it "should recompute the version after input files are re-parsed" do Puppet[:code] = 'class foo { }' Time.stubs(:now).returns(1) node = Puppet::Node.new('mynode') Puppet::Parser::Compiler.compile(node).version.should == 1 Time.stubs(:now).returns(2) Puppet::Parser::Compiler.compile(node).version.should == 1 # no change because files didn't change Puppet::Resource::TypeCollection.any_instance.stubs(:stale?).returns(true).then.returns(false) # pretend change Puppet::Parser::Compiler.compile(node).version.should == 2 end ['define', 'class', 'node'].each do |thing| it "'#{thing}' is not allowed inside evaluated conditional constructs" do expect do compile_to_catalog(<<-PP) if true { #{thing} foo { } notify { decoy: } } PP end.to raise_error(Puppet::Error, /Classes, definitions, and nodes may only appear at toplevel/) end it "'#{thing}' is not allowed inside un-evaluated conditional constructs" do expect do compile_to_catalog(<<-PP) if false { #{thing} foo { } notify { decoy: } } PP end.to raise_error(Puppet::Error, /Classes, definitions, and nodes may only appear at toplevel/) end end describe "relationships can be formed" do def extract_name(ref) ref.sub(/File\[(\w+)\]/, '\1') end def assert_creates_relationships(relationship_code, expectations) base_manifest = <<-MANIFEST file { [a,b,c]: mode => 0644, } file { [d,e]: mode => 0755, } MANIFEST catalog = compile_to_catalog(base_manifest + relationship_code) resources = catalog.resources.select { |res| res.type == 'File' } actual_relationships, actual_subscriptions = [:before, :notify].map do |relation| resources.map do |res| dependents = Array(res[relation]) dependents.map { |ref| [res.title, extract_name(ref)] } end.inject(&:concat) end actual_relationships.should =~ (expectations[:relationships] || []) actual_subscriptions.should =~ (expectations[:subscriptions] || []) end it "of regular type" do assert_creates_relationships("File[a] -> File[b]", :relationships => [['a','b']]) end it "of subscription type" do assert_creates_relationships("File[a] ~> File[b]", :subscriptions => [['a', 'b']]) end it "between multiple resources expressed as resource with multiple titles" do assert_creates_relationships("File[a,b] -> File[c,d]", :relationships => [['a', 'c'], ['b', 'c'], ['a', 'd'], ['b', 'd']]) end it "between collection expressions" do assert_creates_relationships("File <| mode == 0644 |> -> File <| mode == 0755 |>", :relationships => [['a', 'd'], ['b', 'd'], ['c', 'd'], ['a', 'e'], ['b', 'e'], ['c', 'e']]) end it "between resources expressed as Strings" do assert_creates_relationships("'File[a]' -> 'File[b]'", :relationships => [['a', 'b']]) end it "between resources expressed as variables" do assert_creates_relationships(<<-MANIFEST, :relationships => [['a', 'b']]) $var = File[a] $var -> File[b] MANIFEST end it "between resources expressed as case statements" do assert_creates_relationships(<<-MANIFEST, :relationships => [['s1', 't2']]) $var = 10 case $var { 10: { file { s1: } } 12: { file { s2: } } } -> case $var + 2 { 10: { file { t1: } } 12: { file { t2: } } } MANIFEST end it "using deep access in array" do assert_creates_relationships(<<-MANIFEST, :relationships => [['a', 'b']]) $var = [ [ [ File[a], File[b] ] ] ] $var[0][0][0] -> $var[0][0][1] MANIFEST end it "using deep access in hash" do assert_creates_relationships(<<-MANIFEST, :relationships => [['a', 'b']]) $var = {'foo' => {'bar' => {'source' => File[a], 'target' => File[b]}}} $var[foo][bar][source] -> $var[foo][bar][target] MANIFEST end it "using resource declarations" do assert_creates_relationships("file { l: } -> file { r: }", :relationships => [['l', 'r']]) end it "between entries in a chain of relationships" do assert_creates_relationships("File[a] -> File[b] ~> File[c] <- File[d] <~ File[e]", :relationships => [['a', 'b'], ['d', 'c']], :subscriptions => [['b', 'c'], ['e', 'd']]) end end context "when dealing with variable references" do it 'an initial underscore in a variable name is ok' do catalog = compile_to_catalog(<<-MANIFEST) class a { $_a = 10} include a notify { 'test': message => $a::_a } MANIFEST expect(catalog).to have_resource("Notify[test]").with_parameter(:message, 10) end it 'an initial underscore in not ok if elsewhere than last segment' do expect do catalog = compile_to_catalog(<<-MANIFEST) class a { $_a = 10} include a notify { 'test': message => $_a::_a } MANIFEST end.to raise_error(/Illegal variable name/) end it 'a missing variable as default value becomes undef' do + # strict variables not on, catalog = compile_to_catalog(<<-MANIFEST) - class a ($b=$x) { notify {$b: message=>'meh'} } + class a ($b=$x) { notify {test: message=>"yes ${undef == $b}" } } include a MANIFEST - expect(catalog).to have_resource("Notify[undef]").with_parameter(:message, "meh") + expect(catalog).to have_resource("Notify[test]").with_parameter(:message, "yes true") end end context 'when working with the trusted data hash' do context 'and have opted in to hashed_node_data' do before :each do Puppet[:trusted_node_data] = true end it 'should make $trusted available' do node = Puppet::Node.new("testing") node.trusted_data = { "data" => "value" } catalog = compile_to_catalog(<<-MANIFEST, node) notify { 'test': message => $trusted[data] } MANIFEST expect(catalog).to have_resource("Notify[test]").with_parameter(:message, "value") end it 'should not allow assignment to $trusted' do node = Puppet::Node.new("testing") node.trusted_data = { "data" => "value" } expect do compile_to_catalog(<<-MANIFEST, node) $trusted = 'changed' notify { 'test': message => $trusted == 'changed' } MANIFEST end.to raise_error(Puppet::Error, /Attempt to assign to a reserved variable name: 'trusted'/) end end context 'and have not opted in to hashed_node_data' do before :each do Puppet[:trusted_node_data] = false end it 'should not make $trusted available' do node = Puppet::Node.new("testing") node.trusted_data = { "data" => "value" } catalog = compile_to_catalog(<<-MANIFEST, node) notify { 'test': message => ($trusted == undef) } MANIFEST expect(catalog).to have_resource("Notify[test]").with_parameter(:message, true) end it 'should allow assignment to $trusted' do catalog = compile_to_catalog(<<-MANIFEST) $trusted = 'changed' notify { 'test': message => $trusted == 'changed' } MANIFEST expect(catalog).to have_resource("Notify[test]").with_parameter(:message, true) end end end context 'when using typed parameters in definition' do it 'accepts type compliant arguments' do catalog = compile_to_catalog(<<-MANIFEST) define foo(String $x) { } foo { 'test': x =>'say friend' } MANIFEST expect(catalog).to have_resource("Foo[test]").with_parameter(:x, 'say friend') end it 'accepts anything when parameters are untyped' do expect do catalog = compile_to_catalog(<<-MANIFEST) define foo($a, $b, $c) { } foo { 'test': a => String, b=>10, c=>undef } MANIFEST end.to_not raise_error() end it 'denies non type compliant arguments' do expect do catalog = compile_to_catalog(<<-MANIFEST) define foo(Integer $x) { } foo { 'test': x =>'say friend' } MANIFEST end.to raise_error(/type Integer, got String/) end it 'denies non type compliant default argument' do expect do catalog = compile_to_catalog(<<-MANIFEST) define foo(Integer $x = 'pow') { } foo { 'test': } MANIFEST end.to raise_error(/type Integer, got String/) end it 'accepts a Resource as a Type' do catalog = compile_to_catalog(<<-MANIFEST) define foo(Type[Bar] $x) { notify { 'test': message => $x[text] } } define bar($text) { } bar { 'joke': text => 'knock knock' } foo { 'test': x => Bar[joke] } MANIFEST expect(catalog).to have_resource("Notify[test]").with_parameter(:message, 'knock knock') end end context 'when using typed parameters in class' do it 'accepts type compliant arguments' do catalog = compile_to_catalog(<<-MANIFEST) class foo(String $x) { } class { 'foo': x =>'say friend' } MANIFEST expect(catalog).to have_resource("Class[Foo]").with_parameter(:x, 'say friend') end it 'accepts anything when parameters are untyped' do expect do catalog = compile_to_catalog(<<-MANIFEST) class foo($a, $b, $c) { } class { 'foo': a => String, b=>10, c=>undef } MANIFEST end.to_not raise_error() end it 'denies non type compliant arguments' do expect do catalog = compile_to_catalog(<<-MANIFEST) class foo(Integer $x) { } class { 'foo': x =>'say friend' } MANIFEST end.to raise_error(/type Integer, got String/) end it 'denies non type compliant default argument' do expect do catalog = compile_to_catalog(<<-MANIFEST) class foo(Integer $x = 'pow') { } class { 'foo': } MANIFEST end.to raise_error(/type Integer, got String/) end it 'accepts a Resource as a Type' do catalog = compile_to_catalog(<<-MANIFEST) class foo(Type[Bar] $x) { notify { 'test': message => $x[text] } } define bar($text) { } bar { 'joke': text => 'knock knock' } class { 'foo': x => Bar[joke] } MANIFEST expect(catalog).to have_resource("Notify[test]").with_parameter(:message, 'knock knock') end end context 'when using typed parameters in lambdas' do it 'accepts type compliant arguments' do catalog = compile_to_catalog(<<-MANIFEST) with('value') |String $x| { notify { "$x": } } MANIFEST expect(catalog).to have_resource("Notify[value]") end it 'handles an array as a single argument' do catalog = compile_to_catalog(<<-MANIFEST) with(['value', 'second']) |$x| { notify { "${x[0]} ${x[1]}": } } MANIFEST expect(catalog).to have_resource("Notify[value second]") end it 'denies when missing required arguments' do expect do compile_to_catalog(<<-MANIFEST) with(1) |$x, $y| { } MANIFEST end.to raise_error(/Parameter \$y is required but no value was given/m) end it 'accepts anything when parameters are untyped' do catalog = compile_to_catalog(<<-MANIFEST) ['value', 1, true, undef].each |$x| { notify { "value: $x": } } MANIFEST expect(catalog).to have_resource("Notify[value: value]") expect(catalog).to have_resource("Notify[value: 1]") expect(catalog).to have_resource("Notify[value: true]") expect(catalog).to have_resource("Notify[value: ]") end it 'accepts type-compliant, slurped arguments' do catalog = compile_to_catalog(<<-MANIFEST) with(1, 2) |Integer *$x| { notify { "${$x[0] + $x[1]}": } } MANIFEST expect(catalog).to have_resource("Notify[3]") end it 'denies non-type-compliant arguments' do expect do compile_to_catalog(<<-MANIFEST) with(1) |String $x| { } MANIFEST end.to raise_error(/expected.*String.*actual.*Integer/m) end it 'denies non-type-compliant, slurped arguments' do expect do compile_to_catalog(<<-MANIFEST) with(1, "hello") |Integer *$x| { } MANIFEST end.to raise_error(/called with mis-matched arguments.*expected.*Integer.*actual.*Integer, String/m) end it 'denies non-type-compliant default argument' do expect do compile_to_catalog(<<-MANIFEST) with(1) |$x, String $defaulted = 1| { notify { "${$x + $defaulted}": }} MANIFEST end.to raise_error(/expected.*Any.*String.*actual.*Integer.*Integer/m) end it 'raises an error when a default argument value is an incorrect type and there are no arguments passed' do expect do compile_to_catalog(<<-MANIFEST) with() |String $defaulted = 1| {} MANIFEST end.to raise_error(/expected.*String.*actual.*Integer/m) end it 'raises an error when the default argument for a slurped parameter is an incorrect type' do expect do compile_to_catalog(<<-MANIFEST) with() |String *$defaulted = 1| {} MANIFEST end.to raise_error(/expected.*String.*actual.*Integer/m) end it 'allows using an array as the default slurped value' do catalog = compile_to_catalog(<<-MANIFEST) with() |String *$defaulted = [hi]| { notify { $defaulted[0]: } } MANIFEST expect(catalog).to have_resource('Notify[hi]') end it 'allows using a value of the type as the default slurped value' do catalog = compile_to_catalog(<<-MANIFEST) with() |String *$defaulted = hi| { notify { $defaulted[0]: } } MANIFEST expect(catalog).to have_resource('Notify[hi]') end it 'allows specifying the type of a slurped parameter as an array' do catalog = compile_to_catalog(<<-MANIFEST) with() |Array[String] *$defaulted = hi| { notify { $defaulted[0]: } } MANIFEST expect(catalog).to have_resource('Notify[hi]') end it 'raises an error when the number of default values does not match the parameter\'s size specification' do expect do compile_to_catalog(<<-MANIFEST) with() |Array[String, 2] *$defaulted = hi| { } MANIFEST end.to raise_error(/expected.*arg count \{2,\}.*actual.*arg count \{1\}/m) end it 'raises an error when the number of passed values does not match the parameter\'s size specification' do expect do compile_to_catalog(<<-MANIFEST) with(hi) |Array[String, 2] *$passed| { } MANIFEST end.to raise_error(/expected.*arg count \{2,\}.*actual.*arg count \{1\}/m) end it 'matches when the number of arguments passed for a slurp parameter match the size specification' do catalog = compile_to_catalog(<<-MANIFEST) with(hi, bye) |Array[String, 2] *$passed| { $passed.each |$n| { notify { $n: } } } MANIFEST expect(catalog).to have_resource('Notify[hi]') expect(catalog).to have_resource('Notify[bye]') end it 'raises an error when the number of allowed slurp parameters exceeds the size constraint' do expect do compile_to_catalog(<<-MANIFEST) with(hi, bye) |Array[String, 1, 1] *$passed| { } MANIFEST end.to raise_error(/expected.*arg count \{1\}.*actual.*arg count \{2\}/m) end it 'allows passing slurped arrays by specifying an array of arrays' do catalog = compile_to_catalog(<<-MANIFEST) with([hi], [bye]) |Array[Array[String, 1, 1]] *$passed| { notify { $passed[0][0]: } notify { $passed[1][0]: } } MANIFEST expect(catalog).to have_resource('Notify[hi]') expect(catalog).to have_resource('Notify[bye]') end it 'raises an error when a required argument follows an optional one' do expect do compile_to_catalog(<<-MANIFEST) with() |$y = first, $x, Array[String, 1] *$passed = bye| {} MANIFEST end.to raise_error(/Parameter \$x is required/) end it 'raises an error when the minimum size of a slurped argument makes it required and it follows an optional argument' do expect do compile_to_catalog(<<-MANIFEST) with() |$x = first, Array[String, 1] *$passed| {} MANIFEST end.to raise_error(/Parameter \$passed is required/) end it 'allows slurped arguments with a minimum size of 0 after an optional argument' do catalog = compile_to_catalog(<<-MANIFEST) with() |$x = first, Array[String, 0] *$passed| { notify { $x: } } MANIFEST expect(catalog).to have_resource('Notify[first]') end it 'accepts a Resource as a Type' do catalog = compile_to_catalog(<<-MANIFEST) define bar($text) { } bar { 'joke': text => 'knock knock' } with(Bar[joke]) |Type[Bar] $joke| { notify { "${joke[text]}": } } MANIFEST expect(catalog).to have_resource("Notify[knock knock]") end end end context 'when evaluating collection' do it 'matches on container inherited tags' do Puppet[:code] = <<-MANIFEST class xport_test { tag('foo_bar') @notify { 'nbr1': message => 'explicitly tagged', tag => 'foo_bar' } @notify { 'nbr2': message => 'implicitly tagged' } Notify <| tag == 'foo_bar' |> { message => 'overridden' } } include xport_test MANIFEST catalog = Puppet::Parser::Compiler.compile(Puppet::Node.new("mynode")) expect(catalog).to have_resource("Notify[nbr1]").with_parameter(:message, 'overridden') expect(catalog).to have_resource("Notify[nbr2]").with_parameter(:message, 'overridden') end end end diff --git a/spec/integration/parser/resource_expressions_spec.rb b/spec/integration/parser/resource_expressions_spec.rb new file mode 100644 index 000000000..c53e40c52 --- /dev/null +++ b/spec/integration/parser/resource_expressions_spec.rb @@ -0,0 +1,282 @@ +require 'spec_helper' +require 'puppet_spec/compiler' +require 'matchers/resource' + +describe "Puppet resource expressions" do + include PuppetSpec::Compiler + include Matchers::Resource + + def self.produces(expectations) + expectations.each do |manifest, resources| + it "evaluates #{manifest} to produce #{resources}" do + catalog = compile_to_catalog(manifest) + + if resources.empty? + base_resources = ["Class[Settings]", "Class[main]", "Stage[main]"] + expect(catalog.resources.collect(&:ref) - base_resources).to eq([]) + else + resources.each do |reference| + if reference.is_a?(Array) + matcher = have_resource(reference[0]) + reference[1].each do |name, value| + matcher = matcher.with_parameter(name, value) + end + else + matcher = have_resource(reference) + end + + expect(catalog).to matcher + end + end + end + end + end + + def self.fails(expectations) + expectations.each do |manifest, pattern| + it "fails to evaluate #{manifest} with message #{pattern}" do + expect do + compile_to_catalog(manifest) + end.to raise_error(Puppet::Error, pattern) + end + end + end + + describe "future parser" do + before :each do + Puppet[:parser] = 'future' + end + + produces( + "$a = notify; $b = example; $c = { message => hello }; @@$a { $b: * => $c } realize(Resource[$a, $b])" => [["Notify[example]", { :message => "hello" }]]) + + context "resource titles" do + produces( + "notify { thing: }" => ["Notify[thing]"], + "$x = thing notify { $x: }" => ["Notify[thing]"], + + "notify { [thing]: }" => ["Notify[thing]"], + "$x = [thing] notify { $x: }" => ["Notify[thing]"], + + "notify { [[nested, array]]: }" => ["Notify[nested]", "Notify[array]"], + "$x = [[nested, array]] notify { $x: }" => ["Notify[nested]", "Notify[array]"], + + "notify { []: }" => [], + "$x = [] notify { $x: }" => [], + + "notify { default: }" => [], # nothing created because this is just a local default + "$x = default notify { $x: }" => []) + + fails( + "notify { '': }" => /Empty string title/, + "$x = '' notify { $x: }" => /Empty string title/, + + "notify { 1: }" => /Illegal title type.*Expected String, got Integer/, + "$x = 1 notify { $x: }" => /Illegal title type.*Expected String, got Integer/, + + "notify { [1]: }" => /Illegal title type.*Expected String, got Integer/, + "$x = [1] notify { $x: }" => /Illegal title type.*Expected String, got Integer/, + + "notify { 3.0: }" => /Illegal title type.*Expected String, got Float/, + "$x = 3.0 notify { $x: }" => /Illegal title type.*Expected String, got Float/, + + "notify { [3.0]: }" => /Illegal title type.*Expected String, got Float/, + "$x = [3.0] notify { $x: }" => /Illegal title type.*Expected String, got Float/, + + "notify { true: }" => /Illegal title type.*Expected String, got Boolean/, + "$x = true notify { $x: }" => /Illegal title type.*Expected String, got Boolean/, + + "notify { [true]: }" => /Illegal title type.*Expected String, got Boolean/, + "$x = [true] notify { $x: }" => /Illegal title type.*Expected String, got Boolean/, + + "notify { [false]: }" => /Illegal title type.*Expected String, got Boolean/, + "$x = [false] notify { $x: }" => /Illegal title type.*Expected String, got Boolean/, + + "notify { undef: }" => /Missing title.*undef/, + "$x = undef notify { $x: }" => /Missing title.*undef/, + + "notify { [undef]: }" => /Missing title.*undef/, + "$x = [undef] notify { $x: }" => /Missing title.*undef/, + + "notify { {nested => hash}: }" => /Illegal title type.*Expected String, got Hash/, + "$x = {nested => hash} notify { $x: }" => /Illegal title type.*Expected String, got Hash/, + + "notify { [{nested => hash}]: }" => /Illegal title type.*Expected String, got Hash/, + "$x = [{nested => hash}] notify { $x: }" => /Illegal title type.*Expected String, got Hash/, + + "notify { /regexp/: }" => /Illegal title type.*Expected String, got Regexp/, + "$x = /regexp/ notify { $x: }" => /Illegal title type.*Expected String, got Regexp/, + + "notify { [/regexp/]: }" => /Illegal title type.*Expected String, got Regexp/, + "$x = [/regexp/] notify { $x: }" => /Illegal title type.*Expected String, got Regexp/, + + "notify { [dupe, dupe]: }" => /The title 'dupe' has already been used/, + "notify { dupe:; dupe: }" => /The title 'dupe' has already been used/, + "notify { [dupe]:; dupe: }" => /The title 'dupe' has already been used/, + "notify { [default, default]:}" => /The title 'default' has already been used/, + "notify { default:; default:}" => /The title 'default' has already been used/, + "notify { [default]:; default:}" => /The title 'default' has already been used/) + end + + context "type names" do + produces( + "notify { testing: }" => ["Notify[testing]"], + "$a = notify; $a { testing: }" => ["Notify[testing]"], + "'notify' { testing: }" => ["Notify[testing]"], + "sprintf('%s', 'notify') { testing: }" => ["Notify[testing]"], + "$a = ify; \"not$a\" { testing: }" => ["Notify[testing]"], + + "Notify { testing: }" => ["Notify[testing]"], + "Resource[Notify] { testing: }" => ["Notify[testing]"], + "'Notify' { testing: }" => ["Notify[testing]"], + + "class a { notify { testing: } } class { a: }" => ["Notify[testing]"], + "class a { notify { testing: } } Class { a: }" => ["Notify[testing]"], + "class a { notify { testing: } } 'class' { a: }" => ["Notify[testing]"], + + "define a::b { notify { testing: } } a::b { title: }" => ["Notify[testing]"], + "define a::b { notify { testing: } } A::B { title: }" => ["Notify[testing]"], + "define a::b { notify { testing: } } 'a::b' { title: }" => ["Notify[testing]"], + "define a::b { notify { testing: } } Resource['a::b'] { title: }" => ["Notify[testing]"]) + + fails( + "'' { testing: }" => /Illegal type reference/, + "1 { testing: }" => /Illegal Resource Type expression.*got Integer/, + "3.0 { testing: }" => /Illegal Resource Type expression.*got Float/, + "true { testing: }" => /Illegal Resource Type expression.*got Boolean/, + "'not correct' { testing: }" => /Illegal type reference/, + + "Notify[hi] { testing: }" => /Illegal Resource Type expression.*got Notify\['hi'\]/, + "[Notify, File] { testing: }" => /Illegal Resource Type expression.*got Array\[Type\[Resource\]\]/, + + "Does::Not::Exist { title: }" => /Invalid resource type does::not::exist/) + end + + context "local defaults" do + produces( + "notify { example:; default: message => defaulted }" => [["Notify[example]", { :message => "defaulted" }]], + "notify { example: message => specific; default: message => defaulted }" => [["Notify[example]", { :message => "specific" }]], + "notify { example: message => undef; default: message => defaulted }" => [["Notify[example]", { :message => nil }]], + "notify { [example, other]: ; default: message => defaulted }" => [["Notify[example]", { :message => "defaulted" }], + ["Notify[other]", { :message => "defaulted" }]], + "notify { [example, default]: message => set; other: }" => [["Notify[example]", { :message => "set" }], + ["Notify[other]", { :message => "set" }]]) + end + + context "order of evaluation" do + fails("notify { hi: message => value; bye: message => Notify[hi][message] }" => /Resource not found: Notify\['hi'\]/) + + produces("notify { hi: message => (notify { param: message => set }); bye: message => Notify[param][message] }" => ["Notify[hi]", + ["Notify[bye]", { :message => "set" }]]) + fails("notify { bye: message => Notify[param][message]; hi: message => (notify { param: message => set }) }" => /Resource not found: Notify\['param'\]/) + end + + context "parameters" do + produces( + "notify { title: message => set }" => [["Notify[title]", { :message => "set" }]], + "$x = set notify { title: message => $x }" => [["Notify[title]", { :message => "set" }]], + + "notify { title: *=> { message => set } }" => [["Notify[title]", { :message => "set" }]], + "$x = { message => set } notify { title: * => $x }" => [["Notify[title]", { :message => "set" }]]) + + fails( + "notify { title: unknown => value }" => /Invalid parameter unknown/, + + #BUG + "notify { title: * => { hash => value }, message => oops }" => /Invalid parameter hash/, # this really needs to be a better error message. + "notify { title: message => oops, * => { hash => value } }" => /Syntax error/, # should this be a better error message? + + "notify { title: * => { unknown => value } }" => /Invalid parameter unknown/) + end + + context "virtual" do + produces( + "@notify { example: }" => [], + "@notify { example: } realize(Notify[example])" => ["Notify[example]"], + "@notify { virtual: message => set } notify { real: message => Notify[virtual][message] }" => [["Notify[real]", { :message => "set" }]]) + end + + context "exported" do + produces( + "@@notify { example: }" => [], + "@@notify { example: } realize(Notify[example])" => ["Notify[example]"], + "@@notify { exported: message => set } notify { real: message => Notify[exported][message] }" => [["Notify[real]", { :message => "set" }]]) + end + end + + describe "current parser" do + before :each do + Puppet[:parser] = 'current' + end + + produces( + "notify { thing: }" => ["Notify[thing]"], + "$x = thing notify { $x: }" => ["Notify[thing]"], + + "notify { [thing]: }" => ["Notify[thing]"], + "$x = [thing] notify { $x: }" => ["Notify[thing]"], + + "notify { [[nested, array]]: }" => ["Notify[nested]", "Notify[array]"], + "$x = [[nested, array]] notify { $x: }" => ["Notify[nested]", "Notify[array]"], + + # deprecate? + "notify { 1: }" => ["Notify[1]"], + "$x = 1 notify { $x: }" => ["Notify[1]"], + + # deprecate? + "notify { [1]: }" => ["Notify[1]"], + "$x = [1] notify { $x: }" => ["Notify[1]"], + + # deprecate? + "notify { 3.0: }" => ["Notify[3.0]"], + "$x = 3.0 notify { $x: }" => ["Notify[3.0]"], + + # deprecate? + "notify { [3.0]: }" => ["Notify[3.0]"], + "$x = [3.0] notify { $x: }" => ["Notify[3.0]"]) + + # :( + fails( "notify { true: }" => /Syntax error/) + produces("$x = true notify { $x: }" => ["Notify[true]"]) + + # this makes no sense given the [false] case + produces( + "notify { [true]: }" => ["Notify[true]"], + "$x = [true] notify { $x: }" => ["Notify[true]"]) + + # *sigh* + fails( + "notify { false: }" => /Syntax error/, + "$x = false notify { $x: }" => /No title provided and :notify is not a valid resource reference/, + + "notify { [false]: }" => /No title provided and :notify is not a valid resource reference/, + "$x = [false] notify { $x: }" => /No title provided and :notify is not a valid resource reference/) + + # works for variable value, not for literal. deprecate? + fails("notify { undef: }" => /Syntax error/) + produces( + "$x = undef notify { $x: }" => ["Notify[undef]"], + + # deprecate? + "notify { [undef]: }" => ["Notify[undef]"], + "$x = [undef] notify { $x: }" => ["Notify[undef]"]) + + fails("notify { {nested => hash}: }" => /Syntax error/) + #produces("$x = {nested => hash} notify { $x: }" => ["Notify[{nested => hash}]"]) #it is created, but isn't possible to reference the resource. deprecate? + #produces("notify { [{nested => hash}]: }" => ["Notify[{nested => hash}]"]) #it is created, but isn't possible to reference the resource. deprecate? + #produces("$x = [{nested => hash}] notify { $x: }" => ["Notify[{nested => hash}]"]) #it is created, but isn't possible to reference the resource. deprecate? + + fails( + "notify { /regexp/: }" => /Syntax error/, + "$x = /regexp/ notify { $x: }" => /Syntax error/, + + "notify { [/regexp/]: }" => /Syntax error/, + "$x = [/regexp/] notify { $x: }" => /Syntax error/, + + "notify { default: }" => /Syntax error/, + "$x = default notify { $x: }" => /Syntax error/, + + "notify { [default]: }" => /Syntax error/, + "$x = [default] notify { $x: }" => /Syntax error/) + end +end diff --git a/spec/lib/puppet_spec/compiler.rb b/spec/lib/puppet_spec/compiler.rb index 02c448d1f..91ceed24f 100644 --- a/spec/lib/puppet_spec/compiler.rb +++ b/spec/lib/puppet_spec/compiler.rb @@ -1,46 +1,47 @@ module PuppetSpec::Compiler def compile_to_catalog(string, node = Puppet::Node.new('foonode')) Puppet[:code] = string - Puppet::Parser::Compiler.compile(node) + # see lib/puppet/indirector/catalog/compiler.rb#filter + Puppet::Parser::Compiler.compile(node).filter { |r| r.virtual? } end def compile_to_ral(manifest) catalog = compile_to_catalog(manifest) ral = catalog.to_ral ral.finalize ral end def compile_to_relationship_graph(manifest, prioritizer = Puppet::Graph::SequentialPrioritizer.new) ral = compile_to_ral(manifest) graph = Puppet::Graph::RelationshipGraph.new(prioritizer) graph.populate_from(ral) graph end def apply_compiled_manifest(manifest, prioritizer = Puppet::Graph::SequentialPrioritizer.new) catalog = compile_to_ral(manifest) if block_given? catalog.resources.each { |res| yield res } end transaction = Puppet::Transaction.new(catalog, Puppet::Transaction::Report.new("apply"), prioritizer) transaction.evaluate transaction.report.finalize_report transaction end def apply_with_error_check(manifest) apply_compiled_manifest(manifest) do |res| res.expects(:err).never end end def order_resources_traversed_in(relationships) order_seen = [] relationships.traverse { |resource| order_seen << resource.ref } order_seen end end diff --git a/spec/unit/face/parser_spec.rb b/spec/unit/face/parser_spec.rb index 9d96d3889..e9ba07a2d 100644 --- a/spec/unit/face/parser_spec.rb +++ b/spec/unit/face/parser_spec.rb @@ -1,73 +1,111 @@ require 'spec_helper' require 'puppet_spec/files' require 'puppet/face' describe Puppet::Face[:parser, :current] do include PuppetSpec::Files let(:parser) { Puppet::Face[:parser, :current] } - context "from an interactive terminal" do - before :each do - from_an_interactive_terminal + context "validate" do + context "from an interactive terminal" do + before :each do + from_an_interactive_terminal + end + + it "validates the configured site manifest when no files are given" do + manifest = file_containing('site.pp', "{ invalid =>") + + configured_environment = Puppet::Node::Environment.create(:default, [], manifest) + Puppet.override(:current_environment => configured_environment) do + expect { parser.validate() }.to exit_with(1) + end + end + + it "validates the given file" do + manifest = file_containing('site.pp', "{ invalid =>") + + expect { parser.validate(manifest) }.to exit_with(1) + end + + it "runs error free when there are no validation errors" do + manifest = file_containing('site.pp', "notify { valid: }") + + parser.validate(manifest) + end + + it "reports missing files" do + expect do + parser.validate("missing.pp") + end.to raise_error(Puppet::Error, /One or more file\(s\) specified did not exist.*missing\.pp/m) + end + + it "parses supplied manifest files in the context of a directory environment" do + manifest = file_containing('test.pp', "{ invalid =>") + + env = Puppet::Node::Environment.create(:special, []) + env_loader = Puppet::Environments::Static.new(env) + Puppet.override({:environments => env_loader, :current_environment => env}) do + expect { parser.validate(manifest) }.to exit_with(1) + end + + expect(@logs.join).to match(/environment special.*Syntax error at '\{'/) + end + end - it "validates the configured site manifest when no files are given" do - manifest = file_containing('site.pp', "{ invalid =>") + it "validates the contents of STDIN when no files given and STDIN is not a tty" do + from_a_piped_input_of("{ invalid =>") - configured_environment = Puppet::Node::Environment.create(:default, [], manifest) - Puppet.override(:current_environment => configured_environment) do + Puppet.override(:current_environment => Puppet::Node::Environment.create(:special, [])) do expect { parser.validate() }.to exit_with(1) end end + end - it "validates the given file" do - manifest = file_containing('site.pp', "{ invalid =>") - - expect { parser.validate(manifest) }.to exit_with(1) + context "dump" do + it "prints the AST of the passed expression" do + expect(parser.dump({ :e => 'notice hi' })).to eq("(invoke notice hi)\n") end - it "runs error free when there are no validation errors" do - manifest = file_containing('site.pp', "notify { valid: }") + it "prints the AST of the code read from the passed files" do + first_manifest = file_containing('site.pp', "notice hi") + second_manifest = file_containing('site2.pp', "notice bye") + + output = parser.dump(first_manifest, second_manifest) - parser.validate(manifest) + expect(output).to match(/site\.pp.*\(invoke notice hi\)/) + expect(output).to match(/site2\.pp.*\(invoke notice bye\)/) end - it "reports missing files" do - expect do - parser.validate("missing.pp") - end.to raise_error(Puppet::Error, /One or more file\(s\) specified did not exist.*missing\.pp/m) + it "informs the user of files that don't exist" do + expect(parser.dump('does_not_exist_here.pp')).to match(/did not exist:\s*does_not_exist_here\.pp/m) end - it "parses supplied manifest files in the context of a directory environment" do - manifest = file_containing('test.pp', "{ invalid =>") + it "prints the AST of STDIN when no files given and STDIN is not a tty" do + from_a_piped_input_of("notice hi") - env = Puppet::Node::Environment.create(:special, []) - env_loader = Puppet::Environments::Static.new(env) - Puppet.override({:environments => env_loader, :current_environment => env}) do - expect { parser.validate(manifest) }.to exit_with(1) + Puppet.override(:current_environment => Puppet::Node::Environment.create(:special, [])) do + expect(parser.dump()).to eq("(invoke notice hi)\n") end - - expect(@logs.join).to match(/environment special.*Syntax error at '\{'/) end - end - - it "validates the contents of STDIN when no files given and STDIN is not a tty" do - from_a_piped_input_of("{ invalid =>") + it "logs an error if the input cannot be parsed" do + output = parser.dump({ :e => '{ invalid =>' }) - Puppet.override(:current_environment => Puppet::Node::Environment.create(:special, [])) do - expect { parser.validate() }.to exit_with(1) + expect(output).to eq("") + expect(@logs[0].message).to eq("Syntax error at end of file") + expect(@logs[0].level).to eq(:err) end end def from_an_interactive_terminal STDIN.stubs(:tty?).returns(true) end def from_a_piped_input_of(contents) STDIN.stubs(:tty?).returns(false) STDIN.stubs(:read).returns(contents) end end diff --git a/spec/unit/forge/module_release_spec.rb b/spec/unit/forge/module_release_spec.rb index fbf5e157a..a00d4d9c5 100644 --- a/spec/unit/forge/module_release_spec.rb +++ b/spec/unit/forge/module_release_spec.rb @@ -1,131 +1,215 @@ # encoding: utf-8 require 'spec_helper' require 'puppet/forge' require 'net/http' require 'puppet/module_tool' describe Puppet::Forge::ModuleRelease do let(:agent) { "Test/1.0" } let(:repository) { Puppet::Forge::Repository.new('http://fake.com', agent) } let(:ssl_repository) { Puppet::Forge::Repository.new('https://fake.com', agent) } - - let(:release_json) do - <<-EOF - { - "uri": "/v3/releases/puppetlabs-stdlib-4.1.0", - "module": { - "uri": "/v3/modules/puppetlabs-stdlib", - "name": "stdlib", - "owner": { - "uri": "/v3/users/puppetlabs", - "username": "puppetlabs", - "gravatar_id": "fdd009b7c1ec96e088b389f773e87aec" - } - }, - "version": "4.1.0", - "metadata": { - "types": [ ], - "license": "Apache 2.0", - "checksums": { }, - "version": "4.1.0", - "description": "Standard Library for Puppet Modules", - "source": "git://github.com/puppetlabs/puppetlabs-stdlib.git", - "project_page": "https://github.com/puppetlabs/puppetlabs-stdlib", - "summary": "Puppet Module Standard Library", - "dependencies": [ - - ], - "author": "puppetlabs", - "name": "puppetlabs-stdlib" - }, - "tags": [ - "puppetlabs", - "library", - "stdlib", - "standard", - "stages" - ], - "file_uri": "/v3/files/puppetlabs-stdlib-4.1.0.tar.gz", - "file_size": 67586, - "file_md5": "bbf919d7ee9d278d2facf39c25578bf8", - "downloads": 610751, - "readme": "", - "changelog": "", - "license": "", - "created_at": "2013-05-13 08:31:19 -0700", - "updated_at": "2013-05-13 08:31:19 -0700", - "deleted_at": null - } - EOF - end - + let(:api_version) { "v3" } + let(:module_author) { "puppetlabs" } + let(:module_name) { "stdlib" } + let(:module_version) { "4.1.0" } + let(:module_full_name) { "#{module_author}-#{module_name}" } + let(:module_full_name_versioned) { "#{module_full_name}-#{module_version}" } + let(:module_md5) { "bbf919d7ee9d278d2facf39c25578bf8" } + let(:uri) { " "} let(:release) { Puppet::Forge::ModuleRelease.new(ssl_repository, JSON.parse(release_json)) } let(:mock_file) { mock_io = StringIO.new mock_io.stubs(:path).returns('/dev/null') mock_io } let(:mock_dir) { '/tmp' } - def mock_digest_file_with_md5(md5) - Digest::MD5.stubs(:file).returns(stub(:hexdigest => md5)) - end - - describe '#prepare' do - before :each do - release.stubs(:tmpfile).returns(mock_file) - release.stubs(:tmpdir).returns(mock_dir) + shared_examples 'a module release' do + def mock_digest_file_with_md5(md5) + Digest::MD5.stubs(:file).returns(stub(:hexdigest => md5)) end - it 'should call sub methods with correct params' do - release.expects(:download).with('/v3/files/puppetlabs-stdlib-4.1.0.tar.gz', mock_file) - release.expects(:validate_checksum).with(mock_file, 'bbf919d7ee9d278d2facf39c25578bf8') - release.expects(:unpack).with(mock_file, mock_dir) + describe '#prepare' do + before :each do + release.stubs(:tmpfile).returns(mock_file) + release.stubs(:tmpdir).returns(mock_dir) + end + + it 'should call sub methods with correct params' do + release.expects(:download).with("/#{api_version}/files/#{module_full_name_versioned}.tar.gz", mock_file) + release.expects(:validate_checksum).with(mock_file, module_md5) + release.expects(:unpack).with(mock_file, mock_dir) - release.prepare + release.prepare + end end - end - describe '#tmpfile' do + describe '#tmpfile' do - # This is impossible to test under Ruby 1.8.x, but should also occur there. - it 'should be opened in binary mode', :unless => RUBY_VERSION >= '1.8.7' do - Puppet::Forge::Cache.stubs(:base_path).returns(Dir.tmpdir) - release.send(:tmpfile).binmode?.should be_true + # This is impossible to test under Ruby 1.8.x, but should also occur there. + it 'should be opened in binary mode', :unless => RUBY_VERSION >= '1.8.7' do + Puppet::Forge::Cache.stubs(:base_path).returns(Dir.tmpdir) + release.send(:tmpfile).binmode?.should be_true + end end - end - describe '#download' do - it 'should call make_http_request with correct params' do - # valid URI comes from file_uri in JSON blob above - ssl_repository.expects(:make_http_request).with('/v3/files/puppetlabs-stdlib-4.1.0.tar.gz', mock_file).returns(mock_file) + describe '#download' do + it 'should call make_http_request with correct params' do + # valid URI comes from file_uri in JSON blob above + ssl_repository.expects(:make_http_request).with("/#{api_version}/files/#{module_full_name_versioned}.tar.gz", mock_file).returns(mock_file) - release.send(:download, '/v3/files/puppetlabs-stdlib-4.1.0.tar.gz', mock_file) + release.send(:download, "/#{api_version}/files/#{module_full_name_versioned}.tar.gz", mock_file) + end end - end - describe '#verify_checksum' do - it 'passes md5 check when valid' do - # valid hash comes from file_md5 in JSON blob above - mock_digest_file_with_md5('bbf919d7ee9d278d2facf39c25578bf8') + describe '#verify_checksum' do + it 'passes md5 check when valid' do + # valid hash comes from file_md5 in JSON blob above + mock_digest_file_with_md5(module_md5) - release.send(:validate_checksum, mock_file, 'bbf919d7ee9d278d2facf39c25578bf8') + release.send(:validate_checksum, mock_file, module_md5) + end + + it 'fails md5 check when invalid' do + mock_digest_file_with_md5('ffffffffffffffffffffffffffffffff') + + expect { release.send(:validate_checksum, mock_file, module_md5) }.to raise_error(RuntimeError, /did not match expected checksum/) + end end - it 'fails md5 check when invalid' do - mock_digest_file_with_md5('ffffffffffffffffffffffffffffffff') + describe '#unpack' do + it 'should call unpacker with correct params' do + Puppet::ModuleTool::Applications::Unpacker.expects(:unpack).with(mock_file.path, mock_dir).returns(true) + + release.send(:unpack, mock_file, mock_dir) + end + end + end - expect { release.send(:validate_checksum, mock_file, 'bbf919d7ee9d278d2facf39c25578bf8') }.to raise_error(RuntimeError, /did not match expected checksum/) + context 'standard forge module' do + let(:release_json) do %Q{ + { + "uri": "/#{api_version}/releases/#{module_full_name_versioned}", + "module": { + "uri": "/#{api_version}/modules/#{module_full_name}", + "name": "#{module_name}", + "owner": { + "uri": "/#{api_version}/users/#{module_author}", + "username": "#{module_author}", + "gravatar_id": "fdd009b7c1ec96e088b389f773e87aec" + } + }, + "version": "#{module_version}", + "metadata": { + "types": [ ], + "license": "Apache 2.0", + "checksums": { }, + "version": "#{module_version}", + "description": "Standard Library for Puppet Modules", + "source": "git://github.com/puppetlabs/puppetlabs-stdlib.git", + "project_page": "https://github.com/puppetlabs/puppetlabs-stdlib", + "summary": "Puppet Module Standard Library", + "dependencies": [ + + ], + "author": "#{module_author}", + "name": "#{module_full_name}" + }, + "tags": [ + "puppetlabs", + "library", + "stdlib", + "standard", + "stages" + ], + "file_uri": "/#{api_version}/files/#{module_full_name_versioned}.tar.gz", + "file_size": 67586, + "file_md5": "#{module_md5}", + "downloads": 610751, + "readme": "", + "changelog": "", + "license": "", + "created_at": "2013-05-13 08:31:19 -0700", + "updated_at": "2013-05-13 08:31:19 -0700", + "deleted_at": null + } + } end + + it_behaves_like 'a module release' end - describe '#unpack' do - it 'should call unpacker with correct params' do - Puppet::ModuleTool::Applications::Unpacker.expects(:unpack).with(mock_file.path, mock_dir).returns(true) + context 'forge module with no dependencies field' do + let(:release_json) do %Q{ + { + "uri": "/#{api_version}/releases/#{module_full_name_versioned}", + "module": { + "uri": "/#{api_version}/modules/#{module_full_name}", + "name": "#{module_name}", + "owner": { + "uri": "/#{api_version}/users/#{module_author}", + "username": "#{module_author}", + "gravatar_id": "fdd009b7c1ec96e088b389f773e87aec" + } + }, + "version": "#{module_version}", + "metadata": { + "types": [ ], + "license": "Apache 2.0", + "checksums": { }, + "version": "#{module_version}", + "description": "Standard Library for Puppet Modules", + "source": "git://github.com/puppetlabs/puppetlabs-stdlib.git", + "project_page": "https://github.com/puppetlabs/puppetlabs-stdlib", + "summary": "Puppet Module Standard Library", + "author": "#{module_author}", + "name": "#{module_full_name}" + }, + "tags": [ + "puppetlabs", + "library", + "stdlib", + "standard", + "stages" + ], + "file_uri": "/#{api_version}/files/#{module_full_name_versioned}.tar.gz", + "file_size": 67586, + "file_md5": "#{module_md5}", + "downloads": 610751, + "readme": "", + "changelog": "", + "license": "", + "created_at": "2013-05-13 08:31:19 -0700", + "updated_at": "2013-05-13 08:31:19 -0700", + "deleted_at": null + } + } + end + + it_behaves_like 'a module release' + end - release.send(:unpack, mock_file, mock_dir) + context 'forge module with the minimal set of fields' do + let(:release_json) do %Q{ + { + "uri": "/#{api_version}/releases/#{module_full_name_versioned}", + "module": { + "uri": "/#{api_version}/modules/#{module_full_name}", + "name": "#{module_name}" + }, + "metadata": { + "version": "#{module_version}", + "name": "#{module_full_name}" + }, + "file_uri": "/#{api_version}/files/#{module_full_name_versioned}.tar.gz", + "file_size": 67586, + "file_md5": "#{module_md5}" + } + } end + + it_behaves_like 'a module release' end end diff --git a/spec/unit/module_tool/metadata_spec.rb b/spec/unit/module_tool/metadata_spec.rb index 4c9c7c79a..fce9c8f8d 100644 --- a/spec/unit/module_tool/metadata_spec.rb +++ b/spec/unit/module_tool/metadata_spec.rb @@ -1,246 +1,301 @@ require 'spec_helper' require 'puppet/module_tool' describe Puppet::ModuleTool::Metadata do let(:data) { {} } let(:metadata) { Puppet::ModuleTool::Metadata.new } describe 'property lookups' do subject { metadata } %w[ name version author summary license source project_page issues_url dependencies dashed_name release_name description ].each do |prop| describe "##{prop}" do it "responds to the property" do subject.send(prop) end end end end describe "#update" do subject { metadata.update(data) } context "with a valid name" do let(:data) { { 'name' => 'billgates-mymodule' } } it "extracts the author name from the name field" do subject.to_hash['author'].should == 'billgates' end it "extracts a module name from the name field" do subject.module_name.should == 'mymodule' end context "and existing author" do before { metadata.update('author' => 'foo') } it "avoids overwriting the existing author" do subject.to_hash['author'].should == 'foo' end end end context "with a valid name and author" do let(:data) { { 'name' => 'billgates-mymodule', 'author' => 'foo' } } it "use the author name from the author field" do subject.to_hash['author'].should == 'foo' end context "and preexisting author" do before { metadata.update('author' => 'bar') } it "avoids overwriting the existing author" do subject.to_hash['author'].should == 'foo' end end end context "with an invalid name" do context "(short module name)" do let(:data) { { 'name' => 'mymodule' } } it "raises an exception" do expect { subject }.to raise_error(ArgumentError, "Invalid 'name' field in metadata.json: the field must be a namespaced module name") end end context "(missing namespace)" do let(:data) { { 'name' => '/mymodule' } } it "raises an exception" do expect { subject }.to raise_error(ArgumentError, "Invalid 'name' field in metadata.json: the field must be a namespaced module name") end end context "(missing module name)" do let(:data) { { 'name' => 'namespace/' } } it "raises an exception" do expect { subject }.to raise_error(ArgumentError, "Invalid 'name' field in metadata.json: the field must be a namespaced module name") end end context "(invalid namespace)" do let(:data) { { 'name' => "dolla'bill$-mymodule" } } it "raises an exception" do expect { subject }.to raise_error(ArgumentError, "Invalid 'name' field in metadata.json: the namespace contains non-alphanumeric characters") end end context "(non-alphanumeric module name)" do let(:data) { { 'name' => "dollabils-fivedolla'" } } it "raises an exception" do expect { subject }.to raise_error(ArgumentError, "Invalid 'name' field in metadata.json: the module name contains non-alphanumeric (or underscore) characters") end end context "(module name starts with a number)" do let(:data) { { 'name' => "dollabills-5dollars" } } it "raises an exception" do expect { subject }.to raise_error(ArgumentError, "Invalid 'name' field in metadata.json: the module name must begin with a letter") end end end context "with an invalid version" do let(:data) { { 'version' => '3.0' } } it "raises an exception" do expect { subject }.to raise_error(ArgumentError, "Invalid 'version' field in metadata.json: version string cannot be parsed as a valid Semantic Version") end end context "with a valid source" do context "which is a GitHub URL" do context "with a scheme" do before { metadata.update('source' => 'https://github.com/billgates/amazingness') } it "predicts a default project_page" do subject.to_hash['project_page'].should == 'https://github.com/billgates/amazingness' end it "predicts a default issues_url" do subject.to_hash['issues_url'].should == 'https://github.com/billgates/amazingness/issues' end end context "without a scheme" do before { metadata.update('source' => 'github.com/billgates/amazingness') } it "predicts a default project_page" do subject.to_hash['project_page'].should == 'https://github.com/billgates/amazingness' end it "predicts a default issues_url" do subject.to_hash['issues_url'].should == 'https://github.com/billgates/amazingness/issues' end end end context "which is not a GitHub URL" do before { metadata.update('source' => 'https://notgithub.com/billgates/amazingness') } it "does not predict a default project_page" do subject.to_hash['project_page'].should be nil end it "does not predict a default issues_url" do subject.to_hash['issues_url'].should be nil end end context "which is not a URL" do before { metadata.update('source' => 'my brain') } it "does not predict a default project_page" do subject.to_hash['project_page'].should be nil end it "does not predict a default issues_url" do subject.to_hash['issues_url'].should be nil end end end + context "with a valid dependency" do + let(:data) { {'dependencies' => [{'name' => 'puppetlabs-goodmodule'}] }} + + it "adds the dependency" do + subject.dependencies.size.should == 1 + end + end + + context "with a invalid dependency name" do + let(:data) { {'dependencies' => [{'name' => 'puppetlabsbadmodule'}] }} + + it "raises an exception" do + expect { subject }.to raise_error(ArgumentError) + end + end + + context "with a valid dependency version range" do + let(:data) { {'dependencies' => [{'name' => 'puppetlabs-badmodule', 'version_requirement' => '>= 2.0.0'}] }} + + it "adds the dependency" do + subject.dependencies.size.should == 1 + end + end + + context "with a invalid version range" do + let(:data) { {'dependencies' => [{'name' => 'puppetlabsbadmodule', 'version_requirement' => '>= banana'}] }} + + it "raises an exception" do + expect { subject }.to raise_error(ArgumentError) + end + end + + context "with duplicate dependencies" do + let(:data) { {'dependencies' => [{'name' => 'puppetlabs-dupmodule', 'version_requirement' => '1.0.0'}, + {'name' => 'puppetlabs-dupmodule', 'version_requirement' => '0.0.1'}] } + } + + it "raises an exception" do + expect { subject }.to raise_error(ArgumentError) + end + end + + context "adding a duplicate dependency" do + let(:data) { {'dependencies' => [{'name' => 'puppetlabs-origmodule', 'version_requirement' => '1.0.0'}] }} + + it "with a different version raises an exception" do + metadata.add_dependency('puppetlabs-origmodule', '>= 0.0.1') + expect { subject }.to raise_error(ArgumentError) + end + + it "with the same version does not add another dependency" do + metadata.add_dependency('puppetlabs-origmodule', '1.0.0') + subject.dependencies.size.should == 1 + end + end end describe '#dashed_name' do it 'returns nil in the absence of a module name' do expect(metadata.update('version' => '1.0.0').release_name).to be_nil end it 'returns a hyphenated string containing namespace and module name' do data = metadata.update('name' => 'foo-bar') data.dashed_name.should == 'foo-bar' end it 'properly handles slash-separated names' do data = metadata.update('name' => 'foo/bar') data.dashed_name.should == 'foo-bar' end it 'is unaffected by author name' do data = metadata.update('name' => 'foo/bar', 'author' => 'me') data.dashed_name.should == 'foo-bar' end end describe '#release_name' do it 'returns nil in the absence of a module name' do expect(metadata.update('version' => '1.0.0').release_name).to be_nil end it 'returns nil in the absence of a version' do expect(metadata.update('name' => 'foo/bar').release_name).to be_nil end it 'returns a hyphenated string containing module name and version' do data = metadata.update('name' => 'foo/bar', 'version' => '1.0.0') data.release_name.should == 'foo-bar-1.0.0' end it 'is unaffected by author name' do data = metadata.update('name' => 'foo/bar', 'version' => '1.0.0', 'author' => 'me') data.release_name.should == 'foo-bar-1.0.0' end end describe "#to_hash" do subject { metadata.to_hash } it "contains the default set of keys" do subject.keys.sort.should == %w[ name version author summary license source issues_url project_page dependencies ].sort end describe "['license']" do it "defaults to Apache 2" do subject['license'].should == "Apache 2.0" end end describe "['dependencies']" do - it "defaults to an empty Array" do - subject['dependencies'].should == [] + it "defaults to an empty set" do + subject['dependencies'].should == Set.new end end context "when updated with non-default data" do subject { metadata.update('license' => 'MIT', 'non-standard' => 'yup').to_hash } it "overrides the defaults" do subject['license'].should == 'MIT' end it 'contains unanticipated values' do subject['non-standard'].should == 'yup' end end end end diff --git a/spec/unit/parser/functions/create_resources_spec.rb b/spec/unit/parser/functions/create_resources_spec.rb index 44f4755d6..3e7bd8015 100755 --- a/spec/unit/parser/functions/create_resources_spec.rb +++ b/spec/unit/parser/functions/create_resources_spec.rb @@ -1,213 +1,213 @@ require 'puppet' require 'spec_helper' require 'puppet_spec/compiler' describe 'function for dynamically creating resources' do include PuppetSpec::Compiler before :each do node = Puppet::Node.new("floppy", :environment => 'production') @compiler = Puppet::Parser::Compiler.new(node) @scope = Puppet::Parser::Scope.new(@compiler) @topscope = @scope.compiler.topscope @scope.parent = @topscope Puppet::Parser::Functions.function(:create_resources) end it "should exist" do Puppet::Parser::Functions.function(:create_resources).should == "function_create_resources" end it 'should require two or three arguments' do expect { @scope.function_create_resources(['foo']) }.to raise_error(ArgumentError, 'create_resources(): Wrong number of arguments given (1 for minimum 2)') expect { @scope.function_create_resources(['foo', 'bar', 'blah', 'baz']) }.to raise_error(ArgumentError, 'create_resources(): wrong number of arguments (4; must be 2 or 3)') end it 'should require second argument to be a hash' do expect { @scope.function_create_resources(['foo','bar']) }.to raise_error(ArgumentError, 'create_resources(): second argument must be a hash') end it 'should require optional third argument to be a hash' do expect { @scope.function_create_resources(['foo',{},'foo']) }.to raise_error(ArgumentError, 'create_resources(): third argument, if provided, must be a hash') end describe 'when creating native types' do it 'empty hash should not cause resources to be added' do noop_catalog = compile_to_catalog("create_resources('file', {})") empty_catalog = compile_to_catalog("") noop_catalog.resources.size.should == empty_catalog.resources.size end it 'should be able to add' do catalog = compile_to_catalog("create_resources('file', {'/etc/foo'=>{'ensure'=>'present'}})") catalog.resource(:file, "/etc/foo")['ensure'].should == 'present' end it 'should be able to add virtual resources' do catalog = compile_to_catalog("create_resources('@file', {'/etc/foo'=>{'ensure'=>'present'}})\nrealize(File['/etc/foo'])") catalog.resource(:file, "/etc/foo")['ensure'].should == 'present' end it 'should be able to add exported resources' do - catalog = compile_to_catalog("create_resources('@@file', {'/etc/foo'=>{'ensure'=>'present'}})") + catalog = compile_to_catalog("create_resources('@@file', {'/etc/foo'=>{'ensure'=>'present'}}) realize(File['/etc/foo'])") catalog.resource(:file, "/etc/foo")['ensure'].should == 'present' catalog.resource(:file, "/etc/foo").exported.should == true end it 'should accept multiple types' do catalog = compile_to_catalog("create_resources('notify', {'foo'=>{'message'=>'one'}, 'bar'=>{'message'=>'two'}})") catalog.resource(:notify, "foo")['message'].should == 'one' catalog.resource(:notify, "bar")['message'].should == 'two' end it 'should fail to add non-existing type' do expect do @scope.function_create_resources(['create-resource-foo', { 'foo' => {} }]) end.to raise_error(ArgumentError, /Invalid resource type create-resource-foo/) end it 'should be able to add edges' do rg = compile_to_relationship_graph("notify { test: }\n create_resources('notify', {'foo'=>{'require'=>'Notify[test]'}})") test = rg.vertices.find { |v| v.title == 'test' } foo = rg.vertices.find { |v| v.title == 'foo' } test.must be foo.must be rg.path_between(test,foo).should be end it 'should account for default values' do catalog = compile_to_catalog("create_resources('file', {'/etc/foo'=>{'ensure'=>'present'}, '/etc/baz'=>{'group'=>'food'}}, {'group' => 'bar'})") catalog.resource(:file, "/etc/foo")['group'].should == 'bar' catalog.resource(:file, "/etc/baz")['group'].should == 'food' end end describe 'when dynamically creating resource types' do it 'should be able to create defined resource types' do catalog = compile_to_catalog(<<-MANIFEST) define foocreateresource($one) { notify { $name: message => $one } } create_resources('foocreateresource', {'blah'=>{'one'=>'two'}}) MANIFEST catalog.resource(:notify, "blah")['message'].should == 'two' end it 'should fail if defines are missing params' do expect { compile_to_catalog(<<-MANIFEST) define foocreateresource($one) { notify { $name: message => $one } } create_resources('foocreateresource', {'blah'=>{}}) MANIFEST }.to raise_error(Puppet::Error, 'Must pass one to Foocreateresource[blah] on node foonode') end it 'should be able to add multiple defines' do catalog = compile_to_catalog(<<-MANIFEST) define foocreateresource($one) { notify { $name: message => $one } } create_resources('foocreateresource', {'blah'=>{'one'=>'two'}, 'blaz'=>{'one'=>'three'}}) MANIFEST catalog.resource(:notify, "blah")['message'].should == 'two' catalog.resource(:notify, "blaz")['message'].should == 'three' end it 'should be able to add edges' do rg = compile_to_relationship_graph(<<-MANIFEST) define foocreateresource($one) { notify { $name: message => $one } } notify { test: } create_resources('foocreateresource', {'blah'=>{'one'=>'two', 'require' => 'Notify[test]'}}) MANIFEST test = rg.vertices.find { |v| v.title == 'test' } blah = rg.vertices.find { |v| v.title == 'blah' } test.must be blah.must be rg.path_between(test,blah).should be end it 'should account for default values' do catalog = compile_to_catalog(<<-MANIFEST) define foocreateresource($one) { notify { $name: message => $one } } create_resources('foocreateresource', {'blah'=>{}}, {'one' => 'two'}) MANIFEST catalog.resource(:notify, "blah")['message'].should == 'two' end end describe 'when creating classes' do it 'should be able to create classes' do catalog = compile_to_catalog(<<-MANIFEST) class bar($one) { notify { test: message => $one } } create_resources('class', {'bar'=>{'one'=>'two'}}) MANIFEST catalog.resource(:notify, "test")['message'].should == 'two' catalog.resource(:class, "bar").should_not be_nil end it 'should fail to create non-existing classes' do expect do compile_to_catalog(<<-MANIFEST) create_resources('class', {'blah'=>{'one'=>'two'}}) MANIFEST end.to raise_error(Puppet::Error, 'Could not find declared class blah at line 1 on node foonode') end it 'should be able to add edges' do rg = compile_to_relationship_graph(<<-MANIFEST) class bar($one) { notify { test: message => $one } } notify { tester: } create_resources('class', {'bar'=>{'one'=>'two', 'require' => 'Notify[tester]'}}) MANIFEST test = rg.vertices.find { |v| v.title == 'test' } tester = rg.vertices.find { |v| v.title == 'tester' } test.must be tester.must be rg.path_between(tester,test).should be end it 'should account for default values' do catalog = compile_to_catalog(<<-MANIFEST) class bar($one) { notify { test: message => $one } } create_resources('class', {'bar'=>{}}, {'one' => 'two'}) MANIFEST catalog.resource(:notify, "test")['message'].should == 'two' catalog.resource(:class, "bar").should_not be_nil end it 'should fail with a correct error message if the syntax of an imported file is incorrect' do expect{ Puppet[:modulepath] = my_fixture_dir compile_to_catalog('include foo') }.to raise_error(Puppet::Error, /Syntax error at.*/) end end end diff --git a/spec/unit/pops/evaluator/evaluating_parser_spec.rb b/spec/unit/pops/evaluator/evaluating_parser_spec.rb index 2c2d5a001..2720f425e 100644 --- a/spec/unit/pops/evaluator/evaluating_parser_spec.rb +++ b/spec/unit/pops/evaluator/evaluating_parser_spec.rb @@ -1,1303 +1,1304 @@ require 'spec_helper' require 'puppet/pops' require 'puppet/pops/evaluator/evaluator_impl' require 'puppet/loaders' require 'puppet_spec/pops' require 'puppet_spec/scope' require 'puppet/parser/e4_parser_adapter' # relative to this spec file (./) does not work as this file is loaded by rspec #require File.join(File.dirname(__FILE__), '/evaluator_rspec_helper') describe 'Puppet::Pops::Evaluator::EvaluatorImpl' do include PuppetSpec::Pops include PuppetSpec::Scope before(:each) do Puppet[:strict_variables] = true # These must be set since the 3x logic switches some behaviors on these even if the tests explicitly # use the 4x parser and evaluator. # Puppet[:parser] = 'future' # Puppetx cannot be loaded until the correct parser has been set (injector is turned off otherwise) require 'puppetx' # Tests needs a known configuration of node/scope/compiler since it parses and evaluates # snippets as the compiler will evaluate them, butwithout the overhead of compiling a complete # catalog for each tested expression. # @parser = Puppet::Pops::Parser::EvaluatingParser.new @node = Puppet::Node.new('node.example.com') @node.environment = Puppet::Node::Environment.create(:testing, []) @compiler = Puppet::Parser::Compiler.new(@node) @scope = Puppet::Parser::Scope.new(@compiler) @scope.source = Puppet::Resource::Type.new(:node, 'node.example.com') @scope.parent = @compiler.topscope end let(:parser) { @parser } let(:scope) { @scope } types = Puppet::Pops::Types::TypeFactory context "When evaluator evaluates literals" do { "1" => 1, "010" => 8, "0x10" => 16, "3.14" => 3.14, "0.314e1" => 3.14, "31.4e-1" => 3.14, "'1'" => '1', "'banana'" => 'banana', '"banana"' => 'banana', "banana" => 'banana', "banana::split" => 'banana::split', "false" => false, "true" => true, "Array" => types.array_of_data(), "/.*/" => /.*/ }.each do |source, result| it "should parse and evaluate the expression '#{source}' to #{result}" do parser.evaluate_string(scope, source, __FILE__).should == result end end end context "When the evaluator evaluates Lists and Hashes" do { "[]" => [], "[1,2,3]" => [1,2,3], "[1,[2.0, 2.1, [2.2]],[3.0, 3.1]]" => [1,[2.0, 2.1, [2.2]],[3.0, 3.1]], "[2 + 2]" => [4], "[1,2,3] == [1,2,3]" => true, "[1,2,3] != [2,3,4]" => true, "[1,2,3] == [2,2,3]" => false, "[1,2,3] != [1,2,3]" => false, "[1,2,3][2]" => 3, "[1,2,3] + [4,5]" => [1,2,3,4,5], "[1,2,3] + [[4,5]]" => [1,2,3,[4,5]], "[1,2,3] + 4" => [1,2,3,4], "[1,2,3] << [4,5]" => [1,2,3,[4,5]], "[1,2,3] << {'a' => 1, 'b'=>2}" => [1,2,3,{'a' => 1, 'b'=>2}], "[1,2,3] << 4" => [1,2,3,4], "[1,2,3,4] - [2,3]" => [1,4], "[1,2,3,4] - [2,5]" => [1,3,4], "[1,2,3,4] - 2" => [1,3,4], "[1,2,3,[2],4] - 2" => [1,3,[2],4], "[1,2,3,[2,3],4] - [[2,3]]" => [1,2,3,4], "[1,2,3,3,2,4,2,3] - [2,3]" => [1,4], "[1,2,3,['a',1],['b',2]] - {'a' => 1, 'b'=>2}" => [1,2,3], "[1,2,3,{'a'=>1,'b'=>2}] - [{'a' => 1, 'b'=>2}]" => [1,2,3], }.each do |source, result| it "should parse and evaluate the expression '#{source}' to #{result}" do parser.evaluate_string(scope, source, __FILE__).should == result end end { "[1,2,3] + {'a' => 1, 'b'=>2}" => [1,2,3,['a',1],['b',2]], }.each do |source, result| it "should parse and evaluate the expression '#{source}' to #{result}" do # This test must be done with match_array since the order of the hash # is undefined and Ruby 1.8.7 and 1.9.3 produce different results. expect(parser.evaluate_string(scope, source, __FILE__)).to match_array(result) end end { "[1,2,3][a]" => :error, }.each do |source, result| it "should parse and raise error for '#{source}'" do expect { parser.evaluate_string(scope, source, __FILE__) }.to raise_error(Puppet::ParseError) end end { "{}" => {}, "{'a'=>1,'b'=>2}" => {'a'=>1,'b'=>2}, "{'a'=>1,'b'=>{'x'=>2.1,'y'=>2.2}}" => {'a'=>1,'b'=>{'x'=>2.1,'y'=>2.2}}, "{'a'=> 2 + 2}" => {'a'=> 4}, "{'a'=> 1, 'b'=>2} == {'a'=> 1, 'b'=>2}" => true, "{'a'=> 1, 'b'=>2} != {'x'=> 1, 'b'=>2}" => true, "{'a'=> 1, 'b'=>2} == {'a'=> 2, 'b'=>3}" => false, "{'a'=> 1, 'b'=>2} != {'a'=> 1, 'b'=>2}" => false, "{a => 1, b => 2}[b]" => 2, "{2+2 => sum, b => 2}[4]" => 'sum', "{'a'=>1, 'b'=>2} + {'c'=>3}" => {'a'=>1,'b'=>2,'c'=>3}, "{'a'=>1, 'b'=>2} + {'b'=>3}" => {'a'=>1,'b'=>3}, "{'a'=>1, 'b'=>2} + ['c', 3, 'b', 3]" => {'a'=>1,'b'=>3, 'c'=>3}, "{'a'=>1, 'b'=>2} + [['c', 3], ['b', 3]]" => {'a'=>1,'b'=>3, 'c'=>3}, "{'a'=>1, 'b'=>2} - {'b' => 3}" => {'a'=>1}, "{'a'=>1, 'b'=>2, 'c'=>3} - ['b', 'c']" => {'a'=>1}, "{'a'=>1, 'b'=>2, 'c'=>3} - 'c'" => {'a'=>1, 'b'=>2}, }.each do |source, result| it "should parse and evaluate the expression '#{source}' to #{result}" do parser.evaluate_string(scope, source, __FILE__).should == result end end { "{'a' => 1, 'b'=>2} << 1" => :error, }.each do |source, result| it "should parse and raise error for '#{source}'" do expect { parser.evaluate_string(scope, source, __FILE__) }.to raise_error(Puppet::ParseError) end end end context "When the evaluator perform comparisons" do { "'a' == 'a'" => true, "'a' == 'b'" => false, "'a' != 'a'" => false, "'a' != 'b'" => true, "'a' < 'b' " => true, "'a' < 'a' " => false, "'b' < 'a' " => false, "'a' <= 'b'" => true, "'a' <= 'a'" => true, "'b' <= 'a'" => false, "'a' > 'b' " => false, "'a' > 'a' " => false, "'b' > 'a' " => true, "'a' >= 'b'" => false, "'a' >= 'a'" => true, "'b' >= 'a'" => true, "'a' == 'A'" => true, "'a' != 'A'" => false, "'a' > 'A'" => false, "'a' >= 'A'" => true, "'A' < 'a'" => false, "'A' <= 'a'" => true, "1 == 1" => true, "1 == 2" => false, "1 != 1" => false, "1 != 2" => true, "1 < 2 " => true, "1 < 1 " => false, "2 < 1 " => false, "1 <= 2" => true, "1 <= 1" => true, "2 <= 1" => false, "1 > 2 " => false, "1 > 1 " => false, "2 > 1 " => true, "1 >= 2" => false, "1 >= 1" => true, "2 >= 1" => true, "1 == 1.0 " => true, "1 < 1.1 " => true, "'1' < 1.1" => true, "1.0 == 1 " => true, "1.0 < 2 " => true, "1.0 < 'a'" => true, "'1.0' < 1.1" => true, "'1.0' < 'a'" => true, "'1.0' < '' " => true, "'1.0' < ' '" => true, "'a' > '1.0'" => true, "/.*/ == /.*/ " => true, "/.*/ != /a.*/" => true, "true == true " => true, "false == false" => true, "true == false" => false, }.each do |source, result| it "should parse and evaluate the expression '#{source}' to #{result}" do parser.evaluate_string(scope, source, __FILE__).should == result end end { "'a' =~ /.*/" => true, "'a' =~ '.*'" => true, "/.*/ != /a.*/" => true, "'a' !~ /b.*/" => true, "'a' !~ 'b.*'" => true, '$x = a; a =~ "$x.*"' => true, "a =~ Pattern['a.*']" => true, "a =~ Regexp['a.*']" => false, # String is not subtype of Regexp. PUP-957 "$x = /a.*/ a =~ $x" => true, "$x = Pattern['a.*'] a =~ $x" => true, "1 =~ Integer" => true, "1 !~ Integer" => false, "[1,2,3] =~ Array[Integer[1,10]]" => true, }.each do |source, result| it "should parse and evaluate the expression '#{source}' to #{result}" do parser.evaluate_string(scope, source, __FILE__).should == result end end { "666 =~ /6/" => :error, "[a] =~ /a/" => :error, "{a=>1} =~ /a/" => :error, "/a/ =~ /a/" => :error, "Array =~ /A/" => :error, }.each do |source, result| it "should parse and raise error for '#{source}'" do expect { parser.evaluate_string(scope, source, __FILE__) }.to raise_error(Puppet::ParseError) end end { "1 in [1,2,3]" => true, "4 in [1,2,3]" => false, "a in {x=>1, a=>2}" => true, "z in {x=>1, a=>2}" => false, "ana in bananas" => true, "xxx in bananas" => false, "/ana/ in bananas" => true, "/xxx/ in bananas" => false, "ANA in bananas" => false, # ANA is a type, not a String "String[1] in bananas" => false, # Philosophically true though :-) "'ANA' in bananas" => true, "ana in 'BANANAS'" => true, "/ana/ in 'BANANAS'" => false, "/ANA/ in 'BANANAS'" => true, "xxx in 'BANANAS'" => false, "[2,3] in [1,[2,3],4]" => true, "[2,4] in [1,[2,3],4]" => false, "[a,b] in ['A',['A','B'],'C']" => true, "[x,y] in ['A',['A','B'],'C']" => false, "a in {a=>1}" => true, "x in {a=>1}" => false, "'A' in {a=>1}" => true, "'X' in {a=>1}" => false, "a in {'A'=>1}" => true, "x in {'A'=>1}" => false, "/xxx/ in {'aaaxxxbbb'=>1}" => true, "/yyy/ in {'aaaxxxbbb'=>1}" => false, "15 in [1, 0xf]" => true, "15 in [1, '0xf']" => true, "'15' in [1, 0xf]" => true, "15 in [1, 115]" => false, "1 in [11, '111']" => false, "'1' in [11, '111']" => false, "Array[Integer] in [2, 3]" => false, "Array[Integer] in [2, [3, 4]]" => true, "Array[Integer] in [2, [a, 4]]" => false, "Integer in { 2 =>'a'}" => true, "Integer[5,10] in [1,5,3]" => true, "Integer[5,10] in [1,2,3]" => false, "Integer in {'a'=>'a'}" => false, "Integer in {'a'=>1}" => false, }.each do |source, result| it "should parse and evaluate the expression '#{source}' to #{result}" do parser.evaluate_string(scope, source, __FILE__).should == result end end { "if /(ana)/ in bananas {$1}" => 'ana', "if /(xyz)/ in bananas {$1} else {$1}" => nil, "$a = bananas =~ /(ana)/; $b = /(xyz)/ in bananas; $1" => 'ana', "$a = xyz =~ /(xyz)/; $b = /(ana)/ in bananas; $1" => 'ana', "if /p/ in [pineapple, bananas] {$0}" => 'p', "if /b/ in [pineapple, bananas] {$0}" => 'b', }.each do |source, result| it "sets match variables for a regexp search using in such that '#{source}' produces '#{result}'" do parser.evaluate_string(scope, source, __FILE__).should == result end end { 'Any' => ['Data', 'Scalar', 'Numeric', 'Integer', 'Float', 'Boolean', 'String', 'Pattern', 'Collection', 'Array', 'Hash', 'CatalogEntry', 'Resource', 'Class', 'Undef', 'File', 'NotYetKnownResourceType'], # Note, Data > Collection is false (so not included) 'Data' => ['Scalar', 'Numeric', 'Integer', 'Float', 'Boolean', 'String', 'Pattern', 'Array', 'Hash',], 'Scalar' => ['Numeric', 'Integer', 'Float', 'Boolean', 'String', 'Pattern'], 'Numeric' => ['Integer', 'Float'], 'CatalogEntry' => ['Class', 'Resource', 'File', 'NotYetKnownResourceType'], 'Integer[1,10]' => ['Integer[2,3]'], }.each do |general, specials| specials.each do |special | it "should compute that #{general} > #{special}" do parser.evaluate_string(scope, "#{general} > #{special}", __FILE__).should == true end it "should compute that #{special} < #{general}" do parser.evaluate_string(scope, "#{special} < #{general}", __FILE__).should == true end it "should compute that #{general} != #{special}" do parser.evaluate_string(scope, "#{special} != #{general}", __FILE__).should == true end end end { 'Integer[1,10] > Integer[2,3]' => true, 'Integer[1,10] == Integer[2,3]' => false, 'Integer[1,10] > Integer[0,5]' => false, 'Integer[1,10] > Integer[1,10]' => false, 'Integer[1,10] >= Integer[1,10]' => true, 'Integer[1,10] == Integer[1,10]' => true, }.each do |source, result| it "should parse and evaluate the integer range comparison expression '#{source}' to #{result}" do parser.evaluate_string(scope, source, __FILE__).should == result end end end context "When the evaluator performs arithmetic" do context "on Integers" do { "2+2" => 4, "2 + 2" => 4, "7 - 3" => 4, "6 * 3" => 18, "6 / 3" => 2, "6 % 3" => 0, "10 % 3" => 1, "-(6/3)" => -2, "-6/3 " => -2, "8 >> 1" => 4, "8 << 1" => 16, }.each do |source, result| it "should parse and evaluate the expression '#{source}' to #{result}" do parser.evaluate_string(scope, source, __FILE__).should == result end end context "on Floats" do { "2.2 + 2.2" => 4.4, "7.7 - 3.3" => 4.4, "6.1 * 3.1" => 18.91, "6.6 / 3.3" => 2.0, "-(6.0/3.0)" => -2.0, "-6.0/3.0 " => -2.0, }.each do |source, result| it "should parse and evaluate the expression '#{source}' to #{result}" do parser.evaluate_string(scope, source, __FILE__).should == result end end { "3.14 << 2" => :error, "3.14 >> 2" => :error, "6.6 % 3.3" => 0.0, "10.0 % 3.0" => 1.0, }.each do |source, result| it "should parse and raise error for '#{source}'" do expect { parser.evaluate_string(scope, source, __FILE__) }.to raise_error(Puppet::ParseError) end end end context "on strings requiring boxing to Numeric" do { "'2' + '2'" => 4, "'-2' + '2'" => 0, "'- 2' + '2'" => 0, '"-\t 2" + "2"' => 0, "'+2' + '2'" => 4, "'+ 2' + '2'" => 4, "'2.2' + '2.2'" => 4.4, "'-2.2' + '2.2'" => 0.0, "'0xF7' + '010'" => 0xFF, "'0xF7' + '0x8'" => 0xFF, "'0367' + '010'" => 0xFF, "'012.3' + '010'" => 20.3, "'-0x2' + '0x4'" => 2, "'+0x2' + '0x4'" => 6, "'-02' + '04'" => 2, "'+02' + '04'" => 6, }.each do |source, result| it "should parse and evaluate the expression '#{source}' to #{result}" do parser.evaluate_string(scope, source, __FILE__).should == result end end { "'0888' + '010'" => :error, "'0xWTF' + '010'" => :error, "'0x12.3' + '010'" => :error, "'0x12.3' + '010'" => :error, '"-\n 2" + "2"' => :error, '"-\v 2" + "2"' => :error, '"-2\n" + "2"' => :error, '"-2\n " + "2"' => :error, }.each do |source, result| it "should parse and raise error for '#{source}'" do expect { parser.evaluate_string(scope, source, __FILE__) }.to raise_error(Puppet::ParseError) end end end end end # arithmetic context "When the evaluator evaluates assignment" do { "$a = 5" => 5, "$a = 5; $a" => 5, "$a = 5; $b = 6; $a" => 5, "$a = $b = 5; $a == $b" => true, "$a = [1,2,3]; [x].map |$x| { $a += x; $a }" => [[1,2,3,'x']], "$a = [a,x,c]; [x].map |$x| { $a -= x; $a }" => [['a','c']], }.each do |source, result| it "should parse and evaluate the expression '#{source}' to #{result}" do parser.evaluate_string(scope, source, __FILE__).should == result end end { "[a,b,c] = [1,2,3]; $a == 1 and $b == 2 and $c == 3" => :error, "[a,b,c] = {b=>2,c=>3,a=>1}; $a == 1 and $b == 2 and $c == 3" => :error, "$a = [1,2,3]; [x].collect |$x| { [a] += x; $a }" => :error, "$a = [a,x,c]; [x].collect |$x| { [a] -= x; $a }" => :error, }.each do |source, result| it "should parse and evaluate the expression '#{source}' to #{result}" do expect { parser.evaluate_string(scope, source, __FILE__)}.to raise_error(Puppet::ParseError) end end end context "When the evaluator evaluates conditionals" do { "if true {5}" => 5, "if false {5}" => nil, "if false {2} else {5}" => 5, "if false {2} elsif true {5}" => 5, "if false {2} elsif false {5}" => nil, "unless false {5}" => 5, "unless true {5}" => nil, "unless true {2} else {5}" => 5, "$a = if true {5} $a" => 5, "$a = if false {5} $a" => nil, "$a = if false {2} else {5} $a" => 5, "$a = if false {2} elsif true {5} $a" => 5, "$a = if false {2} elsif false {5} $a" => nil, "$a = unless false {5} $a" => 5, "$a = unless true {5} $a" => nil, "$a = unless true {2} else {5} $a" => 5, }.each do |source, result| it "should parse and evaluate the expression '#{source}' to #{result}" do parser.evaluate_string(scope, source, __FILE__).should == result end end { "case 1 { 1 : { yes } }" => 'yes', "case 2 { 1,2,3 : { yes} }" => 'yes', "case 2 { 1,3 : { no } 2: { yes} }" => 'yes', "case 2 { 1,3 : { no } 5: { no } default: { yes }}" => 'yes', "case 2 { 1,3 : { no } 5: { no } }" => nil, "case 'banana' { 1,3 : { no } /.*ana.*/: { yes } }" => 'yes', "case 'banana' { /.*(ana).*/: { $1 } }" => 'ana', "case [1] { Array : { yes } }" => 'yes', "case [1] { Array[String] : { no } Array[Integer]: { yes } }" => 'yes', "case 1 { Integer : { yes } Type[Integer] : { no } }" => 'yes', "case Integer { Integer : { no } Type[Integer] : { yes } }" => 'yes', # supports unfold "case ringo { *[paul, john, ringo, george] : { 'beatle' } }" => 'beatle', }.each do |source, result| it "should parse and evaluate the expression '#{source}' to #{result}" do parser.evaluate_string(scope, source, __FILE__).should == result end end { "2 ? { 1 => no, 2 => yes}" => 'yes', "3 ? { 1 => no, 2 => no, default => yes }" => 'yes', "3 ? { 1 => no, default => yes, 3 => no }" => 'no', "3 ? { 1 => no, 3 => no, default => yes }" => 'no', "4 ? { 1 => no, default => yes, 3 => no }" => 'yes', "4 ? { 1 => no, 3 => no, default => yes }" => 'yes', "'banana' ? { /.*(ana).*/ => $1 }" => 'ana', "[2] ? { Array[String] => yes, Array => yes}" => 'yes', "ringo ? *[paul, john, ringo, george] => 'beatle'" => 'beatle', }.each do |source, result| it "should parse and evaluate the expression '#{source}' to #{result}" do parser.evaluate_string(scope, source, __FILE__).should == result end end it 'fails if a selector does not match' do expect{parser.evaluate_string(scope, "2 ? 3 => 4")}.to raise_error(/No matching entry for selector parameter with value '2'/) end end context "When evaluator evaluated unfold" do { "*[1,2,3]" => [1,2,3], "*1" => [1], "*'a'" => ['a'] }.each do |source, result| it "should parse and evaluate the expression '#{source}' to #{result}" do parser.evaluate_string(scope, source, __FILE__).should == result end end it "should parse and evaluate the expression '*{a=>10, b=>20} to [['a',10],['b',20]]" do result = parser.evaluate_string(scope, '*{a=>10, b=>20}', __FILE__) expect(result).to include(['a', 10]) expect(result).to include(['b', 20]) end end context "When evaluator performs [] operations" do { "[1,2,3][0]" => 1, "[1,2,3][2]" => 3, "[1,2,3][3]" => nil, "[1,2,3][-1]" => 3, "[1,2,3][-2]" => 2, "[1,2,3][-4]" => nil, "[1,2,3,4][0,2]" => [1,2], "[1,2,3,4][1,3]" => [2,3,4], "[1,2,3,4][-2,2]" => [3,4], "[1,2,3,4][-3,2]" => [2,3], "[1,2,3,4][3,5]" => [4], "[1,2,3,4][5,2]" => [], "[1,2,3,4][0,-1]" => [1,2,3,4], "[1,2,3,4][0,-2]" => [1,2,3], "[1,2,3,4][0,-4]" => [1], "[1,2,3,4][0,-5]" => [], "[1,2,3,4][-5,2]" => [1], "[1,2,3,4][-5,-3]" => [1,2], "[1,2,3,4][-6,-3]" => [1,2], "[1,2,3,4][2,-3]" => [], "[1,*[2,3],4]" => [1,2,3,4], "[1,*[2,3],4][1]" => 2, }.each do |source, result| it "should parse and evaluate the expression '#{source}' to #{result}" do parser.evaluate_string(scope, source, __FILE__).should == result end end { "{a=>1, b=>2, c=>3}[a]" => 1, "{a=>1, b=>2, c=>3}[c]" => 3, "{a=>1, b=>2, c=>3}[x]" => nil, "{a=>1, b=>2, c=>3}[c,b]" => [3,2], "{a=>1, b=>2, c=>3}[a,b,c]" => [1,2,3], "{a=>{b=>{c=>'it works'}}}[a][b][c]" => 'it works', "$a = {undef => 10} $a[free_lunch]" => nil, "$a = {undef => 10} $a[undef]" => 10, "$a = {undef => 10} $a[$a[free_lunch]]" => 10, "$a = {} $a[free_lunch] == undef" => true, }.each do |source, result| it "should parse and evaluate the expression '#{source}' to #{result}" do parser.evaluate_string(scope, source, __FILE__).should == result end end { "'abc'[0]" => 'a', "'abc'[2]" => 'c', "'abc'[-1]" => 'c', "'abc'[-2]" => 'b', "'abc'[-3]" => 'a', "'abc'[-4]" => '', "'abc'[3]" => '', "abc[0]" => 'a', "abc[2]" => 'c', "abc[-1]" => 'c', "abc[-2]" => 'b', "abc[-3]" => 'a', "abc[-4]" => '', "abc[3]" => '', "'abcd'[0,2]" => 'ab', "'abcd'[1,3]" => 'bcd', "'abcd'[-2,2]" => 'cd', "'abcd'[-3,2]" => 'bc', "'abcd'[3,5]" => 'd', "'abcd'[5,2]" => '', "'abcd'[0,-1]" => 'abcd', "'abcd'[0,-2]" => 'abc', "'abcd'[0,-4]" => 'a', "'abcd'[0,-5]" => '', "'abcd'[-5,2]" => 'a', "'abcd'[-5,-3]" => 'ab', "'abcd'[-6,-3]" => 'ab', "'abcd'[2,-3]" => '', }.each do |source, result| it "should parse and evaluate the expression '#{source}' to #{result}" do parser.evaluate_string(scope, source, __FILE__).should == result end end # Type operations (full set tested by tests covering type calculator) { "Array[Integer]" => types.array_of(types.integer), "Array[Integer,1]" => types.constrain_size(types.array_of(types.integer),1, :default), "Array[Integer,1,2]" => types.constrain_size(types.array_of(types.integer),1, 2), "Array[Integer,Integer[1,2]]" => types.constrain_size(types.array_of(types.integer),1, 2), "Array[Integer,Integer[1]]" => types.constrain_size(types.array_of(types.integer),1, :default), "Hash[Integer,Integer]" => types.hash_of(types.integer, types.integer), "Hash[Integer,Integer,1]" => types.constrain_size(types.hash_of(types.integer, types.integer),1, :default), "Hash[Integer,Integer,1,2]" => types.constrain_size(types.hash_of(types.integer, types.integer),1, 2), "Hash[Integer,Integer,Integer[1,2]]" => types.constrain_size(types.hash_of(types.integer, types.integer),1, 2), "Hash[Integer,Integer,Integer[1]]" => types.constrain_size(types.hash_of(types.integer, types.integer),1, :default), "Resource[File]" => types.resource('File'), "Resource['File']" => types.resource(types.resource('File')), "File[foo]" => types.resource('file', 'foo'), "File[foo, bar]" => [types.resource('file', 'foo'), types.resource('file', 'bar')], "Pattern[a, /b/, Pattern[c], Regexp[d]]" => types.pattern('a', 'b', 'c', 'd'), "String[1,2]" => types.constrain_size(types.string,1, 2), "String[Integer[1,2]]" => types.constrain_size(types.string,1, 2), "String[Integer[1]]" => types.constrain_size(types.string,1, :default), }.each do |source, result| it "should parse and evaluate the expression '#{source}' to #{result}" do parser.evaluate_string(scope, source, __FILE__).should == result end end # LHS where [] not supported, and missing key(s) { "Array[]" => :error, "'abc'[]" => :error, "Resource[]" => :error, "File[]" => :error, "String[]" => :error, "1[]" => :error, "3.14[]" => :error, "/.*/[]" => :error, "$a=[1] $a[]" => :error, }.each do |source, result| it "should parse and evaluate the expression '#{source}' to #{result}" do expect { parser.evaluate_string(scope, source, __FILE__)}.to raise_error(/Syntax error/) end end # Errors when wrong number/type of keys are used { "Array[0]" => 'Array-Type[] arguments must be types. Got Fixnum', "Hash[0]" => 'Hash-Type[] arguments must be types. Got Fixnum', "Hash[Integer, 0]" => 'Hash-Type[] arguments must be types. Got Fixnum', "Array[Integer,1,2,3]" => 'Array-Type[] accepts 1 to 3 arguments. Got 4', "Array[Integer,String]" => "A Type's size constraint arguments must be a single Integer type, or 1-2 integers (or default). Got a String-Type", "Hash[Integer,String, 1,2,3]" => 'Hash-Type[] accepts 1 to 4 arguments. Got 5', "'abc'[x]" => "The value 'x' cannot be converted to Numeric", "'abc'[1.0]" => "A String[] cannot use Float where Integer is expected", "'abc'[1,2,3]" => "String supports [] with one or two arguments. Got 3", "Resource[0]" => 'First argument to Resource[] must be a resource type or a String. Got Fixnum', "Resource[a, 0]" => 'Error creating type specialization of a Resource-Type, Cannot use Fixnum where String is expected', "File[0]" => 'Error creating type specialization of a File-Type, Cannot use Fixnum where String is expected', "String[a]" => "A Type's size constraint arguments must be a single Integer type, or 1-2 integers (or default). Got a String", "Pattern[0]" => 'Error creating type specialization of a Pattern-Type, Cannot use Fixnum where String or Regexp or Pattern-Type or Regexp-Type is expected', "Regexp[0]" => 'Error creating type specialization of a Regexp-Type, Cannot use Fixnum where String or Regexp is expected', "Regexp[a,b]" => 'A Regexp-Type[] accepts 1 argument. Got 2', "true[0]" => "Operator '[]' is not applicable to a Boolean", "1[0]" => "Operator '[]' is not applicable to an Integer", "3.14[0]" => "Operator '[]' is not applicable to a Float", "/.*/[0]" => "Operator '[]' is not applicable to a Regexp", "[1][a]" => "The value 'a' cannot be converted to Numeric", "[1][0.0]" => "An Array[] cannot use Float where Integer is expected", "[1]['0.0']" => "An Array[] cannot use Float where Integer is expected", "[1,2][1, 0.0]" => "An Array[] cannot use Float where Integer is expected", "[1,2][1.0, -1]" => "An Array[] cannot use Float where Integer is expected", "[1,2][1, -1.0]" => "An Array[] cannot use Float where Integer is expected", }.each do |source, result| it "should parse and evaluate the expression '#{source}' to #{result}" do expect { parser.evaluate_string(scope, source, __FILE__)}.to raise_error(Regexp.new(Regexp.quote(result))) end end context "on catalog types" do it "[n] gets resource parameter [n]" do source = "notify { 'hello': message=>'yo'} Notify[hello][message]" parser.evaluate_string(scope, source, __FILE__).should == 'yo' end it "[n] gets class parameter [n]" do source = "class wonka($produces='chocolate'){ } include wonka Class[wonka][produces]" # This is more complicated since it needs to run like 3.x and do an import_ast adapted_parser = Puppet::Parser::E4ParserAdapter.new adapted_parser.file = __FILE__ ast = adapted_parser.parse(source) Puppet.override({:global_scope => scope}, "test") do scope.known_resource_types.import_ast(ast, '') ast.code.safeevaluate(scope).should == 'chocolate' end end # Resource default and override expressions and resource parameter access with [] { # Properties "notify { id: message=>explicit} Notify[id][message]" => "explicit", "Notify { message=>by_default} notify {foo:} Notify[foo][message]" => "by_default", "notify {foo:} Notify[foo]{message =>by_override} Notify[foo][message]" => "by_override", # Parameters "notify { id: withpath=>explicit} Notify[id][withpath]" => "explicit", "Notify { withpath=>by_default } notify { foo: } Notify[foo][withpath]" => "by_default", "notify {foo:} Notify[foo]{withpath=>by_override} Notify[foo][withpath]" => "by_override", # Metaparameters "notify { foo: tag => evoe} Notify[foo][tag]" => "evoe", # Does not produce the defaults for tag parameter (title, type or names of scopes) "notify { foo: } Notify[foo][tag]" => nil, # But a default may be specified on the type "Notify { tag=>by_default } notify { foo: } Notify[foo][tag]" => "by_default", "Notify { tag=>by_default } notify { foo: } Notify[foo]{ tag=>by_override } Notify[foo][tag]" => "by_override", }.each do |source, result| it "should parse and evaluate the expression '#{source}' to #{result}" do parser.evaluate_string(scope, source, __FILE__).should == result end end # Virtual and realized resource default and overridden resource parameter access with [] { # Properties "@notify { id: message=>explicit } Notify[id][message]" => "explicit", "@notify { id: message=>explicit } realize Notify[id] Notify[id][message]" => "explicit", "Notify { message=>by_default } @notify { id: } Notify[id][message]" => "by_default", "Notify { message=>by_default } @notify { id: tag=>thisone } Notify <| tag == thisone |>; Notify[id][message]" => "by_default", "@notify { id: } Notify[id]{message=>by_override} Notify[id][message]" => "by_override", # Parameters "@notify { id: withpath=>explicit } Notify[id][withpath]" => "explicit", "Notify { withpath=>by_default } @notify { id: } Notify[id][withpath]" => "by_default", "@notify { id: } realize Notify[id] Notify[id]{withpath=>by_override} Notify[id][withpath]" => "by_override", # Metaparameters "@notify { id: tag=>explicit } Notify[id][tag]" => "explicit", }.each do |source, result| it "parses and evaluates virtual and realized resources in the expression '#{source}' to #{result}" do expect(parser.evaluate_string(scope, source, __FILE__)).to eq(result) end end # Exported resource attributes { "@@notify { id: message=>explicit } Notify[id][message]" => "explicit", "@@notify { id: message=>explicit, tag=>thisone } Notify <<| tag == thisone |>> Notify[id][message]" => "explicit", }.each do |source, result| it "parses and evaluates exported resources in the expression '#{source}' to #{result}" do expect(parser.evaluate_string(scope, source, __FILE__)).to eq(result) end end # Resource default and override expressions and resource parameter access error conditions { "notify { xid: message=>explicit} Notify[id][message]" => /Resource not found/, "notify { id: message=>explicit} Notify[id][mustard]" => /does not have a parameter called 'mustard'/, # NOTE: these meta-esque parameters are not recognized as such "notify { id: message=>explicit} Notify[id][title]" => /does not have a parameter called 'title'/, "notify { id: message=>explicit} Notify[id]['type']" => /does not have a parameter called 'type'/, "notify { id: message=>explicit } Notify[id]{message=>override}" => /'message' is already set on Notify\[id\]/ }.each do |source, result| it "should parse '#{source}' and raise error matching #{result}" do expect { parser.evaluate_string(scope, source, __FILE__)}.to raise_error(result) end end context 'with errors' do { "Class['fail-whale']" => /Illegal name/, "Class[0]" => /An Integer cannot be used where a String is expected/, "Class[/.*/]" => /A Regexp cannot be used where a String is expected/, "Class[4.1415]" => /A Float cannot be used where a String is expected/, "Class[Integer]" => /An Integer-Type cannot be used where a String is expected/, "Class[File['tmp']]" => /A File\['tmp'\] Resource-Reference cannot be used where a String is expected/, }.each do | source, error_pattern| it "an error is flagged for '#{source}'" do expect { parser.evaluate_string(scope, source, __FILE__)}.to raise_error(error_pattern) end end end end # end [] operations end context "When the evaluator performs boolean operations" do { "true and true" => true, "false and true" => false, "true and false" => false, "false and false" => false, "true or true" => true, "false or true" => true, "true or false" => true, "false or false" => false, "! true" => false, "!! true" => true, "!! false" => false, "! 'x'" => false, "! ''" => false, "! undef" => true, "! [a]" => false, "! []" => false, "! {a=>1}" => false, "! {}" => false, "true and false and '0xwtf' + 1" => false, "false or true or '0xwtf' + 1" => true, }.each do |source, result| it "should parse and evaluate the expression '#{source}' to #{result}" do parser.evaluate_string(scope, source, __FILE__).should == result end end { "false || false || '0xwtf' + 1" => :error, }.each do |source, result| it "should parse and raise error for '#{source}'" do expect { parser.evaluate_string(scope, source, __FILE__) }.to raise_error(Puppet::ParseError) end end end context "When evaluator performs operations on literal undef" do it "computes non existing hash lookup as undef" do parser.evaluate_string(scope, "{a => 1}[b] == undef", __FILE__).should == true parser.evaluate_string(scope, "undef == {a => 1}[b]", __FILE__).should == true end end context "When evaluator performs calls" do let(:populate) do parser.evaluate_string(scope, "$a = 10 $b = [1,2,3]") end { 'sprintf( "x%iy", $a )' => "x10y", # unfolds 'sprintf( *["x%iy", $a] )' => "x10y", '"x%iy".sprintf( $a )' => "x10y", '$b.reduce |$memo,$x| { $memo + $x }' => 6, 'reduce($b) |$memo,$x| { $memo + $x }' => 6, }.each do |source, result| it "should parse and evaluate the expression '#{source}' to #{result}" do populate parser.evaluate_string(scope, source, __FILE__).should == result end end { '"value is ${a*2} yo"' => :error, }.each do |source, result| it "should parse and raise error for '#{source}'" do expect { parser.evaluate_string(scope, source, __FILE__) }.to raise_error(Puppet::ParseError) end end it "provides location information on error in unparenthesized call logic" do expect{parser.evaluate_string(scope, "include non_existing_class", __FILE__)}.to raise_error(Puppet::ParseError, /line 1\:1/) end it 'defaults can be given in a lambda and used only when arg is missing' do env_loader = @compiler.loaders.public_environment_loader fc = Puppet::Functions.create_function(:test) do dispatch :test do param 'Integer', 'count' required_block_param end def test(count, block) block.call({}, *[].fill(10, 0, count)) end end the_func = fc.new({}, env_loader) env_loader.add_entry(:function, 'test', the_func, __FILE__) expect(parser.evaluate_string(scope, "test(1) |$x, $y=20| { $x + $y}")).to eql(30) expect(parser.evaluate_string(scope, "test(2) |$x, $y=20| { $x + $y}")).to eql(20) end it 'a given undef does not select the default value' do env_loader = @compiler.loaders.public_environment_loader fc = Puppet::Functions.create_function(:test) do dispatch :test do param 'Any', 'lambda_arg' required_block_param end def test(lambda_arg, block) block.call({}, lambda_arg) end end the_func = fc.new({}, env_loader) env_loader.add_entry(:function, 'test', the_func, __FILE__) expect(parser.evaluate_string(scope, "test(undef) |$x=20| { $x == undef}")).to eql(true) end it 'a given undef is given as nil' do env_loader = @compiler.loaders.public_environment_loader fc = Puppet::Functions.create_function(:assert_no_undef) do dispatch :assert_no_undef do param 'Any', 'x' end def assert_no_undef(x) case x when Array return unless x.include?(:undef) when Hash return unless x.keys.include?(:undef) || x.values.include?(:undef) else return unless x == :undef end raise "contains :undef" end end the_func = fc.new({}, env_loader) env_loader.add_entry(:function, 'assert_no_undef', the_func, __FILE__) expect{parser.evaluate_string(scope, "assert_no_undef(undef)")}.to_not raise_error() expect{parser.evaluate_string(scope, "assert_no_undef([undef])")}.to_not raise_error() expect{parser.evaluate_string(scope, "assert_no_undef({undef => 1})")}.to_not raise_error() expect{parser.evaluate_string(scope, "assert_no_undef({1 => undef})")}.to_not raise_error() end end context "When evaluator performs string interpolation" do let(:populate) do parser.evaluate_string(scope, "$a = 10 $b = [1,2,3]") end { '"value is $a yo"' => "value is 10 yo", '"value is \$a yo"' => "value is $a yo", '"value is ${a} yo"' => "value is 10 yo", '"value is \${a} yo"' => "value is ${a} yo", '"value is ${$a} yo"' => "value is 10 yo", '"value is ${$a*2} yo"' => "value is 20 yo", '"value is ${sprintf("x%iy",$a)} yo"' => "value is x10y yo", '"value is ${"x%iy".sprintf($a)} yo"' => "value is x10y yo", '"value is ${[1,2,3]} yo"' => "value is [1, 2, 3] yo", '"value is ${/.*/} yo"' => "value is /.*/ yo", '$x = undef "value is $x yo"' => "value is yo", '$x = default "value is $x yo"' => "value is default yo", '$x = Array[Integer] "value is $x yo"' => "value is Array[Integer] yo", '"value is ${Array[Integer]} yo"' => "value is Array[Integer] yo", }.each do |source, result| it "should parse and evaluate the expression '#{source}' to #{result}" do populate parser.evaluate_string(scope, source, __FILE__).should == result end end it "should parse and evaluate an interpolation of a hash" do source = '"value is ${{a=>1,b=>2}} yo"' # This test requires testing against two options because a hash to string # produces a result that is unordered hashstr = {'a' => 1, 'b' => 2}.to_s alt_results = ["value is {a => 1, b => 2} yo", "value is {b => 2, a => 1} yo" ] populate parse_result = parser.evaluate_string(scope, source, __FILE__) alt_results.include?(parse_result).should == true end it 'should accept a variable with leading underscore when used directly' do source = '$_x = 10; "$_x"' expect(parser.evaluate_string(scope, source, __FILE__)).to eql('10') end it 'should accept a variable with leading underscore when used as an expression' do source = '$_x = 10; "${_x}"' expect(parser.evaluate_string(scope, source, __FILE__)).to eql('10') end { '"value is ${a*2} yo"' => :error, }.each do |source, result| it "should parse and raise error for '#{source}'" do expect { parser.evaluate_string(scope, source, __FILE__) }.to raise_error(Puppet::ParseError) end end end context "When evaluating variables" do context "that are non existing an error is raised for" do it "unqualified variable" do expect { parser.evaluate_string(scope, "$quantum_gravity", __FILE__) }.to raise_error(/Unknown variable/) end it "qualified variable" do expect { parser.evaluate_string(scope, "$quantum_gravity::graviton", __FILE__) }.to raise_error(/Unknown variable/) end end it "a lex error should be raised for '$foo::::bar'" do expect { parser.evaluate_string(scope, "$foo::::bar") }.to raise_error(Puppet::LexError, /Illegal fully qualified name at line 1:7/) end { '$a = $0' => nil, '$a = $1' => nil, }.each do |source, value| it "it is ok to reference numeric unassigned variables '#{source}'" do parser.evaluate_string(scope, source, __FILE__).should == value end end { '$00 = 0' => /must be a decimal value/, '$0xf = 0' => /must be a decimal value/, '$0777 = 0' => /must be a decimal value/, '$123a = 0' => /must be a decimal value/, }.each do |source, error_pattern| it "should raise an error for '#{source}'" do expect { parser.evaluate_string(scope, source, __FILE__) }.to raise_error(error_pattern) end end context "an initial underscore in the last segment of a var name is allowed" do { '$_a = 1' => 1, '$__a = 1' => 1, }.each do |source, value| it "as in this example '#{source}'" do parser.evaluate_string(scope, source, __FILE__).should == value end end end end context "When evaluating relationships" do it 'should form a relation with File[a] -> File[b]' do source = "File[a] -> File[b]" parser.evaluate_string(scope, source, __FILE__) scope.compiler.should have_relationship(['File', 'a', '->', 'File', 'b']) end it 'should form a relation with resource -> resource' do source = "notify{a:} -> notify{b:}" parser.evaluate_string(scope, source, __FILE__) scope.compiler.should have_relationship(['Notify', 'a', '->', 'Notify', 'b']) end it 'should form a relation with [File[a], File[b]] -> [File[x], File[y]]' do source = "[File[a], File[b]] -> [File[x], File[y]]" parser.evaluate_string(scope, source, __FILE__) scope.compiler.should have_relationship(['File', 'a', '->', 'File', 'x']) scope.compiler.should have_relationship(['File', 'b', '->', 'File', 'x']) scope.compiler.should have_relationship(['File', 'a', '->', 'File', 'y']) scope.compiler.should have_relationship(['File', 'b', '->', 'File', 'y']) end it 'should tolerate (eliminate) duplicates in operands' do source = "[File[a], File[a]] -> File[x]" parser.evaluate_string(scope, source, __FILE__) scope.compiler.should have_relationship(['File', 'a', '->', 'File', 'x']) scope.compiler.relationships.size.should == 1 end it 'should form a relation with <-' do source = "File[a] <- File[b]" parser.evaluate_string(scope, source, __FILE__) scope.compiler.should have_relationship(['File', 'b', '->', 'File', 'a']) end it 'should form a relation with <-' do source = "File[a] <~ File[b]" parser.evaluate_string(scope, source, __FILE__) scope.compiler.should have_relationship(['File', 'b', '~>', 'File', 'a']) end end context "When evaluating heredoc" do it "evaluates plain heredoc" do src = "@(END)\nThis is\nheredoc text\nEND\n" parser.evaluate_string(scope, src).should == "This is\nheredoc text\n" end it "parses heredoc with margin" do src = [ "@(END)", " This is", " heredoc text", " | END", "" ].join("\n") parser.evaluate_string(scope, src).should == "This is\nheredoc text\n" end it "parses heredoc with margin and right newline trim" do src = [ "@(END)", " This is", " heredoc text", " |- END", "" ].join("\n") parser.evaluate_string(scope, src).should == "This is\nheredoc text" end it "parses escape specification" do src = <<-CODE @(END/t) Tex\\tt\\n |- END CODE parser.evaluate_string(scope, src).should == "Tex\tt\\n" end it "parses syntax checked specification" do src = <<-CODE @(END:json) ["foo", "bar"] |- END CODE parser.evaluate_string(scope, src).should == '["foo", "bar"]' end it "parses syntax checked specification with error and reports it" do src = <<-CODE @(END:json) ['foo', "bar"] |- END CODE expect { parser.evaluate_string(scope, src)}.to raise_error(/Cannot parse invalid JSON string/) end it "parses interpolated heredoc expression" do src = <<-CODE $name = 'Fjodor' @("END") Hello $name |- END CODE parser.evaluate_string(scope, src).should == "Hello Fjodor" end end context "Handles Deprecations and Discontinuations" do it 'of import statements' do source = "\nimport foo" # Error references position 5 at the opening '{' # Set file to nil to make it easier to match with line number (no file name in output) expect { parser.evaluate_string(scope, source) }.to raise_error(/'import' has been discontinued.*line 2:1/) end end context "Detailed Error messages are reported" do it 'for illegal type references' do source = '1+1 { "title": }' # Error references position 5 at the opening '{' # Set file to nil to make it easier to match with line number (no file name in output) - expect { parser.parse_string(source, nil) }.to raise_error(/Expression is not valid as a resource.*line 1:5/) + expect { parser.evaluate_string(scope, source) }.to raise_error( + /Illegal Resource Type expression, expected result to be a type name, or untitled Resource.*line 1:2/) end it 'for non r-value producing <| |>' do expect { parser.parse_string("$a = File <| |>", nil) }.to raise_error(/A Virtual Query does not produce a value at line 1:6/) end it 'for non r-value producing <<| |>>' do expect { parser.parse_string("$a = File <<| |>>", nil) }.to raise_error(/An Exported Query does not produce a value at line 1:6/) end it 'for non r-value producing define' do Puppet.expects(:err).with("Invalid use of expression. A 'define' expression does not produce a value at line 1:6") Puppet.expects(:err).with("Classes, definitions, and nodes may only appear at toplevel or inside other classes at line 1:6") expect { parser.parse_string("$a = define foo { }", nil) }.to raise_error(/2 errors/) end it 'for non r-value producing class' do Puppet.expects(:err).with("Invalid use of expression. A Host Class Definition does not produce a value at line 1:6") Puppet.expects(:err).with("Classes, definitions, and nodes may only appear at toplevel or inside other classes at line 1:6") expect { parser.parse_string("$a = class foo { }", nil) }.to raise_error(/2 errors/) end it 'for unclosed quote with indication of start position of string' do source = <<-SOURCE.gsub(/^ {6}/,'') $a = "xx yyy SOURCE # first char after opening " reported as being in error. expect { parser.parse_string(source) }.to raise_error(/Unclosed quote after '"' followed by 'xx\\nyy\.\.\.' at line 1:7/) end it 'for multiple errors with a summary exception' do Puppet.expects(:err).with("Invalid use of expression. A Node Definition does not produce a value at line 1:6") Puppet.expects(:err).with("Classes, definitions, and nodes may only appear at toplevel or inside other classes at line 1:6") expect { parser.parse_string("$a = node x { }",nil) }.to raise_error(/2 errors/) end it 'for a bad hostname' do expect { parser.parse_string("node 'macbook+owned+by+name' { }", nil) }.to raise_error(/The hostname 'macbook\+owned\+by\+name' contains illegal characters.*at line 1:6/) end it 'for a hostname with interpolation' do source = <<-SOURCE.gsub(/^ {6}/,'') $name = 'fred' node "macbook-owned-by$name" { } SOURCE expect { parser.parse_string(source, nil) }.to raise_error(/An interpolated expression is not allowed in a hostname of a node at line 2:23/) end end context 'does not leak variables' do it 'local variables are gone when lambda ends' do source = <<-SOURCE [1,2,3].each |$x| { $y = $x} $a = $y SOURCE expect do parser.evaluate_string(scope, source) end.to raise_error(/Unknown variable: 'y'/) end it 'lambda parameters are gone when lambda ends' do source = <<-SOURCE [1,2,3].each |$x| { $y = $x} $a = $x SOURCE expect do parser.evaluate_string(scope, source) end.to raise_error(/Unknown variable: 'x'/) end it 'does not leak match variables' do source = <<-SOURCE if 'xyz' =~ /(x)(y)(z)/ { notice $2 } case 'abc' { /(a)(b)(c)/ : { $x = $2 } } "-$x-$2-" SOURCE expect(parser.evaluate_string(scope, source)).to eq('-b--') end end matcher :have_relationship do |expected| calc = Puppet::Pops::Types::TypeCalculator.new match do |compiler| op_name = {'->' => :relationship, '~>' => :subscription} compiler.relationships.any? do | relation | relation.source.type == expected[0] && relation.source.title == expected[1] && relation.type == op_name[expected[2]] && relation.target.type == expected[3] && relation.target.title == expected[4] end end failure_message_for_should do |actual| "Relationship #{expected[0]}[#{expected[1]}] #{expected[2]} #{expected[3]}[#{expected[4]}] but was unknown to compiler" end end end diff --git a/spec/unit/pops/parser/epp_parser_spec.rb b/spec/unit/pops/parser/epp_parser_spec.rb index 0db4ba7d9..fb32b9ba4 100644 --- a/spec/unit/pops/parser/epp_parser_spec.rb +++ b/spec/unit/pops/parser/epp_parser_spec.rb @@ -1,86 +1,115 @@ require 'spec_helper' require 'puppet/pops' require File.join(File.dirname(__FILE__), '/../factory_rspec_helper') module EppParserRspecHelper include FactoryRspecHelper def parse(code) parser = Puppet::Pops::Parser::EppParser.new() parser.parse_string(code) end end describe "epp parser" do include EppParserRspecHelper it "should instantiate an epp parser" do parser = Puppet::Pops::Parser::EppParser.new() parser.class.should == Puppet::Pops::Parser::EppParser end it "should parse a code string and return a program with epp" do parser = Puppet::Pops::Parser::EppParser.new() model = parser.parse_string("Nothing to see here, move along...").current model.class.should == Puppet::Pops::Model::Program model.body.class.should == Puppet::Pops::Model::LambdaExpression model.body.body.class.should == Puppet::Pops::Model::EppExpression end context "when facing bad input it reports" do it "unbalanced tags" do expect { dump(parse("<% missing end tag")) }.to raise_error(/Unbalanced/) end it "abrupt end" do expect { dump(parse("dum di dum di dum <%")) }.to raise_error(/Unbalanced/) end it "nested epp tags" do expect { dump(parse("<% $a = 10 <% $b = 20 %>%>")) }.to raise_error(/Syntax error/) end it "nested epp expression tags" do expect { dump(parse("<%= 1+1 <%= 2+2 %>%>")) }.to raise_error(/Syntax error/) end it "rendering sequence of expressions" do expect { dump(parse("<%= 1 2 3 %>")) }.to raise_error(/Syntax error/) end end context "handles parsing of" do it "text (and nothing else)" do - dump(parse("Hello World")).should == "(lambda (epp (block (render-s 'Hello World'))))" + dump(parse("Hello World")).should == [ + "(lambda (epp (block", + " (render-s 'Hello World')", + ")))"].join("\n") end it "template parameters" do - dump(parse("<%|$x|%>Hello World")).should == "(lambda (parameters x) (epp (block (render-s 'Hello World'))))" + dump(parse("<%|$x|%>Hello World")).should == [ + "(lambda (parameters x) (epp (block", + " (render-s 'Hello World')", + ")))"].join("\n") end it "template parameters with default" do - dump(parse("<%|$x='cigar'|%>Hello World")).should == "(lambda (parameters (= x 'cigar')) (epp (block (render-s 'Hello World'))))" + dump(parse("<%|$x='cigar'|%>Hello World")).should == [ + "(lambda (parameters (= x 'cigar')) (epp (block", + " (render-s 'Hello World')", + ")))"].join("\n") end it "template parameters with and without default" do - dump(parse("<%|$x='cigar', $y|%>Hello World")).should == "(lambda (parameters (= x 'cigar') y) (epp (block (render-s 'Hello World'))))" + dump(parse("<%|$x='cigar', $y|%>Hello World")).should == [ + "(lambda (parameters (= x 'cigar') y) (epp (block", + " (render-s 'Hello World')", + ")))"].join("\n") end it "template parameters + additional setup" do - dump(parse("<%|$x| $y = 10 %>Hello World")).should == "(lambda (parameters x) (epp (block (= $y 10) (render-s 'Hello World'))))" + dump(parse("<%|$x| $y = 10 %>Hello World")).should == [ + "(lambda (parameters x) (epp (block", + " (= $y 10)", + " (render-s 'Hello World')", + ")))"].join("\n") end it "comments" do - dump(parse("<%#($x='cigar', $y)%>Hello World")).should == "(lambda (epp (block (render-s 'Hello World'))))" + dump(parse("<%#($x='cigar', $y)%>Hello World")).should == [ + "(lambda (epp (block", + " (render-s 'Hello World')", + ")))" + ].join("\n") end it "verbatim epp tags" do - dump(parse("<%% contemplating %%>Hello World")).should == "(lambda (epp (block (render-s '<% contemplating %>Hello World'))))" + dump(parse("<%% contemplating %%>Hello World")).should == [ + "(lambda (epp (block", + " (render-s '<% contemplating %>Hello World')", + ")))" + ].join("\n") end it "expressions" do - dump(parse("We all live in <%= 3.14 - 2.14 %> world")).should == - "(lambda (epp (block (render-s 'We all live in ') (render (- 3.14 2.14)) (render-s ' world'))))" + dump(parse("We all live in <%= 3.14 - 2.14 %> world")).should == [ + "(lambda (epp (block", + " (render-s 'We all live in ')", + " (render (- 3.14 2.14))", + " (render-s ' world')", + ")))" + ].join("\n") end end end diff --git a/spec/unit/pops/parser/parse_calls_spec.rb b/spec/unit/pops/parser/parse_calls_spec.rb index 115c160d6..ee80544f5 100644 --- a/spec/unit/pops/parser/parse_calls_spec.rb +++ b/spec/unit/pops/parser/parse_calls_spec.rb @@ -1,101 +1,104 @@ #! /usr/bin/env ruby require 'spec_helper' require 'puppet/pops' # relative to this spec file (./) does not work as this file is loaded by rspec require File.join(File.dirname(__FILE__), '/parser_rspec_helper') describe "egrammar parsing function calls" do include ParserRspecHelper context "When parsing calls as statements" do context "in top level scope" do it "foo()" do dump(parse("foo()")).should == "(invoke foo)" end it "notice bar" do dump(parse("notice bar")).should == "(invoke notice bar)" end it "notice(bar)" do dump(parse("notice bar")).should == "(invoke notice bar)" end it "foo(bar)" do dump(parse("foo(bar)")).should == "(invoke foo bar)" end it "foo(bar,)" do dump(parse("foo(bar,)")).should == "(invoke foo bar)" end it "foo(bar, fum,)" do dump(parse("foo(bar,fum,)")).should == "(invoke foo bar fum)" end it "notice fqdn_rand(30)" do dump(parse("notice fqdn_rand(30)")).should == '(invoke notice (call fqdn_rand 30))' end end context "in nested scopes" do it "if true { foo() }" do dump(parse("if true {foo()}")).should == "(if true\n (then (invoke foo)))" end it "if true { notice bar}" do dump(parse("if true {notice bar}")).should == "(if true\n (then (invoke notice bar)))" end end end context "When parsing calls as expressions" do it "$a = foo()" do dump(parse("$a = foo()")).should == "(= $a (call foo))" end it "$a = foo(bar)" do dump(parse("$a = foo()")).should == "(= $a (call foo))" end # # For regular grammar where a bare word can not be a "statement" # it "$a = foo bar # illegal, must have parentheses" do # expect { dump(parse("$a = foo bar"))}.to raise_error(Puppet::ParseError) # end # For egrammar where a bare word can be a "statement" it "$a = foo bar # illegal, must have parentheses" do - dump(parse("$a = foo bar")).should == "(block (= $a foo) bar)" + dump(parse("$a = foo bar")).should == "(block\n (= $a foo)\n bar\n)" end context "in nested scopes" do it "if true { $a = foo() }" do dump(parse("if true { $a = foo()}")).should == "(if true\n (then (= $a (call foo))))" end it "if true { $a= foo(bar)}" do dump(parse("if true {$a = foo(bar)}")).should == "(if true\n (then (= $a (call foo bar))))" end end end context "When parsing method calls" do it "$a.foo" do dump(parse("$a.foo")).should == "(call-method (. $a foo))" end it "$a.foo || { }" do dump(parse("$a.foo || { }")).should == "(call-method (. $a foo) (lambda ()))" end it "$a.foo |$x| { }" do dump(parse("$a.foo |$x|{ }")).should == "(call-method (. $a foo) (lambda (parameters x) ()))" end it "$a.foo |$x|{ }" do - dump(parse("$a.foo |$x|{ $b = $x}")).should == - "(call-method (. $a foo) (lambda (parameters x) (block (= $b $x))))" + dump(parse("$a.foo |$x|{ $b = $x}")).should == [ + "(call-method (. $a foo) (lambda (parameters x) (block", + " (= $b $x)", + ")))" + ].join("\n") end end end diff --git a/spec/unit/pops/parser/parse_conditionals_spec.rb b/spec/unit/pops/parser/parse_conditionals_spec.rb index b8b8d9c8e..591b20e97 100644 --- a/spec/unit/pops/parser/parse_conditionals_spec.rb +++ b/spec/unit/pops/parser/parse_conditionals_spec.rb @@ -1,150 +1,157 @@ #! /usr/bin/env ruby require 'spec_helper' require 'puppet/pops' # relative to this spec file (./) does not work as this file is loaded by rspec require File.join(File.dirname(__FILE__), '/parser_rspec_helper') describe "egrammar parsing conditionals" do include ParserRspecHelper context "When parsing if statements" do it "if true { $a = 10 }" do dump(parse("if true { $a = 10 }")).should == "(if true\n (then (= $a 10)))" end it "if true { $a = 10 } else {$a = 20}" do dump(parse("if true { $a = 10 } else {$a = 20}")).should == ["(if true", " (then (= $a 10))", " (else (= $a 20)))"].join("\n") end it "if true { $a = 10 } elsif false { $a = 15} else {$a = 20}" do dump(parse("if true { $a = 10 } elsif false { $a = 15} else {$a = 20}")).should == ["(if true", " (then (= $a 10))", " (else (if false", " (then (= $a 15))", " (else (= $a 20)))))"].join("\n") end it "if true { $a = 10 $b = 10 } else {$a = 20}" do - dump(parse("if true { $a = 10 $b = 20} else {$a = 20}")).should == - ["(if true", - " (then (block (= $a 10) (= $b 20)))", - " (else (= $a 20)))"].join("\n") + dump(parse("if true { $a = 10 $b = 20} else {$a = 20}")).should == [ + "(if true", + " (then (block", + " (= $a 10)", + " (= $b 20)", + " ))", + " (else (= $a 20)))" + ].join("\n") end it "allows a parenthesized conditional expression" do dump(parse("if (true) { 10 }")).should == "(if true\n (then 10))" end it "allows a parenthesized elsif conditional expression" do dump(parse("if true { 10 } elsif (false) { 20 }")).should == ["(if true", " (then 10)", " (else (if false", " (then 20))))"].join("\n") end end context "When parsing unless statements" do it "unless true { $a = 10 }" do dump(parse("unless true { $a = 10 }")).should == "(unless true\n (then (= $a 10)))" end it "unless true { $a = 10 } else {$a = 20}" do dump(parse("unless true { $a = 10 } else {$a = 20}")).should == ["(unless true", " (then (= $a 10))", " (else (= $a 20)))"].join("\n") end it "allows a parenthesized conditional expression" do dump(parse("unless (true) { 10 }")).should == "(unless true\n (then 10))" end it "unless true { $a = 10 } elsif false { $a = 15} else {$a = 20} # is illegal" do expect { parse("unless true { $a = 10 } elsif false { $a = 15} else {$a = 20}")}.to raise_error(Puppet::ParseError) end end context "When parsing selector expressions" do it "$a = $b ? banana => fruit " do dump(parse("$a = $b ? banana => fruit")).should == "(= $a (? $b (banana => fruit)))" end it "$a = $b ? { banana => fruit}" do dump(parse("$a = $b ? { banana => fruit }")).should == "(= $a (? $b (banana => fruit)))" end it "does not fail on a trailing blank line" do dump(parse("$a = $b ? { banana => fruit }\n\n")).should == "(= $a (? $b (banana => fruit)))" end it "$a = $b ? { banana => fruit, grape => berry }" do dump(parse("$a = $b ? {banana => fruit, grape => berry}")).should == "(= $a (? $b (banana => fruit) (grape => berry)))" end it "$a = $b ? { banana => fruit, grape => berry, default => wat }" do dump(parse("$a = $b ? {banana => fruit, grape => berry, default => wat}")).should == "(= $a (? $b (banana => fruit) (grape => berry) (:default => wat)))" end it "$a = $b ? { default => wat, banana => fruit, grape => berry, }" do dump(parse("$a = $b ? {default => wat, banana => fruit, grape => berry}")).should == "(= $a (? $b (:default => wat) (banana => fruit) (grape => berry)))" end end context "When parsing case statements" do it "case $a { a : {}}" do dump(parse("case $a { a : {}}")).should == ["(case $a", " (when (a) (then ())))" ].join("\n") end it "allows a parenthesized value expression" do dump(parse("case ($a) { a : {}}")).should == ["(case $a", " (when (a) (then ())))" ].join("\n") end it "case $a { /.*/ : {}}" do dump(parse("case $a { /.*/ : {}}")).should == ["(case $a", " (when (/.*/) (then ())))" ].join("\n") end it "case $a { a, b : {}}" do dump(parse("case $a { a, b : {}}")).should == ["(case $a", " (when (a b) (then ())))" ].join("\n") end it "case $a { a, b : {} default : {}}" do dump(parse("case $a { a, b : {} default : {}}")).should == ["(case $a", " (when (a b) (then ()))", " (when (:default) (then ())))" ].join("\n") end it "case $a { a : {$b = 10 $c = 20}}" do dump(parse("case $a { a : {$b = 10 $c = 20}}")).should == ["(case $a", - " (when (a) (then (block (= $b 10) (= $c 20)))))" + " (when (a) (then (block", + " (= $b 10)", + " (= $c 20)", + " ))))" ].join("\n") end end end diff --git a/spec/unit/pops/parser/parse_containers_spec.rb b/spec/unit/pops/parser/parse_containers_spec.rb index 8cbb50960..a05c5975d 100644 --- a/spec/unit/pops/parser/parse_containers_spec.rb +++ b/spec/unit/pops/parser/parse_containers_spec.rb @@ -1,229 +1,261 @@ #! /usr/bin/env ruby require 'spec_helper' require 'puppet/pops' # relative to this spec file (./) does not work as this file is loaded by rspec require File.join(File.dirname(__FILE__), '/parser_rspec_helper') describe "egrammar parsing containers" do include ParserRspecHelper context "When parsing file scope" do it "$a = 10 $b = 20" do - dump(parse("$a = 10 $b = 20")).should == "(block (= $a 10) (= $b 20))" + dump(parse("$a = 10 $b = 20")).should == [ + "(block", + " (= $a 10)", + " (= $b 20)", + ")" + ].join("\n") end it "$a = 10" do dump(parse("$a = 10")).should == "(= $a 10)" end end context "When parsing class" do it "class foo {}" do dump(parse("class foo {}")).should == "(class foo ())" end it "class foo { class bar {} }" do - dump(parse("class foo { class bar {}}")).should == "(class foo (block (class foo::bar ())))" + dump(parse("class foo { class bar {}}")).should == [ + "(class foo (block", + " (class foo::bar ())", + "))" + ].join("\n") end it "class foo::bar {}" do dump(parse("class foo::bar {}")).should == "(class foo::bar ())" end it "class foo inherits bar {}" do dump(parse("class foo inherits bar {}")).should == "(class foo (inherits bar) ())" end it "class foo($a) {}" do dump(parse("class foo($a) {}")).should == "(class foo (parameters a) ())" end it "class foo($a, $b) {}" do dump(parse("class foo($a, $b) {}")).should == "(class foo (parameters a b) ())" end it "class foo($a, $b=10) {}" do dump(parse("class foo($a, $b=10) {}")).should == "(class foo (parameters a (= b 10)) ())" end it "class foo($a, $b) inherits belgo::bar {}" do dump(parse("class foo($a, $b) inherits belgo::bar{}")).should == "(class foo (inherits belgo::bar) (parameters a b) ())" end it "class foo {$a = 10 $b = 20}" do - dump(parse("class foo {$a = 10 $b = 20}")).should == "(class foo (block (= $a 10) (= $b 20)))" + dump(parse("class foo {$a = 10 $b = 20}")).should == [ + "(class foo (block", + " (= $a 10)", + " (= $b 20)", + "))" + ].join("\n") end context "it should handle '3x weirdness'" do it "class class {} # a class named 'class'" do # Not as much weird as confusing that it is possible to name a class 'class'. Can have # a very confusing effect when resolving relative names, getting the global hardwired "Class" # instead of some foo::class etc. # This is allowed in 3.x. expect { dump(parse("class class {}")).should == "(class class ())" }.to raise_error(/not a valid classname/) end it "class default {} # a class named 'default'" do # The weirdness here is that a class can inherit 'default' but not declare a class called default. # (It will work with relative names i.e. foo::default though). The whole idea with keywords as # names is flawed to begin with - it generally just a very bad idea. expect { dump(parse("class default {}")).should == "(class default ())" }.to raise_error(Puppet::ParseError) end it "class foo::default {} # a nested name 'default'" do dump(parse("class foo::default {}")).should == "(class foo::default ())" end it "class class inherits default {} # inherits default", :broken => true do expect { dump(parse("class class inherits default {}")).should == "(class class (inherits default) ())" }.to raise_error(/not a valid classname/) end it "class class inherits default {} # inherits default" do # TODO: See previous test marked as :broken=>true, it is actually this test (result) that is wacky, # this because a class is named at parse time (since class evaluation is lazy, the model must have the # full class name for nested classes - only, it gets this wrong when a class is named "class" - or at least # I think it is wrong.) # expect { dump(parse("class class inherits default {}")).should == "(class class::class (inherits default) ())" }.to raise_error(/not a valid classname/) end it "class foo inherits class" do expect { dump(parse("class foo inherits class {}")).should == "(class foo (inherits class) ())" }.to raise_error(/not a valid classname/) end end context 'it should allow keywords as attribute names' do ['and', 'case', 'class', 'default', 'define', 'else', 'elsif', 'if', 'in', 'inherits', 'node', 'or', 'undef', 'unless', 'type', 'attr', 'function', 'private'].each do |keyword| it "such as #{keyword}" do expect {parse("class x ($#{keyword}){} class { x: #{keyword} => 1 }")}.to_not raise_error end end end end context "When the parser parses define" do it "define foo {}" do dump(parse("define foo {}")).should == "(define foo ())" end it "class foo { define bar {}}" do - dump(parse("class foo {define bar {}}")).should == "(class foo (block (define foo::bar ())))" + dump(parse("class foo {define bar {}}")).should == [ + "(class foo (block", + " (define foo::bar ())", + "))" + ].join("\n") end it "define foo { define bar {}}" do # This is illegal, but handled as part of validation - dump(parse("define foo { define bar {}}")).should == "(define foo (block (define bar ())))" + dump(parse("define foo { define bar {}}")).should == [ + "(define foo (block", + " (define bar ())", + "))" + ].join("\n") end it "define foo::bar {}" do dump(parse("define foo::bar {}")).should == "(define foo::bar ())" end it "define foo($a) {}" do dump(parse("define foo($a) {}")).should == "(define foo (parameters a) ())" end it "define foo($a, $b) {}" do dump(parse("define foo($a, $b) {}")).should == "(define foo (parameters a b) ())" end it "define foo($a, $b=10) {}" do dump(parse("define foo($a, $b=10) {}")).should == "(define foo (parameters a (= b 10)) ())" end it "define foo {$a = 10 $b = 20}" do - dump(parse("define foo {$a = 10 $b = 20}")).should == "(define foo (block (= $a 10) (= $b 20)))" + dump(parse("define foo {$a = 10 $b = 20}")).should == [ + "(define foo (block", + " (= $a 10)", + " (= $b 20)", + "))" + ].join("\n") end context "it should handle '3x weirdness'" do it "define class {} # a define named 'class'" do # This is weird because Class already exists, and instantiating this define will probably not # work expect { dump(parse("define class {}")).should == "(define class ())" }.to raise_error(/not a valid classname/) end it "define default {} # a define named 'default'" do # Check unwanted ability to define 'default'. # The expression below is not allowed (which is good). # expect { dump(parse("define default {}")).should == "(define default ())"}.to raise_error(Puppet::ParseError) end end context 'it should allow keywords as attribute names' do ['and', 'case', 'class', 'default', 'define', 'else', 'elsif', 'if', 'in', 'inherits', 'node', 'or', 'undef', 'unless', 'type', 'attr', 'function', 'private'].each do |keyword| it "such as #{keyword}" do expect {parse("define x ($#{keyword}){} x { y: #{keyword} => 1 }")}.to_not raise_error end end end end context "When parsing node" do it "node foo {}" do dump(parse("node foo {}")).should == "(node (matches 'foo') ())" end it "node foo, {} # trailing comma" do dump(parse("node foo, {}")).should == "(node (matches 'foo') ())" end it "node kermit.example.com {}" do dump(parse("node kermit.example.com {}")).should == "(node (matches 'kermit.example.com') ())" end it "node kermit . example . com {}" do dump(parse("node kermit . example . com {}")).should == "(node (matches 'kermit.example.com') ())" end it "node foo, x::bar, default {}" do dump(parse("node foo, x::bar, default {}")).should == "(node (matches 'foo' 'x::bar' :default) ())" end it "node 'foo' {}" do dump(parse("node 'foo' {}")).should == "(node (matches 'foo') ())" end it "node foo inherits x::bar {}" do dump(parse("node foo inherits x::bar {}")).should == "(node (matches 'foo') (parent 'x::bar') ())" end it "node foo inherits 'bar' {}" do dump(parse("node foo inherits 'bar' {}")).should == "(node (matches 'foo') (parent 'bar') ())" end it "node foo inherits default {}" do dump(parse("node foo inherits default {}")).should == "(node (matches 'foo') (parent :default) ())" end it "node /web.*/ {}" do dump(parse("node /web.*/ {}")).should == "(node (matches /web.*/) ())" end it "node /web.*/, /do\.wop.*/, and.so.on {}" do dump(parse("node /web.*/, /do\.wop.*/, 'and.so.on' {}")).should == "(node (matches /web.*/ /do\.wop.*/ 'and.so.on') ())" end it "node wat inherits /apache.*/ {}" do dump(parse("node wat inherits /apache.*/ {}")).should == "(node (matches 'wat') (parent /apache.*/) ())" end it "node foo inherits bar {$a = 10 $b = 20}" do - dump(parse("node foo inherits bar {$a = 10 $b = 20}")).should == "(node (matches 'foo') (parent 'bar') (block (= $a 10) (= $b 20)))" + dump(parse("node foo inherits bar {$a = 10 $b = 20}")).should == [ + "(node (matches 'foo') (parent 'bar') (block", + " (= $a 10)", + " (= $b 20)", + "))" + ].join("\n") end end end diff --git a/spec/unit/pops/parser/parse_resource_spec.rb b/spec/unit/pops/parser/parse_resource_spec.rb index ee7e13445..8e805b27a 100644 --- a/spec/unit/pops/parser/parse_resource_spec.rb +++ b/spec/unit/pops/parser/parse_resource_spec.rb @@ -1,242 +1,324 @@ #! /usr/bin/env ruby require 'spec_helper' require 'puppet/pops' # relative to this spec file (./) does not work as this file is loaded by rspec require File.join(File.dirname(__FILE__), '/parser_rspec_helper') describe "egrammar parsing resource declarations" do include ParserRspecHelper context "When parsing regular resource" do - it "file { 'title': }" do - dump(parse("file { 'title': }")).should == [ - "(resource file", - " ('title'))" - ].join("\n") - end + ["File", "file"].each do |word| + it "#{word} { 'title': }" do + dump(parse("#{word} { 'title': }")).should == [ + "(resource file", + " ('title'))" + ].join("\n") + end - it "file { 'title': path => '/somewhere', mode => 0777}" do - dump(parse("file { 'title': path => '/somewhere', mode => 0777}")).should == [ - "(resource file", - " ('title'", - " (path => '/somewhere')", - " (mode => 0777)))" - ].join("\n") - end + it "#{word} { 'title': path => '/somewhere', mode => 0777}" do + dump(parse("#{word} { 'title': path => '/somewhere', mode => 0777}")).should == [ + "(resource file", + " ('title'", + " (path => '/somewhere')", + " (mode => 0777)))" + ].join("\n") + end - it "file { 'title': path => '/somewhere', }" do - dump(parse("file { 'title': path => '/somewhere', }")).should == [ - "(resource file", - " ('title'", - " (path => '/somewhere')))" - ].join("\n") - end + it "#{word} { 'title': path => '/somewhere', }" do + dump(parse("#{word} { 'title': path => '/somewhere', }")).should == [ + "(resource file", + " ('title'", + " (path => '/somewhere')))" + ].join("\n") + end - it "file { 'title': , }" do - dump(parse("file { 'title': , }")).should == [ - "(resource file", - " ('title'))" - ].join("\n") - end + it "#{word} { 'title': , }" do + dump(parse("#{word} { 'title': , }")).should == [ + "(resource file", + " ('title'))" + ].join("\n") + end - it "file { 'title': ; }" do - dump(parse("file { 'title': ; }")).should == [ - "(resource file", - " ('title'))" - ].join("\n") - end + it "#{word} { 'title': ; }" do + dump(parse("#{word} { 'title': ; }")).should == [ + "(resource file", + " ('title'))" + ].join("\n") + end - it "file { 'title': ; 'other_title': }" do - dump(parse("file { 'title': ; 'other_title': }")).should == [ - "(resource file", - " ('title')", - " ('other_title'))" - ].join("\n") - end + it "#{word} { 'title': ; 'other_title': }" do + dump(parse("#{word} { 'title': ; 'other_title': }")).should == [ + "(resource file", + " ('title')", + " ('other_title'))" + ].join("\n") + end - it "file { 'title1': path => 'x'; 'title2': path => 'y'}" do - dump(parse("file { 'title1': path => 'x'; 'title2': path => 'y'}")).should == [ - "(resource file", - " ('title1'", - " (path => 'x'))", - " ('title2'", - " (path => 'y')))", - ].join("\n") + # PUP-2898, trailing ';' + it "#{word} { 'title': ; 'other_title': ; }" do + dump(parse("#{word} { 'title': ; 'other_title': ; }")).should == [ + "(resource file", + " ('title')", + " ('other_title'))" + ].join("\n") + end + + it "#{word} { 'title1': path => 'x'; 'title2': path => 'y'}" do + dump(parse("#{word} { 'title1': path => 'x'; 'title2': path => 'y'}")).should == [ + "(resource file", + " ('title1'", + " (path => 'x'))", + " ('title2'", + " (path => 'y')))", + ].join("\n") + end + + it "#{word} { title: * => {mode => 0777} }" do + dump(parse("#{word} { title: * => {mode => 0777}}")).should == [ + "(resource file", + " (title", + " (* => ({} (mode 0777)))))" + ].join("\n") + end end end - context "When parsing resource defaults" do + context "When parsing (type based) resource defaults" do it "File { }" do dump(parse("File { }")).should == "(resource-defaults file)" end it "File { mode => 0777 }" do dump(parse("File { mode => 0777}")).should == [ "(resource-defaults file", " (mode => 0777))" ].join("\n") end + + it "File { * => {mode => 0777} } (even if validated to be illegal)" do + dump(parse("File { * => {mode => 0777}}")).should == [ + "(resource-defaults file", + " (* => ({} (mode 0777))))" + ].join("\n") + end end context "When parsing resource override" do it "File['x'] { }" do dump(parse("File['x'] { }")).should == "(override (slice file 'x'))" end it "File['x'] { x => 1 }" do - dump(parse("File['x'] { x => 1}")).should == "(override (slice file 'x')\n (x => 1))" + dump(parse("File['x'] { x => 1}")).should == [ + "(override (slice file 'x')", + " (x => 1))" + ].join("\n") end + it "File['x', 'y'] { x => 1 }" do - dump(parse("File['x', 'y'] { x => 1}")).should == "(override (slice file ('x' 'y'))\n (x => 1))" + dump(parse("File['x', 'y'] { x => 1}")).should == [ + "(override (slice file ('x' 'y'))", + " (x => 1))" + ].join("\n") end it "File['x'] { x => 1, y => 2 }" do - dump(parse("File['x'] { x => 1, y=> 2}")).should == "(override (slice file 'x')\n (x => 1)\n (y => 2))" + dump(parse("File['x'] { x => 1, y=> 2}")).should == [ + "(override (slice file 'x')", + " (x => 1)", + " (y => 2))" + ].join("\n") end it "File['x'] { x +> 1 }" do - dump(parse("File['x'] { x +> 1}")).should == "(override (slice file 'x')\n (x +> 1))" + dump(parse("File['x'] { x +> 1}")).should == [ + "(override (slice file 'x')", + " (x +> 1))" + ].join("\n") + end + + it "File['x'] { * => {mode => 0777} } (even if validated to be illegal)" do + dump(parse("File['x'] { * => {mode => 0777}}")).should == [ + "(override (slice file 'x')", + " (* => ({} (mode 0777))))" + ].join("\n") end end context "When parsing virtual and exported resources" do - it "@@file { 'title': }" do + it "parses exported @@file { 'title': }" do dump(parse("@@file { 'title': }")).should == "(exported-resource file\n ('title'))" end - it "@file { 'title': }" do + it "parses virtual @file { 'title': }" do dump(parse("@file { 'title': }")).should == "(virtual-resource file\n ('title'))" end - it "@file { mode => 0777 }" do - # Defaults are not virtualizeable - expect { - dump(parse("@file { mode => 0777 }")).should == "" - }.to raise_error(Puppet::ParseError, /Defaults are not virtualizable/) + it "nothing before the title colon is a syntax error" do + expect do + parse("@file {: mode => 0777 }") + end.to raise_error(/Syntax error/) + end + + it "raises error for user error; not a resource" do + # The expression results in VIRTUAL, CALL FUNCTION('file', HASH) since the resource body has + # no title. + expect do + parse("@file { mode => 0777 }") + end.to raise_error(/Virtual \(@\) can only be applied to a Resource Expression/) + end + + it "parses global defaults with @ (even if validated to be illegal)" do + dump(parse("@File { mode => 0777 }")).should == [ + "(virtual-resource-defaults file", + " (mode => 0777))" + ].join("\n") + end + + it "parses global defaults with @@ (even if validated to be illegal)" do + dump(parse("@@File { mode => 0777 }")).should == [ + "(exported-resource-defaults file", + " (mode => 0777))" + ].join("\n") + end + + it "parses override with @ (even if validated to be illegal)" do + dump(parse("@File[foo] { mode => 0777 }")).should == [ + "(virtual-override (slice file foo)", + " (mode => 0777))" + ].join("\n") + end + + it "parses override combined with @@ (even if validated to be illegal)" do + dump(parse("@@File[foo] { mode => 0777 }")).should == [ + "(exported-override (slice file foo)", + " (mode => 0777))" + ].join("\n") end end context "When parsing class resource" do it "class { 'cname': }" do dump(parse("class { 'cname': }")).should == [ "(resource class", " ('cname'))" ].join("\n") end it "@class { 'cname': }" do dump(parse("@class { 'cname': }")).should == [ "(virtual-resource class", " ('cname'))" ].join("\n") end it "@@class { 'cname': }" do dump(parse("@@class { 'cname': }")).should == [ "(exported-resource class", " ('cname'))" ].join("\n") end it "class { 'cname': x => 1, y => 2}" do dump(parse("class { 'cname': x => 1, y => 2}")).should == [ "(resource class", " ('cname'", " (x => 1)", " (y => 2)))" ].join("\n") end it "class { 'cname1': x => 1; 'cname2': y => 2}" do dump(parse("class { 'cname1': x => 1; 'cname2': y => 2}")).should == [ "(resource class", " ('cname1'", " (x => 1))", " ('cname2'", " (y => 2)))", ].join("\n") end end context "reported issues in 3.x" do it "should not screw up on brackets in title of resource #19632" do dump(parse('notify { "thisisa[bug]": }')).should == [ "(resource notify", " ('thisisa[bug]'))", ].join("\n") end end context "When parsing Relationships" do it "File[a] -> File[b]" do dump(parse("File[a] -> File[b]")).should == "(-> (slice file a) (slice file b))" end it "File[a] <- File[b]" do dump(parse("File[a] <- File[b]")).should == "(<- (slice file a) (slice file b))" end it "File[a] ~> File[b]" do dump(parse("File[a] ~> File[b]")).should == "(~> (slice file a) (slice file b))" end it "File[a] <~ File[b]" do dump(parse("File[a] <~ File[b]")).should == "(<~ (slice file a) (slice file b))" end it "Should chain relationships" do dump(parse("a -> b -> c")).should == "(-> (-> a b) c)" end it "Should chain relationships" do dump(parse("File[a] -> File[b] ~> File[c] <- File[d] <~ File[e]")).should == "(<~ (<- (~> (-> (slice file a) (slice file b)) (slice file c)) (slice file d)) (slice file e))" end it "should create relationships between collects" do dump(parse("File <| mode == 0644 |> -> File <| mode == 0755 |>")).should == "(-> (collect file\n (<| |> (== mode 0644))) (collect file\n (<| |> (== mode 0755))))" end end context "When parsing collection" do context "of virtual resources" do it "File <| |>" do dump(parse("File <| |>")).should == "(collect file\n (<| |>))" end end context "of exported resources" do it "File <<| |>>" do dump(parse("File <<| |>>")).should == "(collect file\n (<<| |>>))" end end context "queries are parsed with correct precedence" do it "File <| tag == 'foo' |>" do dump(parse("File <| tag == 'foo' |>")).should == "(collect file\n (<| |> (== tag 'foo')))" end it "File <| tag == 'foo' and mode != 0777 |>" do dump(parse("File <| tag == 'foo' and mode != 0777 |>")).should == "(collect file\n (<| |> (&& (== tag 'foo') (!= mode 0777))))" end it "File <| tag == 'foo' or mode != 0777 |>" do dump(parse("File <| tag == 'foo' or mode != 0777 |>")).should == "(collect file\n (<| |> (|| (== tag 'foo') (!= mode 0777))))" end it "File <| tag == 'foo' or tag == 'bar' and mode != 0777 |>" do dump(parse("File <| tag == 'foo' or tag == 'bar' and mode != 0777 |>")).should == "(collect file\n (<| |> (|| (== tag 'foo') (&& (== tag 'bar') (!= mode 0777)))))" end it "File <| (tag == 'foo' or tag == 'bar') and mode != 0777 |>" do dump(parse("File <| (tag == 'foo' or tag == 'bar') and mode != 0777 |>")).should == "(collect file\n (<| |> (&& (|| (== tag 'foo') (== tag 'bar')) (!= mode 0777))))" end end end end diff --git a/spec/unit/pops/transformer/transform_resource_spec.rb b/spec/unit/pops/transformer/transform_resource_spec.rb deleted file mode 100644 index 251ef2f75..000000000 --- a/spec/unit/pops/transformer/transform_resource_spec.rb +++ /dev/null @@ -1,185 +0,0 @@ -#! /usr/bin/env ruby -require 'spec_helper' -require 'puppet/pops' - -# relative to this spec file (./) does not work as this file is loaded by rspec -require File.join(File.dirname(__FILE__), '/transformer_rspec_helper') - -describe "transformation to Puppet AST for resource declarations" do - include TransformerRspecHelper - - context "When transforming regular resource" do - it "file { 'title': }" do - astdump(parse("file { 'title': }")).should == [ - "(resource file", - " ('title'))" - ].join("\n") - end - - it "file { 'title': ; 'other_title': }" do - astdump(parse("file { 'title': ; 'other_title': }")).should == [ - "(resource file", - " ('title')", - " ('other_title'))" - ].join("\n") - end - - it "file { 'title': path => '/somewhere', mode => 0777}" do - astdump(parse("file { 'title': path => '/somewhere', mode => 0777}")).should == [ - "(resource file", - " ('title'", - " (path => '/somewhere')", - " (mode => 0777)))" - ].join("\n") - end - - it "file { 'title1': path => 'x'; 'title2': path => 'y'}" do - astdump(parse("file { 'title1': path => 'x'; 'title2': path => 'y'}")).should == [ - "(resource file", - " ('title1'", - " (path => 'x'))", - " ('title2'", - " (path => 'y')))", - ].join("\n") - end - end - - context "When transforming resource defaults" do - it "File { }" do - astdump(parse("File { }")).should == "(resource-defaults file)" - end - - it "File { mode => 0777 }" do - astdump(parse("File { mode => 0777}")).should == [ - "(resource-defaults file", - " (mode => 0777))" - ].join("\n") - end - end - - context "When transforming resource override" do - it "File['x'] { }" do - astdump(parse("File['x'] { }")).should == "(override (slice file 'x'))" - end - - it "File['x'] { x => 1 }" do - astdump(parse("File['x'] { x => 1}")).should == "(override (slice file 'x')\n (x => 1))" - end - - it "File['x', 'y'] { x => 1 }" do - astdump(parse("File['x', 'y'] { x => 1}")).should == "(override (slice file ('x' 'y'))\n (x => 1))" - end - - it "File['x'] { x => 1, y => 2 }" do - astdump(parse("File['x'] { x => 1, y=> 2}")).should == "(override (slice file 'x')\n (x => 1)\n (y => 2))" - end - - it "File['x'] { x +> 1 }" do - astdump(parse("File['x'] { x +> 1}")).should == "(override (slice file 'x')\n (x +> 1))" - end - end - - context "When transforming virtual and exported resources" do - it "@@file { 'title': }" do - astdump(parse("@@file { 'title': }")).should == "(exported-resource file\n ('title'))" - end - - it "@file { 'title': }" do - astdump(parse("@file { 'title': }")).should == "(virtual-resource file\n ('title'))" - end - end - - context "When transforming class resource" do - it "class { 'cname': }" do - astdump(parse("class { 'cname': }")).should == [ - "(resource class", - " ('cname'))" - ].join("\n") - end - - it "class { 'cname': x => 1, y => 2}" do - astdump(parse("class { 'cname': x => 1, y => 2}")).should == [ - "(resource class", - " ('cname'", - " (x => 1)", - " (y => 2)))" - ].join("\n") - end - - it "class { 'cname1': x => 1; 'cname2': y => 2}" do - astdump(parse("class { 'cname1': x => 1; 'cname2': y => 2}")).should == [ - "(resource class", - " ('cname1'", - " (x => 1))", - " ('cname2'", - " (y => 2)))", - ].join("\n") - end - end - - context "When transforming Relationships" do - it "File[a] -> File[b]" do - astdump(parse("File[a] -> File[b]")).should == "(-> (slice file a) (slice file b))" - end - - it "File[a] <- File[b]" do - astdump(parse("File[a] <- File[b]")).should == "(<- (slice file a) (slice file b))" - end - - it "File[a] ~> File[b]" do - astdump(parse("File[a] ~> File[b]")).should == "(~> (slice file a) (slice file b))" - end - - it "File[a] <~ File[b]" do - astdump(parse("File[a] <~ File[b]")).should == "(<~ (slice file a) (slice file b))" - end - - it "Should chain relationships" do - astdump(parse("a -> b -> c")).should == - "(-> (-> a b) c)" - end - - it "Should chain relationships" do - astdump(parse("File[a] -> File[b] ~> File[c] <- File[d] <~ File[e]")).should == - "(<~ (<- (~> (-> (slice file a) (slice file b)) (slice file c)) (slice file d)) (slice file e))" - end - end - - context "When transforming collection" do - context "of virtual resources" do - it "File <| |>" do - astdump(parse("File <| |>")).should == "(collect file\n (<| |>))" - end - end - - context "of exported resources" do - it "File <<| |>>" do - astdump(parse("File <<| |>>")).should == "(collect file\n (<<| |>>))" - end - end - - context "queries are parsed with correct precedence" do - it "File <| tag == 'foo' |>" do - astdump(parse("File <| tag == 'foo' |>")).should == "(collect file\n (<| |> (== tag 'foo')))" - end - - it "File <| tag == 'foo' and mode != 0777 |>" do - astdump(parse("File <| tag == 'foo' and mode != 0777 |>")).should == "(collect file\n (<| |> (&& (== tag 'foo') (!= mode 0777))))" - end - - it "File <| tag == 'foo' or mode != 0777 |>" do - astdump(parse("File <| tag == 'foo' or mode != 0777 |>")).should == "(collect file\n (<| |> (|| (== tag 'foo') (!= mode 0777))))" - end - - it "File <| tag == 'foo' or tag == 'bar' and mode != 0777 |>" do - astdump(parse("File <| tag == 'foo' or tag == 'bar' and mode != 0777 |>")).should == - "(collect file\n (<| |> (|| (== tag 'foo') (&& (== tag 'bar') (!= mode 0777)))))" - end - - it "File <| (tag == 'foo' or tag == 'bar') and mode != 0777 |>" do - astdump(parse("File <| (tag == 'foo' or tag == 'bar') and mode != 0777 |>")).should == - "(collect file\n (<| |> (&& (|| (== tag 'foo') (== tag 'bar')) (!= mode 0777))))" - end - end - end -end