diff --git a/lib/puppet/defaults.rb b/lib/puppet/defaults.rb index b2b83493e..cdb3bfd8b 100644 --- a/lib/puppet/defaults.rb +++ b/lib/puppet/defaults.rb @@ -1,1894 +1,1911 @@ module Puppet def self.default_diffargs if (Facter.value(:kernel) == "AIX" && Facter.value(:kernelmajversion) == "5300") "" else "-u" end end ############################################################################################ # NOTE: For information about the available values for the ":type" property of settings, # see the docs for Settings.define_settings ############################################################################################ AS_DURATION = %q{This setting can be a time interval in seconds (30 or 30s), minutes (30m), hours (6h), days (2d), or years (5y).} STORECONFIGS_ONLY = %q{This setting is only used by the ActiveRecord storeconfigs and inventory backends, which are deprecated.} define_settings(:main, :confdir => { :default => nil, :type => :directory, :desc => "The main Puppet configuration directory. The default for this setting is calculated based on the user. If the process is running as root or the user that Puppet is supposed to run as, it defaults to a system directory, but if it's running as any other user, it defaults to being in the user's home directory.", }, :vardir => { :default => nil, :type => :directory, :owner => "service", :group => "service", :desc => "Where Puppet stores dynamic and growing data. The default for this setting is calculated specially, like `confdir`_.", }, ### NOTE: this setting is usually being set to a symbol value. We don't officially have a ### setting type for that yet, but we might want to consider creating one. :name => { :default => nil, :desc => "The name of the application, if we are running as one. The default is essentially $0 without the path or `.rb`.", } ) define_settings(:main, :logdir => { :default => nil, :type => :directory, :mode => 0750, :owner => "service", :group => "service", :desc => "The directory in which to store log files", }, :log_level => { :default => 'notice', :type => :enum, :values => ["debug","info","notice","warning","err","alert","emerg","crit"], :desc => "Default logging level", } ) define_settings(:main, :priority => { :default => nil, :type => :priority, :desc => "The scheduling priority of the process. Valid values are 'high', 'normal', 'low', or 'idle', which are mapped to platform-specific values. The priority can also be specified as an integer value and will be passed as is, e.g. -5. Puppet must be running as a privileged user in order to increase scheduling priority.", }, :trace => { :default => false, :type => :boolean, :desc => "Whether to print stack traces on some errors", }, :profile => { :default => false, :type => :boolean, :desc => "Whether to enable experimental performance profiling", }, :autoflush => { :default => true, :type => :boolean, :desc => "Whether log files should always flush to disk.", :hook => proc { |value| Log.autoflush = value } }, :syslogfacility => { :default => "daemon", :desc => "What syslog facility to use when logging to syslog. Syslog has a fixed list of valid facilities, and you must choose one of those; you cannot just make one up." }, :statedir => { :default => "$vardir/state", :type => :directory, :mode => 01755, :desc => "The directory where Puppet state is stored. Generally, this directory can be removed without causing harm (although it might result in spurious service restarts)." }, :rundir => { :default => nil, :type => :directory, :mode => 0755, :owner => "service", :group => "service", :desc => "Where Puppet PID files are kept." }, :genconfig => { :default => false, :type => :boolean, :desc => "When true, causes Puppet applications to print an example config file to stdout and exit. The example will include descriptions of each setting, and the current (or default) value of each setting, incorporating any settings overridden on the CLI (with the exception of `genconfig` itself). This setting only makes sense when specified on the command line as `--genconfig`.", }, :genmanifest => { :default => false, :type => :boolean, :desc => "Whether to just print a manifest to stdout and exit. Only makes sense when specified on the command line as `--genmanifest`. Takes into account arguments specified on the CLI.", }, :configprint => { :default => "", :desc => "Print the value of a specific configuration setting. If the name of a setting is provided for this, then the value is printed and puppet exits. Comma-separate multiple values. For a list of all values, specify 'all'.", }, :color => { :default => "ansi", :type => :string, :desc => "Whether to use colors when logging to the console. Valid values are `ansi` (equivalent to `true`), `html`, and `false`, which produces no color. Defaults to false on Windows, as its console does not support ansi colors.", }, :mkusers => { :default => false, :type => :boolean, :desc => "Whether to create the necessary user and group that puppet agent will run as.", }, :manage_internal_file_permissions => { :default => true, :type => :boolean, :desc => "Whether Puppet should manage the owner, group, and mode of files it uses internally", }, :onetime => { :default => false, :type => :boolean, :desc => "Perform one configuration run and exit, rather than spawning a long-running daemon. This is useful for interactively running puppet agent, or running puppet agent from cron.", :short => 'o', }, :path => { :default => "none", :desc => "The shell search path. Defaults to whatever is inherited from the parent process.", :call_hook => :on_define_and_write, :hook => proc do |value| ENV["PATH"] = "" if ENV["PATH"].nil? ENV["PATH"] = value unless value == "none" paths = ENV["PATH"].split(File::PATH_SEPARATOR) Puppet::Util::Platform.default_paths.each do |path| ENV["PATH"] += File::PATH_SEPARATOR + path unless paths.include?(path) end value end }, :libdir => { :type => :directory, :default => "$vardir/lib", :desc => "An extra search path for Puppet. This is only useful for those files that Puppet will load on demand, and is only guaranteed to work for those cases. In fact, the autoload mechanism is responsible for making sure this directory is in Ruby's search path\n", :call_hook => :on_initialize_and_write, :hook => proc do |value| $LOAD_PATH.delete(@oldlibdir) if defined?(@oldlibdir) and $LOAD_PATH.include?(@oldlibdir) @oldlibdir = value $LOAD_PATH << value end }, :ignoreimport => { :default => false, :type => :boolean, :desc => "If true, allows the parser to continue without requiring all files referenced with `import` statements to exist. This setting was primarily designed for use with commit hooks for parse-checking.", }, :environment => { :default => "production", :desc => "The environment Puppet is running in. For clients (e.g., `puppet agent`) this determines the environment itself, which is used to find modules and much more. For servers (i.e., `puppet master`) this provides the default environment for nodes we know nothing about." }, :environmentpath => { :default => "$confdir/environments", :desc => "A path of environment directories", :type => :path, }, :diff_args => { :default => default_diffargs, :desc => "Which arguments to pass to the diff command when printing differences between files. The command to use can be chosen with the `diff` setting.", }, :diff => { :default => (Puppet.features.microsoft_windows? ? "" : "diff"), :desc => "Which diff command to use when printing differences between files. This setting has no default value on Windows, as standard `diff` is not available, but Puppet can use many third-party diff tools.", }, :show_diff => { :type => :boolean, :default => false, :desc => "Whether to log and report a contextual diff when files are being replaced. This causes partial file contents to pass through Puppet's normal logging and reporting system, so this setting should be used with caution if you are sending Puppet's reports to an insecure destination. This feature currently requires the `diff/lcs` Ruby library.", }, :daemonize => { :type => :boolean, :default => (Puppet.features.microsoft_windows? ? false : true), :desc => "Whether to send the process into the background. This defaults to true on POSIX systems, and to false on Windows (where Puppet currently cannot daemonize).", :short => "D", :hook => proc do |value| if value and Puppet.features.microsoft_windows? raise "Cannot daemonize on Windows" end end }, :maximum_uid => { :default => 4294967290, :desc => "The maximum allowed UID. Some platforms use negative UIDs but then ship with tools that do not know how to handle signed ints, so the UIDs show up as huge numbers that can then not be fed back into the system. This is a hackish way to fail in a slightly more useful way when that happens.", }, :route_file => { :default => "$confdir/routes.yaml", :desc => "The YAML file containing indirector route configuration.", }, :node_terminus => { :type => :terminus, :default => "plain", :desc => "Where to find information about nodes.", }, :node_cache_terminus => { :type => :terminus, :default => nil, :desc => "How to store cached nodes. Valid values are (none), 'json', 'msgpack', 'yaml' or write only yaml ('write_only_yaml'). The master application defaults to 'write_only_yaml', all others to none.", }, :data_binding_terminus => { :type => :terminus, :default => "hiera", :desc => "Where to retrive information about data.", }, :hiera_config => { :default => "$confdir/hiera.yaml", :desc => "The hiera configuration file. Puppet only reads this file on startup, so you must restart the puppet master every time you edit it.", :type => :file, }, :binder => { :default => false, :desc => "Turns the binding system on or off. This includes bindings in modules. The binding system aggregates data from modules and other locations and makes them available for lookup. The binding system is experimental and any or all of it may change.", :type => :boolean, }, :binder_config => { :default => nil, :desc => "The binder configuration file. Puppet reads this file on each request to configure the bindings system. If set to nil (the default), a $confdir/binder_config.yaml is optionally loaded. If it does not exists, a default configuration is used. If the setting :binding_config is specified, it must reference a valid and existing yaml file.", :type => :file, }, :catalog_terminus => { :type => :terminus, :default => "compiler", :desc => "Where to get node catalogs. This is useful to change if, for instance, you'd like to pre-compile catalogs and store them in memcached or some other easily-accessed store.", }, :catalog_cache_terminus => { :type => :terminus, :default => nil, :desc => "How to store cached catalogs. Valid values are 'json', 'msgpack' and 'yaml'. The agent application defaults to 'json'." }, :facts_terminus => { :default => 'facter', :desc => "The node facts terminus.", :call_hook => :on_initialize_and_write, :hook => proc do |value| require 'puppet/node/facts' # Cache to YAML if we're uploading facts away if %w[rest inventory_service].include? value.to_s Puppet.info "configuring the YAML fact cache because a remote terminus is active" Puppet::Node::Facts.indirection.cache_class = :yaml end end }, :inventory_terminus => { :type => :terminus, :default => "$facts_terminus", :desc => "Should usually be the same as the facts terminus", }, :default_file_terminus => { :type => :terminus, :default => "rest", :desc => "The default source for files if no server is given in a uri, e.g. puppet:///file. The default of `rest` causes the file to be retrieved using the `server` setting. When running `apply` the default is `file_server`, causing requests to be filled locally." }, :httplog => { :default => "$logdir/http.log", :type => :file, :owner => "root", :mode => 0640, :desc => "Where the puppet agent web server logs.", }, :http_proxy_host => { :default => "none", :desc => "The HTTP proxy host to use for outgoing connections. Note: You may need to use a FQDN for the server hostname when using a proxy.", }, :http_proxy_port => { :default => 3128, :desc => "The HTTP proxy port to use for outgoing connections", }, :filetimeout => { :default => "15s", :type => :duration, :desc => "The minimum time to wait between checking for updates in configuration files. This timeout determines how quickly Puppet checks whether a file (such as manifests or templates) has changed on disk. #{AS_DURATION}", }, :queue_type => { :default => "stomp", :desc => "Which type of queue to use for asynchronous processing.", }, :queue_type => { :default => "stomp", :desc => "Which type of queue to use for asynchronous processing.", }, :queue_source => { :default => "stomp://localhost:61613/", :desc => "Which type of queue to use for asynchronous processing. If your stomp server requires authentication, you can include it in the URI as long as your stomp client library is at least 1.1.1", }, :async_storeconfigs => { :default => false, :type => :boolean, :desc => "Whether to use a queueing system to provide asynchronous database integration. Requires that `puppet queue` be running.", :hook => proc do |value| if value # This reconfigures the termini for Node, Facts, and Catalog Puppet.settings.override_default(:storeconfigs, true) # But then we modify the configuration Puppet::Resource::Catalog.indirection.cache_class = :queue Puppet.settings.override_default(:catalog_cache_terminus, :queue) else raise "Cannot disable asynchronous storeconfigs in a running process" end end }, :thin_storeconfigs => { :default => false, :type => :boolean, :desc => "Boolean; whether Puppet should store only facts and exported resources in the storeconfigs database. This will improve the performance of exported resources with the older `active_record` backend, but will disable external tools that search the storeconfigs database. Thinning catalogs is generally unnecessary when using PuppetDB to store catalogs.", :hook => proc do |value| Puppet.settings.override_default(:storeconfigs, true) if value end }, :config_version => { :default => "", :desc => "How to determine the configuration version. By default, it will be the time that the configuration is parsed, but you can provide a shell script to override how the version is determined. The output of this script will be added to every log message in the reports, allowing you to correlate changes on your hosts to the source version on the server. This setting is deprecated. Please use an explicit directory environment instead. See http://links.puppetlabs.com/directory-environments", }, :zlib => { :default => true, :type => :boolean, :desc => "Boolean; whether to use the zlib library", }, :prerun_command => { :default => "", :desc => "A command to run before every agent run. If this command returns a non-zero return code, the entire Puppet run will fail.", }, :postrun_command => { :default => "", :desc => "A command to run after every agent run. If this command returns a non-zero return code, the entire Puppet run will be considered to have failed, even though it might have performed work during the normal run.", }, :freeze_main => { :default => false, :type => :boolean, :desc => "Freezes the 'main' class, disallowing any code to be added to it. This essentially means that you can't have any code outside of a node, class, or definition other than in the site manifest.", }, :stringify_facts => { :default => true, :type => :boolean, :desc => "Flatten fact values to strings using #to_s. Means you can't have arrays or hashes as fact values.", }, :trusted_node_data => { :default => false, :type => :boolean, :desc => "Stores trusted node data in a hash called $trusted. When true also prevents $trusted from being overridden in any scope.", }, :immutable_node_data => { :default => '$trusted_node_data', :type => :boolean, :desc => "When true, also prevents $trusted and $facts from being overridden in any scope", } ) Puppet.define_settings(:module_tool, :module_repository => { :default => 'https://forge.puppetlabs.com', :desc => "The module repository", }, :module_working_dir => { :default => '$vardir/puppet-module', :desc => "The directory into which module tool data is stored", }, :module_skeleton_dir => { :default => '$module_working_dir/skeleton', :desc => "The directory which the skeleton for module tool generate is stored.", } ) Puppet.define_settings( :main, # We have to downcase the fqdn, because the current ssl stuff (as oppsed to in master) doesn't have good facilities for # manipulating naming. :certname => { :default => Puppet::Settings.default_certname.downcase, :desc => "The name to use when handling certificates. Defaults to the fully qualified domain name.", :call_hook => :on_define_and_write, # Call our hook with the default value, so we're always downcased :hook => proc { |value| raise(ArgumentError, "Certificate names must be lower case; see #1168") unless value == value.downcase }}, :certdnsnames => { :default => '', :hook => proc do |value| unless value.nil? or value == '' then Puppet.warning < < { :default => '', :desc => < { :default => "$confdir/csr_attributes.yaml", :type => :file, :desc => < { :default => "$ssldir/certs", :type => :directory, :mode => 0755, :owner => "service", :group => "service", :desc => "The certificate directory." }, :ssldir => { :default => "$confdir/ssl", :type => :directory, :mode => 0771, :owner => "service", :group => "service", :desc => "Where SSL certificates are kept." }, :publickeydir => { :default => "$ssldir/public_keys", :type => :directory, :mode => 0755, :owner => "service", :group => "service", :desc => "The public key directory." }, :requestdir => { :default => "$ssldir/certificate_requests", :type => :directory, :mode => 0755, :owner => "service", :group => "service", :desc => "Where host certificate requests are stored." }, :privatekeydir => { :default => "$ssldir/private_keys", :type => :directory, :mode => 0750, :owner => "service", :group => "service", :desc => "The private key directory." }, :privatedir => { :default => "$ssldir/private", :type => :directory, :mode => 0750, :owner => "service", :group => "service", :desc => "Where the client stores private certificate information." }, :passfile => { :default => "$privatedir/password", :type => :file, :mode => 0640, :owner => "service", :group => "service", :desc => "Where puppet agent stores the password for its private key. Generally unused." }, :hostcsr => { :default => "$ssldir/csr_$certname.pem", :type => :file, :mode => 0644, :owner => "service", :group => "service", :desc => "Where individual hosts store and look for their certificate requests." }, :hostcert => { :default => "$certdir/$certname.pem", :type => :file, :mode => 0644, :owner => "service", :group => "service", :desc => "Where individual hosts store and look for their certificates." }, :hostprivkey => { :default => "$privatekeydir/$certname.pem", :type => :file, :mode => 0640, :owner => "service", :group => "service", :desc => "Where individual hosts store and look for their private key." }, :hostpubkey => { :default => "$publickeydir/$certname.pem", :type => :file, :mode => 0644, :owner => "service", :group => "service", :desc => "Where individual hosts store and look for their public key." }, :localcacert => { :default => "$certdir/ca.pem", :type => :file, :mode => 0644, :owner => "service", :group => "service", :desc => "Where each client stores the CA certificate." }, :ssl_client_ca_auth => { :type => :file, :mode => 0644, :owner => "service", :group => "service", :desc => "Certificate authorities who issue server certificates. SSL servers will not be considered authentic unless they possess a certificate issued by an authority listed in this file. If this setting has no value then the Puppet master's CA certificate (localcacert) will be used." }, :ssl_server_ca_auth => { :type => :file, :mode => 0644, :owner => "service", :group => "service", :desc => "Certificate authorities who issue client certificates. SSL clients will not be considered authentic unless they possess a certificate issued by an authority listed in this file. If this setting has no value then the Puppet master's CA certificate (localcacert) will be used." }, :hostcrl => { :default => "$ssldir/crl.pem", :type => :file, :mode => 0644, :owner => "service", :group => "service", :desc => "Where the host's certificate revocation list can be found. This is distinct from the certificate authority's CRL." }, :certificate_revocation => { :default => true, :type => :boolean, :desc => "Whether certificate revocation should be supported by downloading a Certificate Revocation List (CRL) to all clients. If enabled, CA chaining will almost definitely not work.", }, :certificate_expire_warning => { :default => "60d", :type => :duration, :desc => "The window of time leading up to a certificate's expiration that a notification will be logged. This applies to CA, master, and agent certificates. #{AS_DURATION}" } ) define_settings( :ca, :ca_name => { :default => "Puppet CA: $certname", :desc => "The name to use the Certificate Authority certificate.", }, :cadir => { :default => "$ssldir/ca", :type => :directory, :owner => "service", :group => "service", :mode => 0755, :desc => "The root directory for the certificate authority." }, :cacert => { :default => "$cadir/ca_crt.pem", :type => :file, :owner => "service", :group => "service", :mode => 0644, :desc => "The CA certificate." }, :cakey => { :default => "$cadir/ca_key.pem", :type => :file, :owner => "service", :group => "service", :mode => 0640, :desc => "The CA private key." }, :capub => { :default => "$cadir/ca_pub.pem", :type => :file, :owner => "service", :group => "service", :mode => 0644, :desc => "The CA public key." }, :cacrl => { :default => "$cadir/ca_crl.pem", :type => :file, :owner => "service", :group => "service", :mode => 0644, :desc => "The certificate revocation list (CRL) for the CA. Will be used if present but otherwise ignored.", }, :caprivatedir => { :default => "$cadir/private", :type => :directory, :owner => "service", :group => "service", :mode => 0750, :desc => "Where the CA stores private certificate information." }, :csrdir => { :default => "$cadir/requests", :type => :directory, :owner => "service", :group => "service", :mode => 0755, :desc => "Where the CA stores certificate requests" }, :signeddir => { :default => "$cadir/signed", :type => :directory, :owner => "service", :group => "service", :mode => 0755, :desc => "Where the CA stores signed certificates." }, :capass => { :default => "$caprivatedir/ca.pass", :type => :file, :owner => "service", :group => "service", :mode => 0640, :desc => "Where the CA stores the password for the private key." }, :serial => { :default => "$cadir/serial", :type => :file, :owner => "service", :group => "service", :mode => 0644, :desc => "Where the serial number for certificates is stored." }, :autosign => { :default => "$confdir/autosign.conf", :type => :autosign, :desc => "Whether (and how) to autosign certificate requests. This setting is only relevant on a puppet master acting as a certificate authority (CA). Valid values are true (autosigns all certificate requests; not recommended), false (disables autosigning certificates), or the absolute path to a file. The file specified in this setting may be either a **configuration file** or a **custom policy executable.** Puppet will automatically determine what it is: If the Puppet user (see the `user` setting) can execute the file, it will be treated as a policy executable; otherwise, it will be treated as a config file. If a custom policy executable is configured, the CA puppet master will run it every time it receives a CSR. The executable will be passed the subject CN of the request _as a command line argument,_ and the contents of the CSR in PEM format _on stdin._ It should exit with a status of 0 if the cert should be autosigned and non-zero if the cert should not be autosigned. If a certificate request is not autosigned, it will persist for review. An admin user can use the `puppet cert sign` command to manually sign it, or can delete the request. For info on autosign configuration files, see [the guide to Puppet's config files](http://docs.puppetlabs.com/guides/configuring.html).", }, :allow_duplicate_certs => { :default => false, :type => :boolean, :desc => "Whether to allow a new certificate request to overwrite an existing certificate.", }, :ca_ttl => { :default => "5y", :type => :duration, :desc => "The default TTL for new certificates. #{AS_DURATION}" }, :req_bits => { :default => 4096, :desc => "The bit length of the certificates.", }, :keylength => { :default => 4096, :desc => "The bit length of keys.", }, :cert_inventory => { :default => "$cadir/inventory.txt", :type => :file, :mode => 0644, :owner => "service", :group => "service", :desc => "The inventory file. This is a text file to which the CA writes a complete listing of all certificates." } ) # Define the config default. define_settings(:application, :config_file_name => { :type => :string, :default => Puppet::Settings.default_config_file_name, :desc => "The name of the puppet config file.", }, :config => { :type => :file, :default => "$confdir/${config_file_name}", :desc => "The configuration file for the current puppet application.", }, :pidfile => { :type => :file, :default => "$rundir/${run_mode}.pid", :desc => "The file containing the PID of a running process. This file is intended to be used by service management frameworks and monitoring systems to determine if a puppet process is still in the process table.", }, :bindaddress => { :default => "0.0.0.0", :desc => "The address a listening server should bind to.", } ) define_settings(:master, :user => { :default => "puppet", :desc => "The user puppet master should run as.", }, :group => { :default => "puppet", :desc => "The group puppet master should run as.", }, :manifestdir => { :default => "$confdir/manifests", :type => :directory, :desc => "Where puppet master looks for its manifests. This setting is deprecated. Please use an explicit directory environment instead. See http://links.puppetlabs.com/directory-environments" }, :manifest => { :default => "$manifestdir/site.pp", :type => :file_or_directory, :desc => "The entry-point manifest file for puppet master or a directory of manifests to be evaluated in alphabetical order. Puppet manages this path as a directory if it exists or if the path ends with a / or \\. This setting is deprecated. Please use an explicit directory environment instead. See http://links.puppetlabs.com/directory-environments", }, :code => { :default => "", :desc => "Code to parse directly. This is essentially only used by `puppet`, and should only be set if you're writing your own Puppet executable.", }, :masterlog => { :default => "$logdir/puppetmaster.log", :type => :file, :owner => "service", :group => "service", :mode => 0660, :desc => "Where puppet master logs. This is generally not used, since syslog is the default log destination." }, :masterhttplog => { :default => "$logdir/masterhttp.log", :type => :file, :owner => "service", :group => "service", :mode => 0660, :create => true, :desc => "Where the puppet master web server logs." }, :masterport => { :default => 8140, :desc => "The port for puppet master traffic. For puppet master, this is the port to listen on; for puppet agent, this is the port to make requests on. Both applications use this setting to get the port.", }, :node_name => { :default => "cert", :desc => "How the puppet master determines the client's identity and sets the 'hostname', 'fqdn' and 'domain' facts for use in the manifest, in particular for determining which 'node' statement applies to the client. Possible values are 'cert' (use the subject's CN in the client's certificate) and 'facter' (use the hostname that the client reported in its facts)", }, :bucketdir => { :default => "$vardir/bucket", :type => :directory, :mode => 0750, :owner => "service", :group => "service", :desc => "Where FileBucket files are stored." }, :rest_authconfig => { :default => "$confdir/auth.conf", :type => :file, :desc => "The configuration file that defines the rights to the different rest indirections. This can be used as a fine-grained authorization system for `puppet master`.", }, :ca => { :default => true, :type => :boolean, :desc => "Whether the master should function as a certificate authority.", }, :basemodulepath => { :default => "$confdir/modules#{File::PATH_SEPARATOR}/usr/share/puppet/modules", :type => :path, :desc => "A search path for modules, as a list of directories separated by the system path separator character. This set of paths is used as the default for legacy environments and is combined with a directory environments 'modules' directory for a directory environment's default.", }, :modulepath => { :default => "$basemodulepath", :type => :path, :desc => "The search path for modules, as a list of directories separated by the system path separator character. (The POSIX path separator is ':', and the Windows path separator is ';'.) This setting is deprecated. Please use an explicit directory environment instead. See http://links.puppetlabs.com/directory-environments", }, :ssl_client_header => { :default => "HTTP_X_CLIENT_DN", :desc => "The header containing an authenticated client's SSL DN. This header must be set by the proxy to the authenticated client's SSL DN (e.g., `/CN=puppet.puppetlabs.com`). Puppet will parse out the Common Name (CN) from the Distinguished Name (DN) and use the value of the CN field for authorization. Note that the name of the HTTP header gets munged by the web server common gateway inteface: an `HTTP_` prefix is added, dashes are converted to underscores, and all letters are uppercased. Thus, to use the `X-Client-DN` header, this setting should be `HTTP_X_CLIENT_DN`.", }, :ssl_client_verify_header => { :default => "HTTP_X_CLIENT_VERIFY", :desc => "The header containing the status message of the client verification. This header must be set by the proxy to 'SUCCESS' if the client successfully authenticated, and anything else otherwise. Note that the name of the HTTP header gets munged by the web server common gateway inteface: an `HTTP_` prefix is added, dashes are converted to underscores, and all letters are uppercased. Thus, to use the `X-Client-Verify` header, this setting should be `HTTP_X_CLIENT_VERIFY`.", }, # To make sure this directory is created before we try to use it on the server, we need # it to be in the server section (#1138). :yamldir => { :default => "$vardir/yaml", :type => :directory, :owner => "service", :group => "service", :mode => "750", :desc => "The directory in which YAML data is stored, usually in a subdirectory."}, :server_datadir => { :default => "$vardir/server_data", :type => :directory, :owner => "service", :group => "service", :mode => "750", :desc => "The directory in which serialized data is stored, usually in a subdirectory."}, :reports => { :default => "store", :desc => "The list of report handlers to use. When using multiple report handlers, their names should be comma-separated, with whitespace allowed. (For example, `reports = http, tagmail`.) This setting is relevant to puppet master and puppet apply. The puppet master will call these report handlers with the reports it receives from agent nodes, and puppet apply will call them with its own report. (In all cases, the node applying the catalog must have `report = true`.) See the report reference for information on the built-in report handlers; custom report handlers can also be loaded from modules. (Report handlers are loaded from the lib directory, at `puppet/reports/NAME.rb`.)", }, :reportdir => { :default => "$vardir/reports", :type => :directory, :mode => 0750, :owner => "service", :group => "service", :desc => "The directory in which to store reports. Each node gets a separate subdirectory in this directory. This setting is only used when the `store` report processor is enabled (see the `reports` setting)."}, :reporturl => { :default => "http://localhost:3000/reports/upload", :desc => "The URL that reports should be forwarded to. This setting is only used when the `http` report processor is enabled (see the `reports` setting).", }, :fileserverconfig => { :default => "$confdir/fileserver.conf", :type => :file, :desc => "Where the fileserver configuration is stored.", }, :strict_hostname_checking => { :default => false, :desc => "Whether to only search for the complete hostname as it is in the certificate when searching for node information in the catalogs.", } ) define_settings(:metrics, :rrddir => { :type => :directory, :default => "$vardir/rrd", :mode => 0750, :owner => "service", :group => "service", :desc => "The directory where RRD database files are stored. Directories for each reporting host will be created under this directory." }, :rrdinterval => { :default => "$runinterval", :type => :duration, :desc => "How often RRD should expect data. This should match how often the hosts report back to the server. #{AS_DURATION}", } ) define_settings(:device, :devicedir => { :default => "$vardir/devices", :type => :directory, :mode => "750", :desc => "The root directory of devices' $vardir.", }, :deviceconfig => { :default => "$confdir/device.conf", :desc => "Path to the device config file for puppet device.", } ) define_settings(:agent, :node_name_value => { :default => "$certname", :desc => "The explicit value used for the node name for all requests the agent makes to the master. WARNING: This setting is mutually exclusive with node_name_fact. Changing this setting also requires changes to the default auth.conf configuration on the Puppet Master. Please see http://links.puppetlabs.com/node_name_value for more information." }, :node_name_fact => { :default => "", :desc => "The fact name used to determine the node name used for all requests the agent makes to the master. WARNING: This setting is mutually exclusive with node_name_value. Changing this setting also requires changes to the default auth.conf configuration on the Puppet Master. Please see http://links.puppetlabs.com/node_name_fact for more information.", :hook => proc do |value| if !value.empty? and Puppet[:node_name_value] != Puppet[:certname] raise "Cannot specify both the node_name_value and node_name_fact settings" end end }, :localconfig => { :default => "$statedir/localconfig", :type => :file, :owner => "root", :mode => 0660, :desc => "Where puppet agent caches the local configuration. An extension indicating the cache format is added automatically."}, :statefile => { :default => "$statedir/state.yaml", :type => :file, :mode => 0660, :desc => "Where puppet agent and puppet master store state associated with the running configuration. In the case of puppet master, this file reflects the state discovered through interacting with clients." }, :clientyamldir => { :default => "$vardir/client_yaml", :type => :directory, :mode => "750", :desc => "The directory in which client-side YAML data is stored." }, :client_datadir => { :default => "$vardir/client_data", :type => :directory, :mode => "750", :desc => "The directory in which serialized data is stored on the client." }, :classfile => { :default => "$statedir/classes.txt", :type => :file, :owner => "root", :mode => 0640, :desc => "The file in which puppet agent stores a list of the classes associated with the retrieved configuration. Can be loaded in the separate `puppet` executable using the `--loadclasses` option."}, :resourcefile => { :default => "$statedir/resources.txt", :type => :file, :owner => "root", :mode => 0640, :desc => "The file in which puppet agent stores a list of the resources associated with the retrieved configuration." }, :puppetdlog => { :default => "$logdir/puppetd.log", :type => :file, :owner => "root", :mode => 0640, :desc => "The log file for puppet agent. This is generally not used." }, :server => { :default => "puppet", :desc => "The puppet master server to which the puppet agent should connect." }, :use_srv_records => { :default => false, :type => :boolean, :desc => "Whether the server will search for SRV records in DNS for the current domain.", }, :srv_domain => { :default => "#{Puppet::Settings.domain_fact}", :desc => "The domain which will be queried to find the SRV records of servers to use.", }, :ignoreschedules => { :default => false, :type => :boolean, :desc => "Boolean; whether puppet agent should ignore schedules. This is useful for initial puppet agent runs.", }, :default_schedules => { :default => true, :type => :boolean, :desc => "Boolean; whether to generate the default schedule resources. Setting this to false is useful for keeping external report processors clean of skipped schedule resources.", }, :puppetport => { :default => 8139, :desc => "Which port puppet agent listens on.", }, :noop => { :default => false, :type => :boolean, :desc => "Whether to apply catalogs in noop mode, which allows Puppet to partially simulate a normal run. This setting affects puppet agent and puppet apply. When running in noop mode, Puppet will check whether each resource is in sync, like it does when running normally. However, if a resource attribute is not in the desired state (as declared in the catalog), Puppet will take no action, and will instead report the changes it _would_ have made. These simulated changes will appear in the report sent to the puppet master, or be shown on the console if running puppet agent or puppet apply in the foreground. The simulated changes will not send refresh events to any subscribing or notified resources, although Puppet will log that a refresh event _would_ have been sent. **Important note:** [The `noop` metaparameter](http://docs.puppetlabs.com/references/latest/metaparameter.html#noop) allows you to apply individual resources in noop mode, and will override the global value of the `noop` setting. This means a resource with `noop => false` _will_ be changed if necessary, even when running puppet agent with `noop = true` or `--noop`. (Conversely, a resource with `noop => true` will only be simulated, even when noop mode is globally disabled.)", }, :runinterval => { :default => "30m", :type => :duration, :desc => "How often puppet agent applies the catalog. Note that a runinterval of 0 means \"run continuously\" rather than \"never run.\" If you want puppet agent to never run, you should start it with the `--no-client` option. #{AS_DURATION}", }, :listen => { :default => false, :type => :boolean, :desc => "Whether puppet agent should listen for connections. If this is true, then puppet agent will accept incoming REST API requests, subject to the default ACLs and the ACLs set in the `rest_authconfig` file. Puppet agent can respond usefully to requests on the `run`, `facts`, `certificate`, and `resource` endpoints.", }, :ca_server => { :default => "$server", :desc => "The server to use for certificate authority requests. It's a separate server because it cannot and does not need to horizontally scale.", }, :ca_port => { :default => "$masterport", :desc => "The port to use for the certificate authority.", }, :catalog_format => { :default => "", :desc => "(Deprecated for 'preferred_serialization_format') What format to use to dump the catalog. Only supports 'marshal' and 'yaml'. Only matters on the client, since it asks the server for a specific format.", :hook => proc { |value| if value Puppet.deprecation_warning "Setting 'catalog_format' is deprecated; use 'preferred_serialization_format' instead." Puppet.settings.override_default(:preferred_serialization_format, value) end } }, :preferred_serialization_format => { :default => "pson", :desc => "The preferred means of serializing ruby instances for passing over the wire. This won't guarantee that all instances will be serialized using this method, since not all classes can be guaranteed to support this format, but it will be used for all classes that support it.", }, :report_serialization_format => { :default => "pson", :type => :enum, :values => ["pson", "yaml"], :desc => "The serialization format to use when sending reports to the `report_server`. Possible values are `pson` and `yaml`. This setting affects puppet agent, but not puppet apply (which processes its own reports). This should almost always be set to `pson`. It can be temporarily set to `yaml` to let agents using this Puppet version connect to a puppet master running Puppet 3.0.0 through 3.2.x. Note that this is set to 'yaml' automatically if the agent detects an older master, so should never need to be set explicitly." }, :legacy_query_parameter_serialization => { :default => false, :type => :boolean, :desc => "The serialization format to use when sending file_metadata query parameters. Older versions of puppet master expect certain query parameters to be serialized as yaml, which is deprecated. This should almost always be false. It can be temporarily set to true to let agents using this Puppet version connect to a puppet master running Puppet 3.0.0 through 3.2.x. Note that this is set to true automatically if the agent detects an older master, so should never need to be set explicitly." }, :agent_catalog_run_lockfile => { :default => "$statedir/agent_catalog_run.lock", :type => :string, # (#2888) Ensure this file is not added to the settings catalog. :desc => "A lock file to indicate that a puppet agent catalog run is currently in progress. The file contains the pid of the process that holds the lock on the catalog run.", }, :agent_disabled_lockfile => { :default => "$statedir/agent_disabled.lock", :type => :file, :desc => "A lock file to indicate that puppet agent runs have been administratively disabled. File contains a JSON object with state information.", }, :usecacheonfailure => { :default => true, :type => :boolean, :desc => "Whether to use the cached configuration when the remote configuration will not compile. This option is useful for testing new configurations, where you want to fix the broken configuration rather than reverting to a known-good one.", }, :use_cached_catalog => { :default => false, :type => :boolean, :desc => "Whether to only use the cached catalog rather than compiling a new catalog on every run. Puppet can be run with this enabled by default and then selectively disabled when a recompile is desired.", }, :ignoremissingtypes => { :default => false, :type => :boolean, :desc => "Skip searching for classes and definitions that were missing during a prior compilation. The list of missing objects is maintained per-environment and persists until the environment is cleared or the master is restarted.", }, :ignorecache => { :default => false, :type => :boolean, :desc => "Ignore cache and always recompile the configuration. This is useful for testing new configurations, where the local cache may in fact be stale even if the timestamps are up to date - if the facts change or if the server changes.", }, :dynamicfacts => { :default => "memorysize,memoryfree,swapsize,swapfree", :desc => "(Deprecated) Facts that are dynamic; these facts will be ignored when deciding whether changed facts should result in a recompile. Multiple facts should be comma-separated.", :hook => proc { |value| if value Puppet.deprecation_warning "The dynamicfacts setting is deprecated and will be ignored." end } }, :splaylimit => { :default => "$runinterval", :type => :duration, :desc => "The maximum time to delay before runs. Defaults to being the same as the run interval. #{AS_DURATION}", }, :splay => { :default => false, :type => :boolean, :desc => "Whether to sleep for a pseudo-random (but consistent) amount of time before a run.", }, :clientbucketdir => { :default => "$vardir/clientbucket", :type => :directory, :mode => 0750, :desc => "Where FileBucket files are stored locally." }, :configtimeout => { :default => "2m", :type => :duration, :desc => "How long the client should wait for the configuration to be retrieved before considering it a failure. This can help reduce flapping if too many clients contact the server at one time. #{AS_DURATION}", }, :report_server => { :default => "$server", :desc => "The server to send transaction reports to.", }, :report_port => { :default => "$masterport", :desc => "The port to communicate with the report_server.", }, :inventory_server => { :default => "$server", :desc => "The server to send facts to.", }, :inventory_port => { :default => "$masterport", :desc => "The port to communicate with the inventory_server.", }, :report => { :default => true, :type => :boolean, :desc => "Whether to send reports after every transaction.", }, :lastrunfile => { :default => "$statedir/last_run_summary.yaml", :type => :file, :mode => 0644, :desc => "Where puppet agent stores the last run report summary in yaml format." }, :lastrunreport => { :default => "$statedir/last_run_report.yaml", :type => :file, :mode => 0640, :desc => "Where puppet agent stores the last run report in yaml format." }, :graph => { :default => false, :type => :boolean, :desc => "Whether to create dot graph files for the different configuration graphs. These dot files can be interpreted by tools like OmniGraffle or dot (which is part of ImageMagick).", }, :graphdir => { :default => "$statedir/graphs", :type => :directory, :desc => "Where to store dot-outputted graphs.", }, :http_compression => { :default => false, :type => :boolean, :desc => "Allow http compression in REST communication with the master. This setting might improve performance for agent -> master communications over slow WANs. Your puppet master needs to support compression (usually by activating some settings in a reverse-proxy in front of the puppet master, which rules out webrick). It is harmless to activate this settings if your master doesn't support compression, but if it supports it, this setting might reduce performance on high-speed LANs.", }, :waitforcert => { :default => "2m", :type => :duration, :desc => "How frequently puppet agent should ask for a signed certificate. When starting for the first time, puppet agent will submit a certificate signing request (CSR) to the server named in the `ca_server` setting (usually the puppet master); this may be autosigned, or may need to be approved by a human, depending on the CA server's configuration. Puppet agent cannot apply configurations until its approved certificate is available. Since the certificate may or may not be available immediately, puppet agent will repeatedly try to fetch it at this interval. You can turn off waiting for certificates by specifying a time of 0, in which case puppet agent will exit if it cannot get a cert. #{AS_DURATION}", }, :ordering => { :type => :enum, :values => ["manifest", "title-hash", "random"], :default => "title-hash", :desc => "How unrelated resources should be ordered when applying a catalog. Allowed values are `title-hash`, `manifest`, and `random`. This setting affects puppet agent and puppet apply, but not puppet master. * `title-hash` (the default) will order resources randomly, but will use the same order across runs and across nodes. * `manifest` will use the order in which the resources were declared in their manifest files. * `random` will order resources randomly and change their order with each run. This can work like a fuzzer for shaking out undeclared dependencies. Regardless of this setting's value, Puppet will always obey explicit dependencies set with the before/require/notify/subscribe metaparameters and the `->`/`~>` chaining arrows; this setting only affects the relative ordering of _unrelated_ resources." } ) define_settings(:inspect, :archive_files => { :type => :boolean, :default => false, :desc => "During an inspect run, whether to archive files whose contents are audited to a file bucket.", }, :archive_file_server => { :default => "$server", :desc => "During an inspect run, the file bucket server to archive files to if archive_files is set.", } ) # Plugin information. define_settings( :main, :plugindest => { :type => :directory, :default => "$libdir", :desc => "Where Puppet should store plugins that it pulls down from the central server.", }, :pluginsource => { :default => "puppet://$server/plugins", :desc => "From where to retrieve plugins. The standard Puppet `file` type is used for retrieval, so anything that is a valid file source can be used here.", }, :pluginfactdest => { :type => :directory, :default => "$vardir/facts.d", :desc => "Where Puppet should store external facts that are being handled by pluginsync", }, :pluginfactsource => { :default => "puppet://$server/pluginfacts", :desc => "Where to retrieve external facts for pluginsync", }, :pluginsync => { :default => true, :type => :boolean, :desc => "Whether plugins should be synced with the central server.", }, :pluginsignore => { :default => ".svn CVS .git", :desc => "What files to ignore when pulling down plugins.", } ) # Central fact information. define_settings( :main, :factpath => { :type => :path, :default => "$vardir/lib/facter#{File::PATH_SEPARATOR}$vardir/facts", :desc => "Where Puppet should look for facts. Multiple directories should be separated by the system path separator character. (The POSIX path separator is ':', and the Windows path separator is ';'.)", :call_hook => :on_initialize_and_write, # Call our hook with the default value, so we always get the value added to facter. :hook => proc do |value| paths = value.split(File::PATH_SEPARATOR) Facter.search(*paths) end } ) define_settings( :tagmail, :tagmap => { :default => "$confdir/tagmail.conf", :desc => "The mapping between reporting tags and email addresses.", }, :sendmail => { :default => which('sendmail') || '', :desc => "Where to find the sendmail binary with which to send email.", }, :reportfrom => { :default => "report@" + [Facter["hostname"].value,Facter["domain"].value].join("."), :desc => "The 'from' email address for the reports.", }, :smtpserver => { :default => "none", :desc => "The server through which to send email reports.", }, :smtpport => { :default => 25, :desc => "The TCP port through which to send email reports.", }, :smtphelo => { :default => Facter["fqdn"].value, :desc => "The name by which we identify ourselves in SMTP HELO for reports. If you send to a smtpserver which does strict HELO checking (as with Postfix's `smtpd_helo_restrictions` access controls), you may need to ensure this resolves.", } ) define_settings( :rails, :dblocation => { :default => "$statedir/clientconfigs.sqlite3", :type => :file, :mode => 0660, :owner => "service", :group => "service", :desc => "The sqlite database file. #{STORECONFIGS_ONLY}" }, :dbadapter => { :default => "sqlite3", :desc => "The type of database to use. #{STORECONFIGS_ONLY}", }, :dbmigrate => { :default => false, :type => :boolean, :desc => "Whether to automatically migrate the database. #{STORECONFIGS_ONLY}", }, :dbname => { :default => "puppet", :desc => "The name of the database to use. #{STORECONFIGS_ONLY}", }, :dbserver => { :default => "localhost", :desc => "The database server for caching. Only used when networked databases are used.", }, :dbport => { :default => "", :desc => "The database password for caching. Only used when networked databases are used. #{STORECONFIGS_ONLY}", }, :dbuser => { :default => "puppet", :desc => "The database user for caching. Only used when networked databases are used. #{STORECONFIGS_ONLY}", }, :dbpassword => { :default => "puppet", :desc => "The database password for caching. Only used when networked databases are used. #{STORECONFIGS_ONLY}", }, :dbconnections => { :default => '', :desc => "The number of database connections for networked databases. Will be ignored unless the value is a positive integer. #{STORECONFIGS_ONLY}", }, :dbsocket => { :default => "", :desc => "The database socket location. Only used when networked databases are used. Will be ignored if the value is an empty string. #{STORECONFIGS_ONLY}", }, :railslog => { :default => "$logdir/rails.log", :type => :file, :mode => 0600, :owner => "service", :group => "service", :desc => "Where Rails-specific logs are sent. #{STORECONFIGS_ONLY}" }, :rails_loglevel => { :default => "info", :desc => "The log level for Rails connections. The value must be a valid log level within Rails. Production environments normally use `info` and other environments normally use `debug`. #{STORECONFIGS_ONLY}", } ) define_settings( :couchdb, :couchdb_url => { :default => "http://127.0.0.1:5984/puppet", :desc => "The url where the puppet couchdb database will be created. Only used when `facts_terminus` is set to `couch`.", } ) define_settings( :transaction, :tags => { :default => "", :desc => "Tags to use to find resources. If this is set, then only resources tagged with the specified tags will be applied. Values must be comma-separated.", }, :evaltrace => { :default => false, :type => :boolean, :desc => "Whether each resource should log when it is being evaluated. This allows you to interactively see exactly what is being done.", }, :summarize => { :default => false, :type => :boolean, :desc => "Whether to print a transaction summary.", } ) define_settings( :main, :external_nodes => { :default => "none", :desc => "An external command that can produce node information. The command's output must be a YAML dump of a hash, and that hash must have a `classes` key and/or a `parameters` key, where `classes` is an array or hash and `parameters` is a hash. For unknown nodes, the command should exit with a non-zero exit code. This command makes it straightforward to store your node mapping information in other data sources like databases.", } ) define_settings( :ldap, :ldapssl => { :default => false, :type => :boolean, :desc => "Whether SSL should be used when searching for nodes. Defaults to false because SSL usually requires certificates to be set up on the client side.", }, :ldaptls => { :default => false, :type => :boolean, :desc => "Whether TLS should be used when searching for nodes. Defaults to false because TLS usually requires certificates to be set up on the client side.", }, :ldapserver => { :default => "ldap", :desc => "The LDAP server. Only used if `node_terminus` is set to `ldap`.", }, :ldapport => { :default => 389, :desc => "The LDAP port. Only used if `node_terminus` is set to `ldap`.", }, :ldapstring => { :default => "(&(objectclass=puppetClient)(cn=%s))", :desc => "The search string used to find an LDAP node.", }, :ldapclassattrs => { :default => "puppetclass", :desc => "The LDAP attributes to use to define Puppet classes. Values should be comma-separated.", }, :ldapstackedattrs => { :default => "puppetvar", :desc => "The LDAP attributes that should be stacked to arrays by adding the values in all hierarchy elements of the tree. Values should be comma-separated.", }, :ldapattrs => { :default => "all", :desc => "The LDAP attributes to include when querying LDAP for nodes. All returned attributes are set as variables in the top-level scope. Multiple values should be comma-separated. The value 'all' returns all attributes.", }, :ldapparentattr => { :default => "parentnode", :desc => "The attribute to use to define the parent node.", }, :ldapuser => { :default => "", :desc => "The user to use to connect to LDAP. Must be specified as a full DN.", }, :ldappassword => { :default => "", :desc => "The password to use to connect to LDAP.", }, :ldapbase => { :default => "", :desc => "The search base for LDAP searches. It's impossible to provide a meaningful default here, although the LDAP libraries might have one already set. Generally, it should be the 'ou=Hosts' branch under your main directory.", } ) define_settings(:master, :storeconfigs => { :default => false, :type => :boolean, :desc => "Whether to store each client's configuration, including catalogs, facts, and related data. This also enables the import and export of resources in the Puppet language - a mechanism for exchange resources between nodes. By default this uses ActiveRecord and an SQL database to store and query the data; this, in turn, will depend on Rails being available. You can adjust the backend using the storeconfigs_backend setting.", # Call our hook with the default value, so we always get the libdir set. :call_hook => :on_initialize_and_write, :hook => proc do |value| require 'puppet/node' require 'puppet/node/facts' if value if not Puppet.settings[:async_storeconfigs] Puppet::Resource::Catalog.indirection.cache_class = :store_configs Puppet.settings.override_default(:catalog_cache_terminus, :store_configs) end Puppet::Node::Facts.indirection.cache_class = :store_configs Puppet::Resource.indirection.terminus_class = :store_configs end end }, :storeconfigs_backend => { :type => :terminus, :default => "active_record", :desc => "Configure the backend terminus used for StoreConfigs. By default, this uses the ActiveRecord store, which directly talks to the database from within the Puppet Master process." } ) define_settings(:parser, :templatedir => { :default => "$vardir/templates", :type => :directory, :desc => "Where Puppet looks for template files. Can be a list of colon-separated directories. This setting is deprecated. Please use an explicit directory environment instead. See http://links.puppetlabs.com/directory-environments" }, :allow_variables_with_dashes => { :default => false, :desc => <<-'EOT' Permit hyphens (`-`) in variable names and issue deprecation warnings about them. This setting **should always be `false`;** setting it to `true` will cause subtle and wide-ranging bugs. It will be removed in a future version. Hyphenated variables caused major problems in the language, but were allowed between Puppet 2.7.3 and 2.7.14. If you used them during this window, we apologize for the inconvenience --- you can temporarily set this to `true` in order to upgrade, and can rename your variables at your leisure. Please revert it to `false` after you have renamed all affected variables. EOT }, :parser => { :default => "current", :desc => <<-'EOT' Selects the parser to use for parsing puppet manifests (in puppet DSL language/'.pp' files). Available choices are `current` (the default) and `future`. The `curent` parser means that the released version of the parser should be used. The `future` parser is a "time travel to the future" allowing early exposure to new language features. What these features are will vary from release to release and they may be invididually configurable. Available Since Puppet 3.2. EOT }, :evaluator => { :default => "future", :hook => proc do |value| if !['future', 'current'].include?(value) raise "evaluator can only be set to 'future' or 'current', got '#{value}'" end end, :desc => <<-'EOT' Which evaluator to use when compiling Puppet manifests. Valid values are `current` and `future` (the default). **Note:** This setting is only used when `parser = future`. It allows testers to turn off the `future` evaluator when doing detailed tests and comparisons of the new compilation system. Evaluation is the second stage of catalog compilation. After the parser converts a manifest to a model of expressions, the evaluator processes each expression. (For example, a resource declaration signals the evaluator to add a resource to the catalog). The `future` parser and evaluator are slated to become default in Puppet 4. Their purpose is to add new features and improve consistency and reliability. Available Since Puppet 3.5. EOT }, + :biff => { + :default => false, + :type => :boolean, + :hook => proc do |value| + if Puppet.settings[:parser] != 'future' + Puppet.settings.override_default(:parser, 'future') + end + if Puppet.settings[:evaluator] != 'future' + Puppet.settings.override_default(:evaluator, 'future') + end + end, + :desc => <<-EOT + Turns on Biff the catalog builder, future parser, and future evaluator. + This is an experimental feature - and this setting may go away before + release of Pupet 3.6. + EOT + }, :max_errors => { :default => 10, :desc => <<-'EOT' Sets the max number of logged/displayed parser validation errors in case multiple errors have been detected. A value of 0 is the same as value 1. The count is per manifest. EOT }, :max_warnings => { :default => 10, :desc => <<-'EOT' Sets the max number of logged/displayed parser validation warnings in case multiple errors have been detected. A value of 0 is the same as value 1. The count is per manifest. EOT }, :max_deprecations => { :default => 10, :desc => <<-'EOT' Sets the max number of logged/displayed parser validation deprecation warnings in case multiple errors have been detected. A value of 0 is the same as value 1. The count is per manifest. EOT }, :strict_variables => { :default => false, :type => :boolean, :desc => <<-'EOT' Makes the parser raise errors when referencing unknown variables. (This does not affect referencing variables that are explicitly set to undef). EOT } ) define_settings(:puppetdoc, :document_all => { :default => false, :type => :boolean, :desc => "Whether to document all resources when using `puppet doc` to generate manifest documentation.", } ) end diff --git a/lib/puppet/functions.rb b/lib/puppet/functions.rb index dfce58db4..c05bd2aac 100644 --- a/lib/puppet/functions.rb +++ b/lib/puppet/functions.rb @@ -1,636 +1,640 @@ module Puppet::Functions # Creates a new Puppet Function Class with the given func_name with functionality defined by the given block. # The func name should be an unqualified lower case name. The block is evaluated as when a derived Ruby class # is created and it is intended (in the simplest case) that the user defines the actual function in a method named # the same as the function (as shown in the first example below). # # @example A simple function # Puppet::Functions.create_function('min') do # def min(a, b) # a <= b ? a : b # end # end # # Documentation for the function should be placed as comments to the method(s) that define the functionality # The simplest form of defining a function introspects the method signature (in the example `min(a,b)`) and # infers that this means that there are 2 required arguments of Object type. If something else is wanted # the method `dispatch` should be called in the block defining the function to define the details of dispatching # a call of the function. # # In the next example, the function is enhanced to check that arguments are of numeric type. # # @example dispatch and type checking # Puppet::Functions.create_function('min') do # dispatch :min do # param Numeric, 'a' # param Numeric, 'b' # end # # def min(a, b) # a <= b ? a : b # end # end # # It is possible to specify multiple type signatures as defined by the param specification in the dispatch method, and # dispatch to the same, or alternative methods. # When a call is processed the given type signatures are tested in the order they were defined - the first signature # with matching type wins. # # Polymorphic Dispatch # --- # The dispatcher also supports polymorphic dispatch where the method to call is selected based on the type of the # first argument. It is possible to mix regular and polymorphic dispatching, the first with a matching signature wins # in all cases. (Typically one or the other dispatch type is selected for a given function). # # Polymorphic dispatch is based on a method prefix, followed by "_ClassName" where "ClassName" is the simple name # of the class of the first argument. # # @example using polymorphic dispatch # Puppet::Functions.create_function('label') do # dispatch_polymorph do # param Object, 'label' # end # # def label_Object(o) # "A Ruby object of class #{o.class}" # end # # def label_String(o) # "A String with value '#{o}'" # end # end # # In this example, if the argument is a String, a special label is produced and for all others a generic label is # produced. It is now easy to add `label_` methods for other classes as needed without changing the dispatch. # # The type specification of the signature that follows the name of the method are given to the # `Puppet::Pops::Types::TypeFactory` to create a PTupleType. # # Arguments may be Puppet Type References in String form, Ruby classes (for basic types), or Puppet Type instances # as created by the Puppet::Pops::Types::TypeFactory. To make type creation convenient, the logic that builds a dispatcher # redirects any calls to the type factory. # # Injection Support # === # The Function API supports injection of data and services. It is possible to make injection that takes effect # when the function is loaded (for services and runtime configuration that does not change depending on how/from where # in what context the function is called. It is also possible to inject and weave argument values into a call. # # Injection of attributes # --- # Injection of attributes is performed by one of the methods `attr_injected`, and `attr_injected_producer`. # # @example using injected attributes # Puppet::Functions.create_function('test') do # attr_injected String, :larger, 'message_larger' # attr_injected String, :smaller, 'message_smaller' # def test(a, b) # a > b ? larger() : smaller() # end # end # # Injection and Weaving of parameters # --- # It is possible to inject and weave parameters into a call. These extra parameters are not passed from the # Puppet logic. # # @example using injected parameters # Puppet::Functions.create_function('test') do # dispatch :test do # param Scalar, 'a' # param Scalar, 'b' # injected_param String, 'larger', 'message_larger' # injected_param String, 'smaller', 'message_smaller' # end # def test(a, b, larger, smaller) # a > b ? larger : smaller # end # end # The function in the example above is called like this: # # test(10, 20) # + # @param func_name [String, Symbol] a simple or qualified function name + # @param &block [Proc] the block that defines the methods and dispatch of the Function to create + # @return [Class] the newly created Function class + # def self.create_function(func_name, &block) - + func_name = func_name.to_s # Creates an anonymous class to represent the function # The idea being that it is garbage collected when there are no more # references to it. # the_class = Class.new(Function, &block) # TODO: The func_name should be a symbol - else error # Why symbol? They are sticky in memory and the qualified name used in PP is a Fully qualified string # It should probably be either a QualifiedName (counting on it to already be validated? or check again? or # a string # Assume String for now, and that names are properly formed... # Later, must handle name spacing of function, and only use last part as the actual name - better with two # parameters, namespace, and func_name perhaps - or maybe namespace is derived from where it is found, which is # even better # # Make the anonymous class appear to have the class-name # Even if this class is not bound to such a symbol in a global ruby scope and # must be resolved via the loader. # This also overrides any attempt to define a name method in the given block # (Since it redefines it) # - # TODO, the final name of the function class should also reflect the name space # TODO, enforce name in lower case (to further make it stand out since Ruby class names are upper case) # the_class.instance_eval do @func_name = func_name def name @func_name end end # Automatically create an object dispatcher based on introspection if the loaded user code did not # define any dispatchers. Fail if function name does not match a given method name in user code. # if the_class.dispatcher.empty? - type, names = default_dispatcher(the_class, func_name) - the_class.dispatcher.add_dispatch(type, func_name, names, nil, nil) + simple_name = func_name.split(/::/)[-1] + type, names = default_dispatcher(the_class, simple_name) + the_class.dispatcher.add_dispatch(type, simple_name, names, nil, nil) end # The function class is returned as the result of the create function method the_class end # Creates a default dispatcher configured from a method with the same name as the function def self.default_dispatcher(the_class, func_name) unless the_class.method_defined?(func_name) raise ArgumentError, "Function Creation Error, cannot create a default dispatcher for function '#{func_name}', no method with this name found" end object_signature(*min_max_param(the_class.instance_method(func_name))) end def self.min_max_param(method) # Ruby 1.8.7 does not have support for details about parameters if method.respond_to?(:parameters) result = {:req => 0, :opt => 0, :rest => 0 } # TODO: Optimize into one map iteration that produces names map, and sets count as side effect method.parameters.each { |p| result[p[0]] += 1 } from = result[:req] to = result[:rest] > 0 ? :default : from + result[:opt] names = method.parameters.map {|p| p[1] } else # Cannot correctly compute the signature in Ruby 1.8.7 because arity for optional values is # screwed up (there is no way to get the upper limit), an optional looks the same as a varargs # In this case - the failure will simply come later when the call fails # arity = method.arity from = arity >= 0 ? arity : -arity -1 to = arity >= 0 ? arity : :default # i.e. infinite (which is wrong when there are optional - flaw in 1.8.7) names = [] # no names available end [from, to, names] end def self.object_signature(from, to, names) # Construct the type for the signature # Array[Object], Integer[from, to]] factory = Puppet::Pops::Types::TypeFactory optional_object = factory.object [factory.constrain_size(factory.array_of(optional_object), from, to), names] end class Function # The scope where the function is defined attr_reader :closure_scope # The loader that loaded this function # Should be used if function wants to load other things. # attr_reader :loader def initialize(closure_scope, loader) @closure_scope = closure_scope @loader = loader end def call(scope, *args) self.class.dispatcher.dispatch(self, scope, args) end def self.define_dispatch(&block) builder = DispatcherBuilder.new(dispatcher) builder.instance_eval &block end def self.dispatch(meth_name, &block) builder = DispatcherBuilder.new(dispatcher) builder.instance_eval do dispatch(meth_name, &block) end end def self.dispatch_polymorph(meth_name, &block) builder = DispatcherBuilder.new(dispatcher) builder.instance_eval do dispatch_polymorph(meth_name, &block) end end # Defines class level injected attribute with reader method # def self.attr_injected(type, attribute_name, injection_name = nil) define_method(attribute_name) do ivar = :"@#{attribute_name.to_s}" unless instance_variable_defined?(ivar) injector = Puppet.lookup(:injector) instance_variable_set(ivar, injector.lookup(closure_scope, type, injection_name)) end instance_variable_get(ivar) end end # Defines class level injected producer attribute with reader method # def self.attr_injected_producer(type, attribute_name, injection_name = nil) define_method(attribute_name) do ivar = :"@#{attribute_name.to_s}" unless instance_variable_defined?(ivar) injector = Puppet.lookup(:injector) instance_variable_set(ivar, injector.lookup_producer(closure_scope, type, injection_name)) end instance_variable_get(ivar) end end def self.dispatcher @dispatcher ||= Dispatcher.new end # Delegates method calls not supported by Function.class to the TypeFactory # def self.method_missing(meth, *args, &block) if Puppet::Pops::Types::TypeFactory.respond_to?(meth) Puppet::Pops::Types::TypeFactory.send(meth, *args, &block) else super end end def self.respond_to?(meth, include_all=false) Puppet::Pops::Types::TypeFactory.respond_to?(meth, include_all) || super end end class DispatcherBuilder def initialize(dispatcher) @dispatcher = dispatcher end # Delegates method calls not supported by Function.class to the TypeFactory # def method_missing(meth, *args, &block) if Puppet::Pops::Types::TypeFactory.respond_to?(meth) Puppet::Pops::Types::TypeFactory.send(meth, *args, &block) else super end end def respond_to?(meth, include_all=false) Puppet::Pops::Types::TypeFactory.respond_to?(meth, include_all) || super end def dispatch(meth_name, &block) # an array of either an index into names/types, or an array with injection information [type, name, injection_name] # used when the call is being made to weave injections into the given arguments. # @types = [] @names = [] @weaving = [] @injections = [] @min = nil @max = nil self.instance_eval &block @dispatcher.add_dispatch(self.class.create_tuple(@types, @min, @max), meth_name, @names, @injections, @weaving) end def dispatch_polymorph(meth_name, &block) @types = [] @names = [] @weaving = [] @injections = [] @min = nil @max = nil self.instance_eval &block @dispatcher.add_polymorph_dispatch(self.class.create_tuple(@types, @min, @max), meth_name, @names, @injections, @weaving) end def param(type, name) @types << type @names << name # mark what should be picked for this position when dispatching @weaving << @names.size()-1 end # TODO: is param name really needed? Perhaps for error messages? (it is unused now) # def injected_param(type, name, injection_name = '') @injections << [type, name, injection_name] # mark what should be picked for this position when dispatching @weaving << [@injections.size() -1] end # TODO: is param name really needed? Perhaps for error messages? (it is unused now) # def injected_producer_param(type, name, injection_name = '') @injections << [type, name, injection_name, :producer] # mark what should be picked for this position when dispatching @weaving << [@injections.size()-1] end # Specifies the min and max occurance of arguments (of the specified types) if something other than # the exact count from the number of specified types). The max value may be specified as -1 if an infinite # number of arguments are supported. When max is > than the number of specified types, the last specified type # repeats. # def arg_count(min_occurs, max_occurs) @min = min_occurs @max = max_occurs unless min_occurs.is_a?(Integer) && min_occurs >= 0 raise ArgumentError, "min arg_count of function parameter must be an Integer >=0, got #{min_occurs.class} '#{min_occurs}'" end unless max_occurs == :default || (max_occurs.is_a?(Integer) && max_occurs >= 0) raise ArgumentError, "max arg_count of function parameter must be an Integer >= 0, or :default, got #{max_occurs.class} '#{max_occurs}'" end - unless max_occurs == :default || (max_occurs.is_a?(integer) && max_occurs >= min_occurs) + unless max_occurs == :default || (max_occurs.is_a?(Integer) && max_occurs >= min_occurs) raise ArgumentError, "max arg_count must be :default (infinite) or >= min arg_count, got min: '#{min_occurs}, max: '#{max_occurs}'" end end # Handles creation of a tuple type from strings, puppet types, or ruby types and allows # the min/max occurs of the given types to be given as one or two integer values at the end. # def self.create_tuple(types, from, to) mapped_types = types.map do |t| case t when String type_parser ||= Puppet::Pops::Types::TypeParser.new type_parser.parse(t) when Puppet::Pops::Types::PAbstractType t when Class Puppet::Pops::Types::TypeFactory.type_of(t) else raise ArgumentError, "Type signature argument must be a Puppet Type, Class, or a String reference to a type. Got #{t.class}" end end tuple_t = Puppet::Pops::Types::TypeFactory.tuple(*mapped_types) if !(from.nil? && to.nil?) Puppet::Pops::Types::TypeFactory.constrain_size(tuple_t, from,to) else tuple_t end end end # This is a smart dispatcher # For backwards compatible (untyped) API, the dispatcher only enforces simple count, and can be simpler internally # class Dispatcher attr_reader :dispatchers def initialize() @dispatchers = [ ] end # Answers if dispatching has been defined # @return [Boolean] true if dispatching has been defined # def empty? @dispatchers.empty? end # Dispatches the call to the first found signature (entry with matching type). # # @param instance [Puppet::Functions::Function] - the function to call # @param calling_scope [T.B.D::Scope] - the scope of the caller # @param args [Array] - the given arguments in the form of an Array # @return [Object] - what the called function produced # def dispatch(instance, calling_scope, args) tc = Puppet::Pops::Types::TypeCalculator actual = tc.infer_set(args) found = @dispatchers.find { |d| tc.assignable?(d.type, actual) } if found found.invoke(instance, calling_scope, args) else raise ArgumentError, "function '#{instance.class.name}' called with mis-matched arguments\n#{diff_string(instance.class.name, actual)}" end end # Adds a regular dispatch for one method name # # @param type [Puppet::Pops::Types::PArrayType, Puppet::Pops::Types::PTupleType] - type describing signature # @param method_name [String] - the name of the method that will be called when type matches given arguments # @param names [Array] - array with names matching the number of parameters specified by type (or empty array) # def add_dispatch(type, method_name, param_names, injections, weaving) @dispatchers << Dispatch.new(type, NonPolymorphicVisitor.new(method_name), param_names, injections, weaving) end # Adds a polymorph dispatch for one method name # # @param type [Puppet::Pops::Types::PArrayType, Puppet::Pops::Types::PTupleType] - type describing signature # @param method_name [String] - the name of the (polymorph) method that will be called when type matches given arguments # @param names [Array] - array with names matching the number of parameters specified by type (or empty array) # def add_polymorph_dispatch(type, method_name, param_names, injections, weaving) # Type is a CollectionType, its size-type indicates min/max args # This includes the polymorph object which needs to be deducted from the # number of additional args # NOTE: the type is valuable if there are type constraints also on the first arg # (better error message) range = type.size_range # get .from, .to, unbound if nil (from must be bound, to can be nil) raise ArgumentError, "polymorph dispath on collection type without range" unless range raise ArgumentError, "polymorph dispatch on signature without object" if range[0] < 1 from = range[0] - 1 # The object itself is not included to = range[1] -1 # object not included here either (it may be infinity, but does not matter) if !injections.empty? from += injections.size to += injections.size end to = (to == Puppet::Pops::Types::INFINITY) ? -1 : to @dispatchers << Dispatch.new(type, Puppet::Pops::Visitor.new(self, method_name, from, to), param_names, injections, weaving) # @dispatchers << [ type, Puppet::Pops::Visitor.new(self, method_name, from, to), param_names, injections, weaving ] end private class Dispatch attr_reader :type attr_reader :visitor attr_reader :param_names attr_reader :injections attr_reader :weaving def initialize(type, visitor, param_names, injections, weaving) @type = type @visitor = visitor @param_names = param_names || [] @injections = injections || [] @weaving = weaving end def invoke(instance, calling_scope, args) @visitor.visit_this(instance, *weave(calling_scope, args)) end def weave(scope, args) # no nead to weave if there are no injections if injections.empty? args else injector = Puppet.lookup(:injector) weaving.map do |knit| if knit.is_a?(Array) injection_data = @injections[knit[0]] # inject if injection_data[3] == :producer injector.lookup_producer(scope, injection_data[0], injection_data[2]) else injector.lookup(scope, injection_data[0], injection_data[2]) end else # pick that argument args[knit] end end end end end # Produces a string with the difference between the given arguments and support signature(s). # def diff_string(name, args_type) result = [ ] if @dispatchers.size < 2 params_type = @dispatchers[ 0 ].type params_names = @dispatchers[ 0 ].param_names result << "expected:\n #{name}(#{signature_string(params_type, params_names)}) - #{arg_count_string(params_type)}" else result << "expected one of:\n" result << (@dispatchers.map {|d| " #{name}(#{signature_string(d.type, d.param_names)}) - #{arg_count_string(d.type)}"}.join("\n")) end result << "\nactual:\n #{name}(#{arg_types_string(args_type)}) - #{arg_count_string(args_type)}" result.join('') end # Produces a string for the signature(s) # def signature_string(args_type, param_names) size_type = args_type.size_type types = case args_type when Puppet::Pops::Types::PTupleType last_range = args_type.repeat_last_range required_count, _ = args_type.size_range args_type.types when Puppet::Pops::Types::PArrayType from, to = args_type.size_range required_count = from # array has just one element, but there may be multiple names that needs to be subtracted from the count # to make it correct for the last named element adjust = max(0, param_names.size() -1) last_range = [max(0, (from - adjust)), (to - adjust)] [ args_type.element_type ] end tc = Puppet::Pops::Types::TypeCalculator # join type with names (types are always present, names are optional) # separate entries with comma # if param_names.empty? result = types.each_with_index.map {|t, index| tc.string(t) + opt_value_indicator(index, required_count, 0) }.join(', ') else limit = param_names.size result = param_names.each_with_index.map do |name, index| [tc.string(types[index] || types[-1]), name].join(' ') + opt_value_indicator(index, required_count, limit) end.join(', ') end # Add {from, to} for the last type # This works for both Array and Tuple since it describes the allowed count of the "last" type element # for both. It does not show anything when the range is {1,1}. # result += range_string(last_range) result end # Why oh why Ruby do you not have a standard Math.max ? def max(a, b) a >= b ? a : b end def opt_value_indicator(index, required_count, limit) count = index + 1 (count > required_count && count < limit) ? '?' : '' end def arg_count_string(args_type) "arg count #{range_string(args_type.size_range, false)}" end def arg_types_string(args_type) types = case args_type when Puppet::Pops::Types::PTupleType last_range = args_type.repeat_last_range args_type.types when Puppet::Pops::Types::PArrayType last_range = args_type.size_range [ args_type.element_type ] end # stringify generalized versions or it will display Integer[10,10] for "10", String['the content'] etc. # note that type must be copied since generalize is a mutating operation tc = Puppet::Pops::Types::TypeCalculator result = types.map { |t| tc.string(tc.generalize!(t.copy)) }.join(', ') # Add {from, to} for the last type # This works for both Array and Tuple since it describes the allowed count of the "last" type element # for both. It does not show anything when the range is {1,1}. # result += range_string(last_range) result end # Formats a range into a string {from, to} with optimizations when: # * from and to are equal => {from} # * from and to are both and 1 and squelch_one == true => '' # * from is 0 and to is 1 => '?' # * to is INFINITY => {from, } # def range_string(size_range, squelch_one = true) from = size_range[ 0 ] to = size_range[ 1 ] if from == to (squelch_one && from == 1) ? '' : "{#{from}}" elsif to == Puppet::Pops::Types::INFINITY "{#{from},}" elsif from == 0 && to == 1 '?' else "{#{from},#{to}}" end end end # Simple non Polymorphic Visitor class NonPolymorphicVisitor attr_reader :name def initialize(name) @name = name end def visit_this(instance, *args) instance.send(name, *args) end end end \ No newline at end of file diff --git a/lib/puppet/functions/assert_type.rb b/lib/puppet/functions/assert_type.rb new file mode 100644 index 000000000..53d901af7 --- /dev/null +++ b/lib/puppet/functions/assert_type.rb @@ -0,0 +1,42 @@ +# Returns the given value if it is an instance of the given type, and raises an error otherwise. +# +# @example how to assert type +# # assert that `$b` is a non empty `String` and assign to `$a` +# $a = assert_type(String[1], $b) +# +# See the documentation for "The Puppet Type System" for more information about types. +# +Puppet::Functions.create_function(:assert_type) do + dispatch :assert_type do + param type_type(), 'type' + param optional(object()), 'value' + end + + dispatch :assert_type_s do + param String, 'type_string' + param optional(object()), 'value' + end + + # @param type [Type] the type the value must be an instance of + # @param value [Optional[Object]] the value to assert + # + def assert_type(type, value) + unless Puppet::Pops::Types::TypeCalculator.instance?(type,value) + inferred_type = Puppet::Pops::Types::TypeCalculator.infer(value) + # Do not give all the details - i.e. format as Integer, instead of Integer[n, n] for exact value, which + # is just confusing. (OTOH: may need to revisit, or provide a better "type diff" output. + # + actual = Puppet::Pops::Types::TypeCalculator.generalize!(inferred_type) + raise Puppet::ParseError, "assert_type(): Expected type #{type} does not match actual: #{actual}" + end + value + end + + # @param type_string [String] the type the value must be an instance of given in String form + # @param value [Optional[Object]] the value to assert + # + def assert_type_s(type_string, value) + t = Puppet::Pops::Types::TypeParser.new.parse(type_string) + assert_type(t, value) + end +end diff --git a/lib/puppet/loaders.rb b/lib/puppet/loaders.rb new file mode 100644 index 000000000..86bbc03c9 --- /dev/null +++ b/lib/puppet/loaders.rb @@ -0,0 +1,20 @@ +module Puppet + module Pops + require 'puppet/pops/loaders' + + module Loader + require 'puppet/pops/loader/loader' + require 'puppet/pops/loader/base_loader' + require 'puppet/pops/loader/gem_support' + require 'puppet/pops/loader/module_loaders' + require 'puppet/pops/loader/dependency_loader' + require 'puppet/pops/loader/null_loader' + require 'puppet/pops/loader/static_loader' + require 'puppet/pops/loader/puppet_function_instantiator' + require 'puppet/pops/loader/ruby_function_instantiator' + require 'puppet/pops/loader/ruby_legacy_function_instantiator' + require 'puppet/pops/loader/loader_paths' + end + end + +end \ No newline at end of file diff --git a/lib/puppet/parser/compiler.rb b/lib/puppet/parser/compiler.rb index b85deb94a..1985d507a 100644 --- a/lib/puppet/parser/compiler.rb +++ b/lib/puppet/parser/compiler.rb @@ -1,555 +1,572 @@ require 'forwardable' require 'puppet/node' require 'puppet/resource/catalog' require 'puppet/util/errors' require 'puppet/resource/type_collection_helper' # Maintain a graph of scopes, along with a bunch of data # about the individual catalog we're compiling. class Puppet::Parser::Compiler extend Forwardable include Puppet::Util include Puppet::Util::Errors include Puppet::Util::MethodHelper include Puppet::Resource::TypeCollectionHelper def self.compile(node) $env_module_directories = nil node.environment.check_for_reparse new(node).compile.to_resource rescue => detail message = "#{detail} on node #{node.name}" Puppet.log_exception(detail, message) raise Puppet::Error, message, detail.backtrace end attr_reader :node, :facts, :collections, :catalog, :resources, :relationships, :topscope # The injector that provides lookup services, or nil if accessed before the compiler has started compiling and # bootstrapped. The injector is initialized and available before any manifests are evaluated. # # @return [Puppet::Pops::Binder::Injector, nil] The injector that provides lookup services for this compiler/environment # @api public # attr_accessor :injector # The injector that provides lookup services during the creation of the {#injector}. # @return [Puppet::Pops::Binder::Injector, nil] The injector that provides lookup services during injector creation # for this compiler/environment # # @api private # attr_accessor :boot_injector # Add a collection to the global list. def_delegator :@collections, :<<, :add_collection def_delegator :@relationships, :<<, :add_relationship # Store a resource override. def add_override(override) # If possible, merge the override in immediately. if resource = @catalog.resource(override.ref) resource.merge(override) else # Otherwise, store the override for later; these # get evaluated in Resource#finish. @resource_overrides[override.ref] << override end end def add_resource(scope, resource) @resources << resource # Note that this will fail if the resource is not unique. @catalog.add_resource(resource) if not resource.class? and resource[:stage] raise ArgumentError, "Only classes can set 'stage'; normal resources like #{resource} cannot change run stage" end # Stages should not be inside of classes. They are always a # top-level container, regardless of where they appear in the # manifest. return if resource.stage? # This adds a resource to the class it lexically appears in in the # manifest. unless resource.class? return @catalog.add_edge(scope.resource, resource) end end # Do we use nodes found in the code, vs. the external node sources? def_delegator :known_resource_types, :nodes?, :ast_nodes? # Store the fact that we've evaluated a class def add_class(name) @catalog.add_class(name) unless name == "" end # Return a list of all of the defined classes. def_delegator :@catalog, :classes, :classlist # Compiler our catalog. This mostly revolves around finding and evaluating classes. # This is the main entry into our catalog. def compile Puppet.override({ :current_environment => environment }, "For compiling #{node.name}") do # Set the client's parameters into the top scope. Puppet::Util::Profiler.profile("Compile: Set node parameters") { set_node_parameters } Puppet::Util::Profiler.profile("Compile: Created settings scope") { create_settings_scope } if is_binder_active? Puppet::Util::Profiler.profile("Compile: Created injector") { create_injector } end - Puppet::Util::Profiler.profile("Compile: Evaluated main") { evaluate_main } + Puppet.override(context_overrides4x) do - Puppet::Util::Profiler.profile("Compile: Evaluated AST node") { evaluate_ast_node } + Puppet::Util::Profiler.profile("Compile: Evaluated main") { evaluate_main } - Puppet::Util::Profiler.profile("Compile: Evaluated node classes") { evaluate_node_classes } + Puppet::Util::Profiler.profile("Compile: Evaluated AST node") { evaluate_ast_node } - Puppet::Util::Profiler.profile("Compile: Evaluated generators") { evaluate_generators } + Puppet::Util::Profiler.profile("Compile: Evaluated node classes") { evaluate_node_classes } - Puppet::Util::Profiler.profile("Compile: Finished catalog") { finish } + Puppet::Util::Profiler.profile("Compile: Evaluated generators") { evaluate_generators } - fail_on_unevaluated + Puppet::Util::Profiler.profile("Compile: Finished catalog") { finish } - @catalog + fail_on_unevaluated + + @catalog + end + end + end + + # Constructs the overrides for the context + def context_overrides4x() + if Puppet[:parser] == 'future' + require 'puppet/loaders' + { + :global_scope => {}, # 4x placeholder for new global scope + :loaders => Puppet::Pops::Loaders.new(), # 4x loaders + :injector => injector() # 4x API - via context instead of via compiler + } + else + {} end end def_delegator :@collections, :delete, :delete_collection # Return the node's environment. def environment unless node.environment.is_a? Puppet::Node::Environment raise Puppet::DevError, "node #{node} has an invalid environment!" end node.environment end # Evaluate all of the classes specified by the node. # Classes with parameters are evaluated as if they were declared. # Classes without parameters or with an empty set of parameters are evaluated # as if they were included. This means classes with an empty set of # parameters won't conflict even if the class has already been included. def evaluate_node_classes if @node.classes.is_a? Hash classes_with_params, classes_without_params = @node.classes.partition {|name,params| params and !params.empty?} # The results from Hash#partition are arrays of pairs rather than hashes, # so we have to convert to the forms evaluate_classes expects (Hash, and # Array of class names) classes_with_params = Hash[classes_with_params] classes_without_params.map!(&:first) else classes_with_params = {} classes_without_params = @node.classes end evaluate_classes(classes_without_params, @node_scope || topscope) evaluate_classes(classes_with_params, @node_scope || topscope) end # Evaluate each specified class in turn. If there are any classes we can't # find, raise an error. This method really just creates resource objects # that point back to the classes, and then the resources are themselves # evaluated later in the process. # # Sometimes we evaluate classes with a fully qualified name already, in which # case, we tell scope.find_hostclass we've pre-qualified the name so it # doesn't need to search its namespaces again. This gets around a weird # edge case of duplicate class names, one at top scope and one nested in our # namespace and the wrong one (or both!) getting selected. See ticket #13349 # for more detail. --jeffweiss 26 apr 2012 def evaluate_classes(classes, scope, lazy_evaluate = true, fqname = false) raise Puppet::DevError, "No source for scope passed to evaluate_classes" unless scope.source class_parameters = nil # if we are a param class, save the classes hash # and transform classes to be the keys if classes.class == Hash class_parameters = classes classes = classes.keys end classes.each do |name| # If we can find the class, then make a resource that will evaluate it. if klass = scope.find_hostclass(name, :assume_fqname => fqname) # If parameters are passed, then attempt to create a duplicate resource # so the appropriate error is thrown. if class_parameters resource = klass.ensure_in_catalog(scope, class_parameters[name] || {}) else next if scope.class_scope(klass) resource = klass.ensure_in_catalog(scope) end # If they've disabled lazy evaluation (which the :include function does), # then evaluate our resource immediately. resource.evaluate unless lazy_evaluate else raise Puppet::Error, "Could not find class #{name} for #{node.name}" end end end def evaluate_relationships @relationships.each { |rel| rel.evaluate(catalog) } end # Return a resource by either its ref or its type and title. def_delegator :@catalog, :resource, :findresource def initialize(node, options = {}) @node = node set_options(options) initvars end # Create a new scope, with either a specified parent scope or # using the top scope. def newscope(parent, options = {}) parent ||= topscope scope = Puppet::Parser::Scope.new(self, options) scope.parent = parent scope end # Return any overrides for the given resource. def resource_overrides(resource) @resource_overrides[resource.ref] end def injector create_injector if @injector.nil? @injector end def boot_injector create_boot_injector(nil) if @boot_injector.nil? @boot_injector end # Creates the boot injector from registered system, default, and injector config. # @return [Puppet::Pops::Binder::Injector] the created boot injector # @api private Cannot be 'private' since it is called from the BindingsComposer. # def create_boot_injector(env_boot_bindings) assert_binder_active() pb = Puppet::Pops::Binder boot_contribution = pb::SystemBindings.injector_boot_contribution(env_boot_bindings) final_contribution = pb::SystemBindings.final_contribution binder = pb::Binder.new(pb::BindingsFactory.layered_bindings(final_contribution, boot_contribution)) @boot_injector = pb::Injector.new(binder) end # Answers if Puppet Binder should be active or not, and if it should and is not active, then it is activated. # @return [Boolean] true if the Puppet Binder should be activated def is_binder_active? should_be_active = Puppet[:binder] || Puppet[:parser] == 'future' if should_be_active # TODO: this should be in a central place, not just for ParserFactory anymore... Puppet::Parser::ParserFactory.assert_rgen_installed() @@binder_loaded ||= false unless @@binder_loaded require 'puppet/pops' require 'puppetx' @@binder_loaded = true end end should_be_active end private # If ast nodes are enabled, then see if we can find and evaluate one. def evaluate_ast_node return unless ast_nodes? # Now see if we can find the node. astnode = nil @node.names.each do |name| break if astnode = known_resource_types.node(name.to_s.downcase) end unless (astnode ||= known_resource_types.node("default")) raise Puppet::ParseError, "Could not find default node or by name with '#{node.names.join(", ")}'" end # Create a resource to model this node, and then add it to the list # of resources. resource = astnode.ensure_in_catalog(topscope) resource.evaluate @node_scope = topscope.class_scope(astnode) end # Evaluate our collections and return true if anything returned an object. # The 'true' is used to continue a loop, so it's important. def evaluate_collections return false if @collections.empty? exceptwrap do # We have to iterate over a dup of the array because # collections can delete themselves from the list, which # changes its length and causes some collections to get missed. Puppet::Util::Profiler.profile("Evaluated collections") do found_something = false @collections.dup.each do |collection| found_something = true if collection.evaluate end found_something end end end # Make sure all of our resources have been evaluated into native resources. # We return true if any resources have, so that we know to continue the # evaluate_generators loop. def evaluate_definitions exceptwrap do Puppet::Util::Profiler.profile("Evaluated definitions") do !unevaluated_resources.each do |resource| Puppet::Util::Profiler.profile("Evaluated resource #{resource}") do resource.evaluate end end.empty? end end end # Iterate over collections and resources until we're sure that the whole # compile is evaluated. This is necessary because both collections # and defined resources can generate new resources, which themselves could # be defined resources. def evaluate_generators count = 0 loop do done = true Puppet::Util::Profiler.profile("Iterated (#{count + 1}) on generators") do # Call collections first, then definitions. done = false if evaluate_collections done = false if evaluate_definitions end break if done count += 1 if count > 1000 raise Puppet::ParseError, "Somehow looped more than 1000 times while evaluating host catalog" end end end # Find and evaluate our main object, if possible. def evaluate_main @main = known_resource_types.find_hostclass([""], "") || known_resource_types.add(Puppet::Resource::Type.new(:hostclass, "")) @topscope.source = @main @main_resource = Puppet::Parser::Resource.new("class", :main, :scope => @topscope, :source => @main) @topscope.resource = @main_resource add_resource(@topscope, @main_resource) @main_resource.evaluate end # Make sure the entire catalog is evaluated. def fail_on_unevaluated fail_on_unevaluated_overrides fail_on_unevaluated_resource_collections end # If there are any resource overrides remaining, then we could # not find the resource they were supposed to override, so we # want to throw an exception. def fail_on_unevaluated_overrides remaining = @resource_overrides.values.flatten.collect(&:ref) if !remaining.empty? fail Puppet::ParseError, "Could not find resource(s) #{remaining.join(', ')} for overriding" end end # Make sure we don't have any remaining collections that specifically # look for resources, because we want to consider those to be # parse errors. def fail_on_unevaluated_resource_collections remaining = @collections.collect(&:resources).flatten.compact if !remaining.empty? raise Puppet::ParseError, "Failed to realize virtual resources #{remaining.join(', ')}" end end # Make sure all of our resources and such have done any last work # necessary. def finish evaluate_relationships resources.each do |resource| # Add in any resource overrides. if overrides = resource_overrides(resource) overrides.each do |over| resource.merge(over) end # Remove the overrides, so that the configuration knows there # are none left. overrides.clear end resource.finish if resource.respond_to?(:finish) end add_resource_metaparams end def add_resource_metaparams unless main = catalog.resource(:class, :main) raise "Couldn't find main" end names = Puppet::Type.metaparams.select do |name| !Puppet::Parser::Resource.relationship_parameter?(name) end data = {} catalog.walk(main, :out) do |source, target| if source_data = data[source] || metaparams_as_data(source, names) # only store anything in the data hash if we've actually got # data data[source] ||= source_data source_data.each do |param, value| target[param] = value if target[param].nil? end data[target] = source_data.merge(metaparams_as_data(target, names)) end target.tag(*(source.tags)) end end def metaparams_as_data(resource, params) data = nil params.each do |param| unless resource[param].nil? # Because we could be creating a hash for every resource, # and we actually probably don't often have any data here at all, # we're optimizing a bit by only creating a hash if there's # any data to put in it. data ||= {} data[param] = resource[param] end end data end # Set up all of our internal variables. def initvars # The list of overrides. This is used to cache overrides on objects # that don't exist yet. We store an array of each override. @resource_overrides = Hash.new do |overs, ref| overs[ref] = [] end # The list of collections that have been created. This is a global list, # but they each refer back to the scope that created them. @collections = [] # The list of relationships to evaluate. @relationships = [] # For maintaining the relationship between scopes and their resources. @catalog = Puppet::Resource::Catalog.new(@node.name) @catalog.version = known_resource_types.version @catalog.environment = @node.environment.to_s # Create our initial scope and a resource that will evaluate main. @topscope = Puppet::Parser::Scope.new(self) @catalog.add_resource(Puppet::Parser::Resource.new("stage", :main, :scope => @topscope)) # local resource array to maintain resource ordering @resources = [] # Make sure any external node classes are in our class list if @node.classes.class == Hash @catalog.add_class(*@node.classes.keys) else @catalog.add_class(*@node.classes) end end # Set the node's parameters into the top-scope as variables. def set_node_parameters node.parameters.each do |param, value| @topscope[param.to_s] = value end # These might be nil. catalog.client_version = node.parameters["clientversion"] catalog.server_version = node.parameters["serverversion"] if Puppet[:trusted_node_data] @topscope.set_trusted(node.trusted_data) end if(Puppet[:immutable_node_data]) facts_hash = node.facts.nil? ? {} : node.facts.values @topscope.set_facts(facts_hash) end end def create_settings_scope unless settings_type = environment.known_resource_types.hostclass("settings") settings_type = Puppet::Resource::Type.new :hostclass, "settings" environment.known_resource_types.add(settings_type) end settings_resource = Puppet::Parser::Resource.new("class", "settings", :scope => @topscope) @catalog.add_resource(settings_resource) settings_type.evaluate_code(settings_resource) scope = @topscope.class_scope(settings_type) Puppet.settings.each do |name, setting| next if name.to_s == "name" scope[name.to_s] = environment[name] end end # Return an array of all of the unevaluated resources. These will be definitions, # which need to get evaluated into native resources. def unevaluated_resources # The order of these is significant for speed due to short-circuting resources.reject { |resource| resource.evaluated? or resource.virtual? or resource.builtin_type? } end # Creates the injector from bindings found in the current environment. # @return [void] # @api private # def create_injector assert_binder_active() composer = Puppet::Pops::Binder::BindingsComposer.new() layered_bindings = composer.compose(topscope) @injector = Puppet::Pops::Binder::Injector.new(Puppet::Pops::Binder::Binder.new(layered_bindings)) end def assert_binder_active unless is_binder_active? raise ArgumentError, "The Puppet Binder is only available when either '--binder true' or '--parser future' is used" end end end diff --git a/lib/puppet/parser/functions.rb b/lib/puppet/parser/functions.rb index d7ea3e0a9..87bf5de1f 100644 --- a/lib/puppet/parser/functions.rb +++ b/lib/puppet/parser/functions.rb @@ -1,249 +1,255 @@ require 'puppet/util/autoload' require 'puppet/parser/scope' # A module for managing parser functions. Each specified function # is added to a central module that then gets included into the Scope # class. # # @api public module Puppet::Parser::Functions Environment = Puppet::Node::Environment class << self include Puppet::Util end # Reset the list of loaded functions. # # @api private def self.reset @functions = Hash.new { |h,k| h[k] = {} } @modules = Hash.new # Runs a newfunction to create a function for each of the log levels Puppet::Util::Log.levels.each do |level| newfunction(level, :environment => Puppet.lookup(:root_environment), :doc => "Log a message on the server at level #{level.to_s}.") do |vals| send(level, vals.join(" ")) end end end # Accessor for singleton autoloader # # @api private def self.autoloader @autoloader ||= Puppet::Util::Autoload.new( self, "puppet/parser/functions", :wrap => false ) end # Get the module that functions are mixed into corresponding to an # environment # # @api private def self.environment_module(env) @modules[env.name] ||= Module.new end # Create a new Puppet DSL function. # # **The {newfunction} method provides a public API.** # # This method is used both internally inside of Puppet to define parser # functions. For example, template() is defined in # {file:lib/puppet/parser/functions/template.rb template.rb} using the # {newfunction} method. Third party Puppet modules such as # [stdlib](https://forge.puppetlabs.com/puppetlabs/stdlib) use this method to # extend the behavior and functionality of Puppet. # # See also [Docs: Custom # Functions](http://docs.puppetlabs.com/guides/custom_functions.html) # # @example Define a new Puppet DSL Function # >> Puppet::Parser::Functions.newfunction(:double, :arity => 1, # :doc => "Doubles an object, typically a number or string.", # :type => :rvalue) {|i| i[0]*2 } # => {:arity=>1, :type=>:rvalue, # :name=>"function_double", # :doc=>"Doubles an object, typically a number or string."} # # @example Invoke the double function from irb as is done in RSpec examples: # >> require 'puppet_spec/scope' # >> scope = PuppetSpec::Scope.create_test_scope_for_node('example') # => Scope() # >> scope.function_double([2]) # => 4 # >> scope.function_double([4]) # => 8 # >> scope.function_double([]) # ArgumentError: double(): Wrong number of arguments given (0 for 1) # >> scope.function_double([4,8]) # ArgumentError: double(): Wrong number of arguments given (2 for 1) # >> scope.function_double(["hello"]) # => "hellohello" # # @param [Symbol] name the name of the function represented as a ruby Symbol. # The {newfunction} method will define a Ruby method based on this name on # the parser scope instance. # # @param [Proc] block the block provided to the {newfunction} method will be # executed when the Puppet DSL function is evaluated during catalog # compilation. The arguments to the function will be passed as an array to # the first argument of the block. The return value of the block will be # the return value of the Puppet DSL function for `:rvalue` functions. # # @option options [:rvalue, :statement] :type (:statement) the type of function. # Either `:rvalue` for functions that return a value, or `:statement` for # functions that do not return a value. # # @option options [String] :doc ('') the documentation for the function. # This string will be extracted by documentation generation tools. # # @option options [Integer] :arity (-1) the # [arity](http://en.wikipedia.org/wiki/Arity) of the function. When # specified as a positive integer the function is expected to receive # _exactly_ the specified number of arguments. When specified as a # negative number, the function is expected to receive _at least_ the # absolute value of the specified number of arguments incremented by one. # For example, a function with an arity of `-4` is expected to receive at # minimum 3 arguments. A function with the default arity of `-1` accepts # zero or more arguments. A function with an arity of 2 must be provided # with exactly two arguments, no more and no less. Added in Puppet 3.1.0. # # @option options [Puppet::Node::Environment] :environment (nil) can # explicitly pass the environment we wanted the function added to. Only used # to set logging functions in root environment # # @return [Hash] describing the function. # # @api public def self.newfunction(name, options = {}, &block) + # Short circuit this call when 4x "biff" is in effect to allow the new loader system to load + # and define the function a different way. + # + if Puppet[:biff] + return Puppet::Pops::Loader::RubyLegacyFunctionInstantiator.legacy_newfunction(name, options, &block) + end name = name.intern environment = options[:environment] || Puppet.lookup(:current_environment) Puppet.warning "Overwriting previous definition for function #{name}" if get_function(name, environment) arity = options[:arity] || -1 ftype = options[:type] || :statement unless ftype == :statement or ftype == :rvalue raise Puppet::DevError, "Invalid statement type #{ftype.inspect}" end # the block must be installed as a method because it may use "return", # which is not allowed from procs. real_fname = "real_function_#{name}" environment_module(environment).send(:define_method, real_fname, &block) fname = "function_#{name}" environment_module(environment).send(:define_method, fname) do |*args| Puppet::Util::Profiler.profile("Called #{name}") do if args[0].is_a? Array if arity >= 0 and args[0].size != arity raise ArgumentError, "#{name}(): Wrong number of arguments given (#{args[0].size} for #{arity})" elsif arity < 0 and args[0].size < (arity+1).abs raise ArgumentError, "#{name}(): Wrong number of arguments given (#{args[0].size} for minimum #{(arity+1).abs})" end self.send(real_fname, args[0]) else raise ArgumentError, "custom functions must be called with a single array that contains the arguments. For example, function_example([1]) instead of function_example(1)" end end end func = {:arity => arity, :type => ftype, :name => fname} func[:doc] = options[:doc] if options[:doc] add_function(name, func, environment) func end # Determine if a function is defined # # @param [Symbol] name the function # @param [Puppet::Node::Environment] environment the environment to find the function in # # @return [Symbol, false] The name of the function if it's defined, # otherwise false. # # @api public def self.function(name, environment = Puppet.lookup(:current_environment)) name = name.intern func = nil unless func = get_function(name, environment) autoloader.load(name, environment) func = get_function(name, environment) end if func func[:name] else false end end def self.functiondocs(environment = Puppet.lookup(:current_environment)) autoloader.loadall ret = "" merged_functions(environment).sort { |a,b| a[0].to_s <=> b[0].to_s }.each do |name, hash| ret << "#{name}\n#{"-" * name.to_s.length}\n" if hash[:doc] ret << Puppet::Util::Docs.scrub(hash[:doc]) else ret << "Undocumented.\n" end ret << "\n\n- *Type*: #{hash[:type]}\n\n" end ret end # Determine whether a given function returns a value. # # @param [Symbol] name the function # @param [Puppet::Node::Environment] environment The environment to find the function in # @return [Boolean] whether it is an rvalue function # # @api public def self.rvalue?(name, environment = Puppet.lookup(:current_environment)) func = get_function(name, environment) func ? func[:type] == :rvalue : false end # Return the number of arguments a function expects. # # @param [Symbol] name the function # @param [Puppet::Node::Environment] environment The environment to find the function in # @return [Integer] The arity of the function. See {newfunction} for # the meaning of negative values. # # @api public def self.arity(name, environment = Puppet.lookup(:current_environment)) func = get_function(name, environment) func ? func[:arity] : -1 end class << self private def merged_functions(environment) @functions[Puppet.lookup(:root_environment)].merge(@functions[environment]) end def get_function(name, environment) name = name.intern merged_functions(environment)[name] end def add_function(name, func, environment) name = name.intern @functions[environment][name] = func end end end diff --git a/lib/puppet/pops/evaluator/runtime3_support.rb b/lib/puppet/pops/evaluator/runtime3_support.rb index 8c92c315a..697223336 100644 --- a/lib/puppet/pops/evaluator/runtime3_support.rb +++ b/lib/puppet/pops/evaluator/runtime3_support.rb @@ -1,489 +1,503 @@ # A module with bindings between the new evaluator and the 3x runtime. # The intention is to separate all calls into scope, compiler, resource, etc. in this module # to make it easier to later refactor the evaluator for better implementations of the 3x classes. # # @api private module Puppet::Pops::Evaluator::Runtime3Support # Fails the evaluation of _semantic_ with a given issue. # # @param issue [Puppet::Pops::Issue] the issue to report # @param semantic [Puppet::Pops::ModelPopsObject] the object for which evaluation failed in some way. Used to determine origin. # @param options [Hash] hash of optional named data elements for the given issue # @return [!] this method does not return # @raise [Puppet::ParseError] an evaluation error initialized from the arguments (TODO: Change to EvaluationError?) # def fail(issue, semantic, options={}, except=nil) if except.nil? # Want a stacktrace, and it must be passed as an exception begin raise EvaluationError.new() rescue EvaluationError => e except = e end end diagnostic_producer.accept(issue, semantic, options, except) end # Binds the given variable name to the given value in the given scope. # The reference object `o` is intended to be used for origin information - the 3x scope implementation # only makes use of location when there is an error. This is now handled by other mechanisms; first a check # is made if a variable exists and an error is raised if attempting to change an immutable value. Errors # in name, numeric variable assignment etc. have also been validated prior to this call. In the event the # scope.setvar still raises an error, the general exception handling for evaluation of the assignment # expression knows about its location. Because of this, there is no need to extract the location for each # setting (extraction is somewhat expensive since 3x requires line instead of offset). # def set_variable(name, value, o, scope) # Scope also checks this but requires that location information are passed as options. # Those are expensive to calculate and a test is instead made here to enable failing with better information. # The error is not specific enough to allow catching it - need to check the actual message text. # TODO: Improve the messy implementation in Scope. # if scope.bound?(name) if Puppet::Parser::Scope::RESERVED_VARIABLE_NAMES.include?(name) fail(Puppet::Pops::Issues::ILLEGAL_RESERVED_ASSIGNMENT, o, {:name => name} ) else fail(Puppet::Pops::Issues::ILLEGAL_REASSIGNMENT, o, {:name => name} ) end end scope.setvar(name, value) end # Returns the value of the variable (nil is returned if variable has no value, or if variable does not exist) # def get_variable_value(name, o, scope) # Puppet 3x stores all variables as strings (then converts them back to numeric with a regexp... to see if it is a match variable) # Not ideal, scope should support numeric lookup directly instead. # TODO: consider fixing scope catch(:undefined_variable) { return scope.lookupvar(name.to_s) } # It is always ok to reference numeric variables even if they are not assigned. They are always undef # if not set by a match expression. # unless name =~ Puppet::Pops::Patterns::NUMERIC_VAR_NAME fail(Puppet::Pops::Issues::UNKNOWN_VARIABLE, o, {:name => name}) end end # Returns true if the variable of the given name is set in the given most nested scope. True is returned even if # variable is bound to nil. # def variable_bound?(name, scope) scope.bound?(name.to_s) end # Returns true if the variable is bound to a value or nil, in the scope or it's parent scopes. # def variable_exists?(name, scope) scope.exist?(name.to_s) end def set_match_data(match_data, o, scope) # See set_variable for rationale for not passing file and line to ephemeral_from. # NOTE: The 3x scope adds one ephemeral(match) to its internal stack per match that succeeds ! It never # clears anything. Thus a context that performs many matches will get very deep (there simply is no way to # clear the match variables without rolling back the ephemeral stack.) # This implementation does not attempt to fix this, it behaves the same bad way. unless match_data.nil? scope.ephemeral_from(match_data) end end # Creates a local scope with vairalbes set from a hash of variable name to value # def create_local_scope_from(hash, scope) # two dummy values are needed since the scope tries to give an error message (can not happen in this # case - it is just wrong, the error should be reported by the caller who knows in more detail where it # is in the source. # raise ArgumentError, "Internal error - attempt to create a local scope without a hash" unless hash.is_a?(Hash) scope.ephemeral_from(hash) end # Creates a nested match scope def create_match_scope_from(scope) # Create a transparent match scope (for future matches) scope.new_match_scope(nil) end def get_scope_nesting_level(scope) scope.ephemeral_level end def set_scope_nesting_level(scope, level) # Yup, 3x uses this method to reset the level, it also supports passing :all to destroy all # ephemeral/local scopes - which is a sure way to create havoc. # scope.unset_ephemeral_var(level) end # Adds a relationship between the given `source` and `target` of the given `relationship_type` # @param source [Puppet:Pops::Types::PCatalogEntryType] the source end of the relationship (from) # @param target [Puppet:Pops::Types::PCatalogEntryType] the target end of the relationship (to) # @param relationship_type [:relationship, :subscription] the type of the relationship # def add_relationship(source, target, relationship_type, scope) # The 3x way is to record a Puppet::Parser::Relationship that is evaluated at the end of the compilation. # This means it is not possible to detect any duplicates at this point (and signal where an attempt is made to # add a duplicate. There is also no location information to signal the original place in the logic. The user will have # to go fish. # The 3.x implementation is based on Strings :-o, so the source and target must be transformed. The resolution is # done by Catalog#resource(type, title). To do that, it creates a Puppet::Resource since it is responsible for # translating the name/type/title and create index-keys used by the catalog. The Puppet::Resource has bizarre parsing of # the type and title (scan for [] that is interpreted as type/title (but it gets it wrong). # Moreover if the type is "" or "component", the type is Class, and if the type is :main, it is :main, all other cases # undergo capitalization of name-segments (foo::bar becomes Foo::Bar). (This was earlier done in the reverse by the parser). # Further, the title undergoes the same munging !!! # # That bug infested nest of messy logic needs serious Exorcism! # # Unfortunately it is not easy to simply call more intelligent methods at a lower level as the compiler evaluates the recorded # Relationship object at a much later point, and it is responsible for invoking all the messy logic. # # TODO: Revisit the below logic when there is a sane implementation of the catalog, compiler and resource. For now # concentrate on transforming the type references to what is expected by the wacky logic. # # HOWEVER, the Compiler only records the Relationships, and the only method it calls is @relationships.each{|x| x.evaluate(catalog) } # Which means a smarter Relationship class could do this right. Instead of obtaining the resource from the catalog using # the borked resource(type, title) which creates a resource for the purpose of looking it up, it needs to instead # scan the catalog's resources # # GAAAH, it is even worse! # It starts in the parser, which parses "File['foo']" into an AST::ResourceReference with type = File, and title = foo # This AST is evaluated by looking up the type/title in the scope - causing it to be loaded if it exists, and if not, the given # type name/title is used. It does not search for resource instances, only classes and types. It returns symbolic information # [type, [title, title]]. From this, instances of Puppet::Resource are created and returned. These only have type/title information # filled out. One or an array of resources are returned. # This set of evaluated (empty reference) Resource instances are then passed to the relationship operator. It creates a # Puppet::Parser::Relationship giving it a source and a target that are (empty reference) Resource instances. These are then remembered # until the relationship is evaluated by the compiler (at the end). When evaluation takes place, the (empty reference) Resource instances # are converted to String (!?! WTF) on the simple format "#{type}[#{title}]", and the catalog is told to find a resource, by giving # it this string. If it cannot find the resource it fails, else the before/notify parameter is appended with the target. # The search for the resource begin with (you guessed it) again creating an (empty reference) resource from type and title (WTF?!?!). # The catalog now uses the reference resource to compute a key [r.type, r.title.to_s] and also gets a uniqueness key from the # resource (This is only a reference type created from title and type). If it cannot find it with the first key, it uses the # uniqueness key to lookup. # # This is probably done to allow a resource type to munge/translate the title in some way (but it is quite unclear from the long # and convoluted path of evaluation. # In order to do this in a way that is similar to 3.x two resources are created to be used as keys. # # # TODO: logic that creates a PCatalogEntryType should resolve it to ensure it is loaded (to the best of known_resource_types knowledge). # If this is not done, the order in which things are done may be different? OTOH, it probably works anyway :-) # TODO: Not sure if references needs to be resolved via the scope? # # 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 # Asserts that the given function name resolves to an available function. The function is loaded # as a side effect. Fails if the function does not exist. # def assert_function_available(name, o, scope) + # Check first via 4x API (if it is available), and the function exists + if loaders = Puppet.lookup(:loaders) {nil} + if loaders && func = loaders.puppet_system_loader.load(:function, name) + return + end + end + fail(Puppet::Pops::Issues::UNKNOWN_FUNCTION, o, {:name => name}) unless Puppet::Parser::Functions.function(name) end def call_function(name, args, o, scope) + # Call via 4x API if it is available, and the function exists + if loaders = Puppet.lookup(:loaders) {nil} + if loaders && func = loaders.puppet_system_loader.load(:function, name) + return func.call(scope, *args) + end + end + # TODO: if Puppet[:biff] == true, then 3x functions should be called via loaders above # Arguments must be mapped since functions are unaware of the new and magical creatures in 4x. # NOTE: Passing an empty string last converts :undef to empty string mapped_args = args.map {|a| convert(a, scope, '') } scope.send("function_#{name}", mapped_args) end # Returns true if the function produces a value def rvalue_function?(name, o, scope) Puppet::Parser::Functions.rvalue?(name) end # The o is used for source reference def create_resource_parameter(o, scope, name, value, operator) file, line = extract_file_line(o) Puppet::Parser::Resource::Param.new( :name => name, :value => convert(value, scope, :undef), # converted to 3x since 4x supports additional objects / types :source => scope.source, :line => line, :file => file, :add => operator == :'+>' ) end def create_resources(o, scope, virtual, exported, type_name, resource_titles, evaluated_parameters) # TODO: Unknown resource causes creation of Resource to fail with ArgumentError, should give # a proper Issue. Now the result is "Error while evaluating a Resource Statement" with the message # from the raised exception. (It may be good enough). # resolve in scope. fully_qualified_type, resource_titles = scope.resolve_type_and_titles(type_name, resource_titles) # Not 100% accurate as this is the resource expression location and each title is processed separately # The titles are however the result of evaluation and they have no location at this point (an array # of positions for the source expressions are required for this to work). # TODO: Revisit and possible improve the accuracy. # file, line = extract_file_line(o) # Build a resource for each title resource_titles.map do |resource_title| resource = Puppet::Parser::Resource.new( fully_qualified_type, resource_title, :parameters => evaluated_parameters, :file => file, :line => line, :exported => exported, :virtual => virtual, # WTF is this? Which source is this? The file? The name of the context ? :source => scope.source, :scope => scope, :strict => true ) if resource.resource_type.is_a? Puppet::Resource::Type resource.resource_type.instantiate_resource(scope, resource) end scope.compiler.add_resource(scope, resource) scope.compiler.evaluate_classes([resource_title], scope, false, true) if fully_qualified_type == 'class' # Turn the resource into a PType (a reference to a resource type) # weed out nil's resource_to_ptype(resource) end end # Defines default parameters for a type with the given name. # def create_resource_defaults(o, scope, type_name, evaluated_parameters) # Note that name must be capitalized in this 3x call # The 3x impl creates a Resource instance with a bogus title and then asks the created resource # for the type of the name. # Note, locations are available per parameter. # scope.define_settings(type_name.capitalize, evaluated_parameters) end # Creates resource overrides for all resource type objects in evaluated_resources. The same set of # evaluated parameters are applied to all. # def create_resource_overrides(o, scope, evaluated_resources, evaluated_parameters) # Not 100% accurate as this is the resource expression location and each title is processed separately # The titles are however the result of evaluation and they have no location at this point (an array # of positions for the source expressions are required for this to work. # TODO: Revisit and possible improve the accuracy. # file, line = extract_file_line(o) evaluated_resources.each do |r| resource = Puppet::Parser::Resource.new( r.type_name, r.title, :parameters => evaluated_parameters, :file => file, :line => line, # WTF is this? Which source is this? The file? The name of the context ? :source => scope.source, :scope => scope ) scope.compiler.add_override(resource) end end # Finds a resource given a type and a title. # def find_resource(scope, type_name, title) scope.compiler.findresource(type_name, title) end # Returns the value of a resource's parameter by first looking up the parameter in the resource # and then in the defaults for the resource. Since the resource exists (it must in order to look up its # parameters, any overrides have already been applied). Defaults are not applied to a resource until it # has been finished (which typically has not taked place when this is evaluated; hence the dual lookup). # def get_resource_parameter_value(scope, resource, parameter_name) val = resource[parameter_name] if val.nil? && defaults = scope.lookupdefaults(resource.type) # NOTE: 3x resource keeps defaults as hash using symbol for name as key to Parameter which (again) holds # name and value. param = defaults[parameter_name.to_sym] val = param.value end val end # Returns true, if the given name is the name of a resource parameter. # def is_parameter_of_resource?(scope, resource, name) resource.valid_parameter?(name) end def resource_to_ptype(resource) nil if resource.nil? type_calculator.infer(resource) end # This is the same type of "truth" as used in the current Puppet DSL. # def is_true? o # Is the value true? This allows us to control the definition of truth # in one place. case o when '' false when :undef false else !!o end end # Utility method for TrueClass || FalseClass # @param x [Object] the object to test if it is instance of TrueClass or FalseClass def is_boolean? x x.is_a?(TrueClass) || x.is_a?(FalseClass) end def initialize @@convert_visitor ||= Puppet::Pops::Visitor.new(self, "convert", 2, 2) end # Converts 4x supported values to 3x values. This is required because # resources and other objects do not know about the new type system, and does not support # regular expressions. Unfortunately this has to be done for array and hash as well. # A complication is that catalog types needs to be resolved against the scope. # def convert(o, scope, undef_value) @@convert_visitor.visit_this_2(self, o, scope, undef_value) end def convert_NilClass(o, scope, undef_value) undef_value end def convert_Object(o, scope, undef_value) o end def convert_Array(o, scope, undef_value) o.map {|x| convert(x, scope, undef_value) } end def convert_Hash(o, scope, undef_value) result = {} o.each {|k,v| result[convert(k, scope, undef_value)] = convert(v, scope, undef_value) } result end def convert_Regexp(o, scope, undef_value) # Puppet 3x cannot handle parameter values that are reqular expressions. Turn into regexp string in # source form o.inspect end def convert_Symbol(o, scope, undef_value) case o when :undef undef_value # 3x wants :undef as empty string in function else o # :default, and all others are verbatim since they are new in future evaluator end end def convert_PAbstractType(o, scope, undef_value) o end def convert_PResourceType(o,scope, undef_value) # Needs conversion by calling scope to resolve the name and possibly return a different name # Resolution can only be called with an array, and returns an array. Here there is only one name type, titles = scope.resolve_type_and_titles(o.type_name, [o.title]) Puppet::Resource.new(type, titles[0]) end def convert_PHostClassType(o, scope, undef_value) # Needs conversion by calling scope to resolve the name and possibly return a different name # Resolution can only be called with an array, and returns an array. Here there is only one name type, titles = scope.resolve_type_and_titles('class', [o.class_name]) Puppet::Resource.new(type, titles[0]) end private # Produces an array with [type, title] from a PCatalogEntryType # Used to produce reference resource instances (used when 3x is operating on a resource). # def catalog_type_to_split_type_title(catalog_type) case catalog_type when Puppet::Pops::Types::PHostClassType return ['Class', catalog_type.class_name] when Puppet::Pops::Types::PResourceType return [catalog_type.type_name, catalog_type.title] else raise ArgumentError, "Cannot split the type #{catalog_type.class}, it is neither a PHostClassType, nor a PResourceClass." 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 Puppet::Pops::Validation::SeverityProducer.new(), # All issues are errors Puppet::Pops::Model::ModelLabelProvider.new()) 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:" }) raise ArgumentError, "Internal Error: Configuration of runtime error handling wrong: should have raised exception" end end class EvaluationError < StandardError end end diff --git a/lib/puppet/pops/loader/base_loader.rb b/lib/puppet/pops/loader/base_loader.rb new file mode 100644 index 000000000..c8f732367 --- /dev/null +++ b/lib/puppet/pops/loader/base_loader.rb @@ -0,0 +1,96 @@ +# 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 + + # 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_redefined(entry); end + @named_values[typed_name] = named_entry + end + + private + + def fail_redefine(entry) + 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_label(origin)}.#{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 + nil + 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/loader/dependency_loader.rb b/lib/puppet/pops/loader/dependency_loader.rb new file mode 100644 index 000000000..3b0a0f259 --- /dev/null +++ b/lib/puppet/pops/loader/dependency_loader.rb @@ -0,0 +1,60 @@ +# =DependencyLoader +# This loader provides visibility into a set of other loaders. It is used as a child of a ModuleLoader (or other +# loader) to make its direct dependencies visible for loading from contexts that have access to this dependency loader. +# Access is typically given to logic that resides inside of the module, but not to those that just depend on the module. +# +# It is instantiated with a name, and with a set of dependency_loaders. +# +# @api private +# +class Puppet::Pops::Loader::DependencyLoader < Puppet::Pops::Loader::BaseLoader + + # An index of module_name to module loader used to speed up lookup of qualified names + attr_reader :index + + # Creates a DependencyLoader for one parent loader + # + # @param parent_loader [Puppet::Pops::Loader] typically a module loader for the root + # @param name [String] the name of the dependency-loader (used for debugging and tracing only) + # @param depedency_loaders [Array] array of loaders for modules this module depends on + # + def initialize(parent_loader, name, dependency_loaders) + super parent_loader, name + @dependency_loaders = dependency_loaders + end + + # Finds name in a loader this loader depends on / can see + # + def find(typed_name) + if typed_name.qualified + if loader = index()[typed_name.name_parts[0]] + loader.load_typed(typed_name) + else + # no module entered as dependency with name matching first segment of wanted name + nil + end + else + # a non name-spaced name, have to search since it can be anywhere. + # (Note: superclass caches the result in this loader as it would have to repeat this search for every + # lookup otherwise). + loaded = @dependency_loaders.reduce(nil) do |previous, loader| + break previous if !previous.nil? + loader.load_typed(typed_name) + end + if loaded + promote_entry(loaded) + end + loaded + end + end + + def to_s() + "(DependencyLoader '#{@name}' [" + @dependency_loaders.map {|loader| loader.to_s }.join(' ,') + "])" + end + + private + + def index() + @index ||= @dependency_loaders.reduce({}) { |index, loader| index[loader.module_name] = loader; index } + end +end diff --git a/lib/puppet/pops/loader/gem_support.rb b/lib/puppet/pops/loader/gem_support.rb new file mode 100644 index 000000000..bf58244d0 --- /dev/null +++ b/lib/puppet/pops/loader/gem_support.rb @@ -0,0 +1,49 @@ +# GemSupport offers methods to find a gem's location by name or gem://gemname URI. +# +# TODO: The Puppet 3x, uses Puppet::Util::RubyGems to do this, and obtain paths, and avoids using ::Gems +# when ::Bundler is in effect. A quick check what happens on Ruby 1.8.7 and Ruby 1.9.3 with current +# version of bundler seems to work just fine without jumping through any hoops. Hopefully the Puppet::Utils::RubyGems is +# just dealing with arcane things prior to RubyGems 1.8 that are not needed any more. To verify there is +# the need to set up a scenario where additional bundles than what Bundler allows for a given configuration are available +# and then trying to access those. +# +module Puppet::Pops::Loader::GemSupport + + # Produces the root directory of a gem given as an URI (gem://gemname/optional/path), or just the + # gemname as a string. + # + def gem_dir(uri_or_string) + case uri_or_string + when URI + gem_dir_from_uri(uri_or_string) + when String + gem_dir_from_name(uri_or_string) + end + end + + # Produces the root directory of a gem given as an uri, where hostname is the gemname, and an optional + # path is appended to the root of the gem (i.e. if the reference is given to a sub-location within a gem. + # TODO: FIND by name raises exception Gem::LoadError with list of all gems on the path + # + def gem_dir_from_uri(uri) + unless spec = Gem::Specification.find_by_name(uri.hostname) + raise ArgumentError, "Gem not found #{uri}" + end + # if path given append that, else append given subdir + if uri.path.empty? + spec.gem_dir + else + File.join(spec.full_gem_path, uri.path) + end + end + + # Produces the root directory of a gem given as a string with the gem's name. + # TODO: FIND by name raises exception Gem::LoadError with list of all gems on the path + # + def gem_dir_from_name(gem_name) + unless spec = Gem::Specification.find_by_name(gem_name) + raise ArgumentError, "Gem not found '#{gem_name}'" + end + spec.full_gem_path + end +end \ No newline at end of file diff --git a/lib/puppet/pops/loader/loader.rb b/lib/puppet/pops/loader/loader.rb new file mode 100644 index 000000000..68d5cd17c --- /dev/null +++ b/lib/puppet/pops/loader/loader.rb @@ -0,0 +1,172 @@ +# Loader +# === +# A Loader is responsible for loading "entities" ("instantiable and executable objects in the puppet language" which +# are type, hostclass, definition, function, and bindings. +# +# The main method for users of a Loader is the `load` or `load_typed methods`, which returns a previously loaded entity +# of a given type/name, and searches and loads the entity if not already loaded. +# +# private entities +# --- +# TODO: handle loading of entities that are private. Suggest that all calls pass an origin_loader (the loader +# where request originated (or symbol :public). A module loader has one (or possibly a list) of what is +# considered to represent private loader - i.e. the dependency loader for a module. If an entity is private +# it should be stored with this status, and an error should be raised if the origin_loader is not on the list +# of accepted "private" loaders. +# The private loaders can not be given at creation time (they are parented by the loader in question). Another +# alternative is to check if the origin_loader is a child loader, but this requires bidirectional links +# between loaders or a search if loader with private entity is a parent of the origin_loader). +# +# @api public +# +class Puppet::Pops::Loader::Loader + + # Produces the value associated with the given name if already loaded, or available for loading + # by this loader, one of its parents, or other loaders visible to this loader. + # This is the method an external party should use to "get" the named element. + # + # An implementor of this method should first check if the given name is already loaded by self, or a parent + # loader, and if so return that result. If not, it should call `find` to perform the loading. + # + # @param type [:Symbol] the type to load + # @param name [String, Symbol] the name of the entity to load + # @return [Object, nil] the value or nil if not found + # + # @api public + # + def load(type, name) + if result = load_typed(TypedName.new(type, name)) + result.value + end + end + + # Loads the given typed name, and returns a NamedEntry if found, else returns nil. + # This the same a `load`, but returns a NamedEntry with origin/value information. + # + # @param typed_name [TypedName] - the type, name combination to lookup + # @return [NamedEntry, nil] the entry containing the loaded value, or nil if not found + # + # @api public + # + def load_typed(typed_name) + raise NotImplementedError.new + end + + # Produces the value associated with the given name if defined **in this loader**, or nil if not defined. + # This lookup does not trigger any loading, or search of the given name. + # An implementor of this method may not search or look up in any other loader, and it may not + # define the name. + # + # @param typed_name [TypedName] - the type, name combination to lookup + # + # @api private + # + def [] (typed_name) + if found = get_entry(typed_name) + found.value + else + nil + end + end + + # Searches for the given name in this loader's context (parents should already have searched their context(s) without + # producing a result when this method is called). + # An implementation of find typically caches the result. + # + # @param typed_name [TypedName] the type, name combination to lookup + # @return [NamedEntry, nil] the entry for the loaded entry, or nil if not found + # + # @api private + # + def find(typed_name) + raise NotImplementedError.new + end + + # Returns the parent of the loader, or nil, if this is the top most loader. This implementation returns nil. + def parent + nil + end + + # Binds a value to a name. The name should not start with '::', but may contain multiple segments. + # + # @param type [:Symbol] the type of the entity being set + # @param name [String, Symbol] the name of the entity being set + # @param origin [URI, #uri, String] the origin of the set entity, a URI, or provider of URI, or URI in string form + # @return [NamedEntry, nil] the created entry + # + # @api private + # + def set_entry(type, name, value, origin = nil) + raise NotImplementedError.new + end + + # Produces a NamedEntry if a value is bound to the given name, or nil if nothing is bound. + # + # @param typed_name [TypedName] the type, name combination to lookup + # @return [NamedEntry, nil] the value bound in an entry + # + # @api private + # + def get_entry(typed_name) + raise NotImplementedError.new + end + + # An entry for one entity loaded by the loader. + # + class NamedEntry + attr_reader :typed_name + attr_reader :value + attr_reader :origin + + def initialize(typed_name, value, origin) + @name = typed_name + @value = value + @origin = origin + freeze() + end + end + + # A name/type combination that can be used as a compound hash key + # + class TypedName + attr_reader :type + attr_reader :name + attr_reader :name_parts + + # True if name is qualified (more than a single segment) + attr_reader :qualified + + def initialize(type, name) + @type = type + # relativize the name (get rid of leading ::), and make the split string available + @name_parts = name.split(/::/) + @name_parts.shift if name_parts[0].empty? + @name = name_parts.join('::') + @qualified = name_parts.size > 1 + # precompute hash - the name is frozen, so this is safe to do + @hash = [self.class, type, @name].hash + + # Not allowed to have numeric names - 0, 010, 0x10, 1.2 etc + if Puppet::Pops::Utils.is_numeric?(@name) + raise ArgumentError, "Illegal attempt to use a numeric name '#{name}' at #{origin_label(origin)}." + end + + freeze() + end + + def hash + @hash + end + + def ==(o) + o.class == self.class && type == o.type && name == o.name + end + + alias eql? == + + def to_s + "#{type}/#{name}" + end + end +end + diff --git a/lib/puppet/pops/loader/loader_paths.rb b/lib/puppet/pops/loader/loader_paths.rb new file mode 100644 index 000000000..305828528 --- /dev/null +++ b/lib/puppet/pops/loader/loader_paths.rb @@ -0,0 +1,163 @@ + +# LoaderPaths +# === +# The central loader knowledge about paths, what they represent and how to instantiate from them. +# Contains helpers (*smart paths*) to deal with lazy resolution of paths. +# +# TODO: Currently only supports loading of functions (3 kinds) +# +module Puppet::Pops::Loader::LoaderPaths + # Returns an array of SmartPath, each instantiated with a reference to the given loader (for root path resolution + # and existence checks). The smart paths in the array appear in precedence order. The returned array may be + # mutated. + # + def self.relative_paths_for_type(type, loader) #, start_index_in_name) + result = + case type # typed_name.type + when :function + if Puppet[:biff] == true + [FunctionPath4x.new(loader), FunctionPath3x.new(loader), FunctionPathPP.new(loader)] + else + [FunctionPath4x.new(loader), FunctionPathPP.new(loader)] + end + + # when :xxx # TODO: Add all other types + + else + # unknown types, simply produce an empty result; no paths to check, nothing to find... move along... + [] + end + result + end + +# # DO NOT REMOVE YET. needed later? when there is the need to decamel a classname +# def de_camel(fq_name) +# fq_name.to_s.gsub(/::/, '/'). +# gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2'). +# gsub(/([a-z\d])([A-Z])/,'\1_\2'). +# tr("-", "_"). +# downcase +# end + + class SmartPath + # Generic path, in the sense of "if there are any entities of this kind to load, where are they?" + attr_reader :generic_path + + # Creates SmartPath for the given loader (loader knows how to check for existence etc.) + def initialize(loader) + @loader = loader + end + + def generic_path() + return @generic_path unless @generic_path.nil? + + root_path = @loader.path + @generic_path = (root_path.nil? ? relative_path : File.join(root_path, relative_path)) + end + + # Effective path is the generic path + the name part(s) + extension. + # + def effective_path(typed_name, start_index_in_name) +# "#{File.join(generic_path, typed_name.name_parts[ start_index_in_name..-1 ])}#{extension}" + "#{File.join(generic_path, typed_name.name_parts)}#{extension}" + end + + def relative_path() + raise NotImplementedError.new + end + + def instantiator() + raise NotImplementedError.new + end + end + + class RubySmartPath < SmartPath + def extension + ".rb" + end + + # Duplication of extension information, but avoids one call + def effective_path(typed_name, start_index_in_name) +# "#{File.join(generic_path, typed_name.name_parts[ start_index_in_name..-1 ])}.rb" + "#{File.join(generic_path, typed_name.name_parts)}.rb" + end + end + + class PuppetSmartPath < SmartPath + def extension + ".pp" + end + + # Duplication of extension information, but avoids one call + def effective_path(typed_name, start_index_in_name) +# "#{File.join(generic_path, typed_name.name_parts[ start_index_in_name..-1 ])}.pp" + "#{File.join(generic_path, typed_name.name_parts)}.pp" + end + end + + class FunctionPath4x < RubySmartPath + FUNCTION_PATH_4X = File.join('lib', 'puppet', 'functions') + + def relative_path + FUNCTION_PATH_4X + end + + def instantiator() + Puppet::Pops::Loader::RubyFunctionInstantiator + end + end + + class FunctionPath3x < RubySmartPath + FUNCTION_PATH_3X = File.join('lib', 'puppet', 'parser', 'functions') + + def relative_path + FUNCTION_PATH_3X + end + + def instantiator() + Puppet::Pops::Loader::RubyLegacyFunctionInstantiator + end + end + + class FunctionPathPP < PuppetSmartPath + FUNCTION_PATH_PP = 'functions' + + def relative_path + FUNCTION_PATH_PP + end + + def instantiator() + Puppet::Pops::Loader::PuppetFunctionInstantiator + end + end + + # SmartPaths + # === + # Holds effective SmartPath instances per type + # + class SmartPaths + def initialize(path_based_loader) + @loader = path_based_loader + @smart_paths = {} + end + + # Ensures that the paths for the type have been probed and pruned to what is existing relative to + # the given root. + # + # @param type [Symbol] the entity type to load + # @return [Array] array of effective paths for type (may be empty) + # + def effective_paths(type) + smart_paths = @smart_paths + loader = @loader + unless effective_paths = smart_paths[type] + # type not yet processed, does the various directories for the type exist ? + # Get the relative dirs for the type + paths_for_type = Puppet::Pops::Loader::LoaderPaths.relative_paths_for_type(type, loader) + # Check which directories exist in the loader's content/index + effective_paths = smart_paths[type] = paths_for_type.select { |sp| loader.meaningful_to_search?(sp) } + end + effective_paths + end + end +end diff --git a/lib/puppet/pops/loader/module_loaders.rb b/lib/puppet/pops/loader/module_loaders.rb new file mode 100644 index 000000000..fb54df70e --- /dev/null +++ b/lib/puppet/pops/loader/module_loaders.rb @@ -0,0 +1,228 @@ + +# =ModuleLoaders +# A ModuleLoader loads items from a single module. +# The ModuleLoaders (ruby) module contains various such loaders. There is currently one concrete +# implementation, ModuleLoaders::FileBased that loads content from the file system. +# Other implementations can be created - if they are based on name to path mapping where the path +# is relative to a root path, they can derive the base behavior from the ModuleLoaders::AbstractPathBasedModuleLoader class. +# +# Examples of such extensions could be a zip/jar/compressed file base loader. +# +# Notably, a ModuleLoader does not configure itself - it is given the information it needs (the root, its name etc.) +# Logic higher up in the loader hierarchy of things makes decisions based on the "shape of modules", and "available +# modules" to determine which module loader to use for each individual module. (There could be differences in +# internal layout etc.) +# +# A module loader is also not aware of the mapping of name to relative paths - this is performed by the +# included module Puppet::Pops::Loader::PathBasedInstantatorConfig which knows about the map from type/name to +# relative path, and the logic that can instantiate what is expected to be found in the content of that path. +# +# @api private +# +module Puppet::Pops::Loader::ModuleLoaders + class AbstractPathBasedModuleLoader < Puppet::Pops::Loader::BaseLoader + + # The name of the module, or nil, if this is a global "component" + attr_reader :module_name + + # The path to the location of the module/component - semantics determined by subclass + attr_reader :path + + # A map of type to smart-paths that help with minimizing the number of paths to scan + attr_reader :smart_paths + + # Initialize a kind of ModuleLoader for one module + # @param parent_loader [Puppet::Pops::Loader] loader with higher priority + # @param module_name [String] the name of the module (non qualified name), may be nil for a global "component" + # @param path [String] the path to the root of the module (semantics defined by subclass) + # @param loader_name [String] a name that is used for human identification (useful when module_name is nil) + # + def initialize(parent_loader, module_name, path, loader_name) + super parent_loader, loader_name + + # Irrespective of the path referencing a directory or file, the path must exist. + unless Puppet::FileSystem.exist?(path) + raise ArgumentError, "The given path '#{path}' does not exist!" + end + + @module_name = module_name + @path = path + @smart_paths = Puppet::Pops::Loader::LoaderPaths::SmartPaths.new(self) + end + + # Finds typed/named entity in this module + # @param typed_name [Puppet::Pops::Loader::TypedName] the type/name to find + # @return [Puppet::Pops::Loader::Loader::NamedEntry, nil found/created entry, or nil if not found + # + def find(typed_name) + # Assume it is a global name, and that all parts of the name should be used when looking up + name_part_index = 0 + name_parts = typed_name.name_parts + + # Certain types and names can be disqualified up front + if name_parts.size > 1 + # The name is in a name space. + + # Then entity cannot possible be in this module unless the name starts with the module name. + # Note: If "module" represents a "global component", the module_name is nil and cannot match which is + # ok since such a "module" cannot have namespaced content). + # + return nil unless name_parts[0] == module_name + + # Skip the first part of the name when computing the path since the path already contains the name of the + # module + name_part_index = 1 + else + # The name is in the global name space. + + # The only globally name-spaced elements that may be loaded from modules are functions and resource types + case typed_name.type + when :function + when :resource_type + else + # anything else cannot possibly be in this module + # TODO: should not be allowed anyway... may have to revisit this decision + return nil + end + end + + # Get the paths that actually exist in this module (they are lazily processed once and cached). + # The result is an array (that may be empty). + # Find the file to instantiate, and instantiate the entity if file is found + origin = nil + if (smart_path = smart_paths.effective_paths(typed_name.type).find do |sp| + origin = sp.effective_path(typed_name, name_part_index) + existing_path(origin) + end) + value = smart_path.instantiator.create(self, typed_name, origin, get_contents(origin)) + # cache the entry and return it + set_entry(typed_name, value, origin) + else + nil + end + end + + # Abstract method that subclasses override that checks if it is meaningful to search using a generic smart path. + # This optimization is performed to not be tricked into searching an empty directory over and over again. + # The implementation may perform a deep search for file content other than directories and cache this in + # and index. It is guaranteed that a call to meaningful_to_search? takes place before checking any other + # path with relative_path_exists?. + # + # This optimization exists because many modules have been created from a template and they have + # empty directories for functions, types, etc. (It is also the place to create a cached index of the content). + # + # @param relative_path [String] a path relative to the module's root + # @return [Boolean] true if there is content in the directory appointed by the relative path + # + def meaningful_to_search?(smart_path) + raise NotImplementedError.new + end + + # Abstract method that subclasses override to answer if the given relative path exists, and if so returns that path + # + # @param relative_path [String] a path resolved by a smart path against the loader's root (if it has one) + # @return [Boolean] true if the file exists + # + def existing_path(resolved_path) + raise NotImplementedError.new + end + + # Abstract method that subclasses override to produce the content of the effective path. + # It should either succeed and return a String or fail with an exception. + # + # @param relative_path [String] a path as resolved by a smart path + # @return [String] the content of the file + # + def get_contents(effective_path) + raise NotImplementedError.new + end + + # Abstract method that subclasses override to produce a source reference String used to identify the + # system resource (resource in the URI sense). + # + # @param relative_path [String] a path relative to the module's root + # @return [String] a reference to the source file (in file system, zip file, or elsewhere). + # + def get_source_ref(relative_path) + raise NotImplementedError.new + end + end + + # @api private + # + class FileBased < AbstractPathBasedModuleLoader + + attr_reader :smart_paths + attr_reader :path_index + + # Create a kind of ModuleLoader for one module (Puppet Module, or module like) + # + # @param parent_loader [Puppet::Pops::Loader::Loader] typically the loader for the environment or root + # @param module_name [String] the name of the module (non qualified name), may be nil for "modules" only containing globals + # @param path [String] the path to the root of the module (semantics defined by subclass) + # @param loader_name [String] a name that identifies the loader + # + def initialize(parent_loader, module_name, path, loader_name) + super + unless Puppet::FileSystem.directory?(path) + raise ArgumentError, "The given module root path '#{path}' is not a directory (required for file system based module path entry)" + end + @path_index = Set.new() + end + + def existing_path(effective_path) + # Optimized, checks index instead of visiting file system + @path_index.include?(effective_path) ? effective_path : nil + end + + def meaningful_to_search?(smart_path) + ! add_to_index(smart_path).empty? + end + + def to_s() + "(ModuleLoader::FileBased '#{loader_name()}' '#{module_name()}')" + end + + def add_to_index(smart_path) + found = Dir.glob(File.join(smart_path.generic_path, '**', "*#{smart_path.extension}")) + @path_index.merge(found) + found + end + + def get_contents(effective_path) + Puppet::FileSystem.read(effective_path) + end + end + + # Loads from a gem specified as a URI, gem://gemname/optional/path/in/gem, or just a String gemname. + # The source reference (shown in errors etc.) is the expanded path of the gem as this is believed to be more + # helpful - given the location it should be quite obvious which gem it is, without the location, the user would + # need to go on a hunt for where the file actually is located. + # + # TODO: How does this get instantiated? Does the gemname refelect the name of the module (the namespace) + # or is that specified a different way? Can a gem be the container of multiple modules? + # + # @api private + # + class GemBased < FileBased + include Puppet::Pops::Loader::GemSupport + + attr_reader :gem_ref + + # Create a kind of ModuleLoader for one module + # The parameters are: + # * parent_loader - typically the loader for the root + # * module_name - the name of the module (non qualified name) + # * gem_ref - [URI, String] gem reference to the root of the module (URI, gem://gemname/optional/path/in/gem), or + # just the gem's name as a String. + # + def initialize(parent_loader, module_name, gem_ref, loader_name) + @gem_ref = gem_ref + super parent_loader, module_name, gem_dir(gem_ref), loader_name + end + + def to_s() + "(ModuleLoader::GemBased '#{loader_name()}' '#{@gem_ref}' [#{module_name()}])" + end + end +end diff --git a/lib/puppet/pops/loader/null_loader.rb b/lib/puppet/pops/loader/null_loader.rb new file mode 100644 index 000000000..44568c99e --- /dev/null +++ b/lib/puppet/pops/loader/null_loader.rb @@ -0,0 +1,44 @@ +# The null loader is empty and delegates everything to its parent if it has one. +# +class Puppet::Pops::Loader::NullLoader < Puppet::Pops::Loader::Loader + attr_reader :loader_name + + # Construct a NullLoader, optionally with a parent loader + # + def initialize(parent_loader=nil, loader_name = "null-loader") + @loader_name = loader_name + @parent = parent_loader + end + + # Has parent if one was set when constructed + def parent + @parent + end + + def load_typed(typed_name) + if @parent.nil? + nil + else + @parent.load_typed(typed_name) + end + end + + # Has no entries on its own - always nil + def get_entry(typed_name) + nil + end + + # Finds nothing, there are no entries + def find(name) + nil + end + + # Cannot store anything + def set_entry(typed_name, value) + nil + end + + def to_s() + "(NullLoader '#{loader_name}')" + end +end \ No newline at end of file diff --git a/lib/puppet/pops/loader/puppet_function_instantiator.rb b/lib/puppet/pops/loader/puppet_function_instantiator.rb new file mode 100644 index 000000000..7fbab8079 --- /dev/null +++ b/lib/puppet/pops/loader/puppet_function_instantiator.rb @@ -0,0 +1,97 @@ +# The PuppetFunctionInstantiator instantiates a Puppet::Functions::Function given a Puppet Programming language +# source that when called evaluates the Puppet logic it contains. +# +class Puppet::Pops::Loader::PuppetFunctionInstantiator + # Produces an instance of the Function class with the given typed_name, or fails with an error if the + # given puppet source does not produce this instance when evaluated. + # + # @param loader [Puppet::Pops::Loader::Loader] The loader the function is associated with + # @param typed_name [Puppet::Pops::Loader::TypedName] the type / name of the function to load + # @param source_ref [URI, String] a reference to the source / origin of the puppet code to evaluate + # @param pp_code_string [String] puppet code in a string + # + # @return [Puppet::Pops::Functions.Function] - an instantiated function with global scope closure associated with the given loader + # + def self.create(loader, typed_name, source_ref, pp_code_string) + parser = Puppet::Pops::Parser::EvaluatingParser.new() + + # parse and validate + result = parser.parse_string(pp_code_string, source_ref) + # Only one function is allowed (and no other definitions) + case result.model.definitions.size + when 0 + raise ArgumentError, "The code loaded from #{source_ref} does not define the function #{typed_name.name} - it is empty." + when 1 + # ok + else + raise ArgumentError, "The code loaded from #{source_ref} must contain only the function #{typed_name.name} - it has additional definitions." + end + the_function_definition = result.model.definitions[0] + + unless the_function_definition.is_a?(Puppet::Pops::Model::FunctionDefinition) + raise ArgumentError, "The code loaded from #{source_ref} does not define the function #{typed_name.name} - no function found." + end + unless the_function_definition.name == typed_name.name + expected = typed_name.name + actual = the_function_definition.name + raise ArgumentError, "The code loaded from #{source_ref} produced function with the wrong name, expected #{expected}, actual #{actual}" + end + unless result.model().body == the_function_definition + raise ArgumentError, "The code loaded from #{source_ref} contains additional logic - can only contain the function #{typed_name.name}" + end + + # TODO: Cheating wrt. scope - assuming it is found in the context + closure_scope = Puppet.lookup(:global_scope) { {} } + + created = create_function_class(the_function_definition, closure_scope) + # create the function instance - it needs closure (scope), and loader (i.e. where it should start searching for things + # when calling functions etc. + # It should be bound to global scope + + created.new(closure_scope, loader) + end + + def self.create_function_class(function_definition, closure_scope) + method_name = :"#{function_definition.name.split(/::/).slice(-1)}" + closure = Puppet::Pops::Evaluator::Closure.new( + Puppet::Pops::Evaluator::EvaluatorImpl.new(), + function_definition, + closure_scope) + required_optional = function_definition.parameters.reduce([0, 0]) do |memo, p| + if p.value.nil? + memo[0] += 1 + else + memo[1] += 1 + end + memo + end + min_arg_count = required_optional[0] + max_arg_count = required_optional[0] + required_optional[1] + + # Create a 4x function wrapper around the Puppet Function + created_function_class = Puppet::Functions.create_function(function_definition.name) do + # Define the method that is called from dispatch - this method just changes a call + # with multiple unknown arguments to passing all in an array (since this is expected in the closure API. + # + # TODO: The closure will call the evaluator.call method which will again match args with parameters. + # This can be done a better way later - unifying the two concepts - a function instance is really the same + # as the current evaluator closure for lambdas, only that it also binds an evaluator. This could perhaps + # be a specialization of Function... with a special dispatch + # + define_method(:__relay__call__) do |*args| + closure.call(nil, *args) + end + + # Define a dispatch that performs argument type/count checking + # + dispatch :__relay__call__ do + # Use Puppet Type Object (not Optional[Object] since the 3x API passes undef as empty string). + param(optional(object), 'args') + # Specify arg count (transformed from FunctionDefinition.parameters, no types, or varargs yet) + arg_count(min_arg_count, max_arg_count) + end + end + created_function_class + + end +end \ No newline at end of file diff --git a/lib/puppet/pops/loader/ruby_function_instantiator.rb b/lib/puppet/pops/loader/ruby_function_instantiator.rb new file mode 100644 index 000000000..0b66ac126 --- /dev/null +++ b/lib/puppet/pops/loader/ruby_function_instantiator.rb @@ -0,0 +1,34 @@ +# The RubyFunctionInstantiator instantiates a Puppet::Functions::Function given the ruby source +# that calls Puppet::Functions.create_function. +# +class Puppet::Pops::Loader::RubyFunctionInstantiator + # Produces an instance of the Function class with the given typed_name, or fails with an error if the + # given ruby source does not produce this instance when evaluated. + # + # @param loader [Puppet::Pops::Loader::Loader] The loader the function is associated with + # @param typed_name [Puppet::Pops::Loader::TypedName] the type / name of the function to load + # @param source_ref [URI, String] a reference to the source / origin of the ruby code to evaluate + # @param ruby_code_string [String] ruby code in a string + # + # @return [Puppet::Pops::Functions.Function] - an instantiated function with global scope closure associated with the given loader + # + def self.create(loader, typed_name, source_ref, ruby_code_string) + unless ruby_code_string.is_a?(String) && ruby_code_string =~ /Puppet\:\:Functions\.create_function/ + raise ArgumentError, "The code loaded from #{source_ref} does not seem to be a Puppet 4x API function - no create_function call." + end + created = eval(ruby_code_string) + unless created.is_a?(Class) + raise ArgumentError, "The code loaded from #{source_ref} did not produce a Function class when evaluated. Got '#{created.class}'" + end + unless created.name.to_s == typed_name.name() + raise ArgumentError, "The code loaded from #{source_ref} produced mis-matched name, expected '#{typed_name.name}', got #{created.name}" + end + # create the function instance - it needs closure (scope), and loader (i.e. where it should start searching for things + # when calling functions etc. + # It should be bound to global scope + + # TODO: Cheating wrt. scope - assuming it is found in the context + closure_scope = Puppet.lookup(:global_scope) { {} } + created.new(closure_scope, loader) + end +end \ No newline at end of file diff --git a/lib/puppet/pops/loader/ruby_legacy_function_instantiator.rb b/lib/puppet/pops/loader/ruby_legacy_function_instantiator.rb new file mode 100644 index 000000000..a3661ebaa --- /dev/null +++ b/lib/puppet/pops/loader/ruby_legacy_function_instantiator.rb @@ -0,0 +1,109 @@ +# The RubyLegacyFunctionInstantiator loads a 3x function and turns it into a 4x function +# that is called with 3x semantics (values are transformed to be 3x compliant). +# +# The code is loaded from a string obtained by reading the 3x function ruby code into a string +# and then passing it to the loaders class method `create`. When Puppet[:biff] == true, the +# 3x Puppet::Parser::Function.newfunction method relays back to this function loader's +# class method legacy_newfunction which creates a Puppet::Functions class wrapping the +# 3x function's block into a method in a function class derived from Puppet::Function. +# This class is then returned, and the Legacy loader continues the same way as it does +# for a 4x function. +# +# TODO: Wrapping of Scope +# The 3x function expects itself to be Scope. It passes itself as scope to other parts of the runtime, +# it expects to find all sorts of information in itself, get/set variables, get compiler, get environment +# etc. +# TODO: Transformation of arguments to 3x compliant objects +# +class Puppet::Pops::Loader::RubyLegacyFunctionInstantiator + + # Produces an instance of the Function class with the given typed_name, or fails with an error if the + # given ruby source does not produce this instance when evaluated. + # + # @param loader [Puppet::Pops::Loader::Loader] The loader the function is associated with + # @param typed_name [Puppet::Pops::Loader::TypedName] the type / name of the function to load + # @param source_ref [URI, String] a reference to the source / origin of the ruby code to evaluate + # @param ruby_code_string [String] ruby code in a string + # + # @return [Puppet::Pops::Functions.Function] - an instantiated function with global scope closure associated with the given loader + # + def self.create(loader, typed_name, source_ref, ruby_code_string) + # Old Ruby API supports calling a method via :: + # this must also be checked as well as call with '.' + # + unless ruby_code_string.is_a?(String) && ruby_code_string =~ /Puppet\:\:Parser\:\:Functions(?:\.|\:\:)newfunction/ + raise ArgumentError, "The code loaded from #{source_ref} does not seem to be a Puppet 3x API function - no newfunction call." + end + + # The evaluation of the 3x function creation source should result in a call to the legacy_newfunction + # + created = eval(ruby_code_string) + unless created.is_a?(Class) + raise ArgumentError, "The code loaded from #{source_ref} did not produce a Function class when evaluated. Got '#{created.class}'" + end + unless created.name.to_s == typed_name.name() + raise ArgumentError, "The code loaded from #{source_ref} produced mis-matched name, expected '#{typed_name.name}', got #{created.name}" + end + # create the function instance - it needs closure (scope), and loader (i.e. where it should start searching for things + # when calling functions etc. + # It should be bound to global scope + + # TODO: Cheating wrt. scope - assuming it is found in the context + closure_scope = Puppet.lookup(:global_scope) { {} } + created.new(closure_scope, loader) + end + + # This is a new implementation of the method that is used in 3x to create a function. + # The arguments are the same as those passed to Puppet::Parser::Functions.newfunction, hence its + # deviation from regular method naming practice. + # + def self.legacy_newfunction(name, options, &block) + + # 3x api allows arity to be specified, if unspecified it is 0 or more arguments + # arity >= 0, is an exact count + # airty < 0 is the number of required arguments -1 (i.e. -1 is 0 or more) + # (there is no upper cap, there is no support for optional values, or defaults) + # + arity = options[:arity] || -1 + if arity >= 0 + min_arg_count = arity + max_arg_count = arity + else + min_arg_count = (arity + 1).abs + # infinity + max_arg_count = :default + end + + # Create a 4x function wrapper around the 3x Function + created_function_class = Puppet::Functions.create_function(name) do + # define a method on the new Function class with the same name as the function, but + # padded with __ because the function may represent a ruby method with the same name that + # expects to have inherited from Kernel, and then Object. + # (This can otherwise lead to infinite recursion, or that an ArgumentError is raised). + # + __name__ = :"__#{name}__" + define_method(__name__, &block) + + # Define the method that is called from dispatch - this method just changes a call + # with multiple unknown arguments to passing all in an array (since this is expected in the 3x API). + # We want the call to be checked for type and number of arguments so cannot call the function + # defined by the block directly since it is defined to take a single argument. + # + define_method(:__relay__call__) do |*args| + # dup the args since the function may destroy them + # TODO: Should convert arguments to 3x, now :undef is send to the function + send(__name__, args.dup) + end + + # Define a dispatch that performs argument type/count checking + # + dispatch :__relay__call__ do + # Use Puppet Type Object (not Optional[Object] since the 3x API passes undef as empty string). + param object, 'args' + # Specify arg count (transformed from 3x function arity specification). + arg_count(min_arg_count, max_arg_count) + end + end + created_function_class + end +end diff --git a/lib/puppet/pops/loader/static_loader.rb b/lib/puppet/pops/loader/static_loader.rb new file mode 100644 index 000000000..88ba86d85 --- /dev/null +++ b/lib/puppet/pops/loader/static_loader.rb @@ -0,0 +1,32 @@ + # Static Loader contains constants, basic data types and other types required for the system + # to boot. + # +class Puppet::Pops::Loader::StaticLoader < Puppet::Pops::Loader::Loader + + def load_typed(typed_name) + load_constant(typed_name) + end + + def get_entry(typed_name) + load_constant(typed_name) + end + + def find(name) + # There is nothing to search for, everything this loader knows about is already available + nil + end + + def parent + nil # at top of the hierarchy + end + + def to_s() + "(StaticLoader)" + end + private + + def load_constant(typed_name) + # Move along, nothing to see here a.t.m... + nil + end +end diff --git a/lib/puppet/pops/loader/uri_helper.rb b/lib/puppet/pops/loader/uri_helper.rb new file mode 100644 index 000000000..ff42a60af --- /dev/null +++ b/lib/puppet/pops/loader/uri_helper.rb @@ -0,0 +1,22 @@ +module Puppet::Pops::Loader::UriHelper + # Raises an exception if specified gem can not be located + # + def path_for_uri(uri, subdir='lib') + case uri.scheme + when "gem" + begin + spec = Gem::Specification.find_by_name(uri.hostname) + # if path given append that, else append given subdir + File.join(spec.gem_dir, uri.path.empty?() ? subdir : uri.path) + rescue StandardError => e + raise "TODO TYPE: Failed to located gem #{uri}. #{e.message}" + end + when "file" + File.join(uri.path, subdir) + when nil + File.join(uri.path, subdir) + else + raise "Not a valid scheme for a loader: #{uri.scheme}. Use a 'file:' (or just a path), or 'gem://gemname[/path]" + end + end +end diff --git a/lib/puppet/pops/loaders.rb b/lib/puppet/pops/loaders.rb new file mode 100644 index 000000000..051289ee6 --- /dev/null +++ b/lib/puppet/pops/loaders.rb @@ -0,0 +1,224 @@ +class Puppet::Pops::Loaders + class LoaderError < Puppet::Error; end + + attr_reader :static_loader + attr_reader :puppet_system_loader + attr_reader :environment_loader + + def initialize() + # The static loader can only be changed after a reboot + @@static_loader ||= Puppet::Pops::Loader::StaticLoader.new() + + # Create the set of loaders + # 1. Puppet, loads from the "running" puppet - i.e. bundled functions, types, extension points and extensions + # Does not change without rebooting the service running puppet. + # + @@puppet_system_loader ||= create_puppet_system_loader() + + # 2. Environment loader - i.e. what is bound across the environment, may change for each setup + # TODO: loaders need to work when also running in an agent doing catalog application. There is no + # concept of environment the same way as when running as a master (except when doing apply). + # The creation mechanisms should probably differ between the two. + # + @environment_loader = create_environment_loader() + + # 3. module loaders are set up from the create_environment_loader, they register themselves + end + + # Clears the cached static and puppet_system loaders (to enable testing) + # + def self.clear + @@static_loader = nil + @@puppet_system_loader = nil + end + + def static_loader + @@static_loader + end + + def puppet_system_loader + @@puppet_system_loader + end + + def self.create_loaders() + self.new() + end + + def public_loader_for_module(module_name) + md = @module_resolver[module_name] || (return nil) + # Note, this loader is not resolved until it is asked to load something it may contain + md.public_loader + end + + def private_loader_for_module(module_name) + md = @module_resolver[module_name] || (return nil) + unless md.resolved? + @module_resolver.resolve(md) + end + md.private_loader + end + + private + + def create_puppet_system_loader() + module_name = nil + loader_name = 'puppet_system' + + # Puppet system may be installed in a fixed location via RPM, installed as a Gem, via source etc. + # The only way to find this across the different ways puppet can be installed is + # to search up the path from this source file's __FILE__ location until it finds the parent of + # lib/puppet... e.g.. dirname(__FILE__)/../../.. (i.e. /lib/puppet/pops/loaders.rb). + # + puppet_lib = File.join(File.dirname(__FILE__), '../../..') + Puppet::Pops::Loader::ModuleLoaders::FileBased.new(static_loader, module_name, puppet_lib, loader_name) + end + + def create_environment_loader() + # This defines where to start parsing/evaluating - the "initial import" (to use 3x terminology) + # Is either a reference to a single .pp file, or a directory of manifests. If the environment becomes + # a module and can hold functions, types etc. then these are available across all other modules without + # them declaring this dependency - it is however valuable to be able to treat it the same way + # bindings and other such system related configuration. + + # This is further complicated by the many options available: + # - The environment may not have a directory, the code comes from one appointed 'manifest' (site.pp) + # - The environment may have a directory and also point to a 'manifest' + # - The code to run may be set in settings (code) + + # Further complication is that there is nothing specifying what the visibility is into + # available modules. (3x is everyone sees everything). + # Puppet binder currently reads confdir/bindings - that is bad, it should be using the new environment support. + + current_environment = Puppet.lookup(:current_environment) + # The environment is not a namespace, so give it a nil "module_name" + module_name = nil + loader_name = "environment:#{current_environment.name}" + env_dir = Puppet[:environmentdir] + if env_dir.nil? + loader = Puppet::Pops::Loader::NullLoader.new(puppet_system_loader, loader_name) + else + envdir_path = File.join(env_dir, current_environment.name.to_s) + # TODO: Representing Environment as a Module - needs something different (not all types are supported), + # and it must be able to import .pp code from 3x manifest setting, or from code setting as well as from + # a manifests directory under the environment's root. The below is cheating... + # + loader = Puppet::Pops::Loader::ModuleLoaders::FileBased(puppet_system_loader, module_name, envdir_path, loader_name) + end + # An environment has a module path even if it has a null loader + configure_loaders_for_modules(loader, current_environment) + loader + end + + def configure_loaders_for_modules(parent_loader, current_environment) + @module_resolver = mr = ModuleResolver.new() + current_environment.modules.each do |puppet_module| + # Create data about this module + md = LoaderModuleData.new(puppet_module) + mr[puppet_module.name] = md + md.public_loader = Puppet::Pops::Loader::ModuleLoaders::FileBased.new(parent_loader, md.name, md.path, md.name) + end + end + + # =LoaderModuleData + # Information about a Module and its loaders. + # TODO: should have reference to real model element containing all module data; this is faking it + # TODO: Should use Puppet::Module to get the metadata (as a hash) - a somewhat blunt instrument, but that is + # what is available with a reasonable API. + # + class LoaderModuleData + + attr_accessor :state + attr_accessor :public_loader + attr_accessor :private_loader + attr_accessor :resolutions + + # The Puppet::Module this LoaderModuleData represents in the loader configuration + attr_reader :puppet_module + + # @param puppet_module [Puppet::Module] the module instance for the module being represented + # + def initialize(puppet_module) + @state = :initial + @puppet_module = puppet_module + @resolutions = [] + @loader = nil + @private_loader = nil + end + + def name + @puppet_module.name + end + + def version + @puppet_module.version + end + + def path + @puppet_module.path + end + + def requirements + nil # FAKE: this says "wants to see everything" + end + + def resolved? + @state == :resolved + end + end + + # Resolves module loaders - resolution of model dependencies is done by Puppet::Module + # + class ModuleResolver + + def initialize() + @index = {} + @all_module_loaders = nil + end + + def [](name) + @index[name] + end + + def []=(name, module_data) + @index[name] = module_data + end + + def all_module_loaders + @all_module_loaders ||= @index.map {|md| md.loader } + end + + def resolve(module_data) + return if module_data.resolved? + pm = module_data.puppet_module + # Resolution rules + # If dependencies.nil? means "see all other modules" (This to make older modules work, and modules w/o metadata) + # TODO: Control via flag/feature ? + module_data.private_loader = + if pm.dependencies.nil? + # see everything + if Puppet::Util::Log.level == :debug + Puppet.debug("ModuleLoader: module '#{module_data.name}' has unknown dependencies - it will have all other modules visible") + end + + Puppet::Pops::Loader::DependencyLoader.new(module_data.loader, module_data.name, all_module_loaders()) + else + # If module has resolutions they must resolve - it will not see into other modules otherwise + # TODO: possible give errors if there are unresolved references + # i.e. !pm.unmet_dependencies.empty? (if module lacks metadata it is considered to have met all). + # The face "module" can display error information. + # Here, we are just giving up without explaining - the user can check with the module face (or console) + # + unless pm.unmet_dependencies.empty? + # TODO: Exception or just warning? + Puppet.warning("ModuleLoader: module '#{module_data.name}' has unresolved dependencies"+ + " - it will only see those that are resolved."+ + " Use 'puppet module list --tree' to see information about modules") + # raise Puppet::Pops::Loader::Loader::Error, "Loader Error: Module '#{module_data.name}' has unresolved dependencies - use 'puppet module list --tree' to see information" + end + dependency_loaders = pm.dependencies_as_modules.map { |dep| @index[dep.name].loader } + Puppet::Pops::Loader::DependencyLoader.new(module_data.loader, module_data.name, dependency_loaders) + end + + end + end +end \ No newline at end of file diff --git a/lib/puppet/pops/model/factory.rb b/lib/puppet/pops/model/factory.rb index 83af10242..f382001d5 100644 --- a/lib/puppet/pops/model/factory.rb +++ b/lib/puppet/pops/model/factory.rb @@ -1,970 +1,974 @@ # Factory is a helper class that makes construction of a Pops Model # much more convenient. It can be viewed as a small internal DSL for model # constructions. # For usage see tests using the factory. # # @todo All those uppercase methods ... they look bad in one way, but stand out nicely in the grammar... # decide if they should change into lower case names (some of the are lower case)... # class Puppet::Pops::Model::Factory Model = Puppet::Pops::Model attr_accessor :current alias_method :model, :current # Shared build_visitor, since there are many instances of Factory being used @@build_visitor = Puppet::Pops::Visitor.new(self, "build") @@interpolation_visitor = Puppet::Pops::Visitor.new(self, "interpolate") # Initialize a factory with a single object, or a class with arguments applied to build of # created instance # def initialize o, *args @current = case o when Model::PopsObject o when Puppet::Pops::Model::Factory o.current else build(o, *args) end end # Polymorphic build def build(o, *args) begin @@build_visitor.visit_this(self, o, *args) rescue =>e # debug here when in trouble... raise e end end # Polymorphic interpolate def interpolate() begin @@interpolation_visitor.visit_this_0(self, current) rescue =>e # debug here when in trouble... raise e end end # Building of Model classes def build_ArithmeticExpression(o, op, a, b) o.operator = op build_BinaryExpression(o, a, b) end def build_AssignmentExpression(o, op, a, b) o.operator = op build_BinaryExpression(o, a, b) end def build_AttributeOperation(o, name, op, value) o.operator = op o.attribute_name = name.to_s # BOOLEAN is allowed in the grammar o.value_expr = build(value) o end def build_AccessExpression(o, left, *keys) o.left_expr = to_ops(left) keys.each {|expr| o.addKeys(to_ops(expr)) } o end def build_BinaryExpression(o, left, right) o.left_expr = to_ops(left) o.right_expr = to_ops(right) o end def build_BlockExpression(o, *args) args.each {|expr| o.addStatements(to_ops(expr)) } o end def build_CollectExpression(o, type_expr, query_expr, attribute_operations) o.type_expr = to_ops(type_expr) o.query = build(query_expr) attribute_operations.each {|op| o.addOperations(build(op)) } o end def build_ComparisonExpression(o, op, a, b) o.operator = op build_BinaryExpression(o, a, b) end def build_ConcatenatedString(o, *args) args.each {|expr| o.addSegments(build(expr)) } o end def build_CreateTypeExpression(o, name, super_name = nil) o.name = name o.super_name = super_name o end def build_CreateEnumExpression(o, *args) o.name = args.slice(0) if args.size == 2 o.values = build(args.last) o end def build_CreateAttributeExpression(o, name, datatype_expr) o.name = name o.type = to_ops(datatype_expr) o end def build_HeredocExpression(o, name, expr) o.syntax = name o.text_expr = build(expr) o end # @param name [String] a valid classname # @param parameters [Array] may be empty # @param parent_class_name [String, nil] a valid classname referencing a parent class, optional. # @param body [Array, Expression, nil] expression that constitute the body # @return [Model::HostClassDefinition] configured from the parameters # def build_HostClassDefinition(o, name, parameters, parent_class_name, body) build_NamedDefinition(o, name, parameters, body) o.parent_class = parent_class_name if parent_class_name o end def build_ResourceOverrideExpression(o, resources, attribute_operations) o.resources = build(resources) attribute_operations.each {|ao| o.addOperations(build(ao)) } o end def build_KeyedEntry(o, k, v) o.key = to_ops(k) o.value = to_ops(v) o end def build_LiteralHash(o, *keyed_entries) keyed_entries.each {|entry| o.addEntries build(entry) } o end def build_LiteralList(o, *values) values.each {|v| o.addValues build(v) } o end def build_LiteralFloat(o, val) o.value = val o end def build_LiteralInteger(o, val, radix) o.value = val o.radix = radix o end def build_IfExpression(o, t, ift, els) o.test = build(t) o.then_expr = build(ift) o.else_expr= build(els) o end def build_MatchExpression(o, op, a, b) o.operator = op build_BinaryExpression(o, a, b) end # Builds body :) from different kinds of input # @overload f_build_body(nothing) # @param nothing [nil] unchanged, produces nil # @overload f_build_body(array) # @param array [Array] turns into a BlockExpression # @overload f_build_body(expr) # @param expr [Expression] produces the given expression # @overload f_build_body(obj) # @param obj [Object] produces the result of calling #build with body as argument def f_build_body(body) case body when NilClass nil when Array Puppet::Pops::Model::Factory.new(Model::BlockExpression, *body) else build(body) end end def build_LambdaExpression(o, parameters, body) parameters.each {|p| o.addParameters(build(p)) } b = f_build_body(body) o.body = to_ops(b) if b o end def build_NamedDefinition(o, name, parameters, body) parameters.each {|p| o.addParameters(build(p)) } b = f_build_body(body) o.body = b.current if b o.name = name o end # @param o [Model::NodeDefinition] # @param hosts [Array] host matches # @param parent [Expression] parent node matcher # @param body [Object] see {#f_build_body} def build_NodeDefinition(o, hosts, parent, body) hosts.each {|h| o.addHost_matches(build(h)) } o.parent = build(parent) if parent # no nop here b = f_build_body(body) o.body = b.current if b o end def build_Parameter(o, name, expr) o.name = name o.value = build(expr) if expr # don't build a nil/nop o end def build_QualifiedReference(o, name) o.value = name.to_s.downcase o end def build_RelationshipExpression(o, op, a, b) o.operator = op build_BinaryExpression(o, a, b) end def build_ResourceExpression(o, type_name, bodies) o.type_name = build(type_name) bodies.each {|b| o.addBodies(build(b)) } o end def build_RenderStringExpression(o, string) o.value = string; o end def build_ResourceBody(o, title_expression, attribute_operations) o.title = build(title_expression) attribute_operations.each {|ao| o.addOperations(build(ao)) } o end def build_ResourceDefaultsExpression(o, type_ref, attribute_operations) o.type_ref = build(type_ref) attribute_operations.each {|ao| o.addOperations(build(ao)) } o end def build_SelectorExpression(o, left, *selectors) o.left_expr = to_ops(left) selectors.each {|s| o.addSelectors(build(s)) } o end # Builds a SubLocatedExpression - this wraps the expression in a sublocation configured # from the given token # A SubLocated holds its own locator that is used for subexpressions holding positions relative # to what it describes. # def build_SubLocatedExpression(o, token, expression) o.expr = build(expression) o.offset = token.offset o.length = token.length locator = token.locator o.locator = locator o.leading_line_count = locator.leading_line_count o.leading_line_offset = locator.leading_line_offset # Index is held in sublocator's parent locator - needed to be able to reconstruct o.line_offsets = locator.locator.line_index o end def build_SelectorEntry(o, matching, value) o.matching_expr = build(matching) o.value_expr = build(value) o end def build_QueryExpression(o, expr) ops = to_ops(expr) o.expr = ops unless Puppet::Pops::Model::Factory.nop? ops o end def build_UnaryExpression(o, expr) ops = to_ops(expr) o.expr = ops unless Puppet::Pops::Model::Factory.nop? ops o end def build_Program(o, body, definitions, locator) o.body = to_ops(body) # non containment definitions.each { |d| o.addDefinitions(d) } o.source_ref = locator.file o.source_text = locator.string o.line_offsets = locator.line_index o.locator = locator o end def build_QualifiedName(o, name) o.value = name.to_s o end # Puppet::Pops::Model::Factory helpers def f_build_unary(klazz, expr) Puppet::Pops::Model::Factory.new(build(klazz.new, expr)) end def f_build_binary_op(klazz, op, left, right) Puppet::Pops::Model::Factory.new(build(klazz.new, op, left, right)) end def f_build_binary(klazz, left, right) Puppet::Pops::Model::Factory.new(build(klazz.new, left, right)) end def f_build_vararg(klazz, left, *arg) Puppet::Pops::Model::Factory.new(build(klazz.new, left, *arg)) end def f_arithmetic(op, r) f_build_binary_op(Model::ArithmeticExpression, op, current, r) end def f_comparison(op, r) f_build_binary_op(Model::ComparisonExpression, op, current, r) end def f_match(op, r) f_build_binary_op(Model::MatchExpression, op, current, r) end # Operator helpers def in(r) f_build_binary(Model::InExpression, current, r); end def or(r) f_build_binary(Model::OrExpression, current, r); end def and(r) f_build_binary(Model::AndExpression, current, r); end def not(); f_build_unary(Model::NotExpression, self); end def minus(); f_build_unary(Model::UnaryMinusExpression, self); end def text(); f_build_unary(Model::TextExpression, self); end def var(); f_build_unary(Model::VariableExpression, self); end def [](*r); f_build_vararg(Model::AccessExpression, current, *r); end def dot r; f_build_binary(Model::NamedAccessExpression, current, r); end def + r; f_arithmetic(:+, r); end def - r; f_arithmetic(:-, r); end def / r; f_arithmetic(:/, r); end def * r; f_arithmetic(:*, r); end def % r; f_arithmetic(:%, r); end def << r; f_arithmetic(:<<, r); end def >> r; f_arithmetic(:>>, r); end def < r; f_comparison(:<, r); end def <= r; f_comparison(:<=, r); end def > r; f_comparison(:>, r); end def >= r; f_comparison(:>=, r); end def == r; f_comparison(:==, r); end def ne r; f_comparison(:'!=', r); end def =~ r; f_match(:'=~', r); end def mne r; f_match(:'!~', r); end def paren(); f_build_unary(Model::ParenthesizedExpression, current); end def relop op, r f_build_binary_op(Model::RelationshipExpression, op.to_sym, current, r) end def select *args Puppet::Pops::Model::Factory.new(build(Model::SelectorExpression, current, *args)) end # For CaseExpression, setting the default for an already build CaseExpression def default r current.addOptions(Puppet::Pops::Model::Factory.WHEN(:default, r).current) self end def lambda=(lambda) current.lambda = lambda.current self end # Assignment = def set(r) f_build_binary_op(Model::AssignmentExpression, :'=', current, r) end # Assignment += def plus_set(r) f_build_binary_op(Model::AssignmentExpression, :'+=', current, r) end # Assignment -= def minus_set(r) f_build_binary_op(Model::AssignmentExpression, :'-=', current, r) end def attributes(*args) args.each {|a| current.addAttributes(build(a)) } self end # Catch all delegation to current def method_missing(meth, *args, &block) if current.respond_to?(meth) current.send(meth, *args, &block) else super end end def respond_to?(meth, include_all=false) current.respond_to?(meth, include_all) || super end # Records the position (start -> end) and computes the resulting length. # def record_position(start_locatable, end_locatable) from = start_locatable.is_a?(Puppet::Pops::Model::Factory) ? start_locatable.current : start_locatable to = end_locatable.is_a?(Puppet::Pops::Model::Factory) ? end_locatable.current : end_locatable to = from if to.nil? o = current # record information directly in the Model::Positioned object o.offset = from.offset o.length ||= to.offset - from.offset + to.length self end # @return [Puppet::Pops::Adapters::SourcePosAdapter] with location information def loc() Puppet::Pops::Adapters::SourcePosAdapter.adapt(current) end # Returns symbolic information about an expected share of a resource expression given the LHS of a resource expr. # # * `name { }` => `:resource`, create a resource of the given type # * `Name { }` => ':defaults`, set defaults for the referenced type # * `Name[] { }` => `:override`, overrides instances referenced by LHS # * _any other_ => ':error', all other are considered illegal # def self.resource_shape(expr) expr = expr.current if expr.is_a?(Puppet::Pops::Model::Factory) case expr when Model::QualifiedName :resource when Model::QualifiedReference :defaults when Model::AccessExpression :override when 'class' :class else :error end end # Factory starting points def self.literal(o); new(o); end def self.minus(o); new(o).minus; end def self.var(o); new(o).var; end def self.block(*args); new(Model::BlockExpression, *args); end def self.string(*args); new(Model::ConcatenatedString, *args); end def self.text(o); new(o).text; end def self.IF(test_e,then_e,else_e); new(Model::IfExpression, test_e, then_e, else_e); end def self.UNLESS(test_e,then_e,else_e); new(Model::UnlessExpression, test_e, then_e, else_e); end def self.CASE(test_e,*options); new(Model::CaseExpression, test_e, *options); end def self.WHEN(values_list, block); new(Model::CaseOption, values_list, block); end def self.MAP(match, value); new(Model::SelectorEntry, match, value); end def self.TYPE(name, super_name=nil); new(Model::CreateTypeExpression, name, super_name); end def self.ATTR(name, type_expr=nil); new(Model::CreateAttributeExpression, name, type_expr); end def self.ENUM(*args); new(Model::CreateEnumExpression, *args); end def self.KEY_ENTRY(key, val); new(Model::KeyedEntry, key, val); end def self.HASH(entries); new(Model::LiteralHash, *entries); end # TODO_HEREDOC def self.HEREDOC(name, expr); new(Model::HeredocExpression, name, expr); end def self.SUBLOCATE(token, expr) new(Model::SubLocatedExpression, token, expr); end def self.LIST(entries); new(Model::LiteralList, *entries); end def self.PARAM(name, expr=nil); new(Model::Parameter, name, expr); end def self.NODE(hosts, parent, body); new(Model::NodeDefinition, hosts, parent, body); end # Creates a QualifiedName representation of o, unless o already represents a QualifiedName in which # case it is returned. # def self.fqn(o) o = o.current if o.is_a?(Puppet::Pops::Model::Factory) o = new(Model::QualifiedName, o) unless o.is_a? Model::QualifiedName o end # Creates a QualifiedName representation of o, unless o already represents a QualifiedName in which # case it is returned. # def self.fqr(o) o = o.current if o.is_a?(Puppet::Pops::Model::Factory) o = new(Model::QualifiedReference, o) unless o.is_a? Model::QualifiedReference o end def self.TEXT(expr) new(Model::TextExpression, new(expr).interpolate) end # TODO_EPP def self.RENDER_STRING(o) new(Model::RenderStringExpression, o) end def self.RENDER_EXPR(expr) new(Model::RenderExpression, expr) end def self.EPP(parameters, body) see_scope = false params = parameters if parameters.nil? params = [] see_scope = true end LAMBDA(params, new(Model::EppExpression, see_scope, body)) end # TODO: This is the same a fqn factory method, don't know if callers to fqn and QNAME can live with the # same result or not yet - refactor into one method when decided. # def self.QNAME(name) new(Model::QualifiedName, name) end def self.NUMBER(name_or_numeric) if n_radix = Puppet::Pops::Utils.to_n_with_radix(name_or_numeric) val, radix = n_radix if val.is_a?(Float) new(Model::LiteralFloat, val) else new(Model::LiteralInteger, val, radix) end else # Bad number should already have been caught by lexer - this should never happen raise ArgumentError, "Internal Error, NUMBER token does not contain a valid number, #{name_or_numeric}" end end # Convert input string to either a qualified name, a LiteralInteger with radix, or a LiteralFloat # def self.QNAME_OR_NUMBER(name) if n_radix = Puppet::Pops::Utils.to_n_with_radix(name) val, radix = n_radix if val.is_a?(Float) new(Model::LiteralFloat, val) else new(Model::LiteralInteger, val, radix) end else new(Model::QualifiedName, name) end end def self.QREF(name) new(Model::QualifiedReference, name) end def self.VIRTUAL_QUERY(query_expr) new(Model::VirtualQuery, query_expr) end def self.EXPORTED_QUERY(query_expr) new(Model::ExportedQuery, query_expr) end def self.ATTRIBUTE_OP(name, op, expr) new(Model::AttributeOperation, name, op, expr) end def self.CALL_NAMED(name, rval_required, argument_list) unless name.kind_of?(Model::PopsObject) name = Puppet::Pops::Model::Factory.fqn(name) unless name.is_a?(Puppet::Pops::Model::Factory) end new(Model::CallNamedFunctionExpression, name, rval_required, *argument_list) end def self.CALL_METHOD(functor, argument_list) new(Model::CallMethodExpression, functor, true, nil, *argument_list) end def self.COLLECT(type_expr, query_expr, attribute_operations) new(Model::CollectExpression, type_expr, query_expr, attribute_operations) end def self.NAMED_ACCESS(type_name, bodies) new(Model::NamedAccessExpression, type_name, bodies) end def self.RESOURCE(type_name, bodies) new(Model::ResourceExpression, type_name, bodies) end def self.RESOURCE_DEFAULTS(type_name, attribute_operations) new(Model::ResourceDefaultsExpression, type_name, attribute_operations) end def self.RESOURCE_OVERRIDE(resource_ref, attribute_operations) new(Model::ResourceOverrideExpression, resource_ref, attribute_operations) end def self.RESOURCE_BODY(resource_title, attribute_operations) new(Model::ResourceBody, resource_title, attribute_operations) end def self.PROGRAM(body, definitions, locator) new(Model::Program, body, definitions, locator) end # Builds a BlockExpression if args size > 1, else the single expression/value in args def self.block_or_expression(*args) if args.size > 1 new(Model::BlockExpression, *args) else new(args[0]) end end def self.HOSTCLASS(name, parameters, parent, body) new(Model::HostClassDefinition, name, parameters, parent, body) end def self.DEFINITION(name, parameters, body) new(Model::ResourceTypeDefinition, name, parameters, body) end + def self.FUNCTION(name, parameters, body) + new(Model::FunctionDefinition, name, parameters, body) + end + def self.LAMBDA(parameters, body) new(Model::LambdaExpression, parameters, body) end def self.nop? o o.nil? || o.is_a?(Puppet::Pops::Model::Nop) end STATEMENT_CALLS = { 'require' => true, 'realize' => true, 'include' => true, 'contain' => true, 'debug' => true, 'info' => true, 'notice' => true, 'warning' => true, 'error' => true, 'fail' => true, } # 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] memo[-1] = Puppet::Pops::Model::Factory.CALL_NAMED(name, false, expr.is_a?(Array) ? expr : [expr]) if expr.is_a?(Model::CallNamedFunctionExpression) # Patch statement function call to expression style # This is needed because it is first parsed as a "statement" and the requirement changes as it becomes # an argument to the name to call transform above. expr.rval_required = true end else memo << expr if expr.is_a?(Model::CallNamedFunctionExpression) # Patch rvalue expression function call to statement style. # This is not really required but done to be AST model compliant expr.rval_required = false end end memo end end # Transforms a left expression followed by an untitled resource (in the form of attribute_operations) # @param left [Factory, Expression] the lhs followed what may be a hash def self.transform_resource_wo_title(left, attribute_ops) return nil unless attribute_ops.is_a? Array # return nil if attribute_ops.find { |ao| ao.operator == :'+>' } keyed_entries = attribute_ops.map do |ao| return nil if ao.operator == :'+>' KEY_ENTRY(ao.attribute_name, ao.value_expr) end result = block_or_expression(*transform_calls([left, HASH(keyed_entries)])) result end # Building model equivalences of Ruby objects # Allows passing regular ruby objects to the factory to produce instructions # that when evaluated produce the same thing. def build_String(o) x = Model::LiteralString.new x.value = o; x end def build_NilClass(o) x = Model::Nop.new x end def build_TrueClass(o) x = Model::LiteralBoolean.new x.value = o x end def build_FalseClass(o) x = Model::LiteralBoolean.new x.value = o x end def build_Fixnum(o) x = Model::LiteralInteger.new x.value = o; x end def build_Float(o) x = Model::LiteralFloat.new x.value = o; x end def build_Regexp(o) x = Model::LiteralRegularExpression.new x.value = o; x end def build_EppExpression(o, see_scope, body) o.see_scope = see_scope b = f_build_body(body) o.body = b.current if b o end # If building a factory, simply unwrap the model oject contained in the factory. def build_Factory(o) o.current end # Creates a String literal, unless the symbol is one of the special :undef, or :default # which instead creates a LiterlUndef, or a LiteralDefault. def build_Symbol(o) case o when :undef Model::LiteralUndef.new when :default Model::LiteralDefault.new else build_String(o.to_s) end end # Creates a LiteralList instruction from an Array, where the entries are built. def build_Array(o) x = Model::LiteralList.new o.each { |v| x.addValues(build(v)) } x end # Create a LiteralHash instruction from a hash, where keys and values are built # The hash entries are added in sorted order based on key.to_s # def build_Hash(o) x = Model::LiteralHash.new (o.sort_by {|k,v| k.to_s}).each {|k,v| x.addEntries(build(Model::KeyedEntry.new, k, v)) } x end # @param rval_required [Boolean] if the call must produce a value def build_CallExpression(o, functor, rval_required, *args) o.functor_expr = to_ops(functor) o.rval_required = rval_required args.each {|x| o.addArguments(to_ops(x)) } o end def build_CallMethodExpression(o, functor, rval_required, lambda, *args) build_CallExpression(o, functor, rval_required, *args) o.lambda = lambda o end def build_CaseExpression(o, test, *args) o.test = build(test) args.each {|opt| o.addOptions(build(opt)) } o end def build_CaseOption(o, value_list, then_expr) value_list = [value_list] unless value_list.is_a? Array value_list.each { |v| o.addValues(build(v)) } b = f_build_body(then_expr) o.then_expr = to_ops(b) if b o end # Build a Class by creating an instance of it, and then calling build on the created instance # with the given arguments def build_Class(o, *args) build(o.new(), *args) end def interpolate_Factory(o) interpolate(o.current) end def interpolate_LiteralInteger(o) # convert number to a variable self.class.new(o).var end def interpolate_Object(o) o end def interpolate_QualifiedName(o) self.class.new(o).var end # rewrite left expression to variable if it is name, number, and recurse if it is an access expression # this is for interpolation support in new lexer (${NAME}, ${NAME[}}, ${NUMBER}, ${NUMBER[]} - all # other expressions requires variables to be preceded with $ # def interpolate_AccessExpression(o) if is_interop_rewriteable?(o.left_expr) o.left_expr = to_ops(self.class.new(o.left_expr).interpolate) end o end def interpolate_NamedAccessExpression(o) if is_interop_rewriteable?(o.left_expr) o.left_expr = to_ops(self.class.new(o.left_expr).interpolate) end o end # Rewrite method calls on the form ${x.each ...} to ${$x.each} def interpolate_CallMethodExpression(o) if is_interop_rewriteable?(o.functor_expr) o.functor_expr = to_ops(self.class.new(o.functor_expr).interpolate) end o end def is_interop_rewriteable?(o) case o when Model::AccessExpression, Model::QualifiedName, Model::NamedAccessExpression, Model::CallMethodExpression true when Model::LiteralInteger # Only decimal integers can represent variables, else it is a number o.radix == 10 else false end end # Checks if the object is already a model object, or build it def to_ops(o, *args) case o when Model::PopsObject o when Puppet::Pops::Model::Factory o.current else build(o, *args) end end def self.concat(*args) new(args.map do |e| e = e.current if e.is_a?(self) case e when Model::LiteralString e.value when String e else raise ArgumentError, "can only concatenate strings, got #{e.class}" end end.join('')) end end diff --git a/lib/puppet/pops/model/model.rb b/lib/puppet/pops/model/model.rb index b186aca3f..5337ce264 100644 --- a/lib/puppet/pops/model/model.rb +++ b/lib/puppet/pops/model/model.rb @@ -1,606 +1,610 @@ # # The Puppet Pops Metamodel # # This module contains a formal description of the Puppet Pops (*P*uppet *OP*eration instruction*S*). # It describes a Metamodel containing DSL instructions, a description of PuppetType and related # classes needed to evaluate puppet logic. # The metamodel resembles the existing AST model, but it is a semantic model of instructions and # the types that they operate on rather than an Abstract Syntax Tree, although closely related. # # The metamodel is anemic (has no behavior) except basic datatype and type # assertions and reference/containment assertions. # The metamodel is also a generalized description of the Puppet DSL to enable the # same metamodel to be used to express Puppet DSL models (instances) with different semantics as # the language evolves. # # The metamodel is concretized by a validator for a particular version of # the Puppet DSL language. # # This metamodel is expressed using RGen. # require 'rgen/metamodel_builder' module Puppet::Pops::Model extend RGen::MetamodelBuilder::ModuleExtension # A base class for modeled objects that makes them Visitable, and Adaptable. # class PopsObject < RGen::MetamodelBuilder::MMBase include Puppet::Pops::Visitable include Puppet::Pops::Adaptable include Puppet::Pops::Containment abstract end # A Positioned object has an offset measured in an opaque unit (representing characters) from the start # of a source text (starting # from 0), and a length measured in the same opaque unit. The resolution of the opaque unit requires the # aid of a Locator instance that knows about the measure. This information is stored in the model's # root node - a Program. # # The offset and length are optional if the source of the model is not from parsed text. # class Positioned < PopsObject abstract has_attr 'offset', Integer has_attr 'length', Integer end # @abstract base class for expressions class Expression < Positioned abstract end # A Nop - the "no op" expression. # @note not really needed since the evaluator can evaluate nil with the meaning of NoOp # @todo deprecate? May be useful if there is the need to differentiate between nil and Nop when transforming model. # class Nop < Expression end # A binary expression is abstract and has a left and a right expression. The order of evaluation # and semantics are determined by the concrete subclass. # class BinaryExpression < Expression abstract # # @!attribute [rw] left_expr # @return [Expression] contains_one_uni 'left_expr', Expression, :lowerBound => 1 contains_one_uni 'right_expr', Expression, :lowerBound => 1 end # An unary expression is abstract and contains one expression. The semantics are determined by # a concrete subclass. # class UnaryExpression < Expression abstract contains_one_uni 'expr', Expression, :lowerBound => 1 end # A class that simply evaluates to the contained expression. # It is of value in order to preserve user entered parentheses in transformations, and # transformations from model to source. # class ParenthesizedExpression < UnaryExpression; end # A boolean not expression, reversing the truth of the unary expr. # class NotExpression < UnaryExpression; end # An arithmetic expression reversing the polarity of the numeric unary expr. # class UnaryMinusExpression < UnaryExpression; end # An assignment expression assigns a value to the lval() of the left_expr. # class AssignmentExpression < BinaryExpression has_attr 'operator', RGen::MetamodelBuilder::DataTypes::Enum.new([:'=', :'+=', :'-=']), :lowerBound => 1 end # An arithmetic expression applies an arithmetic operator on left and right expressions. # class ArithmeticExpression < BinaryExpression has_attr 'operator', RGen::MetamodelBuilder::DataTypes::Enum.new([:'+', :'-', :'*', :'%', :'/', :'<<', :'>>' ]), :lowerBound => 1 end # A relationship expression associates the left and right expressions # class RelationshipExpression < BinaryExpression has_attr 'operator', RGen::MetamodelBuilder::DataTypes::Enum.new([:'->', :'<-', :'~>', :'<~']), :lowerBound => 1 end # A binary expression, that accesses the value denoted by right in left. i.e. typically # expressed concretely in a language as left[right]. # class AccessExpression < Expression contains_one_uni 'left_expr', Expression, :lowerBound => 1 contains_many_uni 'keys', Expression, :lowerBound => 1 end # A comparison expression compares left and right using a comparison operator. # class ComparisonExpression < BinaryExpression has_attr 'operator', RGen::MetamodelBuilder::DataTypes::Enum.new([:'==', :'!=', :'<', :'>', :'<=', :'>=' ]), :lowerBound => 1 end # A match expression matches left and right using a matching operator. # class MatchExpression < BinaryExpression has_attr 'operator', RGen::MetamodelBuilder::DataTypes::Enum.new([:'!~', :'=~']), :lowerBound => 1 end # An 'in' expression checks if left is 'in' right # class InExpression < BinaryExpression; end # A boolean expression applies a logical connective operator (and, or) to left and right expressions. # class BooleanExpression < BinaryExpression abstract end # An and expression applies the logical connective operator and to left and right expression # and does not evaluate the right expression if the left expression is false. # class AndExpression < BooleanExpression; end # An or expression applies the logical connective operator or to the left and right expression # and does not evaluate the right expression if the left expression is true # class OrExpression < BooleanExpression; end # A literal list / array containing 0:M expressions. # class LiteralList < Expression contains_many_uni 'values', Expression end # A Keyed entry has a key and a value expression. It is typically used as an entry in a Hash. # class KeyedEntry < Positioned contains_one_uni 'key', Expression, :lowerBound => 1 contains_one_uni 'value', Expression, :lowerBound => 1 end # A literal hash is a collection of KeyedEntry objects # class LiteralHash < Expression contains_many_uni 'entries', KeyedEntry end # A block contains a list of expressions # class BlockExpression < Expression contains_many_uni 'statements', Expression end # A case option entry in a CaseStatement # class CaseOption < Expression contains_many_uni 'values', Expression, :lowerBound => 1 contains_one_uni 'then_expr', Expression, :lowerBound => 1 end # A case expression has a test, a list of options (multi values => block map). # One CaseOption may contain a LiteralDefault as value. This option will be picked if nothing # else matched. # class CaseExpression < Expression contains_one_uni 'test', Expression, :lowerBound => 1 contains_many_uni 'options', CaseOption end # A query expression is an expression that is applied to some collection. # The contained optional expression may contain different types of relational expressions depending # on what the query is applied to. # class QueryExpression < Expression abstract contains_one_uni 'expr', Expression, :lowerBound => 0 end # An exported query is a special form of query that searches for exported objects. # class ExportedQuery < QueryExpression end # A virtual query is a special form of query that searches for virtual objects. # class VirtualQuery < QueryExpression end # An attribute operation sets or appends a value to a named attribute. # class AttributeOperation < Positioned has_attr 'attribute_name', String, :lowerBound => 1 has_attr 'operator', RGen::MetamodelBuilder::DataTypes::Enum.new([:'=>', :'+>', ]), :lowerBound => 1 contains_one_uni 'value_expr', Expression, :lowerBound => 1 end # An object that collects stored objects from the central cache and returns # them to the current host. Operations may optionally be applied. # class CollectExpression < Expression contains_one_uni 'type_expr', Expression, :lowerBound => 1 contains_one_uni 'query', QueryExpression, :lowerBound => 1 contains_many_uni 'operations', AttributeOperation end class Parameter < Positioned has_attr 'name', String, :lowerBound => 1 contains_one_uni 'value', Expression end # Abstract base class for definitions. # class Definition < Expression abstract end # Abstract base class for named and parameterized definitions. class NamedDefinition < Definition abstract has_attr 'name', String, :lowerBound => 1 contains_many_uni 'parameters', Parameter contains_one_uni 'body', Expression end + # A function written in the Puppet Langauge + class FunctionDefinition < NamedDefinition + end + # A resource type definition (a 'define' in the DSL). # class ResourceTypeDefinition < NamedDefinition end # A node definition matches hosts using Strings, or Regular expressions. It may inherit from # a parent node (also using a String or Regular expression). # class NodeDefinition < Definition contains_one_uni 'parent', Expression contains_many_uni 'host_matches', Expression, :lowerBound => 1 contains_one_uni 'body', Expression end class LocatableExpression < Expression has_many_attr 'line_offsets', Integer has_attr 'locator', Object, :lowerBound => 1, :transient => true module ClassModule # Go through the gymnastics of making either value or pattern settable # with synchronization to the other form. A derived value cannot be serialized # and we want to serialize the pattern. When recreating the object we need to # recreate it from the pattern string. # The below sets both values if one is changed. # def locator unless result = getLocator setLocator(result = Puppet::Pops::Parser::Locator.locator(source_text, source_ref(), line_offsets)) end result end end end # Contains one expression which has offsets reported virtually (offset against the Program's # overall locator). # class SubLocatedExpression < Expression contains_one_uni 'expr', Expression, :lowerBound => 1 # line offset index for contained expressions has_many_attr 'line_offsets', Integer # Number of preceding lines (before the line_offsets) has_attr 'leading_line_count', Integer # The offset of the leading source line (i.e. size of "left margin"). has_attr 'leading_line_offset', Integer # The locator for the sub-locatable's children (not for the sublocator itself) # The locator is not serialized and is recreated on demand from the indexing information # in self. # has_attr 'locator', Object, :lowerBound => 1, :transient => true module ClassModule def locator unless result = getLocator # Adapt myself to get the Locator for me adapter = Puppet::Pops::Adapters::SourcePosAdapter.adapt(self) # Get the program (root), and deal with case when not contained in a program program = eAllContainers.find {|c| c.is_a?(Program) } source_ref = program.nil? ? '' : program.source_ref # An outer locator is needed since SubLocator only deals with offsets. This outer locator # has 0,0 as origin. outer_locator = Puppet::Pops::Parser::Locator.locator(adpater.extract_text, source_ref, line_offsets) # Create a sublocator that describes an offset from the outer # NOTE: the offset of self is the same as the sublocator's leading_offset result = Puppet::Pops::Parser::Locator::SubLocator.new(outer_locator, leading_line_count, offset, leading_line_offset) setLocator(result) end result end end end # A heredoc is a wrapper around a LiteralString or a ConcatenatedStringExpression with a specification # of syntax. The expectation is that "syntax" has meaning to a validator. A syntax of nil or '' means # "unspecified syntax". # class HeredocExpression < Expression has_attr 'syntax', String contains_one_uni 'text_expr', Expression, :lowerBound => 1 end # A class definition # class HostClassDefinition < NamedDefinition has_attr 'parent_class', String end # i.e {|parameters| body } class LambdaExpression < Expression contains_many_uni 'parameters', Parameter contains_one_uni 'body', Expression end # If expression. If test is true, the then_expr part should be evaluated, else the (optional) # else_expr. An 'elsif' is simply an else_expr = IfExpression, and 'else' is simply else == Block. # a 'then' is typically a Block. # class IfExpression < Expression contains_one_uni 'test', Expression, :lowerBound => 1 contains_one_uni 'then_expr', Expression, :lowerBound => 1 contains_one_uni 'else_expr', Expression end # An if expression with boolean reversed test. # class UnlessExpression < IfExpression end # An abstract call. # class CallExpression < Expression abstract # A bit of a crutch; functions are either procedures (void return) or has an rvalue # this flag tells the evaluator that it is a failure to call a function that is void/procedure # where a value is expected. # has_attr 'rval_required', Boolean, :defaultValueLiteral => "false" contains_one_uni 'functor_expr', Expression, :lowerBound => 1 contains_many_uni 'arguments', Expression contains_one_uni 'lambda', Expression end # A function call where the functor_expr should evaluate to something callable. # class CallFunctionExpression < CallExpression; end # A function call where the given functor_expr should evaluate to the name # of a function. # class CallNamedFunctionExpression < CallExpression; end # A method/function call where the function expr is a NamedAccess and with support for # an optional lambda block # class CallMethodExpression < CallExpression end # Abstract base class for literals. # class Literal < Expression abstract end # A literal value is an abstract value holder. The type of the contained value is # determined by the concrete subclass. # class LiteralValue < Literal abstract end # A Regular Expression Literal. # class LiteralRegularExpression < LiteralValue has_attr 'value', Object, :lowerBound => 1, :transient => true has_attr 'pattern', String, :lowerBound => 1 module ClassModule # Go through the gymnastics of making either value or pattern settable # with synchronization to the other form. A derived value cannot be serialized # and we want to serialize the pattern. When recreating the object we need to # recreate it from the pattern string. # The below sets both values if one is changed. # def value= regexp setValue regexp setPattern regexp.to_s end def pattern= regexp_string setPattern regexp_string setValue Regexp.new(regexp_string) end end end # A Literal String # class LiteralString < LiteralValue has_attr 'value', String, :lowerBound => 1 end class LiteralNumber < LiteralValue abstract end # A literal number has a radix of decimal (10), octal (8), or hex (16) to enable string conversion with the input radix. # By default, a radix of 10 is used. # class LiteralInteger < LiteralNumber has_attr 'radix', Integer, :lowerBound => 1, :defaultValueLiteral => "10" has_attr 'value', Integer, :lowerBound => 1 end class LiteralFloat < LiteralNumber has_attr 'value', Float, :lowerBound => 1 end # The DSL `undef`. # class LiteralUndef < Literal; end # The DSL `default` class LiteralDefault < Literal; end # DSL `true` or `false` class LiteralBoolean < LiteralValue has_attr 'value', Boolean, :lowerBound => 1 end # A text expression is an interpolation of an expression. If the embedded expression is # a QualifiedName, it is taken as a variable name and resolved. All other expressions are evaluated. # The result is transformed to a string. # class TextExpression < UnaryExpression; end # An interpolated/concatenated string. The contained segments are expressions. Verbatim sections # should be LiteralString instances, and interpolated expressions should either be # TextExpression instances (if QualifiedNames should be turned into variables), or any other expression # if such treatment is not needed. # class ConcatenatedString < Expression contains_many_uni 'segments', Expression end # A DSL NAME (one or multiple parts separated by '::'). # class QualifiedName < LiteralValue has_attr 'value', String, :lowerBound => 1 end # A DSL CLASSREF (one or multiple parts separated by '::' where (at least) the first part starts with an upper case letter). # class QualifiedReference < LiteralValue has_attr 'value', String, :lowerBound => 1 end # A Variable expression looks up value of expr (some kind of name) in scope. # The expression is typically a QualifiedName, or QualifiedReference. # class VariableExpression < UnaryExpression; end # Epp start class EppExpression < Expression has_attr 'see_scope', Boolean contains_one_uni 'body', Expression end # A string to render class RenderStringExpression < LiteralString end # An expression to evluate and render class RenderExpression < UnaryExpression end # A resource body describes one resource instance # class ResourceBody < Positioned contains_one_uni 'title', Expression contains_many_uni 'operations', AttributeOperation end # An abstract resource describes the form of the resource (regular, virtual or exported) # and adds convenience methods to ask if it is virtual or exported. # All derived classes may not support all forms, and these needs to be validated # class AbstractResource < Expression abstract has_attr 'form', RGen::MetamodelBuilder::DataTypes::Enum.new([:regular, :virtual, :exported ]), :lowerBound => 1, :defaultValueLiteral => "regular" has_attr 'virtual', Boolean, :derived => true has_attr 'exported', Boolean, :derived => true module ClassModule def virtual_derived form == :virtual || form == :exported end def exported_derived form == :exported end end end # A resource expression is used to instantiate one or many resource. Resources may optionally # be virtual or exported, an exported resource is always virtual. # class ResourceExpression < AbstractResource contains_one_uni 'type_name', Expression, :lowerBound => 1 contains_many_uni 'bodies', ResourceBody end # A resource defaults sets defaults for a resource type. This class inherits from AbstractResource # but does only support the :regular form (this is intentional to be able to produce better error messages # when illegal forms are applied to a model. # class ResourceDefaultsExpression < AbstractResource contains_one_uni 'type_ref', QualifiedReference contains_many_uni 'operations', AttributeOperation end # A resource override overrides already set values. # class ResourceOverrideExpression < Expression contains_one_uni 'resources', Expression, :lowerBound => 1 contains_many_uni 'operations', AttributeOperation end # A selector entry describes a map from matching_expr to value_expr. # class SelectorEntry < Positioned contains_one_uni 'matching_expr', Expression, :lowerBound => 1 contains_one_uni 'value_expr', Expression, :lowerBound => 1 end # A selector expression represents a mapping from a left_expr to a matching SelectorEntry. # class SelectorExpression < Expression contains_one_uni 'left_expr', Expression, :lowerBound => 1 contains_many_uni 'selectors', SelectorEntry end # A named access expression looks up a named part. (e.g. $a.b) # class NamedAccessExpression < BinaryExpression; end # A Program is the top level construct returned by the parser # it contains the parsed result in the body, and has a reference to the full source text, # and its origin. The line_offset's is an array with the start offset of each line. # class Program < PopsObject contains_one_uni 'body', Expression has_many 'definitions', Definition has_attr 'source_text', String has_attr 'source_ref', String has_many_attr 'line_offsets', Integer has_attr 'locator', Object, :lowerBound => 1, :transient => true module ClassModule def locator unless result = getLocator setLocator(result = Puppet::Pops::Parser::Locator.locator(source_text, source_ref(), line_offsets)) end result end end end end diff --git a/lib/puppet/pops/model/model_label_provider.rb b/lib/puppet/pops/model/model_label_provider.rb index 979aa4bc5..dd4531761 100644 --- a/lib/puppet/pops/model/model_label_provider.rb +++ b/lib/puppet/pops/model/model_label_provider.rb @@ -1,104 +1,105 @@ # A provider of labels for model object, producing a human name for the model object. # As an example, if object is an ArithmeticExpression with operator +, `#a_an(o)` produces "a '+' Expression", # #the(o) produces "the + Expression", and #label produces "+ Expression". # class Puppet::Pops::Model::ModelLabelProvider < Puppet::Pops::LabelProvider def initialize @@label_visitor ||= Puppet::Pops::Visitor.new(self,"label",0,0) end # Produces a label for the given objects type/operator without article. # If a Class is given, its name is used as label # def label o @@label_visitor.visit(o) end def label_Factory o ; label(o.current) end def label_Array o ; "Array" end def label_LiteralInteger o ; "Literal Integer" end def label_LiteralFloat o ; "Literal Float" end def label_ArithmeticExpression o ; "'#{o.operator}' expression" end def label_AccessExpression o ; "'[]' expression" end def label_MatchExpression o ; "'#{o.operator}' expression" end def label_CollectExpression o ; label(o.query) end def label_EppExpression o ; "Epp Template" end def label_ExportedQuery o ; "Exported Query" end def label_VirtualQuery o ; "Virtual Query" end def label_QueryExpression o ; "Collect Query" end def label_ComparisonExpression o ; "'#{o.operator}' expression" end def label_AndExpression o ; "'and' expression" end def label_OrExpression o ; "'or' expression" end def label_InExpression o ; "'in' expression" end def label_AssignmentExpression o ; "'#{o.operator}' expression" end def label_AttributeOperation o ; "'#{o.operator}' expression" end def label_LiteralList o ; "Array Expression" end def label_LiteralHash o ; "Hash Expression" end def label_KeyedEntry o ; "Hash Entry" end def label_LiteralBoolean o ; "Boolean" end def label_TrueClass o ; "Boolean" end def label_FalseClass o ; "Boolean" end def label_LiteralString o ; "String" end def label_LambdaExpression o ; "Lambda" end def label_LiteralDefault o ; "'default' expression" end def label_LiteralUndef o ; "'undef' expression" end def label_LiteralRegularExpression o ; "Regular Expression" end def label_Nop o ; "Nop Expression" end def label_NamedAccessExpression o ; "'.' expression" end def label_NilClass o ; "Nil Object" end def label_NotExpression o ; "'not' expression" end def label_VariableExpression o ; "Variable" end def label_TextExpression o ; "Expression in Interpolated String" end def label_UnaryMinusExpression o ; "Unary Minus" end def label_BlockExpression o ; "Block Expression" end def label_ConcatenatedString o ; "Double Quoted String" end def label_HeredocExpression o ; "'@(#{o.syntax})' expression" end def label_HostClassDefinition o ; "Host Class Definition" end def label_NodeDefinition o ; "Node Definition" end def label_ResourceTypeDefinition o ; "'define' expression" end def label_ResourceOverrideExpression o ; "Resource Override" end def label_Parameter o ; "Parameter Definition" end def label_ParenthesizedExpression o ; "Parenthesized Expression" end def label_IfExpression o ; "'if' statement" end def label_UnlessExpression o ; "'unless' Statement" end def label_CallNamedFunctionExpression o ; "Function Call" end def label_CallMethodExpression o ; "Method call" end def label_CaseExpression o ; "'case' statement" end def label_CaseOption o ; "Case Option" end def label_RenderStringExpression o ; "Epp Text" end def label_RenderExpression o ; "Epp Interpolated Expression" end def label_RelationshipExpression o ; "'#{o.operator}' expression" end def label_ResourceBody o ; "Resource Instance Definition" end def label_ResourceDefaultsExpression o ; "Resource Defaults Expression" end def label_ResourceExpression o ; "Resource Statement" end def label_SelectorExpression o ; "Selector Expression" end def label_SelectorEntry o ; "Selector Option" end def label_Integer o ; "Integer" end def label_Fixnum o ; "Integer" end def label_Bignum o ; "Integer" end def label_Float o ; "Float" end def label_String o ; "String" end def label_Regexp o ; "Regexp" end def label_Object o ; "Object" end def label_Hash o ; "Hash" end def label_QualifiedName o ; "Name" end def label_QualifiedReference o ; "Type-Name" end + def label_FunctionDefinition o ; "Function" end def label_PAbstractType o ; "#{Puppet::Pops::Types::TypeCalculator.string(o)}-Type" end def label_PResourceType o if o.title "#{Puppet::Pops::Types::TypeCalculator.string(o)} Resource-Reference" else "#{Puppet::Pops::Types::TypeCalculator.string(o)}-Type" end end def label_Class o if o <= Puppet::Pops::Types::PAbstractType simple_name = o.name.split('::').last simple_name[1..-5] + "-Type" else o.name end end end diff --git a/lib/puppet/pops/model/model_tree_dumper.rb b/lib/puppet/pops/model/model_tree_dumper.rb index 1c4bf19cd..a0e94a60e 100644 --- a/lib/puppet/pops/model/model_tree_dumper.rb +++ b/lib/puppet/pops/model/model_tree_dumper.rb @@ -1,377 +1,390 @@ # Dumps a Pops::Model in reverse polish notation; i.e. LISP style # The intention is to use this for debugging output # TODO: BAD NAME - A DUMP is a Ruby Serialization # class Puppet::Pops::Model::ModelTreeDumper < Puppet::Pops::Model::TreeDumper def dump_Array o o.collect {|e| do_dump(e) } end def dump_LiteralFloat o o.value.to_s end def dump_LiteralInteger o case o.radix when 10 o.value.to_s when 8 "0%o" % o.value when 16 "0x%X" % o.value else "bad radix:" + o.value.to_s end end def dump_LiteralValue o o.value.to_s end def dump_Factory o do_dump(o.current) end def dump_ArithmeticExpression o [o.operator.to_s, do_dump(o.left_expr), do_dump(o.right_expr)] end # x[y] prints as (slice x y) def dump_AccessExpression o if o.keys.size <= 1 ["slice", do_dump(o.left_expr), do_dump(o.keys[0])] else ["slice", do_dump(o.left_expr), do_dump(o.keys)] end end def dump_MatchesExpression o [o.operator.to_s, do_dump(o.left_expr), do_dump(o.right_expr)] end def dump_CollectExpression o result = ["collect", do_dump(o.type_expr), :indent, :break, do_dump(o.query), :indent] o.operations do |ao| result << :break << do_dump(ao) end result += [:dedent, :dedent ] result end def dump_EppExpression o result = ["epp"] # result << ["parameters"] + o.parameters.collect {|p| do_dump(p) } if o.parameters.size() > 0 if o.body result << do_dump(o.body) else result << [] end result end def dump_ExportedQuery o result = ["<<| |>>"] result += dump_QueryExpression(o) unless is_nop?(o.expr) result end def dump_VirtualQuery o result = ["<| |>"] result += dump_QueryExpression(o) unless is_nop?(o.expr) result end def dump_QueryExpression o [do_dump(o.expr)] end def dump_ComparisonExpression o [o.operator.to_s, do_dump(o.left_expr), do_dump(o.right_expr)] end def dump_AndExpression o ["&&", do_dump(o.left_expr), do_dump(o.right_expr)] end def dump_OrExpression o ["||", do_dump(o.left_expr), do_dump(o.right_expr)] end def dump_InExpression o ["in", do_dump(o.left_expr), do_dump(o.right_expr)] end def dump_AssignmentExpression o [o.operator.to_s, do_dump(o.left_expr), do_dump(o.right_expr)] end # Produces (name => expr) or (name +> expr) def dump_AttributeOperation o [o.attribute_name, o.operator, do_dump(o.value_expr)] end def dump_LiteralList o ["[]"] + o.values.collect {|x| do_dump(x)} end def dump_LiteralHash o ["{}"] + o.entries.collect {|x| do_dump(x)} end def dump_KeyedEntry o [do_dump(o.key), do_dump(o.value)] end def dump_MatchExpression o [o.operator.to_s, do_dump(o.left_expr), do_dump(o.right_expr)] end def dump_LiteralString o "'#{o.value}'" end def dump_LambdaExpression o result = ["lambda"] result << ["parameters"] + o.parameters.collect {|p| do_dump(p) } if o.parameters.size() > 0 if o.body result << do_dump(o.body) else result << [] end result end def dump_LiteralDefault o ":default" end def dump_LiteralUndef o ":undef" end def dump_LiteralRegularExpression o "/#{o.value.source}/" end def dump_Nop o ":nop" end def dump_NamedAccessExpression o [".", do_dump(o.left_expr), do_dump(o.right_expr)] end def dump_NilClass o "()" end def dump_NotExpression o ['!', dump(o.expr)] end def dump_VariableExpression o "$#{dump(o.expr)}" end # Interpolation (to string) shown as (str expr) def dump_TextExpression o ["str", do_dump(o.expr)] end def dump_UnaryMinusExpression o ['-', do_dump(o.expr)] end def dump_BlockExpression o ["block"] + o.statements.collect {|x| do_dump(x) } end # Interpolated strings are shown as (cat seg0 seg1 ... segN) def dump_ConcatenatedString o ["cat"] + o.segments.collect {|x| do_dump(x)} end def dump_HeredocExpression(o) result = ["@(#{o.syntax})", :indent, :break, do_dump(o.text_expr), :dedent, :break] end def dump_HostClassDefinition o result = ["class", o.name] result << ["inherits", o.parent_class] if o.parent_class result << ["parameters"] + o.parameters.collect {|p| do_dump(p) } if o.parameters.size() > 0 if o.body result << do_dump(o.body) else result << [] end result end def dump_NodeDefinition o result = ["node"] result << ["matches"] + o.host_matches.collect {|m| do_dump(m) } result << ["parent", do_dump(o.parent)] if o.parent if o.body result << do_dump(o.body) else result << [] end result end - def dump_ResourceTypeDefinition o - result = ["define", o.name] + 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_FunctionDefinition o + result = dump_NamedDefinition(o) + result[0] = 'function' + result + end + def dump_ResourceOverrideExpression o result = ["override", do_dump(o.resources), :indent] o.operations.each do |p| result << :break << do_dump(p) end result << :dedent result end # Produces parameters as name, or (= name value) def dump_Parameter o if o.value ["=", o.name, do_dump(o.value)] else o.name end end def dump_ParenthesizedExpression o do_dump(o.expr) end # Hides that Program exists in the output (only its body is shown), the definitions are just # references to contained classes, resource types, and nodes def dump_Program(o) dump(o.body) end def dump_IfExpression o result = ["if", do_dump(o.test), :indent, :break, ["then", :indent, do_dump(o.then_expr), :dedent]] result += [:break, ["else", :indent, do_dump(o.else_expr), :dedent], :dedent] unless is_nop? o.else_expr result end def dump_UnlessExpression o result = ["unless", do_dump(o.test), :indent, :break, ["then", :indent, do_dump(o.then_expr), :dedent]] result += [:break, ["else", :indent, do_dump(o.else_expr), :dedent], :dedent] unless is_nop? o.else_expr result end # Produces (invoke name args...) when not required to produce an rvalue, and # (call name args ... ) otherwise. # def dump_CallNamedFunctionExpression o result = [o.rval_required ? "call" : "invoke", do_dump(o.functor_expr)] o.arguments.collect {|a| result << do_dump(a) } result end # def dump_CallNamedFunctionExpression o # result = [o.rval_required ? "call" : "invoke", do_dump(o.functor_expr)] # o.arguments.collect {|a| result << do_dump(a) } # result # end def dump_CallMethodExpression o result = [o.rval_required ? "call-method" : "invoke-method", do_dump(o.functor_expr)] o.arguments.collect {|a| result << do_dump(a) } result << do_dump(o.lambda) if o.lambda result end def dump_CaseExpression o result = ["case", do_dump(o.test), :indent] o.options.each do |s| result << :break << do_dump(s) end result << :dedent end def dump_CaseOption o result = ["when"] result << o.values.collect {|x| do_dump(x) } result << ["then", do_dump(o.then_expr) ] result end def dump_RelationshipExpression o [o.operator.to_s, do_dump(o.left_expr), do_dump(o.right_expr)] end def dump_RenderStringExpression o ["render-s", " '#{o.value}'"] end def dump_RenderExpression o ["render", do_dump(o.expr)] end def dump_ResourceBody o result = [do_dump(o.title), :indent] o.operations.each do |p| result << :break << do_dump(p) end result << :dedent result end def dump_ResourceDefaultsExpression o result = ["resource-defaults", do_dump(o.type_ref), :indent] o.operations.each do |p| result << :break << do_dump(p) end result << :dedent result end def dump_ResourceExpression o form = o.form == :regular ? '' : o.form.to_s + "-" result = [form+"resource", do_dump(o.type_name), :indent] o.bodies.each do |b| result << :break << do_dump(b) end result << :dedent result end def dump_SelectorExpression o ["?", do_dump(o.left_expr)] + o.selectors.collect {|x| do_dump(x) } end def dump_SelectorEntry o [do_dump(o.matching_expr), "=>", do_dump(o.value_expr)] end def dump_SubLocatedExpression o ["sublocated", do_dump(o.expr)] end def dump_Object o [o.class.to_s, o.to_s] end def is_nop? o o.nil? || o.is_a?(Puppet::Pops::Model::Nop) end end diff --git a/lib/puppet/pops/parser/egrammar.ra b/lib/puppet/pops/parser/egrammar.ra index 1660b47a1..b23b04f09 100644 --- a/lib/puppet/pops/parser/egrammar.ra +++ b/lib/puppet/pops/parser/egrammar.ra @@ -1,752 +1,762 @@ # vim: syntax=ruby # Parser using the Pops model, expression based class Puppet::Pops::Parser::Parser token STRING DQPRE DQMID DQPOST token LBRACK RBRACK LBRACE RBRACE SYMBOL FARROW COMMA TRUE token FALSE EQUALS APPENDS DELETES LESSEQUAL NOTEQUAL DOT COLON LLCOLLECT RRCOLLECT token QMARK LPAREN RPAREN ISEQUAL GREATEREQUAL GREATERTHAN LESSTHAN token IF ELSE token DEFINE ELSIF VARIABLE CLASS INHERITS NODE BOOLEAN token NAME SEMIC CASE DEFAULT AT ATAT LCOLLECT RCOLLECT CLASSREF token NOT OR AND UNDEF PARROW PLUS MINUS TIMES DIV LSHIFT RSHIFT UMINUS token MATCH NOMATCH REGEX IN_EDGE OUT_EDGE IN_EDGE_SUB OUT_EDGE_SUB token IN UNLESS PIPE token LAMBDA SELBRACE token NUMBER token HEREDOC SUBLOCATE token RENDER_STRING RENDER_EXPR EPP_START EPP_END EPP_END_TRIM +token FUNCTION token LOW prechigh left HIGH left SEMIC left PIPE left LPAREN left RPAREN left AT ATAT left DOT left CALL nonassoc EPP_START left LBRACK LISTSTART left RBRACK left QMARK left LCOLLECT LLCOLLECT right NOT nonassoc UMINUS left IN left MATCH NOMATCH left TIMES DIV MODULO left MINUS PLUS left LSHIFT RSHIFT left NOTEQUAL ISEQUAL left GREATEREQUAL GREATERTHAN LESSTHAN LESSEQUAL left AND left OR right APPENDS DELETES EQUALS left LBRACE left SELBRACE left RBRACE left IN_EDGE OUT_EDGE IN_EDGE_SUB OUT_EDGE_SUB left TITLE_COLON left CASE_COLON left FARROW left COMMA nonassoc RENDER_EXPR nonassoc RENDER_STRING left LOW preclow rule # Produces [Model::BlockExpression, Model::Expression, nil] depending on multiple statements, single statement or empty program : statements { result = create_program(Factory.block_or_expression(*val[0])) } | epp_expression { result = create_program(Factory.block_or_expression(*val[0])) } | nil # Produces a semantic model (non validated, but semantically adjusted). statements : syntactic_statements { result = transform_calls(val[0]) } # Change may have issues with nil; i.e. program is a sequence of nils/nops # Simplified from original which had validation for top level constructs - see statement rule # Produces Array syntactic_statements : syntactic_statement { result = [val[0]]} | syntactic_statements SEMIC syntactic_statement { result = val[0].push val[2] } | syntactic_statements syntactic_statement { result = val[0].push val[1] } # Produce a single expression or Array of expression syntactic_statement : any_expression { result = val[0] } | syntactic_statement COMMA any_expression { result = aryfy(val[0]).push val[2] } any_expression : relationship_expression relationship_expression : resource_expression =LOW { result = val[0] } | relationship_expression IN_EDGE relationship_expression { result = val[0].relop(val[1][:value], val[2]); loc result, val[1] } | relationship_expression IN_EDGE_SUB relationship_expression { result = val[0].relop(val[1][:value], val[2]); loc result, val[1] } | relationship_expression OUT_EDGE relationship_expression { result = val[0].relop(val[1][:value], val[2]); loc result, val[1] } | relationship_expression OUT_EDGE_SUB relationship_expression { result = val[0].relop(val[1][:value], val[2]); loc result, val[1] } #---EXPRESSION # # Produces Model::Expression expression : higher_precedence | expression LBRACK expressions RBRACK =LBRACK { result = val[0][*val[2]] ; loc result, val[0], val[3] } | expression IN expression { result = val[0].in val[2] ; loc result, val[1] } | expression MATCH expression { result = val[0] =~ val[2] ; loc result, val[1] } | expression NOMATCH expression { result = val[0].mne val[2] ; loc result, val[1] } | expression PLUS expression { result = val[0] + val[2] ; loc result, val[1] } | expression MINUS expression { result = val[0] - val[2] ; loc result, val[1] } | expression DIV expression { result = val[0] / val[2] ; loc result, val[1] } | expression TIMES expression { result = val[0] * val[2] ; loc result, val[1] } | expression MODULO expression { result = val[0] % val[2] ; loc result, val[1] } | expression LSHIFT expression { result = val[0] << val[2] ; loc result, val[1] } | expression RSHIFT expression { result = val[0] >> val[2] ; loc result, val[1] } | MINUS expression =UMINUS { result = val[1].minus() ; loc result, val[0] } | expression NOTEQUAL expression { result = val[0].ne val[2] ; loc result, val[1] } | expression ISEQUAL expression { result = val[0] == val[2] ; loc result, val[1] } | expression GREATERTHAN expression { result = val[0] > val[2] ; loc result, val[1] } | expression GREATEREQUAL expression { result = val[0] >= val[2] ; loc result, val[1] } | expression LESSTHAN expression { result = val[0] < val[2] ; loc result, val[1] } | expression LESSEQUAL expression { result = val[0] <= val[2] ; loc result, val[1] } | NOT expression { result = val[1].not ; loc result, val[0] } | expression AND expression { result = val[0].and val[2] ; loc result, val[1] } | expression OR expression { result = val[0].or val[2] ; loc result, val[1] } | expression EQUALS expression { result = val[0].set(val[2]) ; loc result, val[1] } | expression APPENDS expression { result = val[0].plus_set(val[2]) ; loc result, val[1] } | expression DELETES expression { result = val[0].minus_set(val[2]); loc result, val[1] } | expression QMARK selector_entries { result = val[0].select(*val[2]) ; loc result, val[0] } | LPAREN expression RPAREN { result = val[1].paren() ; loc result, val[0] } #---EXPRESSIONS # (e.g. argument list) # # This expression list can not contain function calls without parentheses around arguments # Produces Array expressions : expression { result = [val[0]] } | expressions COMMA expression { result = val[0].push(val[2]) } # These go through a chain of left recursion, ending with primary_expression higher_precedence : call_function_expression primary_expression : literal_expression | variable | call_method_with_lambda_expression | collection_expression | case_expression | if_expression | unless_expression | definition_expression | hostclass_expression | node_definition_expression | epp_render_expression + | function_definition -# Aleways have the same value +# Allways have the same value literal_expression : array | boolean | default | hash | regex | text_or_name | number | type | undef text_or_name : name { result = val[0] } | quotedtext { result = val[0] } #---CALL FUNCTION # # Produces Model::CallNamedFunction call_function_expression : primary_expression LPAREN expressions endcomma RPAREN { result = Factory.CALL_NAMED(val[0], true, val[2]) loc result, val[0], val[4] } | primary_expression LPAREN RPAREN { result = Factory.CALL_NAMED(val[0], true, []) loc result, val[0], val[2] } | primary_expression LPAREN expressions endcomma RPAREN lambda { result = Factory.CALL_NAMED(val[0], true, val[2]) loc result, val[0], val[4] result.lambda = val[5] } | primary_expression LPAREN RPAREN lambda { result = Factory.CALL_NAMED(val[0], true, []) loc result, val[0], val[2] result.lambda = val[3] } | primary_expression = LOW { result = val[0] } #---CALL METHOD # call_method_with_lambda_expression : call_method_expression =LOW { result = val[0] } | call_method_expression lambda { result = val[0]; val[0].lambda = val[1] } call_method_expression : named_access LPAREN expressions RPAREN { result = Factory.CALL_METHOD(val[0], val[2]); loc result, val[1], val[3] } | named_access LPAREN RPAREN { result = Factory.CALL_METHOD(val[0], []); loc result, val[1], val[3] } | named_access =LOW { result = Factory.CALL_METHOD(val[0], []); loc result, val[0] } # TODO: It may be of value to access named elements of types too named_access : expression DOT NAME { result = val[0].dot(Factory.fqn(val[2][:value])) loc result, val[1], val[2] } #---LAMBDA # # This is a temporary switch while experimenting with concrete syntax # One should be picked for inclusion in puppet. # Lambda with parameters to the left of the body lambda : lambda_parameter_list lambda_rest { result = Factory.LAMBDA(val[0], val[1]) # loc result, val[1] # TODO } lambda_rest : LBRACE statements RBRACE { result = val[1] } | LBRACE RBRACE { result = nil } # Produces Array lambda_parameter_list : PIPE PIPE { result = [] } | PIPE parameters endcomma PIPE { result = val[1] } #---CONDITIONALS # #--IF # # Produces Model::IfExpression if_expression : IF if_part { result = val[1] loc(result, val[0], val[1]) } # Produces Model::IfExpression if_part : expression LBRACE statements RBRACE else { result = Factory.IF(val[0], Factory.block_or_expression(*val[2]), val[4]) loc(result, val[0], (val[4] ? val[4] : val[3])) } | expression LBRACE RBRACE else { result = Factory.IF(val[0], nil, val[3]) loc(result, val[0], (val[3] ? val[3] : val[2])) } # Produces [Model::Expression, nil] - nil if there is no else or elsif part else : # nothing | ELSIF if_part { result = val[1] loc(result, val[0], val[1]) } | ELSE LBRACE statements RBRACE { result = Factory.block_or_expression(*val[2]) loc result, val[0], val[3] } | ELSE LBRACE RBRACE { result = nil # don't think a nop is needed here either } #--UNLESS # # Changed from Puppet 3x where there is no else part on unless # unless_expression : UNLESS expression LBRACE statements RBRACE unless_else { result = Factory.UNLESS(val[1], Factory.block_or_expression(*val[3]), val[5]) loc result, val[0], val[4] } | UNLESS expression LBRACE RBRACE unless_else { result = Factory.UNLESS(val[1], nil, nil) loc result, val[0], val[4] } # Different from else part of if, since "elsif" is not supported, but 'else' is # # Produces [Model::Expression, nil] - nil if there is no else or elsif part unless_else : # nothing | ELSE LBRACE statements RBRACE { result = Factory.block_or_expression(*val[2]) loc result, val[0], val[3] } | ELSE LBRACE RBRACE { result = nil # don't think a nop is needed here either } #--- CASE EXPRESSION # # Produces Model::CaseExpression case_expression : CASE expression LBRACE case_options RBRACE { result = Factory.CASE(val[1], *val[3]) loc result, val[0], val[4] } # Produces Array case_options : case_option { result = [val[0]] } | case_options case_option { result = val[0].push val[1] } # Produced Model::CaseOption (aka When) case_option : expressions case_colon LBRACE statements RBRACE { result = Factory.WHEN(val[0], val[3]) loc result, val[1], val[4] } | expressions case_colon LBRACE RBRACE = LOW { result = Factory.WHEN(val[0], nil) loc result, val[1], val[3] } case_colon: COLON =CASE_COLON { result = val[0] } # This special construct is required or racc will produce the wrong result when the selector entry # LHS is generalized to any expression (LBRACE looks like a hash). Thus it is not possible to write # a selector with a single entry where the entry LHS is a hash. # The SELBRACE token is a LBRACE that follows a QMARK, and this is produced by the lexer with a lookback # Produces Array # selector_entries : selector_entry | SELBRACE selector_entry_list endcomma RBRACE { result = val[1] } # Produces Array selector_entry_list : selector_entry { result = [val[0]] } | selector_entry_list COMMA selector_entry { result = val[0].push val[2] } # Produces a Model::SelectorEntry # This FARROW wins over FARROW in Hash selector_entry : expression FARROW expression { result = Factory.MAP(val[0], val[2]) ; loc result, val[1] } #---RESOURCE # # Produces [Model::ResourceExpression, Model::ResourceDefaultsExpression] # The resource expression parses a generalized syntax and then selects the correct # resulting model based on the combinatoin of the LHS and what follows. # It also handled exported and virtual resources, and the class case # resource_expression : expression =LOW { result = val[0] } | at expression LBRACE resourceinstances endsemi RBRACE { result = case Factory.resource_shape(val[1]) when :resource, :class tmp = Factory.RESOURCE(Factory.fqn(token_text(val[1])), val[3]) tmp.form = val[0] tmp when :defaults error val[1], "A resource default can not be virtual or exported" when :override error val[1], "A resource override can not be virtual or exported" else error val[1], "Expression is not valid as a resource, resource-default, or resource-override" end loc result, val[1], val[4] } | at expression LBRACE attribute_operations endcomma RBRACE { result = case Factory.resource_shape(val[1]) when :resource, :class, :defaults, :override error val[1], "Defaults are not virtualizable" else error val[1], "Expression is not valid as a resource, resource-default, or resource-override" end } | expression LBRACE resourceinstances endsemi RBRACE { result = case Factory.resource_shape(val[0]) when :resource, :class Factory.RESOURCE(Factory.fqn(token_text(val[0])), val[2]) when :defaults error val[1], "A resource default can not specify a resource name" when :override error val[1], "A resource override does not allow override of name of resource" else error val[1], "Expression is not valid as a resource, resource-default, or resource-override" end loc result, val[0], val[4] } | expression LBRACE attribute_operations endcomma RBRACE { result = case Factory.resource_shape(val[0]) when :resource, :class # This catches deprecated syntax. # If the attribute operations does not include +>, then the found expression # is actually a LEFT followed by LITERAL_HASH # unless tmp = transform_resource_wo_title(val[0], val[2]) error val[1], "Syntax error resource body without title or hash with +>" end tmp when :defaults Factory.RESOURCE_DEFAULTS(val[0], val[2]) when :override # This was only done for override in original - TODO shuld it be here at all Factory.RESOURCE_OVERRIDE(val[0], val[2]) else error val[0], "Expression is not valid as a resource, resource-default, or resource-override" end loc result, val[0], val[4] } | at CLASS LBRACE resourceinstances endsemi RBRACE { result = Factory.RESOURCE(Factory.fqn(token_text(val[1])), val[3]) result.form = val[0] loc result, val[1], val[5] } | CLASS LBRACE resourceinstances endsemi RBRACE { result = Factory.RESOURCE(Factory.fqn(token_text(val[0])), val[2]) loc result, val[0], val[4] } resourceinst : expression title_colon attribute_operations endcomma { result = Factory.RESOURCE_BODY(val[0], val[2]) } title_colon : COLON =TITLE_COLON { result = val[0] } resourceinstances : resourceinst { result = [val[0]] } | resourceinstances SEMIC resourceinst { result = val[0].push val[2] } # Produces Symbol corresponding to resource form # at : AT { result = :virtual } | AT AT { result = :exported } | ATAT { result = :exported } #---COLLECTION # # A Collection is a predicate applied to a set of objects with an implied context (used variables are # attributes of the object. # i.e. this is equivalent for source.select(QUERY).apply(ATTRIBUTE_OPERATIONS) # # Produces Model::CollectExpression # collection_expression : expression collect_query LBRACE attribute_operations endcomma RBRACE { result = Factory.COLLECT(val[0], val[1], val[3]) loc result, val[0], val[5] } | expression collect_query =LOW { result = Factory.COLLECT(val[0], val[1], []) loc result, val[0], val[1] } collect_query : LCOLLECT optional_query RCOLLECT { result = Factory.VIRTUAL_QUERY(val[1]) ; loc result, val[0], val[2] } | LLCOLLECT optional_query RRCOLLECT { result = Factory.EXPORTED_QUERY(val[1]) ; loc result, val[0], val[2] } optional_query : nil | expression #---ATTRIBUTE OPERATIONS # # (Not an expression) # # Produces Array # attribute_operations : { result = [] } | attribute_operation { result = [val[0]] } | attribute_operations COMMA attribute_operation { result = val[0].push(val[2]) } # Produces String # QUESTION: Why is BOOLEAN valid as an attribute name? # attribute_name : NAME | keyword | BOOLEAN # In this version, illegal combinations are validated instead of producing syntax errors # (Can give nicer error message "+> is not applicable to...") # Produces Model::AttributeOperation # attribute_operation : attribute_name FARROW expression { result = Factory.ATTRIBUTE_OP(val[0][:value], :'=>', val[2]) loc result, val[0], val[2] } | attribute_name PARROW expression { result = Factory.ATTRIBUTE_OP(val[0][:value], :'+>', val[2]) loc result, val[0], val[2] } #---DEFINE # # Produces Model::Definition # definition_expression : DEFINE classname parameter_list LBRACE opt_statements RBRACE { result = add_definition(Factory.DEFINITION(classname(val[1][:value]), val[2], val[4])) loc result, val[0], val[5] # New lexer does not keep track of this, this is done in validation if @lexer.respond_to?(:'indefine=') @lexer.indefine = false end } #---HOSTCLASS # # Produces Model::HostClassDefinition # hostclass_expression : CLASS stacked_classname parameter_list classparent LBRACE opt_statements RBRACE { # Remove this class' name from the namestack as all nested classes have been parsed namepop result = add_definition(Factory.HOSTCLASS(classname(val[1][:value]), val[2], token_text(val[3]), val[5])) loc result, val[0], val[6] } # Record the classname so nested classes gets a fully qualified name at parse-time # This is a separate rule since racc does not support intermediate actions. # stacked_classname : classname { namestack(val[0][:value]) ; result = val[0] } opt_statements : statements | nil # Produces String, name or nil result classparent : nil | INHERITS classnameordefault { result = val[1] } # Produces String (this construct allows a class to be named "default" and to be referenced as # the parent class. # TODO: Investigate the validity # Produces a String (classname), or a token (DEFAULT). # classnameordefault : classname | DEFAULT #---NODE # # Produces Model::NodeDefinition # node_definition_expression : NODE hostnames nodeparent LBRACE statements RBRACE { result = add_definition(Factory.NODE(val[1], val[2], val[4])) loc result, val[0], val[5] } | NODE hostnames nodeparent LBRACE RBRACE { result = add_definition(Factory.NODE(val[1], val[2], nil)) loc result, val[0], val[4] } # Hostnames is not a list of names, it is a list of name matchers (including a Regexp). # (The old implementation had a special "Hostname" object with some minimal validation) # # Produces Array # hostnames : hostname { result = [result] } | hostnames COMMA hostname { result = val[0].push(val[2]) } # Produces a LiteralExpression (string, :default, or regexp) # String with interpolation is validated for better error message hostname : dotted_name { result = val[0] } | quotedtext { result = val[0] } | DEFAULT { result = Factory.literal(:default); loc result, val[0] } | regex dotted_name : NAME { result = Factory.literal(val[0][:value]); loc result, val[0] } | dotted_name DOT NAME { result = Factory.concat(val[0], '.', val[2][:value]); loc result, val[0], val[2] } # Produces Expression, since hostname is an Expression nodeparent : nil | INHERITS hostname { result = val[1] } +#---FUNCTION DEFINITION +# +function_definition + : FUNCTION classname parameter_list LBRACE opt_statements RBRACE { + result = add_definition(Factory.FUNCTION(val[1][:value], val[2], val[4])) + loc result, val[0], val[5] + } + #---NAMES AND PARAMETERS COMMON TO SEVERAL RULES # Produces String -# +# TODO: The error that "class" is not a valid classname is bad - classname rule is also used for other things classname : NAME { result = val[0] } | CLASS { error val[0], "'class' is not a valid classname" } # Produces Array parameter_list : nil { result = [] } | LPAREN RPAREN { result = [] } | LPAREN parameters endcomma RPAREN { result = val[1] } # Produces Array parameters : parameter { result = [val[0]] } | parameters COMMA parameter { result = val[0].push(val[2]) } # Produces Model::Parameter parameter : VARIABLE EQUALS expression { result = Factory.PARAM(val[0][:value], val[2]) ; loc result, val[0] } | VARIABLE { result = Factory.PARAM(val[0][:value]); loc result, val[0] } #--RESTRICTED EXPRESSIONS # i.e. where one could have expected an expression, but the set is limited ## What is allowed RHS of match operators (see expression) #match_rvalue # : regex # | text_or_name #--VARIABLE # variable : VARIABLE { result = Factory.fqn(val[0][:value]).var ; loc result, val[0] } #---LITERALS (dynamic and static) # array : LBRACK expressions RBRACK { result = Factory.LIST(val[1]); loc result, val[0], val[2] } | LBRACK expressions COMMA RBRACK { result = Factory.LIST(val[1]); loc result, val[0], val[3] } | LBRACK RBRACK { result = Factory.literal([]) ; loc result, val[0] } | LISTSTART expressions RBRACK { result = Factory.LIST(val[1]); loc result, val[0], val[2] } | LISTSTART expressions COMMA RBRACK { result = Factory.LIST(val[1]); loc result, val[0], val[3] } | LISTSTART RBRACK { result = Factory.literal([]) ; loc result, val[0] } hash : LBRACE hashpairs RBRACE { result = Factory.HASH(val[1]); loc result, val[0], val[2] } | LBRACE hashpairs COMMA RBRACE { result = Factory.HASH(val[1]); loc result, val[0], val[3] } | LBRACE RBRACE { result = Factory.literal({}) ; loc result, val[0], val[3] } hashpairs : hashpair { result = [val[0]] } | hashpairs COMMA hashpair { result = val[0].push val[2] } hashpair : expression FARROW expression { result = Factory.KEY_ENTRY(val[0], val[2]); loc result, val[1] } quotedtext : string | dq_string | heredoc string : STRING { result = Factory.literal(val[0][:value]) ; loc result, val[0] } dq_string : dqpre dqrval { result = Factory.string(val[0], *val[1]) ; loc result, val[0], val[1][-1] } dqpre : DQPRE { result = Factory.literal(val[0][:value]); loc result, val[0] } dqpost : DQPOST { result = Factory.literal(val[0][:value]); loc result, val[0] } dqmid : DQMID { result = Factory.literal(val[0][:value]); loc result, val[0] } dqrval : text_expression dqtail { result = [val[0]] + val[1] } text_expression : expression { result = Factory.TEXT(val[0]) } dqtail : dqpost { result = [val[0]] } | dqmid dqrval { result = [val[0]] + val[1] } heredoc : HEREDOC sublocated_text { result = Factory.HEREDOC(val[0][:value], val[1]); loc result, val[0] } sublocated_text : SUBLOCATE string { result = Factory.SUBLOCATE(val[0], val[1]); loc result, val[0] } | SUBLOCATE dq_string { result = Factory.SUBLOCATE(val[0], val[1]); loc result, val[0] } epp_expression : EPP_START epp_parameters_list statements { result = Factory.EPP(val[1], val[2]); loc result, val[0] } epp_parameters_list : =LOW{ result = nil } | PIPE PIPE { result = [] } | PIPE parameters endcomma PIPE { result = val[1] } epp_render_expression : RENDER_STRING { result = Factory.RENDER_STRING(val[0][:value]); loc result, val[0] } | RENDER_EXPR expression epp_end { result = Factory.RENDER_EXPR(val[1]); loc result, val[0], val[2] } | RENDER_EXPR LBRACE statements RBRACE epp_end { result = Factory.RENDER_EXPR(Factory.block_or_expression(*val[2])); loc result, val[0], val[4] } epp_end : EPP_END | EPP_END_TRIM number : NUMBER { result = Factory.NUMBER(val[0][:value]) ; loc result, val[0] } name : NAME { result = Factory.QNAME_OR_NUMBER(val[0][:value]) ; loc result, val[0] } type : CLASSREF { result = Factory.QREF(val[0][:value]) ; loc result, val[0] } undef : UNDEF { result = Factory.literal(:undef); loc result, val[0] } default : DEFAULT { result = Factory.literal(:default); loc result, val[0] } # Assumes lexer produces a Boolean value for booleans, or this will go wrong and produce a literal string # with the text 'true'. #TODO: could be changed to a specific boolean literal factory method to prevent this possible glitch. boolean : BOOLEAN { result = Factory.literal(val[0][:value]) ; loc result, val[0] } regex : REGEX { result = Factory.literal(val[0][:value]); loc result, val[0] } #---MARKERS, SPECIAL TOKENS, SYNTACTIC SUGAR, etc. endcomma : # | COMMA { result = nil } endsemi : # | SEMIC keyword : AND | CASE | CLASS | DEFAULT | DEFINE | ELSE | ELSIF | IF | IN | INHERITS | NODE | OR | UNDEF | UNLESS nil : { result = nil} end ---- header ---- require 'puppet' require 'puppet/pops' module Puppet class ParseError < Puppet::Error; end class ImportError < Racc::ParseError; end class AlreadyImportedError < ImportError; end end ---- inner ---- # Make emacs happy # Local Variables: # mode: ruby # End: diff --git a/lib/puppet/pops/parser/eparser.rb b/lib/puppet/pops/parser/eparser.rb index 6d1a3668d..9667e12f7 100644 --- a/lib/puppet/pops/parser/eparser.rb +++ b/lib/puppet/pops/parser/eparser.rb @@ -1,2579 +1,2616 @@ # # 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', 748) +module_eval(<<'...end egrammar.ra/module_eval...', 'egrammar.ra', 758) # Make emacs happy # Local Variables: # mode: ruby # End: ...end egrammar.ra/module_eval... ##### State transition tables begin ### clist = [ -'57,59,275,-130,51,265,53,-216,79,-132,-225,126,313,126,79,125,265,125', -'363,298,224,224,102,14,106,353,101,360,102,41,106,48,101,50,45,235,49', -'69,65,238,43,68,46,47,276,-130,66,13,105,-216,67,-132,-225,12,105,258', -'57,59,260,261,51,70,53,395,240,224,247,42,245,248,80,64,60,244,62,63', -'61,328,243,14,231,264,52,242,126,41,265,48,125,50,45,126,49,69,65,125', -'43,68,46,47,221,316,66,13,57,59,67,235,126,12,57,59,125,331,51,126,53', -'70,79,125,348,272,347,42,348,333,347,64,60,220,62,63,102,14,106,335', -'101,74,52,41,297,48,135,50,45,133,49,69,65,72,43,68,46,47,296,122,66', -'13,105,274,67,57,59,12,114,70,57,59,250,249,51,70,53,393,340,341,60', -'42,342,224,79,64,60,126,62,63,211,125,345,14,241,349,52,351,102,41,106', -'48,101,50,45,290,49,69,65,187,43,68,46,47,272,274,66,13,272,359,67,296', -'289,12,105,296,57,59,74,154,51,70,53,391,151,149,370,42,312,81,82,64', -'60,288,62,63,80,372,274,14,274,127,52,272,375,41,114,48,115,50,45,315', -'49,69,65,114,43,68,46,47,379,351,66,13,381,382,67,383,384,12,57,59,385', -'111,51,387,53,70,75,77,76,78,388,42,389,319,74,64,60,71,62,63,396,14', -'397,57,59,398,52,41,399,48,,50,108,,49,69,65,,43,68,,,,,66,13,,,67,', -',12,57,59,,,51,,53,70,,135,,,133,42,,,,64,60,,62,63,,14,,57,59,,52,41', -',48,70,50,108,,49,69,65,,43,68,,60,,,66,13,,,67,,,12,57,59,,,51,,53', -'70,,135,,,133,42,,,,64,60,,62,63,,14,,57,59,,52,41,,48,70,50,108,,49', -'69,65,,43,68,,60,,,66,13,,,67,,,12,57,59,,,51,,53,70,79,135,,,133,42', -',,,64,60,,62,63,102,14,106,,101,,52,41,,48,70,50,108,,49,69,65,,43,68', -',60,,,66,13,105,,67,,,12,57,59,,,51,,53,70,79,,,,,42,,,80,64,60,,62', -'63,102,14,106,,101,,52,41,,48,,50,45,,49,69,65,,43,68,46,47,,,66,13', -'105,,67,,,12,57,59,,,51,,53,70,79,81,82,,,42,,,80,64,60,,62,63,102,14', -'106,,101,,52,41,,48,,50,108,,49,69,65,,43,68,,,,,66,13,105,,67,,,12', -'57,59,,,51,,53,70,79,81,82,,,42,,,80,64,60,,62,63,102,14,106,,101,,52', -'41,,48,,50,108,,49,69,65,,43,68,,,,,66,13,105,,67,,,12,57,59,,,51,,53', -'70,,,,,,42,,,,64,60,,62,63,,14,,,,,52,41,,48,,50,108,,49,69,65,,43,68', -',,,,66,13,,,67,,,12,57,59,,,51,,53,70,,,,,,42,,,,64,60,,62,63,,14,,', -',,52,41,,48,,50,108,,49,69,65,,43,68,,,,,66,13,,,67,,,12,57,59,,,51', -',53,70,,,,,,42,,,,64,60,,62,63,,14,,,,,52,41,,48,,50,108,,49,69,65,', -'43,68,,,,,66,13,,,67,,,12,57,59,,,51,,53,70,,,,,,42,,,,64,60,,62,63', -',14,,,,,52,41,,48,,50,121,,49,69,65,,43,68,,,,,66,13,,,67,,,12,57,59', -',,51,,53,70,,,,,,42,,,,64,60,,62,63,,14,,,,,52,41,,48,,50,108,,49,69', -'65,,43,68,,,,,66,13,,,67,,,12,57,59,,,51,,53,70,,,,,,42,,,,64,60,,62', -'63,,14,,,,,52,41,,48,,50,108,,49,69,65,,43,68,,,,,66,13,,,67,,,12,57', -'59,,,51,,53,70,,,,,,42,,,,64,60,,62,63,,14,,,,,52,41,,48,,50,108,,49', -'69,65,,43,68,,,,,66,13,,,67,,,12,,,57,59,,,51,70,53,294,,,,42,,,,64', -'60,,62,63,,,,14,,,52,,,41,,48,,50,45,,49,69,65,,43,68,46,47,,,66,13', -',,67,,,12,57,59,,,51,138,53,70,,,,,,42,,,,64,60,,62,63,,14,,,,,52,41', -',48,,50,108,,49,69,65,,43,68,,,,,66,13,,,67,,,12,57,59,,,51,140,53,70', -',,,,,42,,,,64,60,,62,63,,14,,,,,52,41,,48,,50,108,,49,69,65,,43,68,', -',,,66,13,,,67,,,12,,,57,59,,,51,70,53,143,,,,42,,,,64,60,,62,63,,,,14', -',,52,,,41,,48,,50,108,,49,69,65,,43,68,,,,,66,13,,,67,,,12,57,59,,,51', -',53,70,,,,,,42,,,,64,60,,62,63,,14,,,,,52,41,,48,,50,108,,49,69,65,', -'43,68,,,,,66,13,,,67,,,12,,,57,59,,,51,70,53,300,,,,42,,,,64,60,,62', -'63,,,,14,,,52,,,41,,48,,50,45,,49,69,65,,43,68,46,47,,,66,13,,,67,,', -'12,,,57,59,,,51,70,53,143,,,,42,,,,64,60,,62,63,,,,14,,,52,,,41,,48', -',50,45,,49,69,65,,43,68,46,47,,,66,13,,,67,,,12,57,59,,,51,,153,70,', -',,,,42,,,,64,60,,62,63,,14,,,,,52,41,,48,,50,108,,49,69,65,,43,68,,', -',,66,13,,,67,,,12,,,57,59,,,51,70,53,369,,,,42,,,,64,60,,62,63,,,,14', -',,52,,,41,,48,,50,45,,49,69,65,,43,68,46,47,,,66,13,,,67,,,12,57,59', -',,51,,53,70,,,,,,42,,,,64,60,,62,63,,14,,,,,52,41,,48,,50,45,,49,69', -'65,,43,68,46,47,,,66,13,,,67,,,12,57,59,,,51,,53,70,,,,,,42,,,,64,60', -',62,63,,14,,,,,52,41,,48,,50,45,,49,69,65,,43,68,46,47,,,66,13,,,67', -',,12,57,59,,,51,,53,70,,,,,,42,,,,64,60,,62,63,,14,,,,,52,41,,48,,50', -'45,,49,69,65,,43,68,46,47,,,66,13,,,67,,,12,57,59,,,51,,53,70,,,,,,42', -',,,64,60,,62,63,,14,,,,,52,41,,48,,50,45,,49,69,65,,43,68,46,47,,,66', -'13,,,67,,,12,57,59,,,51,,53,70,,,,,,42,,,,64,60,,62,63,,14,,,,,52,41', -',48,,50,45,,49,69,65,,43,68,46,47,,,66,13,,,67,,,12,57,59,,,51,,53,70', -',,,,,42,,,,64,60,,62,63,,14,,,,,52,41,,48,,50,45,,49,69,65,,43,68,46', -'47,,,66,13,,,67,,,12,57,59,,,51,,53,70,,,,,,42,,,,64,60,,62,63,,14,', -',,,52,41,,48,,50,45,,49,69,65,,43,68,46,47,,,66,13,,,67,,,12,57,59,', -',51,,53,70,,,,,,42,,,,64,60,,62,63,,14,,,,,52,41,,48,,50,108,,49,69', -'65,,43,68,,,,,66,13,,,67,,,12,57,59,,,51,,53,70,,,,,,42,,,,64,60,,62', -'63,,14,,,,,52,41,,48,,50,108,,49,69,65,,43,68,,,,,66,13,,,67,,,12,57', -'59,,,51,,53,70,,,,,,42,,,,64,60,,62,63,,14,,,,,52,41,,48,,50,108,,49', -'69,65,,43,68,,,,,66,13,,,67,,,12,57,59,,,51,,53,70,,,,,,42,,,,64,60', -',62,63,,14,,,,,52,41,,48,,50,108,,49,69,65,,43,68,,,,,66,13,,,67,,,12', -'57,59,,,51,,53,70,,,,,,42,,,,64,60,,62,63,,14,,,,,52,41,,48,,50,108', -',49,69,65,,43,68,,,,,66,13,,,67,,,12,57,59,,,51,,53,70,,,,,,42,,,,64', -'60,,62,63,,14,,,,,52,41,,48,,50,108,,49,69,65,,43,68,,,,,66,13,,,67', -',,12,57,59,,,51,,53,70,,,,,,42,,,,64,60,,62,63,,14,,,,,52,41,,48,,50', -'108,,49,69,65,,43,68,,,,,66,13,,,67,,,12,57,59,,,51,,53,70,,,,,,42,', -',,64,60,,62,63,,14,,,,,52,41,,48,,50,108,,49,69,65,,43,68,,,,,66,13', -',,67,,,12,57,59,,,51,,53,70,,,,,,42,,,,64,60,,62,63,,14,,,,,52,41,,48', -',50,108,,49,69,65,,43,68,,,,,66,13,,,67,,,12,57,59,,,51,,53,70,,,,,', -'42,,,,64,60,,62,63,,14,,,,,52,41,,48,,50,108,,49,69,65,,43,68,,,,,66', -'13,,,67,,,12,57,59,,,51,,53,70,,,,,,42,,,,64,60,,62,63,,14,,,,,52,41', -',48,,50,108,,49,69,65,,43,68,,,,,66,13,,,67,,,12,57,59,,,51,,53,70,', -',,,,42,,,,64,60,,62,63,,14,,,,,52,41,,48,,50,108,,49,69,65,,43,68,,', -',,66,13,,,67,,,12,57,59,,,51,,53,70,,,,,,42,,,,64,60,,62,63,,14,,,,', -'52,41,,48,,50,108,,49,69,65,,43,68,,,,,66,13,,,67,,,12,57,59,,,51,,53', -'70,,,,,,42,,,,64,60,,62,63,,14,,,,,52,41,,48,,50,108,,49,69,65,,43,68', -',,,,66,13,,,67,,,12,57,59,,,51,,53,70,,,,,,42,,,,64,60,,62,63,,14,,', -',,52,41,,48,,50,108,,49,69,65,,43,68,,,,,66,13,,,67,,,12,57,59,,,51', -',53,70,,,,,,42,,,,64,60,,62,63,,14,,,,,52,41,,48,,50,108,,49,69,65,', -'43,68,,,,,66,13,,,67,,,12,57,59,,,51,,53,70,,,,,,42,,,,64,60,,62,63', -',14,,,,,52,41,,48,,50,108,,49,69,65,,43,68,,,,,66,13,,,67,,,12,57,59', -',,51,,53,70,,,,,,42,,,,64,60,,62,63,,14,,,,,52,41,,48,,50,108,,49,69', -'65,,43,68,,,,,66,13,,,67,,,12,57,59,,,51,,53,70,,,,,,42,,,,64,60,,62', -'63,,14,,,,,52,41,,48,,50,108,,49,69,65,,43,68,,,,,66,13,,,67,,,12,57', -'59,,,51,,53,70,,,,,,42,,,,64,60,,62,63,,14,,,,,52,41,,48,,50,108,,49', -'69,65,,43,68,,,,,66,13,,,67,,,12,57,59,,,51,,53,70,,,,,,42,,,,64,60', -',62,63,,14,,,,,52,41,,48,,50,108,,49,69,65,,43,68,,,,,66,13,,,67,,,12', -'57,59,,,51,,53,70,,,,,,42,,,,64,60,,62,63,,14,,,,,52,41,,48,,50,108', -',49,69,65,,43,68,,,,,66,13,,,67,,,12,57,59,,,51,,53,70,,,,,,42,,,,64', -'60,,62,63,,14,,,,,52,41,,48,,50,108,,49,69,65,,43,68,,,,,66,13,,,67', -',,12,,,57,59,,,51,70,53,354,,,,42,,,186,64,60,,62,63,,,,14,,,52,,,41', -',48,,50,108,,49,69,65,,43,68,,,,,66,13,,,67,,,12,57,59,,,51,,53,70,', -',,,,42,,,,64,60,,62,63,,14,,,,,52,189,206,200,207,50,201,209,202,198', -'196,,191,204,,,,,66,13,210,205,203,,,12,57,59,,,51,,53,70,,,,,208,190', -',,,64,60,,62,63,,14,,,,,52,41,,48,,50,108,,49,69,65,,43,68,,,,,66,13', -',,67,,,12,57,59,,,51,,53,70,,,,,,42,,,,64,60,,62,63,,14,,,,,52,41,,48', -',50,108,,49,69,65,,43,68,,,,,66,13,,,67,,,12,57,59,,,51,,53,70,,,,,', -'42,,,,64,60,,62,63,,14,,,,,52,41,,48,,50,108,,49,69,65,,43,68,,,,,66', -'13,,,67,,,12,57,59,,,51,,53,70,,,,,,42,,,,64,60,,62,63,,14,,,,,52,41', -',48,,50,108,,49,69,65,,43,68,,,,,66,13,,,67,,,12,57,59,,,51,,53,70,', -',,,,42,,,,64,60,,62,63,,14,,,,,52,41,,48,,50,108,,49,69,65,,43,68,,', -',,66,13,,,67,,,12,57,59,,,51,,53,70,,,,,,42,,,,64,60,,62,63,,14,,,,', -'52,41,,48,,50,108,,49,69,65,,43,68,,,,,66,13,,,67,,,12,,,57,59,,,51', -'70,53,302,,,,42,,,,64,60,,62,63,,,,14,,,52,,,41,,48,,50,45,,49,69,65', -',43,68,46,47,,,66,13,,,67,,,12,57,59,,,51,,53,70,,,,,,42,,,,64,60,,62', -'63,,14,218,,,,52,41,,48,,50,108,,49,69,65,,43,68,,,,,66,13,,,67,,,12', -'57,59,,,51,,53,70,,,,,,42,,,,64,60,,62,63,,14,,,,,52,41,,48,,50,108', -',49,69,65,,43,68,,,,,66,13,,,67,,,12,57,59,,,51,,53,70,,,,,,42,,,,64', -'60,,62,63,,14,,,,,52,41,,48,,50,108,,49,69,65,,43,68,,,,,66,13,,,67', -',,12,57,59,,,51,,53,70,,,,,,42,,,,64,60,,62,63,,14,226,,,,52,41,,48', -',50,108,,49,69,65,,43,68,,,,,66,13,,,67,,,12,57,59,,,51,,53,70,,,,,', -'42,,,,64,60,,62,63,,14,,,,,52,41,,48,,50,45,,49,69,65,,43,68,46,47,', -',66,13,,,67,,,12,57,59,,,51,,53,70,,,,,,42,,,,64,60,,62,63,,14,,,,,52', -'41,,48,,50,108,,49,69,65,,43,68,,,,,66,13,,,67,,,12,57,59,,,51,322,53', -'70,,,,,,42,,,,64,60,,62,63,,14,,,,,52,41,,48,,50,108,,49,69,65,,43,68', -',,,,66,13,,,67,,,12,57,59,,,51,,53,70,,,,,,42,,,,64,60,,62,63,,14,,', -',,52,41,,48,,50,108,,49,69,65,,43,68,,,,,66,13,,,67,,,12,57,59,,,51', -',53,70,,,,,,42,,,,64,60,,62,63,,14,,,,,52,41,,48,,50,108,,49,69,65,', -'43,68,,,,,66,13,,,67,,,12,57,59,,,51,,53,70,,,,,,42,,,,64,60,,62,63', -',14,,,,,52,41,,48,,50,108,,49,69,65,,43,68,,,,,66,13,,,67,,,12,57,59', -',,51,321,53,70,,,,,,42,,,,64,60,,62,63,,14,,,,,52,41,,48,,50,108,,49', -'69,65,,43,68,,,,,66,13,,,67,,,12,57,59,,,51,,53,70,,,,,,42,,,,64,60', -',62,63,,14,,,,,52,41,,48,,50,108,,49,69,65,,43,68,,,,,66,13,,,67,,,12', -',,57,59,,,51,70,53,324,,,,42,,,,64,60,,62,63,,,,14,,,52,,,41,,48,,50', -'108,,49,69,65,,43,68,,,,,66,13,,,67,,,12,57,59,,,51,,53,70,,,,,,42,', -',,64,60,,62,63,,14,,,,,52,41,,48,,50,108,,49,69,65,,43,68,,,,,66,13', -',,67,,,12,57,59,,,51,,53,70,,,,,,42,,,,64,60,,62,63,,14,,,,,52,189,206', -'200,207,50,201,209,202,198,196,,191,204,,,,,66,13,210,205,203,,,12,', -',,,,,,70,,,,,208,190,,,,64,60,,62,63,79,,,,,,52,,,98,99,100,95,90,102', -',106,,101,,,91,93,92,94,,,,,,,,,,,,,,,,105,,,,97,96,,,83,84,86,85,88', -'89,,81,82,,,79,,103,80,,246,,,,98,99,100,95,90,102,,106,,101,,87,91', -'93,92,94,,,,,,,,,,,,,,,,105,,,,97,96,,,83,84,86,85,88,89,79,81,82,,', -'246,,,80,98,99,100,95,90,102,,106,,101,,,91,93,92,94,87,,,,,,,,,,,,', -',,105,,,,97,96,,,83,84,86,85,88,89,,81,82,,,79,,230,80,,,,,,98,99,100', -'95,90,102,,106,,101,,87,91,93,92,94,,,,,,,,,,,,,,,,105,,,,97,96,,,83', -'84,86,85,88,89,79,81,82,,,,,,80,98,99,100,95,90,102,,106,,101,,,91,93', -'92,94,87,,,,,,,,,,,,,,,105,,,,97,96,,,83,84,86,85,88,89,,81,82,,,79', -',229,80,,,,,,98,99,100,95,90,102,,106,,101,,87,91,93,92,94,,,,,,,,,', -',,,,,,105,,,,97,96,,,83,84,86,85,88,89,,81,82,,,79,,228,80,,,,,,98,99', -'100,95,90,102,,106,,101,,87,91,93,92,94,,,,,,,,,,,,,,,,105,,,,97,96', -',,83,84,86,85,88,89,,81,82,,,79,,227,80,,,,,,98,99,100,95,90,102,,106', -',101,,87,91,93,92,94,,,,,,,,,,,,,,,,105,,,,97,96,,,83,84,86,85,88,89', -'79,81,82,,,,,,80,98,99,100,95,90,102,,106,,101,,216,91,93,92,94,87,', -',,,,,,,,,,,,,105,,,,97,96,,,83,84,86,85,88,89,79,81,82,,,,,,80,98,99', -'100,95,90,102,,106,,101,,,91,93,92,94,87,,,,,,,,,,,,,,,105,,,,97,96', -',,83,84,86,85,88,89,79,81,82,,,,,,80,98,99,100,95,90,102,,106,,101,260', -'261,91,93,92,94,87,,,,,,,,,,,,,,,105,,,,97,96,,,83,84,86,85,88,89,79', -'81,82,,,,,,80,98,99,100,95,90,102,,106,,101,,,91,93,92,94,87,,,,,,,', -',,,,,,,105,,,,97,96,,,83,84,86,85,88,89,79,81,82,,,,,,80,98,99,100,95', -'90,102,,106,,101,,,91,93,92,94,87,,,,,,,,,,,,,,,105,,,,97,96,,,83,84', -'86,85,88,89,79,81,82,,,,,,80,98,99,100,95,90,102,,106,,101,,,91,93,92', -'94,87,,,,,,,,,,,,,,,105,,,,97,96,,,83,84,86,85,88,89,79,81,82,,,,,,80', -'98,99,100,95,90,102,,106,,101,,,91,93,92,94,87,,,,,,,,,,,,,,,105,,,', -'97,96,,,83,84,86,85,88,89,79,81,82,,,,,,80,98,99,100,95,90,102,,106', -',101,79,,91,93,92,94,87,,,,,,,,102,,106,,101,,,105,,,,97,96,,,83,84', -'86,85,88,89,,81,82,,,105,,,80,,79,,,,,86,85,,,,81,82,,,102,87,106,80', -'101,,,,,,,,,,,,,,,,87,,,,,,105,,,,,,,,,,86,85,,,79,81,82,,,,,,80,98', -'99,100,95,90,102,,106,,101,,,91,93,92,94,87,,,,,,,,,,,,,,,105,,,,97', -'96,,,83,84,86,85,88,89,79,81,82,,,,,,80,98,99,100,95,90,102,270,106', -',101,,,91,93,92,94,87,,,,,,,,,,,,,,,105,,,,97,96,,,83,84,86,85,88,89', -',81,82,,,79,,103,80,,,,,,98,99,100,95,90,102,,106,,101,79,87,91,93,92', -'94,,,,,,,,,102,,106,,101,,,105,,,,97,96,,,83,84,86,85,88,89,,81,82,', -',105,,,80,,79,,,83,84,86,85,,,,81,82,,,102,87,106,80,101,79,,,,,,,,', -',,,,,102,87,106,,101,,,105,,,,,,,,83,84,86,85,,,,81,82,,,105,,,80,,79', -',,83,84,86,85,88,89,,81,82,,,102,87,106,80,101,,,,,,,79,,,,,,,,,87,', -',,90,102,105,106,,101,,79,91,,83,84,86,85,88,89,,81,82,,90,102,,106', -'80,101,,105,91,,,,79,,,83,84,86,85,88,89,87,81,82,,90,102,105,106,80', -'101,,,91,,83,84,86,85,88,89,,81,82,,,87,,,80,,,105,,,,,79,,,83,84,86', -'85,88,89,87,81,82,,90,102,,106,80,101,,,91,,,,,,,,,,,,,87,,,,,,105,', -',,,79,,,83,84,86,85,88,89,,81,82,95,90,102,,106,80,101,,,91,93,92,94', -',,,,,,,,,87,,,,,,105,,,,,79,,,83,84,86,85,88,89,,81,82,95,90,102,,106', -'80,101,,,91,93,92,94,,,,,,,,,,87,,,,,,105,,,,,96,,,83,84,86,85,88,89', -'79,81,82,,,,,,80,98,99,100,95,90,102,,106,,101,,,91,93,92,94,87,,,,', -',,,,,,,,,,105,,,,97,96,,,83,84,86,85,88,89,79,81,82,,,266,,,80,98,99', -'100,95,90,102,,106,,101,,,91,93,92,94,87,,,,,,,,,,,,,,,105,,,,97,96', -',,83,84,86,85,88,89,79,81,82,,,,,,80,98,99,100,95,90,102,,106,,101,', -',91,93,92,94,87,,,,,,,,,,,,,,,105,,,,97,96,,,83,84,86,85,88,89,79,81', -'82,,,,,,80,98,99,100,95,90,102,,106,,101,,,91,93,92,94,87,,,,,,,,,,', -',,,,105,,,,97,96,,,83,84,86,85,88,89,,81,82,,,,,,80,284,206,283,207', -',281,209,285,279,278,,280,282,,,,87,,,210,205,286,284,206,283,207,,281', -'209,285,279,278,,280,282,,,208,287,,,210,205,286,284,206,283,207,,281', -'209,285,279,278,,280,282,,,208,287,,,210,205,286,,,,,,,,,,,,,,,,208', -'287' ] - racc_action_table = arr = ::Array.new(6233, nil) +'59,61,-133,279,53,241,55,-218,269,-131,-227,214,269,227,249,317,59,61', +'300,248,227,358,302,14,227,59,61,247,124,42,243,49,246,52,46,128,50', +'71,67,127,44,70,47,48,-133,280,68,13,224,-218,69,-131,-227,12,137,262', +'128,135,59,61,127,72,53,137,55,402,135,43,268,254,253,66,62,269,64,65', +'63,72,234,51,128,14,251,54,127,252,72,42,62,49,244,52,46,333,50,71,67', +'62,44,70,47,48,190,128,68,13,128,127,69,128,127,12,76,127,128,336,59', +'61,127,72,53,368,55,128,81,43,353,127,352,66,62,76,64,65,353,338,352', +'51,104,14,108,54,103,264,265,42,157,49,340,52,46,324,50,71,67,74,44', +'70,47,48,59,61,68,13,107,154,69,152,278,12,77,79,78,80,59,61,323,72', +'53,345,55,400,81,43,346,347,82,66,62,227,64,65,320,350,319,51,104,14', +'108,54,103,354,356,42,316,49,276,52,46,276,50,71,67,278,44,70,47,48', +'276,364,68,13,107,365,69,223,300,12,129,301,117,300,59,61,238,72,53', +'376,55,398,81,43,116,294,82,66,62,378,64,65,116,278,293,51,104,14,108', +'54,103,292,381,42,116,49,278,52,46,276,50,71,67,113,44,70,47,48,385', +'356,68,13,107,387,69,388,389,12,390,391,238,393,59,61,394,72,53,395', +'55,396,81,43,238,76,73,66,62,403,64,65,404,405,406,51,104,14,108,54', +'103,,,42,,49,,52,46,,50,71,67,,44,70,47,48,,,68,13,107,,69,,,12,,,,', +'59,61,,72,53,,55,,81,43,,,,66,62,,64,65,,,,51,104,14,108,54,103,,,42', +',49,,52,110,,50,71,67,,44,70,,,,,68,13,107,,69,,,12,,,,,59,61,,72,53', +',55,,,43,,,,66,62,,64,65,,,,51,,14,,54,,,,42,,49,,52,110,,50,71,67,', +'44,70,,,,,68,13,,,69,,,12,,,,,59,61,,72,53,,55,,,43,,,,66,62,,64,65', +',,,51,,14,,54,,,,42,,49,,52,110,,50,71,67,,44,70,,,,,68,13,,,69,,,12', +',,,,59,61,,72,53,,55,,,43,,,,66,62,,64,65,,,,51,,14,,54,,,,42,,49,,52', +'110,,50,71,67,,44,70,,,,,68,13,,,69,,,12,,,,,59,61,,72,53,,55,,,43,', +',,66,62,,64,65,,,,51,,14,,54,,,,42,,49,,52,110,,50,71,67,,44,70,,,,', +'68,13,,,69,,,12,,,,,59,61,,72,53,,55,,,43,,,,66,62,,64,65,,,,51,,14', +',54,,,,42,,49,,52,46,,50,71,67,,44,70,47,48,,,68,13,,,69,,,12,,,,,59', +'61,,72,53,,55,,,43,,,,66,62,,64,65,,,,51,,14,,54,,,,42,,49,,52,110,', +'50,71,67,,44,70,,,,,68,13,,,69,,,12,,,,,59,61,,72,53,,55,,,43,,,,66', +'62,,64,65,,,,51,,14,,54,,,,42,,49,,52,110,,50,71,67,,44,70,,,,,68,13', +',,69,,,12,,,,,59,61,,72,53,,55,,,43,,,,66,62,,64,65,,,,51,,14,,54,,', +',42,,49,,52,110,,50,71,67,,44,70,,,,,68,13,,,69,,,12,,,,,59,61,,72,53', +',55,,,43,,,,66,62,,64,65,,,,51,,14,,54,,,,42,,49,,52,123,,50,71,67,', +'44,70,,,,,68,13,,,69,,,12,,,,,59,61,,72,53,,55,374,,43,,,,66,62,,64', +'65,,,,51,,14,,54,,,,42,,49,,52,46,,50,71,67,,44,70,47,48,,,68,13,,,69', +',,12,,,,,59,61,,72,53,,55,,,43,,,,66,62,,64,65,,,,51,,14,,54,,,,42,', +'49,,52,46,,50,71,67,,44,70,47,48,,,68,13,,,69,,,12,,,,,59,61,,72,53', +',55,359,,43,,,,66,62,,64,65,,,,51,,14,,54,,,,42,,49,,52,110,,50,71,67', +',44,70,,,,,68,13,,,69,,,12,,,,,59,61,,72,53,,55,,,43,,,,66,62,,64,65', +',,,51,,14,,54,,,,42,,49,,52,110,,50,71,67,,44,70,,,,,68,13,,,69,,,12', +',,,,59,61,,72,53,,55,,,43,,,,66,62,,64,65,,,,51,,14,,54,,,,42,,49,,52', +'110,,50,71,67,,44,70,,,,,68,13,,,69,,,12,,,,,59,61,,72,53,141,55,,,43', +',,,66,62,,64,65,,,,51,,14,,54,,,,42,,49,,52,110,,50,71,67,,44,70,,,', +',68,13,,,69,,,12,,,,,59,61,,72,53,143,55,,,43,,,,66,62,,64,65,,,,51', +',14,,54,,,,42,,49,,52,110,,50,71,67,,44,70,,,,,68,13,,,69,,,12,,,,,59', +'61,,72,53,,55,146,,43,,,,66,62,,64,65,,,,51,,14,,54,,,,42,,49,,52,110', +',50,71,67,,44,70,,,,,68,13,,,69,,,12,,,,,59,61,,72,53,,55,,,43,,,,66', +'62,,64,65,,,,51,,14,,54,,,,42,,49,,52,110,,50,71,67,,44,70,,,,,68,13', +',,69,,,12,,,,,59,61,,72,53,,55,,,43,,,,66,62,,64,65,,,,51,,14,,54,,', +',42,,49,,52,110,,50,71,67,,44,70,,,,,68,13,,,69,,,12,,,,,59,61,,72,53', +',55,,,43,,,,66,62,,64,65,,,,51,,14,,54,,,,42,,49,,52,110,,50,71,67,', +'44,70,,,,,68,13,,,69,,,12,,,,,59,61,,72,53,,156,,,43,,,,66,62,,64,65', +',,,51,,14,,54,,,,42,,49,,52,110,,50,71,67,,44,70,,,,,68,13,,,69,,,12', +',,,,59,61,,72,53,,55,,,43,,,,66,62,,64,65,,,,51,,14,,54,,,,42,,49,,52', +'110,,50,71,67,,44,70,,,,,68,13,,,69,,,12,,,,,59,61,,72,53,,55,,,43,', +',,66,62,,64,65,,,,51,,14,,54,,,,42,,49,,52,46,,50,71,67,,44,70,47,48', +',,68,13,,,69,,,12,,,,,59,61,,72,53,,55,,,43,,,,66,62,,64,65,,,,51,,14', +',54,,,,42,,49,,52,110,,50,71,67,,44,70,,,,,68,13,,,69,,,12,,,,,59,61', +',72,53,,55,,,43,,,,66,62,,64,65,,,,51,,14,,54,,,,42,,49,,52,46,,50,71', +'67,,44,70,47,48,,,68,13,,,69,,,12,,,,,59,61,,72,53,,55,,,43,,,,66,62', +',64,65,,,,51,,14,,54,,,,42,,49,,52,46,,50,71,67,,44,70,47,48,,,68,13', +',,69,,,12,,,,,59,61,,72,53,,55,,,43,,,,66,62,,64,65,,,,51,,14,,54,,', +',42,,49,,52,46,,50,71,67,,44,70,47,48,,,68,13,,,69,,,12,,,,,59,61,,72', +'53,,55,,,43,,,,66,62,,64,65,,,,51,,14,,54,,,,42,,49,,52,46,,50,71,67', +',44,70,47,48,,,68,13,,,69,,,12,,,,,59,61,,72,53,,55,,,43,,,,66,62,,64', +'65,,,,51,,14,,54,,,,42,,49,,52,46,,50,71,67,,44,70,47,48,,,68,13,,,69', +',,12,,,,,59,61,,72,53,,55,,,43,,,,66,62,,64,65,,,,51,,14,,54,,,,42,', +'49,,52,110,,50,71,67,,44,70,,,,,68,13,,,69,,,12,,,,,59,61,,72,53,,55', +',,43,,,,66,62,,64,65,,,,51,,14,,54,,,,42,,49,,52,110,,50,71,67,,44,70', +',,,,68,13,,,69,,,12,,,,,59,61,,72,53,,55,,,43,,,,66,62,,64,65,,,,51', +',14,,54,,,,42,,49,,52,110,,50,71,67,,44,70,,,,,68,13,,,69,,,12,,,,,59', +'61,,72,53,,55,,,43,,,,66,62,,64,65,,,,51,,14,,54,,,,42,,49,,52,110,', +'50,71,67,,44,70,,,,,68,13,,,69,,,12,,,,,59,61,,72,53,,55,,,43,,,,66', +'62,,64,65,,,,51,,14,,54,,,,42,,49,,52,110,,50,71,67,,44,70,,,,,68,13', +',,69,,,12,,,,,59,61,,72,53,,55,,,43,,,,66,62,,64,65,,,,51,,14,,54,,', +',42,,49,,52,110,,50,71,67,,44,70,,,,,68,13,,,69,,,12,,,,,59,61,,72,53', +',55,,,43,,,,66,62,,64,65,,,,51,,14,,54,,,,42,,49,,52,110,,50,71,67,', +'44,70,,,,,68,13,,,69,,,12,,,,,59,61,,72,53,,55,,,43,,,,66,62,,64,65', +',,,51,,14,,54,,,,42,,49,,52,110,,50,71,67,,44,70,,,,,68,13,,,69,,,12', +',,,,59,61,,72,53,,55,,,43,,,,66,62,,64,65,,,,51,,14,,54,,,,42,,49,,52', +'110,,50,71,67,,44,70,,,,,68,13,,,69,,,12,,,,,59,61,,72,53,,55,,,43,', +',,66,62,,64,65,,,,51,,14,,54,,,,42,,49,,52,110,,50,71,67,,44,70,,,,', +'68,13,,,69,,,12,,,,,59,61,,72,53,,55,,,43,,,,66,62,,64,65,,,,51,,14', +',54,,,,42,,49,,52,110,,50,71,67,,44,70,,,,,68,13,,,69,,,12,,,,,59,61', +',72,53,,55,,,43,,,,66,62,,64,65,,,,51,,14,,54,,,,42,,49,,52,110,,50', +'71,67,,44,70,,,,,68,13,,,69,,,12,,,,,59,61,,72,53,,55,,,43,,,,66,62', +',64,65,,,,51,,14,,54,,,,42,,49,,52,110,,50,71,67,,44,70,,,,,68,13,,', +'69,,,12,,,,,59,61,,72,53,,55,,,43,,,,66,62,,64,65,,,,51,,14,,54,,,,42', +',49,,52,110,,50,71,67,,44,70,,,,,68,13,,,69,,,12,,,,,59,61,,72,53,,55', +',,43,,,,66,62,,64,65,,,,51,,14,,54,,,,42,,49,,52,110,,50,71,67,,44,70', +',,,,68,13,,,69,,,12,,,,,59,61,,72,53,,55,,,43,,,,66,62,,64,65,,,,51', +',14,,54,,,,42,,49,,52,110,,50,71,67,,44,70,,,,,68,13,,,69,,,12,,,,,59', +'61,,72,53,,55,,,43,,,,66,62,,64,65,,,,51,,14,,54,,,,42,,49,,52,110,', +'50,71,67,,44,70,,,,,68,13,,,69,,,12,,,,,59,61,,72,53,,55,,,43,,,,66', +'62,,64,65,,,,51,,14,,54,,,,42,,49,,52,110,,50,71,67,,44,70,,,,,68,13', +',,69,,,12,,,,,59,61,,72,53,,55,,,43,,,,66,62,,64,65,,,,51,,14,,54,,', +',42,,49,,52,110,,50,71,67,,44,70,,,,,68,13,,,69,,,12,,,,,59,61,,72,53', +',55,,,43,,,,66,62,,64,65,,,,51,,14,,54,,,,42,,49,,52,110,,50,71,67,', +'44,70,,,,,68,13,,,69,,,12,,,,,59,61,,72,53,,55,,,43,,,,66,62,,64,65', +',,,51,,14,,54,,,,42,,49,,52,110,,50,71,67,,44,70,,,,,68,13,,,69,,,12', +',,,,59,61,,72,53,,55,,,43,,,,66,62,,64,65,,,,51,,14,,54,,,,42,,49,,52', +'110,,50,71,67,,44,70,,,,,68,13,,,69,,,12,,,,,59,61,,72,53,,55,,,43,', +',,66,62,,64,65,,,,51,,14,,54,,,,42,,49,,52,110,,50,71,67,,44,70,,,,', +'68,13,,,69,,,12,,,,,59,61,,72,53,,55,,,43,,,189,66,62,,64,65,,,,51,', +'14,,54,,,,42,,49,,52,110,,50,71,67,,44,70,,,,,68,13,,,69,,,12,,,,,59', +'61,,72,53,,55,,,43,,,,66,62,,64,65,,,,51,,14,,54,,,,192,209,203,210', +'52,204,212,205,201,199,,194,207,,,,,68,13,213,208,206,,,12,,,,,59,61', +',72,53,,55,,211,193,,,,66,62,,64,65,,,,51,,14,,54,,,,42,,49,,52,110', +',50,71,67,,44,70,,,,,68,13,,,69,,,12,,,,,59,61,,72,53,,55,,,43,,,,66', +'62,,64,65,,,,51,,14,,54,,,,42,,49,,52,110,,50,71,67,,44,70,,,,,68,13', +',,69,,,12,,,,,59,61,,72,53,,55,,,43,,,,66,62,,64,65,,,,51,,14,,54,,', +',42,,49,,52,110,,50,71,67,,44,70,,,,,68,13,,,69,,,12,,,,,59,61,,72,53', +',55,329,,43,,,,66,62,,64,65,,,,51,,14,,54,,,,42,,49,,52,110,,50,71,67', +',44,70,,,,,68,13,,,69,,,12,,,,,59,61,,72,53,,55,,,43,,,,66,62,,64,65', +',,,51,,14,,54,,,,42,,49,,52,110,,50,71,67,,44,70,,,,,68,13,,,69,,,12', +',,,,59,61,,72,53,327,55,,,43,,,,66,62,,64,65,,,,51,,14,,54,,,,42,,49', +',52,110,,50,71,67,,44,70,,,,,68,13,,,69,,,12,,,,,59,61,,72,53,326,55', +',,43,,,,66,62,,64,65,,,,51,,14,,54,,,,42,,49,,52,110,,50,71,67,,44,70', +',,,,68,13,,,69,,,12,,,,,59,61,,72,53,,55,,,43,,,,66,62,,64,65,,,,51', +',14,221,54,,,,42,,49,,52,110,,50,71,67,,44,70,,,,,68,13,,,69,,,12,,', +',,59,61,,72,53,,55,,,43,,,,66,62,,64,65,,,,51,,14,,54,,,,42,,49,,52', +'110,,50,71,67,,44,70,,,,,68,13,,,69,,,12,,,,,59,61,,72,53,,55,,,43,', +',,66,62,,64,65,,,,51,,14,,54,,,,192,209,203,210,52,204,212,205,201,199', +',194,207,,,,,68,13,213,208,206,,,12,,,,,59,61,,72,53,,55,,211,193,,', +',66,62,,64,65,,,,51,,14,229,54,,,,42,,49,,52,110,,50,71,67,,44,70,,', +',,68,13,,,69,,,12,,,,,59,61,,72,53,,55,,,43,,,,66,62,,64,65,,,,51,,14', +',54,,,,42,,49,,52,110,,50,71,67,,44,70,,,,,68,13,,,69,,,12,,,,,59,61', +',72,53,,55,306,,43,,,,66,62,,64,65,,,,51,,14,,54,,,,42,,49,,52,46,,50', +'71,67,,44,70,47,48,,,68,13,,,69,,,12,,,,,59,61,,72,53,,55,304,,43,,', +',66,62,,64,65,,,,51,,14,,54,,,,42,,49,,52,46,,50,71,67,,44,70,47,48', +',,68,13,,,69,,,12,,,,,59,61,,72,53,,55,298,,43,,,,66,62,,64,65,,,,51', +',14,,54,,,,42,,49,,52,46,,50,71,67,,44,70,47,48,,,68,13,,,69,,,12,,', +',,59,61,,72,53,,55,,,43,,,,66,62,,64,65,,,,51,,14,,54,,,,42,,49,,52', +'110,,50,71,67,,44,70,,,,,68,13,,,69,,,12,,,,,59,61,,72,53,,55,,,43,', +',,66,62,,64,65,,,,51,,14,,54,,,,42,,49,,52,110,,50,71,67,,44,70,,,,', +'68,13,,,69,,,12,,,,,59,61,,72,53,,55,,,43,,,,66,62,,64,65,,,,51,,14', +',54,,,,42,,49,,52,110,,50,71,67,,44,70,,,,,68,13,,,69,,,12,,,,,59,61', +',72,53,,55,,,43,,,,66,62,,64,65,,,,51,,14,,54,,,,42,,49,,52,110,,50', +'71,67,,44,70,,,,,68,13,,,69,,,12,,,,,59,61,,72,53,,55,,,43,,,,66,62', +',64,65,,,,51,,14,,54,,,,42,,49,,52,110,,50,71,67,,44,70,,,,,68,13,,', +'69,,,12,,,,,59,61,,72,53,,55,146,,43,,,,66,62,,64,65,,,,51,,14,,54,', +',,42,,49,,52,46,,50,71,67,,44,70,47,48,,,68,13,,,69,,,12,,,,,59,61,', +'72,53,,55,,,43,,,,66,62,,64,65,,59,61,51,,14,,54,,,,42,,49,,52,46,,50', +'71,67,,44,70,47,48,,,68,13,,,69,,,12,,,,137,,,135,72,,,,,,43,,59,61', +'66,62,,64,65,81,,,51,72,,,54,,100,101,102,97,92,104,62,108,,103,,,93', +'95,94,96,,,,,,,137,,,135,,,,,,107,,,,99,98,,,85,86,88,87,90,91,72,83', +'84,,,,,81,82,105,,62,,,,,100,101,102,97,92,104,,108,,103,89,,93,95,94', +'96,,,,,,,,,,,,,,,,107,,,,99,98,,,85,86,88,87,90,91,81,83,84,,,,,,82', +'100,101,102,97,92,104,,108,,103,,,93,95,94,96,,89,,,,,,,,,,,,,,107,', +',,99,98,,,85,86,88,87,90,91,81,83,84,,,250,,,82,100,101,102,97,92,104', +',108,,103,,,93,95,94,96,,89,,,,,,,,,,,,,,107,,,,99,98,,,85,86,88,87', +'90,91,81,83,84,,,,,,82,100,101,102,97,92,104,,108,,103,,,93,95,94,96', +',89,,,,,,,,,,,,,,107,,,,99,98,,,85,86,88,87,90,91,81,83,84,,,,,,82,100', +'101,102,97,92,104,,108,,103,,,93,95,94,96,,89,,,,,,,,,,,,,,107,,,,99', +'98,,,85,86,88,87,90,91,81,83,84,,,,,,82,100,101,102,97,92,104,,108,', +'103,,,93,95,94,96,,89,,,,,,,,,,,,,,107,,,,99,98,,,85,86,88,87,90,91', +'81,83,84,,,,,,82,100,101,102,97,92,104,,108,,103,,,93,95,94,96,,89,', +',,,,,,,,,,,,107,,,,99,98,,,85,86,88,87,90,91,81,83,84,,,,,,82,100,101', +'102,97,92,104,,108,,103,,,93,95,94,96,,89,,,,,,,,,,,,,,107,,,,99,98', +',,85,86,88,87,90,91,81,83,84,,,,,,82,100,101,102,97,92,104,,108,,103', +',,93,95,94,96,,89,,,,,,,,,,,,,,107,,,,99,98,,,85,86,88,87,90,91,81,83', +'84,,,,,,82,100,101,102,97,92,104,,108,,103,,,93,95,94,96,,89,,,,,,,', +',,,,,,107,,,,99,98,,,85,86,88,87,90,91,,83,84,,,,,,82,,,,,,,,,,,264', +'265,,81,,105,,89,250,,,,100,101,102,97,92,104,,108,,103,,,93,95,94,96', +',,,,,,,,,,,,,,,107,,,,99,98,,,85,86,88,87,90,91,81,83,84,,,,,,82,100', +'101,102,97,92,104,,108,,103,,219,93,95,94,96,,89,,,,,,,,,,,,,,107,,', +',99,98,,,85,86,88,87,90,91,,83,84,,,,,81,82,230,,,,,,,100,101,102,97', +'92,104,,108,,103,89,,93,95,94,96,,,,,,,,,,,,,,,,107,,,,99,98,,,85,86', +'88,87,90,91,,83,84,,,,,81,82,231,,,,,,,100,101,102,97,92,104,,108,,103', +'89,,93,95,94,96,,,,,,,,,,,,,,,,107,,,,99,98,,,85,86,88,87,90,91,,83', +'84,,,,,81,82,232,,,,,,,100,101,102,97,92,104,,108,,103,89,,93,95,94', +'96,,,,,,,,,,,,,,,,107,,,,99,98,,,85,86,88,87,90,91,,83,84,,,,,81,82', +'233,,,,,,,100,101,102,97,92,104,,108,81,103,89,81,93,95,94,96,,,,,,', +'104,,108,104,103,108,,103,,107,,,,99,98,,,85,86,88,87,90,91,,83,84,107', +',,107,,82,,,,,88,87,81,88,87,83,84,,83,84,,,82,89,,82,104,,108,,103', +',,,,81,,,,,89,,,89,100,101,102,97,92,104,,108,107,103,,,93,95,94,96', +'85,86,88,87,,,,83,84,,,,,,82,107,,,,99,98,,,85,86,88,87,90,91,81,83', +'84,89,,,,,82,100,101,102,97,92,104,,108,,103,,,93,95,94,96,,89,,,,,', +',,,,,,,,107,,,,99,98,,,85,86,88,87,90,91,81,83,84,,,270,,,82,100,101', +'102,97,92,104,,108,81,103,,,93,95,94,96,,89,,,,,104,,108,,103,,,,,107', +',,,99,98,,,85,86,88,87,90,91,,83,84,107,,,81,,82,,,85,86,88,87,,,,83', +'84,104,,108,81,103,82,89,,,,,,,,,,,104,,108,,103,,89,,,107,,,,,,,,85', +'86,88,87,90,91,,83,84,107,,,,,82,,,85,86,88,87,90,91,81,83,84,,,,,,82', +'89,,,,92,104,,108,81,103,,,93,,,,,89,,,,92,104,,108,,103,,,93,,107,', +',,,,,,85,86,88,87,90,91,,83,84,107,,,,,82,,,85,86,88,87,90,91,81,83', +'84,,,,,,82,89,,,,92,104,,108,81,103,,,93,,,,,89,,,,92,104,,108,,103', +',,93,,107,,,,,,,,85,86,88,87,90,91,,83,84,107,,,,,82,,,85,86,88,87,90', +'91,81,83,84,,,,,,82,89,,,97,92,104,,108,,103,,81,93,95,94,96,,89,,,', +',,97,92,104,,108,,103,,107,93,95,94,96,,,,85,86,88,87,90,91,,83,84,', +',,107,,82,,,98,,,85,86,88,87,90,91,81,83,84,,,,89,,82,100,101,102,97', +'92,104,,108,,103,,,93,95,94,96,,89,,,,,,,,,,,,,,107,,,,99,98,,,85,86', +'88,87,90,91,81,83,84,,,,,,82,100,101,102,97,92,104,,108,,103,,,93,95', +'94,96,,89,,,,,,,,,,,,,,107,,,,99,98,,,85,86,88,87,90,91,81,83,84,,,', +',,82,100,101,102,97,92,104,274,108,81,103,,81,93,95,94,96,,89,,,,,104', +',108,104,103,108,,103,,107,,,,99,98,,81,85,86,88,87,90,91,,83,84,107', +',,107,104,82,108,,103,,,,,,,83,84,,83,84,,,82,89,,82,,,,,107,,,,,,,', +',,,,,,,83,84,,288,209,287,210,82,285,212,289,283,282,,284,286,,,,,,', +'213,208,290,288,209,287,210,,285,212,289,283,282,,284,286,,,211,291', +',,213,208,290,288,209,287,210,,285,212,289,283,282,,284,286,,,211,291', +',,213,208,290,,,,,,,,,,,,,,,,211,291' ] + racc_action_table = arr = ::Array.new(6596, 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,199,196,0,225,0,204,163,198,203,312,235,201,162,312,303,201,312', -'225,151,235,163,0,163,303,163,310,162,0,162,0,162,0,0,123,0,0,0,129', -'0,0,0,0,199,196,0,0,163,204,0,198,203,0,162,151,382,382,328,328,382', -'0,382,382,129,114,142,0,139,142,163,0,0,139,0,0,0,263,137,382,121,161', -'0,137,48,382,161,382,48,382,382,200,382,382,382,200,382,382,382,382', -'114,237,382,382,240,240,382,128,121,382,5,5,121,267,5,108,5,382,109', -'108,345,232,345,382,300,271,300,382,382,113,382,382,109,5,109,273,109', -'155,382,5,224,5,240,5,5,240,5,5,5,5,5,5,5,5,222,45,5,5,109,277,5,149', -'149,5,218,240,381,381,147,147,381,5,381,381,291,293,240,5,295,296,168', -'5,5,45,5,5,104,45,299,381,131,301,5,302,168,381,168,381,168,381,381', -'217,381,381,381,102,381,381,381,381,306,307,381,381,308,309,381,257', -'215,381,168,314,379,379,73,71,379,381,379,379,61,60,327,381,233,168', -'168,381,381,213,381,381,168,330,193,379,332,46,381,192,339,379,340,379', -'40,379,379,236,379,379,379,39,379,379,379,379,348,349,379,379,351,352', -'379,356,357,379,186,186,358,38,186,364,186,379,8,8,8,8,365,379,368,241', -'6,379,379,1,379,379,386,186,390,238,238,392,379,186,394,186,,186,186', -',186,186,186,,186,186,,,,,186,186,,,186,,,186,12,12,,,12,,12,186,,238', -',,238,186,,,,186,186,,186,186,,12,,202,202,,186,12,,12,238,12,12,,12', -'12,12,,12,12,,238,,,12,12,,,12,,,12,13,13,,,13,,13,12,,202,,,202,12', -',,,12,12,,12,12,,13,,49,49,,12,13,,13,202,13,13,,13,13,13,,13,13,,202', -',,13,13,,,13,,,13,14,14,,,14,,14,13,164,49,,,49,13,,,,13,13,,13,13,164', -'14,164,,164,,13,14,,14,49,14,14,,14,14,14,,14,14,,49,,,14,14,164,,14', -',,14,360,360,,,360,,360,14,167,,,,,14,,,164,14,14,,14,14,167,360,167', -',167,,14,360,,360,,360,360,,360,360,360,,360,360,360,360,,,360,360,167', -',360,,,360,347,347,,,347,,347,360,169,167,167,,,360,,,167,360,360,,360', -'360,169,347,169,,169,,360,347,,347,,347,347,,347,347,347,,347,347,,', -',,347,347,169,,347,,,347,189,189,,,189,,189,347,107,169,169,,,347,,', -'169,347,347,,347,347,107,189,107,,107,,347,189,,189,,189,189,,189,189', -'189,,189,189,,,,,189,189,107,,189,,,189,41,41,,,41,,41,189,,,,,,189', -',,,189,189,,189,189,,41,,,,,189,41,,41,,41,41,,41,41,41,,41,41,,,,,41', -'41,,,41,,,41,42,42,,,42,,42,41,,,,,,41,,,,41,41,,41,41,,42,,,,,41,42', -',42,,42,42,,42,42,42,,42,42,,,,,42,42,,,42,,,42,43,43,,,43,,43,42,,', -',,,42,,,,42,42,,42,42,,43,,,,,42,43,,43,,43,43,,43,43,43,,43,43,,,,', -'43,43,,,43,,,43,44,44,,,44,,44,43,,,,,,43,,,,43,43,,43,43,,44,,,,,43', -'44,,44,,44,44,,44,44,44,,44,44,,,,,44,44,,,44,,,44,190,190,,,190,,190', -'44,,,,,,44,,,,44,44,,44,44,,190,,,,,44,190,,190,,190,190,,190,190,190', -',190,190,,,,,190,190,,,190,,,190,191,191,,,191,,191,190,,,,,,190,,,', -'190,190,,190,190,,191,,,,,190,191,,191,,191,191,,191,191,191,,191,191', -',,,,191,191,,,191,,,191,331,331,,,331,,331,191,,,,,,191,,,,191,191,', -'191,191,,331,,,,,191,331,,331,,331,331,,331,331,331,,331,331,,,,,331', -'331,,,331,,,331,,,220,220,,,220,331,220,220,,,,331,,,,331,331,,331,331', -',,,220,,,331,,,220,,220,,220,220,,220,220,220,,220,220,220,220,,,220', -'220,,,220,,,220,51,51,,,51,51,51,220,,,,,,220,,,,220,220,,220,220,,51', -',,,,220,51,,51,,51,51,,51,51,51,,51,51,,,,,51,51,,,51,,,51,52,52,,,52', -'52,52,51,,,,,,51,,,,51,51,,51,51,,52,,,,,51,52,,52,,52,52,,52,52,52', -',52,52,,,,,52,52,,,52,,,52,,,53,53,,,53,52,53,53,,,,52,,,,52,52,,52', -'52,,,,53,,,52,,,53,,53,,53,53,,53,53,53,,53,53,,,,,53,53,,,53,,,53,58', -'58,,,58,,58,53,,,,,,53,,,,53,53,,53,53,,58,,,,,53,58,,58,,58,58,,58', -'58,58,,58,58,,,,,58,58,,,58,,,58,,,227,227,,,227,58,227,227,,,,58,,', -',58,58,,58,58,,,,227,,,58,,,227,,227,,227,227,,227,227,227,,227,227', -'227,227,,,227,227,,,227,,,227,,,153,153,,,153,227,153,153,,,,227,,,', -'227,227,,227,227,,,,153,,,227,,,153,,153,,153,153,,153,153,153,,153', -'153,153,153,,,153,153,,,153,,,153,63,63,,,63,,63,153,,,,,,153,,,,153', -'153,,153,153,,63,,,,,153,63,,63,,63,63,,63,63,63,,63,63,,,,,63,63,,', -'63,,,63,,,316,316,,,316,63,316,316,,,,63,,,,63,63,,63,63,,,,316,,,63', -',,316,,316,,316,316,,316,316,316,,316,316,316,316,,,316,316,,,316,,', -'316,72,72,,,72,,72,316,,,,,,316,,,,316,316,,316,316,,72,,,,,316,72,', -'72,,72,72,,72,72,72,,72,72,72,72,,,72,72,,,72,,,72,315,315,,,315,,315', -'72,,,,,,72,,,,72,72,,72,72,,315,,,,,72,315,,315,,315,315,,315,315,315', -',315,315,315,315,,,315,315,,,315,,,315,74,74,,,74,,74,315,,,,,,315,', -',,315,315,,315,315,,74,,,,,315,74,,74,,74,74,,74,74,74,,74,74,74,74', -',,74,74,,,74,,,74,75,75,,,75,,75,74,,,,,,74,,,,74,74,,74,74,,75,,,,', -'74,75,,75,,75,75,,75,75,75,,75,75,75,75,,,75,75,,,75,,,75,76,76,,,76', -',76,75,,,,,,75,,,,75,75,,75,75,,76,,,,,75,76,,76,,76,76,,76,76,76,,76', -'76,76,76,,,76,76,,,76,,,76,77,77,,,77,,77,76,,,,,,76,,,,76,76,,76,76', -',77,,,,,76,77,,77,,77,77,,77,77,77,,77,77,77,77,,,77,77,,,77,,,77,78', -'78,,,78,,78,77,,,,,,77,,,,77,77,,77,77,,78,,,,,77,78,,78,,78,78,,78', -'78,78,,78,78,78,78,,,78,78,,,78,,,78,79,79,,,79,,79,78,,,,,,78,,,,78', -'78,,78,78,,79,,,,,78,79,,79,,79,79,,79,79,79,,79,79,,,,,79,79,,,79,', -',79,80,80,,,80,,80,79,,,,,,79,,,,79,79,,79,79,,80,,,,,79,80,,80,,80', -'80,,80,80,80,,80,80,,,,,80,80,,,80,,,80,81,81,,,81,,81,80,,,,,,80,,', -',80,80,,80,80,,81,,,,,80,81,,81,,81,81,,81,81,81,,81,81,,,,,81,81,,', -'81,,,81,82,82,,,82,,82,81,,,,,,81,,,,81,81,,81,81,,82,,,,,81,82,,82', -',82,82,,82,82,82,,82,82,,,,,82,82,,,82,,,82,83,83,,,83,,83,82,,,,,,82', -',,,82,82,,82,82,,83,,,,,82,83,,83,,83,83,,83,83,83,,83,83,,,,,83,83', -',,83,,,83,84,84,,,84,,84,83,,,,,,83,,,,83,83,,83,83,,84,,,,,83,84,,84', -',84,84,,84,84,84,,84,84,,,,,84,84,,,84,,,84,85,85,,,85,,85,84,,,,,,84', -',,,84,84,,84,84,,85,,,,,84,85,,85,,85,85,,85,85,85,,85,85,,,,,85,85', -',,85,,,85,86,86,,,86,,86,85,,,,,,85,,,,85,85,,85,85,,86,,,,,85,86,,86', -',86,86,,86,86,86,,86,86,,,,,86,86,,,86,,,86,87,87,,,87,,87,86,,,,,,86', -',,,86,86,,86,86,,87,,,,,86,87,,87,,87,87,,87,87,87,,87,87,,,,,87,87', -',,87,,,87,88,88,,,88,,88,87,,,,,,87,,,,87,87,,87,87,,88,,,,,87,88,,88', -',88,88,,88,88,88,,88,88,,,,,88,88,,,88,,,88,89,89,,,89,,89,88,,,,,,88', -',,,88,88,,88,88,,89,,,,,88,89,,89,,89,89,,89,89,89,,89,89,,,,,89,89', -',,89,,,89,90,90,,,90,,90,89,,,,,,89,,,,89,89,,89,89,,90,,,,,89,90,,90', -',90,90,,90,90,90,,90,90,,,,,90,90,,,90,,,90,91,91,,,91,,91,90,,,,,,90', -',,,90,90,,90,90,,91,,,,,90,91,,91,,91,91,,91,91,91,,91,91,,,,,91,91', -',,91,,,91,92,92,,,92,,92,91,,,,,,91,,,,91,91,,91,91,,92,,,,,91,92,,92', -',92,92,,92,92,92,,92,92,,,,,92,92,,,92,,,92,93,93,,,93,,93,92,,,,,,92', -',,,92,92,,92,92,,93,,,,,92,93,,93,,93,93,,93,93,93,,93,93,,,,,93,93', -',,93,,,93,94,94,,,94,,94,93,,,,,,93,,,,93,93,,93,93,,94,,,,,93,94,,94', -',94,94,,94,94,94,,94,94,,,,,94,94,,,94,,,94,95,95,,,95,,95,94,,,,,,94', -',,,94,94,,94,94,,95,,,,,94,95,,95,,95,95,,95,95,95,,95,95,,,,,95,95', -',,95,,,95,96,96,,,96,,96,95,,,,,,95,,,,95,95,,95,95,,96,,,,,95,96,,96', -',96,96,,96,96,96,,96,96,,,,,96,96,,,96,,,96,97,97,,,97,,97,96,,,,,,96', -',,,96,96,,96,96,,97,,,,,96,97,,97,,97,97,,97,97,97,,97,97,,,,,97,97', -',,97,,,97,98,98,,,98,,98,97,,,,,,97,,,,97,97,,97,97,,98,,,,,97,98,,98', -',98,98,,98,98,98,,98,98,,,,,98,98,,,98,,,98,99,99,,,99,,99,98,,,,,,98', -',,,98,98,,98,98,,99,,,,,98,99,,99,,99,99,,99,99,99,,99,99,,,,,99,99', -',,99,,,99,100,100,,,100,,100,99,,,,,,99,,,,99,99,,99,99,,100,,,,,99', -'100,,100,,100,100,,100,100,100,,100,100,,,,,100,100,,,100,,,100,101', -'101,,,101,,101,100,,,,,,100,,,,100,100,,100,100,,101,,,,,100,101,,101', -',101,101,,101,101,101,,101,101,,,,,101,101,,,101,,,101,,,304,304,,,304', -'101,304,304,,,,101,,,101,101,101,,101,101,,,,304,,,101,,,304,,304,,304', -'304,,304,304,304,,304,304,,,,,304,304,,,304,,,304,103,103,,,103,,103', -'304,,,,,,304,,,,304,304,,304,304,,103,,,,,304,103,103,103,103,103,103', -'103,103,103,103,,103,103,,,,,103,103,103,103,103,,,103,297,297,,,297', -',297,103,,,,,103,103,,,,103,103,,103,103,,297,,,,,103,297,,297,,297', -'297,,297,297,297,,297,297,,,,,297,297,,,297,,,297,105,105,,,105,,105', -'297,,,,,,297,,,,297,297,,297,297,,105,,,,,297,105,,105,,105,105,,105', -'105,105,,105,105,,,,,105,105,,,105,,,105,106,106,,,106,,106,105,,,,', -',105,,,,105,105,,105,105,,106,,,,,105,106,,106,,106,106,,106,106,106', -',106,106,,,,,106,106,,,106,,,106,290,290,,,290,,290,106,,,,,,106,,,', -'106,106,,106,106,,290,,,,,106,290,,290,,290,290,,290,290,290,,290,290', -',,,,290,290,,,290,,,290,276,276,,,276,,276,290,,,,,,290,,,,290,290,', -'290,290,,276,,,,,290,276,,276,,276,276,,276,276,276,,276,276,,,,,276', -'276,,,276,,,276,275,275,,,275,,275,276,,,,,,276,,,,276,276,,276,276', -',275,,,,,276,275,,275,,275,275,,275,275,275,,275,275,,,,,275,275,,,275', -',,275,,,228,228,,,228,275,228,228,,,,275,,,,275,275,,275,275,,,,228', -',,275,,,228,,228,,228,228,,228,228,228,,228,228,228,228,,,228,228,,', -'228,,,228,111,111,,,111,,111,228,,,,,,228,,,,228,228,,228,228,,111,111', -',,,228,111,,111,,111,111,,111,111,111,,111,111,,,,,111,111,,,111,,,111', -'272,272,,,272,,272,111,,,,,,111,,,,111,111,,111,111,,272,,,,,111,272', -',272,,272,272,,272,272,272,,272,272,,,,,272,272,,,272,,,272,266,266', -',,266,,266,272,,,,,,272,,,,272,272,,272,272,,266,,,,,272,266,,266,,266', -'266,,266,266,266,,266,266,,,,,266,266,,,266,,,266,115,115,,,115,,115', -'266,,,,,,266,,,,266,266,,266,266,,115,115,,,,266,115,,115,,115,115,', -'115,115,115,,115,115,,,,,115,115,,,115,,,115,150,150,,,150,,150,115', -',,,,,115,,,,115,115,,115,115,,150,,,,,115,150,,150,,150,150,,150,150', -'150,,150,150,150,150,,,150,150,,,150,,,150,229,229,,,229,,229,150,,', -',,,150,,,,150,150,,150,150,,229,,,,,150,229,,229,,229,229,,229,229,229', -',229,229,,,,,229,229,,,229,,,229,244,244,,,244,244,244,229,,,,,,229', -',,,229,229,,229,229,,244,,,,,229,244,,244,,244,244,,244,244,244,,244', -'244,,,,,244,244,,,244,,,244,231,231,,,231,,231,244,,,,,,244,,,,244,244', -',244,244,,231,,,,,244,231,,231,,231,231,,231,231,231,,231,231,,,,,231', -'231,,,231,,,231,265,265,,,265,,265,231,,,,,,231,,,,231,231,,231,231', -',265,,,,,231,265,,265,,265,265,,265,265,265,,265,265,,,,,265,265,,,265', -',,265,122,122,,,122,,122,265,,,,,,265,,,,265,265,,265,265,,122,,,,,265', -'122,,122,,122,122,,122,122,122,,122,122,,,,,122,122,,,122,,,122,242', -'242,,,242,242,242,122,,,,,,122,,,,122,122,,122,122,,242,,,,,122,242', -',242,,242,242,,242,242,242,,242,242,,,,,242,242,,,242,,,242,253,253', -',,253,,253,242,,,,,,242,,,,242,242,,242,242,,253,,,,,242,253,,253,,253', -'253,,253,253,253,,253,253,,,,,253,253,,,253,,,253,,,248,248,,,248,253', -'248,248,,,,253,,,,253,253,,253,253,,,,248,,,253,,,248,,248,,248,248', -',248,248,248,,248,248,,,,,248,248,,,248,,,248,246,246,,,246,,246,248', -',,,,,248,,,,248,248,,248,248,,246,,,,,248,246,,246,,246,246,,246,246', -'246,,246,246,,,,,246,246,,,246,,,246,230,230,,,230,,230,246,,,,,,246', -',,,246,246,,246,246,,230,,,,,246,230,230,230,230,230,230,230,230,230', -'230,,230,230,,,,,230,230,230,230,230,,,230,,,,,,,,230,,,,,230,230,,', -',230,230,,230,230,136,,,,,,230,,,136,136,136,136,136,136,,136,,136,', -',136,136,136,136,,,,,,,,,,,,,,,,136,,,,136,136,,,136,136,136,136,136', -'136,,136,136,,,262,,262,136,,262,,,,262,262,262,262,262,262,,262,,262', -',136,262,262,262,262,,,,,,,,,,,,,,,,262,,,,262,262,,,262,262,262,262', -'262,262,141,262,262,,,141,,,262,141,141,141,141,141,141,,141,,141,,', -'141,141,141,141,262,,,,,,,,,,,,,,,141,,,,141,141,,,141,141,141,141,141', -'141,,141,141,,,120,,120,141,,,,,,120,120,120,120,120,120,,120,,120,', -'141,120,120,120,120,,,,,,,,,,,,,,,,120,,,,120,120,,,120,120,120,120', -'120,120,145,120,120,,,,,,120,145,145,145,145,145,145,,145,,145,,,145', -'145,145,145,120,,,,,,,,,,,,,,,145,,,,145,145,,,145,145,145,145,145,145', -',145,145,,,119,,119,145,,,,,,119,119,119,119,119,119,,119,,119,,145', -'119,119,119,119,,,,,,,,,,,,,,,,119,,,,119,119,,,119,119,119,119,119', -'119,,119,119,,,118,,118,119,,,,,,118,118,118,118,118,118,,118,,118,', -'119,118,118,118,118,,,,,,,,,,,,,,,,118,,,,118,118,,,118,118,118,118', -'118,118,,118,118,,,116,,116,118,,,,,,116,116,116,116,116,116,,116,,116', -',118,116,116,116,116,,,,,,,,,,,,,,,,116,,,,116,116,,,116,116,116,116', -'116,116,110,116,116,,,,,,116,110,110,110,110,110,110,,110,,110,,110', -'110,110,110,110,116,,,,,,,,,,,,,,,110,,,,110,110,,,110,110,110,110,110', -'110,152,110,110,,,,,,110,152,152,152,152,152,152,,152,,152,,,152,152', -'152,152,110,,,,,,,,,,,,,,,152,,,,152,152,,,152,152,152,152,152,152,320', -'152,152,,,,,,152,320,320,320,320,320,320,,320,,320,152,152,320,320,320', -'320,152,,,,,,,,,,,,,,,320,,,,320,320,,,320,320,320,320,320,320,323,320', -'320,,,,,,320,323,323,323,323,323,323,,323,,323,,,323,323,323,323,320', -',,,,,,,,,,,,,,323,,,,323,323,,,323,323,323,323,323,323,329,323,323,', -',,,,323,329,329,329,329,329,329,,329,,329,,,329,329,329,329,323,,,,', -',,,,,,,,,,329,,,,329,329,,,329,329,329,329,329,329,212,329,329,,,,,', -'329,212,212,212,212,212,212,,212,,212,,,212,212,212,212,329,,,,,,,,', -',,,,,,212,,,,212,212,,,212,212,212,212,212,212,337,212,212,,,,,,212', -'337,337,337,337,337,337,,337,,337,,,337,337,337,337,212,,,,,,,,,,,,', -',,337,,,,337,337,,,337,337,337,337,337,337,338,337,337,,,,,,337,338', -'338,338,338,338,338,,338,,338,165,,338,338,338,338,337,,,,,,,,165,,165', -',165,,,338,,,,338,338,,,338,338,338,338,338,338,,338,338,,,165,,,338', -',166,,,,,165,165,,,,165,165,,,166,338,166,165,166,,,,,,,,,,,,,,,,165', -',,,,,166,,,,,,,,,,166,166,,,344,166,166,,,,,,166,344,344,344,344,344', -'344,,344,,344,,,344,344,344,344,166,,,,,,,,,,,,,,,344,,,,344,344,,,344', -'344,344,344,344,344,188,344,344,,,,,,344,188,188,188,188,188,188,188', -'188,,188,,,188,188,188,188,344,,,,,,,,,,,,,,,188,,,,188,188,,,188,188', -'188,188,188,188,,188,188,,,11,,11,188,,,,,,11,11,11,11,11,11,,11,,11', -'170,188,11,11,11,11,,,,,,,,,170,,170,,170,,,11,,,,11,11,,,11,11,11,11', -'11,11,,11,11,,,170,,,11,,171,,,170,170,170,170,,,,170,170,,,171,11,171', -'170,171,172,,,,,,,,,,,,,,172,170,172,,172,,,171,,,,,,,,171,171,171,171', -',,,171,171,,,172,,,171,,173,,,172,172,172,172,172,172,,172,172,,,173', -'171,173,172,173,,,,,,,174,,,,,,,,,172,,,,174,174,173,174,,174,,175,174', -',173,173,173,173,173,173,,173,173,,175,175,,175,173,175,,174,175,,,', -'176,,,174,174,174,174,174,174,173,174,174,,176,176,175,176,174,176,', -',176,,175,175,175,175,175,175,,175,175,,,174,,,175,,,176,,,,,177,,,176', -'176,176,176,176,176,175,176,176,,177,177,,177,176,177,,,177,,,,,,,,', -',,,,176,,,,,,177,,,,,178,,,177,177,177,177,177,177,,177,177,178,178', -'178,,178,177,178,,,178,178,178,178,,,,,,,,,,177,,,,,,178,,,,,179,,,178', -'178,178,178,178,178,,178,178,179,179,179,,179,178,179,,,179,179,179', -'179,,,,,,,,,,178,,,,,,179,,,,,179,,,179,179,179,179,179,179,180,179', -'179,,,,,,179,180,180,180,180,180,180,,180,,180,,,180,180,180,180,179', -',,,,,,,,,,,,,,180,,,,180,180,,,180,180,180,180,180,180,183,180,180,', -',183,,,180,183,183,183,183,183,183,,183,,183,,,183,183,183,183,180,', -',,,,,,,,,,,,,183,,,,183,183,,,183,183,183,183,183,183,182,183,183,,', -',,,183,182,182,182,182,182,182,,182,,182,,,182,182,182,182,183,,,,,', -',,,,,,,,,182,,,,182,182,,,182,182,182,182,182,182,181,182,182,,,,,,182', -'181,181,181,181,181,181,,181,,181,,,181,181,181,181,182,,,,,,,,,,,,', -',,181,,,,181,181,,,181,181,181,181,181,181,,181,181,,,,,,181,274,274', -'274,274,,274,274,274,274,274,,274,274,,,,181,,,274,274,274,269,269,269', -'269,,269,269,269,269,269,,269,269,,,274,274,,,269,269,269,211,211,211', -'211,,211,211,211,211,211,,211,211,,,269,269,,,211,211,211,,,,,,,,,,', -',,,,,211,211' ] - racc_action_check = arr = ::Array.new(6233, nil) +'0,0,201,202,0,131,0,207,228,199,206,106,307,116,142,238,50,50,261,142', +'154,307,228,0,238,243,243,140,46,0,131,0,140,0,0,49,0,0,0,49,0,0,0,0', +'201,202,0,0,116,207,0,199,206,0,50,154,46,50,388,388,46,0,388,243,388', +'388,243,0,164,150,150,0,0,164,0,0,0,50,123,0,203,388,145,0,203,145,243', +'388,50,388,133,388,388,267,388,388,388,243,388,388,388,388,104,204,388', +'388,123,204,388,51,123,388,75,51,316,271,5,5,316,388,5,316,5,110,167', +'388,304,110,304,388,388,158,388,388,350,275,350,388,167,5,167,388,167', +'333,333,5,73,5,277,5,5,245,5,5,5,5,5,5,5,5,152,152,5,5,167,63,5,62,281', +'5,8,8,8,8,387,387,244,5,387,295,387,387,166,5,297,299,167,5,5,300,5', +'5,240,303,239,5,166,387,166,5,166,305,306,387,236,387,235,387,387,310', +'387,387,387,311,387,387,387,387,312,313,387,387,166,314,387,115,318', +'387,47,227,41,225,385,385,138,387,385,332,385,385,111,387,221,220,166', +'387,387,335,387,387,40,337,218,387,111,385,111,387,111,216,344,385,345', +'385,196,385,385,195,385,385,385,39,385,385,385,385,353,354,385,385,111', +'356,385,357,361,385,362,363,125,369,365,365,370,385,365,373,365,375', +'165,385,130,6,1,385,385,392,385,385,397,399,401,385,165,365,165,385', +'165,,,365,,365,,365,365,,365,365,365,,365,365,365,365,,,365,365,165', +',365,,,365,,,,,12,12,,365,12,,12,,109,365,,,,365,365,,365,365,,,,365', +'109,12,109,365,109,,,12,,12,,12,12,,12,12,12,,12,12,,,,,12,12,109,,12', +',,12,,,,,13,13,,12,13,,13,,,12,,,,12,12,,12,12,,,,12,,13,,12,,,,13,', +'13,,13,13,,13,13,13,,13,13,,,,,13,13,,,13,,,13,,,,,14,14,,13,14,,14', +',,13,,,,13,13,,13,13,,,,13,,14,,13,,,,14,,14,,14,14,,14,14,14,,14,14', +',,,,14,14,,,14,,,14,,,,,352,352,,14,352,,352,,,14,,,,14,14,,14,14,,', +',14,,352,,14,,,,352,,352,,352,352,,352,352,352,,352,352,,,,,352,352', +',,352,,,352,,,,,336,336,,352,336,,336,,,352,,,,352,352,,352,352,,,,352', +',336,,352,,,,336,,336,,336,336,,336,336,336,,336,336,,,,,336,336,,,336', +',,336,,,,,324,324,,336,324,,324,,,336,,,,336,336,,336,336,,,,336,,324', +',336,,,,324,,324,,324,324,,324,324,324,,324,324,324,324,,,324,324,,', +'324,,,324,,,,,42,42,,324,42,,42,,,324,,,,324,324,,324,324,,,,324,,42', +',324,,,,42,,42,,42,42,,42,42,42,,42,42,,,,,42,42,,,42,,,42,,,,,43,43', +',42,43,,43,,,42,,,,42,42,,42,42,,,,42,,43,,42,,,,43,,43,,43,43,,43,43', +'43,,43,43,,,,,43,43,,,43,,,43,,,,,44,44,,43,44,,44,,,43,,,,43,43,,43', +'43,,,,43,,44,,43,,,,44,,44,,44,44,,44,44,44,,44,44,,,,,44,44,,,44,,', +'44,,,,,45,45,,44,45,,45,,,44,,,,44,44,,44,44,,,,44,,45,,44,,,,45,,45', +',45,45,,45,45,45,,45,45,,,,,45,45,,,45,,,45,,,,,320,320,,45,320,,320', +'320,,45,,,,45,45,,45,45,,,,45,,320,,45,,,,320,,320,,320,320,,320,320', +'320,,320,320,320,320,,,320,320,,,320,,,320,,,,,319,319,,320,319,,319', +',,320,,,,320,320,,320,320,,,,320,,319,,320,,,,319,,319,,319,319,,319', +'319,319,,319,319,319,319,,,319,319,,,319,,,319,,,,,308,308,,319,308', +',308,308,,319,,,,319,319,,319,319,,,,319,,308,,319,,,,308,,308,,308', +'308,,308,308,308,,308,308,,,,,308,308,,,308,,,308,,,,,301,301,,308,301', +',301,,,308,,,,308,308,,308,308,,,,308,,301,,308,,,,301,,301,,301,301', +',301,301,301,,301,301,,,,,301,301,,,301,,,301,,,,,294,294,,301,294,', +'294,,,301,,,,301,301,,301,301,,,,301,,294,,301,,,,294,,294,,294,294', +',294,294,294,,294,294,,,,,294,294,,,294,,,294,,,,,53,53,,294,53,53,53', +',,294,,,,294,294,,294,294,,,,294,,53,,294,,,,53,,53,,53,53,,53,53,53', +',53,53,,,,,53,53,,,53,,,53,,,,,54,54,,53,54,54,54,,,53,,,,53,53,,53', +'53,,,,53,,54,,53,,,,54,,54,,54,54,,54,54,54,,54,54,,,,,54,54,,,54,,', +'54,,,,,55,55,,54,55,,55,55,,54,,,,54,54,,54,54,,,,54,,55,,54,,,,55,', +'55,,55,55,,55,55,55,,55,55,,,,,55,55,,,55,,,55,,,,,60,60,,55,60,,60', +',,55,,,,55,55,,55,55,,,,55,,60,,55,,,,60,,60,,60,60,,60,60,60,,60,60', +',,,,60,60,,,60,,,60,,,,,280,280,,60,280,,280,,,60,,,,60,60,,60,60,,', +',60,,280,,60,,,,280,,280,,280,280,,280,280,280,,280,280,,,,,280,280', +',,280,,,280,,,,,279,279,,280,279,,279,,,280,,,,280,280,,280,280,,,,280', +',279,,280,,,,279,,279,,279,279,,279,279,279,,279,279,,,,,279,279,,,279', +',,279,,,,,65,65,,279,65,,65,,,279,,,,279,279,,279,279,,,,279,,65,,279', +',,,65,,65,,65,65,,65,65,65,,65,65,,,,,65,65,,,65,,,65,,,,,276,276,,65', +'276,,276,,,65,,,,65,65,,65,65,,,,65,,276,,65,,,,276,,276,,276,276,,276', +'276,276,,276,276,,,,,276,276,,,276,,,276,,,,,153,153,,276,153,,153,', +',276,,,,276,276,,276,276,,,,276,,153,,276,,,,153,,153,,153,153,,153', +'153,153,,153,153,153,153,,,153,153,,,153,,,153,,,,,270,270,,153,270', +',270,,,153,,,,153,153,,153,153,,,,153,,270,,153,,,,270,,270,,270,270', +',270,270,270,,270,270,,,,,270,270,,,270,,,270,,,,,76,76,,270,76,,76', +',,270,,,,270,270,,270,270,,,,270,,76,,270,,,,76,,76,,76,76,,76,76,76', +',76,76,76,76,,,76,76,,,76,,,76,,,,,77,77,,76,77,,77,,,76,,,,76,76,,76', +'76,,,,76,,77,,76,,,,77,,77,,77,77,,77,77,77,,77,77,77,77,,,77,77,,,77', +',,77,,,,,78,78,,77,78,,78,,,77,,,,77,77,,77,77,,,,77,,78,,77,,,,78,', +'78,,78,78,,78,78,78,,78,78,78,78,,,78,78,,,78,,,78,,,,,79,79,,78,79', +',79,,,78,,,,78,78,,78,78,,,,78,,79,,78,,,,79,,79,,79,79,,79,79,79,,79', +'79,79,79,,,79,79,,,79,,,79,,,,,80,80,,79,80,,80,,,79,,,,79,79,,79,79', +',,,79,,80,,79,,,,80,,80,,80,80,,80,80,80,,80,80,80,80,,,80,80,,,80,', +',80,,,,,81,81,,80,81,,81,,,80,,,,80,80,,80,80,,,,80,,81,,80,,,,81,,81', +',81,81,,81,81,81,,81,81,,,,,81,81,,,81,,,81,,,,,82,82,,81,82,,82,,,81', +',,,81,81,,81,81,,,,81,,82,,81,,,,82,,82,,82,82,,82,82,82,,82,82,,,,', +'82,82,,,82,,,82,,,,,83,83,,82,83,,83,,,82,,,,82,82,,82,82,,,,82,,83', +',82,,,,83,,83,,83,83,,83,83,83,,83,83,,,,,83,83,,,83,,,83,,,,,84,84', +',83,84,,84,,,83,,,,83,83,,83,83,,,,83,,84,,83,,,,84,,84,,84,84,,84,84', +'84,,84,84,,,,,84,84,,,84,,,84,,,,,85,85,,84,85,,85,,,84,,,,84,84,,84', +'84,,,,84,,85,,84,,,,85,,85,,85,85,,85,85,85,,85,85,,,,,85,85,,,85,,', +'85,,,,,86,86,,85,86,,86,,,85,,,,85,85,,85,85,,,,85,,86,,85,,,,86,,86', +',86,86,,86,86,86,,86,86,,,,,86,86,,,86,,,86,,,,,87,87,,86,87,,87,,,86', +',,,86,86,,86,86,,,,86,,87,,86,,,,87,,87,,87,87,,87,87,87,,87,87,,,,', +'87,87,,,87,,,87,,,,,88,88,,87,88,,88,,,87,,,,87,87,,87,87,,,,87,,88', +',87,,,,88,,88,,88,88,,88,88,88,,88,88,,,,,88,88,,,88,,,88,,,,,89,89', +',88,89,,89,,,88,,,,88,88,,88,88,,,,88,,89,,88,,,,89,,89,,89,89,,89,89', +'89,,89,89,,,,,89,89,,,89,,,89,,,,,90,90,,89,90,,90,,,89,,,,89,89,,89', +'89,,,,89,,90,,89,,,,90,,90,,90,90,,90,90,90,,90,90,,,,,90,90,,,90,,', +'90,,,,,91,91,,90,91,,91,,,90,,,,90,90,,90,90,,,,90,,91,,90,,,,91,,91', +',91,91,,91,91,91,,91,91,,,,,91,91,,,91,,,91,,,,,92,92,,91,92,,92,,,91', +',,,91,91,,91,91,,,,91,,92,,91,,,,92,,92,,92,92,,92,92,92,,92,92,,,,', +'92,92,,,92,,,92,,,,,93,93,,92,93,,93,,,92,,,,92,92,,92,92,,,,92,,93', +',92,,,,93,,93,,93,93,,93,93,93,,93,93,,,,,93,93,,,93,,,93,,,,,94,94', +',93,94,,94,,,93,,,,93,93,,93,93,,,,93,,94,,93,,,,94,,94,,94,94,,94,94', +'94,,94,94,,,,,94,94,,,94,,,94,,,,,95,95,,94,95,,95,,,94,,,,94,94,,94', +'94,,,,94,,95,,94,,,,95,,95,,95,95,,95,95,95,,95,95,,,,,95,95,,,95,,', +'95,,,,,96,96,,95,96,,96,,,95,,,,95,95,,95,95,,,,95,,96,,95,,,,96,,96', +',96,96,,96,96,96,,96,96,,,,,96,96,,,96,,,96,,,,,97,97,,96,97,,97,,,96', +',,,96,96,,96,96,,,,96,,97,,96,,,,97,,97,,97,97,,97,97,97,,97,97,,,,', +'97,97,,,97,,,97,,,,,98,98,,97,98,,98,,,97,,,,97,97,,97,97,,,,97,,98', +',97,,,,98,,98,,98,98,,98,98,98,,98,98,,,,,98,98,,,98,,,98,,,,,99,99', +',98,99,,99,,,98,,,,98,98,,98,98,,,,98,,99,,98,,,,99,,99,,99,99,,99,99', +'99,,99,99,,,,,99,99,,,99,,,99,,,,,100,100,,99,100,,100,,,99,,,,99,99', +',99,99,,,,99,,100,,99,,,,100,,100,,100,100,,100,100,100,,100,100,,,', +',100,100,,,100,,,100,,,,,101,101,,100,101,,101,,,100,,,,100,100,,100', +'100,,,,100,,101,,100,,,,101,,101,,101,101,,101,101,101,,101,101,,,,', +'101,101,,,101,,,101,,,,,102,102,,101,102,,102,,,101,,,,101,101,,101', +'101,,,,101,,102,,101,,,,102,,102,,102,102,,102,102,102,,102,102,,,,', +'102,102,,,102,,,102,,,,,103,103,,102,103,,103,,,102,,,,102,102,,102', +'102,,,,102,,103,,102,,,,103,,103,,103,103,,103,103,103,,103,103,,,,', +'103,103,,,103,,,103,,,,,269,269,,103,269,,269,,,103,,,103,103,103,,103', +'103,,,,103,,269,,103,,,,269,,269,,269,269,,269,269,269,,269,269,,,,', +'269,269,,,269,,,269,,,,,105,105,,269,105,,105,,,269,,,,269,269,,269', +'269,,,,269,,105,,269,,,,105,105,105,105,105,105,105,105,105,105,,105', +'105,,,,,105,105,105,105,105,,,105,,,,,257,257,,105,257,,257,,105,105', +',,,105,105,,105,105,,,,105,,257,,105,,,,257,,257,,257,257,,257,257,257', +',257,257,,,,,257,257,,,257,,,257,,,,,107,107,,257,107,,107,,,257,,,', +'257,257,,257,257,,,,257,,107,,257,,,,107,,107,,107,107,,107,107,107', +',107,107,,,,,107,107,,,107,,,107,,,,,108,108,,107,108,,108,,,107,,,', +'107,107,,107,107,,,,107,,108,,107,,,,108,,108,,108,108,,108,108,108', +',108,108,,,,,108,108,,,108,,,108,,,,,252,252,,108,252,,252,252,,108', +',,,108,108,,108,108,,,,108,,252,,108,,,,252,,252,,252,252,,252,252,252', +',252,252,,,,,252,252,,,252,,,252,,,,,250,250,,252,250,,250,,,252,,,', +'252,252,,252,252,,,,252,,250,,252,,,,250,,250,,250,250,,250,250,250', +',250,250,,,,,250,250,,,250,,,250,,,,,248,248,,250,248,248,248,,,250', +',,,250,250,,250,250,,,,250,,248,,250,,,,248,,248,,248,248,,248,248,248', +',248,248,,,,,248,248,,,248,,,248,,,,,246,246,,248,246,246,246,,,248', +',,,248,248,,248,248,,,,248,,246,,248,,,,246,,246,,246,246,,246,246,246', +',246,246,,,,,246,246,,,246,,,246,,,,,113,113,,246,113,,113,,,246,,,', +'246,246,,246,246,,,,246,,113,113,246,,,,113,,113,,113,113,,113,113,113', +',113,113,,,,,113,113,,,113,,,113,,,,,234,234,,113,234,,234,,,113,,,', +'113,113,,113,113,,,,113,,234,,113,,,,234,,234,,234,234,,234,234,234', +',234,234,,,,,234,234,,,234,,,234,,,,,233,233,,234,233,,233,,,234,,,', +'234,234,,234,234,,,,234,,233,,234,,,,233,233,233,233,233,233,233,233', +'233,233,,233,233,,,,,233,233,233,233,233,,,233,,,,,117,117,,233,117', +',117,,233,233,,,,233,233,,233,233,,,,233,,117,117,233,,,,117,,117,,117', +'117,,117,117,117,,117,117,,,,,117,117,,,117,,,117,,,,,232,232,,117,232', +',232,,,117,,,,117,117,,117,117,,,,117,,232,,117,,,,232,,232,,232,232', +',232,232,232,,232,232,,,,,232,232,,,232,,,232,,,,,231,231,,232,231,', +'231,231,,232,,,,232,232,,232,232,,,,232,,231,,232,,,,231,,231,,231,231', +',231,231,231,,231,231,231,231,,,231,231,,,231,,,231,,,,,230,230,,231', +'230,,230,230,,231,,,,231,231,,231,231,,,,231,,230,,231,,,,230,,230,', +'230,230,,230,230,230,,230,230,230,230,,,230,230,,,230,,,230,,,,,223', +'223,,230,223,,223,223,,230,,,,230,230,,230,230,,,,230,,223,,230,,,,223', +',223,,223,223,,223,223,223,,223,223,223,223,,,223,223,,,223,,,223,,', +',,194,194,,223,194,,194,,,223,,,,223,223,,223,223,,,,223,,194,,223,', +',,194,,194,,194,194,,194,194,194,,194,194,,,,,194,194,,,194,,,194,,', +',,124,124,,194,124,,124,,,194,,,,194,194,,194,194,,,,194,,124,,194,', +',,124,,124,,124,124,,124,124,124,,124,124,,,,,124,124,,,124,,,124,,', +',,193,193,,124,193,,193,,,124,,,,124,124,,124,124,,,,124,,193,,124,', +',,193,,193,,193,193,,193,193,193,,193,193,,,,,193,193,,,193,,,193,,', +',,192,192,,193,192,,192,,,193,,,,193,193,,193,193,,,,193,,192,,193,', +',,192,,192,,192,192,,192,192,192,,192,192,,,,,192,192,,,192,,,192,,', +',,189,189,,192,189,,189,,,192,,,,192,192,,192,192,,,,192,,189,,192,', +',,189,,189,,189,189,,189,189,189,,189,189,,,,,189,189,,,189,,,189,,', +',,156,156,,189,156,,156,156,,189,,,,189,189,,189,189,,,,189,,156,,189', +',,,156,,156,,156,156,,156,156,156,,156,156,156,156,,,156,156,,,156,', +',156,,,,,74,74,,156,74,,74,,,156,,,,156,156,,156,156,,205,205,156,,74', +',156,,,,74,,74,,74,74,,74,74,74,,74,74,74,74,,,74,74,,,74,,,74,,,,205', +',,205,74,,,,,,74,,241,241,74,74,,74,74,139,,,74,205,,,74,,139,139,139', +'139,139,139,205,139,,139,,,139,139,139,139,,,,,,,241,,,241,,,,,,139', +',,,139,139,,,139,139,139,139,139,139,241,139,139,,,,,11,139,11,,241', +',,,,11,11,11,11,11,11,,11,,11,139,,11,11,11,11,,,,,,,,,,,,,,,,11,,,', +'11,11,,,11,11,11,11,11,11,349,11,11,,,,,,11,349,349,349,349,349,349', +',349,,349,,,349,349,349,349,,11,,,,,,,,,,,,,,349,,,,349,349,,,349,349', +'349,349,349,349,144,349,349,,,144,,,349,144,144,144,144,144,144,,144', +',144,,,144,144,144,144,,349,,,,,,,,,,,,,,144,,,,144,144,,,144,144,144', +'144,144,144,343,144,144,,,,,,144,343,343,343,343,343,343,,343,,343,', +',343,343,343,343,,144,,,,,,,,,,,,,,343,,,,343,343,,,343,343,343,343', +'343,343,148,343,343,,,,,,343,148,148,148,148,148,148,,148,,148,,,148', +'148,148,148,,343,,,,,,,,,,,,,,148,,,,148,148,,,148,148,148,148,148,148', +'342,148,148,,,,,,148,342,342,342,342,342,342,,342,,342,,,342,342,342', +'342,,148,,,,,,,,,,,,,,342,,,,342,342,,,342,342,342,342,342,342,334,342', +'342,,,,,,342,334,334,334,334,334,334,,334,,334,,,334,334,334,334,,342', +',,,,,,,,,,,,,334,,,,334,334,,,334,334,334,334,334,334,328,334,334,,', +',,,334,328,328,328,328,328,328,,328,,328,,,328,328,328,328,,334,,,,', +',,,,,,,,,328,,,,328,328,,,328,328,328,328,328,328,325,328,328,,,,,,328', +'325,325,325,325,325,325,,325,,325,,,325,325,325,325,,328,,,,,,,,,,,', +',,325,,,,325,325,,,325,325,325,325,325,325,155,325,325,,,,,,325,155', +'155,155,155,155,155,,155,,155,,,155,155,155,155,,325,,,,,,,,,,,,,,155', +',,,155,155,,,155,155,155,155,155,155,,155,155,,,,,,155,,,,,,,,,,,155', +'155,,266,,266,,155,266,,,,266,266,266,266,266,266,,266,,266,,,266,266', +'266,266,,,,,,,,,,,,,,,,266,,,,266,266,,,266,266,266,266,266,266,112', +'266,266,,,,,,266,112,112,112,112,112,112,,112,,112,,112,112,112,112', +'112,,266,,,,,,,,,,,,,,112,,,,112,112,,,112,112,112,112,112,112,,112', +'112,,,,,118,112,118,,,,,,,118,118,118,118,118,118,,118,,118,112,,118', +'118,118,118,,,,,,,,,,,,,,,,118,,,,118,118,,,118,118,118,118,118,118', +',118,118,,,,,120,118,120,,,,,,,120,120,120,120,120,120,,120,,120,118', +',120,120,120,120,,,,,,,,,,,,,,,,120,,,,120,120,,,120,120,120,120,120', +'120,,120,120,,,,,121,120,121,,,,,,,121,121,121,121,121,121,,121,,121', +'120,,121,121,121,121,,,,,,,,,,,,,,,,121,,,,121,121,,,121,121,121,121', +'121,121,,121,121,,,,,122,121,122,,,,,,,122,122,122,122,122,122,,122', +'168,122,121,169,122,122,122,122,,,,,,,168,,168,169,168,169,,169,,122', +',,,122,122,,,122,122,122,122,122,122,,122,122,168,,,169,,122,,,,,168', +'168,173,169,169,168,168,,169,169,,,168,122,,169,173,,173,,173,,,,,215', +',,,,168,,,169,215,215,215,215,215,215,,215,173,215,,,215,215,215,215', +'173,173,173,173,,,,173,173,,,,,,173,215,,,,215,215,,,215,215,215,215', +'215,215,185,215,215,173,,,,,215,185,185,185,185,185,185,,185,,185,,', +'185,185,185,185,,215,,,,,,,,,,,,,,185,,,,185,185,,,185,185,185,185,185', +'185,186,185,185,,,186,,,185,186,186,186,186,186,186,,186,174,186,,,186', +'186,186,186,,185,,,,,174,,174,,174,,,,,186,,,,186,186,,,186,186,186', +'186,186,186,,186,186,174,,,175,,186,,,174,174,174,174,,,,174,174,175', +',175,176,175,174,186,,,,,,,,,,,176,,176,,176,,174,,,175,,,,,,,,175,175', +'175,175,175,175,,175,175,176,,,,,175,,,176,176,176,176,176,176,177,176', +'176,,,,,,176,175,,,,177,177,,177,178,177,,,177,,,,,176,,,,178,178,,178', +',178,,,178,,177,,,,,,,,177,177,177,177,177,177,,177,177,178,,,,,177', +',,178,178,178,178,178,178,179,178,178,,,,,,178,177,,,,179,179,,179,180', +'179,,,179,,,,,178,,,,180,180,,180,,180,,,180,,179,,,,,,,,179,179,179', +'179,179,179,,179,179,180,,,,,179,,,180,180,180,180,180,180,181,180,180', +',,,,,180,179,,,181,181,181,,181,,181,,182,181,181,181,181,,180,,,,,', +'182,182,182,,182,,182,,181,182,182,182,182,,,,181,181,181,181,181,181', +',181,181,,,,182,,181,,,182,,,182,182,182,182,182,182,183,182,182,,,', +'181,,182,183,183,183,183,183,183,,183,,183,,,183,183,183,183,,182,,', +',,,,,,,,,,,183,,,,183,183,,,183,183,183,183,183,183,184,183,183,,,,', +',183,184,184,184,184,184,184,,184,,184,,,184,184,184,184,,183,,,,,,', +',,,,,,,184,,,,184,184,,,184,184,184,184,184,184,191,184,184,,,,,,184', +'191,191,191,191,191,191,191,191,171,191,,170,191,191,191,191,,184,,', +',,171,,171,170,171,170,,170,,191,,,,191,191,,172,191,191,191,191,191', +'191,,191,191,171,,,170,172,191,172,,172,,,,,,,171,171,,170,170,,,171', +'191,,170,,,,,172,,,,,,,,,,,,,,,172,172,,214,214,214,214,172,214,214', +'214,214,214,,214,214,,,,,,,214,214,214,273,273,273,273,,273,273,273', +'273,273,,273,273,,,214,214,,,273,273,273,278,278,278,278,,278,278,278', +'278,278,,278,278,,,273,273,,,278,278,278,,,,,,,,,,,,,,,,278,278' ] + racc_action_check = arr = ::Array.new(6596, 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, 295, nil, nil, nil, 108, 280, nil, 220, nil, - nil, 5532, 328, 382, 436, nil, nil, nil, nil, nil, + -2, 302, nil, nil, nil, 114, 289, nil, 106, nil, + nil, 4887, 346, 404, 462, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, - nil, nil, nil, nil, nil, nil, nil, nil, 254, 191, - 229, 652, 706, 760, 814, 147, 203, nil, 48, 407, - nil, 1086, 1140, 1196, nil, nil, nil, nil, 1250, nil, - 156, 160, nil, 1416, nil, nil, nil, nil, nil, nil, - nil, 225, 1526, 212, 1634, 1688, 1742, 1796, 1850, 1904, - 1958, 2012, 2066, 2120, 2174, 2228, 2282, 2336, 2390, 2444, - 2498, 2552, 2606, 2660, 2714, 2768, 2822, 2876, 2930, 2984, - 3038, 3092, 165, 3202, 178, 3310, 3364, 602, 79, 112, - 4923, 3636, nil, 121, 30, 3798, 4869, nil, 4810, 4751, - 4638, 72, 4122, 10, nil, nil, nil, nil, 82, 27, - nil, 170, nil, nil, nil, nil, 4466, 71, nil, 61, - nil, 4579, 57, nil, nil, 4692, nil, 164, nil, 159, - 3852, -15, 4977, 1362, nil, 125, nil, nil, nil, nil, - nil, 74, 8, 2, 440, 5320, 5365, 494, 174, 548, - 5551, 5596, 5615, 5660, 5685, 5705, 5730, 5775, 5820, 5865, - 5919, 6081, 6027, 5973, nil, nil, 274, nil, 5473, 598, - 868, 922, 208, 232, nil, nil, -8, nil, -2, -9, - 55, -23, 353, -1, -4, nil, nil, nil, nil, nil, - nil, 6163, 5193, 192, nil, 195, nil, 189, 94, nil, - 1032, nil, 142, nil, 125, -7, nil, 1306, 3582, 3906, - 4394, 4014, 80, 197, nil, -14, 249, 93, 299, nil, - 102, 251, 4176, nil, 3960, nil, 4340, nil, 4286, nil, - nil, nil, nil, 4230, nil, nil, nil, 205, nil, nil, - nil, nil, 4525, 68, nil, 4068, 3744, 101, nil, 6141, - nil, 116, 3690, 126, 6119, 3526, 3472, 147, nil, nil, - nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, - 3418, 148, nil, 166, nil, 108, 144, 3256, nil, 179, - 92, 182, 161, 4, 3148, nil, 169, 199, 173, 206, - 19, nil, -25, nil, 209, 1580, 1472, nil, nil, nil, - 5031, nil, nil, 5085, nil, nil, nil, 162, -21, 5139, - 234, 976, 234, nil, nil, nil, nil, 5247, 5301, 241, - 182, nil, nil, nil, 5419, 88, nil, 544, 258, 235, - nil, 262, 263, nil, nil, nil, 264, 265, 269, nil, - 490, nil, nil, nil, 255, 279, nil, nil, 281, nil, - nil, nil, nil, nil, nil, nil, nil, nil, nil, 220, - nil, 164, 54, nil, nil, nil, 289, nil, nil, nil, - 291, nil, 294, nil, 297, nil, nil, nil, nil, nil ] + nil, nil, nil, nil, nil, nil, nil, nil, nil, 246, + 180, 205, 694, 752, 810, 868, 20, 184, nil, -1, + 14, 73, nil, 1216, 1274, 1332, nil, nil, nil, nil, + 1390, nil, 92, 95, nil, 1564, nil, nil, nil, nil, + nil, nil, nil, 146, 4754, 100, 1796, 1854, 1912, 1970, + 2028, 2086, 2144, 2202, 2260, 2318, 2376, 2434, 2492, 2550, + 2608, 2666, 2724, 2782, 2840, 2898, 2956, 3014, 3072, 3130, + 3188, 3246, 3304, 3362, 62, 3478, 3, 3594, 3652, 350, + 87, 234, 5503, 3942, nil, 217, -22, 4116, 5564, nil, + 5625, 5686, 5747, 70, 4464, 263, nil, nil, nil, nil, + 275, -7, nil, 70, nil, nil, nil, nil, 209, 4826, + 20, nil, 7, nil, 4995, 73, nil, nil, 5103, nil, + 65, nil, 158, 1680, -15, 5373, 4696, nil, 119, nil, + nil, nil, nil, nil, 61, 292, 176, 118, 5764, 5767, + 6422, 6419, 6449, 5816, 5964, 6007, 6024, 6078, 6095, 6149, + 6166, 6220, 6240, 6294, 6348, 5893, 5947, nil, nil, 4638, + nil, 6402, 4580, 4522, 4406, 226, 252, nil, nil, -2, + nil, -9, -8, 44, 67, 4773, -1, -4, nil, nil, + nil, nil, nil, nil, 6482, 5839, 212, nil, 229, nil, + 231, 172, nil, 4348, nil, 219, nil, 214, -4, nil, + 4290, 4232, 4174, 4058, 4000, 165, 167, nil, -11, 186, + 184, 4823, nil, 23, 136, 143, 3884, nil, 3826, nil, + 3768, nil, 3710, nil, nil, nil, nil, 3536, nil, nil, + nil, 6, nil, nil, nil, nil, 5449, 84, nil, 3420, + 1738, 103, nil, 6504, nil, 126, 1622, 139, 6526, 1506, + 1448, 156, nil, nil, nil, nil, nil, nil, nil, nil, + nil, nil, nil, nil, 1158, 153, nil, 175, nil, 115, + 154, 1100, nil, 184, 94, 192, 170, 0, 1042, nil, + 168, 201, 177, 210, 215, nil, 78, nil, 214, 984, + 926, nil, nil, nil, 636, 5319, nil, nil, 5265, nil, + nil, nil, 167, 64, 5211, 238, 578, 239, nil, nil, + nil, nil, 5157, 5049, 251, 192, nil, nil, nil, 4941, + 102, nil, 520, 268, 245, nil, 273, 275, nil, nil, + nil, 275, 277, 278, nil, 288, nil, nil, nil, 263, + 283, nil, nil, 286, nil, 288, nil, nil, nil, nil, + nil, nil, nil, nil, nil, 230, nil, 172, 56, nil, + nil, nil, 296, nil, nil, nil, nil, 299, nil, 300, + nil, 301, nil, nil, nil, nil, nil ] racc_action_default = [ - -227, -228, -1, -2, -3, -4, -5, -8, -10, -11, - -16, -107, -228, -228, -228, -45, -46, -47, -48, -49, + -229, -230, -1, -2, -3, -4, -5, -8, -10, -11, + -16, -108, -230, -230, -230, -45, -46, -47, -48, -49, -50, -51, -52, -53, -54, -55, -56, -57, -58, -59, - -60, -61, -62, -63, -64, -65, -66, -67, -72, -73, - -77, -228, -228, -228, -228, -228, -118, -120, -228, -228, - -165, -228, -228, -228, -178, -179, -180, -181, -228, -183, - -228, -194, -197, -228, -202, -203, -204, -205, -206, -207, - -208, -228, -228, -7, -228, -228, -228, -228, -228, -228, - -228, -228, -228, -228, -228, -228, -228, -228, -228, -228, - -228, -228, -228, -228, -228, -228, -228, -228, -228, -228, - -228, -228, -228, -127, -122, -227, -227, -28, -228, -35, - -228, -228, -74, -228, -228, -228, -228, -84, -228, -228, - -228, -228, -228, -227, -137, -156, -157, -119, -227, -227, - -146, -148, -149, -150, -151, -152, -43, -228, -168, -228, - -171, -228, -228, -174, -175, -187, -182, -228, -190, -228, - -228, -228, -228, -228, 400, -6, -9, -12, -13, -14, - -15, -228, -18, -19, -20, -21, -22, -23, -24, -25, - -26, -27, -29, -30, -31, -32, -33, -34, -36, -37, - -38, -39, -40, -228, -41, -102, -228, -78, -228, -220, - -226, -214, -211, -209, -116, -128, -203, -131, -207, -228, - -217, -215, -223, -205, -206, -213, -218, -219, -221, -222, - -224, -127, -126, -228, -125, -228, -42, -209, -69, -79, - -228, -82, -209, -161, -164, -228, -76, -228, -228, -228, - -127, -228, -211, -227, -158, -228, -228, -228, -228, -154, - -228, -228, -228, -166, -228, -169, -228, -172, -228, -184, - -185, -186, -188, -228, -191, -192, -193, -209, -195, -198, - -200, -201, -107, -228, -17, -228, -228, -209, -104, -127, - -115, -228, -212, -228, -210, -228, -228, -209, -130, -132, - -214, -215, -216, -217, -220, -223, -225, -226, -123, -124, - -210, -228, -71, -228, -81, -228, -210, -228, -75, -228, - -87, -228, -93, -228, -228, -97, -211, -209, -211, -228, - -228, -140, -228, -159, -209, -227, -228, -147, -155, -153, - -44, -167, -170, -177, -173, -176, -189, -228, -228, -106, - -228, -210, -209, -110, -117, -111, -129, -133, -134, -228, - -68, -80, -83, -162, -163, -87, -86, -228, -228, -93, - -92, -228, -228, -101, -96, -98, -228, -228, -228, -113, - -227, -141, -142, -143, -228, -228, -138, -139, -228, -145, - -196, -199, -103, -105, -114, -121, -70, -85, -88, -228, - -91, -228, -228, -108, -109, -112, -228, -160, -135, -144, - -228, -90, -228, -95, -228, -100, -136, -89, -94, -99 ] + -60, -61, -62, -63, -64, -65, -66, -67, -68, -73, + -74, -78, -230, -230, -230, -230, -230, -119, -121, -230, + -230, -230, -167, -230, -230, -230, -180, -181, -182, -183, + -230, -185, -230, -196, -199, -230, -204, -205, -206, -207, + -208, -209, -210, -230, -230, -7, -230, -230, -230, -230, + -230, -230, -230, -230, -230, -230, -230, -230, -230, -230, + -230, -230, -230, -230, -230, -230, -230, -230, -230, -230, + -230, -230, -230, -230, -230, -128, -123, -229, -229, -28, + -230, -35, -230, -230, -75, -230, -230, -230, -230, -85, + -230, -230, -230, -230, -230, -229, -138, -158, -159, -120, + -229, -229, -147, -149, -150, -151, -152, -153, -229, -43, + -230, -170, -230, -173, -230, -230, -176, -177, -189, -184, + -230, -192, -230, -230, -230, -230, -230, 407, -6, -9, + -12, -13, -14, -15, -230, -18, -19, -20, -21, -22, + -23, -24, -25, -26, -27, -29, -30, -31, -32, -33, + -34, -36, -37, -38, -39, -40, -230, -41, -103, -230, + -79, -230, -222, -228, -216, -213, -211, -117, -129, -205, + -132, -209, -230, -219, -217, -225, -207, -208, -215, -220, + -221, -223, -224, -226, -128, -127, -230, -126, -230, -42, + -211, -70, -80, -230, -83, -211, -163, -166, -230, -77, + -230, -230, -230, -128, -230, -213, -229, -160, -230, -230, + -230, -230, -155, -230, -230, -230, -230, -168, -230, -171, + -230, -174, -230, -186, -187, -188, -190, -230, -193, -194, + -195, -211, -197, -200, -202, -203, -108, -230, -17, -230, + -230, -211, -105, -128, -116, -230, -214, -230, -212, -230, + -230, -211, -131, -133, -216, -217, -218, -219, -222, -225, + -227, -228, -124, -125, -212, -230, -72, -230, -82, -230, + -212, -230, -76, -230, -88, -230, -94, -230, -230, -98, + -213, -211, -213, -230, -230, -141, -230, -161, -211, -229, + -230, -148, -156, -154, -229, -44, -169, -172, -179, -175, + -178, -191, -230, -230, -107, -230, -212, -211, -111, -118, + -112, -130, -134, -135, -230, -69, -81, -84, -164, -165, + -88, -87, -230, -230, -94, -93, -230, -230, -102, -97, + -99, -230, -230, -230, -114, -229, -142, -143, -144, -230, + -230, -139, -140, -230, -146, -230, -198, -201, -104, -106, + -115, -122, -71, -86, -89, -230, -92, -230, -230, -109, + -110, -113, -230, -162, -136, -145, -157, -230, -91, -230, + -96, -230, -101, -137, -90, -95, -100 ] racc_goto_table = [ - 2, 112, 4, 146, 107, 109, 110, 128, 134, 185, - 259, 132, 222, 193, 365, 350, 184, 233, 346, 192, - 156, 271, 236, 334, 305, 157, 158, 159, 160, 73, - 317, 269, 318, 116, 118, 119, 120, 352, 232, 336, - 137, 139, 267, 136, 136, 141, 213, 215, 304, 257, - 145, 378, 310, 361, 237, 152, 219, 343, 325, 386, - 254, 309, 380, 377, 255, 3, 252, 253, 161, 251, - 148, 136, 162, 163, 164, 165, 166, 167, 168, 169, - 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, - 180, 181, 182, 183, 268, 188, 155, 212, 212, 355, - 217, 150, 1, 136, 225, nil, nil, 136, nil, nil, - 273, nil, nil, nil, 188, nil, nil, nil, nil, nil, - nil, 277, nil, nil, nil, 234, nil, nil, nil, nil, - 234, 239, nil, 314, 291, 356, nil, 358, nil, 295, - 307, nil, nil, nil, nil, 262, 306, 308, nil, nil, - 256, nil, nil, 263, nil, nil, nil, nil, nil, 128, - nil, 134, nil, nil, 132, nil, nil, nil, nil, nil, - nil, nil, nil, nil, 327, nil, nil, nil, 183, 332, - 292, 116, 118, 119, 330, nil, 371, nil, nil, nil, - nil, nil, nil, nil, 339, nil, nil, 134, 326, 134, - 132, nil, 132, nil, nil, nil, nil, nil, nil, nil, + 2, 114, 4, 149, 136, 134, 109, 111, 112, 130, + 196, 138, 188, 263, 195, 225, 370, 275, 355, 351, + 187, 375, 236, 321, 309, 322, 159, 239, 160, 161, + 162, 163, 339, 235, 75, 245, 118, 120, 121, 122, + 277, 216, 218, 273, 140, 142, 357, 139, 139, 144, + 341, 271, 308, 261, 148, 384, 314, 313, 366, 155, + 240, 222, 392, 348, 295, 383, 386, 330, 258, 299, + 259, 3, 164, 256, 257, 139, 165, 166, 167, 168, + 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, + 179, 180, 181, 182, 183, 184, 185, 186, 272, 191, + 360, 215, 215, 158, 220, 332, 255, 139, 228, 217, + 217, 139, 151, 153, 1, 335, nil, nil, 191, 281, + nil, nil, nil, nil, nil, 344, nil, nil, nil, nil, + nil, nil, 361, 242, 363, nil, nil, 318, 311, nil, + nil, nil, 310, 312, nil, nil, nil, nil, nil, nil, + 266, nil, nil, 260, nil, 362, 267, nil, nil, 136, + 134, nil, 369, 130, nil, nil, nil, nil, nil, nil, + nil, nil, nil, nil, nil, nil, nil, nil, 337, nil, + nil, 380, 296, 186, nil, nil, 118, 120, 121, nil, + nil, 377, nil, nil, nil, 136, 134, 136, 134, nil, + 331, nil, nil, nil, nil, nil, nil, nil, nil, nil, + nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, + nil, nil, nil, 297, nil, nil, 139, 191, 191, nil, + 303, 305, nil, nil, nil, nil, nil, nil, 315, nil, + 325, nil, 325, nil, 328, 379, 144, nil, nil, nil, + nil, 148, nil, nil, nil, nil, nil, nil, nil, nil, + nil, nil, nil, 325, 334, nil, nil, nil, nil, nil, + 191, nil, nil, 342, 343, nil, 367, nil, nil, nil, + nil, nil, nil, nil, nil, nil, nil, nil, 325, nil, + nil, nil, nil, nil, nil, 349, nil, nil, nil, nil, + nil, nil, 139, nil, nil, nil, 382, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, - 293, 136, 188, 188, 357, nil, nil, 299, 301, nil, - nil, 364, nil, nil, 320, 311, 320, nil, 323, 373, - 141, nil, nil, nil, nil, 145, nil, nil, nil, 374, - nil, nil, nil, nil, nil, nil, nil, 320, 329, nil, - nil, nil, nil, nil, 188, nil, nil, 337, 338, nil, - nil, 362, nil, nil, nil, nil, nil, nil, nil, nil, - nil, nil, 320, nil, nil, nil, nil, nil, nil, 344, - nil, nil, nil, nil, nil, nil, 136, nil, nil, nil, - nil, nil, 376, nil, nil, nil, nil, nil, nil, nil, - nil, nil, nil, nil, nil, nil, 368, 367, nil, nil, - nil, nil, nil, 183, nil, nil, nil, nil, nil, nil, - nil, nil, nil, nil, nil, nil, nil, nil, nil, 116, + 373, 372, nil, nil, nil, nil, 372, nil, nil, nil, + 186, nil, nil, nil, nil, nil, nil, nil, nil, nil, + nil, nil, nil, nil, nil, nil, 118, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, + nil, nil, nil, nil, nil, nil, nil, 372, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, - nil, nil, 367, nil, nil, nil, nil, nil, nil, nil, - nil, nil, nil, nil, nil, nil, nil, nil, nil, 390, - nil, 392, 394 ] + nil, nil, nil, nil, nil, 397, nil, 399, 401 ] racc_goto_check = [ - 2, 39, 4, 81, 10, 10, 10, 64, 31, 51, - 88, 37, 44, 56, 66, 47, 13, 65, 46, 54, - 7, 55, 65, 57, 49, 8, 8, 8, 8, 6, - 72, 58, 72, 10, 10, 10, 10, 50, 54, 61, - 12, 12, 52, 10, 10, 10, 60, 60, 48, 44, - 10, 45, 68, 69, 71, 10, 43, 74, 76, 66, - 77, 55, 47, 46, 78, 3, 82, 83, 12, 85, - 86, 10, 10, 10, 10, 10, 10, 10, 10, 10, + 2, 40, 4, 82, 32, 38, 10, 10, 10, 65, + 57, 65, 52, 89, 55, 45, 67, 56, 48, 47, + 13, 67, 66, 73, 50, 73, 7, 66, 8, 8, + 8, 8, 58, 55, 6, 66, 10, 10, 10, 10, + 39, 61, 61, 59, 12, 12, 51, 10, 10, 10, + 62, 53, 49, 45, 10, 46, 69, 56, 70, 10, + 72, 44, 67, 75, 39, 47, 48, 77, 78, 39, + 79, 3, 12, 83, 84, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, - 10, 10, 10, 10, 51, 10, 6, 10, 10, 49, - 12, 87, 1, 10, 12, nil, nil, 10, nil, nil, - 38, nil, nil, nil, 10, nil, nil, nil, nil, nil, - nil, 56, nil, nil, nil, 4, nil, nil, nil, nil, - 4, 4, nil, 44, 38, 55, nil, 55, nil, 38, - 56, nil, nil, nil, nil, 10, 54, 54, nil, nil, - 2, nil, nil, 2, nil, nil, nil, nil, nil, 64, - nil, 31, nil, nil, 37, nil, nil, nil, nil, nil, - nil, nil, nil, nil, 38, nil, nil, nil, 10, 56, - 39, 10, 10, 10, 38, nil, 88, nil, nil, nil, - nil, nil, nil, nil, 38, nil, nil, 31, 81, 31, - 37, nil, 37, nil, nil, nil, nil, nil, nil, nil, + 10, 10, 10, 10, 10, 10, 10, 10, 52, 10, + 50, 10, 10, 6, 12, 39, 86, 10, 12, 4, + 4, 10, 87, 88, 1, 39, nil, nil, 10, 57, + nil, nil, nil, nil, nil, 39, nil, nil, nil, nil, + nil, nil, 56, 4, 56, nil, nil, 45, 57, nil, + nil, nil, 55, 55, nil, nil, nil, nil, nil, nil, + 10, nil, nil, 2, nil, 39, 2, nil, nil, 32, + 38, nil, 39, 65, nil, nil, nil, nil, nil, nil, + nil, nil, nil, nil, nil, nil, nil, nil, 57, nil, + nil, 39, 40, 10, nil, nil, 10, 10, 10, nil, + nil, 89, nil, nil, nil, 32, 38, 32, 38, nil, + 82, nil, nil, nil, nil, nil, nil, nil, nil, nil, + nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, + nil, nil, nil, 2, nil, nil, 10, 10, 10, nil, + 2, 2, nil, nil, nil, nil, nil, nil, 4, nil, + 10, nil, 10, nil, 10, 52, 10, nil, nil, nil, + nil, 10, nil, nil, nil, nil, nil, nil, nil, nil, + nil, nil, nil, 10, 10, nil, nil, nil, nil, nil, + 10, nil, nil, 10, 10, nil, 65, nil, nil, nil, + nil, nil, nil, nil, nil, nil, nil, nil, 10, nil, + nil, nil, nil, nil, nil, 10, nil, nil, nil, nil, + nil, nil, 10, nil, nil, nil, 40, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, - 2, 10, 10, 10, 38, nil, nil, 2, 2, nil, - nil, 38, nil, nil, 10, 4, 10, nil, 10, 51, - 10, nil, nil, nil, nil, 10, nil, nil, nil, 38, - nil, nil, nil, nil, nil, nil, nil, 10, 10, nil, - nil, nil, nil, nil, 10, nil, nil, 10, 10, nil, - nil, 64, nil, nil, nil, nil, nil, nil, nil, nil, - nil, nil, 10, nil, nil, nil, nil, nil, nil, 10, + 2, 4, nil, nil, nil, nil, 4, nil, nil, nil, + 10, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 10, nil, nil, nil, - nil, nil, 39, nil, nil, nil, nil, nil, nil, nil, - nil, nil, nil, nil, nil, nil, 2, 4, nil, nil, - nil, nil, nil, 10, nil, nil, nil, nil, nil, nil, - nil, nil, nil, nil, nil, nil, nil, nil, nil, 10, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, + nil, nil, nil, nil, nil, nil, nil, 4, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, - nil, nil, 4, nil, nil, nil, nil, nil, nil, nil, - nil, nil, nil, nil, nil, nil, nil, nil, nil, 2, - nil, 2, 2 ] + nil, nil, nil, nil, nil, 2, nil, 2, 2 ] racc_goto_pointer = [ - nil, 102, 0, 65, 2, nil, 24, -54, -50, nil, - -8, nil, -11, -85, nil, nil, nil, nil, nil, nil, + nil, 114, 0, 71, 2, nil, 29, -50, -49, nil, + -6, nil, -9, -83, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, - nil, -41, nil, nil, nil, nil, nil, -38, -83, -38, - nil, nil, nil, -57, -102, -296, -282, -287, -181, -205, - -266, -92, -144, nil, -84, -171, -90, -249, -157, nil, - -59, -235, nil, nil, -41, -106, -301, nil, -181, -259, - nil, -75, -208, nil, -239, nil, -190, -89, -85, nil, - nil, -55, -81, -80, nil, -78, 10, 40, -142 ] + nil, nil, -46, nil, nil, nil, nil, nil, -45, -156, + -39, nil, nil, nil, -54, -101, -297, -285, -288, -180, + -208, -261, -91, -138, nil, -91, -178, -95, -244, -148, + nil, -66, -228, nil, nil, -40, -103, -303, nil, -180, + -258, nil, -71, -218, nil, -237, nil, -185, -84, -82, + nil, nil, -57, -77, -76, nil, -44, 50, 50, -142 ] racc_goto_default = [ - nil, nil, 366, nil, 214, 5, 6, 7, 8, 9, - 11, 10, 303, nil, 15, 38, 16, 17, 18, 19, + nil, nil, 371, nil, 237, 5, 6, 7, 8, 9, + 11, 10, 307, 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, nil, nil, - 39, 40, 113, nil, nil, 117, nil, nil, nil, nil, - nil, nil, nil, 44, nil, nil, nil, 194, nil, 104, - nil, 195, 199, 197, 124, nil, nil, 123, nil, nil, - 129, nil, 130, 131, 223, 142, 144, 54, 55, 56, - 58, nil, nil, nil, 147, nil, nil, nil, nil ] + 30, 31, 32, 33, 34, 35, 36, 37, 38, nil, + nil, 40, 41, 115, nil, nil, 119, nil, nil, nil, + nil, nil, nil, nil, 45, nil, nil, nil, 197, nil, + 106, nil, 198, 202, 200, 126, nil, nil, 125, nil, + nil, 131, nil, 132, 133, 226, 145, 147, 56, 57, + 58, 60, nil, nil, nil, 150, nil, nil, nil, nil ] racc_reduce_table = [ 0, 0, :racc_error, - 1, 89, :_reduce_1, - 1, 89, :_reduce_2, - 1, 89, :_reduce_none, - 1, 90, :_reduce_4, - 1, 93, :_reduce_5, - 3, 93, :_reduce_6, - 2, 93, :_reduce_7, - 1, 94, :_reduce_8, - 3, 94, :_reduce_9, - 1, 95, :_reduce_none, - 1, 96, :_reduce_11, - 3, 96, :_reduce_12, - 3, 96, :_reduce_13, - 3, 96, :_reduce_14, - 3, 96, :_reduce_15, - 1, 98, :_reduce_none, - 4, 98, :_reduce_17, - 3, 98, :_reduce_18, - 3, 98, :_reduce_19, - 3, 98, :_reduce_20, - 3, 98, :_reduce_21, - 3, 98, :_reduce_22, - 3, 98, :_reduce_23, - 3, 98, :_reduce_24, - 3, 98, :_reduce_25, - 3, 98, :_reduce_26, - 3, 98, :_reduce_27, - 2, 98, :_reduce_28, - 3, 98, :_reduce_29, - 3, 98, :_reduce_30, - 3, 98, :_reduce_31, - 3, 98, :_reduce_32, - 3, 98, :_reduce_33, - 3, 98, :_reduce_34, - 2, 98, :_reduce_35, - 3, 98, :_reduce_36, - 3, 98, :_reduce_37, - 3, 98, :_reduce_38, - 3, 98, :_reduce_39, - 3, 98, :_reduce_40, - 3, 98, :_reduce_41, - 3, 98, :_reduce_42, - 1, 100, :_reduce_43, - 3, 100, :_reduce_44, + 1, 90, :_reduce_1, + 1, 90, :_reduce_2, + 1, 90, :_reduce_none, + 1, 91, :_reduce_4, + 1, 94, :_reduce_5, + 3, 94, :_reduce_6, + 2, 94, :_reduce_7, + 1, 95, :_reduce_8, + 3, 95, :_reduce_9, + 1, 96, :_reduce_none, + 1, 97, :_reduce_11, + 3, 97, :_reduce_12, + 3, 97, :_reduce_13, + 3, 97, :_reduce_14, + 3, 97, :_reduce_15, 1, 99, :_reduce_none, - 1, 103, :_reduce_none, - 1, 103, :_reduce_none, - 1, 103, :_reduce_none, - 1, 103, :_reduce_none, - 1, 103, :_reduce_none, - 1, 103, :_reduce_none, - 1, 103, :_reduce_none, - 1, 103, :_reduce_none, - 1, 103, :_reduce_none, - 1, 103, :_reduce_none, - 1, 103, :_reduce_none, + 4, 99, :_reduce_17, + 3, 99, :_reduce_18, + 3, 99, :_reduce_19, + 3, 99, :_reduce_20, + 3, 99, :_reduce_21, + 3, 99, :_reduce_22, + 3, 99, :_reduce_23, + 3, 99, :_reduce_24, + 3, 99, :_reduce_25, + 3, 99, :_reduce_26, + 3, 99, :_reduce_27, + 2, 99, :_reduce_28, + 3, 99, :_reduce_29, + 3, 99, :_reduce_30, + 3, 99, :_reduce_31, + 3, 99, :_reduce_32, + 3, 99, :_reduce_33, + 3, 99, :_reduce_34, + 2, 99, :_reduce_35, + 3, 99, :_reduce_36, + 3, 99, :_reduce_37, + 3, 99, :_reduce_38, + 3, 99, :_reduce_39, + 3, 99, :_reduce_40, + 3, 99, :_reduce_41, + 3, 99, :_reduce_42, + 1, 101, :_reduce_43, + 3, 101, :_reduce_44, + 1, 100, :_reduce_none, 1, 104, :_reduce_none, 1, 104, :_reduce_none, 1, 104, :_reduce_none, 1, 104, :_reduce_none, 1, 104, :_reduce_none, 1, 104, :_reduce_none, 1, 104, :_reduce_none, 1, 104, :_reduce_none, 1, 104, :_reduce_none, - 1, 120, :_reduce_66, - 1, 120, :_reduce_67, - 5, 102, :_reduce_68, - 3, 102, :_reduce_69, - 6, 102, :_reduce_70, - 4, 102, :_reduce_71, - 1, 102, :_reduce_72, - 1, 106, :_reduce_73, - 2, 106, :_reduce_74, - 4, 128, :_reduce_75, - 3, 128, :_reduce_76, - 1, 128, :_reduce_77, - 3, 129, :_reduce_78, - 2, 127, :_reduce_79, - 3, 131, :_reduce_80, - 2, 131, :_reduce_81, - 2, 130, :_reduce_82, - 4, 130, :_reduce_83, - 2, 109, :_reduce_84, - 5, 133, :_reduce_85, - 4, 133, :_reduce_86, - 0, 134, :_reduce_none, - 2, 134, :_reduce_88, - 4, 134, :_reduce_89, - 3, 134, :_reduce_90, - 6, 110, :_reduce_91, - 5, 110, :_reduce_92, - 0, 135, :_reduce_none, - 4, 135, :_reduce_94, - 3, 135, :_reduce_95, - 5, 108, :_reduce_96, - 1, 136, :_reduce_97, - 2, 136, :_reduce_98, - 5, 137, :_reduce_99, - 4, 137, :_reduce_100, - 1, 138, :_reduce_101, - 1, 101, :_reduce_none, - 4, 101, :_reduce_103, - 1, 140, :_reduce_104, - 3, 140, :_reduce_105, - 3, 139, :_reduce_106, - 1, 97, :_reduce_107, - 6, 97, :_reduce_108, - 6, 97, :_reduce_109, - 5, 97, :_reduce_110, - 5, 97, :_reduce_111, - 6, 97, :_reduce_112, - 5, 97, :_reduce_113, - 4, 145, :_reduce_114, - 1, 146, :_reduce_115, - 1, 142, :_reduce_116, - 3, 142, :_reduce_117, - 1, 141, :_reduce_118, - 2, 141, :_reduce_119, - 1, 141, :_reduce_120, - 6, 107, :_reduce_121, - 2, 107, :_reduce_122, - 3, 147, :_reduce_123, - 3, 147, :_reduce_124, - 1, 148, :_reduce_none, - 1, 148, :_reduce_none, - 0, 144, :_reduce_127, - 1, 144, :_reduce_128, - 3, 144, :_reduce_129, - 1, 150, :_reduce_none, + 1, 104, :_reduce_none, + 1, 104, :_reduce_none, + 1, 104, :_reduce_none, + 1, 105, :_reduce_none, + 1, 105, :_reduce_none, + 1, 105, :_reduce_none, + 1, 105, :_reduce_none, + 1, 105, :_reduce_none, + 1, 105, :_reduce_none, + 1, 105, :_reduce_none, + 1, 105, :_reduce_none, + 1, 105, :_reduce_none, + 1, 122, :_reduce_67, + 1, 122, :_reduce_68, + 5, 103, :_reduce_69, + 3, 103, :_reduce_70, + 6, 103, :_reduce_71, + 4, 103, :_reduce_72, + 1, 103, :_reduce_73, + 1, 107, :_reduce_74, + 2, 107, :_reduce_75, + 4, 130, :_reduce_76, + 3, 130, :_reduce_77, + 1, 130, :_reduce_78, + 3, 131, :_reduce_79, + 2, 129, :_reduce_80, + 3, 133, :_reduce_81, + 2, 133, :_reduce_82, + 2, 132, :_reduce_83, + 4, 132, :_reduce_84, + 2, 110, :_reduce_85, + 5, 135, :_reduce_86, + 4, 135, :_reduce_87, + 0, 136, :_reduce_none, + 2, 136, :_reduce_89, + 4, 136, :_reduce_90, + 3, 136, :_reduce_91, + 6, 111, :_reduce_92, + 5, 111, :_reduce_93, + 0, 137, :_reduce_none, + 4, 137, :_reduce_95, + 3, 137, :_reduce_96, + 5, 109, :_reduce_97, + 1, 138, :_reduce_98, + 2, 138, :_reduce_99, + 5, 139, :_reduce_100, + 4, 139, :_reduce_101, + 1, 140, :_reduce_102, + 1, 102, :_reduce_none, + 4, 102, :_reduce_104, + 1, 142, :_reduce_105, + 3, 142, :_reduce_106, + 3, 141, :_reduce_107, + 1, 98, :_reduce_108, + 6, 98, :_reduce_109, + 6, 98, :_reduce_110, + 5, 98, :_reduce_111, + 5, 98, :_reduce_112, + 6, 98, :_reduce_113, + 5, 98, :_reduce_114, + 4, 147, :_reduce_115, + 1, 148, :_reduce_116, + 1, 144, :_reduce_117, + 3, 144, :_reduce_118, + 1, 143, :_reduce_119, + 2, 143, :_reduce_120, + 1, 143, :_reduce_121, + 6, 108, :_reduce_122, + 2, 108, :_reduce_123, + 3, 149, :_reduce_124, + 3, 149, :_reduce_125, 1, 150, :_reduce_none, 1, 150, :_reduce_none, - 3, 149, :_reduce_133, - 3, 149, :_reduce_134, - 6, 111, :_reduce_135, - 7, 112, :_reduce_136, - 1, 155, :_reduce_137, - 1, 154, :_reduce_none, - 1, 154, :_reduce_none, + 0, 146, :_reduce_128, + 1, 146, :_reduce_129, + 3, 146, :_reduce_130, + 1, 152, :_reduce_none, + 1, 152, :_reduce_none, + 1, 152, :_reduce_none, + 3, 151, :_reduce_134, + 3, 151, :_reduce_135, + 6, 112, :_reduce_136, + 7, 113, :_reduce_137, + 1, 157, :_reduce_138, + 1, 156, :_reduce_none, 1, 156, :_reduce_none, - 2, 156, :_reduce_141, - 1, 157, :_reduce_none, - 1, 157, :_reduce_none, - 6, 113, :_reduce_144, - 5, 113, :_reduce_145, - 1, 158, :_reduce_146, - 3, 158, :_reduce_147, - 1, 160, :_reduce_148, - 1, 160, :_reduce_149, - 1, 160, :_reduce_150, - 1, 160, :_reduce_none, - 1, 161, :_reduce_152, - 3, 161, :_reduce_153, + 1, 158, :_reduce_none, + 2, 158, :_reduce_142, + 1, 159, :_reduce_none, 1, 159, :_reduce_none, - 2, 159, :_reduce_155, - 1, 152, :_reduce_156, - 1, 152, :_reduce_157, - 1, 153, :_reduce_158, - 2, 153, :_reduce_159, - 4, 153, :_reduce_160, - 1, 132, :_reduce_161, - 3, 132, :_reduce_162, - 3, 162, :_reduce_163, - 1, 162, :_reduce_164, - 1, 105, :_reduce_165, - 3, 115, :_reduce_166, - 4, 115, :_reduce_167, - 2, 115, :_reduce_168, - 3, 115, :_reduce_169, - 4, 115, :_reduce_170, - 2, 115, :_reduce_171, - 3, 118, :_reduce_172, - 4, 118, :_reduce_173, - 2, 118, :_reduce_174, - 1, 163, :_reduce_175, - 3, 163, :_reduce_176, - 3, 164, :_reduce_177, - 1, 125, :_reduce_none, - 1, 125, :_reduce_none, - 1, 125, :_reduce_none, - 1, 165, :_reduce_181, - 2, 166, :_reduce_182, - 1, 168, :_reduce_183, - 1, 170, :_reduce_184, - 1, 171, :_reduce_185, - 2, 169, :_reduce_186, - 1, 172, :_reduce_187, - 1, 173, :_reduce_188, - 2, 173, :_reduce_189, - 2, 167, :_reduce_190, - 2, 174, :_reduce_191, - 2, 174, :_reduce_192, - 3, 91, :_reduce_193, - 0, 175, :_reduce_194, - 2, 175, :_reduce_195, - 4, 175, :_reduce_196, - 1, 114, :_reduce_197, - 3, 114, :_reduce_198, - 5, 114, :_reduce_199, - 1, 176, :_reduce_none, - 1, 176, :_reduce_none, - 1, 121, :_reduce_202, - 1, 124, :_reduce_203, - 1, 122, :_reduce_204, - 1, 123, :_reduce_205, - 1, 117, :_reduce_206, - 1, 116, :_reduce_207, + 6, 114, :_reduce_145, + 5, 114, :_reduce_146, + 1, 160, :_reduce_147, + 3, 160, :_reduce_148, + 1, 162, :_reduce_149, + 1, 162, :_reduce_150, + 1, 162, :_reduce_151, + 1, 162, :_reduce_none, + 1, 163, :_reduce_153, + 3, 163, :_reduce_154, + 1, 161, :_reduce_none, + 2, 161, :_reduce_156, + 6, 116, :_reduce_157, + 1, 154, :_reduce_158, + 1, 154, :_reduce_159, + 1, 155, :_reduce_160, + 2, 155, :_reduce_161, + 4, 155, :_reduce_162, + 1, 134, :_reduce_163, + 3, 134, :_reduce_164, + 3, 164, :_reduce_165, + 1, 164, :_reduce_166, + 1, 106, :_reduce_167, + 3, 117, :_reduce_168, + 4, 117, :_reduce_169, + 2, 117, :_reduce_170, + 3, 117, :_reduce_171, + 4, 117, :_reduce_172, + 2, 117, :_reduce_173, + 3, 120, :_reduce_174, + 4, 120, :_reduce_175, + 2, 120, :_reduce_176, + 1, 165, :_reduce_177, + 3, 165, :_reduce_178, + 3, 166, :_reduce_179, + 1, 127, :_reduce_none, + 1, 127, :_reduce_none, + 1, 127, :_reduce_none, + 1, 167, :_reduce_183, + 2, 168, :_reduce_184, + 1, 170, :_reduce_185, + 1, 172, :_reduce_186, + 1, 173, :_reduce_187, + 2, 171, :_reduce_188, + 1, 174, :_reduce_189, + 1, 175, :_reduce_190, + 2, 175, :_reduce_191, + 2, 169, :_reduce_192, + 2, 176, :_reduce_193, + 2, 176, :_reduce_194, + 3, 92, :_reduce_195, + 0, 177, :_reduce_196, + 2, 177, :_reduce_197, + 4, 177, :_reduce_198, + 1, 115, :_reduce_199, + 3, 115, :_reduce_200, + 5, 115, :_reduce_201, + 1, 178, :_reduce_none, + 1, 178, :_reduce_none, + 1, 123, :_reduce_204, + 1, 126, :_reduce_205, + 1, 124, :_reduce_206, + 1, 125, :_reduce_207, 1, 119, :_reduce_208, - 0, 126, :_reduce_none, - 1, 126, :_reduce_210, - 0, 143, :_reduce_none, - 1, 143, :_reduce_none, - 1, 151, :_reduce_none, - 1, 151, :_reduce_none, - 1, 151, :_reduce_none, - 1, 151, :_reduce_none, - 1, 151, :_reduce_none, - 1, 151, :_reduce_none, - 1, 151, :_reduce_none, - 1, 151, :_reduce_none, - 1, 151, :_reduce_none, - 1, 151, :_reduce_none, - 1, 151, :_reduce_none, - 1, 151, :_reduce_none, - 1, 151, :_reduce_none, - 1, 151, :_reduce_none, - 0, 92, :_reduce_227 ] - -racc_reduce_n = 228 - -racc_shift_n = 400 + 1, 118, :_reduce_209, + 1, 121, :_reduce_210, + 0, 128, :_reduce_none, + 1, 128, :_reduce_212, + 0, 145, :_reduce_none, + 1, 145, :_reduce_none, + 1, 153, :_reduce_none, + 1, 153, :_reduce_none, + 1, 153, :_reduce_none, + 1, 153, :_reduce_none, + 1, 153, :_reduce_none, + 1, 153, :_reduce_none, + 1, 153, :_reduce_none, + 1, 153, :_reduce_none, + 1, 153, :_reduce_none, + 1, 153, :_reduce_none, + 1, 153, :_reduce_none, + 1, 153, :_reduce_none, + 1, 153, :_reduce_none, + 1, 153, :_reduce_none, + 0, 93, :_reduce_229 ] + +racc_reduce_n = 230 + +racc_shift_n = 407 racc_token_table = { false => 0, :error => 1, :STRING => 2, :DQPRE => 3, :DQMID => 4, :DQPOST => 5, :LBRACK => 6, :RBRACK => 7, :LBRACE => 8, :RBRACE => 9, :SYMBOL => 10, :FARROW => 11, :COMMA => 12, :TRUE => 13, :FALSE => 14, :EQUALS => 15, :APPENDS => 16, :DELETES => 17, :LESSEQUAL => 18, :NOTEQUAL => 19, :DOT => 20, :COLON => 21, :LLCOLLECT => 22, :RRCOLLECT => 23, :QMARK => 24, :LPAREN => 25, :RPAREN => 26, :ISEQUAL => 27, :GREATEREQUAL => 28, :GREATERTHAN => 29, :LESSTHAN => 30, :IF => 31, :ELSE => 32, :DEFINE => 33, :ELSIF => 34, :VARIABLE => 35, :CLASS => 36, :INHERITS => 37, :NODE => 38, :BOOLEAN => 39, :NAME => 40, :SEMIC => 41, :CASE => 42, :DEFAULT => 43, :AT => 44, :ATAT => 45, :LCOLLECT => 46, :RCOLLECT => 47, :CLASSREF => 48, :NOT => 49, :OR => 50, :AND => 51, :UNDEF => 52, :PARROW => 53, :PLUS => 54, :MINUS => 55, :TIMES => 56, :DIV => 57, :LSHIFT => 58, :RSHIFT => 59, :UMINUS => 60, :MATCH => 61, :NOMATCH => 62, :REGEX => 63, :IN_EDGE => 64, :OUT_EDGE => 65, :IN_EDGE_SUB => 66, :OUT_EDGE_SUB => 67, :IN => 68, :UNLESS => 69, :PIPE => 70, :LAMBDA => 71, :SELBRACE => 72, :NUMBER => 73, :HEREDOC => 74, :SUBLOCATE => 75, :RENDER_STRING => 76, :RENDER_EXPR => 77, :EPP_START => 78, :EPP_END => 79, :EPP_END_TRIM => 80, - :LOW => 81, - :HIGH => 82, - :CALL => 83, - :LISTSTART => 84, - :MODULO => 85, - :TITLE_COLON => 86, - :CASE_COLON => 87 } + :FUNCTION => 81, + :LOW => 82, + :HIGH => 83, + :CALL => 84, + :LISTSTART => 85, + :MODULO => 86, + :TITLE_COLON => 87, + :CASE_COLON => 88 } -racc_nt_base = 88 +racc_nt_base = 89 racc_use_result_var = true Racc_arg = [ racc_action_table, racc_action_check, racc_action_default, racc_action_pointer, racc_goto_table, racc_goto_check, racc_goto_default, racc_goto_pointer, racc_nt_base, racc_reduce_table, racc_token_table, racc_shift_n, racc_reduce_n, racc_use_result_var ] Racc_token_to_s_table = [ "$end", "error", "STRING", "DQPRE", "DQMID", "DQPOST", "LBRACK", "RBRACK", "LBRACE", "RBRACE", "SYMBOL", "FARROW", "COMMA", "TRUE", "FALSE", "EQUALS", "APPENDS", "DELETES", "LESSEQUAL", "NOTEQUAL", "DOT", "COLON", "LLCOLLECT", "RRCOLLECT", "QMARK", "LPAREN", "RPAREN", "ISEQUAL", "GREATEREQUAL", "GREATERTHAN", "LESSTHAN", "IF", "ELSE", "DEFINE", "ELSIF", "VARIABLE", "CLASS", "INHERITS", "NODE", "BOOLEAN", "NAME", "SEMIC", "CASE", "DEFAULT", "AT", "ATAT", "LCOLLECT", "RCOLLECT", "CLASSREF", "NOT", "OR", "AND", "UNDEF", "PARROW", "PLUS", "MINUS", "TIMES", "DIV", "LSHIFT", "RSHIFT", "UMINUS", "MATCH", "NOMATCH", "REGEX", "IN_EDGE", "OUT_EDGE", "IN_EDGE_SUB", "OUT_EDGE_SUB", "IN", "UNLESS", "PIPE", "LAMBDA", "SELBRACE", "NUMBER", "HEREDOC", "SUBLOCATE", "RENDER_STRING", "RENDER_EXPR", "EPP_START", "EPP_END", "EPP_END_TRIM", + "FUNCTION", "LOW", "HIGH", "CALL", "LISTSTART", "MODULO", "TITLE_COLON", "CASE_COLON", "$start", "program", "statements", "epp_expression", "nil", "syntactic_statements", "syntactic_statement", "any_expression", "relationship_expression", "resource_expression", "expression", "higher_precedence", "expressions", "selector_entries", "call_function_expression", "primary_expression", "literal_expression", "variable", "call_method_with_lambda_expression", "collection_expression", "case_expression", "if_expression", "unless_expression", "definition_expression", "hostclass_expression", "node_definition_expression", "epp_render_expression", + "function_definition", "array", "boolean", "default", "hash", "regex", "text_or_name", "number", "type", "undef", "name", "quotedtext", "endcomma", "lambda", "call_method_expression", "named_access", "lambda_parameter_list", "lambda_rest", "parameters", "if_part", "else", "unless_else", "case_options", "case_option", "case_colon", "selector_entry", "selector_entry_list", "at", "resourceinstances", "endsemi", "attribute_operations", "resourceinst", "title_colon", "collect_query", "optional_query", "attribute_operation", "attribute_name", "keyword", "classname", "parameter_list", "opt_statements", "stacked_classname", "classparent", "classnameordefault", "hostnames", "nodeparent", "hostname", "dotted_name", "parameter", "hashpairs", "hashpair", "string", "dq_string", "heredoc", "dqpre", "dqrval", "dqpost", "dqmid", "text_expression", "dqtail", "sublocated_text", "epp_parameters_list", "epp_end" ] Racc_debug_parser = false ##### State transition tables end ##### # reduce 0 omitted -module_eval(<<'.,.,', 'egrammar.ra', 64) +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', 65) +module_eval(<<'.,.,', 'egrammar.ra', 66) def _reduce_2(val, _values, result) result = create_program(Factory.block_or_expression(*val[0])) result end .,., # reduce 3 omitted -module_eval(<<'.,.,', 'egrammar.ra', 70) +module_eval(<<'.,.,', 'egrammar.ra', 71) def _reduce_4(val, _values, result) result = transform_calls(val[0]) result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 76) +module_eval(<<'.,.,', 'egrammar.ra', 77) def _reduce_5(val, _values, result) result = [val[0]] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 77) +module_eval(<<'.,.,', 'egrammar.ra', 78) def _reduce_6(val, _values, result) result = val[0].push val[2] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 78) +module_eval(<<'.,.,', 'egrammar.ra', 79) def _reduce_7(val, _values, result) result = val[0].push val[1] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 82) +module_eval(<<'.,.,', 'egrammar.ra', 83) def _reduce_8(val, _values, result) result = val[0] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 83) +module_eval(<<'.,.,', 'egrammar.ra', 84) def _reduce_9(val, _values, result) result = aryfy(val[0]).push val[2] result end .,., # reduce 10 omitted -module_eval(<<'.,.,', 'egrammar.ra', 89) +module_eval(<<'.,.,', 'egrammar.ra', 90) def _reduce_11(val, _values, result) result = val[0] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 90) +module_eval(<<'.,.,', 'egrammar.ra', 91) def _reduce_12(val, _values, result) result = val[0].relop(val[1][:value], val[2]); loc result, val[1] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 91) +module_eval(<<'.,.,', 'egrammar.ra', 92) def _reduce_13(val, _values, result) result = val[0].relop(val[1][:value], val[2]); loc result, val[1] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 92) +module_eval(<<'.,.,', 'egrammar.ra', 93) def _reduce_14(val, _values, result) result = val[0].relop(val[1][:value], val[2]); loc result, val[1] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 93) +module_eval(<<'.,.,', 'egrammar.ra', 94) def _reduce_15(val, _values, result) result = val[0].relop(val[1][:value], val[2]); loc result, val[1] result end .,., # reduce 16 omitted -module_eval(<<'.,.,', 'egrammar.ra', 100) +module_eval(<<'.,.,', 'egrammar.ra', 101) def _reduce_17(val, _values, result) result = val[0][*val[2]] ; loc result, val[0], val[3] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 101) +module_eval(<<'.,.,', 'egrammar.ra', 102) def _reduce_18(val, _values, result) result = val[0].in val[2] ; loc result, val[1] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 102) +module_eval(<<'.,.,', 'egrammar.ra', 103) def _reduce_19(val, _values, result) result = val[0] =~ val[2] ; loc result, val[1] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 103) +module_eval(<<'.,.,', 'egrammar.ra', 104) def _reduce_20(val, _values, result) result = val[0].mne val[2] ; loc result, val[1] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 104) +module_eval(<<'.,.,', 'egrammar.ra', 105) def _reduce_21(val, _values, result) result = val[0] + val[2] ; loc result, val[1] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 105) +module_eval(<<'.,.,', 'egrammar.ra', 106) def _reduce_22(val, _values, result) result = val[0] - val[2] ; loc result, val[1] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 106) +module_eval(<<'.,.,', 'egrammar.ra', 107) def _reduce_23(val, _values, result) result = val[0] / val[2] ; loc result, val[1] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 107) +module_eval(<<'.,.,', 'egrammar.ra', 108) def _reduce_24(val, _values, result) result = val[0] * val[2] ; loc result, val[1] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 108) +module_eval(<<'.,.,', 'egrammar.ra', 109) def _reduce_25(val, _values, result) result = val[0] % val[2] ; loc result, val[1] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 109) +module_eval(<<'.,.,', 'egrammar.ra', 110) def _reduce_26(val, _values, result) result = val[0] << val[2] ; loc result, val[1] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 110) +module_eval(<<'.,.,', 'egrammar.ra', 111) def _reduce_27(val, _values, result) result = val[0] >> val[2] ; loc result, val[1] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 111) +module_eval(<<'.,.,', 'egrammar.ra', 112) def _reduce_28(val, _values, result) result = val[1].minus() ; loc result, val[0] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 112) +module_eval(<<'.,.,', 'egrammar.ra', 113) def _reduce_29(val, _values, result) result = val[0].ne val[2] ; loc result, val[1] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 113) +module_eval(<<'.,.,', 'egrammar.ra', 114) def _reduce_30(val, _values, result) result = val[0] == val[2] ; loc result, val[1] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 114) +module_eval(<<'.,.,', 'egrammar.ra', 115) def _reduce_31(val, _values, result) result = val[0] > val[2] ; loc result, val[1] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 115) +module_eval(<<'.,.,', 'egrammar.ra', 116) def _reduce_32(val, _values, result) result = val[0] >= val[2] ; loc result, val[1] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 116) +module_eval(<<'.,.,', 'egrammar.ra', 117) def _reduce_33(val, _values, result) result = val[0] < val[2] ; loc result, val[1] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 117) +module_eval(<<'.,.,', 'egrammar.ra', 118) def _reduce_34(val, _values, result) result = val[0] <= val[2] ; loc result, val[1] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 118) +module_eval(<<'.,.,', 'egrammar.ra', 119) def _reduce_35(val, _values, result) result = val[1].not ; loc result, val[0] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 119) +module_eval(<<'.,.,', 'egrammar.ra', 120) def _reduce_36(val, _values, result) result = val[0].and val[2] ; loc result, val[1] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 120) +module_eval(<<'.,.,', 'egrammar.ra', 121) def _reduce_37(val, _values, result) result = val[0].or val[2] ; loc result, val[1] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 121) +module_eval(<<'.,.,', 'egrammar.ra', 122) def _reduce_38(val, _values, result) result = val[0].set(val[2]) ; loc result, val[1] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 122) +module_eval(<<'.,.,', 'egrammar.ra', 123) def _reduce_39(val, _values, result) result = val[0].plus_set(val[2]) ; loc result, val[1] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 123) +module_eval(<<'.,.,', 'egrammar.ra', 124) def _reduce_40(val, _values, result) result = val[0].minus_set(val[2]); loc result, val[1] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 124) +module_eval(<<'.,.,', 'egrammar.ra', 125) def _reduce_41(val, _values, result) result = val[0].select(*val[2]) ; loc result, val[0] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 125) +module_eval(<<'.,.,', 'egrammar.ra', 126) def _reduce_42(val, _values, result) result = val[1].paren() ; loc result, val[0] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 133) +module_eval(<<'.,.,', 'egrammar.ra', 134) def _reduce_43(val, _values, result) result = [val[0]] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 134) +module_eval(<<'.,.,', 'egrammar.ra', 135) def _reduce_44(val, _values, result) result = val[0].push(val[2]) result end .,., # reduce 45 omitted # reduce 46 omitted # reduce 47 omitted # reduce 48 omitted # reduce 49 omitted # reduce 50 omitted # reduce 51 omitted # reduce 52 omitted # reduce 53 omitted # reduce 54 omitted # reduce 55 omitted # reduce 56 omitted # reduce 57 omitted # reduce 58 omitted # reduce 59 omitted # reduce 60 omitted # reduce 61 omitted # reduce 62 omitted # reduce 63 omitted # reduce 64 omitted # reduce 65 omitted -module_eval(<<'.,.,', 'egrammar.ra', 166) - def _reduce_66(val, _values, result) +# reduce 66 omitted + +module_eval(<<'.,.,', 'egrammar.ra', 168) + def _reduce_67(val, _values, result) result = val[0] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 167) - def _reduce_67(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 169) + def _reduce_68(val, _values, result) result = val[0] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 175) - def _reduce_68(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 177) + def _reduce_69(val, _values, result) result = Factory.CALL_NAMED(val[0], true, val[2]) loc result, val[0], val[4] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 179) - def _reduce_69(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 181) + def _reduce_70(val, _values, result) result = Factory.CALL_NAMED(val[0], true, []) loc result, val[0], val[2] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 183) - def _reduce_70(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 185) + def _reduce_71(val, _values, result) result = Factory.CALL_NAMED(val[0], true, val[2]) loc result, val[0], val[4] result.lambda = val[5] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 188) - def _reduce_71(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 190) + def _reduce_72(val, _values, result) result = Factory.CALL_NAMED(val[0], true, []) loc result, val[0], val[2] result.lambda = val[3] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 192) - def _reduce_72(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 194) + def _reduce_73(val, _values, result) result = val[0] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 197) - def _reduce_73(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 199) + def _reduce_74(val, _values, result) result = val[0] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 198) - def _reduce_74(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 200) + def _reduce_75(val, _values, result) result = val[0]; val[0].lambda = val[1] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 201) - def _reduce_75(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 203) + def _reduce_76(val, _values, result) result = Factory.CALL_METHOD(val[0], val[2]); loc result, val[1], val[3] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 202) - def _reduce_76(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 204) + def _reduce_77(val, _values, result) result = Factory.CALL_METHOD(val[0], []); loc result, val[1], val[3] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 203) - def _reduce_77(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 205) + def _reduce_78(val, _values, result) result = Factory.CALL_METHOD(val[0], []); loc result, val[0] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 208) - def _reduce_78(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 210) + def _reduce_79(val, _values, result) result = val[0].dot(Factory.fqn(val[2][:value])) loc result, val[1], val[2] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 220) - def _reduce_79(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 222) + def _reduce_80(val, _values, result) result = Factory.LAMBDA(val[0], val[1]) # loc result, val[1] # TODO result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 225) - def _reduce_80(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 227) + def _reduce_81(val, _values, result) result = val[1] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 226) - def _reduce_81(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 228) + def _reduce_82(val, _values, result) result = nil result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 230) - def _reduce_82(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 232) + def _reduce_83(val, _values, result) result = [] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 231) - def _reduce_83(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 233) + def _reduce_84(val, _values, result) result = val[1] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 241) - def _reduce_84(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 243) + def _reduce_85(val, _values, result) result = val[1] loc(result, val[0], val[1]) result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 248) - def _reduce_85(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 250) + def _reduce_86(val, _values, result) result = Factory.IF(val[0], Factory.block_or_expression(*val[2]), val[4]) loc(result, val[0], (val[4] ? val[4] : val[3])) result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 252) - def _reduce_86(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 254) + def _reduce_87(val, _values, result) result = Factory.IF(val[0], nil, val[3]) loc(result, val[0], (val[3] ? val[3] : val[2])) result end .,., -# reduce 87 omitted +# reduce 88 omitted -module_eval(<<'.,.,', 'egrammar.ra', 260) - def _reduce_88(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 262) + def _reduce_89(val, _values, result) result = val[1] loc(result, val[0], val[1]) result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 264) - def _reduce_89(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 266) + def _reduce_90(val, _values, result) result = Factory.block_or_expression(*val[2]) loc result, val[0], val[3] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 268) - def _reduce_90(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 270) + def _reduce_91(val, _values, result) result = nil # don't think a nop is needed here either result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 277) - def _reduce_91(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 279) + def _reduce_92(val, _values, result) result = Factory.UNLESS(val[1], Factory.block_or_expression(*val[3]), val[5]) loc result, val[0], val[4] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 281) - def _reduce_92(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 283) + def _reduce_93(val, _values, result) result = Factory.UNLESS(val[1], nil, nil) loc result, val[0], val[4] result end .,., -# reduce 93 omitted +# reduce 94 omitted -module_eval(<<'.,.,', 'egrammar.ra', 291) - def _reduce_94(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 293) + def _reduce_95(val, _values, result) result = Factory.block_or_expression(*val[2]) loc result, val[0], val[3] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 295) - def _reduce_95(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 297) + def _reduce_96(val, _values, result) result = nil # don't think a nop is needed here either result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 303) - def _reduce_96(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 305) + def _reduce_97(val, _values, result) result = Factory.CASE(val[1], *val[3]) loc result, val[0], val[4] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 309) - def _reduce_97(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 311) + def _reduce_98(val, _values, result) result = [val[0]] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 310) - def _reduce_98(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 312) + def _reduce_99(val, _values, result) result = val[0].push val[1] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 315) - def _reduce_99(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 317) + def _reduce_100(val, _values, result) result = Factory.WHEN(val[0], val[3]) loc result, val[1], val[4] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 319) - def _reduce_100(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 321) + def _reduce_101(val, _values, result) result = Factory.WHEN(val[0], nil) loc result, val[1], val[3] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 323) - def _reduce_101(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 325) + def _reduce_102(val, _values, result) result = val[0] result end .,., -# reduce 102 omitted +# reduce 103 omitted -module_eval(<<'.,.,', 'egrammar.ra', 334) - def _reduce_103(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 336) + def _reduce_104(val, _values, result) result = val[1] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 339) - def _reduce_104(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 341) + def _reduce_105(val, _values, result) result = [val[0]] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 340) - def _reduce_105(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 342) + def _reduce_106(val, _values, result) result = val[0].push val[2] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 345) - def _reduce_106(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 347) + def _reduce_107(val, _values, result) result = Factory.MAP(val[0], val[2]) ; loc result, val[1] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 357) - def _reduce_107(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 359) + def _reduce_108(val, _values, result) result = val[0] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 360) - def _reduce_108(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 362) + def _reduce_109(val, _values, result) result = case Factory.resource_shape(val[1]) when :resource, :class tmp = Factory.RESOURCE(Factory.fqn(token_text(val[1])), val[3]) tmp.form = val[0] tmp when :defaults error val[1], "A resource default can not be virtual or exported" when :override error val[1], "A resource override can not be virtual or exported" else error val[1], "Expression is not valid as a resource, resource-default, or resource-override" end loc result, val[1], val[4] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 375) - def _reduce_109(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 377) + def _reduce_110(val, _values, result) result = case Factory.resource_shape(val[1]) when :resource, :class, :defaults, :override error val[1], "Defaults are not virtualizable" else error val[1], "Expression is not valid as a resource, resource-default, or resource-override" end result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 383) - def _reduce_110(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 385) + def _reduce_111(val, _values, result) result = case Factory.resource_shape(val[0]) when :resource, :class Factory.RESOURCE(Factory.fqn(token_text(val[0])), val[2]) when :defaults error val[1], "A resource default can not specify a resource name" when :override error val[1], "A resource override does not allow override of name of resource" else error val[1], "Expression is not valid as a resource, resource-default, or resource-override" end loc result, val[0], val[4] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 396) - def _reduce_111(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 398) + def _reduce_112(val, _values, result) result = case Factory.resource_shape(val[0]) when :resource, :class # This catches deprecated syntax. # If the attribute operations does not include +>, then the found expression # is actually a LEFT followed by LITERAL_HASH # unless tmp = transform_resource_wo_title(val[0], val[2]) error val[1], "Syntax error resource body without title or hash with +>" end tmp when :defaults Factory.RESOURCE_DEFAULTS(val[0], val[2]) when :override # This was only done for override in original - TODO shuld it be here at all Factory.RESOURCE_OVERRIDE(val[0], val[2]) else error val[0], "Expression is not valid as a resource, resource-default, or resource-override" end loc result, val[0], val[4] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 417) - def _reduce_112(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 419) + def _reduce_113(val, _values, result) result = Factory.RESOURCE(Factory.fqn(token_text(val[1])), val[3]) result.form = val[0] loc result, val[1], val[5] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 422) - def _reduce_113(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 424) + def _reduce_114(val, _values, result) result = Factory.RESOURCE(Factory.fqn(token_text(val[0])), val[2]) loc result, val[0], val[4] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 427) - def _reduce_114(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 429) + def _reduce_115(val, _values, result) result = Factory.RESOURCE_BODY(val[0], val[2]) result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 429) - def _reduce_115(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 431) + def _reduce_116(val, _values, result) result = val[0] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 432) - def _reduce_116(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 434) + def _reduce_117(val, _values, result) result = [val[0]] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 433) - def _reduce_117(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 435) + def _reduce_118(val, _values, result) result = val[0].push val[2] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 438) - def _reduce_118(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 440) + def _reduce_119(val, _values, result) result = :virtual result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 439) - def _reduce_119(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 441) + def _reduce_120(val, _values, result) result = :exported result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 440) - def _reduce_120(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 442) + def _reduce_121(val, _values, result) result = :exported result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 452) - def _reduce_121(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 454) + def _reduce_122(val, _values, result) result = Factory.COLLECT(val[0], val[1], val[3]) loc result, val[0], val[5] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 456) - def _reduce_122(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 458) + def _reduce_123(val, _values, result) result = Factory.COLLECT(val[0], val[1], []) loc result, val[0], val[1] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 461) - def _reduce_123(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 463) + def _reduce_124(val, _values, result) result = Factory.VIRTUAL_QUERY(val[1]) ; loc result, val[0], val[2] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 462) - def _reduce_124(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 464) + def _reduce_125(val, _values, result) result = Factory.EXPORTED_QUERY(val[1]) ; loc result, val[0], val[2] result end .,., -# reduce 125 omitted - # reduce 126 omitted -module_eval(<<'.,.,', 'egrammar.ra', 475) - def _reduce_127(val, _values, result) +# reduce 127 omitted + +module_eval(<<'.,.,', 'egrammar.ra', 477) + def _reduce_128(val, _values, result) result = [] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 476) - def _reduce_128(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 478) + def _reduce_129(val, _values, result) result = [val[0]] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 477) - def _reduce_129(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 479) + def _reduce_130(val, _values, result) result = val[0].push(val[2]) result end .,., -# reduce 130 omitted - # reduce 131 omitted # reduce 132 omitted -module_eval(<<'.,.,', 'egrammar.ra', 493) - def _reduce_133(val, _values, result) +# reduce 133 omitted + +module_eval(<<'.,.,', 'egrammar.ra', 495) + def _reduce_134(val, _values, result) result = Factory.ATTRIBUTE_OP(val[0][:value], :'=>', val[2]) loc result, val[0], val[2] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 497) - def _reduce_134(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 499) + def _reduce_135(val, _values, result) result = Factory.ATTRIBUTE_OP(val[0][:value], :'+>', val[2]) loc result, val[0], val[2] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 507) - def _reduce_135(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 509) + def _reduce_136(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', 521) - def _reduce_136(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 523) + def _reduce_137(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', 531) - def _reduce_137(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 533) + def _reduce_138(val, _values, result) namestack(val[0][:value]) ; result = val[0] result end .,., -# reduce 138 omitted - # reduce 139 omitted # reduce 140 omitted -module_eval(<<'.,.,', 'egrammar.ra', 540) - def _reduce_141(val, _values, result) +# reduce 141 omitted + +module_eval(<<'.,.,', 'egrammar.ra', 542) + def _reduce_142(val, _values, result) result = val[1] result end .,., -# reduce 142 omitted - # reduce 143 omitted -module_eval(<<'.,.,', 'egrammar.ra', 557) - def _reduce_144(val, _values, result) +# reduce 144 omitted + +module_eval(<<'.,.,', 'egrammar.ra', 559) + def _reduce_145(val, _values, result) result = add_definition(Factory.NODE(val[1], val[2], val[4])) loc result, val[0], val[5] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 561) - def _reduce_145(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 563) + def _reduce_146(val, _values, result) result = add_definition(Factory.NODE(val[1], val[2], nil)) loc result, val[0], val[4] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 571) - def _reduce_146(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 573) + def _reduce_147(val, _values, result) result = [result] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 572) - def _reduce_147(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 574) + def _reduce_148(val, _values, result) result = val[0].push(val[2]) result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 577) - def _reduce_148(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 579) + def _reduce_149(val, _values, result) result = val[0] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 578) - def _reduce_149(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 580) + def _reduce_150(val, _values, result) result = val[0] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 579) - def _reduce_150(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 581) + def _reduce_151(val, _values, result) result = Factory.literal(:default); loc result, val[0] result end .,., -# reduce 151 omitted +# reduce 152 omitted -module_eval(<<'.,.,', 'egrammar.ra', 583) - def _reduce_152(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 585) + def _reduce_153(val, _values, result) result = Factory.literal(val[0][:value]); loc result, val[0] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 584) - def _reduce_153(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 586) + def _reduce_154(val, _values, result) result = Factory.concat(val[0], '.', val[2][:value]); loc result, val[0], val[2] result end .,., -# reduce 154 omitted +# reduce 155 omitted -module_eval(<<'.,.,', 'egrammar.ra', 589) - def _reduce_155(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 591) + def _reduce_156(val, _values, result) result = val[1] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 595) - def _reduce_156(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 597) + def _reduce_157(val, _values, result) + result = add_definition(Factory.FUNCTION(val[1][:value], val[2], val[4])) + loc result, val[0], val[5] + + result + end +.,., + +module_eval(<<'.,.,', 'egrammar.ra', 605) + def _reduce_158(val, _values, result) result = val[0] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 596) - def _reduce_157(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 606) + def _reduce_159(val, _values, result) error val[0], "'class' is not a valid classname" result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 600) - def _reduce_158(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 610) + def _reduce_160(val, _values, result) result = [] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 601) - def _reduce_159(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 611) + def _reduce_161(val, _values, result) result = [] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 602) - def _reduce_160(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 612) + def _reduce_162(val, _values, result) result = val[1] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 606) - def _reduce_161(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 616) + def _reduce_163(val, _values, result) result = [val[0]] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 607) - def _reduce_162(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 617) + def _reduce_164(val, _values, result) result = val[0].push(val[2]) result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 611) - def _reduce_163(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 621) + def _reduce_165(val, _values, result) result = Factory.PARAM(val[0][:value], val[2]) ; loc result, val[0] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 612) - def _reduce_164(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 622) + def _reduce_166(val, _values, result) result = Factory.PARAM(val[0][:value]); loc result, val[0] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 625) - def _reduce_165(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 635) + def _reduce_167(val, _values, result) result = Factory.fqn(val[0][:value]).var ; loc result, val[0] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 631) - def _reduce_166(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 641) + def _reduce_168(val, _values, result) result = Factory.LIST(val[1]); loc result, val[0], val[2] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 632) - def _reduce_167(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 642) + def _reduce_169(val, _values, result) result = Factory.LIST(val[1]); loc result, val[0], val[3] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 633) - def _reduce_168(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 643) + def _reduce_170(val, _values, result) result = Factory.literal([]) ; loc result, val[0] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 634) - def _reduce_169(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 644) + def _reduce_171(val, _values, result) result = Factory.LIST(val[1]); loc result, val[0], val[2] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 635) - def _reduce_170(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 645) + def _reduce_172(val, _values, result) result = Factory.LIST(val[1]); loc result, val[0], val[3] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 636) - def _reduce_171(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 646) + def _reduce_173(val, _values, result) result = Factory.literal([]) ; loc result, val[0] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 639) - def _reduce_172(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 649) + def _reduce_174(val, _values, result) result = Factory.HASH(val[1]); loc result, val[0], val[2] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 640) - def _reduce_173(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 650) + def _reduce_175(val, _values, result) result = Factory.HASH(val[1]); loc result, val[0], val[3] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 641) - def _reduce_174(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 651) + def _reduce_176(val, _values, result) result = Factory.literal({}) ; loc result, val[0], val[3] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 644) - def _reduce_175(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 654) + def _reduce_177(val, _values, result) result = [val[0]] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 645) - def _reduce_176(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 655) + def _reduce_178(val, _values, result) result = val[0].push val[2] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 648) - def _reduce_177(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 658) + def _reduce_179(val, _values, result) result = Factory.KEY_ENTRY(val[0], val[2]); loc result, val[1] result end .,., -# reduce 178 omitted +# reduce 180 omitted -# reduce 179 omitted +# reduce 181 omitted -# reduce 180 omitted +# reduce 182 omitted -module_eval(<<'.,.,', 'egrammar.ra', 655) - def _reduce_181(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 665) + def _reduce_183(val, _values, result) result = Factory.literal(val[0][:value]) ; loc result, val[0] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 656) - def _reduce_182(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 666) + def _reduce_184(val, _values, result) result = Factory.string(val[0], *val[1]) ; loc result, val[0], val[1][-1] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 657) - def _reduce_183(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 667) + def _reduce_185(val, _values, result) result = Factory.literal(val[0][:value]); loc result, val[0] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 658) - def _reduce_184(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 668) + def _reduce_186(val, _values, result) result = Factory.literal(val[0][:value]); loc result, val[0] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 659) - def _reduce_185(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 669) + def _reduce_187(val, _values, result) result = Factory.literal(val[0][:value]); loc result, val[0] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 660) - def _reduce_186(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 670) + def _reduce_188(val, _values, result) result = [val[0]] + val[1] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 661) - def _reduce_187(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 671) + def _reduce_189(val, _values, result) result = Factory.TEXT(val[0]) result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 664) - def _reduce_188(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 674) + def _reduce_190(val, _values, result) result = [val[0]] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 665) - def _reduce_189(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 675) + def _reduce_191(val, _values, result) result = [val[0]] + val[1] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 668) - def _reduce_190(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 678) + def _reduce_192(val, _values, result) result = Factory.HEREDOC(val[0][:value], val[1]); loc result, val[0] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 671) - def _reduce_191(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 681) + def _reduce_193(val, _values, result) result = Factory.SUBLOCATE(val[0], val[1]); loc result, val[0] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 672) - def _reduce_192(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 682) + def _reduce_194(val, _values, result) result = Factory.SUBLOCATE(val[0], val[1]); loc result, val[0] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 675) - def _reduce_193(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 685) + def _reduce_195(val, _values, result) result = Factory.EPP(val[1], val[2]); loc result, val[0] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 678) - def _reduce_194(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 688) + def _reduce_196(val, _values, result) result = nil result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 679) - def _reduce_195(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 689) + def _reduce_197(val, _values, result) result = [] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 680) - def _reduce_196(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 690) + def _reduce_198(val, _values, result) result = val[1] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 683) - def _reduce_197(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 693) + def _reduce_199(val, _values, result) result = Factory.RENDER_STRING(val[0][:value]); loc result, val[0] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 684) - def _reduce_198(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 694) + def _reduce_200(val, _values, result) result = Factory.RENDER_EXPR(val[1]); loc result, val[0], val[2] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 685) - def _reduce_199(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 695) + def _reduce_201(val, _values, result) result = Factory.RENDER_EXPR(Factory.block_or_expression(*val[2])); loc result, val[0], val[4] result end .,., -# reduce 200 omitted +# reduce 202 omitted -# reduce 201 omitted +# reduce 203 omitted -module_eval(<<'.,.,', 'egrammar.ra', 691) - def _reduce_202(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 701) + def _reduce_204(val, _values, result) result = Factory.NUMBER(val[0][:value]) ; loc result, val[0] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 692) - def _reduce_203(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 702) + def _reduce_205(val, _values, result) result = Factory.QNAME_OR_NUMBER(val[0][:value]) ; loc result, val[0] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 693) - def _reduce_204(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 703) + def _reduce_206(val, _values, result) result = Factory.QREF(val[0][:value]) ; loc result, val[0] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 694) - def _reduce_205(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 704) + def _reduce_207(val, _values, result) result = Factory.literal(:undef); loc result, val[0] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 695) - def _reduce_206(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 705) + def _reduce_208(val, _values, result) result = Factory.literal(:default); loc result, val[0] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 700) - def _reduce_207(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 710) + def _reduce_209(val, _values, result) result = Factory.literal(val[0][:value]) ; loc result, val[0] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 703) - def _reduce_208(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 713) + def _reduce_210(val, _values, result) result = Factory.literal(val[0][:value]); loc result, val[0] result end .,., -# reduce 209 omitted +# reduce 211 omitted -module_eval(<<'.,.,', 'egrammar.ra', 709) - def _reduce_210(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 719) + def _reduce_212(val, _values, result) result = nil result end .,., -# reduce 211 omitted - -# reduce 212 omitted - # reduce 213 omitted # reduce 214 omitted # reduce 215 omitted # reduce 216 omitted # reduce 217 omitted # reduce 218 omitted # reduce 219 omitted # reduce 220 omitted # reduce 221 omitted # reduce 222 omitted # reduce 223 omitted # reduce 224 omitted # reduce 225 omitted # reduce 226 omitted -module_eval(<<'.,.,', 'egrammar.ra', 732) - def _reduce_227(val, _values, result) +# reduce 227 omitted + +# reduce 228 omitted + +module_eval(<<'.,.,', 'egrammar.ra', 742) + def _reduce_229(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/lexer2.rb b/lib/puppet/pops/parser/lexer2.rb index d9cec9352..bef7902a4 100644 --- a/lib/puppet/pops/parser/lexer2.rb +++ b/lib/puppet/pops/parser/lexer2.rb @@ -1,684 +1,685 @@ # The Lexer is responsbile for turning source text into tokens. # This version is a performance enhanced lexer (in comparison to the 3.x and earlier "future parser" lexer. # # Old returns tokens [:KEY, value, { locator = } # Could return [[token], locator] # or Token.new([token], locator) with the same API x[0] = token_symbol, x[1] = self, x[:key] = (:value, :file, :line, :pos) etc require 'strscan' require 'puppet/pops/parser/lexer_support' require 'puppet/pops/parser/heredoc_support' require 'puppet/pops/parser/interpolation_support' require 'puppet/pops/parser/epp_support' require 'puppet/pops/parser/slurp_support' class Puppet::Pops::Parser::Lexer2 include Puppet::Pops::Parser::LexerSupport include Puppet::Pops::Parser::HeredocSupport include Puppet::Pops::Parser::InterpolationSupport include Puppet::Pops::Parser::SlurpSupport include Puppet::Pops::Parser::EppSupport # ALl tokens have three slots, the token name (a Symbol), the token text (String), and a token text length. # All operator and punctuation tokens reuse singleton arrays Tokens that require unique values create # a unique array per token. # # PEFORMANCE NOTES: # This construct reduces the amount of object that needs to be created for operators and punctuation. # The length is pre-calculated for all singleton tokens. The length is used both to signal the length of # the token, and to advance the scanner position (without having to advance it with a scan(regexp)). # TOKEN_LBRACK = [:LBRACK, '['.freeze, 1].freeze TOKEN_LISTSTART = [:LISTSTART, '['.freeze, 1].freeze TOKEN_RBRACK = [:RBRACK, ']'.freeze, 1].freeze TOKEN_LBRACE = [:LBRACE, '{'.freeze, 1].freeze TOKEN_RBRACE = [:RBRACE, '}'.freeze, 1].freeze TOKEN_SELBRACE = [:SELBRACE, '{'.freeze, 1].freeze TOKEN_LPAREN = [:LPAREN, '('.freeze, 1].freeze TOKEN_RPAREN = [:RPAREN, ')'.freeze, 1].freeze TOKEN_EQUALS = [:EQUALS, '='.freeze, 1].freeze TOKEN_APPENDS = [:APPENDS, '+='.freeze, 2].freeze TOKEN_DELETES = [:DELETES, '-='.freeze, 2].freeze TOKEN_ISEQUAL = [:ISEQUAL, '=='.freeze, 2].freeze TOKEN_NOTEQUAL = [:NOTEQUAL, '!='.freeze, 2].freeze TOKEN_MATCH = [:MATCH, '=~'.freeze, 2].freeze TOKEN_NOMATCH = [:NOMATCH, '!~'.freeze, 2].freeze TOKEN_GREATEREQUAL = [:GREATEREQUAL, '>='.freeze, 2].freeze TOKEN_GREATERTHAN = [:GREATERTHAN, '>'.freeze, 1].freeze TOKEN_LESSEQUAL = [:LESSEQUAL, '<='.freeze, 2].freeze TOKEN_LESSTHAN = [:LESSTHAN, '<'.freeze, 1].freeze TOKEN_FARROW = [:FARROW, '=>'.freeze, 2].freeze TOKEN_PARROW = [:PARROW, '+>'.freeze, 2].freeze TOKEN_LSHIFT = [:LSHIFT, '<<'.freeze, 2].freeze TOKEN_LLCOLLECT = [:LLCOLLECT, '<<|'.freeze, 3].freeze TOKEN_LCOLLECT = [:LCOLLECT, '<|'.freeze, 2].freeze TOKEN_RSHIFT = [:RSHIFT, '>>'.freeze, 2].freeze TOKEN_RRCOLLECT = [:RRCOLLECT, '|>>'.freeze, 3].freeze TOKEN_RCOLLECT = [:RCOLLECT, '|>'.freeze, 2].freeze TOKEN_PLUS = [:PLUS, '+'.freeze, 1].freeze TOKEN_MINUS = [:MINUS, '-'.freeze, 1].freeze TOKEN_DIV = [:DIV, '/'.freeze, 1].freeze TOKEN_TIMES = [:TIMES, '*'.freeze, 1].freeze TOKEN_MODULO = [:MODULO, '%'.freeze, 1].freeze TOKEN_NOT = [:NOT, '!'.freeze, 1].freeze TOKEN_DOT = [:DOT, '.'.freeze, 1].freeze TOKEN_PIPE = [:PIPE, '|'.freeze, 1].freeze TOKEN_AT = [:AT , '@'.freeze, 1].freeze TOKEN_ATAT = [:ATAT , '@@'.freeze, 2].freeze TOKEN_COLON = [:COLON, ':'.freeze, 1].freeze TOKEN_COMMA = [:COMMA, ','.freeze, 1].freeze TOKEN_SEMIC = [:SEMIC, ';'.freeze, 1].freeze TOKEN_QMARK = [:QMARK, '?'.freeze, 1].freeze TOKEN_TILDE = [:TILDE, '~'.freeze, 1].freeze # lexed but not an operator in Puppet TOKEN_REGEXP = [:REGEXP, nil, 0].freeze TOKEN_IN_EDGE = [:IN_EDGE, '->'.freeze, 2].freeze TOKEN_IN_EDGE_SUB = [:IN_EDGE_SUB, '~>'.freeze, 2].freeze TOKEN_OUT_EDGE = [:OUT_EDGE, '<-'.freeze, 2].freeze TOKEN_OUT_EDGE_SUB = [:OUT_EDGE_SUB, '<~'.freeze, 2].freeze # Tokens that are always unique to what has been lexed TOKEN_STRING = [:STRING, nil, 0].freeze TOKEN_DQPRE = [:DQPRE, nil, 0].freeze TOKEN_DQMID = [:DQPRE, nil, 0].freeze TOKEN_DQPOS = [:DQPRE, nil, 0].freeze TOKEN_NUMBER = [:NUMBER, nil, 0].freeze TOKEN_VARIABLE = [:VARIABLE, nil, 1].freeze TOKEN_VARIABLE_EMPTY = [:VARIABLE, ''.freeze, 1].freeze # HEREDOC has syntax as an argument. TOKEN_HEREDOC = [:HEREDOC, nil, 0].freeze # EPP_START is currently a marker token, may later get syntax TOKEN_EPPSTART = [:EPP_START, nil, 0].freeze TOKEN_EPPEND = [:EPP_END, '%>', 2].freeze TOKEN_EPPEND_TRIM = [:EPP_END_TRIM, '-%>', 3].freeze # This is used for unrecognized tokens, will always be a single character. This particular instance # is not used, but is kept here for documentation purposes. TOKEN_OTHER = [:OTHER, nil, 0] # Keywords are all singleton tokens with pre calculated lengths. # Booleans are pre-calculated (rather than evaluating the strings "false" "true" repeatedly. # KEYWORDS = { - "case" => [:CASE, 'case', 4], - "class" => [:CLASS, 'class', 5], - "default" => [:DEFAULT, 'default', 7], - "define" => [:DEFINE, 'define', 6], - "if" => [:IF, 'if', 2], - "elsif" => [:ELSIF, 'elsif', 5], - "else" => [:ELSE, 'else', 4], - "inherits" => [:INHERITS,'inherits', 8], - "node" => [:NODE, 'node', 4], - "and" => [:AND, 'and', 3], - "or" => [:OR, 'or', 2], - "undef" => [:UNDEF, 'undef', 5], - "false" => [:BOOLEAN, false, 5], - "true" => [:BOOLEAN, true, 4], - "in" => [:IN, 'in', 2], - "unless" => [:UNLESS, 'unless', 6], + "case" => [:CASE, 'case', 4], + "class" => [:CLASS, 'class', 5], + "default" => [:DEFAULT, 'default', 7], + "define" => [:DEFINE, 'define', 6], + "if" => [:IF, 'if', 2], + "elsif" => [:ELSIF, 'elsif', 5], + "else" => [:ELSE, 'else', 4], + "inherits" => [:INHERITS, 'inherits', 8], + "node" => [:NODE, 'node', 4], + "and" => [:AND, 'and', 3], + "or" => [:OR, 'or', 2], + "undef" => [:UNDEF, 'undef', 5], + "false" => [:BOOLEAN, false, 5], + "true" => [:BOOLEAN, true, 4], + "in" => [:IN, 'in', 2], + "unless" => [:UNLESS, 'unless', 6], + "function" => [:FUNCTION, 'function', 8], } KEYWORDS.each {|k,v| v[1].freeze; v.freeze } KEYWORDS.freeze # Reverse lookup of keyword name to string KEYWORD_NAMES = {} KEYWORDS.each {|k, v| KEYWORD_NAMES[v[0]] = k } KEYWORD_NAMES.freeze PATTERN_WS = %r{[[:blank:]\r]+} # The single line comment includes the line ending. PATTERN_COMMENT = %r{#.*\r?} PATTERN_MLCOMMENT = %r{/\*(.*?)\*/}m PATTERN_REGEX = %r{/[^/\n]*/} PATTERN_REGEX_END = %r{/} PATTERN_REGEX_A = %r{\A/} # for replacement to "" PATTERN_REGEX_Z = %r{/\Z} # for replacement to "" PATTERN_REGEX_ESC = %r{\\/} # for replacement to "/" # The 3x patterns: # PATTERN_CLASSREF = %r{((::){0,1}[A-Z][-\w]*)+} # PATTERN_NAME = %r{((::)?[a-z0-9][-\w]*)(::[a-z0-9][-\w]*)*} # The NAME and CLASSREF in 4x are strict. Each segment must start with # a letter a-z and may not contain dashes (\w includes letters, digits and _). # PATTERN_CLASSREF = %r{((::){0,1}[A-Z][\w]*)+} PATTERN_NAME = %r{((::)?[a-z][\w]*)(::[a-z][\w]*)*} PATTERN_BARE_WORD = %r{[a-z_](?:[\w-]*[\w])?} PATTERN_DOLLAR_VAR = %r{\$(::)?(\w+::)*\w+} PATTERN_NUMBER = %r{\b(?:0[xX][0-9A-Fa-f]+|0?\d+(?:\.\d+)?(?:[eE]-?\d+)?)\b} # PERFORMANCE NOTE: # Comparison against a frozen string is faster (than unfrozen). # STRING_BSLASH_BSLASH = '\\'.freeze attr_reader :locator def initialize() end # Clears the lexer state (it is not required to call this as it will be garbage collected # and the next lex call (lex_string, lex_file) will reset the internal state. # def clear() # not really needed, but if someone wants to ensure garbage is collected as early as possible @scanner = nil @locator = nil @lexing_context = nil end # Convenience method, and for compatibility with older lexer. Use the lex_string instead which allows # passing the path to use without first having to call file= (which reads the file if it exists). # (Bad form to use overloading of assignment operator for something that is not really an assignment. Also, # overloading of = does not allow passing more than one argument). # def string=(string) lex_string(string, '') end def lex_string(string, path='') initvars @scanner = StringScanner.new(string) @locator = Puppet::Pops::Parser::Locator.locator(string, path) end # Lexes an unquoted string. # @param string [String] the string to lex # @param locator [Puppet::Pops::Parser::Locator] the locator to use (a default is used if nil is given) # @param escapes [Array] array of character strings representing the escape sequences to transform # @param interpolate [Boolean] whether interpolation of expressions should be made or not. # def lex_unquoted_string(string, locator, escapes, interpolate) initvars @scanner = StringScanner.new(string) @locator = locator || Puppet::Pops::Parser::Locator.locator(string, '') @lexing_context[:escapes] = escapes || UQ_ESCAPES @lexing_context[:uq_slurp_pattern] = (interpolate || !escapes.empty?) ? SLURP_UQ_PATTERN : SLURP_ALL_PATTERN end # Convenience method, and for compatibility with older lexer. Use the lex_file instead. # (Bad form to use overloading of assignment operator for something that is not really an assignment). # def file=(file) lex_file(file) end # TODO: This method should not be used, callers should get the locator since it is most likely required to # compute line, position etc given offsets. # def file @locator ? @locator.file : nil end # Initializes lexing of the content of the given file. An empty string is used if the file does not exist. # def lex_file(file) initvars contents = Puppet::FileSystem.exist?(file) ? Puppet::FileSystem.read(file) : "" @scanner = StringScanner.new(contents.freeze) @locator = Puppet::Pops::Parser::Locator.locator(contents, file) end def initvars @token_queue = [] # NOTE: additional keys are used; :escapes, :uq_slurp_pattern, :newline_jump, :epp_* @lexing_context = { :brace_count => 0, :after => nil, } end # Scans all of the content and returns it in an array # Note that the terminating [false, false] token is included in the result. # def fullscan result = [] scan {|token, value| result.push([token, value]) } result end # A block must be passed to scan. It will be called with two arguments, a symbol for the token, # and an instance of LexerSupport::TokenValue # PERFORMANCE NOTE: The TokenValue is designed to reduce the amount of garbage / temporary data # and to only convert the lexer's internal tokens on demand. It is slightly more costly to create an # instance of a class defined in Ruby than an Array or Hash, but the gain is much bigger since transformation # logic is avoided for many of its members (most are never used (e.g. line/pos information which is only of # value in general for error messages, and for some expressions (which the lexer does not know about). # def scan # PERFORMANCE note: it is faster to access local variables than instance variables. # This makes a small but notable difference since instance member access is avoided for # every token in the lexed content. # scn = @scanner ctx = @lexing_context queue = @token_queue lex_error_without_pos("Internal Error: No string or file given to lexer to process.") unless scn scn.skip(PATTERN_WS) # This is the lexer's main loop until queue.empty? && scn.eos? do if token = queue.shift || lex_token yield [ ctx[:after] = token[0], token[1] ] end end # Signals end of input yield [false, false] end # This lexes one token at the current position of the scanner. # PERFORMANCE NOTE: Any change to this logic should be performance measured. # def lex_token # Using three char look ahead (may be faster to do 2 char look ahead since only 2 tokens require a third scn = @scanner ctx = @lexing_context before = @scanner.pos # A look ahead of 3 characters is used since the longest operator ambiguity is resolved at that point. # PERFORMANCE NOTE: It is faster to peek once and use three separate variables for lookahead 0, 1 and 2. # la = scn.peek(3) return nil if la.empty? # Ruby 1.8.7 requires using offset and length (or integers are returned. # PERFORMANCE NOTE. # It is slightly faster to use these local variables than accessing la[0], la[1] etc. in ruby 1.9.3 # But not big enough to warrant two completely different implementations. # la0 = la[0,1] la1 = la[1,1] la2 = la[2,1] # PERFORMANCE NOTE: # A case when, where all the cases are literal values is the fastest way to map from data to code. # It is much faster than using a hash with lambdas, hash with symbol used to then invoke send etc. # This case statement is evaluated for most character positions in puppet source, and great care must # be taken to not introduce performance regressions. # case la0 when '.' emit(TOKEN_DOT, before) when ',' emit(TOKEN_COMMA, before) when '[' if ctx[:after] == :NAME && (before == 0 || scn.string[before-1,1] =~ /[[:blank:]\r\n]+/) emit(TOKEN_LISTSTART, before) else emit(TOKEN_LBRACK, before) end when ']' emit(TOKEN_RBRACK, before) when '(' emit(TOKEN_LPAREN, before) when ')' emit(TOKEN_RPAREN, before) when ';' emit(TOKEN_SEMIC, before) when '?' emit(TOKEN_QMARK, before) when '*' emit(TOKEN_TIMES, before) when '%' if la1 == '>' && ctx[:epp_mode] scn.pos += 2 if ctx[:epp_mode] == :expr enqueue_completed(TOKEN_EPPEND, before) end ctx[:epp_mode] = :text interpolate_epp else emit(TOKEN_MODULO, before) end when '{' # The lexer needs to help the parser since the technology used cannot deal with # lookahead of same token with different precedence. This is solved by making left brace # after ? into a separate token. # ctx[:brace_count] += 1 emit(if ctx[:after] == :QMARK TOKEN_SELBRACE else TOKEN_LBRACE end, before) when '}' ctx[:brace_count] -= 1 emit(TOKEN_RBRACE, before) # TOKENS @, @@, @( when '@' case la1 when '@' emit(TOKEN_ATAT, before) # TODO; Check if this is good for the grammar when '(' heredoc else emit(TOKEN_AT, before) end # TOKENS |, |>, |>> when '|' emit(case la1 when '>' la2 == '>' ? TOKEN_RRCOLLECT : TOKEN_RCOLLECT else TOKEN_PIPE end, before) # TOKENS =, =>, ==, =~ when '=' emit(case la1 when '=' TOKEN_ISEQUAL when '>' TOKEN_FARROW when '~' TOKEN_MATCH else TOKEN_EQUALS end, before) # TOKENS '+', '+=', and '+>' when '+' emit(case la1 when '=' TOKEN_APPENDS when '>' TOKEN_PARROW else TOKEN_PLUS end, before) # TOKENS '-', '->', and epp '-%>' (end of interpolation with trim) when '-' if ctx[:epp_mode] && la1 == '%' && la2 == '>' scn.pos += 3 if ctx[:epp_mode] == :expr enqueue_completed(TOKEN_EPPEND_TRIM, before) end interpolate_epp(:with_trim) else emit(case la1 when '>' TOKEN_IN_EDGE when '=' TOKEN_DELETES else TOKEN_MINUS end, before) end # TOKENS !, !=, !~ when '!' emit(case la1 when '=' TOKEN_NOTEQUAL when '~' TOKEN_NOMATCH else TOKEN_NOT end, before) # TOKENS ~>, ~ when '~' emit(la1 == '>' ? TOKEN_IN_EDGE_SUB : TOKEN_TILDE, before) when '#' scn.skip(PATTERN_COMMENT) nil # TOKENS '/', '/*' and '/ regexp /' when '/' case la1 when '*' scn.skip(PATTERN_MLCOMMENT) nil else # regexp position is a regexp, else a div if regexp_acceptable? && value = scn.scan(PATTERN_REGEX) # Ensure an escaped / was not matched while value[-2..-2] == STRING_BSLASH_BSLASH # i.e. \\ value += scn.scan_until(PATTERN_REGEX_END) end regex = value.sub(PATTERN_REGEX_A, '').sub(PATTERN_REGEX_Z, '').gsub(PATTERN_REGEX_ESC, '/') emit_completed([:REGEX, Regexp.new(regex), scn.pos-before], before) else emit(TOKEN_DIV, before) end end # TOKENS <, <=, <|, <<|, <<, <-, <~ when '<' emit(case la1 when '<' if la2 == '|' TOKEN_LLCOLLECT else TOKEN_LSHIFT end when '=' TOKEN_LESSEQUAL when '|' TOKEN_LCOLLECT when '-' TOKEN_OUT_EDGE when '~' TOKEN_OUT_EDGE_SUB else TOKEN_LESSTHAN end, before) # TOKENS >, >=, >> when '>' emit(case la1 when '>' TOKEN_RSHIFT when '=' TOKEN_GREATEREQUAL else TOKEN_GREATERTHAN end, before) # TOKENS :, ::CLASSREF, ::NAME when ':' if la1 == ':' before = scn.pos # PERFORMANCE NOTE: This could potentially be speeded up by using a case/when listing all # upper case letters. Alternatively, the 'A', and 'Z' comparisons may be faster if they are # frozen. # if la2 >= 'A' && la2 <= 'Z' # CLASSREF or error value = scn.scan(PATTERN_CLASSREF) if value after = scn.pos emit_completed([:CLASSREF, value, after-before], before) else # move to faulty position ('::' was ok) scn.pos = scn.pos + 3 lex_error("Illegal fully qualified class reference") end else # NAME or error value = scn.scan(PATTERN_NAME) if value emit_completed([:NAME, value, scn.pos-before], before) else # move to faulty position ('::' was ok) scn.pos = scn.pos + 2 lex_error("Illegal fully qualified name") end end else emit(TOKEN_COLON, before) end when '$' if value = scn.scan(PATTERN_DOLLAR_VAR) emit_completed([:VARIABLE, value[1..-1], scn.pos - before], before) else # consume the $ and let higher layer complain about the error instead of getting a syntax error emit(TOKEN_VARIABLE_EMPTY, before) end when '"' # Recursive string interpolation, 'interpolate' either returns a STRING token, or # a DQPRE with the rest of the string's tokens placed in the @token_queue interpolate_dq when "'" emit_completed([:STRING, slurp_sqstring, before-scn.pos], before) when '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' value = scn.scan(PATTERN_NUMBER) if value length = scn.pos - before assert_numeric(value, length) emit_completed([:NUMBER, value, length], before) else # move to faulty position ([0-9] was ok) scn.pos = scn.pos + 1 lex_error("Illegal number") end when 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z' value = scn.scan(PATTERN_NAME) # NAME or false start because followed by hyphen(s) and word if value && !scn.match?(/-+\w/) emit_completed(KEYWORDS[value] || [:NAME, value, scn.pos - before], before) else # Restart and check entire pattern (for ease of detecting non allowed trailing hyphen) scn.pos = before value = scn.scan(PATTERN_BARE_WORD) if value emit_completed([:STRING, value, scn.pos - before], before) else # move to faulty position ([a-z] was ok) scn.pos = scn.pos + 1 lex_error("Illegal name") end end when 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z' value = scn.scan(PATTERN_CLASSREF) if value emit_completed([:CLASSREF, value, scn.pos - before], before) else # move to faulty position ([A-Z] was ok) scn.pos = scn.pos + 1 lex_error("Illegal class reference") end when "\n" # If heredoc_cont is in effect there are heredoc text lines to skip over # otherwise just skip the newline. # if ctx[:newline_jump] scn.pos = ctx[:newline_jump] ctx[:newline_jump] = nil else scn.pos += 1 end return nil when ' ', "\t", "\r" scn.skip(PATTERN_WS) return nil else # In case of unicode spaces of various kinds that are captured by a regexp, but not by the # simpler case expression above (not worth handling those special cases with better performance). if scn.skip(PATTERN_WS) nil else # "unrecognized char" emit([:OTHER, la0, 1], before) end end end # Emits (produces) a token [:tokensymbol, TokenValue] and moves the scanner's position past the token # def emit(token, byte_offset) @scanner.pos = byte_offset + token[2] [token[0], TokenValue.new(token, byte_offset, @locator)] end # Emits the completed token on the form [:tokensymbol, TokenValue. This method does not alter # the scanner's position. # def emit_completed(token, byte_offset) [token[0], TokenValue.new(token, byte_offset, @locator)] end # Enqueues a completed token at the given offset def enqueue_completed(token, byte_offset) @token_queue << emit_completed(token, byte_offset) end # Allows subprocessors for heredoc etc to enqueue tokens that are tokenized by a different lexer instance # def enqueue(emitted_token) @token_queue << emitted_token end # Answers after which tokens it is acceptable to lex a regular expression. # PERFORMANCE NOTE: # It may be beneficial to turn this into a hash with default value of true for missing entries. # A case expression with literal values will however create a hash internally. Since a reference is # always needed to the hash, this access is almost as costly as a method call. # def regexp_acceptable? case @lexing_context[:after] # Ends of (potential) R-value generating expressions when :RPAREN, :RBRACK, :RRCOLLECT, :RCOLLECT false # End of (potential) R-value - but must be allowed because of case expressions # Called out here to not be mistaken for a bug. when :RBRACE true # Operands (that can be followed by DIV (even if illegal in grammar) when :NAME, :CLASSREF, :NUMBER, :STRING, :BOOLEAN, :DQPRE, :DQMID, :DQPOST, :HEREDOC, :REGEX false else true end end end diff --git a/lib/puppet/pops/types/type_calculator.rb b/lib/puppet/pops/types/type_calculator.rb index ddab7016d..f6d76a2d1 100644 --- a/lib/puppet/pops/types/type_calculator.rb +++ b/lib/puppet/pops/types/type_calculator.rb @@ -1,1460 +1,1460 @@ # The TypeCalculator can answer questions about puppet types. # # The Puppet type system is primarily based on sub-classing. When asking the type calculator to infer types from Ruby in general, it # may not provide the wanted answer; it does not for instance take module inclusions and extensions into account. In general the type # system should be unsurprising for anyone being exposed to the notion of type. The type `Data` may require a bit more explanation; this # is an abstract type that includes all scalar types, as well as Array with an element type compatible with Data, and Hash with key # compatible with scalar and elements compatible with Data. Expressed differently; Data is what you typically express using JSON (with # the exception that the Puppet type system also includes Pattern (regular expression) as a scalar. # # Inference # --------- # The `infer(o)` method infers a Puppet type for scalar Ruby objects, and for Arrays and Hashes. # The inference result is instance specific for single typed collections # and allows answering questions about its embedded type. It does not however preserve multiple types in # a collection, and can thus not answer questions like `[1,a].infer() =~ Array[Integer, String]` since the inference # computes the common type Scalar when combining Integer and String. # # The `infer_generic(o)` method infers a generic Puppet type for scalar Ruby object, Arrays and Hashes. # This inference result does not contain instance specific information; e.g. Array[Integer] where the integer # range is the generic default. Just `infer` it also combines types into a common type. # # The `infer_set(o)` method works like `infer` but preserves all type information. It does not do any # reduction into common types or ranges. This method of inference is best suited for answering questions # about an object being an instance of a type. It correctly answers: `[1,a].infer_set() =~ Array[Integer, String]` # # The `generalize!(t)` method modifies an instance specific inference result to a generic. The method mutates # the given argument. Basically, this removes string instances from String, and range from Integer and Float. # # Assignability # ------------- # The `assignable?(t1, t2)` method answers if t2 conforms to t1. The type t2 may be an instance, in which case # its type is inferred, or a type. # # Instance? # --------- # The `instance?(t, o)` method answers if the given object (instance) is an instance that is assignable to the given type. # # String # ------ # Creates a string representation of a type. # # Creation of Type instances # -------------------------- # Instance of the classes in the {Puppet::Pops::Types type model} are used to denote a specific type. It is most convenient # to use the {Puppet::Pops::Types::TypeFactory TypeFactory} when creating instances. # # @note # In general, new instances of the wanted type should be created as they are assigned to models using containment, and a # contained object can only be in one container at a time. Also, the type system may include more details in each type # instance, such as if it may be nil, be empty, contain a certain count etc. Or put differently, the puppet types are not # singletons. # # All types support `copy` which should be used when assigning a type where it is unknown if it is bound or not # to a parent type. A check can be made with `t.eContainer().nil?` # # Equality and Hash # ----------------- # Type instances are equal in terms of Ruby eql? and `==` if they describe the same type, but they are not `equal?` if they are not # the same type instance. Two types that describe the same type have identical hash - this makes them usable as hash keys. # # Types and Subclasses # -------------------- # In general, the type calculator should be used to answer questions if a type is a subtype of another (using {#assignable?}, or # {#instance?} if the question is if a given object is an instance of a given type (or is a subtype thereof). # Many of the types also have a Ruby subtype relationship; e.g. PHashType and PArrayType are both subtypes of PCollectionType, and # PIntegerType, PFloatType, PStringType,... are subtypes of PScalarType. Even if it is possible to answer certain questions about # type by looking at the Ruby class of the types this is considered an implementation detail, and such checks should in general # be performed by the type_calculator which implements the type system semantics. # # The PRubyType # ------------- # The PRubyType corresponds to a Ruby Class, except for the puppet types that are specialized (i.e. PRubyType should not be # used for Integer, String, etc. since there are specialized types for those). # When the type calculator deals with PRubyTypes and checks for assignability, it determines the "common ancestor class" of two classes. # This check is made based on the superclasses of the two classes being compared. In order to perform this, the classes must be present # (i.e. they are resolved from the string form in the PRubyType to a loaded, instantiated Ruby Class). In general this is not a problem, # since the question to produce the common super type for two objects means that the classes must be present or there would have been # no instances present in the first place. If however the classes are not present, the type calculator will fall back and state that # the two types at least have Object in common. # # @see Puppet::Pops::Types::TypeFactory TypeFactory for how to create instances of types # @see Puppet::Pops::Types::TypeParser TypeParser how to construct a type instance from a String # @see Puppet::Pops::Types Types for details about the type model # # Using the Type Calculator # ----- # The type calculator can be directly used via its class methods. If doing time critical work and doing many # calls to the type calculator, it is more performant to create an instance and invoke the corresponding # instance methods. Note that inference is an expensive operation, rather than infering the same thing # several times, it is in general better to infer once and then copy the result if mutation to a more generic form is # required. # # @api public # class Puppet::Pops::Types::TypeCalculator Types = Puppet::Pops::Types TheInfinity = 1.0 / 0.0 # because the Infinity symbol is not defined # @api public def self.assignable?(t1, t2) singleton.assignable?(t1,t2) end # @api public def self.string(t) singleton.string(t) end # @api public def self.infer(o) singleton.infer(o) end # @api public def self.generalize!(o) singleton.generalize!(o) end # @api public def self.infer_set(o) singleton.infer_set(o) end # @api public def self.debug_string(t) singleton.debug_string(t) end # @api public def self.enumerable(t) singleton.enumerable(t) end # @api private def self.singleton() @tc_instance ||= new end # @api public # def initialize @@assignable_visitor ||= Puppet::Pops::Visitor.new(nil,"assignable",1,1) @@infer_visitor ||= Puppet::Pops::Visitor.new(nil,"infer",0,0) @@infer_set_visitor ||= Puppet::Pops::Visitor.new(nil,"infer_set",0,0) @@instance_of_visitor ||= Puppet::Pops::Visitor.new(nil,"instance_of",1,1) @@string_visitor ||= Puppet::Pops::Visitor.new(nil,"string",0,0) @@inspect_visitor ||= Puppet::Pops::Visitor.new(nil,"debug_string",0,0) @@enumerable_visitor ||= Puppet::Pops::Visitor.new(nil,"enumerable",0,0) @@extract_visitor ||= Puppet::Pops::Visitor.new(nil,"extract",0,0) @@generalize_visitor ||= Puppet::Pops::Visitor.new(nil,"generalize",0,0) da = Types::PArrayType.new() da.element_type = Types::PDataType.new() @data_array = da h = Types::PHashType.new() h.element_type = Types::PDataType.new() h.key_type = Types::PScalarType.new() @data_hash = h @data_t = Types::PDataType.new() @scalar_t = Types::PScalarType.new() @numeric_t = Types::PNumericType.new() @t = Types::PObjectType.new() # Data accepts a Tuple that has 0-infinity Data compatible entries (e.g. a Tuple equivalent to Array). data_tuple = Types::PTupleType.new() data_tuple.addTypes(Types::PDataType.new()) data_tuple.size_type = Types::PIntegerType.new() data_tuple.size_type.from = 0 data_tuple.size_type.to = nil # infinity @data_tuple_t = data_tuple # Variant type compatible with Data data_variant = Types::PVariantType.new() data_variant.addTypes(@data_hash.copy) data_variant.addTypes(@data_array.copy) data_variant.addTypes(Types::PScalarType.new) data_variant.addTypes(Types::PNilType.new) data_variant.addTypes(@data_tuple_t.copy) @data_variant_t = data_variant collection_default_size = Types::PIntegerType.new() collection_default_size.from = 0 collection_default_size.to = nil # infinity @collection_default_size_t = collection_default_size non_empty_string = Types::PStringType.new non_empty_string.size_type = Types::PIntegerType.new() non_empty_string.size_type.from = 1 non_empty_string.size_type.to = nil # infinity @non_empty_string_t = non_empty_string end # Convenience method to get a data type for comparisons # @api private the returned value may not be contained in another element # def data @data_t end # Convenience method to get a variant compatible with the Data type. # @api private the returned value may not be contained in another element # def data_variant @data_variant_t end def self.data_variant singleton.data_variant end # Answers the question 'is it possible to inject an instance of the given class' # A class is injectable if it has a special *assisted inject* class method called `inject` taking # an injector and a scope as argument, or if it has a zero args `initialize` method. # # @param klazz [Class, PRubyType] the class/type to check if it is injectable # @return [Class, nil] the injectable Class, or nil if not injectable # @api public # def injectable_class(klazz) # Handle case when we get a PType instead of a class if klazz.is_a?(Types::PRubyType) klazz = Puppet::Pops::Types::ClassLoader.provide(klazz) end # data types can not be injected (check again, it is not safe to assume that given RubyType klazz arg was ok) return false unless type(klazz).is_a?(Types::PRubyType) if (klazz.respond_to?(:inject) && klazz.method(:inject).arity() == -4) || klazz.instance_method(:initialize).arity() == 0 klazz else nil end end # Answers 'can an instance of type t2 be assigned to a variable of type t' # @api public # def assignable?(t, t2) # nil is assignable to anything except to required types return true if is_pnil?(t2) if t.is_a?(Class) t = type(t) end if t2.is_a?(Class) t2 = type(t2) end @@assignable_visitor.visit_this_1(self, t, t2) end # Returns an enumerable if the t represents something that can be iterated def enumerable(t) @@enumerable_visitor.visit_this_0(self, t) end # Answers if the two given types describe the same type def equals(left, right) return false unless left.is_a?(Types::PAbstractType) && right.is_a?(Types::PAbstractType) # Types compare per class only - an extra test must be made if the are mutually assignable # to find all types that represent the same type of instance # left == right || (assignable?(right, left) && assignable?(left, right)) end # Answers 'what is the Puppet Type corresponding to the given Ruby class' # @param c [Class] the class for which a puppet type is wanted # @api public # def type(c) raise ArgumentError, "Argument must be a Class" unless c.is_a? Class # Can't use a visitor here since we don't have an instance of the class case when c <= Integer type = Types::PIntegerType.new() when c == Float type = Types::PFloatType.new() when c == Numeric type = Types::PNumericType.new() when c == String type = Types::PStringType.new() when c == Regexp type = Types::PRegexpType.new() when c == NilClass type = Types::PNilType.new() when c == FalseClass, c == TrueClass type = Types::PBooleanType.new() when c == Class type = Types::PType.new() when c == Array # Assume array of data values type = Types::PArrayType.new() type.element_type = Types::PDataType.new() when c == Hash # Assume hash with scalar keys and data values type = Types::PHashType.new() type.key_type = Types::PScalarType.new() type.element_type = Types::PDataType.new() else type = Types::PRubyType.new() type.ruby_class = c.name end type end # Generalizes value specific types. The given type is mutated and returned. # @api public def generalize!(o) @@generalize_visitor.visit_this_0(self, o) o.eAllContents.each { |x| @@generalize_visitor.visit_this_0(self, x) } o end def generalize_Object(o) # do nothing, there is nothing to change for most types end def generalize_PStringType(o) o.values = [] o.size_type = nil [] end def generalize_PCollectionType(o) # erase the size constraint from Array and Hash (if one exists, it is transformed to -Infinity - + Infinity, which is # not desirable. o.size_type = nil end def generalize_PFloatType(o) o.to = nil o.from = nil end def generalize_PIntegerType(o) o.to = nil o.from = nil end # Answers 'what is the single common Puppet Type describing o', or if o is an Array or Hash, what is the # single common type of the elements (or keys and elements for a Hash). # @api public # def infer(o) @@infer_visitor.visit_this_0(self, o) end def infer_generic(o) result = generalize!(infer(o)) result end # Answers 'what is the set of Puppet Types of o' # @api public # def infer_set(o) @@infer_set_visitor.visit_this_0(self, o) end def instance_of(t, o) # return true if o.nil? && !t.is_a?(Types::PRequiredType) @@instance_of_visitor.visit_this_1(self, t, o) end def instance_of_Object(t, o) # Undef is Undef and Object, but nothing else when checking instance? return false if (o.nil? || o == :undef) && t.class != Types::PObjectType assignable?(t, infer(o)) end def instance_of_PArrayType(t, o) return false unless o.is_a?(Array) return false unless o.all? {|element| instance_of(t.element_type, element) } size_t = t.size_type || @collection_default_size_t size_t2 = size_as_type(o) assignable?(size_t, size_t2) end def instance_of_PTupleType(t, o) return false unless o.is_a?(Array) # compute the tuple's min/max size, and check if that size matches size_t = t.size_type || Puppet::Pops::Types::TypeFactory.range(*t.size_range) # compute the array's size as type size_t2 = size_as_type(o) return false unless assignable?(size_t, size_t2) o.each_with_index do |element, index| return false unless instance_of(t.types[index] || t.types[-1], element) end true end def instance_of_PStructType(t, o) return false unless o.is_a?(Hash) h = t.hashed_elements # all keys must be present and have a value (even if nil/undef) (o.keys - h.keys).empty? && h.all? { |k,v| instance_of(v, o[k]) } end def instance_of_PHashType(t, o) return false unless o.is_a?(Hash) key_t = t.key_type element_t = t.element_type return false unless o.keys.all? {|key| instance_of(key_t, key) } && o.values.all? {|value| instance_of(element_t, value) } size_t = t.size_type || @collection_default_size_t size_t2 = size_as_type(o) assignable?(size_t, size_t2) end def instance_of_PDataType(t, o) instance_of(@data_variant_t, o) end def instance_of_PNilType(t, o) return o.nil? || o == :undef end def instance_of_POptionalType(t, o) return true if (o.nil? || o == :undef) instance_of(t.optional_type, o) end def instance_of_PVariantType(t, o) # instance of variant if o is instance? of any of variant's types t.types.any? { |option_t| instance_of(option_t, o) } end # Answers 'is o an instance of type t' # @api public # def self.instance?(t, o) singleton.instance_of(t,o) end # Answers 'is o an instance of type t' # @api public # def instance?(t, o) instance_of(t,o) end # Answers if t is a puppet type # @api public # def is_ptype?(t) return t.is_a?(Types::PAbstractType) end # Answers if t represents the puppet type PNilType # @api public # def is_pnil?(t) return t.nil? || t.is_a?(Types::PNilType) end # Answers, 'What is the common type of t1 and t2?' # # TODO: The current implementation should be optimized for performance # # @api public # def common_type(t1, t2) raise ArgumentError, 'two types expected' unless (is_ptype?(t1) || is_pnil?(t1)) && (is_ptype?(t2) || is_pnil?(t2)) # if either is nil, the common type is the other if is_pnil?(t1) return t2 elsif is_pnil?(t2) return t1 end # Simple case, one is assignable to the other if assignable?(t1, t2) return t1 elsif assignable?(t2, t1) return t2 end # when both are arrays, return an array with common element type if t1.is_a?(Types::PArrayType) && t2.is_a?(Types::PArrayType) type = Types::PArrayType.new() type.element_type = common_type(t1.element_type, t2.element_type) return type end # when both are hashes, return a hash with common key- and element type if t1.is_a?(Types::PHashType) && t2.is_a?(Types::PHashType) type = Types::PHashType.new() type.key_type = common_type(t1.key_type, t2.key_type) type.element_type = common_type(t1.element_type, t2.element_type) return type end # when both are host-classes, reduce to PHostClass[] (since one was not assignable to the other) if t1.is_a?(Types::PHostClassType) && t2.is_a?(Types::PHostClassType) return Types::PHostClassType.new() end # when both are resources, reduce to Resource[T] or Resource[] (since one was not assignable to the other) if t1.is_a?(Types::PResourceType) && t2.is_a?(Types::PResourceType) result = Types::PResourceType.new() # only Resource[] unless the type name is the same if t1.type_name == t2.type_name then result.type_name = t1.type_name end # the cross assignability test above has already determined that they do not have the same type and title return result end # Integers have range, expand the range to the common range if t1.is_a?(Types::PIntegerType) && t2.is_a?(Types::PIntegerType) t1range = from_to_ordered(t1.from, t1.to) t2range = from_to_ordered(t2.from, t2.to) t = Types::PIntegerType.new() from = [t1range[0], t2range[0]].min to = [t1range[1], t2range[1]].max t.from = from unless from == TheInfinity t.to = to unless to == TheInfinity return t end # Floats have range, expand the range to the common range if t1.is_a?(Types::PFloatType) && t2.is_a?(Types::PFloatType) t1range = from_to_ordered(t1.from, t1.to) t2range = from_to_ordered(t2.from, t2.to) t = Types::PFloatType.new() from = [t1range[0], t2range[0]].min to = [t1range[1], t2range[1]].max t.from = from unless from == TheInfinity t.to = to unless to == TheInfinity return t end if t1.is_a?(Types::PStringType) && t2.is_a?(Types::PStringType) t = Types::PStringType.new() t.values = t1.values | t2.values return t end if t1.is_a?(Types::PPatternType) && t2.is_a?(Types::PPatternType) t = Types::PPatternType.new() # must make copies since patterns are contained types, not data-types t.patterns = (t1.patterns | t2.patterns).map {|p| p.copy } return t end if t1.is_a?(Types::PEnumType) && t2.is_a?(Types::PEnumType) # The common type is one that complies with either set t = Types::PEnumType.new t.values = t1.values | t2.values return t end if t1.is_a?(Types::PVariantType) && t2.is_a?(Types::PVariantType) # The common type is one that complies with either set t = Types::PVariantType.new t.types = (t1.types | t2.types).map {|opt_t| opt_t.copy } return t end if t1.is_a?(Types::PRegexpType) && t2.is_a?(Types::PRegexpType) # if they were identical, the general rule would return a parameterized regexp # since they were not, the result is a generic regexp type return Types::PPatternType.new() end # Common abstract types, from most specific to most general if common_numeric?(t1, t2) return Types::PNumericType.new() end if common_scalar?(t1, t2) return Types::PScalarType.new() end if common_data?(t1,t2) return Types::PDataType.new() end # Meta types Type[Integer] + Type[String] => Type[Data] if t1.is_a?(Types::PType) && t2.is_a?(Types::PType) type = Types::PType.new() type.type = common_type(t1.type, t2.type) return type end if t1.is_a?(Types::PRubyType) && t2.is_a?(Types::PRubyType) if t1.ruby_class == t2.ruby_class return t1 end # finding the common super class requires that names are resolved to class c1 = Types::ClassLoader.provide_from_type(t1) c2 = Types::ClassLoader.provide_from_type(t2) if c1 && c2 c2_superclasses = superclasses(c2) superclasses(c1).each do|c1_super| c2_superclasses.each do |c2_super| if c1_super == c2_super result = Types::PRubyType.new() result.ruby_class = c1_super.name return result end end end end end # If both are RubyObjects if common_pobject?(t1, t2) return Types::PObjectType.new() end end # Produces the superclasses of the given class, including the class def superclasses(c) result = [c] while s = c.superclass result << s c = s end result end # Produces a string representing the type # @api public # def string(t) @@string_visitor.visit_this_0(self, t) end # Produces a debug string representing the type (possibly with more information that the regular string format) # @api public # def debug_string(t) @@inspect_visitor.visit_this_0(self, t) end # Reduces an enumerable of types to a single common type. # @api public # def reduce_type(enumerable) enumerable.reduce(nil) {|memo, t| common_type(memo, t) } end # Reduce an enumerable of objects to a single common type # @api public # def infer_and_reduce_type(enumerable) reduce_type(enumerable.collect() {|o| infer(o) }) end # The type of all classes is PType # @api private # def infer_Class(o) Types::PType.new() end # @api private def infer_Object(o) type = Types::PRubyType.new() type.ruby_class = o.class.name type end # The type of all types is PType # @api private # - def infer_PObjectType(o) + def infer_PAbstractType(o) type = Types::PType.new() type.type = o.copy type end # The type of all types is PType # This is the metatype short circuit. # @api private # def infer_PType(o) type = Types::PType.new() type.type = o.copy type end # @api private def infer_String(o) t = Types::PStringType.new() t.addValues(o) t.size_type = size_as_type(o) t end # @api private def infer_Float(o) t = Types::PFloatType.new() t.from = o t.to = o t end # @api private def infer_Integer(o) t = Types::PIntegerType.new() t.from = o t.to = o t end # @api private def infer_Regexp(o) t = Types::PRegexpType.new() t.pattern = o.source t end # @api private def infer_NilClass(o) Types::PNilType.new() end # Inference of :undef as PNilType, all other are Ruby[Symbol] # @api private def infer_Symbol(o) o == :undef ? infer_NilClass(o) : infer_Object(o) end # @api private def infer_TrueClass(o) Types::PBooleanType.new() end # @api private def infer_FalseClass(o) Types::PBooleanType.new() end # @api private # A Puppet::Parser::Resource, or Puppet::Resource # def infer_Resource(o) t = Types::PResourceType.new() t.type_name = o.type.to_s t.title = o.title t end # @api private def infer_Array(o) type = Types::PArrayType.new() type.element_type = if o.empty? Types::PNilType.new() else infer_and_reduce_type(o) end type.size_type = size_as_type(o) type end # @api private def infer_Hash(o) type = Types::PHashType.new() if o.empty? ktype = Types::PNilType.new() etype = Types::PNilType.new() else ktype = infer_and_reduce_type(o.keys()) etype = infer_and_reduce_type(o.values()) end type.key_type = ktype type.element_type = etype type.size_type = size_as_type(o) type end def size_as_type(collection) size = collection.size t = Types::PIntegerType.new() t.from = size t.to = size t end # Common case for everything that intrinsically only has a single type def infer_set_Object(o) infer(o) end def infer_set_Array(o) if o.empty? type = Types::PArrayType.new() type.element_type = Types::PNilType.new() else type = Types::PTupleType.new() type.types = o.map() {|x| infer_set(x) } end type end def infer_set_Hash(o) type = Types::PHashType.new() if o.empty? ktype = Types::PNilType.new() etype = Types::PNilType.new() else ktype = Types::PVariantType.new() ktype.types = o.keys.map() {|k| infer_set(k) } etype = Types::PVariantType.new() etype.types = o.values.map() {|e| infer_set(e) } end type.key_type = unwrap_single_variant(ktype) type.element_type = unwrap_single_variant(vtype) type.size_type = size_as_type(o) type end def unwrap_single_variant(possible_variant) if possible_variant.is_a?(Types::PVariantType) && possible_variant.types.size == 1 possible_variant.types[0] else possible_variant end end # False in general type calculator # @api private def assignable_Object(t, t2) false end # @api private def assignable_PObjectType(t, t2) t2.is_a?(Types::PObjectType) end # @api private def assignable_PNilType(t, t2) # Only undef/nil is assignable to nil type t2.is_a?(Types::PNilType) end # @api private def assignable_PScalarType(t, t2) t2.is_a?(Types::PScalarType) end # @api private def assignable_PNumericType(t, t2) t2.is_a?(Types::PNumericType) end # @api private def assignable_PIntegerType(t, t2) return false unless t2.is_a?(Types::PIntegerType) trange = from_to_ordered(t.from, t.to) t2range = from_to_ordered(t2.from, t2.to) # If t2 min and max are within the range of t trange[0] <= t2range[0] && trange[1] >= t2range[1] end # Transform int range to a size constraint # if range == nil the constraint is 1,1 # if range.from == nil min size = 1 # if range.to == nil max size == Infinity # def size_range(range) return [1,1] if range.nil? from = range.from to = range.to x = from.nil? ? 1 : from y = to.nil? ? TheInfinity : to if x < y [x, y] else [y, x] end end # @api private def from_to_ordered(from, to) x = (from.nil? || from == :default) ? -TheInfinity : from y = (to.nil? || to == :default) ? TheInfinity : to if x < y [x, y] else [y, x] end end # @api private def assignable_PVariantType(t, t2) # Data is a specific variant t2 = @data_variant_t if t2.is_a?(Types::PDataType) if t2.is_a?(Types::PVariantType) # A variant is assignable if all of its options are assignable to one of this type's options return true if t == t2 t2.types.all? do |other| # if the other is a Variant, all if its options, but be assignable to one of this type's options other = other.is_a?(Types::PDataType) ? @data_variant_t : other if other.is_a?(Types::PVariantType) assignable?(t, other) else t.types.any? {|option_t| assignable?(option_t, other) } end end else # A variant is assignable if t2 is assignable to any of its types t.types.any? { |option_t| assignable?(option_t, t2) } end end def max(a,b) a >=b ? a : b end def min(a,b) a <= b ? a : b end def assignable_PTupleType(t, t2) return true if t == t2 || t.types.empty? && (t2.is_a?(Types::PArrayType)) size_t = t.size_type || Puppet::Pops::Types::TypeFactory.range(*t.size_range) if t2.is_a?(Types::PTupleType) size_t2 = t2.size_type || Puppet::Pops::Types::TypeFactory.range(*t2.size_range) # not assignable if the number of types in t2 is outside number of types in t1 return false unless assignable?(size_t, size_t2) max(t.types.size, t2.types.size).times do |index| return false unless assignable?((t.types[index] || t.types[-1]), (t2.types[index] || t2.types[-1])) end true elsif t2.is_a?(Types::PArrayType) t2_entry = t2.element_type # Array of anything can not be assigned (unless tuple is tuple of anything) - this case # was handled at the top of this method. # return false if t2_entry.nil? size_t = t.size_type || Puppet::Pops::Types::TypeFactory.range(*t.size_range) size_t2 = t2.size_type || @collection_default_size_t return false unless assignable?(size_t, size_t2) min(t.types.size, size_t2.range()[1]).times do |index| return false unless assignable?((t.types[index] || t.types[-1]), t2_entry) end true else false end end # Produces the tuple entry at the given index given a tuple type, its from/to constraints on the last # type, and an index. # Produces nil if the index is out of bounds # from must be less than to, and from may not be less than 0 # # @api private # def tuple_entry_at(tuple_t, from, to, index) regular = (tuple_t.types.size - 1) if index < regular tuple_t.types[index] elsif index < regular + to # in the varargs part tuple_t.types[-1] else nil end end # @api private # def assignable_PStructType(t, t2) return true if t == t2 || t.elements.empty? && (t2.is_a?(Types::PHashType)) h = t.hashed_elements if t2.is_a?(Types::PStructType) h2 = t2.hashed_elements h.size == h2.size && h.all? {|k, v| assignable?(v, h2[k]) } elsif t2.is_a?(Types::PHashType) size_t2 = t2.size_type || @collection_default_size_t size_t = Types::PIntegerType.new size_t.from = size_t.to = h.size # compatible size # hash key type must be string of min 1 size # hash value t must be assignable to each key element_type = t2.element_type assignable?(size_t, size_t2) && assignable?(@non_empty_string_t, t2.key_type) && h.all? {|k,v| assignable?(v, element_type) } else false end end # @api private def assignable_POptionalType(t, t2) return true if t2.is_a?(Types::PNilType) if t2.is_a?(Types::POptionalType) assignable?(t.optional_type, t2.optional_type) else assignable?(t.optional_type, t2) end end # @api private def assignable_PEnumType(t, t2) return true if t == t2 || (t.values.empty? && (t2.is_a?(Types::PStringType) || t2.is_a?(Types::PEnumType))) if t2.is_a?(Types::PStringType) # if the set of strings are all found in the set of enums t2.values.all? { |s| t.values.any? { |e| e == s }} else false end end # @api private def assignable_PStringType(t, t2) if t.values.empty? # A general string is assignable by any other string or pattern restricted string # if the string has a size constraint it does not match since there is no reasonable way # to compute the min/max length a pattern will match. For enum, it is possible to test that # each enumerator value is within range size_t = t.size_type || @collection_default_size_t case t2 when Types::PStringType # true if size compliant size_t2 = t2.size_type || @collection_default_size_t assignable?(size_t, size_t2) when Types::PPatternType # true if size constraint is at least 0 to +Infinity (which is the same as the default) assignable?(size_t, @collection_default_size_t) when Types::PEnumType if t2.values # true if all enum values are within range min, max = t2.values.map(&:size).minmax trange = from_to_ordered(size_t.from, size_t.to) t2range = [min, max] # If t2 min and max are within the range of t trange[0] <= t2range[0] && trange[1] >= t2range[1] else # no string can match this enum anyway since it does not accept anything false end end elsif t2.is_a?(Types::PStringType) # A specific string acts as a set of strings - must have exactly the same strings # In this case, size does not matter since the definition is very precise anyway Set.new(t.values) == Set.new(t2.values) else # All others are false, since no other type describes the same set of specific strings false end end # @api private def assignable_PPatternType(t, t2) return true if t == t2 return false unless t2.is_a?(Types::PStringType) || t2.is_a?(Types::PEnumType) if t2.values.empty? # Strings / Enums (unknown which ones) cannot all match a pattern, but if there is no pattern it is ok # (There should really always be a pattern, but better safe than sorry). return t.patterns.empty? ? true : false end # all strings in String/Enum type must match one of the patterns in Pattern type regexps = t.patterns.map {|p| p.regexp } t2.values.all? { |v| regexps.any? {|re| re.match(v) } } end # @api private def assignable_PFloatType(t, t2) return false unless t2.is_a?(Types::PFloatType) trange = from_to_ordered(t.from, t.to) t2range = from_to_ordered(t2.from, t2.to) # If t2 min and max are within the range of t trange[0] <= t2range[0] && trange[1] >= t2range[1] end # @api private def assignable_PBooleanType(t, t2) t2.is_a?(Types::PBooleanType) end # @api private def assignable_PRegexpType(t, t2) t2.is_a?(Types::PRegexpType) && (t.pattern.nil? || t.pattern == t2.pattern) end # @api private def assignable_PCollectionType(t, t2) size_t = t.size_type || @collection_default_size_t case t2 when Types::PCollectionType size_t2 = t2.size_type || @collection_default_size_t assignable?(size_t, size_t2) when Types::PTupleType # compute the tuple's min/max size, and check if that size matches from, to = size_range(t2.size_type) t2s = Types::PIntegerType.new() t2s.from = t2.types.size - 1 + from t2s.to = t2.types.size - 1 + to assignable?(size_t, t2s) when Types::PStructType from = to = t2.elements.size t2s = Types::PIntegerType.new() t2s.from = from t2s.to = to assignable?(size_t, t2s) else false end end # @api private def assignable_PType(t, t2) return false unless t2.is_a?(Types::PType) return true if t.type.nil? # wide enough to handle all types return false if t2.type.nil? # wider than t assignable?(t.type, t2.type) end # Array is assignable if t2 is an Array and t2's element type is assignable, or if t2 is a Tuple # where # @api private def assignable_PArrayType(t, t2) if t2.is_a?(Types::PArrayType) return false unless assignable?(t.element_type, t2.element_type) assignable_PCollectionType(t, t2) elsif t2.is_a?(Types::PTupleType) return false unless t2.types.all? {|t2_element| assignable?(t.element_type, t2_element) } t2_regular = t2.types[0..-2] t2_ranged = t2.types[-1] t2_from, t2_to = size_range(t2.size_type) t2_required = t2_regular.size + t2_from t_entry = t.element_type # Tuple of anything can not be assigned (unless array is tuple of anything) - this case # was handled at the top of this method. # return false if t_entry.nil? # array type may be size constrained size_t = t.size_type || @collection_default_size_t min, max = size_t.range # Tuple with fewer min entries can not be assigned return false if t2_required < min # Tuple with more optionally available entries can not be assigned return false if t2_regular.size + t2_to > max # each tuple type must be assignable to the element type t2_required.times do |index| t2_entry = tuple_entry_at(t2, t2_from, t2_to, index) return false unless assignable?(t_entry, t2_entry) end # ... and so must the last, possibly optional (ranged) type return assignable?(t_entry, t2_ranged) else false end end # Hash is assignable if t2 is a Hash and t2's key and element types are assignable # @api private def assignable_PHashType(t, t2) case t2 when Types::PHashType return false unless assignable?(t.key_type, t2.key_type) && assignable?(t.element_type, t2.element_type) assignable_PCollectionType(t, t2) when Types::PStructType # hash must accept String as key type # hash must accept all value types # hash must accept the size of the struct size_t = t.size_type || @collection_default_size_t min, max = size_t.range struct_size = t2.elements.size element_type = t.element_type ( struct_size >= min && struct_size <= max && assignable?(t.key_type, @non_emptry_string_t) && t2.hashed_elements.all? {|k,v| assignable?(element_type, v) }) else false end end # @api private def assignable_PCatalogEntryType(t1, t2) t2.is_a?(Types::PCatalogEntryType) end # @api private def assignable_PHostClassType(t1, t2) return false unless t2.is_a?(Types::PHostClassType) # Class = Class[name}, Class[name] != Class return true if t1.class_name.nil? # Class[name] = Class[name] return t1.class_name == t2.class_name end # @api private def assignable_PResourceType(t1, t2) return false unless t2.is_a?(Types::PResourceType) return true if t1.type_name.nil? return false if t1.type_name != t2.type_name return true if t1.title.nil? return t1.title == t2.title end # Data is assignable by other Data and by Array[Data] and Hash[Scalar, Data] # @api private def assignable_PDataType(t, t2) t2.is_a?(Types::PDataType) || assignable?(@data_variant_t, t2) end # Assignable if t2's ruby class is same or subclass of t1's ruby class # @api private def assignable_PRubyType(t1, t2) return false unless t2.is_a?(Types::PRubyType) return true if t1.ruby_class.nil? # t1 is wider return false if t2.ruby_class.nil? # t1 not nil, so t2 can not be wider c1 = class_from_string(t1.ruby_class) c2 = class_from_string(t2.ruby_class) return false unless c1.is_a?(Class) && c2.is_a?(Class) !!(c2 <= c1) end # @api private def debug_string_Object(t) string(t) end # @api private def string_PType(t) if t.type.nil? "Type" else "Type[#{string(t.type)}]" end end # @api private def string_NilClass(t) ; '?' ; end # @api private def string_String(t) ; t ; end # @api private def string_PObjectType(t) ; "Object" ; end # @api private def string_PNilType(t) ; 'Undef' ; end # @api private def string_PBooleanType(t) ; "Boolean" ; end # @api private def string_PScalarType(t) ; "Scalar" ; end # @api private def string_PDataType(t) ; "Data" ; end # @api private def string_PNumericType(t) ; "Numeric" ; end # @api private def string_PIntegerType(t) range = range_array_part(t) unless range.empty? "Integer[#{range.join(', ')}]" else "Integer" end end # Produces a string from an Integer range type that is used inside other type strings # @api private def range_array_part(t) return [] if t.nil? || (t.from.nil? && t.to.nil?) [t.from.nil? ? 'default' : t.from , t.to.nil? ? 'default' : t.to ] end # @api private def string_PFloatType(t) range = range_array_part(t) unless range.empty? "Float[#{range.join(', ')}]" else "Float" end end # @api private def string_PRegexpType(t) t.pattern.nil? ? "Regexp" : "Regexp[#{t.regexp.inspect}]" end # @api private def string_PStringType(t) # skip values in regular output - see debug_string range = range_array_part(t.size_type) unless range.empty? "String[#{range.join(', ')}]" else "String" end end # @api private def debug_string_PStringType(t) range = range_array_part(t.size_type) range_part = range.empty? ? '' : '[' << range.join(' ,') << '], ' "String[" << range_part << (t.values.map {|s| "'#{s}'" }).join(', ') << ']' end # @api private def string_PEnumType(t) return "Enum" if t.values.empty? "Enum[" << t.values.map {|s| "'#{s}'" }.join(', ') << ']' end # @api private def string_PVariantType(t) return "Variant" if t.types.empty? "Variant[" << t.types.map {|t2| string(t2) }.join(', ') << ']' end # @api private def string_PTupleType(t) range = range_array_part(t.size_type) return "Tuple" if t.types.empty? s = "Tuple[" << t.types.map {|t2| string(t2) }.join(', ') unless range.empty? s << ", " << range.join(', ') end s << "]" s end # @api private def string_PStructType(t) return "Struct" if t.elements.empty? "Struct[{" << t.elements.map {|element| string(element) }.join(', ') << "}]" end def string_PStructElement(t) "'#{t.name}'=>#{string(t.type)}" end # @api private def string_PPatternType(t) return "Pattern" if t.patterns.empty? "Pattern[" << t.patterns.map {|s| "#{s.regexp.inspect}" }.join(', ') << ']' end # @api private def string_PCollectionType(t) range = range_array_part(t.size_type) unless range.empty? "Collection[#{range.join(', ')}]" else "Collection" end end # @api private def string_PRubyType(t) ; "Ruby[#{string(t.ruby_class)}]" ; end # @api private def string_PArrayType(t) parts = [string(t.element_type)] + range_array_part(t.size_type) "Array[#{parts.join(', ')}]" end # @api private def string_PHashType(t) parts = [string(t.key_type), string(t.element_type)] + range_array_part(t.size_type) "Hash[#{parts.join(', ')}]" end # @api private def string_PCatalogEntryType(t) "CatalogEntry" end # @api private def string_PHostClassType(t) if t.class_name "Class[#{t.class_name}]" else "Class" end end # @api private def string_PResourceType(t) if t.type_name if t.title "#{t.type_name.capitalize}['#{t.title}']" else "#{t.type_name.capitalize}" end else "Resource" end end def string_POptionalType(t) if t.optional_type.nil? "Optional" else "Optional[#{string(t.optional_type)}]" end end # Catches all non enumerable types # @api private def enumerable_Object(o) nil end # @api private def enumerable_PIntegerType(t) # Not enumerable if representing an infinite range return nil if t.size == TheInfinity t end private def class_from_string(str) begin str.split('::').inject(Object) do |memo, name_segment| memo.const_get(name_segment) end rescue NameError return nil end end def common_data?(t1, t2) assignable?(@data_t, t1) && assignable?(@data_t, t2) end def common_scalar?(t1, t2) assignable?(@scalar_t, t1) && assignable?(@scalar_t, t2) end def common_numeric?(t1, t2) assignable?(@numeric_t, t1) && assignable?(@numeric_t, t2) end def common_pobject?(t1, t2) assignable?(@t, t1) && assignable?(@t, t2) end end diff --git a/lib/puppet/pops/types/type_factory.rb b/lib/puppet/pops/types/type_factory.rb index 36dbd12d7..475366581 100644 --- a/lib/puppet/pops/types/type_factory.rb +++ b/lib/puppet/pops/types/type_factory.rb @@ -1,337 +1,339 @@ # Helper module that makes creation of type objects simpler. # @api public # module Puppet::Pops::Types::TypeFactory @type_calculator = Puppet::Pops::Types::TypeCalculator.new() Types = Puppet::Pops::Types # Produces the Integer type # @api public # def self.integer() Types::PIntegerType.new() end # Produces an Integer range type # @api public # def self.range(from, to) t = Types::PIntegerType.new() t.from = from unless (from == :default || from == 'default') t.to = to unless (to == :default || to == 'default') t end # Produces a Float range type # @api public # def self.float_range(from, to) t = Types::PFloatType.new() t.from = Float(from) unless from == :default || from.nil? t.to = Float(to) unless to == :default || to.nil? t end # Produces the Float type # @api public # def self.float() Types::PFloatType.new() end # Produces the Numeric type # @api public # def self.numeric() Types::PNumericType.new() end # Produces a string representation of the type # @api public # def self.label(t) @type_calculator.string(t) end # Produces the String type, optionally with specific string values # @api public # def self.string(*values) t = Types::PStringType.new() values.each {|v| t.addValues(v) } t end # Produces the Optional type, i.e. a short hand for Variant[T, Undef] def self.optional(optional_type = nil) t = Types::POptionalType.new t.optional_type = type_of(optional_type) t end # Produces the Enum type, optionally with specific string values # @api public # def self.enum(*values) t = Types::PEnumType.new() values.each {|v| t.addValues(v) } t end # Produces the Variant type, optionally with the "one of" types # @api public # def self.variant(*types) t = Types::PVariantType.new() types.each {|v| t.addTypes(type_of(v)) } t end # Produces the Struct type, either a non parameterized instance representing all structs (i.e. all hashes) # or a hash with a given set of keys of String type (names), bound to a value of a given type. Type may be # a Ruby Class, a Puppet Type, or an instance from which the type is inferred. # def self.struct(name_type_hash = {}) t = Types::PStructType.new name_type_hash.map do |name, type| elem = Types::PStructElement.new if name.is_a?(String) && name.empty? raise ArgumentError, "An empty String can not be used where a String[1, default] is expected" end elem.name = name elem.type = type_of(type) elem end.each {|elem| t.addElements(elem) } t end def self.tuple(*types) t = Types::PTupleType.new types.each {|elem| t.addTypes(type_of(elem)) } t end # Produces the Boolean type # @api public # def self.boolean() Types::PBooleanType.new() end # Produces the Object type # @api public # def self.object() Types::PObjectType.new() end # Produces the Regexp type # @param pattern [Regexp, String, nil] (nil) The regular expression object or a regexp source string, or nil for bare type # @api public # def self.regexp(pattern = nil) t = Types::PRegexpType.new() if pattern t.pattern = pattern.is_a?(Regexp) ? pattern.inspect[1..-2] : pattern end + t.regexp() unless pattern.nil? # compile pattern to catch errors t end def self.pattern(*regular_expressions) t = Types::PPatternType.new() regular_expressions.each do |re| case re when String re_T = Types::PRegexpType.new() re_T.pattern = re + re_T.regexp() # compile it to catch errors t.addPatterns(re_T) when Regexp re_T = Types::PRegexpType.new() # Regep.to_s includes options user did not enter and does not escape source # to work either as a string or as a // regexp. The inspect method does a better # job, but includes the // re_T.pattern = re.inspect[1..-2] t.addPatterns(re_T) when Types::PRegexpType t.addPatterns(re.copy) when Types::PPatternType re.patterns.each do |p| t.addPatterns(p.copy) end else raise ArgumentError, "Only String, Regexp, Pattern-Type, and Regexp-Type are allowed: got '#{re.class}" end end t end # Produces the Literal type # @api public # def self.scalar() Types::PScalarType.new() end # Produces the abstract type Collection # @api public # def self.collection() Types::PCollectionType.new() end # Produces the Data type # @api public # def self.data() Types::PDataType.new() end # Creates an instance of the Undef type # @api public def self.undef() Types::PNilType.new() end # Produces an instance of the abstract type PCatalogEntryType def self.catalog_entry() Types::PCatalogEntryType.new() end # Produces a PResourceType with a String type_name # A PResourceType with a nil or empty name is compatible with any other PResourceType. # A PResourceType with a given name is only compatible with a PResourceType with the same name. # (There is no resource-type subtyping in Puppet (yet)). # def self.resource(type_name = nil, title = nil) type = Types::PResourceType.new() type_name = type_name.type_name if type_name.is_a?(Types::PResourceType) type.type_name = type_name.downcase unless type_name.nil? type.title = title type end # Produces PHostClassType with a string class_name. # A PHostClassType with nil or empty name is compatible with any other PHostClassType. # A PHostClassType with a given name is only compatible with a PHostClassType with the same name. # def self.host_class(class_name = nil) type = Types::PHostClassType.new() unless class_name.nil? type.class_name = class_name.sub(/^::/, '') end type end # Produces a type for Array[o] where o is either a type, or an instance for which a type is inferred. # @api public # def self.array_of(o) type = Types::PArrayType.new() type.element_type = type_of(o) type end # Produces a type for Hash[Scalar, o] where o is either a type, or an instance for which a type is inferred. # @api public # def self.hash_of(value, key = scalar()) type = Types::PHashType.new() type.key_type = type_of(key) type.element_type = type_of(value) type end # Produces a type for Array[Data] # @api public # def self.array_of_data() type = Types::PArrayType.new() type.element_type = data() type end # Produces a type for Hash[Scalar, Data] # @api public # def self.hash_of_data() type = Types::PHashType.new() type.key_type = scalar() type.element_type = data() type end # Produces a type for Type[T] # @api public # def self.type_type(inst_type = nil) type = Types::PType.new() type.type = inst_type type end # Produce a type corresponding to the class of given unless given is a String, Class or a PAbstractType. # When a String is given this is taken as a classname. # def self.type_of(o) if o.is_a?(Class) @type_calculator.type(o) elsif o.is_a?(Types::PAbstractType) o elsif o.is_a?(String) type = Types::PRubyType.new() type.ruby_class = o type else @type_calculator.infer_generic(o) end end # Produces a type for a class or infers a type for something that is not a class # @note # To get the type for the class' class use `TypeCalculator.infer(c)` # # @overload ruby(o) # @param o [Class] produces the type corresponding to the class (e.g. Integer becomes PIntegerType) # @overload ruby(o) # @param o [Object] produces the type corresponding to the instance class (e.g. 3 becomes PIntegerType) # # @api public # def self.ruby(o) if o.is_a?(Class) @type_calculator.type(o) else type = Types::PRubyType.new() type.ruby_class = o.class.name type end end # Generic creator of a RubyType - allows creating the Ruby type with nil name, or String name. # Also see ruby(o) which performs inference, or mapps a Ruby Class to its name. # def self.ruby_type(class_name = nil) type = Types::PRubyType.new() type.ruby_class = class_name type end # Sets the accepted size range of a collection if something other than the default 0 to Infinity # is wanted. The semantics for from/to are the same as for #range # def self.constrain_size(collection_t, from, to) collection_t.size_type = range(from, to) collection_t end # Returns true if the given type t is of valid range parameter type (integer or literal default). def self.is_range_parameter?(t) t.is_a?(Integer) || t == 'default' || t == :default end end diff --git a/spec/fixtures/unit/pops/loaders/loaders/single_module/modules/modulea/functions/func_a.pp b/spec/fixtures/unit/pops/loaders/loaders/single_module/modules/modulea/functions/func_a.pp new file mode 100644 index 000000000..ef98a1d0e --- /dev/null +++ b/spec/fixtures/unit/pops/loaders/loaders/single_module/modules/modulea/functions/func_a.pp @@ -0,0 +1 @@ +function func_a() { "I am func_a()" } \ No newline at end of file diff --git a/spec/fixtures/unit/pops/loaders/loaders/single_module/modules/modulea/functions/modulea/func_a.pp b/spec/fixtures/unit/pops/loaders/loaders/single_module/modules/modulea/functions/modulea/func_a.pp new file mode 100644 index 000000000..fb4238340 --- /dev/null +++ b/spec/fixtures/unit/pops/loaders/loaders/single_module/modules/modulea/functions/modulea/func_a.pp @@ -0,0 +1 @@ +function modulea::func_a() { "I am modulea::func_a()" } \ No newline at end of file diff --git a/spec/fixtures/unit/pops/loaders/loaders/single_module/modules/modulea/lib/puppet/functions/modulea/rb_func_a.rb b/spec/fixtures/unit/pops/loaders/loaders/single_module/modules/modulea/lib/puppet/functions/modulea/rb_func_a.rb new file mode 100644 index 000000000..b83a2c1cd --- /dev/null +++ b/spec/fixtures/unit/pops/loaders/loaders/single_module/modules/modulea/lib/puppet/functions/modulea/rb_func_a.rb @@ -0,0 +1,5 @@ +Puppet::Functions.create_function(:'modulea::rb_func_a') do + def rb_func_a() + "I am modulea::rb_func_a()" + end +end \ No newline at end of file diff --git a/spec/fixtures/unit/pops/loaders/loaders/single_module/modules/modulea/lib/puppet/functions/rb_func_a.rb b/spec/fixtures/unit/pops/loaders/loaders/single_module/modules/modulea/lib/puppet/functions/rb_func_a.rb new file mode 100644 index 000000000..68439f478 --- /dev/null +++ b/spec/fixtures/unit/pops/loaders/loaders/single_module/modules/modulea/lib/puppet/functions/rb_func_a.rb @@ -0,0 +1,5 @@ +Puppet::Functions.create_function(:rb_func_a) do + def rb_func_a() + "I am rb_func_a()" + end +end \ No newline at end of file diff --git a/spec/fixtures/unit/pops/loaders/loaders/single_module/modules/modulea/manifests/init.pp b/spec/fixtures/unit/pops/loaders/loaders/single_module/modules/modulea/manifests/init.pp new file mode 100644 index 000000000..526cd08f0 --- /dev/null +++ b/spec/fixtures/unit/pops/loaders/loaders/single_module/modules/modulea/manifests/init.pp @@ -0,0 +1,3 @@ +class modulea { + +} diff --git a/spec/fixtures/unit/pops/loaders/loaders/single_module/modules/modulea/metadata.json b/spec/fixtures/unit/pops/loaders/loaders/single_module/modules/modulea/metadata.json new file mode 100644 index 000000000..c55330860 --- /dev/null +++ b/spec/fixtures/unit/pops/loaders/loaders/single_module/modules/modulea/metadata.json @@ -0,0 +1,19 @@ +{ + "name": "test-modulea", + "author": "test", + "description": "", + "license": "", + "project_page": "", + "source": "", + "summary": "", + "version": "1.0.0", + "checksums": { + "Modulefile": "55ef13d250cf6c477561becd8f2edcbe", + "functions/func_a.pp": "4a1f48920cd37c487dbf7468a5e78364", + "functions/modulea/func_a.pp": "296a3330c602b919ae063d6d6fde2460", + "lib/puppet/functions/modulea/rb_func_a.rb": "d5b3b83f77b742292008cd06361d6673", + "lib/puppet/functions/rb_func_a.rb": "a255470197127a6e512c9ab9dbd914c1", + "manifests/init.pp": "e50d8b91b632fb8357f021e6e0b6e043" + }, + "dependencies": [] +} \ No newline at end of file diff --git a/spec/lib/puppet_spec/files.rb b/spec/lib/puppet_spec/files.rb index 65b04aab9..1e1076b91 100755 --- a/spec/lib/puppet_spec/files.rb +++ b/spec/lib/puppet_spec/files.rb @@ -1,60 +1,78 @@ require 'fileutils' require 'tempfile' require 'tmpdir' require 'pathname' # A support module for testing files. module PuppetSpec::Files def self.cleanup $global_tempfiles ||= [] while path = $global_tempfiles.pop do begin Dir.unstub(:entries) FileUtils.rm_rf path, :secure => true rescue Errno::ENOENT # nothing to do end end end def make_absolute(path) PuppetSpec::Files.make_absolute(path) end def self.make_absolute(path) path = File.expand_path(path) path[0] = 'c' if Puppet.features.microsoft_windows? path end def tmpfile(name, dir = nil) PuppetSpec::Files.tmpfile(name, dir) end def self.tmpfile(name, dir = nil) # Generate a temporary file, just for the name... source = dir ? Tempfile.new(name, dir) : Tempfile.new(name) path = source.path source.close! record_tmp(File.expand_path(path)) path end def file_containing(name, contents) PuppetSpec::Files.file_containing(name, contents) end def self.file_containing(name, contents) file = tmpfile(name) File.open(file, 'wb') { |f| f.write(contents) } file end def tmpdir(name) PuppetSpec::Files.tmpdir(name) end def self.tmpdir(name) dir = Dir.mktmpdir(name) record_tmp(dir) dir end + def dir_containing(name, contents_hash) PuppetSpec::Files.dir_containing(name, contents_hash) end + def self.dir_containing(name, contents_hash) + dir_contained_in(tmpdir(name), contents_hash) + end + + def self.dir_contained_in(dir, contents_hash) + contents_hash.each do |k,v| + if v.is_a?(Hash) + Dir.mkdir(tmp = File.join(dir,k)) + dir_contained_in(tmp, v) + else + file = File.join(dir, k) + File.open(file, 'wb') {|f| f.write(v) } + end + end + dir + end + def self.record_tmp(tmp) # ...record it for cleanup, $global_tempfiles ||= [] $global_tempfiles << tmp end end diff --git a/spec/unit/functions/assert_type_spec.rb b/spec/unit/functions/assert_type_spec.rb new file mode 100644 index 000000000..7fb84d651 --- /dev/null +++ b/spec/unit/functions/assert_type_spec.rb @@ -0,0 +1,52 @@ +require 'spec_helper' +require 'puppet/pops' +require 'puppet/loaders' + +describe 'the assert_type function' do + + after(:all) { Puppet::Pops::Loaders.clear } + let(:func) do + loaders = Puppet::Pops::Loaders.new() + loaders.puppet_system_loader.load(:function, 'assert_type') + end + + it 'asserts compliant type by returning the value' do + expect(func.call({}, type(String), 'hello world')).to eql('hello world') + end + + it 'accepts type given as a String' do + expect(func.call({}, 'String', 'hello world')).to eql('hello world') + end + + it 'asserts non compliant type by raising an error' do + expect do + func.call({}, type(Integer), 'hello world') + end.to raise_error(Puppet::ParseError, /does not match actual/) + end + + it 'checks that first argument is a type' do + expect do + func.call({}, 10, 10) + end.to raise_error(ArgumentError, Regexp.new(Regexp.escape( +"function 'assert_type' called with mis-matched arguments +expected one of: + assert_type(Type type, Optional[Object] value) - arg count {2} + assert_type(String type_string, Optional[Object] value) - arg count {2} +actual: + assert_type(Integer, Integer) - arg count {2}"))) + end + + it 'allows the second arg to be undef/nil)' do + expect do + func.call({}, optional(String), nil) + end.to_not raise_error(ArgumentError) + end + + def optional(type_ref) + Puppet::Pops::Types::TypeFactory.optional(type(type_ref)) + end + + def type(type_ref) + Puppet::Pops::Types::TypeFactory.type_of(type_ref) + end +end diff --git a/spec/unit/pops/loaders/dependency_loader_spec.rb b/spec/unit/pops/loaders/dependency_loader_spec.rb new file mode 100644 index 000000000..6b84f25ab --- /dev/null +++ b/spec/unit/pops/loaders/dependency_loader_spec.rb @@ -0,0 +1,41 @@ +require 'spec_helper' +require 'puppet_spec/files' +require 'puppet/pops' +require 'puppet/loaders' + +describe 'dependency loader' do + include PuppetSpec::Files + + let(:static_loader) { Puppet::Pops::Loader::StaticLoader.new() } + + describe 'FileBased module loader' do + it 'can load something in global name space from module it depends on' do + module_dir = dir_containing('testmodule', { + 'functions' => { + 'foo.pp' => 'function foo() { yay }'}}) + + module_loader = Puppet::Pops::Loader::ModuleLoaders::FileBased.new(static_loader, 'testmodule', module_dir, 'test1') + dep_loader = Puppet::Pops::Loader::DependencyLoader.new(static_loader, 'test-dep', [module_loader]) + function = dep_loader.load_typed(typed_name(:function, 'foo')).value + expect(function.class.name).to eq('foo') + expect(function.is_a?(Puppet::Functions::Function)).to eq(true) + end + + it 'can load something in a qualified name space' do + module_dir = dir_containing('testmodule', { + 'functions' => { + 'testmodule' => { + 'foo.pp' => 'function testmodule::foo() { yay }'}}}) + + module_loader = Puppet::Pops::Loader::ModuleLoaders::FileBased.new(static_loader, 'testmodule', module_dir, 'test1') + dep_loader = Puppet::Pops::Loader::DependencyLoader.new(static_loader, 'test-dep', [module_loader]) + function = dep_loader.load_typed(typed_name(:function, 'testmodule::foo')).value + expect(function.class.name).to eq('testmodule::foo') + expect(function.is_a?(Puppet::Functions::Function)).to eq(true) + end + end + + def typed_name(type, name) + Puppet::Pops::Loader::Loader::TypedName.new(type, name) + end +end diff --git a/spec/unit/pops/loaders/loader_paths_spec.rb b/spec/unit/pops/loaders/loader_paths_spec.rb new file mode 100644 index 000000000..c17861e59 --- /dev/null +++ b/spec/unit/pops/loaders/loader_paths_spec.rb @@ -0,0 +1,74 @@ +require 'spec_helper' +require 'puppet_spec/files' +require 'puppet/pops' +require 'puppet/loaders' + +describe 'loader paths' do + include PuppetSpec::Files + before(:each) { Puppet[:biff] = true } + + let(:static_loader) { Puppet::Pops::Loader::StaticLoader.new() } + + it 'expects dir_containing to create a temp directory structure from a hash' do + module_dir = dir_containing('testmodule', { 'test.txt' => 'Hello world', 'sub' => { 'foo.txt' => 'foo'}}) + expect(File.read(File.join(module_dir, 'test.txt'))).to be_eql('Hello world') + expect(File.read(File.join(module_dir, 'sub', 'foo.txt'))).to be_eql('foo') + end + + describe 'the relative_path_for_types method' do + it 'produces paths to load in precendence order' do + module_dir = dir_containing('testmodule', { + 'functions' => {}, + 'lib' => { + 'puppet' => { + 'functions' => {}, + 'parser' => { + 'functions' => {}, + } + }}}) + # Must have a File/Path based loader to talk to + module_loader = Puppet::Pops::Loader::ModuleLoaders::FileBased.new(static_loader, 'testmodule', module_dir, 'test1') + effective_paths = Puppet::Pops::Loader::LoaderPaths.relative_paths_for_type(:function, module_loader) + expect(effective_paths.size).to be_eql(3) + # 4x + expect(effective_paths[0].generic_path).to be_eql(File.join(module_dir, 'lib', 'puppet', 'functions')) + # 3x + expect(effective_paths[1].generic_path).to be_eql(File.join(module_dir, 'lib', 'puppet','parser', 'functions')) + # .pp + expect(effective_paths[2].generic_path).to be_eql(File.join(module_dir, 'functions')) + end + + it 'module loader has smart-paths that prunes unavailable paths' do + module_dir = dir_containing('testmodule', {'functions' => {'foo.pp' => 'function foo() { yay }'} }) + # Must have a File/Path based loader to talk to + module_loader = Puppet::Pops::Loader::ModuleLoaders::FileBased.new(static_loader, 'testmodule', module_dir, 'test1') + effective_paths = module_loader.smart_paths.effective_paths(:function) + expect(effective_paths.size).to be_eql(1) + expect(effective_paths[0].generic_path).to be_eql(File.join(module_dir, 'functions')) + expect(module_loader.path_index.size).to be_eql(1) + expect(module_loader.path_index.include?(File.join(module_dir, 'functions', 'foo.pp'))).to be(true) + end + + it 'all function smart-paths produces entries if they exist' do + module_dir = dir_containing('testmodule', { + 'functions' => {'foo.pp' => 'function foo() { yay }'}, + 'lib' => { + 'puppet' => { + 'functions' => {'foo4x.rb' => 'ignored in this test'}, + 'parser' => { + 'functions' => {'foo3x.rb' => 'ignored in this test'}, + } + }}}) + # Must have a File/Path based loader to talk to + module_loader = Puppet::Pops::Loader::ModuleLoaders::FileBased.new(static_loader, 'testmodule', module_dir, 'test1') + effective_paths = module_loader.smart_paths.effective_paths(:function) + expect(effective_paths.size).to eq(3) + expect(module_loader.path_index.size).to eq(3) + path_index = module_loader.path_index + expect(path_index.include?(File.join(module_dir, 'functions', 'foo.pp'))).to eq(true) + expect(path_index.include?(File.join(module_dir, 'lib', 'puppet', 'functions', 'foo4x.rb'))).to eq(true) + expect(path_index.include?(File.join(module_dir, 'lib', 'puppet', 'parser', 'functions', 'foo3x.rb'))).to eq(true) + end + end + +end diff --git a/spec/unit/pops/loaders/loaders_spec.rb b/spec/unit/pops/loaders/loaders_spec.rb new file mode 100644 index 000000000..4e5e424bb --- /dev/null +++ b/spec/unit/pops/loaders/loaders_spec.rb @@ -0,0 +1,83 @@ +require 'spec_helper' +require 'puppet_spec/files' + +require 'puppet/pops' +require 'puppet/loaders' + +describe 'loaders' do + include PuppetSpec::Files + + def config_dir(config_name) + my_fixture(config_name) + end + + # Loaders caches the puppet_system_loader, must reset between tests + # + before(:each) { Puppet::Pops::Loaders.clear() } + + it 'creates a puppet_system loader' do + loaders = Puppet::Pops::Loaders.new() + expect(loaders.puppet_system_loader().class).to be(Puppet::Pops::Loader::ModuleLoaders::FileBased) + end + + it 'creates an environment loader' do + loaders = Puppet::Pops::Loaders.new() + # When this test is running, there is no environments dir configured, and a NullLoader is therefore used a.t.m + expect(loaders.environment_loader().class).to be(Puppet::Pops::Loader::NullLoader) + # The default name of the enironment is '*root*', and the loader should identify itself that way + expect(loaders.environment_loader().to_s).to eql("(NullLoader 'environment:*root*')") + end + + context 'when delegating 3x to 4x' do + before(:each) { Puppet[:biff] = true } + + it 'the puppet system loader can load 3x functions' do + loaders = Puppet::Pops::Loaders.new() + puppet_loader = loaders.puppet_system_loader() + function = puppet_loader.load_typed(typed_name(:function, 'sprintf')).value + expect(function.class.name).to eq('sprintf') + expect(function.is_a?(Puppet::Functions::Function)).to eq(true) + end + end + + # TODO: LOADING OF MODULES ON MODULEPATH + context 'loading from path with single module' do + before do + env = Puppet::Node::Environment.create(:'*test*', [File.join(config_dir('single_module'), 'modules')], '') + overrides = { + :current_environment => env + } + Puppet.push_context(overrides, "single-module-test-loaders") + end + + after do + Puppet.pop_context() + end + + it 'can load from a module path' do + loaders = Puppet::Pops::Loaders.new() + modulea_loader = loaders.public_loader_for_module('modulea') + expect(modulea_loader.class).to eql(Puppet::Pops::Loader::ModuleLoaders::FileBased) + + function = modulea_loader.load_typed(typed_name(:function, 'func_a')).value + expect(function.is_a?(Puppet::Functions::Function)).to eq(true) + expect(function.class.name).to eq('func_a') + + function = modulea_loader.load_typed(typed_name(:function, 'modulea::func_a')).value + expect(function.is_a?(Puppet::Functions::Function)).to eq(true) + expect(function.class.name).to eq('modulea::func_a') + + function = modulea_loader.load_typed(typed_name(:function, 'rb_func_a')).value + expect(function.is_a?(Puppet::Functions::Function)).to eq(true) + expect(function.class.name).to eq('rb_func_a') + + function = modulea_loader.load_typed(typed_name(:function, 'modulea::rb_func_a')).value + expect(function.is_a?(Puppet::Functions::Function)).to eq(true) + expect(function.class.name).to eq('modulea::rb_func_a') + end + end + + def typed_name(type, name) + Puppet::Pops::Loader::Loader::TypedName.new(type, name) + end +end \ No newline at end of file diff --git a/spec/unit/pops/loaders/module_loaders_spec.rb b/spec/unit/pops/loaders/module_loaders_spec.rb new file mode 100644 index 000000000..685162363 --- /dev/null +++ b/spec/unit/pops/loaders/module_loaders_spec.rb @@ -0,0 +1,134 @@ +require 'spec_helper' +require 'puppet_spec/files' +require 'puppet/pops' +require 'puppet/loaders' + +describe 'module loaders' do + include PuppetSpec::Files + + let(:static_loader) { Puppet::Pops::Loader::StaticLoader.new() } + + describe 'FileBased module loader' do + it 'can load a .pp function in global name space' do + module_dir = dir_containing('testmodule', { + 'functions' => { + 'foo.pp' => 'function foo() { yay }'}}) + + module_loader = Puppet::Pops::Loader::ModuleLoaders::FileBased.new(static_loader, 'testmodule', module_dir, 'test1') + function = module_loader.load_typed(typed_name(:function, 'foo')).value + expect(function.class.name).to eq('foo') + expect(function.is_a?(Puppet::Functions::Function)).to eq(true) + end + + it 'can load a .pp function in a qualified name space' do + module_dir = dir_containing('testmodule', { + 'functions' => { + 'testmodule' => { + 'foo.pp' => 'function testmodule::foo() { yay }'}}}) + + module_loader = Puppet::Pops::Loader::ModuleLoaders::FileBased.new(static_loader, 'testmodule', module_dir, 'test1') + function = module_loader.load_typed(typed_name(:function, 'testmodule::foo')).value + expect(function.class.name).to eq('testmodule::foo') + expect(function.is_a?(Puppet::Functions::Function)).to eq(true) + end + + it 'can load a 4x function API ruby function in global name space' do + module_dir = dir_containing('testmodule', { + 'lib' => { + 'puppet' => { + 'functions' => { + 'foo4x.rb' => <<-CODE + Puppet::Functions.create_function(:foo4x) do + def foo4x() + 'yay' + end + end + CODE + } + } + } + }) + + module_loader = Puppet::Pops::Loader::ModuleLoaders::FileBased.new(static_loader, 'testmodule', module_dir, 'test1') + function = module_loader.load_typed(typed_name(:function, 'foo4x')).value + expect(function.class.name).to eq('foo4x') + expect(function.is_a?(Puppet::Functions::Function)).to eq(true) + end + + it 'can load a 4x function API ruby function in qualified name space' do + module_dir = dir_containing('testmodule', { + 'lib' => { + 'puppet' => { + 'functions' => { + 'testmodule' => { + 'foo4x.rb' => <<-CODE + Puppet::Functions.create_function('testmodule::foo4x') do + def foo4x() + 'yay' + end + end + CODE + } + } + } + }}) + + module_loader = Puppet::Pops::Loader::ModuleLoaders::FileBased.new(static_loader, 'testmodule', module_dir, 'test1') + function = module_loader.load_typed(typed_name(:function, 'testmodule::foo4x')).value + expect(function.class.name).to eq('testmodule::foo4x') + expect(function.is_a?(Puppet::Functions::Function)).to eq(true) + end + + it 'makes parent loader win over entries in child' do + module_dir = dir_containing('testmodule', { + 'functions' => { + 'foo.pp' => 'function foo() { yay }'}}) + + module_loader = Puppet::Pops::Loader::ModuleLoaders::FileBased.new(static_loader, 'testmodule', module_dir, 'test1') + module_dir2 = dir_containing('testmodule2', { + 'functions' => { + 'foo.pp' => 'fail(should not happen)'}}) + + module_loader2 = Puppet::Pops::Loader::ModuleLoaders::FileBased.new(module_loader, 'testmodule2', module_dir2, 'test2') + function = module_loader2.load_typed(typed_name(:function, 'foo')).value + expect(function.class.name).to eq('foo') + expect(function.is_a?(Puppet::Functions::Function)).to eq(true) + end + + context 'when delegating 3x to 4x' do + before(:each) { Puppet[:biff] = true } + + it 'can load a 3x function API ruby function in global name space' do + module_dir = dir_containing('testmodule', { + 'lib' => { + 'puppet' => { + 'parser' => { + 'functions' => { + 'foo3x.rb' => <<-CODE + Puppet::Parser::Functions::newfunction( + :foo3x, :type => :rvalue, + :arity => 1 + ) do |args| + args[0] + end + CODE + } + } + } + }}) + + module_loader = Puppet::Pops::Loader::ModuleLoaders::FileBased.new(static_loader, 'testmodule', module_dir, 'test1') + function = module_loader.load_typed(typed_name(:function, 'foo3x')).value + expect(function.class.name).to eq('foo3x') + expect(function.is_a?(Puppet::Functions::Function)).to eq(true) + end + end + + # Gives error when loading something with mismatched name + + end + + def typed_name(type, name) + Puppet::Pops::Loader::Loader::TypedName.new(type, name) + end +end diff --git a/spec/unit/pops/loaders/static_loader_spec.rb b/spec/unit/pops/loaders/static_loader_spec.rb new file mode 100644 index 000000000..6d8f516a6 --- /dev/null +++ b/spec/unit/pops/loaders/static_loader_spec.rb @@ -0,0 +1,26 @@ +require 'spec_helper' +require 'puppet/pops' +require 'puppet/loaders' + +describe 'static loader' do + it 'has no parent' do + expect(Puppet::Pops::Loader::StaticLoader.new.parent).to be(nil) + end + + it 'identifies itself in string form' do + expect(Puppet::Pops::Loader::StaticLoader.new.to_s).to be_eql('(StaticLoader)') + end + + it 'support the Loader API' do + # it may produce things later, this is just to test that calls work as they should - now all lookups are nil. + loader = Puppet::Pops::Loader::StaticLoader.new() + a_typed_name = typed_name(:function, 'foo') + expect(loader[a_typed_name]).to be(nil) + expect(loader.load_typed(a_typed_name)).to be(nil) + expect(loader.find(a_typed_name)).to be(nil) + end + + def typed_name(type, name) + Puppet::Pops::Loader::Loader::TypedName.new(type, name) + end +end \ No newline at end of file diff --git a/spec/unit/pops/types/type_calculator_spec.rb b/spec/unit/pops/types/type_calculator_spec.rb index 37b8cfd88..e6ac7549e 100644 --- a/spec/unit/pops/types/type_calculator_spec.rb +++ b/spec/unit/pops/types/type_calculator_spec.rb @@ -1,1470 +1,1472 @@ require 'spec_helper' require 'puppet/pops' describe 'The type calculator' do let(:calculator) { Puppet::Pops::Types::TypeCalculator.new() } def range_t(from, to) t = Puppet::Pops::Types::PIntegerType.new t.from = from t.to = to t end def pattern_t(*patterns) Puppet::Pops::Types::TypeFactory.pattern(*patterns) end def regexp_t(pattern) Puppet::Pops::Types::TypeFactory.regexp(pattern) end def string_t(*strings) Puppet::Pops::Types::TypeFactory.string(*strings) end def enum_t(*strings) Puppet::Pops::Types::TypeFactory.enum(*strings) end def variant_t(*types) Puppet::Pops::Types::TypeFactory.variant(*types) end def integer_t() Puppet::Pops::Types::TypeFactory.integer() end def array_t(t) Puppet::Pops::Types::TypeFactory.array_of(t) end def hash_t(k,v) Puppet::Pops::Types::TypeFactory.hash_of(v, k) end def data_t() Puppet::Pops::Types::TypeFactory.data() end def factory() Puppet::Pops::Types::TypeFactory end def collection_t() Puppet::Pops::Types::TypeFactory.collection() end def tuple_t(*types) Puppet::Pops::Types::TypeFactory.tuple(*types) end def struct_t(type_hash) Puppet::Pops::Types::TypeFactory.struct(type_hash) end def types Puppet::Pops::Types end shared_context "types_setup" do def all_types [ Puppet::Pops::Types::PObjectType, Puppet::Pops::Types::PNilType, Puppet::Pops::Types::PDataType, Puppet::Pops::Types::PScalarType, Puppet::Pops::Types::PStringType, Puppet::Pops::Types::PNumericType, Puppet::Pops::Types::PIntegerType, Puppet::Pops::Types::PFloatType, Puppet::Pops::Types::PRegexpType, Puppet::Pops::Types::PBooleanType, Puppet::Pops::Types::PCollectionType, Puppet::Pops::Types::PArrayType, Puppet::Pops::Types::PHashType, Puppet::Pops::Types::PRubyType, Puppet::Pops::Types::PHostClassType, Puppet::Pops::Types::PResourceType, Puppet::Pops::Types::PPatternType, Puppet::Pops::Types::PEnumType, Puppet::Pops::Types::PVariantType, Puppet::Pops::Types::PStructType, Puppet::Pops::Types::PTupleType, ] end def scalar_types # PVariantType is also scalar, if its types are all Scalar [ Puppet::Pops::Types::PScalarType, Puppet::Pops::Types::PStringType, Puppet::Pops::Types::PNumericType, Puppet::Pops::Types::PIntegerType, Puppet::Pops::Types::PFloatType, Puppet::Pops::Types::PRegexpType, Puppet::Pops::Types::PBooleanType, Puppet::Pops::Types::PPatternType, Puppet::Pops::Types::PEnumType, ] end def numeric_types # PVariantType is also numeric, if its types are all numeric [ Puppet::Pops::Types::PNumericType, Puppet::Pops::Types::PIntegerType, Puppet::Pops::Types::PFloatType, ] end def string_types # PVariantType is also string type, if its types are all compatible [ Puppet::Pops::Types::PStringType, Puppet::Pops::Types::PPatternType, Puppet::Pops::Types::PEnumType, ] end def collection_types # PVariantType is also string type, if its types are all compatible [ Puppet::Pops::Types::PCollectionType, Puppet::Pops::Types::PHashType, Puppet::Pops::Types::PArrayType, Puppet::Pops::Types::PStructType, Puppet::Pops::Types::PTupleType, ] end def data_compatible_types result = scalar_types result << Puppet::Pops::Types::PDataType result << array_t(types::PDataType.new) result << types::TypeFactory.hash_of_data result << Puppet::Pops::Types::PNilType tmp = tuple_t(types::PDataType.new) result << (tmp) tmp.size_type = range_t(0, nil) result end def type_from_class(c) c.is_a?(Class) ? c.new : c end end context 'when inferring ruby' do it 'fixnum translates to PIntegerType' do calculator.infer(1).class.should == Puppet::Pops::Types::PIntegerType end it 'large fixnum (or bignum depending on architecture) translates to PIntegerType' do calculator.infer(2**33).class.should == Puppet::Pops::Types::PIntegerType end it 'float translates to PFloatType' do calculator.infer(1.3).class.should == Puppet::Pops::Types::PFloatType end it 'string translates to PStringType' do calculator.infer('foo').class.should == Puppet::Pops::Types::PStringType end it 'inferred string type knows the string value' do t = calculator.infer('foo') t.class.should == Puppet::Pops::Types::PStringType t.values.should == ['foo'] end it 'boolean true translates to PBooleanType' do calculator.infer(true).class.should == Puppet::Pops::Types::PBooleanType end it 'boolean false translates to PBooleanType' do calculator.infer(false).class.should == Puppet::Pops::Types::PBooleanType end it 'regexp translates to PRegexpType' do calculator.infer(/^a regular expression$/).class.should == Puppet::Pops::Types::PRegexpType end it 'nil translates to PNilType' do calculator.infer(nil).class.should == Puppet::Pops::Types::PNilType end it ':undef translates to PNilType' do calculator.infer(:undef).class.should == Puppet::Pops::Types::PNilType end it 'an instance of class Foo translates to PRubyType[Foo]' do class Foo end t = calculator.infer(Foo.new) t.class.should == Puppet::Pops::Types::PRubyType t.ruby_class.should == 'Foo' end context 'array' do it 'translates to PArrayType' do calculator.infer([1,2]).class.should == Puppet::Pops::Types::PArrayType end it 'with fixnum values translates to PArrayType[PIntegerType]' do calculator.infer([1,2]).element_type.class.should == Puppet::Pops::Types::PIntegerType end it 'with 32 and 64 bit integer values translates to PArrayType[PIntegerType]' do calculator.infer([1,2**33]).element_type.class.should == Puppet::Pops::Types::PIntegerType end it 'Range of integer values are computed' do t = calculator.infer([-3,0,42]).element_type t.class.should == Puppet::Pops::Types::PIntegerType t.from.should == -3 t.to.should == 42 end it "Compound string values are computed" do t = calculator.infer(['a','b', 'c']).element_type t.class.should == Puppet::Pops::Types::PStringType t.values.should == ['a', 'b', 'c'] end it 'with fixnum and float values translates to PArrayType[PNumericType]' do calculator.infer([1,2.0]).element_type.class.should == Puppet::Pops::Types::PNumericType end it 'with fixnum and string values translates to PArrayType[PScalarType]' do calculator.infer([1,'two']).element_type.class.should == Puppet::Pops::Types::PScalarType end it 'with float and string values translates to PArrayType[PScalarType]' do calculator.infer([1.0,'two']).element_type.class.should == Puppet::Pops::Types::PScalarType end it 'with fixnum, float, and string values translates to PArrayType[PScalarType]' do calculator.infer([1, 2.0,'two']).element_type.class.should == Puppet::Pops::Types::PScalarType end it 'with fixnum and regexp values translates to PArrayType[PScalarType]' do calculator.infer([1, /two/]).element_type.class.should == Puppet::Pops::Types::PScalarType end it 'with string and regexp values translates to PArrayType[PScalarType]' do calculator.infer(['one', /two/]).element_type.class.should == Puppet::Pops::Types::PScalarType end it 'with string and symbol values translates to PArrayType[PObjectType]' do calculator.infer(['one', :two]).element_type.class.should == Puppet::Pops::Types::PObjectType end it 'with fixnum and nil values translates to PArrayType[PIntegerType]' do calculator.infer([1, nil]).element_type.class.should == Puppet::Pops::Types::PIntegerType end it 'with arrays of string values translates to PArrayType[PArrayType[PStringType]]' do et = calculator.infer([['first' 'array'], ['second','array']]) et.class.should == Puppet::Pops::Types::PArrayType et = et.element_type et.class.should == Puppet::Pops::Types::PArrayType et = et.element_type et.class.should == Puppet::Pops::Types::PStringType end it 'with array of string values and array of fixnums translates to PArrayType[PArrayType[PScalarType]]' do et = calculator.infer([['first' 'array'], [1,2]]) et.class.should == Puppet::Pops::Types::PArrayType et = et.element_type et.class.should == Puppet::Pops::Types::PArrayType et = et.element_type et.class.should == Puppet::Pops::Types::PScalarType end it 'with hashes of string values translates to PArrayType[PHashType[PStringType]]' do et = calculator.infer([{:first => 'first', :second => 'second' }, {:first => 'first', :second => 'second' }]) et.class.should == Puppet::Pops::Types::PArrayType et = et.element_type et.class.should == Puppet::Pops::Types::PHashType et = et.element_type et.class.should == Puppet::Pops::Types::PStringType end it 'with hash of string values and hash of fixnums translates to PArrayType[PHashType[PScalarType]]' do et = calculator.infer([{:first => 'first', :second => 'second' }, {:first => 1, :second => 2 }]) et.class.should == Puppet::Pops::Types::PArrayType et = et.element_type et.class.should == Puppet::Pops::Types::PHashType et = et.element_type et.class.should == Puppet::Pops::Types::PScalarType end end context 'hash' do it 'translates to PHashType' do calculator.infer({:first => 1, :second => 2}).class.should == Puppet::Pops::Types::PHashType end it 'with symbolic keys translates to PHashType[PRubyType[Symbol],value]' do k = calculator.infer({:first => 1, :second => 2}).key_type k.class.should == Puppet::Pops::Types::PRubyType k.ruby_class.should == 'Symbol' end it 'with string keys translates to PHashType[PStringType,value]' do calculator.infer({'first' => 1, 'second' => 2}).key_type.class.should == Puppet::Pops::Types::PStringType end it 'with fixnum values translates to PHashType[key,PIntegerType]' do calculator.infer({:first => 1, :second => 2}).element_type.class.should == Puppet::Pops::Types::PIntegerType end end end context 'patterns' do it "constructs a PPatternType" do t = pattern_t('a(b)c') t.class.should == Puppet::Pops::Types::PPatternType t.patterns.size.should == 1 t.patterns[0].class.should == Puppet::Pops::Types::PRegexpType t.patterns[0].pattern.should == 'a(b)c' t.patterns[0].regexp.match('abc')[1].should == 'b' end it "constructs a PStringType with multiple strings" do t = string_t('a', 'b', 'c', 'abc') t.values.should == ['a', 'b', 'c', 'abc'] end end # Deal with cases not covered by computing common type context 'when computing common type' do it 'computes given resource type commonality' do r1 = Puppet::Pops::Types::PResourceType.new() r1.type_name = 'File' r2 = Puppet::Pops::Types::PResourceType.new() r2.type_name = 'File' calculator.string(calculator.common_type(r1, r2)).should == "File" r2 = Puppet::Pops::Types::PResourceType.new() r2.type_name = 'File' r2.title = '/tmp/foo' calculator.string(calculator.common_type(r1, r2)).should == "File" r1 = Puppet::Pops::Types::PResourceType.new() r1.type_name = 'File' r1.title = '/tmp/foo' calculator.string(calculator.common_type(r1, r2)).should == "File['/tmp/foo']" r1 = Puppet::Pops::Types::PResourceType.new() r1.type_name = 'File' r1.title = '/tmp/bar' calculator.string(calculator.common_type(r1, r2)).should == "File" r2 = Puppet::Pops::Types::PResourceType.new() r2.type_name = 'Package' r2.title = 'apache' calculator.string(calculator.common_type(r1, r2)).should == "Resource" end it 'computes given hostclass type commonality' do r1 = Puppet::Pops::Types::PHostClassType.new() r1.class_name = 'foo' r2 = Puppet::Pops::Types::PHostClassType.new() r2.class_name = 'foo' calculator.string(calculator.common_type(r1, r2)).should == "Class[foo]" r2 = Puppet::Pops::Types::PHostClassType.new() r2.class_name = 'bar' calculator.string(calculator.common_type(r1, r2)).should == "Class" r2 = Puppet::Pops::Types::PHostClassType.new() calculator.string(calculator.common_type(r1, r2)).should == "Class" r1 = Puppet::Pops::Types::PHostClassType.new() calculator.string(calculator.common_type(r1, r2)).should == "Class" end it 'computes pattern commonality' do t1 = pattern_t('abc') t2 = pattern_t('xyz') common_t = calculator.common_type(t1,t2) common_t.class.should == Puppet::Pops::Types::PPatternType common_t.patterns.map { |pr| pr.pattern }.should == ['abc', 'xyz'] calculator.string(common_t).should == "Pattern[/abc/, /xyz/]" end it 'computes enum commonality to value set sum' do t1 = enum_t('a', 'b', 'c') t2 = enum_t('x', 'y', 'z') common_t = calculator.common_type(t1, t2) common_t.should == enum_t('a', 'b', 'c', 'x', 'y', 'z') end it 'computed variant commonality to type union where added types are not sub-types' do a_t1 = integer_t() a_t2 = enum_t('b') v_a = variant_t(a_t1, a_t2) b_t1 = enum_t('a') v_b = variant_t(b_t1) common_t = calculator.common_type(v_a, v_b) common_t.class.should == Puppet::Pops::Types::PVariantType Set.new(common_t.types).should == Set.new([a_t1, a_t2, b_t1]) end it 'computed variant commonality to type union where added types are sub-types' do a_t1 = integer_t() a_t2 = string_t() v_a = variant_t(a_t1, a_t2) b_t1 = enum_t('a') v_b = variant_t(b_t1) common_t = calculator.common_type(v_a, v_b) common_t.class.should == Puppet::Pops::Types::PVariantType Set.new(common_t.types).should == Set.new([a_t1, a_t2]) end end context 'computes assignability' do include_context "types_setup" context "for Object, such that" do it 'all types are assignable to Object' do t = Puppet::Pops::Types::PObjectType.new() all_types.each { |t2| t2.new.should be_assignable_to(t) } end it 'Object is not assignable to anything but Object' do tested_types = all_types() - [Puppet::Pops::Types::PObjectType] t = Puppet::Pops::Types::PObjectType.new() tested_types.each { |t2| t.should_not be_assignable_to(t2.new) } end end context "for Data, such that" do it 'all scalars + array and hash are assignable to Data' do t = Puppet::Pops::Types::PDataType.new() data_compatible_types.each { |t2| type_from_class(t2).should be_assignable_to(t) } end it 'a Variant of scalar, hash, or array is assignable to Data' do t = Puppet::Pops::Types::PDataType.new() data_compatible_types.each { |t2| variant_t(type_from_class(t2)).should be_assignable_to(t) } end it 'Data is not assignable to any of its subtypes' do t = Puppet::Pops::Types::PDataType.new() types_to_test = data_compatible_types- [Puppet::Pops::Types::PDataType] types_to_test.each {|t2| t.should_not be_assignable_to(type_from_class(t2)) } end it 'Data is not assignable to a Variant of Data subtype' do t = Puppet::Pops::Types::PDataType.new() types_to_test = data_compatible_types- [Puppet::Pops::Types::PDataType] types_to_test.each { |t2| t.should_not be_assignable_to(variant_t(type_from_class(t2))) } end it 'Data is not assignable to any disjunct type' do tested_types = all_types - [Puppet::Pops::Types::PObjectType, Puppet::Pops::Types::PDataType] - scalar_types t = Puppet::Pops::Types::PDataType.new() tested_types.each {|t2| t.should_not be_assignable_to(t2.new) } end end context "for Scalar, such that" do it "all scalars are assignable to Scalar" do t = Puppet::Pops::Types::PScalarType.new() scalar_types.each {|t2| t2.new.should be_assignable_to(t) } end it 'Scalar is not assignable to any of its subtypes' do t = Puppet::Pops::Types::PScalarType.new() types_to_test = scalar_types - [Puppet::Pops::Types::PScalarType] types_to_test.each {|t2| t.should_not be_assignable_to(t2.new) } end it 'Scalar is not assignable to any disjunct type' do tested_types = all_types - [Puppet::Pops::Types::PObjectType, Puppet::Pops::Types::PDataType] - scalar_types t = Puppet::Pops::Types::PScalarType.new() tested_types.each {|t2| t.should_not be_assignable_to(t2.new) } end end context "for Numeric, such that" do it "all numerics are assignable to Numeric" do t = Puppet::Pops::Types::PNumericType.new() numeric_types.each {|t2| t2.new.should be_assignable_to(t) } end it 'Numeric is not assignable to any of its subtypes' do t = Puppet::Pops::Types::PNumericType.new() types_to_test = numeric_types - [Puppet::Pops::Types::PNumericType] types_to_test.each {|t2| t.should_not be_assignable_to(t2.new) } end it 'Numeric is not assignable to any disjunct type' do tested_types = all_types - [ Puppet::Pops::Types::PObjectType, Puppet::Pops::Types::PDataType, Puppet::Pops::Types::PScalarType, ] - numeric_types t = Puppet::Pops::Types::PNumericType.new() tested_types.each {|t2| t.should_not be_assignable_to(t2.new) } end end context "for Collection, such that" do it "all collections are assignable to Collection" do t = Puppet::Pops::Types::PCollectionType.new() collection_types.each {|t2| t2.new.should be_assignable_to(t) } end it 'Collection is not assignable to any of its subtypes' do t = Puppet::Pops::Types::PCollectionType.new() types_to_test = collection_types - [Puppet::Pops::Types::PCollectionType] types_to_test.each {|t2| t.should_not be_assignable_to(t2.new) } end it 'Collection is not assignable to any disjunct type' do tested_types = all_types - [Puppet::Pops::Types::PObjectType] - collection_types t = Puppet::Pops::Types::PCollectionType.new() tested_types.each {|t2| t.should_not be_assignable_to(t2.new) } end end context "for Array, such that" do it "Array is not assignable to non Array based Collection type" do t = Puppet::Pops::Types::PArrayType.new() tested_types = collection_types - [ Puppet::Pops::Types::PCollectionType, Puppet::Pops::Types::PArrayType, Puppet::Pops::Types::PTupleType] tested_types.each {|t2| t.should_not be_assignable_to(t2.new) } end it 'Array is not assignable to any disjunct type' do tested_types = all_types - [ Puppet::Pops::Types::PObjectType, Puppet::Pops::Types::PDataType] - collection_types t = Puppet::Pops::Types::PArrayType.new() tested_types.each {|t2| t.should_not be_assignable_to(t2.new) } end end context "for Hash, such that" do it "Hash is not assignable to any other Collection type" do t = Puppet::Pops::Types::PHashType.new() tested_types = collection_types - [ Puppet::Pops::Types::PCollectionType, Puppet::Pops::Types::PStructType, Puppet::Pops::Types::PHashType] tested_types.each {|t2| t.should_not be_assignable_to(t2.new) } end it 'Hash is not assignable to any disjunct type' do tested_types = all_types - [ Puppet::Pops::Types::PObjectType, Puppet::Pops::Types::PDataType] - collection_types t = Puppet::Pops::Types::PHashType.new() tested_types.each {|t2| t.should_not be_assignable_to(t2.new) } end end context "for Tuple, such that" do it "Tuple is not assignable to any other non Array based Collection type" do t = Puppet::Pops::Types::PTupleType.new() tested_types = collection_types - [ Puppet::Pops::Types::PCollectionType, Puppet::Pops::Types::PTupleType, Puppet::Pops::Types::PArrayType] tested_types.each {|t2| t.should_not be_assignable_to(t2.new) } end it 'Tuple is not assignable to any disjunct type' do tested_types = all_types - [ Puppet::Pops::Types::PObjectType, Puppet::Pops::Types::PDataType] - collection_types t = Puppet::Pops::Types::PTupleType.new() tested_types.each {|t2| t.should_not be_assignable_to(t2.new) } end end context "for Struct, such that" do it "Struct is not assignable to any other non Hashed based Collection type" do t = Puppet::Pops::Types::PStructType.new() tested_types = collection_types - [ Puppet::Pops::Types::PCollectionType, Puppet::Pops::Types::PStructType, Puppet::Pops::Types::PHashType] tested_types.each {|t2| t.should_not be_assignable_to(t2.new) } end it 'Struct is not assignable to any disjunct type' do tested_types = all_types - [ Puppet::Pops::Types::PObjectType, Puppet::Pops::Types::PDataType] - collection_types t = Puppet::Pops::Types::PStructType.new() tested_types.each {|t2| t.should_not be_assignable_to(t2.new) } end end it 'should recognize mapped ruby types' do { Integer => Puppet::Pops::Types::PIntegerType.new, Fixnum => Puppet::Pops::Types::PIntegerType.new, Bignum => Puppet::Pops::Types::PIntegerType.new, Float => Puppet::Pops::Types::PFloatType.new, Numeric => Puppet::Pops::Types::PNumericType.new, NilClass => Puppet::Pops::Types::PNilType.new, TrueClass => Puppet::Pops::Types::PBooleanType.new, FalseClass => Puppet::Pops::Types::PBooleanType.new, String => Puppet::Pops::Types::PStringType.new, Regexp => Puppet::Pops::Types::PRegexpType.new, Regexp => Puppet::Pops::Types::PRegexpType.new, Array => Puppet::Pops::Types::TypeFactory.array_of_data(), Hash => Puppet::Pops::Types::TypeFactory.hash_of_data() }.each do |ruby_type, puppet_type | ruby_type.should be_assignable_to(puppet_type) end end context 'when dealing with integer ranges' do it 'should accept an equal range' do calculator.assignable?(range_t(2,5), range_t(2,5)).should == true end it 'should accept an equal reverse range' do calculator.assignable?(range_t(2,5), range_t(5,2)).should == true end it 'should accept a narrower range' do calculator.assignable?(range_t(2,10), range_t(3,5)).should == true end it 'should accept a narrower reverse range' do calculator.assignable?(range_t(2,10), range_t(5,3)).should == true end it 'should reject a wider range' do calculator.assignable?(range_t(3,5), range_t(2,10)).should == false end it 'should reject a wider reverse range' do calculator.assignable?(range_t(3,5), range_t(10,2)).should == false end it 'should reject a partially overlapping range' do calculator.assignable?(range_t(3,5), range_t(2,4)).should == false calculator.assignable?(range_t(3,5), range_t(4,6)).should == false end it 'should reject a partially overlapping reverse range' do calculator.assignable?(range_t(3,5), range_t(4,2)).should == false calculator.assignable?(range_t(3,5), range_t(6,4)).should == false end end context 'when dealing with patterns' do it 'should accept a string matching a pattern' do p_t = pattern_t('abc') p_s = string_t('XabcY') calculator.assignable?(p_t, p_s).should == true end it 'should accept a regexp matching a pattern' do p_t = pattern_t(/abc/) p_s = string_t('XabcY') calculator.assignable?(p_t, p_s).should == true end it 'should accept a pattern matching a pattern' do p_t = pattern_t(pattern_t('abc')) p_s = string_t('XabcY') calculator.assignable?(p_t, p_s).should == true end it 'should accept a regexp matching a pattern' do p_t = pattern_t(regexp_t('abc')) p_s = string_t('XabcY') calculator.assignable?(p_t, p_s).should == true end it 'should accept a string matching all patterns' do p_t = pattern_t('abc', 'ab', 'c') p_s = string_t('XabcY') calculator.assignable?(p_t, p_s).should == true end it 'should accept multiple strings if they all match any patterns' do p_t = pattern_t('X', 'Y', 'abc') p_s = string_t('Xa', 'aY', 'abc') calculator.assignable?(p_t, p_s).should == true end it 'should reject a string not matching any patterns' do p_t = pattern_t('abc', 'ab', 'c') p_s = string_t('XqqqY') calculator.assignable?(p_t, p_s).should == false end it 'should reject multiple strings if not all match any patterns' do p_t = pattern_t('abc', 'ab', 'c', 'q') p_s = string_t('X', 'Y', 'Z') calculator.assignable?(p_t, p_s).should == false end it 'should accept enum matching patterns as instanceof' do enum = enum_t('XS', 'S', 'M', 'L' 'XL', 'XXL') pattern = pattern_t('S', 'M', 'L') calculator.assignable?(pattern, enum).should == true end end context 'when dealing with tuples' do it 'should accept matching tuples' do tuple1 = tuple_t(1,2) tuple2 = tuple_t(Integer,Integer) calculator.assignable?(tuple1, tuple2).should == true calculator.assignable?(tuple2, tuple1).should == true end it 'should accept matching tuples where one is more general than the other' do tuple1 = tuple_t(1,2) tuple2 = tuple_t(Numeric,Numeric) calculator.assignable?(tuple1, tuple2).should == false calculator.assignable?(tuple2, tuple1).should == true end it 'should accept ranged tuples' do tuple1 = tuple_t(1) factory.constrain_size(tuple1, 5, 5) tuple2 = tuple_t(Integer,Integer, Integer, Integer, Integer) calculator.assignable?(tuple1, tuple2).should == true calculator.assignable?(tuple2, tuple1).should == true end it 'should reject ranged tuples when ranges does not match' do tuple1 = tuple_t(1) factory.constrain_size(tuple1, 4, 5) tuple2 = tuple_t(Integer,Integer, Integer, Integer, Integer) calculator.assignable?(tuple1, tuple2).should == true calculator.assignable?(tuple2, tuple1).should == false end it 'should reject ranged tuples when ranges does not match (using infinite upper bound)' do tuple1 = tuple_t(1) factory.constrain_size(tuple1, 4, :default) tuple2 = tuple_t(Integer,Integer, Integer, Integer, Integer) calculator.assignable?(tuple1, tuple2).should == true calculator.assignable?(tuple2, tuple1).should == false end it 'should accept matching tuples with optional entries by repeating last' do tuple1 = tuple_t(1,2) factory.constrain_size(tuple1, 0, :default) tuple2 = tuple_t(Numeric,Numeric) factory.constrain_size(tuple2, 0, :default) calculator.assignable?(tuple1, tuple2).should == false calculator.assignable?(tuple2, tuple1).should == true end it 'should accept matching tuples with optional entries' do tuple1 = tuple_t(Integer, Integer, String) factory.constrain_size(tuple1, 1, 3) array2 = factory.constrain_size(array_t(Integer),2,2) calculator.assignable?(tuple1, array2).should == true factory.constrain_size(tuple1, 3, 3) calculator.assignable?(tuple1, array2).should == false end it 'should accept matching array' do tuple1 = tuple_t(1,2) array = array_t(Integer) factory.constrain_size(array, 2, 2) calculator.assignable?(tuple1, array).should == true calculator.assignable?(array, tuple1).should == true end end context 'when dealing with structs' do it 'should accept matching structs' do struct1 = struct_t({'a'=>Integer, 'b'=>Integer}) struct2 = struct_t({'a'=>Integer, 'b'=>Integer}) calculator.assignable?(struct1, struct2).should == true calculator.assignable?(struct2, struct1).should == true end it 'should accept matching structs where one is more general than the other' do struct1 = struct_t({'a'=>Integer, 'b'=>Integer}) struct2 = struct_t({'a'=>Numeric, 'b'=>Numeric}) calculator.assignable?(struct1, struct2).should == false calculator.assignable?(struct2, struct1).should == true end it 'should accept matching hash' do struct1 = struct_t({'a'=>Integer, 'b'=>Integer}) non_empty_string = string_t() non_empty_string.size_type = range_t(1, nil) hsh = hash_t(non_empty_string, Integer) factory.constrain_size(hsh, 2, 2) calculator.assignable?(struct1, hsh).should == true calculator.assignable?(hsh, struct1).should == true end end it 'should recognize ruby type inheritance' do class Foo end class Bar < Foo end fooType = calculator.infer(Foo.new) barType = calculator.infer(Bar.new) calculator.assignable?(fooType, fooType).should == true calculator.assignable?(Foo, fooType).should == true calculator.assignable?(fooType, barType).should == true calculator.assignable?(Foo, barType).should == true calculator.assignable?(barType, fooType).should == false calculator.assignable?(Bar, fooType).should == false end it "should allow host class with same name" do hc1 = Puppet::Pops::Types::TypeFactory.host_class('the_name') hc2 = Puppet::Pops::Types::TypeFactory.host_class('the_name') calculator.assignable?(hc1, hc2).should == true end it "should allow host class with name assigned to hostclass without name" do hc1 = Puppet::Pops::Types::TypeFactory.host_class() hc2 = Puppet::Pops::Types::TypeFactory.host_class('the_name') calculator.assignable?(hc1, hc2).should == true end it "should reject host classes with different names" do hc1 = Puppet::Pops::Types::TypeFactory.host_class('the_name') hc2 = Puppet::Pops::Types::TypeFactory.host_class('another_name') calculator.assignable?(hc1, hc2).should == false end it "should reject host classes without name assigned to host class with name" do hc1 = Puppet::Pops::Types::TypeFactory.host_class('the_name') hc2 = Puppet::Pops::Types::TypeFactory.host_class() calculator.assignable?(hc1, hc2).should == false end it "should allow resource with same type_name and title" do r1 = Puppet::Pops::Types::TypeFactory.resource('file', 'foo') r2 = Puppet::Pops::Types::TypeFactory.resource('file', 'foo') calculator.assignable?(r1, r2).should == true end it "should allow more specific resource assignment" do r1 = Puppet::Pops::Types::TypeFactory.resource() r2 = Puppet::Pops::Types::TypeFactory.resource('file') calculator.assignable?(r1, r2).should == true r2 = Puppet::Pops::Types::TypeFactory.resource('file', '/tmp/foo') calculator.assignable?(r1, r2).should == true r1 = Puppet::Pops::Types::TypeFactory.resource('file') calculator.assignable?(r1, r2).should == true end it "should reject less specific resource assignment" do r1 = Puppet::Pops::Types::TypeFactory.resource('file', '/tmp/foo') r2 = Puppet::Pops::Types::TypeFactory.resource('file') calculator.assignable?(r1, r2).should == false r2 = Puppet::Pops::Types::TypeFactory.resource() calculator.assignable?(r1, r2).should == false end end context 'when testing if x is instance of type t' do include_context "types_setup" it 'should consider undef to be instance of Object and NilType' do calculator.instance?(Puppet::Pops::Types::PNilType.new(), nil).should == true calculator.instance?(Puppet::Pops::Types::PObjectType.new(), nil).should == true end it 'should not consider undef to be an instance of any other type than Object and NilType and Data' do types_to_test = all_types - [ Puppet::Pops::Types::PObjectType, Puppet::Pops::Types::PNilType, Puppet::Pops::Types::PDataType] types_to_test.each {|t| calculator.instance?(t.new, nil).should == false } types_to_test.each {|t| calculator.instance?(t.new, :undef).should == false } end it 'should consider fixnum instanceof PIntegerType' do calculator.instance?(Puppet::Pops::Types::PIntegerType.new(), 1).should == true end it 'should consider fixnum instanceof Fixnum' do calculator.instance?(Fixnum, 1).should == true end it 'should consider integer in range' do range = range_t(0,10) calculator.instance?(range, 1).should == true calculator.instance?(range, 10).should == true calculator.instance?(range, -1).should == false calculator.instance?(range, 11).should == false end it 'should consider string in length range' do range = factory.constrain_size(string_t, 1,3) calculator.instance?(range, 'a').should == true calculator.instance?(range, 'abc').should == true calculator.instance?(range, '').should == false calculator.instance?(range, 'abcd').should == false end it 'should consider array in length range' do range = factory.constrain_size(array_t(integer_t), 1,3) calculator.instance?(range, [1]).should == true calculator.instance?(range, [1,2,3]).should == true calculator.instance?(range, []).should == false calculator.instance?(range, [1,2,3,4]).should == false end it 'should consider hash in length range' do range = factory.constrain_size(hash_t(integer_t, integer_t), 1,2) calculator.instance?(range, {1=>1}).should == true calculator.instance?(range, {1=>1, 2=>2}).should == true calculator.instance?(range, {}).should == false calculator.instance?(range, {1=>1, 2=>2, 3=>3}).should == false end it 'should consider collection in length range for array ' do range = factory.constrain_size(collection_t, 1,3) calculator.instance?(range, [1]).should == true calculator.instance?(range, [1,2,3]).should == true calculator.instance?(range, []).should == false calculator.instance?(range, [1,2,3,4]).should == false end it 'should consider collection in length range for hash' do range = factory.constrain_size(collection_t, 1,2) calculator.instance?(range, {1=>1}).should == true calculator.instance?(range, {1=>1, 2=>2}).should == true calculator.instance?(range, {}).should == false calculator.instance?(range, {1=>1, 2=>2, 3=>3}).should == false end it 'should consider string matching enum as instanceof' do enum = enum_t('XS', 'S', 'M', 'L', 'XL', '0') calculator.instance?(enum, 'XS').should == true calculator.instance?(enum, 'S').should == true calculator.instance?(enum, 'XXL').should == false calculator.instance?(enum, '').should == false calculator.instance?(enum, '0').should == true calculator.instance?(enum, 0).should == false end it 'should consider array[string] as instance of Array[Enum] when strings are instance of Enum' do enum = enum_t('XS', 'S', 'M', 'L', 'XL', '0') array = array_t(enum) calculator.instance?(array, ['XS', 'S', 'XL']).should == true calculator.instance?(array, ['XS', 'S', 'XXL']).should == false end it 'should consider array[mixed] as instance of Variant[mixed] when mixed types are listed in Variant' do enum = enum_t('XS', 'S', 'M', 'L', 'XL') sizes = range_t(30, 50) array = array_t(variant_t(enum, sizes)) calculator.instance?(array, ['XS', 'S', 30, 50]).should == true calculator.instance?(array, ['XS', 'S', 'XXL']).should == false calculator.instance?(array, ['XS', 'S', 29]).should == false end it 'should consider array[seq] as instance of Tuple[seq] when elements of seq are instance of' do tuple = tuple_t(Integer, String, Float) calculator.instance?(tuple, [1, 'a', 3.14]).should == true calculator.instance?(tuple, [1.2, 'a', 3.14]).should == false calculator.instance?(tuple, [1, 1, 3.14]).should == false calculator.instance?(tuple, [1, 'a', 1]).should == false end it 'should consider hash[cont] as instance of Struct[cont-t]' do struct = struct_t({'a'=>Integer, 'b'=>String, 'c'=>Float}) calculator.instance?(struct, {'a'=>1, 'b'=>'a', 'c'=>3.14}).should == true calculator.instance?(struct, {'a'=>1.2, 'b'=>'a', 'c'=>3.14}).should == false calculator.instance?(struct, {'a'=>1, 'b'=>1, 'c'=>3.14}).should == false calculator.instance?(struct, {'a'=>1, 'b'=>'a', 'c'=>1}).should == false end context 'and t is Data' do it 'undef should be considered instance of Data' do calculator.instance?(data_t, :undef).should == true end it 'other symbols should not be considered instance of Data' do calculator.instance?(data_t, :love).should == false end it 'an empty array should be considered instance of Data' do calculator.instance?(data_t, []).should == true end it 'an empty hash should be considered instance of Data' do calculator.instance?(data_t, {}).should == true end it 'a hash with nil/undef data should be considered instance of Data' do calculator.instance?(data_t, {'a' => nil}).should == true calculator.instance?(data_t, {'a' => :undef}).should == true end it 'a hash with nil/undef key should not considered instance of Data' do calculator.instance?(data_t, {nil => 10}).should == false calculator.instance?(data_t, {:undef => 10}).should == false end it 'an array with undef entries should be considered instance of Data' do calculator.instance?(data_t, [:undef]).should == true calculator.instance?(data_t, [nil]).should == true end it 'an array with undef / data entries should be considered instance of Data' do calculator.instance?(data_t, [1, :undef, 'a']).should == true calculator.instance?(data_t, [1, nil, 'a']).should == true end end end context 'when converting a ruby class' do it 'should yield \'PIntegerType\' for Integer, Fixnum, and Bignum' do [Integer,Fixnum,Bignum].each do |c| calculator.type(c).class.should == Puppet::Pops::Types::PIntegerType end end it 'should yield \'PFloatType\' for Float' do calculator.type(Float).class.should == Puppet::Pops::Types::PFloatType end it 'should yield \'PBooleanType\' for FalseClass and TrueClass' do [FalseClass,TrueClass].each do |c| calculator.type(c).class.should == Puppet::Pops::Types::PBooleanType end end it 'should yield \'PNilType\' for NilClass' do calculator.type(NilClass).class.should == Puppet::Pops::Types::PNilType end it 'should yield \'PStringType\' for String' do calculator.type(String).class.should == Puppet::Pops::Types::PStringType end it 'should yield \'PRegexpType\' for Regexp' do calculator.type(Regexp).class.should == Puppet::Pops::Types::PRegexpType end it 'should yield \'PArrayType[PDataType]\' for Array' do t = calculator.type(Array) t.class.should == Puppet::Pops::Types::PArrayType t.element_type.class.should == Puppet::Pops::Types::PDataType end it 'should yield \'PHashType[PScalarType,PDataType]\' for Hash' do t = calculator.type(Hash) t.class.should == Puppet::Pops::Types::PHashType t.key_type.class.should == Puppet::Pops::Types::PScalarType t.element_type.class.should == Puppet::Pops::Types::PDataType end end context 'when representing the type as string' do it 'should yield \'Type\' for PType' do calculator.string(Puppet::Pops::Types::PType.new()).should == 'Type' end it 'should yield \'Object\' for PObjectType' do calculator.string(Puppet::Pops::Types::PObjectType.new()).should == 'Object' end it 'should yield \'Scalar\' for PScalarType' do calculator.string(Puppet::Pops::Types::PScalarType.new()).should == 'Scalar' end it 'should yield \'Boolean\' for PBooleanType' do calculator.string(Puppet::Pops::Types::PBooleanType.new()).should == 'Boolean' end it 'should yield \'Data\' for PDataType' do calculator.string(Puppet::Pops::Types::PDataType.new()).should == 'Data' end it 'should yield \'Numeric\' for PNumericType' do calculator.string(Puppet::Pops::Types::PNumericType.new()).should == 'Numeric' end it 'should yield \'Integer\' and from/to for PIntegerType' do int_T = Puppet::Pops::Types::PIntegerType calculator.string(int_T.new()).should == 'Integer' int = int_T.new() int.from = 1 int.to = 1 calculator.string(int).should == 'Integer[1, 1]' int = int_T.new() int.from = 1 int.to = 2 calculator.string(int).should == 'Integer[1, 2]' int = int_T.new() int.from = nil int.to = 2 calculator.string(int).should == 'Integer[default, 2]' int = int_T.new() int.from = 2 int.to = nil calculator.string(int).should == 'Integer[2, default]' end it 'should yield \'Float\' for PFloatType' do calculator.string(Puppet::Pops::Types::PFloatType.new()).should == 'Float' end it 'should yield \'Regexp\' for PRegexpType' do calculator.string(Puppet::Pops::Types::PRegexpType.new()).should == 'Regexp' end it 'should yield \'Regexp[/pat/]\' for parameterized PRegexpType' do t = Puppet::Pops::Types::PRegexpType.new() t.pattern = ('a/b') calculator.string(Puppet::Pops::Types::PRegexpType.new()).should == 'Regexp' end it 'should yield \'String\' for PStringType' do calculator.string(Puppet::Pops::Types::PStringType.new()).should == 'String' end it 'should yield \'String\' for PStringType with multiple values' do calculator.string(string_t('a', 'b', 'c')).should == 'String' end it 'should yield \'String\' and from/to for PStringType' do string_T = Puppet::Pops::Types::PStringType calculator.string(factory.constrain_size(string_T.new(), 1,1)).should == 'String[1, 1]' calculator.string(factory.constrain_size(string_T.new(), 1,2)).should == 'String[1, 2]' calculator.string(factory.constrain_size(string_T.new(), :default, 2)).should == 'String[default, 2]' calculator.string(factory.constrain_size(string_T.new(), 2, :default)).should == 'String[2, default]' end it 'should yield \'Array[Integer]\' for PArrayType[PIntegerType]' do t = Puppet::Pops::Types::PArrayType.new() t.element_type = Puppet::Pops::Types::PIntegerType.new() calculator.string(t).should == 'Array[Integer]' end it 'should yield \'Collection\' and from/to for PCollectionType' do col = collection_t() calculator.string(factory.constrain_size(col.copy, 1,1)).should == 'Collection[1, 1]' calculator.string(factory.constrain_size(col.copy, 1,2)).should == 'Collection[1, 2]' calculator.string(factory.constrain_size(col.copy, :default, 2)).should == 'Collection[default, 2]' calculator.string(factory.constrain_size(col.copy, 2, :default)).should == 'Collection[2, default]' end it 'should yield \'Array\' and from/to for PArrayType' do arr = array_t(string_t) calculator.string(factory.constrain_size(arr.copy, 1,1)).should == 'Array[String, 1, 1]' calculator.string(factory.constrain_size(arr.copy, 1,2)).should == 'Array[String, 1, 2]' calculator.string(factory.constrain_size(arr.copy, :default, 2)).should == 'Array[String, default, 2]' calculator.string(factory.constrain_size(arr.copy, 2, :default)).should == 'Array[String, 2, default]' end it 'should yield \'Tuple[Integer]\' for PTupleType[PIntegerType]' do t = Puppet::Pops::Types::PTupleType.new() t.addTypes(Puppet::Pops::Types::PIntegerType.new()) calculator.string(t).should == 'Tuple[Integer]' end it 'should yield \'Tuple[T, T,..]\' for PTupleType[T, T, ...]' do t = Puppet::Pops::Types::PTupleType.new() t.addTypes(Puppet::Pops::Types::PIntegerType.new()) t.addTypes(Puppet::Pops::Types::PIntegerType.new()) t.addTypes(Puppet::Pops::Types::PStringType.new()) calculator.string(t).should == 'Tuple[Integer, Integer, String]' end it 'should yield \'Tuple\' and from/to for PTupleType' do tuple_t = tuple_t(string_t) calculator.string(factory.constrain_size(tuple_t.copy, 1,1)).should == 'Tuple[String, 1, 1]' calculator.string(factory.constrain_size(tuple_t.copy, 1,2)).should == 'Tuple[String, 1, 2]' calculator.string(factory.constrain_size(tuple_t.copy, :default, 2)).should == 'Tuple[String, default, 2]' calculator.string(factory.constrain_size(tuple_t.copy, 2, :default)).should == 'Tuple[String, 2, default]' end it 'should yield \'Struct\' and details for PStructType' do struct_t = struct_t({'a'=>Integer, 'b'=>String}) s = calculator.string(struct_t) # Ruby 1.8.7 - noone likes you... (s == "Struct[{'a'=>Integer, 'b'=>String}]" || s == "Struct[{'b'=>String, 'a'=>Integer}]").should == true struct_t = struct_t({}) calculator.string(struct_t).should == "Struct" end it 'should yield \'Hash[String, Integer]\' for PHashType[PStringType, PIntegerType]' do t = Puppet::Pops::Types::PHashType.new() t.key_type = Puppet::Pops::Types::PStringType.new() t.element_type = Puppet::Pops::Types::PIntegerType.new() calculator.string(t).should == 'Hash[String, Integer]' end it 'should yield \'Hash\' and from/to for PHashType' do hsh = hash_t(string_t, string_t) calculator.string(factory.constrain_size(hsh.copy, 1,1)).should == 'Hash[String, String, 1, 1]' calculator.string(factory.constrain_size(hsh.copy, 1,2)).should == 'Hash[String, String, 1, 2]' calculator.string(factory.constrain_size(hsh.copy, :default, 2)).should == 'Hash[String, String, default, 2]' calculator.string(factory.constrain_size(hsh.copy, 2, :default)).should == 'Hash[String, String, 2, default]' end it "should yield 'Class' for a PHostClassType" do t = Puppet::Pops::Types::PHostClassType.new() calculator.string(t).should == 'Class' end it "should yield 'Class[x]' for a PHostClassType[x]" do t = Puppet::Pops::Types::PHostClassType.new() t.class_name = 'x' calculator.string(t).should == 'Class[x]' end it "should yield 'Resource' for a PResourceType" do t = Puppet::Pops::Types::PResourceType.new() calculator.string(t).should == 'Resource' end it 'should yield \'File\' for a PResourceType[\'File\']' do t = Puppet::Pops::Types::PResourceType.new() t.type_name = 'File' calculator.string(t).should == 'File' end it "should yield 'File['/tmp/foo']' for a PResourceType['File', '/tmp/foo']" do t = Puppet::Pops::Types::PResourceType.new() t.type_name = 'File' t.title = '/tmp/foo' calculator.string(t).should == "File['/tmp/foo']" end it "should yield 'Enum[s,...]' for a PEnumType[s,...]" do t = enum_t('a', 'b', 'c') calculator.string(t).should == "Enum['a', 'b', 'c']" end it "should yield 'Pattern[/pat/,...]' for a PPatternType['pat',...]" do t = pattern_t('a') t2 = pattern_t('a', 'b', 'c') calculator.string(t).should == "Pattern[/a/]" calculator.string(t2).should == "Pattern[/a/, /b/, /c/]" end it "should escape special characters in the string for a PPatternType['pat',...]" do t = pattern_t('a/b') calculator.string(t).should == "Pattern[/a\\/b/]" end it "should yield 'Variant[t1,t2,...]' for a PVariantType[t1, t2,...]" do t1 = string_t() t2 = integer_t() t3 = pattern_t('a') t = variant_t(t1, t2, t3) calculator.string(t).should == "Variant[String, Integer, Pattern[/a/]]" end end context 'when processing meta type' do it 'should infer PType as the type of all other types' do ptype = Puppet::Pops::Types::PType calculator.infer(Puppet::Pops::Types::PNilType.new() ).is_a?(ptype).should() == true calculator.infer(Puppet::Pops::Types::PDataType.new() ).is_a?(ptype).should() == true calculator.infer(Puppet::Pops::Types::PScalarType.new() ).is_a?(ptype).should() == true calculator.infer(Puppet::Pops::Types::PStringType.new() ).is_a?(ptype).should() == true calculator.infer(Puppet::Pops::Types::PNumericType.new() ).is_a?(ptype).should() == true calculator.infer(Puppet::Pops::Types::PIntegerType.new() ).is_a?(ptype).should() == true calculator.infer(Puppet::Pops::Types::PFloatType.new() ).is_a?(ptype).should() == true calculator.infer(Puppet::Pops::Types::PRegexpType.new() ).is_a?(ptype).should() == true calculator.infer(Puppet::Pops::Types::PBooleanType.new() ).is_a?(ptype).should() == true calculator.infer(Puppet::Pops::Types::PCollectionType.new()).is_a?(ptype).should() == true calculator.infer(Puppet::Pops::Types::PArrayType.new() ).is_a?(ptype).should() == true calculator.infer(Puppet::Pops::Types::PHashType.new() ).is_a?(ptype).should() == true calculator.infer(Puppet::Pops::Types::PRubyType.new() ).is_a?(ptype).should() == true calculator.infer(Puppet::Pops::Types::PHostClassType.new() ).is_a?(ptype).should() == true calculator.infer(Puppet::Pops::Types::PResourceType.new() ).is_a?(ptype).should() == true calculator.infer(Puppet::Pops::Types::PEnumType.new() ).is_a?(ptype).should() == true calculator.infer(Puppet::Pops::Types::PPatternType.new() ).is_a?(ptype).should() == true calculator.infer(Puppet::Pops::Types::PVariantType.new() ).is_a?(ptype).should() == true calculator.infer(Puppet::Pops::Types::PTupleType.new() ).is_a?(ptype).should() == true + calculator.infer(Puppet::Pops::Types::POptionalType.new() ).is_a?(ptype).should() == true end it 'should infer PType as the type of all other types' do ptype = Puppet::Pops::Types::PType calculator.string(calculator.infer(Puppet::Pops::Types::PNilType.new() )).should == "Type[Undef]" calculator.string(calculator.infer(Puppet::Pops::Types::PDataType.new() )).should == "Type[Data]" calculator.string(calculator.infer(Puppet::Pops::Types::PScalarType.new() )).should == "Type[Scalar]" calculator.string(calculator.infer(Puppet::Pops::Types::PStringType.new() )).should == "Type[String]" calculator.string(calculator.infer(Puppet::Pops::Types::PNumericType.new() )).should == "Type[Numeric]" calculator.string(calculator.infer(Puppet::Pops::Types::PIntegerType.new() )).should == "Type[Integer]" calculator.string(calculator.infer(Puppet::Pops::Types::PFloatType.new() )).should == "Type[Float]" calculator.string(calculator.infer(Puppet::Pops::Types::PRegexpType.new() )).should == "Type[Regexp]" calculator.string(calculator.infer(Puppet::Pops::Types::PBooleanType.new() )).should == "Type[Boolean]" calculator.string(calculator.infer(Puppet::Pops::Types::PCollectionType.new())).should == "Type[Collection]" calculator.string(calculator.infer(Puppet::Pops::Types::PArrayType.new() )).should == "Type[Array[?]]" calculator.string(calculator.infer(Puppet::Pops::Types::PHashType.new() )).should == "Type[Hash[?, ?]]" calculator.string(calculator.infer(Puppet::Pops::Types::PRubyType.new() )).should == "Type[Ruby[?]]" calculator.string(calculator.infer(Puppet::Pops::Types::PHostClassType.new() )).should == "Type[Class]" calculator.string(calculator.infer(Puppet::Pops::Types::PResourceType.new() )).should == "Type[Resource]" calculator.string(calculator.infer(Puppet::Pops::Types::PEnumType.new() )).should == "Type[Enum]" calculator.string(calculator.infer(Puppet::Pops::Types::PVariantType.new() )).should == "Type[Variant]" calculator.string(calculator.infer(Puppet::Pops::Types::PPatternType.new() )).should == "Type[Pattern]" calculator.string(calculator.infer(Puppet::Pops::Types::PTupleType.new() )).should == "Type[Tuple]" + calculator.string(calculator.infer(Puppet::Pops::Types::POptionalType.new() )).should == "Type[Optional]" end it "computes the common type of PType's type parameter" do int_t = Puppet::Pops::Types::PIntegerType.new() string_t = Puppet::Pops::Types::PStringType.new() calculator.string(calculator.infer([int_t])).should == "Array[Type[Integer], 1, 1]" calculator.string(calculator.infer([int_t, string_t])).should == "Array[Type[Scalar], 2, 2]" end it 'should infer PType as the type of ruby classes' do class Foo end [Object, Numeric, Integer, Fixnum, Bignum, Float, String, Regexp, Array, Hash, Foo].each do |c| calculator.infer(c).is_a?(Puppet::Pops::Types::PType).should() == true end end it 'should infer PType as the type of PType (meta regression short-circuit)' do calculator.infer(Puppet::Pops::Types::PType.new()).is_a?(Puppet::Pops::Types::PType).should() == true end it 'computes instance? to be true if parameterized and type match' do int_t = Puppet::Pops::Types::PIntegerType.new() type_t = Puppet::Pops::Types::TypeFactory.type_type(int_t) type_type_t = Puppet::Pops::Types::TypeFactory.type_type(type_t) calculator.instance?(type_type_t, type_t).should == true end it 'computes instance? to be false if parameterized and type do not match' do int_t = Puppet::Pops::Types::PIntegerType.new() string_t = Puppet::Pops::Types::PStringType.new() type_t = Puppet::Pops::Types::TypeFactory.type_type(int_t) type_t2 = Puppet::Pops::Types::TypeFactory.type_type(string_t) type_type_t = Puppet::Pops::Types::TypeFactory.type_type(type_t) # i.e. Type[Integer] =~ Type[Type[Integer]] # false calculator.instance?(type_type_t, type_t2).should == false end it 'computes instance? to be true if unparameterized and matched against a type[?]' do int_t = Puppet::Pops::Types::PIntegerType.new() type_t = Puppet::Pops::Types::TypeFactory.type_type(int_t) calculator.instance?(Puppet::Pops::Types::PType.new, type_t).should == true end end context "when asking for an enumerable " do it "should produce an enumerable for an Integer range that is not infinite" do t = Puppet::Pops::Types::PIntegerType.new() t.from = 1 t.to = 10 calculator.enumerable(t).respond_to?(:each).should == true end it "should not produce an enumerable for an Integer range that has an infinite side" do t = Puppet::Pops::Types::PIntegerType.new() t.from = nil t.to = 10 calculator.enumerable(t).should == nil t = Puppet::Pops::Types::PIntegerType.new() t.from = 1 t.to = nil calculator.enumerable(t).should == nil end it "all but Integer range are not enumerable" do [Object, Numeric, Float, String, Regexp, Array, Hash].each do |t| calculator.enumerable(calculator.type(t)).should == nil end end end context "when dealing with different types of inference" do it "an instance specific inference is produced by infer" do calculator.infer(['a','b']).element_type.values.should == ['a', 'b'] end it "a generic inference is produced using infer_generic" do calculator.infer_generic(['a','b']).element_type.values.should == [] end it "a generic result is created by generalize! given an instance specific result for an Array" do generic = calculator.infer(['a','b']) generic.element_type.values.should == ['a', 'b'] calculator.generalize!(generic) generic.element_type.values.should == [] end it "a generic result is created by generalize! given an instance specific result for a Hash" do generic = calculator.infer({'a' =>1,'b' => 2}) generic.key_type.values.sort.should == ['a', 'b'] generic.element_type.from.should == 1 generic.element_type.to.should == 2 calculator.generalize!(generic) generic.key_type.values.should == [] generic.element_type.from.should == nil generic.element_type.to.should == nil end it "does not reduce by combining types when using infer_set" do element_type = calculator.infer(['a','b',1,2]).element_type element_type.class.should == Puppet::Pops::Types::PScalarType inferred_type = calculator.infer_set(['a','b',1,2]) inferred_type.class.should == Puppet::Pops::Types::PTupleType element_types = inferred_type.types element_types[0].class.should == Puppet::Pops::Types::PStringType element_types[1].class.should == Puppet::Pops::Types::PStringType element_types[2].class.should == Puppet::Pops::Types::PIntegerType element_types[3].class.should == Puppet::Pops::Types::PIntegerType end it "does not reduce by combining types when using infer_set and values are undef" do element_type = calculator.infer(['a',nil]).element_type element_type.class.should == Puppet::Pops::Types::PStringType inferred_type = calculator.infer_set(['a',nil]) inferred_type.class.should == Puppet::Pops::Types::PTupleType element_types = inferred_type.types element_types[0].class.should == Puppet::Pops::Types::PStringType element_types[1].class.should == Puppet::Pops::Types::PNilType end end matcher :be_assignable_to do |type| calc = Puppet::Pops::Types::TypeCalculator.new match do |actual| calc.assignable?(type, actual) end failure_message_for_should do |actual| "#{calc.string(actual)} should be assignable to #{calc.string(type)}" end failure_message_for_should_not do |actual| "#{calc.string(actual)} is assignable to #{calc.string(type)} when it should not" end end end