diff --git a/lib/puppet/defaults.rb b/lib/puppet/defaults.rb index 9b9502f06..5f716f163 100644 --- a/lib/puppet/defaults.rb +++ b/lib/puppet/defaults.rb @@ -1,1766 +1,1773 @@ 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", } ) 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." }, :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', '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 hiera-2 and data 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' 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[:storeconfigs] = true # But then we modify the configuration Puppet::Resource::Catalog.indirection.cache_class = :queue Puppet.settings[: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[: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.", }, :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.", } ) 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, :owner => "service", :desc => "The certificate directory." }, :ssldir => { :default => "$confdir/ssl", :type => :directory, :mode => 0771, :owner => "service", :desc => "Where SSL certificates are kept." }, :publickeydir => { :default => "$ssldir/public_keys", :type => :directory, :owner => "service", :desc => "The public key directory." }, :requestdir => { :default => "$ssldir/certificate_requests", :type => :directory, :owner => "service", :desc => "Where host certificate requests are stored." }, :privatekeydir => { :default => "$ssldir/private_keys", :type => :directory, :mode => 0750, :owner => "service", :desc => "The private key directory." }, :privatedir => { :default => "$ssldir/private", :type => :directory, :mode => 0750, :owner => "service", :desc => "Where the client stores private certificate information." }, :passfile => { :default => "$privatedir/password", :type => :file, :mode => 0640, :owner => "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", :desc => "Where individual hosts store and look for their certificate requests." }, :hostcert => { :default => "$certdir/$certname.pem", :type => :file, :mode => 0644, :owner => "service", :desc => "Where individual hosts store and look for their certificates." }, :hostprivkey => { :default => "$privatekeydir/$certname.pem", :type => :file, :mode => 0600, :owner => "service", :desc => "Where individual hosts store and look for their private key." }, :hostpubkey => { :default => "$publickeydir/$certname.pem", :type => :file, :mode => 0644, :owner => "service", :desc => "Where individual hosts store and look for their public key." }, :localcacert => { :default => "$certdir/ca.pem", :type => :file, :mode => 0644, :owner => "service", :desc => "Where each client stores the CA certificate." }, :ssl_client_ca_auth => { :type => :file, :mode => 0644, :owner => "service", :desc => "Certificate authorities who issue server certificates. SSL servers will not be considered authentic unless they posses 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", :desc => "Certificate authorities who issue client certificates. SSL clients will not be considered authentic unless they posses 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", :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 => 0770, :desc => "The root directory for the certificate authority." }, :cacert => { :default => "$cadir/ca_crt.pem", :type => :file, :owner => "service", :group => "service", :mode => 0660, :desc => "The CA certificate." }, :cakey => { :default => "$cadir/ca_key.pem", :type => :file, :owner => "service", :group => "service", :mode => 0660, :desc => "The CA private key." }, :capub => { :default => "$cadir/ca_pub.pem", :type => :file, :owner => "service", :group => "service", :desc => "The CA public key." }, :cacrl => { :default => "$cadir/ca_crl.pem", :type => :file, :owner => "service", :group => "service", :mode => 0664, :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 => 0770, :desc => "Where the CA stores private certificate information." }, :csrdir => { :default => "$cadir/requests", :type => :directory, :owner => "service", :group => "service", :desc => "Where the CA stores certificate requests" }, :signeddir => { :default => "$cadir/signed", :type => :directory, :owner => "service", :group => "service", :mode => 0770, :desc => "Where the CA stores signed certificates." }, :capass => { :default => "$caprivatedir/ca.pass", :type => :file, :owner => "service", :group => "service", :mode => 0660, :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. If this setting is set, ca_days is ignored. #{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 => "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.", }, :manifest => { :default => "$manifestdir/site.pp", :type => :file, :desc => "The entry-point manifest for puppet master.", }, :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.", }, :modulepath => { :default => "$confdir/modules#{File::PATH_SEPARATOR}/usr/share/puppet/modules", :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 ';'.)", }, :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 reports to generate. All reports are looked for in `puppet/reports/name.rb`, and multiple report names should be comma-separated (whitespace is okay).", }, :reportdir => { :default => "$vardir/reports", :type => :directory, :mode => 0750, :owner => "service", :group => "service", :desc => "The directory in which to store reports received from the client. Each client gets a separate subdirectory."}, :reporturl => { :default => "http://localhost:3000/reports/upload", :desc => "The URL used by the http reports processor to send reports", }, :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 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 puppet agent should be run in noop mode.", }, :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[: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 { |value| Facter.search(value) if Facter.respond_to?(:search) }} ) 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[: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.", }, :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 fatures are will vary from release to release and they may be invididually configurable. Available Since Puppet 3.2. 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/error.rb b/lib/puppet/error.rb index 34da17219..c31c360a2 100644 --- a/lib/puppet/error.rb +++ b/lib/puppet/error.rb @@ -1,61 +1,62 @@ module Puppet # The base class for all Puppet errors. It can wrap another exception class Error < RuntimeError attr_accessor :original def initialize(message, original=nil) super(message) @original = original end end module ExternalFileError # This module implements logging with a filename and line number. Use this # for errors that need to report a location in a non-ruby file that we # parse. attr_accessor :line, :file, :pos # May be called with 3 arguments for message, file, line, and exception, or # 4 args including the position on the line. # def initialize(message, file=nil, line=nil, pos=nil, original=nil) if pos.kind_of? Exception original = pos pos = nil end super(message, original) - @file = file + @file = file unless (file.is_a?(String) && file.empty?) @line = line @pos = pos end def to_s msg = super + @file = nil if (@file.is_a?(String) && @file.empty?) if @file and @line and @pos "#{msg} at #{@file}:#{@line}:#{@pos}" elsif @file and @line "#{msg} at #{@file}:#{@line}" elsif @line and @pos "#{msg} at line #{@line}:#{@pos}" elsif @line "#{msg} at line #{@line}" elsif @file "#{msg} in #{@file}" else msg end end end class ParseError < Puppet::Error include ExternalFileError end class ResourceError < Puppet::Error include ExternalFileError end # An error class for when I don't know what happened. Automatically # prints a stack trace when in debug mode. class DevError < Puppet::Error include ExternalFileError end end diff --git a/lib/puppet/parser/ast.rb b/lib/puppet/parser/ast.rb index 438cb49b4..4cb1f3705 100644 --- a/lib/puppet/parser/ast.rb +++ b/lib/puppet/parser/ast.rb @@ -1,130 +1,131 @@ # the parent class for all of our syntactical objects require 'puppet' require 'puppet/util/autoload' # The base class for all of the objects that make up the parse trees. # Handles things like file name, line #, and also does the initialization # for all of the parameters of all of the child objects. class Puppet::Parser::AST # Do this so I don't have to type the full path in all of the subclasses AST = Puppet::Parser::AST include Puppet::Util::Errors include Puppet::Util::MethodHelper include Puppet::Util::Docs attr_accessor :parent, :scope, :file, :line, :pos def inspect "( #{self.class} #{self.to_s} #{@children.inspect} )" end # don't fetch lexer comment by default def use_docs self.class.use_docs end # allow our subclass to specify they want documentation class << self attr_accessor :use_docs def associates_doc self.use_docs = true end end # Evaluate the current object. Just a stub method, since the subclass # should override this method. def evaluate(*options) raise Puppet::DevError, "Did not override #evaluate in #{self.class}" end # Throw a parse error. def parsefail(message) self.fail(Puppet::ParseError, message) end # Wrap a statemp in a reusable way so we always throw a parse error. def parsewrap exceptwrap :type => Puppet::ParseError do yield end end # The version of the evaluate method that should be called, because it # correctly handles errors. It is critical to use this method because # it can enable you to catch the error where it happens, rather than # much higher up the stack. def safeevaluate(*options) # We duplicate code here, rather than using exceptwrap, because this # is called so many times during parsing. begin return self.evaluate(*options) rescue Puppet::Error => detail raise adderrorcontext(detail) rescue => detail error = Puppet::ParseError.new(detail.to_s, nil, nil, detail) # We can't use self.fail here because it always expects strings, # not exceptions. raise adderrorcontext(error, detail) end end # Initialize the object. Requires a hash as the argument, and # takes each of the parameters of the hash and calls the settor # method for them. This is probably pretty inefficient and should # likely be changed at some point. def initialize(args) set_options(args) end # evaluate ourselves, and match def evaluate_match(value, scope) obj = self.safeevaluate(scope) obj = obj.downcase if obj.respond_to?(:downcase) value = value.downcase if value.respond_to?(:downcase) obj = Puppet::Parser::Scope.number?(obj) || obj value = Puppet::Parser::Scope.number?(value) || value # "" == undef for case/selector/if obj == value or (obj == "" and value == :undef) or (obj == :undef and value == "") end end # And include all of the AST subclasses. require 'puppet/parser/ast/arithmetic_operator' require 'puppet/parser/ast/astarray' require 'puppet/parser/ast/asthash' require 'puppet/parser/ast/boolean_operator' require 'puppet/parser/ast/branch' require 'puppet/parser/ast/caseopt' require 'puppet/parser/ast/casestatement' require 'puppet/parser/ast/collection' require 'puppet/parser/ast/collexpr' require 'puppet/parser/ast/comparison_operator' require 'puppet/parser/ast/definition' require 'puppet/parser/ast/else' require 'puppet/parser/ast/function' require 'puppet/parser/ast/hostclass' require 'puppet/parser/ast/ifstatement' require 'puppet/parser/ast/in_operator' require 'puppet/parser/ast/lambda' require 'puppet/parser/ast/leaf' require 'puppet/parser/ast/match_operator' require 'puppet/parser/ast/method_call' require 'puppet/parser/ast/minus' require 'puppet/parser/ast/node' require 'puppet/parser/ast/nop' require 'puppet/parser/ast/not' require 'puppet/parser/ast/relationship' require 'puppet/parser/ast/resource' require 'puppet/parser/ast/resource_defaults' require 'puppet/parser/ast/resource_instance' require 'puppet/parser/ast/resource_override' require 'puppet/parser/ast/resource_reference' require 'puppet/parser/ast/resourceparam' require 'puppet/parser/ast/selector' require 'puppet/parser/ast/tag' require 'puppet/parser/ast/vardef' +require 'puppet/parser/ast/pops_bridge' diff --git a/lib/puppet/parser/ast/collexpr.rb b/lib/puppet/parser/ast/collexpr.rb index 6032094ab..9f266ed47 100644 --- a/lib/puppet/parser/ast/collexpr.rb +++ b/lib/puppet/parser/ast/collexpr.rb @@ -1,57 +1,109 @@ require 'puppet' require 'puppet/parser/ast/branch' require 'puppet/parser/collector' # An object that collects stored objects from the central cache and returns # them to the current host, yo. class Puppet::Parser::AST class CollExpr < AST::Branch attr_accessor :test1, :test2, :oper, :form, :type, :parens - # We return an object that does a late-binding evaluation. def evaluate(scope) + if Puppet[:parser] == 'future' + evaluate4x(scope) + else + evaluate3x(scope) + end + end + + # We return an object that does a late-binding evaluation. + def evaluate3x(scope) # Make sure our contained expressions have all the info they need. [@test1, @test2].each do |t| if t.is_a?(self.class) t.form ||= self.form t.type ||= self.type end end # The code is only used for virtual lookups match1, code1 = @test1.safeevaluate scope match2, code2 = @test2.safeevaluate scope # First build up the virtual code. # If we're a conjunction operator, then we're calling code. I did # some speed comparisons, and it's at least twice as fast doing these # case statements as doing an eval here. code = proc do |resource| case @oper when "and"; code1.call(resource) and code2.call(resource) when "or"; code1.call(resource) or code2.call(resource) when "==" if match1 == "tag" resource.tagged?(match2) else if resource[match1].is_a?(Array) resource[match1].include?(match2) else resource[match1] == match2 end end when "!="; resource[match1] != match2 end end match = [match1, @oper, match2] return match, code end + # Late binding evaluation of a collect expression (as done in 3x), but with proper Puppet Langauge + # semantics for equals and include + # + def evaluate4x(scope) + # Make sure our contained expressions have all the info they need. + [@test1, @test2].each do |t| + if t.is_a?(self.class) + t.form ||= self.form + t.type ||= self.type + end + end + + # The code is only used for virtual lookups + match1, code1 = @test1.safeevaluate scope + match2, code2 = @test2.safeevaluate scope + + # First build up the virtual code. + # If we're a conjunction operator, then we're calling code. I did + # some speed comparisons, and it's at least twice as fast doing these + # case statements as doing an eval here. + code = proc do |resource| + case @oper + when "and"; code1.call(resource) and code2.call(resource) + when "or"; code1.call(resource) or code2.call(resource) + when "==" + if match1 == "tag" + resource.tagged?(match2) + else + if resource[match1].is_a?(Array) + @@compare_operator.include?(resource[match1], match2) + else + @@compare_operator.equals(resource[match1], match2) + end + end + when "!="; ! @@compare_operator.equals(resource[match1], match2) + end + end + + match = [match1, @oper, match2] + return match, code + end + def initialize(hash = {}) super - + if Puppet[:parser] == "future" + @@compare_operator ||= Puppet::Pops::Evaluator::CompareOperator.new + end raise ArgumentError, "Invalid operator #{@oper}" unless %w{== != and or}.include?(@oper) end end end diff --git a/lib/puppet/parser/ast/lambda.rb b/lib/puppet/parser/ast/lambda.rb index 976f817cd..74ffa710c 100644 --- a/lib/puppet/parser/ast/lambda.rb +++ b/lib/puppet/parser/ast/lambda.rb @@ -1,126 +1,131 @@ require 'puppet/parser/ast/block_expression' class Puppet::Parser::AST # A block of statements/expressions with additional parameters # Requires scope to contain the values for the defined parameters when evaluated # If evaluated without a prepared scope, the lambda will behave like its super class. # class Lambda < AST::BlockExpression # The lambda parameters. # These are encoded as an array where each entry is an array of one or two object. The first # is the parameter name, and the optional second object is the value expression (that will # be evaluated when bound to a scope). # The value expression is the default value for the parameter. All default values must be # at the end of the parameter list. # # @return [Array>] list of parameter names with optional value expression attr_accessor :parameters # Evaluates each expression/statement and produce the last expression evaluation result # @return [Object] what the last expression evaluated to def evaluate(scope) if @children.is_a? Puppet::Parser::AST::ASTArray result = nil @children.each {|expr| result = expr.evaluate(scope) } result else @children.evaluate(scope) end end # Calls the lambda. # Assigns argument values in a nested local scope that should be used to evaluate the lambda # and then evaluates the lambda. # @param scope [Puppet::Scope] the calling scope # @return [Object] the result of evaluating the expression(s) in the lambda # def call(scope, *args) raise Puppet::ParseError, "Too many arguments: #{args.size} for #{parameters.size}" unless args.size <= parameters.size # associate values with parameters merged = parameters.zip(args) # calculate missing arguments missing = parameters.slice(args.size, parameters.size - args.size).select {|e| e.size == 1} unless missing.empty? optional = parameters.count { |p| p.size == 2 } raise Puppet::ParseError, "Too few arguments; #{args.size} for #{optional > 0 ? ' min ' : ''}#{parameters.size - optional}" end evaluated = merged.collect do |m| # m can be one of # m = [["name"], "given"] # | [["name", default_expr], "given"] # # "given" is always an optional entry. If a parameter was provided then # the entry will be in the array, otherwise the m array will be a # single element. given_argument = m[1] argument_name = m[0][0] default_expression = m[0][1] value = if m.size == 1 default_expression.safeevaluate(scope) else given_argument end [argument_name, value] end # Store the evaluated name => value associations in a new inner/local/ephemeral scope # (This is made complicated due to the fact that the implementation of scope is overloaded with # functionality and an inner ephemeral scope must be used (as opposed to just pushing a local scope # on a scope "stack"). # Ensure variable exists with nil value if error occurs. # Some ruby implementations does not like creating variable on return result = nil begin elevel = scope.ephemeral_level scope.ephemeral_from(Hash[evaluated], file, line) result = safeevaluate(scope) ensure scope.unset_ephemeral_var(elevel) end result end # Validates the lambda. # Validation checks if parameters with default values are at the end of the list. (It is illegal # to have a parameter with default value followed by one without). # # @raise [Puppet::ParseError] if a parameter with a default comes before a parameter without default value # def validate params = parameters || [] defaults = params.drop_while {|p| p.size < 2 } trailing = defaults.drop_while {|p| p.size == 2 } raise Puppet::ParseError, "Lambda parameters with default values must be placed last" unless trailing.empty? end # Returns the number of parameters (required and optional) # @return [Integer] the total number of accepted parameters def parameter_count @parameters.size end # Returns the number of optional parameters. # @return [Integer] the number of optional accepted parameters def optional_parameter_count @parameters.count {|p| p.size == 2 } end def initialize(options) super(options) # ensure there is an empty parameters structure if not given by creator @parameters = [] unless options[:parameters] validate end def to_s result = ["{|"] result += @parameters.collect {|p| "#{p[0]}" + (p.size == 2 && p[1]) ? p[1].to_s() : '' }.join(', ') result << "| ... }" result.join('') end + + # marker method checked with respond_to :puppet_lambda + def puppet_lambda() + true + end end end diff --git a/lib/puppet/parser/ast/pops_bridge.rb b/lib/puppet/parser/ast/pops_bridge.rb new file mode 100644 index 000000000..7b697b031 --- /dev/null +++ b/lib/puppet/parser/ast/pops_bridge.rb @@ -0,0 +1,160 @@ +require 'puppet/parser/ast/top_level_construct' +require 'puppet/pops' + +# The AST::Bridge contains classes that bridges between the new Pops based model +# and the 3.x AST. This is required to be able to reuse the Puppet::Resource::Type which is +# fundamental for the rest of the logic. +# +class Puppet::Parser::AST::PopsBridge + + # Bridges to one Pops Model Expression + # The @value is the expression + # This is used to represent the body of a class, definition, or node, and for each parameter's default value + # expression. + # + class Expression < Puppet::Parser::AST::Leaf + + def initialize args + super + @@evaluator ||= Puppet::Pops::Parser::EvaluatingParser::Transitional.new() + end + + def to_s + Puppet::Pops::Model::ModelTreeDumper.new(dump(@value)) + end + + def evaluate(scope) + @@evaluator.evaluate(scope, @value) + end + + # Adapts to 3x where top level constructs needs to have each to iterate over children. Short circuit this + # by yielding self. By adding this there is no need to wrap a pops expression inside an AST::BlockExpression + # + def each + yield self + end + + def sequence_with(other) + if value.nil? + # This happens when testing and not having a complete setup + other + else + # When does this happen ? Ever ? + raise "sequence_with called on Puppet::Parser::AST::PopsBridge::Expression - please report use case" + # What should be done if the above happens (We don't want this to happen). + # Puppet::Parser::AST::BlockExpression.new(:children => [self] + other.children) + end + end + end + + # Bridges the top level "Program" produced by the pops parser. + # Its main purpose is to give one point where all definitions are instantiated (actually defined since the + # Puppet 3x terminology is somewhat misleading - the definitions are instantiated, but instances of the created types + # are not created, that happens when classes are included / required, nodes are matched and when resources are instantiated + # by a resource expression (which is also used to instantiate a host class). + # + class Program < Puppet::Parser::AST::TopLevelConstruct + attr_reader :program_model, :context + + def initialize(program_model, context = {}) + @program_model = program_model + @context = context + @ast_transformer ||= Puppet::Pops::Model::AstTransformer.new(@context[:file]) + @@evaluator ||= Puppet::Pops::Parser::EvaluatingParser::Transitional.new() + end + + # This is the 3x API, the 3x AST searches through all code to find the instructions that can be instantiated. + # This Pops-model based instantiation relies on the parser to build this list while parsing (which is more + # efficient as it avoids one full scan of all logic via recursive enumeration/yield) + # + def instantiate(modname) + @program_model.definitions.collect do |d| + case d + when Puppet::Pops::Model::HostClassDefinition + instantiate_HostClassDefinition(d, modname) + when Puppet::Pops::Model::ResourceTypeDefinition + instantiate_ResourceTypeDefinition(d, modname) + when Puppet::Pops::Model::NodeDefinition + instantiate_NodeDefinition(d, modname) + else + raise Puppet::ParseError("Internal Error: Unknown type of definition - got '#{d.class}'") + end + end.flatten() # flatten since node definition may have returned an array + end + + def evaluate(scope) + @@evaluator.evaluate(scope, program_model) + end + + # Adapts to 3x where top level constructs needs to have each to iterate over children. Short circuit this + # by yielding self. This means that the HostClass container will call this bridge instance with `instantiate`. + # + def each + yield self + end + + private + + def instantiate_Parameter(o) + # 3x needs parameters as an array of `[name]` or `[name, value_expr]` + # One problem is that the parameter evaluation takes place in the wrong context in 3x (the caller's and + # can thus reference all sorts of information. Here the value expression is wrapped in an AST Bridge to a Pops + # expression since the Pops side can not control the evaluation + if o.value + [ o.name, Expression.new(:value => o.value) ] + else + [ o.name ] + end + end + + # Produces a hash with data for Definition and HostClass + def args_from_definition(o, modname) + args = { + :arguments => o.parameters.collect {|p| instantiate_Parameter(p) }, + :module_name => modname + } + unless is_nop?(o.body) + args[:code] = Expression.new(:value => o.body) + end + @ast_transformer.merge_location(args, o) + end + + def instantiate_HostClassDefinition(o, modname) + args = args_from_definition(o, modname) + args[:parent] = o.parent_class + Puppet::Resource::Type.new(:hostclass, o.name, @context.merge(args)) + end + + def instantiate_ResourceTypeDefinition(o, modname) + Puppet::Resource::Type.new(:definition, o.name, @context.merge(args_from_definition(o, modname))) + end + + def instantiate_NodeDefinition(o, modname) + args = { :module_name => modname } + + unless is_nop?(o.body) + args[:code] = Expression.new(:value => o.body) + end + + unless is_nop?(o.parent) + args[:parent] = @ast_transformer.hostname(o.parent) + end + + host_matches = @ast_transformer.hostname(o.host_matches) + @ast_transformer.merge_location(args, o) + host_matches.collect do |name| + Puppet::Resource::Type.new(:node, name, @context.merge(args)) + end + end + + def code() + Expression.new(:value => @value) + end + + def is_nop?(o) + @ast_transformer.is_nop?(o) + end + + end + +end diff --git a/lib/puppet/parser/collector.rb b/lib/puppet/parser/collector.rb index 07c462b7f..a5f4cb0ff 100644 --- a/lib/puppet/parser/collector.rb +++ b/lib/puppet/parser/collector.rb @@ -1,177 +1,177 @@ # An object that collects stored objects from the central cache and returns # them to the current host, yo. class Puppet::Parser::Collector attr_accessor :type, :scope, :vquery, :equery, :form attr_accessor :resources, :overrides, :collected # Call the collection method, mark all of the returned objects as # non-virtual, optionally applying parameter overrides. The collector can # also delete himself from the compiler if there is no more resources to # collect (valid only for resource fixed-set collector which get their # resources from +collect_resources+ and not from the catalog) def evaluate # Shortcut if we're not using storeconfigs and they're trying to collect # exported resources. if form == :exported and Puppet[:storeconfigs] != true Puppet.warning "Not collecting exported resources without storeconfigs" return false end if self.resources unless objects = collect_resources and ! objects.empty? return false end else method = "collect_#{@form.to_s}" objects = send(method).each do |obj| obj.virtual = false end return false if objects.empty? end # we have an override for the collected resources if @overrides and !objects.empty? # force the resource to be always child of any other resource overrides[:source].meta_def(:child_of?) do |klass| true end # tell the compiler we have some override for him unless we already # overrided those resources objects.each do |res| unless @collected.include?(res.ref) newres = Puppet::Parser::Resource. new(res.type, res.title, :parameters => overrides[:parameters], :file => overrides[:file], :line => overrides[:line], :source => overrides[:source], :scope => overrides[:scope]) scope.compiler.add_override(newres) end end end # filter out object that this collector has previously found. objects.reject! { |o| @collected.include?(o.ref) } return false if objects.empty? # keep an eye on the resources we have collected objects.inject(@collected) { |c,o| c[o.ref]=o; c } # return our newly collected resources objects end def initialize(scope, type, equery, vquery, form) @scope = scope @vquery = vquery @equery = equery # initialisation @collected = {} # Canonize the type @type = Puppet::Resource.new(type, "whatever").type unless [:exported, :virtual].include?(form) raise ArgumentError, "Invalid query form #{form}" end @form = form end # add a resource override to the soon to be exported/realized resources def add_override(hash) raise ArgumentError, "Exported resource try to override without parameters" unless hash[:parameters] # schedule an override for an upcoming collection @overrides = hash end private # Collect exported objects. def collect_exported resources = [] time = Puppet::Util.thinmark do # First get everything from the export table. Just reuse our # collect_virtual method but tell it to use 'exported? for the test. resources = collect_virtual(true).reject { |r| ! r.virtual? } # key is '#{type}/#{name}', and host and filter. found = Puppet::Resource.indirection. - search(@type, :host => @scope.host, :filter => @equery, :scope => @scope) + search(@type, :host => @scope.compiler.node.name, :filter => @equery, :scope => @scope) found_resources = found.map {|x| x.is_a?(Puppet::Parser::Resource) ? x : x.to_resource(@scope)} found_resources.each do |item| if existing = @scope.findresource(item.type, item.title) unless existing.collector_id == item.collector_id # unless this is the one we've already collected raise Puppet::ParseError, "Another local or imported resource exists with the type and title #{item.ref}" end else item.exported = false @scope.compiler.add_resource(@scope, item) resources << item end end end scope.debug("Collected %s %s resource%s in %.2f seconds" % [resources.length, @type, resources.length == 1 ? "" : "s", time]) resources end def collect_resources @resources = [@resources] unless @resources.is_a?(Array) method = "collect_#{form.to_s}_resources" send(method) end def collect_exported_resources raise Puppet::ParseError, "realize() is not yet implemented for exported resources" end # Collect resources directly; this is the result of using 'realize', # which specifies resources, rather than using a normal collection. def collect_virtual_resources return [] unless defined?(@resources) and ! @resources.empty? result = @resources.dup.collect do |ref| if res = @scope.findresource(ref.to_s) @resources.delete(ref) res end end.reject { |r| r.nil? }.each do |res| res.virtual = false end # If there are no more resources to find, delete this from the list # of collections. @scope.compiler.delete_collection(self) if @resources.empty? result end # Collect just virtual objects, from our local compiler. def collect_virtual(exported = false) scope.compiler.resources.find_all do |resource| resource.type == @type and (exported ? resource.exported? : true) and match?(resource) end end # Does the resource match our tests? We don't yet support tests, # so it's always true at the moment. def match?(resource) if self.vquery return self.vquery.call(resource) else return true end end end diff --git a/lib/puppet/parser/e4_parser_adapter.rb b/lib/puppet/parser/e4_parser_adapter.rb new file mode 100644 index 000000000..8afc9dc8a --- /dev/null +++ b/lib/puppet/parser/e4_parser_adapter.rb @@ -0,0 +1,69 @@ +require 'puppet/pops' + +module Puppet; module Parser; end; end; +# Adapts an egrammar/eparser to respond to the public API of the classic parser +# and makes use of the new evaluator. +# +class Puppet::Parser::E4ParserAdapter + + def initialize() + @file = '' + @string = '' + @use = :undefined + @@evaluating_parser ||= Puppet::Pops::Parser::EvaluatingParser::Transitional.new() + end + + def file=(file) + @file = file + @use = :file + end + + def parse(string = nil) + self.string= string if string + + if @file =~ /\.rb$/ && @use != :string + # Will throw an error + parse_ruby_file + end + + parse_result = + if @use == :string + # Parse with a source_file to set in created AST objects (it was either given, or it may be unknown + # if caller did not set a file and the present a string. + # + @@evaluating_parser.parse_string(@string, @file || "unknown-source-location") + else + @@evaluating_parser.parse_file(@file) + end + + # the parse_result may be + # * empty / nil (no input) + # * a Model::Program + # * a Model::Expression + # + model = parse_result.nil? ? nil : parse_result.current + args = {} + Puppet::Pops::Model::AstTransformer.new(@file).merge_location(args, model) + + ast_code = + if model.is_a? Puppet::Pops::Model::Program + Puppet::Parser::AST::PopsBridge::Program.new(model, args) + else + args[:value] = model + Puppet::Parser::AST::PopsBridge::Expression.new(args) + end + + # Create the "main" class for the content - this content will get merged with all other "main" content + Puppet::Parser::AST::Hostclass.new('', :code => ast_code) + + end + + def string=(string) + @string = string + @use = :string + end + + def parse_ruby_file + raise Puppet::ParseError, "Ruby DSL is no longer supported. Attempt to parse #{@file}" + end +end diff --git a/lib/puppet/parser/functions.rb b/lib/puppet/parser/functions.rb index 360387727..ab8f7038d 100644 --- a/lib/puppet/parser/functions.rb +++ b/lib/puppet/parser/functions.rb @@ -1,242 +1,243 @@ 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, :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 = nil) if env and ! env.is_a?(Puppet::Node::Environment) env = Puppet::Node::Environment.new(env) end @modules[ (env || Environment.current || Environment.root).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: - # >> scope = Puppet::Parser::Scope.new_for_test_harness('example') + # >> 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. # # @return [Hash] describing the function. # # @api public def self.newfunction(name, options = {}, &block) name = name.intern Puppet.warning "Overwriting previous definition for function #{name}" if get_function(name) 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.send(:define_method, real_fname, &block) fname = "function_#{name}" environment_module.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) func end # Determine if a function is defined # # @param [Symbol] name the function # # @return [Symbol, false] The name of the function if it's defined, # otherwise false. # # @api public def self.function(name) name = name.intern func = nil unless func = get_function(name) autoloader.load(name, Environment.current) func = get_function(name) end if func func[:name] else false end end def self.functiondocs autoloader.loadall ret = "" merged_functions.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 # # @api public def self.rvalue?(name) func = get_function(name) func ? func[:type] == :rvalue : false end # Return the number of arguments a function expects. # # @param [Symbol] name the function # @return [Integer] The arity of the function. See {newfunction} for # the meaning of negative values. # # @api public def self.arity(name) func = get_function(name) func ? func[:arity] : -1 end class << self private def merged_functions @functions[Environment.root].merge(@functions[Environment.current]) end def get_function(name) name = name.intern merged_functions[name] end def add_function(name, func) name = name.intern @functions[Environment.current][name] = func end end reset # initialize the class instance variables end diff --git a/lib/puppet/parser/functions/each.rb b/lib/puppet/parser/functions/each.rb index 3a522098d..569a6c416 100644 --- a/lib/puppet/parser/functions/each.rb +++ b/lib/puppet/parser/functions/each.rb @@ -1,95 +1,140 @@ Puppet::Parser::Functions::newfunction( :each, :type => :rvalue, :arity => 2, :doc => <<-'ENDHEREDOC') do |args| Applies a parameterized block to each element in a sequence of selected entries from the first argument and returns the first argument. This function takes two mandatory arguments: the first should be an Array or a Hash, and the second a parameterized block as produced by the puppet syntax: $a.each {|$x| ... } When the first argument is an Array, the parameterized block should define one or two block parameters. For each application of the block, the next element from the array is selected, and it is passed to the block if the block has one parameter. If the block has two parameters, the first is the elements index, and the second the value. The index starts from 0. $a.each {|$index, $value| ... } When the first argument is a Hash, the parameterized block should define one or two parameters. When one parameter is defined, the iteration is performed with each entry as an array of `[key, value]`, and when two parameters are defined the iteration is performed with key and value. $a.each {|$entry| ..."key ${$entry[0]}, value ${$entry[1]}" } $a.each {|$key, $value| ..."key ${key}, value ${value}" } + When the first argument is a Type and the type is Enumerable, the parameterized block should define one + or two block parameters. + For each application of the block, the next element from the type enumerator is selected, and it is passed to + the block if the block has one parameter. If the block has two parameters, the first is the elements + index, and the second the value. The index starts from 0. + + Integer[ 10, 20 ].each {|$index, $value| ... } + - Since 3.2 - requires `parser = future`. ENDHEREDOC require 'puppet/parser/ast/lambda' def foreach_Array(o, scope, pblock) return nil unless pblock serving_size = pblock.parameter_count if serving_size == 0 raise ArgumentError, "Block must define at least one parameter; value." end if serving_size > 2 raise ArgumentError, "Block must define at most two parameters; index, value" end enumerator = o.each index = 0 if serving_size == 1 (o.size).times do pblock.call(scope, enumerator.next) end else (o.size).times do pblock.call(scope, index, enumerator.next) index = index +1 end end o end def foreach_Hash(o, scope, pblock) return nil unless pblock serving_size = pblock.parameter_count case serving_size when 0 raise ArgumentError, "Block must define at least one parameter (for hash entry key)." when 1 when 2 else raise ArgumentError, "Block must define at most two parameters (for hash entry key and value)." end enumerator = o.each_pair if serving_size == 1 (o.size).times do pblock.call(scope, enumerator.next) end else (o.size).times do pblock.call(scope, *enumerator.next) end end o end + def foreach_Type(o, scope, pblock) + return nil unless pblock + tc = Puppet::Pops::Types::TypeCalculator.new() + enumerable = tc.enumerable(o) + if enumerable.nil? + raise ArgumentError, ("each(): given type '#{tc.string(o)}' is not enumerable") + end + serving_size = pblock.parameter_count + if serving_size == 0 + raise ArgumentError, "Block must define at least one parameter; value." + end + if serving_size > 2 + raise ArgumentError, "Block must define at most two parameters; index, value" + end + enumerator = enumerable.each + index = 0 + if serving_size == 1 + begin + loop { pblock.call(scope, enumerator.next) } + rescue StopIteration + end + else + begin + loop do + pblock.call(scope, index, enumerator.next) + index = index +1 + end + rescue StopIteration + end + end + o + end + raise ArgumentError, ("each(): wrong number of arguments (#{args.length}; must be 2)") if args.length != 2 receiver = args[0] pblock = args[1] - raise ArgumentError, ("each(): wrong argument type (#{args[1].class}; must be a parameterized block.") unless pblock.is_a? Puppet::Parser::AST::Lambda + raise ArgumentError, ("each(): wrong argument type (#{args[1].class}; must be a parameterized block.") unless pblock.respond_to?(:puppet_lambda) case receiver when Array foreach_Array(receiver, self, pblock) when Hash foreach_Hash(receiver, self, pblock) else - raise ArgumentError, ("each(): wrong argument type (#{args[0].class}; must be an Array or a Hash.") + if receiver.is_a?(Puppet::Pops::Types::PAbstractType) + foreach_Type(receiver, self, pblock) + else + raise ArgumentError, ("each(): wrong argument type (#{args[0].class}; must be an Array, Hash, or Enumerable Type.") + end end end diff --git a/lib/puppet/parser/functions/filter.rb b/lib/puppet/parser/functions/filter.rb index 7894fa48f..ca17a386d 100644 --- a/lib/puppet/parser/functions/filter.rb +++ b/lib/puppet/parser/functions/filter.rb @@ -1,48 +1,48 @@ require 'puppet/parser/ast/lambda' Puppet::Parser::Functions::newfunction( :filter, :type => :rvalue, :arity => 2, :doc => <<-'ENDHEREDOC') do |args| Applies a parameterized block to each element in a sequence of entries from the first argument and returns an array or hash (same type as left operand) with the entries for which the block evaluates to true. This function takes two mandatory arguments: the first should be an Array or a Hash, and the second a parameterized block as produced by the puppet syntax: $a.filter |$x| { ... } When the first argument is an Array, the block is called with each entry in turn. When the first argument is a Hash the entry is an array with `[key, value]`. The returned filtered object is of the same type as the receiver. *Examples* # selects all that end with berry $a = ["raspberry", "blueberry", "orange"] $a.filter |$x| { $x =~ /berry$/ } - Since 3.4 - requires `parser = future`. ENDHEREDOC receiver = args[0] pblock = args[1] - raise ArgumentError, ("filter(): wrong argument type (#{pblock.class}; must be a parameterized block.") unless pblock.is_a? Puppet::Parser::AST::Lambda + raise ArgumentError, ("reject(): wrong argument type (#{pblock.class}; must be a parameterized block.") unless pblock.respond_to?(:puppet_lambda) case receiver when Array receiver.select {|x| pblock.call(self, x) } when Hash result = receiver.select {|x, y| pblock.call(self, [x, y]) } # Ruby 1.8.7 returns Array result = Hash[result] unless result.is_a? Hash result else raise ArgumentError, ("filter(): wrong argument type (#{receiver.class}; must be an Array or a Hash.") end end diff --git a/lib/puppet/parser/functions/lookup.rb b/lib/puppet/parser/functions/lookup.rb index 47ab719a0..800c9bd3d 100644 --- a/lib/puppet/parser/functions/lookup.rb +++ b/lib/puppet/parser/functions/lookup.rb @@ -1,44 +1,44 @@ Puppet::Parser::Functions.newfunction(:lookup, :type => :rvalue, :arity => -2, :doc => <<-'ENDHEREDOC') do |args| Looks up data defined using Puppet Bindings. The function is callable with one or two arguments and optionally with a lambda to process the result. The second argument can be a type specification; a String that describes the type of the produced result. If a value is found, an assert is made that the value is compliant with the specified type. When called with one argument; the name: lookup('the_name') When called with two arguments; the name, and the expected type: lookup('the_name', 'String') Using a lambda to process the looked up result. lookup('the_name') |$result| { if $result == undef { 'Jane Doe' } else { $result }} The type specification is one of: * the basic types; 'Integer', 'String', 'Float', 'Boolean', or 'Pattern' (regular expression) * an Array with an optional element type given in '[]', that when not given defaults to '[Data]' * a Hash with optional key and value types given in '[]', where key type defaults to 'Literal' and value to 'Data', if only one type is given, the key defaults to 'Literal' * the abstract type 'Literal' which is one of the basic types * the abstract type 'Data' which is 'Literal', or type compatible with Array[Data], or Hash[Literal, Data] * the abstract type 'Collection' which is Array or Hash of any element type. * the abstract type 'Object' which is any kind of type ENDHEREDOC unless Puppet[:binder] || Puppet[:parser] == 'future' raise Puppet::ParseError, "The lookup function is only available with settings --binder true, or --parser future" end type_parser = Puppet::Pops::Types::TypeParser.new - pblock = args[-1] if args[-1].is_a?(Puppet::Parser::AST::Lambda) - type_name = args[1] unless args[1].is_a?(Puppet::Parser::AST::Lambda) + pblock = args[-1] if args[-1].respond_to?(:puppet_lambda) + type_name = args[1] unless args[1].respond_to?(:puppet_lambda) type = type_parser.parse( type_name || "Data") result = compiler.injector.lookup(self, type, args[0]) if pblock result = pblock.call(self, result.nil? ? :undef : result) end result.nil? ? :undef : result end diff --git a/lib/puppet/parser/functions/map.rb b/lib/puppet/parser/functions/map.rb index 3c871bc85..94ef4a0b7 100644 --- a/lib/puppet/parser/functions/map.rb +++ b/lib/puppet/parser/functions/map.rb @@ -1,44 +1,44 @@ require 'puppet/parser/ast/lambda' Puppet::Parser::Functions::newfunction( :map, :type => :rvalue, :arity => 2, :doc => <<-'ENDHEREDOC') do |args| Applies a parameterized block to each element in a sequence of entries from the first argument and returns an array with the result of each invocation of the parameterized block. This function takes two mandatory arguments: the first should be an Array or a Hash, and the second a parameterized block as produced by the puppet syntax: $a.map |$x| { ... } When the first argument `$a` is an Array, the block is called with each entry in turn. When the first argument is a hash the entry is an array with `[key, value]`. *Examples* # Turns hash into array of values $a.map |$x|{ $x[1] } # Turns hash into array of keys $a.map |$x| { $x[0] } - Since 3.4 - requires `parser = future`. ENDHEREDOC receiver = args[0] pblock = args[1] - raise ArgumentError, ("map(): wrong argument type (#{pblock.class}; must be a parameterized block.") unless pblock.is_a? Puppet::Parser::AST::Lambda + raise ArgumentError, ("map(): wrong argument type (#{pblock.class}; must be a parameterized block.") unless pblock.respond_to?(:puppet_lambda) case receiver when Array when Hash else raise ArgumentError, ("map(): wrong argument type (#{receiver.class}; must be an Array or a Hash.") end receiver.to_a.map {|x| pblock.call(self, x) } end diff --git a/lib/puppet/parser/functions/reduce.rb b/lib/puppet/parser/functions/reduce.rb index afa321058..824f564c4 100644 --- a/lib/puppet/parser/functions/reduce.rb +++ b/lib/puppet/parser/functions/reduce.rb @@ -1,76 +1,76 @@ Puppet::Parser::Functions::newfunction( :reduce, :type => :rvalue, :arity => -2, :doc => <<-'ENDHEREDOC') do |args| Applies a parameterized block to each element in a sequence of entries from the first argument (_the collection_) and returns the last result of the invocation of the parameterized block. This function takes two mandatory arguments: the first should be an Array or a Hash, and the last a parameterized block as produced by the puppet syntax: $a.reduce |$memo, $x| { ... } When the first argument is an Array, the block is called with each entry in turn. When the first argument is a hash each entry is converted to an array with `[key, value]` before being fed to the block. An optional 'start memo' value may be supplied as an argument between the array/hash and mandatory block. If no 'start memo' is given, the first invocation of the parameterized block will be given the first and second elements of the collection, and if the collection has fewer than 2 elements, the first element is produced as the result of the reduction without invocation of the block. On each subsequent invocations, the produced value of the invoked parameterized block is given as the memo in the next invocation. *Examples* # Reduce an array $a = [1,2,3] $a.reduce |$memo, $entry| { $memo + $entry } #=> 6 # Reduce hash values $a = {a => 1, b => 2, c => 3} $a.reduce |$memo, $entry| { [sum, $memo[1]+$entry[1]] } #=> [sum, 6] It is possible to provide a starting 'memo' as an argument. *Examples* # Reduce an array $a = [1,2,3] $a.reduce(4) |$memo, $entry| { $memo + $entry } #=> 10 # Reduce hash values $a = {a => 1, b => 2, c => 3} $a.reduce([na, 4]) |$memo, $entry| { [sum, $memo[1]+$entry[1]] } #=> [sum, 10] - Since 3.2 - requires `parser = future`. ENDHEREDOC require 'puppet/parser/ast/lambda' case args.length when 2 pblock = args[1] when 3 pblock = args[2] else raise ArgumentError, ("reduce(): wrong number of arguments (#{args.length}; must be 2 or 3)") end - unless pblock.is_a? Puppet::Parser::AST::Lambda + unless pblock.respond_to?(:puppet_lambda) raise ArgumentError, ("reduce(): wrong argument type (#{args[1].class}; must be a parameterized block.") end receiver = args[0] unless [Array, Hash].include?(receiver.class) raise ArgumentError, ("collect(): wrong argument type (#{args[0].class}; must be an Array or a Hash.") end if args.length == 3 receiver.reduce(args[1]) {|memo, x| pblock.call(self, memo, x) } else receiver.reduce {|memo, x| pblock.call(self, memo, x) } end end diff --git a/lib/puppet/parser/functions/slice.rb b/lib/puppet/parser/functions/slice.rb index 505ab7261..f721c984f 100644 --- a/lib/puppet/parser/functions/slice.rb +++ b/lib/puppet/parser/functions/slice.rb @@ -1,97 +1,97 @@ Puppet::Parser::Functions::newfunction( :slice, :type => :rvalue, :arity => -2, :doc => <<-'ENDHEREDOC') do |args| Applies a parameterized block to each _slice_ of elements in a sequence of selected entries from the first argument and returns the first argument, or if no block is given returns a new array with a concatenation of the slices. This function takes two mandatory arguments: the first, `$a`, should be an Array or a Hash, and the second, `$n`, the number of elements to include in each slice. The optional third argument should be a a parameterized block as produced by the puppet syntax: $a.slice($n) |$x| { ... } The parameterized block should have either one parameter (receiving an array with the slice), or the same number of parameters as specified by the slice size (each parameter receiving its part of the slice). In case there are fewer remaining elements than the slice size for the last slice it will contain the remaining elements. When the block has multiple parameters, excess parameters are set to :undef for an array, and to empty arrays for a Hash. $a.slice(2) |$first, $second| { ... } When the first argument is a Hash, each key,value entry is counted as one, e.g, a slice size of 2 will produce an array of two arrays with key, value. $a.slice(2) |$entry| { notice "first ${$entry[0]}, second ${$entry[1]}" } $a.slice(2) |$first, $second| { notice "first ${first}, second ${second}" } When called without a block, the function produces a concatenated result of the slices. slice($[1,2,3,4,5,6], 2) # produces [[1,2], [3,4], [5,6]] - Since 3.2 - requires `parser = future`. ENDHEREDOC require 'puppet/parser/ast/lambda' require 'puppet/parser/scope' def each_Common(o, slice_size, filler, scope, pblock) serving_size = pblock ? pblock.parameter_count : 1 if serving_size == 0 raise ArgumentError, "Block must define at least one parameter." end unless serving_size == 1 || serving_size == slice_size raise ArgumentError, "Block must define one parameter, or the same number of parameters as the given size of the slice (#{slice_size})." end enumerator = o.each_slice(slice_size) result = [] if serving_size == 1 ((o.size.to_f / slice_size).ceil).times do if pblock pblock.call(scope, enumerator.next) else result << enumerator.next end end else ((o.size.to_f / slice_size).ceil).times do a = enumerator.next if a.size < serving_size a = a.dup.fill(filler, a.length...serving_size) end pblock.call(scope, *a) end end if pblock o else result end end raise ArgumentError, ("slice(): wrong number of arguments (#{args.length}; must be 2 or 3)") unless args.length == 2 || args.length == 3 if args.length >= 2 begin slice_size = Puppet::Parser::Scope.number?(args[1]) rescue raise ArgumentError, ("slice(): wrong argument type (#{args[1]}; must be number.") end end raise ArgumentError, ("slice(): wrong argument type (#{args[1]}; must be number.") unless slice_size raise ArgumentError, ("slice(): wrong argument value: #{slice_size}; is not a positive integer number > 0") unless slice_size.is_a?(Fixnum) && slice_size > 0 receiver = args[0] # the block is optional, ok if nil, function then produces an array pblock = args[2] - raise ArgumentError, ("slice(): wrong argument type (#{args[2].class}; must be a parameterized block.") unless pblock.is_a?(Puppet::Parser::AST::Lambda) || args.length == 2 + raise ArgumentError, ("slice(): wrong argument type (#{args[2].class}; must be a parameterized block.") unless pblock.respond_to?(:puppet_lambda) || args.length == 2 case receiver when Array each_Common(receiver, slice_size, :undef, self, pblock) when Hash each_Common(receiver, slice_size, [], self, pblock) else raise ArgumentError, ("slice(): wrong argument type (#{args[0].class}; must be an Array or a Hash.") end end diff --git a/lib/puppet/parser/parser_factory.rb b/lib/puppet/parser/parser_factory.rb index 06c612f39..7e76bf652 100644 --- a/lib/puppet/parser/parser_factory.rb +++ b/lib/puppet/parser/parser_factory.rb @@ -1,63 +1,74 @@ module Puppet; end module Puppet::Parser # The ParserFactory makes selection of parser possible. # Currently, it is possible to switch between two different parsers: # * classic_parser, the parser in 3.1 # * eparser, the Expression Based Parser # class ParserFactory # Produces a parser instance for the given environment def self.parser(environment) case Puppet[:parser] when 'future' - eparser(environment) + evaluating_parser() +# eparser(environment) else classic_parser(environment) end end # Creates an instance of the classic parser. # def self.classic_parser(environment) require 'puppet/parser' Puppet::Parser::Parser.new(environment) end + # Returns an instance of an EvaluatingParser + def self.evaluating_parser + # Since RGen is optional, test that it is installed + @@asserted ||= false + assert_rgen_installed() unless @@asserted + @@asserted = true + require 'puppet/parser/e4_parser_adapter' + E4ParserAdapter.new() + end + # Creates an instance of the expression based parser 'eparser' # def self.eparser(environment) # Since RGen is optional, test that it is installed @@asserted ||= false assert_rgen_installed() unless @@asserted @@asserted = true require 'puppet/parser' require 'puppet/parser/e_parser_adapter' EParserAdapter.new(Puppet::Parser::Parser.new(environment)) end private def self.assert_rgen_installed begin require 'rgen/metamodel_builder' rescue LoadError raise Puppet::DevError.new("The gem 'rgen' version >= 0.6.1 is required when using the setting '--parser future'. Please install 'rgen'.") end # Since RGen is optional, there is nothing specifying its version. # It is not installed in any controlled way, so not possible to use gems to check (it may be installed some other way). # Instead check that "eContainer, and eContainingFeature" has been installed. require 'puppet/pops' begin litstring = Puppet::Pops::Model::LiteralString.new(); container = Puppet::Pops::Model::ArithmeticExpression.new(); container.left_expr = litstring raise "no eContainer" if litstring.eContainer() != container raise "no eContainingFeature" if litstring.eContainingFeature() != :left_expr rescue raise Puppet::DevError.new("The gem 'rgen' version >= 0.6.1 is required when using '--parser future'. An older version is installed, please update.") end end end end diff --git a/lib/puppet/parser/scope.rb b/lib/puppet/parser/scope.rb index 2cad0c1dd..3eb92e967 100644 --- a/lib/puppet/parser/scope.rb +++ b/lib/puppet/parser/scope.rb @@ -1,684 +1,762 @@ # The scope class, which handles storing and retrieving variables and types and # such. require 'forwardable' require 'puppet/parser' require 'puppet/parser/templatewrapper' require 'puppet/resource/type_collection_helper' require 'puppet/util/methodhelper' # This class is part of the internal parser/evaluator/compiler functionality of Puppet. # It is passed between the various classes that participate in evaluation. # None of its methods are API except those that are clearly marked as such. # # @api public class Puppet::Parser::Scope extend Forwardable include Puppet::Util::MethodHelper include Puppet::Resource::TypeCollectionHelper require 'puppet/parser/resource' AST = Puppet::Parser::AST Puppet::Util.logmethods(self) - include Enumerable include Puppet::Util::Errors attr_accessor :source, :resource - attr_accessor :base, :keyword - attr_accessor :top, :translated, :compiler + attr_accessor :compiler attr_accessor :parent attr_reader :namespaces # Add some alias methods that forward to the compiler, since we reference # them frequently enough to justify the extra method call. def_delegators :compiler, :catalog, :environment - # thin wrapper around an ephemeral - # symbol table. - # when a symbol + + # Abstract base class for LocalScope and MatchScope + # class Ephemeral - extend Forwardable - def initialize(parent=nil, local=false) - @symbols = {} + attr_reader :parent + + def initialize(parent = nil) @parent = parent - @local_scope = local end - def_delegators :@symbols, :delete, :[]=, :each + def is_local_scope? + false + end def [](name) - if @symbols.include?(name) or @parent.nil? - @symbols[name] - else + if @parent @parent[name] end end def include?(name) - bound?(name) or (@parent and @parent.include?(name)) + (@parent and @parent.include?(name)) end def bound?(name) - @symbols.include?(name) + false + end + + def add_entries_to(target = {}) + @parent.add_entries_to(target) unless @parent.nil? + # do not include match data ($0-$n) + target + end + end + + class LocalScope < Ephemeral + + def initialize(parent=nil) + super parent + @symbols = {} + end + + def [](name) + if @symbols.include?(name) + @symbols[name] + else + super + end end def is_local_scope? - @local_scope + true + end + + def []=(name, value) + @symbols[name] = value end - # @return [Ephemeral, Hash, nil] - def parent - @parent + def include?(name) + bound?(name) || super + end + + def delete(name) + @symbols.delete(name) + end + + def bound?(name) + @symbols.include?(name) end def add_entries_to(target = {}) - @parent.add_entries_to(target) unless @parent.nil? - # do not return pure ephemeral ($0-$n) - if is_local_scope? - @symbols.each do |k, v| - if v == :undef - target.delete(k) - else - target[ k ] = v - end + super + @symbols.each do |k, v| + if v == :undef + target.delete(k) + else + target[ k ] = v end end target end - end - # Initialize a new scope suitable for parser function testing. This method - # should be considered a public API for external modules. A shared spec - # helper should consume this API method. - # - # @api protected - # - def self.new_for_test_harness(node_name) - node = Puppet::Node.new(node_name) - compiler = Puppet::Parser::Compiler.new(node) - scope = new(compiler) - scope.source = Puppet::Resource::Type.new(:node, node_name) - scope.parent = compiler.topscope - scope - end + class MatchScope < Ephemeral + + attr_accessor :match_data + + def initialize(parent = nil, match_data = nil) + super parent + @match_data = match_data + end + + def is_local_scope? + false + end + + def [](name) + if bound?(name) + @match_data[name.to_i] + else + super + end + end + + def include?(name) + bound?(name) or super + end + + def bound?(name) + # A "match variables" scope reports all numeric variables to be bound if the scope has + # match_data. Without match data the scope is transparent. + # + @match_data && name =~ /^\d+$/ + end + + def []=(name, value) + # TODO: Bad choice of exception + raise Puppet::ParseError, "Numerical variables cannot be changed. Attempt to set $#{name}" + end + + def delete(name) + # TODO: Bad choice of exception + raise Puppet::ParseError, "Numerical variables cannot be deleted: Attempt to delete: $#{name}" + end + + def add_entries_to(target = {}) + # do not include match data ($0-$n) + super + end - def each - to_hash.each { |name, value| yield(name, value) } end - # Proxy accessors - def host - compiler.node.name + # Returns true if the variable of the given name has a non nil value. + # TODO: This has vague semantics - does the variable exist or not? + # use ['name'] to get nil or value, and if nil check with exist?('name') + # this include? is only useful because of checking against the boolean value false. + # + def include?(name) + ! self[name].nil? end - # TODO: 19514 - this is smelly; who uses this? functions? templates? - # What about trusted facts ? Should untrusted facts be removed from facts? + # Returns true if the variable of the given name is set to any value (including nil) # - def facts - compiler.node.facts + def exist?(name) + effective_symtable(true).include?(name) end - def include?(name) - ! self[name].nil? + # Returns true if the given name is bound in the current (most nested) scope. + # + def bound?(name) + effective_symtable(true).bound?(name) end # Is the value true? This allows us to control the definition of truth # in one place. def self.true?(value) case value when '' false when :undef false else !!value end end # Coerce value to a number, or return `nil` if it isn't one. def self.number?(value) case value when Numeric value when /^-?\d+(:?\.\d+|(:?\.\d+)?e\d+)$/ value.to_f when /^0x[0-9a-f]+$/i value.to_i(16) when /^0[0-7]+$/ value.to_i(8) when /^-?\d+$/ value.to_i else nil end end # Add to our list of namespaces. def add_namespace(ns) return false if @namespaces.include?(ns) if @namespaces == [""] @namespaces = [ns] else @namespaces << ns end end def find_hostclass(name, options = {}) known_resource_types.find_hostclass(namespaces, name, options) end def find_definition(name) known_resource_types.find_definition(namespaces, name) end # This just delegates directly. def_delegator :compiler, :findresource # Initialize our new scope. Defaults to having no parent. def initialize(compiler, options = {}) if compiler.is_a? Puppet::Parser::Compiler self.compiler = compiler else raise Puppet::DevError, "you must pass a compiler instance to a new scope object" end if n = options.delete(:namespace) @namespaces = [n] else @namespaces = [""] end raise Puppet::DevError, "compiler passed in options" if options.include? :compiler set_options(options) extend_with_functions_module # The symbol table for this scope. This is where we store variables. - @symtable = Ephemeral.new(nil, true) + # @symtable = Ephemeral.new(nil, true) + @symtable = LocalScope.new(nil) - @ephemeral = [ Ephemeral.new(@symtable) ] + @ephemeral = [ MatchScope.new(@symtable, nil) ] # All of the defaults set for types. It's a hash of hashes, # with the first key being the type, then the second key being # the parameter. @defaults = Hash.new { |dhash,type| dhash[type] = {} } # The table for storing class singletons. This will only actually # be used by top scopes and node scopes. @class_scopes = {} end # Store the fact that we've evaluated a class, and store a reference to # the scope in which it was evaluated, so that we can look it up later. def class_set(name, scope) if parent parent.class_set(name, scope) else @class_scopes[name] = scope end end # Return the scope associated with a class. This is just here so # that subclasses can set their parent scopes to be the scope of # their parent class, and it's also used when looking up qualified # variables. def class_scope(klass) # They might pass in either the class or class name k = klass.respond_to?(:name) ? klass.name : klass @class_scopes[k] || (parent && parent.class_scope(k)) end # Collect all of the defaults set at any higher scopes. # This is a different type of lookup because it's additive -- # it collects all of the defaults, with defaults in closer scopes # overriding those in later scopes. def lookupdefaults(type) values = {} # first collect the values from the parents if parent parent.lookupdefaults(type).each { |var,value| values[var] = value } end # then override them with any current values # this should probably be done differently if @defaults.include?(type) @defaults[type].each { |var,value| values[var] = value } end values end # Look up a defined type. def lookuptype(name) find_definition(name) || find_hostclass(name) end def undef_as(x,v) if v.nil? or v == :undef x else v end end # Lookup a variable within this scope using the Puppet language's # scoping rules. Variables can be qualified using just as in a # manifest. # # @param [String] name the variable name to lookup # # @return Object the value of the variable, or nil if it's not found # # @api public def lookupvar(name, options = {}) unless name.is_a? String raise Puppet::ParseError, "Scope variable name #{name.inspect} is a #{name.class}, not a string" end table = @ephemeral.last if name =~ /^(.*)::(.+)$/ class_name = $1 variable_name = $2 lookup_qualified_variable(class_name, variable_name, options) + + # TODO: optimize with an assoc instead, this searches through scopes twice for a hit elsif table.include?(name) table[name] else next_scope = inherited_scope || enclosing_scope if next_scope next_scope.lookupvar(name, options) else - nil + variable_not_found() end end end + def variable_not_found + if Puppet[:strict_variables] + throw :undefined_variable + else + nil + end + end # Retrieves the variable value assigned to the name given as an argument. The name must be a String, # and namespace can be qualified with '::'. The value is looked up in this scope, its parent scopes, # or in a specific visible named scope. # # @param varname [String] the name of the variable (may be a qualified name using `(ns'::')*varname` # @param options [Hash] Additional options, not part of api. # @return [Object] the value assigned to the given varname # @see #[]= # @api public # def [](varname, options={}) lookupvar(varname, options) end # The scope of the inherited thing of this scope's resource. This could # either be a node that was inherited or the class. # # @return [Puppet::Parser::Scope] The scope or nil if there is not an inherited scope def inherited_scope if has_inherited_class? qualified_scope(resource.resource_type.parent) else nil end end # The enclosing scope (topscope or nodescope) of this scope. # The enclosing scopes are produced when a class or define is included at # some point. The parent scope of the included class or define becomes the # scope in which it was included. The chain of parent scopes is followed # until a node scope or the topscope is found # # @return [Puppet::Parser::Scope] The scope or nil if there is no enclosing scope def enclosing_scope if has_enclosing_scope? if parent.is_topscope? or parent.is_nodescope? parent else parent.enclosing_scope end else nil end end def is_classscope? resource and resource.type == "Class" end def is_nodescope? resource and resource.type == "Node" end def is_topscope? compiler and self == compiler.topscope end def lookup_qualified_variable(class_name, variable_name, position) begin if lookup_as_local_name?(class_name, variable_name) self[variable_name] else qualified_scope(class_name).lookupvar(variable_name, position) end rescue RuntimeError => e location = if position[:lineproc] " at #{position[:lineproc].call}" elsif position[:file] && position[:line] " at #{position[:file]}:#{position[:line]}" else "" end warning "Could not look up qualified variable '#{class_name}::#{variable_name}'; #{e.message}#{location}" - nil + variable_not_found() end end # Handles the special case of looking up fully qualified variable in not yet evaluated top scope # This is ok if the lookup request originated in topscope (this happens when evaluating # bindings; using the top scope to provide the values for facts. # @param class_name [String] the classname part of a variable name, may be special "" # @param variable_name [String] the variable name without the absolute leading '::' # @return [Boolean] true if the given variable name should be looked up directly in this scope # def lookup_as_local_name?(class_name, variable_name) # not a local if name has more than one segment return nil if variable_name =~ /::/ # partial only if the class for "" cannot be found return nil unless class_name == "" && klass = find_hostclass(class_name) && class_scope(klass).nil? is_topscope? end def has_inherited_class? is_classscope? and resource.resource_type.parent end private :has_inherited_class? def has_enclosing_scope? not parent.nil? end private :has_enclosing_scope? def qualified_scope(classname) raise "class #{classname} could not be found" unless klass = find_hostclass(classname) raise "class #{classname} has not been evaluated" unless kscope = class_scope(klass) kscope end private :qualified_scope # Returns a Hash containing all variables and their values, optionally (and # by default) including the values defined in parent. Local values # shadow parent values. Ephemeral scopes for match results ($0 - $n) are not included. # def to_hash(recursive = true) if recursive and parent target = parent.to_hash(recursive) else target = Hash.new end # add all local scopes @ephemeral.last.add_entries_to(target) target end def namespaces @namespaces.dup end # Create a new scope and set these options. def newscope(options = {}) compiler.newscope(self, options) end def parent_module_name return nil unless @parent return nil unless @parent.source @parent.source.module_name end # Set defaults for a type. The typename should already be downcased, # so that the syntax is isolated. We don't do any kind of type-checking # here; instead we let the resource do it when the defaults are used. def define_settings(type, params) table = @defaults[type] # if we got a single param, it'll be in its own array params = [params] unless params.is_a?(Array) params.each { |param| if table.include?(param.name) raise Puppet::ParseError.new("Default already defined for #{type} { #{param.name} }; cannot redefine", param.line, param.file) end table[param.name] = param } end RESERVED_VARIABLE_NAMES = ['trusted'].freeze # Set a variable in the current scope. This will override settings # in scopes above, but will not allow variables in the current scope # to be reassigned. # It's preferred that you use self[]= instead of this; only use this # when you need to set options. def setvar(name, value, options = {}) if name =~ /^[0-9]+$/ - raise Puppet::ParseError.new("Cannot assign to a numeric match result variable '$#{name}'") unless options[:ephemeral] + raise Puppet::ParseError.new("Cannot assign to a numeric match result variable '$#{name}'") # unless options[:ephemeral] end unless name.is_a? String raise Puppet::ParseError, "Scope variable name #{name.inspect} is a #{name.class}, not a string" end # Check for reserved variable names if Puppet[:trusted_node_data] && !options[:privileged] && RESERVED_VARIABLE_NAMES.include?(name) raise Puppet::ParseError, "Attempt to assign to a reserved variable name: '#{name}'" end table = effective_symtable options[:ephemeral] if table.bound?(name) if options[:append] error = Puppet::ParseError.new("Cannot append, variable #{name} is defined in this scope") else error = Puppet::ParseError.new("Cannot reassign variable #{name}") end error.file = options[:file] if options[:file] error.line = options[:line] if options[:line] raise error end if options[:append] table[name] = append_value(undef_as('', self[name]), value) else table[name] = value end table[name] end def set_trusted(hash) setvar('trusted', deep_freeze(hash), :privileged => true) end # Deeply freezes the given object. The object and its content must be of the types: # Array, Hash, Numeric, Boolean, Symbol, Regexp, NilClass, or String. All other types raises an Error. # (i.e. if they are assignable to Puppet::Pops::Types::Data type). # def deep_freeze(object) case object when Hash object.each {|k, v| deep_freeze(k); deep_freeze(v) } when NilClass # do nothing when String object.freeze else raise Puppet::Error, "Unsupported data type: '#{object.class}" end object end private :deep_freeze # Return the effective "table" for setting variables. # This method returns the first ephemeral "table" that acts as a local scope, or this # scope's symtable. If the parameter `use_ephemeral` is true, the "top most" ephemeral "table" # will be returned (irrespective of it being a match scope or a local scope). # # @param use_ephemeral [Boolean] whether the top most ephemeral (of any kind) should be used or not def effective_symtable use_ephemeral s = @ephemeral.last return s if use_ephemeral + # Why check if ephemeral is a Hash ??? Not needed, a hash cannot be a parent scope ??? while s && !(s.is_a?(Hash) || s.is_local_scope?()) s = s.parent end s ? s : @symtable end # Sets the variable value of the name given as an argument to the given value. The value is # set in the current scope and may shadow a variable with the same name in a visible outer scope. # It is illegal to re-assign a variable in the same scope. It is illegal to set a variable in some other # scope/namespace than the scope passed to a method. # # @param varname [String] The variable name to which the value is assigned. Must not contain `::` # @param value [String] The value to assign to the given variable name. # @param options [Hash] Additional options, not part of api. # # @api public # def []=(varname, value, options = {}) setvar(varname, value, options = {}) end def append_value(bound_value, new_value) case new_value when Array bound_value + new_value when Hash bound_value.merge(new_value) else if bound_value.is_a?(Hash) raise ArgumentError, "Trying to append to a hash with something which is not a hash is unsupported" end bound_value + new_value end end private :append_value # Return the tags associated with this scope. def_delegator :resource, :tags # Used mainly for logging def to_s "Scope(#{@resource})" end # remove ephemeral scope up to level + # TODO: Who uses :all ? Remove ?? + # def unset_ephemeral_var(level=:all) if level == :all - @ephemeral = [ Ephemeral.new(@symtable)] + @ephemeral = [ MatchScope.new(@symtable, nil)] else @ephemeral.pop(@ephemeral.size - level) end end - # check if name exists in one of the ephemeral scopes. - def ephemeral_include?(name) - @ephemeral.any? {|eph| eph.include?(name) } + def ephemeral_level + @ephemeral.size end - # Checks whether the variable should be processed in the ephemeral scope or not. - # All numerical variables are processed in ephemeral scope at all times, and all other - # variables when the ephemeral scope is a local scope. - # - def ephemeral?(name) - @ephemeral.last.is_local_scope? || name =~ /^\d+$/ + # TODO: Who calls this? + def new_ephemeral(local_scope = false) + if local_scope + @ephemeral.push(LocalScope.new(@ephemeral.last)) + else + @ephemeral.push(MatchScope.new(@ephemeral.last, nil)) + end end - def ephemeral_level - @ephemeral.size + # Sets match data in the most nested scope (which always is a MatchScope), it clobbers match data already set there + # + def set_match_data(match_data) + @ephemeral.last.match_data = match_data end - def new_ephemeral(local_scope = false) - @ephemeral.push(Ephemeral.new(@ephemeral.last, local_scope)) + # Nests a match data scope + def new_match_scope(match_data) + @ephemeral.push(MatchScope.new(@ephemeral.last, match_data)) end def ephemeral_from(match, file = nil, line = nil) case match when Hash # Create local scope ephemeral and set all values from hash - new_ephemeral true + new_ephemeral(true) match.each {|k,v| setvar(k, v, :file => file, :line => line, :ephemeral => true) } + # Must always have an inner match data scope (that starts out as transparent) + # In 3x slightly wasteful, since a new nested scope is created for a match + # (TODO: Fix that problem) + new_ephemeral(false) else raise(ArgumentError,"Invalid regex match data. Got a #{match.class}") unless match.is_a?(MatchData) # Create a match ephemeral and set values from match data - new_ephemeral false - setvar("0", match[0], :file => file, :line => line, :ephemeral => true) - match.captures.each_with_index do |m,i| - setvar("#{i+1}", m, :file => file, :line => line, :ephemeral => true) - end + new_match_scope(match) end end def find_resource_type(type) # It still works fine without the type == 'class' short-cut, but it is a lot slower. return nil if ["class", "node"].include? type.to_s.downcase find_builtin_resource_type(type) || find_defined_resource_type(type) end def find_builtin_resource_type(type) Puppet::Type.type(type.to_s.downcase.to_sym) end def find_defined_resource_type(type) environment.known_resource_types.find_definition(namespaces, type.to_s.downcase) end + def method_missing(method, *args, &block) method.to_s =~ /^function_(.*)$/ name = $1 super unless name super unless Puppet::Parser::Functions.function(name) # In odd circumstances, this might not end up defined by the previous # method, so we might as well be certain. if respond_to? method send(method, *args) else raise Puppet::DevError, "Function #{name} not defined despite being loaded!" end end def resolve_type_and_titles(type, titles) raise ArgumentError, "titles must be an array" unless titles.is_a?(Array) case type.downcase when "class" # resolve the titles titles = titles.collect do |a_title| hostclass = find_hostclass(a_title) hostclass ? hostclass.name : a_title end when "node" # no-op else # resolve the type resource_type = find_resource_type(type) type = resource_type.name if resource_type end return [type, titles] end private def extend_with_functions_module extend Puppet::Parser::Functions.environment_module(Puppet::Node::Environment.root) extend Puppet::Parser::Functions.environment_module(environment) if environment != Puppet::Node::Environment.root end end diff --git a/lib/puppet/pops.rb b/lib/puppet/pops.rb index be30a59ee..62e2cd138 100644 --- a/lib/puppet/pops.rb +++ b/lib/puppet/pops.rb @@ -1,85 +1,95 @@ module Puppet module Pops require 'puppet/pops/patterns' require 'puppet/pops/utils' require 'puppet/pops/adaptable' require 'puppet/pops/adapters' require 'puppet/pops/visitable' require 'puppet/pops/visitor' require 'puppet/pops/containment' require 'puppet/pops/issues' require 'puppet/pops/label_provider' require 'puppet/pops/validation' require 'puppet/pops/issue_reporter' require 'puppet/pops/model/model' module Types require 'puppet/pops/types/types' require 'puppet/pops/types/type_calculator' require 'puppet/pops/types/type_factory' require 'puppet/pops/types/type_parser' require 'puppet/pops/types/class_loader' end module Model require 'puppet/pops/model/tree_dumper' require 'puppet/pops/model/ast_transformer' require 'puppet/pops/model/ast_tree_dumper' require 'puppet/pops/model/factory' require 'puppet/pops/model/model_tree_dumper' require 'puppet/pops/model/model_label_provider' end module Binder module SchemeHandler # the handlers are auto loaded via bindings end module Producers require 'puppet/pops/binder/producers' end require 'puppet/pops/binder/binder' require 'puppet/pops/binder/bindings_model' require 'puppet/pops/binder/binder_issues' require 'puppet/pops/binder/bindings_checker' require 'puppet/pops/binder/bindings_factory' require 'puppet/pops/binder/bindings_label_provider' require 'puppet/pops/binder/bindings_validator_factory' require 'puppet/pops/binder/injector_entry' require 'puppet/pops/binder/key_factory' require 'puppet/pops/binder/injector' require 'puppet/pops/binder/hiera2' require 'puppet/pops/binder/bindings_composer' require 'puppet/pops/binder/bindings_model_dumper' require 'puppet/pops/binder/system_bindings' require 'puppet/pops/binder/bindings_loader' module Config require 'puppet/pops/binder/config/binder_config' require 'puppet/pops/binder/config/binder_config_checker' require 'puppet/pops/binder/config/issues' require 'puppet/pops/binder/config/diagnostic_producer' end end module Parser require 'puppet/pops/parser/eparser' require 'puppet/pops/parser/parser_support' + require 'puppet/pops/parser/locator' + require 'puppet/pops/parser/locatable' require 'puppet/pops/parser/lexer' + require 'puppet/pops/parser/lexer2' require 'puppet/pops/parser/evaluating_parser' end module Validation require 'puppet/pops/validation/checker3_1' require 'puppet/pops/validation/validator_factory_3_1' + require 'puppet/pops/validation/checker4_0' + require 'puppet/pops/validation/validator_factory_4_0' + end + + module Evaluator + require 'puppet/pops/evaluator/runtime3_support' + require 'puppet/pops/evaluator/evaluator_impl' end end require 'puppet/bindings' end diff --git a/lib/puppet/pops/adapters.rb b/lib/puppet/pops/adapters.rb index 13e53845c..ac84e4e92 100644 --- a/lib/puppet/pops/adapters.rb +++ b/lib/puppet/pops/adapters.rb @@ -1,69 +1,94 @@ # The Adapters module contains adapters for Documentation, Origin, SourcePosition, and Loader. # module Puppet::Pops::Adapters # A documentation adapter adapts an object with a documentation string. # (The intended use is for a source text parser to extract documentation and store this # in DocumentationAdapter instances). # class DocumentationAdapter < Puppet::Pops::Adaptable::Adapter # @return [String] The documentation associated with an object attr_accessor :documentation end # An origin adapter adapts an object with where it came from. This origin # describes the resource (a file, etc.) where source text originates. # Instances of SourcePosAdapter is then used on other objects in a model to # describe their relative position versus the origin. # # @see Puppet::Pops::Utils#find_adapter # class OriginAdapter < Puppet::Pops::Adaptable::Adapter # @return [String] the origin of the adapted (usually a filename) attr_accessor :origin end - # A SourcePosAdapter describes a position relative to an origin. (Typically an {OriginAdapter} is - # associated with the root of a model. This origin has a URI to the resource, and a line number. - # The offset in the SourcePosAdapter is then relative to this origin. - # (This somewhat complex structure makes it possible to correctly refer to a source position + # A SourcePosAdapter holds a reference to something *locateable* (a position in source text). + # This is represented by an instance of Puppet::Pops::Parser::Locateable (it has an offset, a length, and + # a Puppet::Pops::Parser::Locator) that are used together to provide derived information (line, and position + # on line). + # This somewhat complex structure makes it possible to correctly refer to a source position # in source that is embedded in some resource; a parser only sees the embedded snippet of source text - # and does not know where it was embedded). + # and does not know where it was embedded. It also enables lazy evaluation of source positions (they are + # rarely needed - typically just when there is an error to report. + # + # @note It is relatively expensive to compute line and postion on line - it is not something that + # should be done for every token or model object. # # @see Puppet::Pops::Utils#find_adapter # class SourcePosAdapter < Puppet::Pops::Adaptable::Adapter - # @return [Fixnum] The start line in source starting from 1 - attr_accessor :line + attr_accessor :locatable + + def locator + locatable.locator + end + + def offset + locatable.offset + end - # @return [Fixnum] The position on the start_line (in characters) starting from 0 - attr_accessor :pos + def length + locatable.length + end - # @return [Fixnum] The (start) offset of source text characters - # (starting from 0) representing the adapted object. - # Value may be nil - attr_accessor :offset + # Produces the line number for the given offset. + # @note This is an expensive operation + # + def line + locatable.locator.line_for_offset(offset) + end - # @return [Fixnum] The length (count) of characters of source text - # representing the adapted object from the origin. Not including any - # trailing whitespace. - attr_accessor :length + # Produces the position on the line of the given offset. + # @note This is an expensive operation + # + def pos + locatable.locator.pos_on_line(offset) + end + + # Extracts the text represented by this source position (the string is obtained from the locator) + def extract_text + locatable.locator.string.slice(offset, length) + end + # Extracts the text represented by this source position from a given string (which needs to be identical + # to what is held in the locator - why is this needed ? + # TODO: def extract_text_from_string(string) string.slice(offset, length) end end # A LoaderAdapter adapts an object with a {Puppet::Pops::Loader}. This is used to make further loading from the # perspective of the adapted object take place in the perspective of this Loader. # # It is typically enough to adapt the root of a model as a search is made towards the root of the model # until a loader is found, but there is no harm in duplicating this information provided a contained # object is adapted with the correct loader. # # @see Puppet::Pops::Utils#find_adapter # class LoaderAdapter < Puppet::Pops::Adaptable::Adapter # @return [Puppet::Pops::Loader] the loader attr_accessor :loader end end diff --git a/lib/puppet/pops/binder/bindings_factory.rb b/lib/puppet/pops/binder/bindings_factory.rb index a5d3ca46a..7c8f02ac7 100644 --- a/lib/puppet/pops/binder/bindings_factory.rb +++ b/lib/puppet/pops/binder/bindings_factory.rb @@ -1,847 +1,847 @@ # A helper class that makes it easier to construct a Bindings model. # # The Bindings Model # ------------------ # The BindingsModel (defined in {Puppet::Pops::Binder::Bindings} is a model that is intended to be generally free from Ruby concerns. # This means that it is possible for system integrators to create and serialize such models using other technologies than # Ruby. This manifests itself in the model in that producers are described using instances of a `ProducerDescriptor` rather than # describing Ruby classes directly. This is also true of the type system where type is expressed using the {Puppet::Pops::Types} model # to describe all types. # # This class, the `BindingsFactory` is a concrete Ruby API for constructing instances of classes in the model. # # Named Bindings # -------------- # The typical usage of the factory is to call {named_bindings} which creates a container of bindings wrapped in a *build object* # equipped with convenience methods to define the details of the just created named bindings. # The returned builder is an instance of {Puppet::Pops::Binder::BindingsFactory::BindingsContainerBuilder BindingsContainerBuilder}. # # Binding # ------- # A Binding binds a type/name key to a producer of a value. A binding is conveniently created by calling `bind` on a # `BindingsContainerBuilder`. The call to bind, produces a binding wrapped in a build object equipped with convenience methods # to define the details of the just created binding. The returned builder is an instance of # {Puppet::Pops::Binder::BindingsFactory::BindingsBuilder BindingsBuilder}. # # Multibinding # ------------ # A multibinding works like a binding, but it requires an additional ID. It also places constraints on the type of the binding; # it must be a collection type (Hash or Array). # # Constructing and Contributing Bindings from Ruby # ------------------------------------------------ # The bindings system is used by referencing bindings symbolically; these are then specified in a Ruby file which is autoloaded # by Puppet. The entry point for user code that creates bindings is described in {Puppet::Bindings Bindings}. # That class makes use of a BindingsFactory, and the builder objects to make it easy to construct bindings. # # It is intended that a user defining bindings in Ruby should be able to use the builder object methods for the majority of tasks. # If something advanced is wanted, use of one of the helper class methods on the BuildingsFactory, and/or the # {Puppet::Pops::Types::TypeCalculator TypeCalculator} will be required to create and configure objects that are not handled by # the methods in the builder objects. # # Chaining of calls # ------------------ # Since all the build methods return the build object it is easy to stack on additional calls. The intention is to # do this in an order that is readable from left to right: `bind.string.name('thename').to(42)`, but there is nothing preventing # making the calls in some other order e.g. `bind.to(42).name('thename').string`, the second is quite unreadable but produces # the same result. # # For sake of human readability, the method `name` is alsp available as `named`, with the intention that it is used after a type, # e.g. `bind.integer.named('the meaning of life').to(42)` # # Methods taking blocks # ---------------------- # Several methods take an optional block. The block evaluates with the builder object as `self`. This means that there is no # need to chain the methods calls, they can instead be made in sequence - e.g. # # bind do # integer # named 'the meaning of life' # to 42 # end # # or mix the two styles # # bind do # integer.named 'the meaning of life' # to 42 # end # # Unwrapping the result # --------------------- # The result from all methods is a builder object. Call the method `model` to unwrap the constructed bindings model object. # # bindings = BindingsFactory.named_bindings('my named bindings') do # # bind things # end.model # # @example Create a NamedBinding with content # result = Puppet::Pops::Binder::BindingsFactory.named_bindings("mymodule::mybindings") do # bind.name("foo").to(42) # when_in_category("node", "kermit.example.com").bind.name("foo").to(43) # bind.string.name("site url").to("http://www.example.com") # end # result.model() # # @api public # module Puppet::Pops::Binder::BindingsFactory # Alias for the {Puppet::Pops::Types::TypeFactory TypeFactory}. This is also available as the method # `type_factory`. # T = Puppet::Pops::Types::TypeFactory # Abstract base class for bindings object builders. # Supports delegation of method calls to the BindingsFactory class methods for all methods not implemented # by a concrete builder. # # @abstract # class AbstractBuilder # The built model object. attr_reader :model # @param binding [Puppet::Pops::Binder::Bindings::AbstractBinding] The binding to build. # @api public def initialize(binding) @model = binding end # Provides convenient access to the Bindings Factory class methods. The intent is to provide access to the # methods that return producers for the purpose of composing more elaborate things than the builder convenience # methods support directly. # @api private # def method_missing(meth, *args, &block) factory = Puppet::Pops::Binder::BindingsFactory if factory.respond_to?(meth) factory.send(meth, *args, &block) else super end end end # A bindings builder for an AbstractBinding containing other AbstractBinding instances. # @api public class BindingsContainerBuilder < AbstractBuilder # Adds an empty binding to the container, and returns a builder for it for further detailing. # An optional block may be given which is evaluated using `instance_eval`. # @return [BindingsBuilder] the builder for the created binding # @api public # def bind(&block) binding = Puppet::Pops::Binder::Bindings::Binding.new() model.addBindings(binding) builder = BindingsBuilder.new(binding) builder.instance_eval(&block) if block_given? builder end # Binds a multibind with the given identity where later, the looked up result contains all # contributions to this key. An optional block may be given which is evaluated using `instance_eval`. # @param id [String] the multibind's id used when adding contributions # @return [MultibindingsBuilder] the builder for the created multibinding # @api public # def multibind(id, &block) binding = Puppet::Pops::Binder::Bindings::Multibinding.new() binding.id = id model.addBindings(binding) builder = MultibindingsBuilder.new(binding) builder.instance_eval(&block) if block_given? builder end # Adds a categorized bindings to this container. Returns a BindingsContainerBuilder to allow adding # bindings in the newly created container. An optional block may be given which is evaluated using `instance_eval`. # @param categorization [String] the name of the categorization e.g. 'node' # @param category_value [String] the value in that category e.g. 'kermit.example.com' # @return [BindingsContainerBuilder] the builder for the created categorized bindings container # @api public # def when_in_category(categorization, category_value, &block) when_in_categories({categorization => category_value}, &block) end # Adds a categorized bindings to this container. Returns a BindingsContainerBuilder to allow adding # bindings in the newly created container. # The result is that a processed request must match all the given categorizations # with the given values. An optional block may be given which is evaluated using `instance_eval`. # @param categories_hash Hash[String, String] a hash with categorization and categorization value entries # @return [BindingsContainerBuilder] the builder for the created categorized bindings container # @api public # def when_in_categories(categories_hash, &block) binding = Puppet::Pops::Binder::Bindings::CategorizedBindings.new() categories_hash.each do |k,v| pred = Puppet::Pops::Binder::Bindings::Category.new() pred.categorization = k pred.value = v binding.addPredicates(pred) end model.addBindings(binding) builder = BindingsContainerBuilder.new(binding) builder.instance_eval(&block) if block_given? builder end end # Builds a Binding via convenience methods. # # @api public # class BindingsBuilder < AbstractBuilder # @param binding [Puppet::Pops::Binder::Bindings::AbstractBinding] the binding to build. # @api public def initialize(binding) super binding data() end # Sets the name of the binding. # @param name [String] the name to bind. # @api public def name(name) model.name = name self end # Same as {#name}, but reads better in certain combinations. # @api public alias_method :named, :name # Sets the binding to be abstract (it must be overridden) # @api public def abstract model.abstract = true self end # Sets the binding to be override (it must override something) # @api public def override model.override = true self end # Makes the binding a multibind contribution to the given multibind id # @param id [String] the multibind id to contribute this binding to # @api public def in_multibind(id) model.multibind_id = id self end # Sets the type of the binding to the given type. # @note # This is only needed if something other than the default type `Data` is wanted, or if the wanted type is # not provided by one of the convenience methods {#array_of_data}, {#boolean}, {#float}, {#hash_of_data}, # {#integer}, {#literal}, {#pattern}, {#string}, or one of the collection methods {#array_of}, or {#hash_of}. # # To create a type, use the method {#type_factory}, to obtain the type. # @example creating a Hash with Integer key and Array[Integer] element type # tc = type_factory # type(tc.hash(tc.array_of(tc.integer), tc.integer) # @param type [Puppet::Pops::Types::PObjectType] the type to set for the binding # @api public # def type(type) model.type = type self end # Sets the type of the binding to Integer. # @return [Puppet::Pops::Types::PIntegerType] the type # @api public def integer() type(T.integer()) end # Sets the type of the binding to Float. # @return [Puppet::Pops::Types::PFloatType] the type # @api public def float() type(T.float()) end # Sets the type of the binding to Boolean. # @return [Puppet::Pops::Types::PBooleanType] the type # @api public def boolean() type(T.boolean()) end # Sets the type of the binding to String. # @return [Puppet::Pops::Types::PStringType] the type # @api public def string() type(T.string()) end # Sets the type of the binding to Pattern. - # @return [Puppet::Pops::Types::PPatternType] the type + # @return [Puppet::Pops::Types::PRegexpType] the type # @api public def pattern() type(T.pattern()) end # Sets the type of the binding to the abstract type Literal. # @return [Puppet::Pops::Types::PLiteralType] the type # @api public def literal() type(T.literal()) end # Sets the type of the binding to the abstract type Data. # @return [Puppet::Pops::Types::PDataType] the type # @api public def data() type(T.data()) end # Sets the type of the binding to Array[Data]. # @return [Puppet::Pops::Types::PArrayType] the type # @api public def array_of_data() type(T.array_of_data()) end # Sets the type of the binding to Array[T], where T is given. # @param t [Puppet::Pops::Types::PObjectType] the type of the elements of the array # @return [Puppet::Pops::Types::PArrayType] the type # @api public def array_of(t) type(T.array_of(t)) end # Sets the type of the binding to Hash[Literal, Data]. # @return [Puppet::Pops::Types::PHashType] the type # @api public def hash_of_data() type(T.hash_of_data()) end # Sets type of the binding to `Hash[Literal, t]`. # To also limit the key type, use {#type} and give it a fully specified # hash using {#type_factory} and then `hash_of(value_type, key_type)`. # @return [Puppet::Pops::Types::PHashType] the type # @api public def hash_of(t) type(T.hash_of(t)) end # Sets the type of the binding based on the given argument. # @overload instance_of(t) # The same as calling {#type} with `t`. # @param t [Puppet::Pops::Types::PObjectType] the type # @overload instance_of(o) # Infers the type from the given Ruby object and sets that as the type - i.e. "set the type # of the binding to be that of the given data object". # @param o [Object] the object to infer the type from # @overload instance_of(c) # @param c [Class] the Class to base the type on. # Sets the type based on the given ruby class. The result is one of the specific puppet types # if the class can be represented by a specific type, or the open ended PRubyType otherwise. # @overload instance_of(s) # The same as using a class, but instead of giving a class instance, the class is expressed using its fully # qualified name. This method of specifying the type allows late binding (the class does not have to be loaded # before it can be used in a binding). # @param s [String] the fully qualified classname to base the type on. # @return the resulting type # @api public # def instance_of(t) type(T.type_of(t)) end # Provides convenient access to the type factory. # This is intended to be used when methods taking a type as argument i.e. {#type}, {#array_of}, {#hash_of}, and {#instance_of}. # @note # The type factory is also available via the constant {T}. # @api public def type_factory Puppet::Pops::Types::TypeFactory end # Sets the binding's producer to a singleton producer, if given argument is a value, a literal producer is created for it. # To create a producer producing an instance of a class with lazy loading of the class, use {#to_instance}. # # @overload to(a_literal) # Sets a constant producer in the binding. # @overload to(a_class, *args) # Sets an Instantiating producer (producing an instance of the given class) # @overload to(a_producer_descriptor) # Sets the producer from the given producer descriptor # @return [BindingsBuilder] self # @api public # def to(producer, *args) case producer when Class producer = Puppet::Pops::Binder::BindingsFactory.instance_producer(producer.name, *args) when Puppet::Pops::Model::Expression producer = Puppet::Pops::Binder::BindingsFactory.evaluating_producer(producer) when Puppet::Pops::Binder::Bindings::ProducerDescriptor else # If given producer is not a producer, create a literal producer producer = Puppet::Pops::Binder::BindingsFactory.literal_producer(producer) end model.producer = producer self end # Sets the binding's producer to a producer of an instance of given class (a String class name, or a Class instance). # Use a string class name when lazy loading of the class is wanted. # # @overload to_instance(class_name, *args) # @param class_name [String] the name of the class to instantiate # @param args [Object] optional arguments to the constructor # @overload to_instance(a_class) # @param a_class [Class] the class to instantiate # @param args [Object] optional arguments to the constructor # def to_instance(type, *args) class_name = case type when Class type.name when String type else raise ArgumentError, "to_instance accepts String (a class name), or a Class.*args got: #{type.class}." end model.producer = Puppet::Pops::Binder::BindingsFactory.instance_producer(class_name, *args) end # Sets the binding's producer to a singleton producer # @overload to_producer(a_producer) # Sets the producer to an instantiated producer. The resulting model can not be serialized as a consequence as there # is no meta-model describing the specialized producer. Use this only in exceptional cases, or where there is never the # need to serialize the model. # @param a_producer [Puppet::Pops::Binder::Producers::Producer] an instantiated producer, not serializeable ! # # @overload to_producer(a_class, *args) # @param a_class [Class] the class to create an instance of # @param args [Object] the arguments to the given class' new # # @overload to_producer(a_producer_descriptor) # @param a_producer_descriptor [Puppet::Pops::Binder::Bindings::ProducerDescriptor] a descriptor # producing Puppet::Pops::Binder::Producers::Producer # # @api public # def to_producer(producer, *args) case producer when Class producer = Puppet::Pops::Binder::BindingsFactory.instance_producer(producer.name, *args) when Puppet::Pops::Binder::Bindings::ProducerDescriptor when Puppet::Pops::Binder::Producers::Producer # a custom producer instance producer = Puppet::Pops::Binder::BindingsFactory.literal_producer(producer) else raise ArgumentError, "Given producer argument is none of a producer descriptor, a class, or a producer" end metaproducer = Puppet::Pops::Binder::BindingsFactory.producer_producer(producer) model.producer = metaproducer self end # Sets the binding's producer to a series of producers. # Use this when you want to produce a different producer on each request for a producer # # @overload to_producer(a_producer) # Sets the producer to an instantiated producer. The resulting model can not be serialized as a consequence as there # is no meta-model describing the specialized producer. Use this only in exceptional cases, or where there is never the # need to serialize the model. # @param a_producer [Puppet::Pops::Binder::Producers::Producer] an instantiated producer, not serializeable ! # # @overload to_producer(a_class, *args) # @param a_class [Class] the class to create an instance of # @param args [Object] the arguments to the given class' new # # @overload to_producer(a_producer_descriptor) # @param a_producer_descriptor [Puppet::Pops::Binder::Bindings::ProducerDescriptor] a descriptor # producing Puppet::Pops::Binder::Producers::Producer # # @api public # def to_producer_series(producer, *args) case producer when Class producer = Puppet::Pops::Binder::BindingsFactory.instance_producer(producer.name, *args) when Puppet::Pops::Binder::Bindings::ProducerDescriptor when Puppet::Pops::Binder::Producers::Producer # a custom producer instance producer = Puppet::Pops::Binder::BindingsFactory.literal_producer(producer) else raise ArgumentError, "Given producer argument is none of a producer descriptor, a class, or a producer" end non_caching = Puppet::Pops::Binder::Bindings::NonCachingProducerDescriptor.new() non_caching.producer = producer metaproducer = Puppet::Pops::Binder::BindingsFactory.producer_producer(non_caching) non_caching = Puppet::Pops::Binder::Bindings::NonCachingProducerDescriptor.new() non_caching.producer = metaproducer model.producer = non_caching self end # Sets the binding's producer to a "non singleton" producer (each call to produce produces a new instance/copy). # @overload to_series_of(a_literal) # a constant producer # @overload to_series_of(a_class, *args) # Instantiating producer # @overload to_series_of(a_producer_descriptor) # a given producer # # @api public # def to_series_of(producer, *args) case producer when Class producer = Puppet::Pops::Binder::BindingsFactory.instance_producer(producer.name, *args) when Puppet::Pops::Binder::Bindings::ProducerDescriptor else # If given producer is not a producer, create a literal producer producer = Puppet::Pops::Binder::BindingsFactory.literal_producer(producer) end non_caching = Puppet::Pops::Binder::Bindings::NonCachingProducerDescriptor.new() non_caching.producer = producer model.producer = non_caching self end # Sets the binding's producer to one that performs a lookup of another key # @overload to_lookup_of(type, name) # @overload to_lookup_of(name) # @api public # def to_lookup_of(type, name=nil) unless name name = type type = Puppet::Pops::Types::TypeFactory.data() end model.producer = Puppet::Pops::Binder::BindingsFactory.lookup_producer(type, name) self end # Sets the binding's producer to a one that performs a lookup of another key and they applies hash lookup on # the result. # # @overload to_lookup_of(type, name) # @overload to_lookup_of(name) # @api public # def to_hash_lookup_of(type, name, key) model.producer = Puppet::Pops::Binder::BindingsFactory.hash_lookup_producer(type, name, key) self end # Sets the binding's producer to one that produces the first found lookup of another key # @param list_of_lookups [Array] array of arrays [type name], or just name (implies data) # @example # binder.bind().name('foo').to_first_found('fee', 'fum', 'extended-bar') # binder.bind().name('foo').to_first_found( # [T.ruby(ThisClass), 'fee'], # [T.ruby(ThatClass), 'fum'], # 'extended-bar') # @api public # def to_first_found(*list_of_lookups) producers = list_of_lookups.collect do |entry| if entry.is_a?(Array) case entry.size when 2 Puppet::Pops::Binder::BindingsFactory.lookup_producer(entry[0], entry[1]) when 1 Puppet::Pops::Binder::BindingsFactory.lookup_producer(Puppet::Pops::Types::TypeFactory.data(), entry[0]) else raise ArgumentError, "Not an array of [type, name], name, or [name]" end else Puppet::Pops::Binder::BindingsFactory.lookup_producer(T.data(), entry) end end model.producer = Puppet::Pops::Binder::BindingsFactory.first_found_producer(*producers) self end # Sets options to the producer. # See the respective producer for the options it supports. All producers supports the option `:transformer`, a # puppet or ruby lambda that is evaluated with the produced result as an argument. The ruby lambda gets scope and # value as arguments. # @note # A Ruby lambda is not cross platform safe. Use a puppet lambda if you want a bindings model that is. # # @api public def producer_options(options) options.each do |k, v| arg = Puppet::Pops::Binder::Bindings::NamedArgument.new() arg.name = k.to_s arg.value = v model.addProducer_args(arg) end self end end # A builder specialized for multibind - checks that type is Array or Hash based. A new builder sets the # multibinding to be of type Hash[Data]. # # @api public class MultibindingsBuilder < BindingsBuilder # Constraints type to be one of {Puppet::Pops::Types::PArrayType PArrayType}, or {Puppet::Pops::Types::PHashType PHashType}. # @raise [ArgumentError] if type constraint is not met. # @api public def type(type) unless type.class == Puppet::Pops::Types::PArrayType || type.class == Puppet::Pops::Types::PHashType raise ArgumentError, "Wrong type; only PArrayType, or PHashType allowed, got '#{type.to_s}'" end model.type = type self end # Overrides the default implementation that will raise an exception as a multibind requires a hash type. # Thus, if nothing else is requested, a multibind will be configured as Hash[Data]. # def data() hash_of_data() end end # Produces a ContributedBindings. # A ContributedBindings is used by bindings providers to return a set of named bindings. # # @param name [String] the name of the contributed bindings (for human use in messages/logs only) # @param named_bindings [Puppet::Pops::Binder::Bindings::NamedBindings, Array] the # named bindings to include # @param effective_categories [Puppet::Pops::Binder::Bindings::EffectiveCategories] the contributors opinion about categorization # this is used to ensure consistent use of categories. # def self.contributed_bindings(name, named_bindings, effective_categories) cb = Puppet::Pops::Binder::Bindings::ContributedBindings.new() cb.name = name named_bindings = [named_bindings] unless named_bindings.is_a?(Array) named_bindings.each {|b| cb.addBindings(b) } cb.effective_categories = effective_categories cb end # Creates a named binding container, the top bindings model object. # A NamedBindings is typically produced by a bindings provider. # # The created container is wrapped in a BindingsContainerBuilder for further detailing. # Unwrap the built result when done. # @api public # def self.named_bindings(name, &block) binding = Puppet::Pops::Binder::Bindings::NamedBindings.new() binding.name = name builder = BindingsContainerBuilder.new(binding) builder.instance_eval(&block) if block_given? builder end # This variant of {named_bindings} evaluates the given block as a method on an anonymous class, # thus, if the block defines methods or do something with the class itself, this does not pollute # the base class (BindingsContainerBuilder). # @api private # def self.safe_named_bindings(name, scope, &block) binding = Puppet::Pops::Binder::Bindings::NamedBindings.new() binding.name = name anon = Class.new(BindingsContainerBuilder) do def initialize(b) super b end end anon.send(:define_method, :_produce, block) builder = anon.new(binding) case block.arity when 0 builder._produce() when 1 builder._produce(scope) end builder end # Creates a literal/constant producer # @param value [Object] the value to produce # @return [Puppet::Pops::Binder::Bindings::ProducerDescriptor] a producer description # @api public # def self.literal_producer(value) producer = Puppet::Pops::Binder::Bindings::ConstantProducerDescriptor.new() producer.value = value producer end # Creates a non caching producer # @param producer [Puppet::Pops::Binder::Bindings::Producer] the producer to make non caching # @return [Puppet::Pops::Binder::Bindings::ProducerDescriptor] a producer description # @api public # def self.non_caching_producer(producer) p = Puppet::Pops::Binder::Bindings::NonCachingProducerDescriptor.new() p.producer = producer p end # Creates a producer producer # @param producer [Puppet::Pops::Binder::Bindings::Producer] a producer producing a Producer. # @return [Puppet::Pops::Binder::Bindings::ProducerDescriptor] a producer description # @api public # def self.producer_producer(producer) p = Puppet::Pops::Binder::Bindings::ProducerProducerDescriptor.new() p.producer = producer p end # Creates an instance producer # An instance producer creates a new instance of a class. # If the class implements the class method `inject` this method is called instead of `new` to allow further lookups # to take place. This is referred to as *assisted inject*. If the class method `inject` is missing, the regular `new` method # is called. # # @param class_name [String] the name of the class # @param args[Object] arguments to the class' `new` method. # @return [Puppet::Pops::Binder::Bindings::ProducerDescriptor] a producer description # @api public # def self.instance_producer(class_name, *args) p = Puppet::Pops::Binder::Bindings::InstanceProducerDescriptor.new() p.class_name = class_name args.each {|a| p.addArguments(a) } p end # Creates a Producer that looks up a value. # @param type [Puppet::Pops::Types::PObjectType] the type to lookup # @param name [String] the name to lookup # @return [Puppet::Pops::Binder::Bindings::ProducerDescriptor] a producer description # @api public def self.lookup_producer(type, name) p = Puppet::Pops::Binder::Bindings::LookupProducerDescriptor.new() p.type = type p.name = name p end # Creates a Hash lookup producer that looks up a hash value, and then a key in the hash. # # @return [Puppet::Pops::Binder::Bindings::ProducerDescriptor] a producer description # @param type [Puppet::Pops::Types::PObjectType] the type to lookup (i.e. a Hash of some key/value type). # @param name [String] the name to lookup # @param key [Object] the key to lookup in the looked up hash (type should comply with given key type). # @api public # def self.hash_lookup_producer(type, name, key) p = Puppet::Pops::Binder::Bindings::HashLookupProducerDescriptor.new() p.type = type p.name = name p.key = key p end # Creates a first-found producer that looks up from a given series of keys. The first found looked up # value will be produced. # @param producers [Array] the producers to consult in given order # @return [Puppet::Pops::Binder::Bindings::ProducerDescriptor] a producer descriptor # @api public def self.first_found_producer(*producers) p = Puppet::Pops::Binder::Bindings::FirstFoundProducerDescriptor.new() producers.each {|p2| p.addProducers(p2) } p end # Creates an evaluating producer that evaluates a puppet expression. # A puppet expression is most conveniently created by using the {Puppet::Pops::Parser::EvaluatingParser EvaluatingParser} as it performs # all set up and validation of the parsed source. Two convenience methods are used to parse an expression, or parse a ruby string # as a puppet string. See methods {puppet_expression}, {puppet_string} and {parser} for more information. # # @example producing a puppet expression # expr = puppet_string("Interpolated $fqdn", __FILE__) # # @param expression [Puppet::Pops::Model::Expression] a puppet DSL expression as producer by the eparser. # @return [Puppet::Pops::Binder::Bindings::ProducerDescriptor] a producer descriptor # @api public # def self.evaluating_producer(expression) p = Puppet::Pops::Binder::Bindings::EvaluatingProducerDescriptor.new() p.expression = expression p end # Creates an EffectiveCategories from a list of tuples `[categorization category ...]`, or `[[categorization category] ...]` # This method is used by backends to create a model of the effective categories. # @api public # def self.categories(tuple_array) result = Puppet::Pops::Binder::Bindings::EffectiveCategories.new() tuple_array.flatten.each_slice(2) do |c| cat = Puppet::Pops::Binder::Bindings::Category.new() cat.categorization = c[0] cat.value = c[1] result.addCategories(cat) end result end # Creates a NamedLayer. This is used by the bindings system to create a model of the layers. # # @api public # def self.named_layer(name, *bindings) result = Puppet::Pops::Binder::Bindings::NamedLayer.new() result.name = name bindings.each { |b| result.addBindings(b) } result end # Create a LayeredBindings. This is used by the bindings system to create a model of all given layers. # @param named_layers [Puppet::Pops::Binder::Bindings::NamedLayer] one or more named layers # @return [Puppet::Pops::Binder::Bindings::LayeredBindings] the constructed layered bindings. # @api public # def self.layered_bindings(*named_layers) result = Puppet::Pops::Binder::Bindings::LayeredBindings.new() named_layers.each {|b| result.addLayers(b) } result end # @return [Puppet::Pops::Parser::EvaluatingParser] a parser for puppet expressions def self.parser @parser ||= Puppet::Pops::Parser::EvaluatingParser.new() end # Parses and produces a puppet expression from the given string. # @param string [String] puppet source e.g. "1 + 2" # @param source_file [String] the source location, typically `__File__` # @return [Puppet::Pops::Model::Expression] an expression (that can be bound) # @api public # def self.puppet_expression(string, source_file) parser.parse_string(string, source_file).current end # Parses and produces a puppet string expression from the given string. # The string will automatically be quoted and special characters escaped. # As an example if given the (ruby) string "Hi\nMary" it is transformed to # the puppet string (illustrated with a ruby string) "\"Hi\\nMary\”" before being # parsed. # # @param string [String] puppet source e.g. "On node $!{fqdn}" # @param source_file [String] the source location, typically `__File__` # @return [Puppet::Pops::Model::Expression] an expression (that can be bound) # @api public # def self.puppet_string(string, source_file) parser.parse_string(parser.quote(string), source_file).current end end diff --git a/lib/puppet/pops/binder/hiera2/bindings_provider.rb b/lib/puppet/pops/binder/hiera2/bindings_provider.rb index d0e5d9a5e..06285bb1c 100644 --- a/lib/puppet/pops/binder/hiera2/bindings_provider.rb +++ b/lib/puppet/pops/binder/hiera2/bindings_provider.rb @@ -1,148 +1,152 @@ module Puppet::Pops::Binder::Hiera2 Model = Puppet::Pops::Model # A BindingsProvider instance is used for creating a bindings model from a module directory # @api public # class BindingsProvider # The resulting name of loaded bindings (given when initializing) attr_reader :name # Creates a new BindingsProvider by reading the hiera_conf.yaml configuration file. Problems # with the configuration are reported propagated to the acceptor # # @param name [String] the name to assign to the result (and in error messages if there is no result) # @param hiera_config_dir [String] Path to the directory containing a hiera_config # @param acceptor [Puppet::Pops::Validation::Acceptor] Acceptor that will receive diagnostics def initialize(name, hiera_config_dir, acceptor) @name = name @parser = Puppet::Pops::Parser::EvaluatingParser.new() @diagnostics = DiagnosticProducer.new(acceptor) @type_calculator = Puppet::Pops::Types::TypeCalculator.new() @config = Config.new(hiera_config_dir, @diagnostics) end # Loads a bindings model using the hierarchy and backends configured for this instance. # # @param scope [Puppet::Parser::Scope] The hash used when expanding # @return [Puppet::Pops::Binder::Bindings::ContributedBindings] A bindings model with effective categories def load_bindings(scope) backends = BackendHelper.new(scope) factory = Puppet::Pops::Binder::BindingsFactory result = factory.named_bindings(name) hierarchy = {} precedence = [] @config.hierarchy.each do |key, value, path| source_file = File.join(@config.module_dir, 'hiera.yaml') category_value = @parser.evaluate_string(scope, @parser.quote(value), source_file) hierarchy[key] = { :bindings => result.when_in_category(key, category_value), :path => @parser.evaluate_string(scope, @parser.quote(path)), :unique_keys =>Set.new()} precedence << [key, category_value] end @config.backends.each do |backend_key| backend = backends[backend_key] hierarchy.each_pair do |hier_key, hier_val| bindings = hier_val[:bindings] unique_keys = hier_val[:unique_keys] hiera_data_file_path = hier_val[:path] backend.read_data(@config.module_dir, hiera_data_file_path).each_pair do |key, value| if unique_keys.add?(key) b = bindings.bind().name(key) # Transform value into a Model::Expression expr = build_expr(value, hiera_data_file_path) if is_constant?(expr) # The value is constant so toss the expression b.type(@type_calculator.infer(value)).to(value) else # Use an evaluating producer for the binding b.to(expr) end end end end end factory.contributed_bindings(name, result.model, factory.categories(precedence)) end private # @return true unless the expression is a Model::ConcatenatedString or # somehow contains one def is_constant?(expr) if expr.is_a?(Model::ConcatenatedString) false else !expr.eAllContents.any? { |v| v.is_a?(Model::ConcatenatedString) } end end # Transform the value into a Model::Expression. Strings are parsed using # the Pops::Parser::Parser to produce either Model::LiteralString or Model::ConcatenatedString # # @param value [Object] May be an String, Number, TrueClass, FalseClass, or NilClass nested to any depth using Hash or Array. # @param hiera_data_file_path [String] The source_file used when reporting errors # @return [Model::Expression] The expression that corresponds to the value def build_expr(value, hiera_data_file_path) case value when Symbol value.to_s when String @parser.parse_string(@parser.quote(value)).current when Hash value.inject(Model::LiteralHash.new) do |h,(k,v)| e = Model::KeyedEntry.new e.key = build_expr(k, hiera_data_file_path) e.value = build_expr(v, hiera_data_file_path) h.addEntries(e) h end when Enumerable value.inject(Model::LiteralList.new) {|a,v| a.addValues(build_expr(v, hiera_data_file_path)); a } - when Numeric - expr = Model::LiteralNumber.new + when Integer + expr = Model::LiteralInteger.new + expr.value = value; + expr + when Float + expr = Model::LiteralFloat.new expr.value = value; expr when TrueClass, FalseClass expr = Model::LiteralBoolean.new expr.value = value; expr when NilClass Model::Nop.new else @diagnostics.accept(Issues::UNABLE_TO_PARSE_INSTANCE, value.class.name) nil end end end # @api private class BackendHelper T = Puppet::Pops::Types::TypeFactory HASH_OF_BACKENDS = T.hash_of(T.type_of('Puppetx::Puppet::Hiera2Backend')) def initialize(scope) @scope = scope @cache = nil end def [] (backend_key) load_backends unless @cache @cache[backend_key] end def load_backends @cache = @scope.compiler.boot_injector.lookup(@scope, HASH_OF_BACKENDS, Puppetx::HIERA2_BACKENDS) || {} end end end diff --git a/lib/puppet/pops/containment.rb b/lib/puppet/pops/containment.rb index a019044b0..b28695dc9 100644 --- a/lib/puppet/pops/containment.rb +++ b/lib/puppet/pops/containment.rb @@ -1,37 +1,75 @@ # FIXME: This module should be updated when a newer version of RGen (>0.6.2) adds required meta model "e-method" supports. # +require 'rgen/ecore/ecore' module Puppet::Pops::Containment # Returns Enumerable, thus allowing # some_element.eAllContents each {|contained| } # This is a depth first enumeration where parent appears before children. # @note the top-most object itself is not included in the enumeration, only what it contains. def eAllContents EAllContentsEnumerator.new(self) end class EAllContentsEnumerator include Enumerable def initialize o @element = o + @@cache ||= {} end def each &block if block_given? eAllContents(@element, &block) @element else self end end def eAllContents(element, &block) - element.class.ecore.eAllReferences.select{|r| r.containment}.each do |r| - children = element.getGenericAsArray(r.name) - children.each do |c| - block.call(c) - eAllContents(c, &block) + # This method is performance critical and code has been manually in-lined. + # Resist the urge to make this pretty. + # The slow way is element.eAllContainments.each {|c| element.getGenericsAsArray(c.name) } + # + (@@cache[element.class] || all_containment_getters(element)).each do |r| + children = element.send(r) + if children.is_a?(Array) + children.each do |c| + yield c + eAllContents(c, &block) + end + elsif !children.nil? + yield children + eAllContents(children, &block) end end end + + private + + def all_containment_getters(element) + elem_class = element.class + containments = [] + collect_getters(elem_class.ecore, containments) + @@cache[elem_class] = containments + end + + def collect_getters(eclass, containments) + eclass.eStructuralFeatures.select {|r| r.is_a?(RGen::ECore::EReference) && r.containment}.each do |r| + n = r.name + containments << :"get#{n[0..0].upcase + ( n[1..-1] || "" )}" + end + eclass.eSuperTypes.each do |t| + if cached = @@cache[ t.instanceClass ] + containments.concat(cached) + else + super_containments = [] + collect_getters(t, super_containments) + @@cache[ t.instanceClass ] = super_containments + containments.concat(super_containments) + end + end + end + end end diff --git a/lib/puppet/pops/evaluator/access_operator.rb b/lib/puppet/pops/evaluator/access_operator.rb new file mode 100644 index 000000000..aedb122c5 --- /dev/null +++ b/lib/puppet/pops/evaluator/access_operator.rb @@ -0,0 +1,406 @@ +# AccessOperator handles operator [] +# This operator is part of evaluation. +# +class Puppet::Pops::Evaluator::AccessOperator + # Provides access to the Puppet 3.x runtime (scope, etc.) + # This separation has been made to make it easier to later migrate the evaluator to an improved runtime. + # + include Puppet::Pops::Evaluator::Runtime3Support + + Issues = Puppet::Pops::Issues + + attr_reader :semantic + + # Initialize with AccessExpression to enable reporting issues + # @param access_expression [Puppet::Pops::Model::AccessExpression] the semantic object being evaluated + # @return [void] + # + def initialize(access_expression) + @@access_visitor ||= Puppet::Pops::Visitor.new(self, "access", 2, nil) + @semantic = access_expression + end + + def access (o, scope, *keys) + @@access_visitor.visit_this_2(self, o, scope, keys) + end + + protected + + def access_Object(o, scope, keys) + fail(Issues::OPERATOR_NOT_APPLICABLE, @semantic.left_expr, :operator=>'[]', :left_value => o) + end + + def access_String(o, scope, keys) + result = case keys.size + when 0 + fail(Puppet::Pops::Issues::BAD_STRING_SLICE_ARITY, @semantic.left_expr, {:actual => keys.size}) + when 1 + # Note that Ruby 1.8.7 requires a length of 1 to produce a String + k1 = coerce_numeric(keys[0], @semantic.keys, scope) + bad_access_key_type(o, 0, k1, Integer) unless k1.is_a?(Integer) + k2 = 1 + k1 = k1 < 0 ? o.length + k1 : k1 # abs pos + # if k1 is outside, a length of 1 always produces an empty string + if k1 < 0 + '' + else + o[ k1, k2 ] + end + when 2 + k1 = coerce_numeric(keys[0], @semantic.keys, scope) + k2 = coerce_numeric(keys[1], @semantic.keys, scope) + [k1, k2].each_with_index { |k,i| bad_access_key_type(o, i, k, Integer) unless k.is_a?(Integer) } + + k1 = k1 < 0 ? o.length + k1 : k1 # abs pos (negative is count from end) + k2 = k2 < 0 ? o.length - k1 + k2 + 1 : k2 # abs length (negative k2 is length from pos to end count) + # if k1 is outside, adjust to first position, and adjust length + if k1 < 0 + k2 = k2 + k1 + k1 = 0 + end + o[ k1, k2 ] + else + fail(Puppet::Pops::Issues::BAD_STRING_SLICE_ARITY, @semantic.left_expr, {:actual => keys.size}) + end + # Specified as: an index outside of range, or empty result == empty string + (result.nil? || result.empty?) ? '' : result + end + + # Parameterizes a PRegexp Type with a pattern string or r ruby egexp + # + def access_PRegexpType(o, scope, keys) + unless keys.size == 1 + blamed = keys.size == 0 ? @semantic : @semantic.keys[2] + fail(Puppet::Pops::Issues::BAD_TYPE_SLICE_ARITY, blamed, :base_type => o, :min=>1, :actual => keys.size) + end + assert_keys(keys, o, 1, 1, String, Regexp) + Puppet::Pops::Types::TypeFactory.regexp(*keys) + end + + # Evaluates [] with 1 or 2 arguments. One argument is an index lookup, two arguments is a slice from/to. + # + def access_Array(o, scope, keys) + case keys.size + when 0 + fail(Puppet::Pops::Issues::BAD_ARRAY_SLICE_ARITY, @semantic.left_expr, {:actual => keys.size}) + when 1 + k = coerce_numeric(keys[0], @semantic.keys[0], scope) + unless k.is_a?(Integer) + bad_access_key_type(o, 0, k, Integer) + end + o[k] + when 2 + # A slice [from, to] with support for -1 to mean start, or end respectively. + k1 = coerce_numeric(keys[0], @semantic.keys[0], scope) + k2 = coerce_numeric(keys[1], @semantic.keys[1], scope) + + [k1, k2].each_with_index { |k,i| bad_access_key_type(o, i, k, Integer) unless k.is_a?(Integer) } + + # Help confused Ruby do the right thing (it truncates to the right, but negative index + length can never overlap + # the available range. + k1 = k1 < 0 ? o.length + k1 : k1 # abs pos (negative is count from end) + k2 = k2 < 0 ? o.length - k1 + k2 + 1 : k2 # abs length (negative k2 is length from pos to end count) + # if k1 is outside, adjust to first position, and adjust length + if k1 < 0 + k2 = k2 + k1 + k1 = 0 + end + # Help ruby always return empty array when asking for a sub array + result = o[ k1, k2 ] + result.nil? ? [] : result + else + fail(Puppet::Pops::Issues::BAD_ARRAY_SLICE_ARITY, @semantic.left_expr, {:actual => keys.size}) + end + end + + + # Evaluates [] with support for one or more arguments. If more than one argument is used, the result + # is an array with each lookup. + # + def access_Hash(o, scope, keys) + # Look up key in hash, if key is nil or :undef, try alternate form before giving up. + # This makes :undef and nil "be the same key". (The laternative is to always only write one or the other + # in all hashes - that is much harder to guarantee since the Hash is a regular Ruby hash. + # + result = keys.collect do |k| + o.fetch(k) do |key| + case key + when nil + o[:undef] + when :undef + o[:nil] + else + nil + end + end + end + case result.size + when 0 + fail(Puppet::Pops::Issues::BAD_HASH_SLICE_ARITY, @semantic.left_expr, {:actual => keys.size}) + when 1 + result.pop + else + # remove nil elements and return + result.compact! + result + end + end + + # Ruby does not have an infinity constant. TODO: Consider having one constant in Puppet. Now it is in several places. + INFINITY = 1.0 / 0.0 + + def access_PEnumType(o, scope, keys) + assert_keys(keys, o, 1, INFINITY, String) + Puppet::Pops::Types::TypeFactory.enum(*keys) + end + + def access_PVariantType(o, scope, keys) + assert_keys(keys, o, 1, INFINITY, Puppet::Pops::Types::PAbstractType) + Puppet::Pops::Types::TypeFactory.variant(*keys) + end + + def access_PStringType(o, scope, keys) + assert_keys(keys, o, 1, INFINITY, String) + Puppet::Pops::Types::TypeFactory.string(*keys) + end + + # Asserts type of each key and calls fail with BAD_TYPE_SPECIFICATION + # @param keys [Array] the evaluated keys + # @param o [Object] evaluated LHS reported as :base_type + # @param min [Integer] the minimum number of keys (typically 1) + # @param max [Numeric] the maximum number of keys (use same as min, specific number, or INFINITY) + # @param allowed_classes [Class] a variable number of classes that each key must be an instance of (any) + # @api private + # + def assert_keys(keys, o, min, max, *allowed_classes) + size = keys.size + unless size.between?(min, max || INFINITY) + fail(Puppet::Pops::Issues::BAD_TYPE_SLICE_ARITY, blamed, :base_type => o, :min=>1, :max => max, :actual => keys.size) + end + keys.each_with_index do |k, i| + unless allowed_classes.any? {|clazz| k.is_a?(clazz) } + bad_type_specialization_key_type(o, i, k, allowed_classes) + end + end + end + + def bad_access_key_type(lhs, key_index, actual, *expected_classes) + fail(Puppet::Pops::Issues::BAD_SLICE_KEY_TYPE, @semantic.keys[key_index], { + :left_value => lhs, + :actual => bad_key_type_name(actual), + :expected_classes => expected_classes + }) + end + + def bad_key_type_name(actual) + case actual + when nil, :undef + 'Undef' + when :default + 'Default' + else + actual.class.name + end + end + + def bad_type_specialization_key_type(type, key_index, actual, *expected_classes) + fail(Puppet::Pops::Issues::BAD_TYPE_SPECIALIZATION, @semantic.keys[key_index], { + :type => type, + :message => "Cannot use #{bad_key_type_name(actual)} where #{expected_classes.join(' or ')} is expected" + }) + end + + def access_PPatternType(o, scope, keys) + assert_keys(keys, o, 1, INFINITY, String, Regexp) + Puppet::Pops::Types::TypeFactory.pattern(*keys) + end + + def access_PIntegerType(o, scope, keys) + unless keys.size.between?(1, 2) + fail(Puppet::Pops::Issues::BAD_INTEGER_SLICE_ARITY, @semantic, {:actual => keys.size}) + end + keys.each_with_index do |x, index| + fail(Puppet::Pops::Issues::BAD_INTEGER_SLICE_TYPE, @semantic.keys[index], + {:actual => x.class}) unless (x.is_a?(Integer) || x == :default) + end + ranged_integer = Puppet::Pops::Types::PIntegerType.new() + from, to = keys + ranged_integer.from = from == :default ? nil : from + ranged_integer.to = to == :default ? nil : to + ranged_integer + end + + def access_PFloatType(o, scope, keys) + unless keys.size.between?(1, 2) + fail(Puppet::Pops::Issues::BAD_FLOAT_SLICE_ARITY, @semantic, {:actual => keys.size}) + end + keys.each_with_index do |x, index| + fail(Puppet::Pops::Issues::BAD_FLOAT_SLICE_TYPE, @semantic.keys[index], + {:actual => x.class}) unless (x.is_a?(Float) || x.is_a?(Integer) || x == :default) + end + ranged_float = Puppet::Pops::Types::PFloatType.new() + from, to = keys + ranged_float.from = from == :default || from.nil? ? nil : Float(from) + ranged_float.to = to == :default || to.nil? ? nil : Float(to) + ranged_float + end + + # A Hash can create a new Hash type, one arg sets value type, two args sets key and value type in new type + # It is not possible to create a collection of Hash types. + # + def access_PHashType(o, scope, keys) + keys.each_with_index do |k, index| + unless k.is_a?(Puppet::Pops::Types::PAbstractType) + fail(Puppet::Pops::Issues::BAD_TYPE_SLICE_TYPE, @semantic.keys[index], {:base_type => 'Hash-Type', :actual => k.class}) + end + end + case keys.size + when 1 + result = Puppet::Pops::Types::PHashType.new() + result.key_type = Marshal.load(Marshal.dump(o.key_type)) + result.element_type = keys[0] + result + when 2 + result = Puppet::Pops::Types::PHashType.new() + result.key_type = keys[0] + result.element_type = keys[1] + result + else + fail(Puppet::Pops::Issues::BAD_TYPE_SLICE_ARITY, @semantic, {:base_type => 'Hash-Type', :min => 1, :max => 2, :actual => keys.size}) + end + end + + # An Array can create a new Array type. It is not possible to create a collection of Array types. + # + def access_PArrayType(o, scope, keys) + if keys.size == 1 + unless keys[0].is_a?(Puppet::Pops::Types::PAbstractType) + fail(Puppet::Pops::Issues::BAD_TYPE_SLICE_TYPE, @semantic.keys[0], {:base_type => 'Array-Type', :actual => keys[0].class}) + end + result = Puppet::Pops::Types::PArrayType.new() + result.element_type = keys[0] + result + else + fail(Puppet::Pops::Issues::BAD_TYPE_SLICE_ARITY, @semantic, {:base_type => 'Array-Type', :min => 1, :actual => keys.size}) + end + end + + # A Resource can create a new more specific Resource type, and/or an array of resource types + # If the given type has title set, it can not be specified further. + # @example + # Resource[File] # => File + # Resource[File, 'foo'] # => File[foo] + # Resource[File. 'foo', 'bar'] # => [File[foo], File[bar]] + # File['foo', 'bar'] # => [File[foo], File[bar]] + # File['foo']['bar'] # => Value of the 'bar' parameter in the File['foo'] resource + # Resource[File]['foo', 'bar'] # => [File[Foo], File[bar]] + # Resource[File, 'foo', 'bar'] # => [File[foo], File[bar]] + # Resource[File, 'foo']['bar'] # => Value of the 'bar' parameter in the File['foo'] resource + # + def access_PResourceType(o, scope, keys) + if keys.size == 0 + fail(Puppet::Pops::Issues::BAD_TYPE_SLICE_ARITY, o, + :base_type => Puppet::Pops::Types::TypeCalculator.new().string(o), :min => 1, :actual => 0) + end + if !o.title.nil? + # lookup resource and return one or more parameter values + resource = find_resource(scope, o.type_name, o.title) + unless resource + fail(Puppet::Pops::Issues::UNKNOWN_RESOURCE, @semantic, {:type_name => o.type_name, :title => o.title}) + end + result = keys.map do |k| + unless is_parameter_of_resource?(scope, resource, k) + fail(Puppet::Pops::Issues::UNKNOWN_RESOURCE_PARAMETER, @semantic, + {:type_name => o.type_name, :title => o.title, :param_name=>k}) + end + get_resource_parameter_value(scope, resource, k) + end + return result.size <= 1 ? result.pop : result + end + + # type_name is LHS type_name if set, else the first given arg + keys_orig_size = keys.size + type_name = o.type_name || keys.shift + type_name = case type_name + when Puppet::Pops::Types::PResourceType + type_name.type_name + when String + type_name.downcase + else + blame = keys_orig_size != keys.size ? @semantic.keys[0] : @semantic.left_expr + fail(Puppet::Pops::Issues::ILLEGAL_RESOURCE_SPECIALIZATION, blame, {:actual => type_name.class}) + end + + keys = [:no_title] if keys.size < 1 # if there was only a type_name and it was consumed + result = keys.each_with_index.map do |t, i| + unless t.is_a?(String) || t == :no_title + type_to_report = case t + when nil, :undef + 'Undef' + when :default + 'Default' + else + t.class.name + end + index = keys_orig_size != keys.size ? i+1 : i + fail(Puppet::Pops::Issues::BAD_TYPE_SPECIALIZATION, @semantic.keys[index], { + :type => o, + :message => "Cannot use #{type_to_report} where String is expected" + }) + end + + rtype = Puppet::Pops::Types::PResourceType.new() + rtype.type_name = type_name + rtype.title = (t == :no_title ? nil : t) + rtype + end + # returns single type as type, else an array of types + result.size == 1 ? result.pop : result + end + + def access_PHostClassType(o, scope, keys) + if keys.size == 0 + fail(Puppet::Pops::Issues::BAD_TYPE_SLICE_ARITY, o, + :base_type => Puppet::Pops::Types::TypeCalculator.new().string(o), :min => 1, :actual => 0) + end + if ! o.class_name.nil? + # lookup class resource and return one or more parameter values + resource = find_resource(scope, 'class', o.class_name) + unless resource + fail(Puppet::Pops::Issues::UNKNOWN_RESOURCE, @semantic, {:type_name => 'Class', :title => o.class_name}) + end + result = keys.map do |k| + unless is_parameter_of_resource?(scope, resource, k) + fail(Puppet::Pops::Issues::UNKNOWN_RESOURCE_PARAMETER, @semantic, + {:type_name => 'Class', :title => o.class_name, :param_name=>k}) + end + get_resource_parameter_value(scope, resource, k) + end + return result.size <= 1 ? result.pop : result + # TODO: if [] is applied to specific class, it should be treated the same as getting + # a resource parameter. Now it fails the operation + # + fail(Puppet::Pops::Issues::ILLEGAL_TYPE_SPECIALIZATION, semantic.left_expr, {:kind => 'Class'}) + end + # The type argument may be a Resource Type - the Puppet Language allows a reference such as + # Class[Foo], and this is interpreted as Class[Resource[Foo]] - which is ok as long as the resource + # does not have a title. This should probably be deprecated. + # + result = keys.each_with_index.map do |c, i| + ctype = Puppet::Pops::Types::PHostClassType.new() + if c.is_a?(Puppet::Pops::Types::PResourceType) && !c.type_name.nil? && c.title.nil? + c = c.type_name.downcase + end + unless c.is_a?(String) + fail(Puppet::Pops::Issues::ILLEGAL_HOSTCLASS_NAME, @semantic.keys[i], {:name => c}) + end + if c !~ Puppet::Pops::Patterns::NAME + fail(Issues::ILLEGAL_NAME, @semantic.keys[i], {:name=>c}) + end + ctype.class_name = c + ctype + end + # returns single type as type, else an array of types + result.size == 1 ? result.pop : result + end +end diff --git a/lib/puppet/pops/evaluator/closure.rb b/lib/puppet/pops/evaluator/closure.rb new file mode 100644 index 000000000..980f3d139 --- /dev/null +++ b/lib/puppet/pops/evaluator/closure.rb @@ -0,0 +1,42 @@ + +# A Closure represents logic bound to a particular scope. +# As long as the runtime (basically the scope implementation) has the behaviour of Puppet 3x it is not +# safe to use this closure when the the scope given to it when initialized goes "out of scope". +# +# Note that the implementation is backwards compatible in that the call method accepts a scope, but this +# scope is not used. +# +class Puppet::Pops::Evaluator::Closure + attr_reader :evaluator + attr_reader :model + attr_reader :enclosing_scope + + def initialize(evaluator, model, scope) + @evaluator = evaluator + @model = model + @enclosing_scope = scope + end + + # marker method checked with respond_to :puppet_lambda + def puppet_lambda() + true + end + + # compatible with 3x AST::Lambda + def call(scope, *args) + @evaluator.call(self, args, @enclosing_scope) + end + + # incompatible with 3x except that it is an array of the same size + def parameters() + @model.parameters || [] + end + + # Returns the number of parameters (required and optional) + # @return [Integer] the total number of accepted parameters + def parameter_count + # yes, this is duplication of code, but it saves a method call + (@model.parameters || []).size + end + +end diff --git a/lib/puppet/pops/evaluator/compare_operator.rb b/lib/puppet/pops/evaluator/compare_operator.rb new file mode 100644 index 000000000..df5730d17 --- /dev/null +++ b/lib/puppet/pops/evaluator/compare_operator.rb @@ -0,0 +1,168 @@ +# Compares the puppet DSL way +# +# ==Equality +# All string vs. numeric equalities check for numeric equality first, then string equality +# Arrays are equal to arrays if they have the same length, and each element #equals +# Hashes are equal to hashes if they have the same size and keys and values #equals. +# All other objects are equal if they are ruby #== equal +# +class Puppet::Pops::Evaluator::CompareOperator + include Puppet::Pops::Utils + + def initialize + @@equals_visitor ||= Puppet::Pops::Visitor.new(self, "equals", 1, 1) + @@compare_visitor ||= Puppet::Pops::Visitor.new(self, "cmp", 1, 1) + @@include_visitor ||= Puppet::Pops::Visitor.new(self, "include", 1, 1) + @type_calculator = Puppet::Pops::Types::TypeCalculator.new() + end + + def equals (a, b) + @@equals_visitor.visit_this_1(self, a, b) + end + + # Performs a comparison of a and b, and return > 0 if a is bigger, 0 if equal, and < 0 if b is bigger. + # Comparison of String vs. Numeric always compares using numeric. + def compare(a, b) + @@compare_visitor.visit_this_1(self, a, b) + end + + # Answers is b included in a + def include?(a, b) + @@include_visitor.visit_this_1(self, a, b) + end + + protected + + def cmp_String(a, b) + # if both are numerics in string form, compare as number + n1 = Puppet::Pops::Utils.to_n(a) + n2 = Puppet::Pops::Utils.to_n(b) + + # Numeric is always lexically smaller than a string, even if the string is empty. + return n1 <=> n2 if n1 && n2 + return -1 if n1 && b.is_a?(String) + return 1 if n2 + return a.casecmp(b) if b.is_a?(String) + + raise ArgumentError.new("A String is not comparable to a non String or Number") + end + + # Equality is case independent. + def equals_String(a, b) + if n1 = Puppet::Pops::Utils.to_n(a) + if n2 = Puppet::Pops::Utils.to_n(b) + n1 == n2 + else + false + end + else + return false unless b.is_a?(String) + a.casecmp(b) == 0 + end + end + + def cmp_Numeric(a, b) + if n2 = Puppet::Pops::Utils.to_n(b) + a <=> n2 + elsif b.kind_of(String) + # Numeric is always lexiographically smaller than a string, even if the string is empty. + -1 + else + raise ArgumentError.new("A Numeric is not comparable to non Numeric or String") + end + end + + def equals_Numeric(a, b) + if n2 = Puppet::Pops::Utils.to_n(b) + a == n2 + else + false + end + end + + def equals_Array(a, b) + return false unless b.is_a?(Array) && a.size == b.size + a.each_index {|i| return false unless equals(a.slice(i), b.slice(i)) } + true + end + + def equals_Hash(a, b) + return false unless b.is_a?(Hash) && a.size == b.size + a.each {|ak, av| return false unless equals(b[ak], av)} + true + end + + def cmp_Symbol(a, b) + if b.is_a?(Symbol) + a <=> b + else + raise ArgumentError.new("Symbol not comparable to non Symbol") + end + end + + def cmp_Object(a, b) + raise ArgumentError.new("Only Strings and Numbers are comparable") + end + + + def equals_Object(a, b) + a == b + end + + def equals_NilClass(a, b) + b.nil? || b == :undef + end + + def equals_Symbol(a, b) + a == b || a == :undef && b.nil? + end + + def include_Object(a, b) + false + end + + def include_String(a, b) + case b + when String + # subsstring search downcased + a.downcase.include?(b.downcase) + when Regexp + # match (convert to boolean) + !!(a =~ b) + when Numeric + # convert string to number, true if == + equals(a, b) + when Puppet::Pops::Types::PStringType + # is there a string in a string? (yes, each char is a string, and an empty string contains an empty string) + true + else + if b == Puppet::Pops::Types::PDataType || b == Puppet::Pops::Types::PObjectType + # A String is Data and Object (but not of all subtypes of those types). + true + else + false + end + end + end + + def include_Array(a, b) + case b + when Regexp + a.each do |element| + next unless element.is_a? String + return true if element =~ b + end + return false + when Puppet::Pops::Types::PAbstractType + a.each {|element| return true if @type_calculator.instance?(b, element) } + return false + else + a.each {|element| return true if equals(element, b) } + return false + end + end + + def include_Hash(a, b) + include?(a.keys, b) + end +end diff --git a/lib/puppet/pops/evaluator/evaluator_impl.rb b/lib/puppet/pops/evaluator/evaluator_impl.rb new file mode 100644 index 000000000..8219678f2 --- /dev/null +++ b/lib/puppet/pops/evaluator/evaluator_impl.rb @@ -0,0 +1,970 @@ +require 'rgen/ecore/ecore' +require 'puppet/pops/evaluator/compare_operator' +require 'puppet/pops/evaluator/relationship_operator' +require 'puppet/pops/evaluator/access_operator' +require 'puppet/pops/evaluator/closure' + +# This implementation of {Puppet::Pops::Evaluator} performs evaluation using the puppet 3.x runtime system +# in a manner largely compatible with Puppet 3.x, but adds new features and introduces constraints. +# +# The evaluation uses _polymorphic dispatch_ which works by dispatching to the first found method named after +# the class or one of its super-classes. The EvaluatorImpl itself mainly deals with evaluation (it currently +# also handles assignment), and it uses a delegation pattern to more specialized handlers of some operators +# that in turn use polymorphic dispatch; this to not clutter EvaluatorImpl with too much responsibility). +# +# Since a pattern is used, only the main entry points are fully documented. The parameters _o_ and _scope_ are +# the same in all the polymorphic methods, (the type of the parameter _o_ is reflected in the method's name; +# either the actual class, or one of its super classes). The _scope_ parameter is always the scope in which +# the evaluation takes place. If nothing else is mentioned, the return is always the result of evaluation. +# +# See {Puppet::Pops::Visitable} and {Puppet::Pops::Visitor} for more information about +# polymorphic calling. +# +class Puppet::Pops::Evaluator::EvaluatorImpl + include Puppet::Pops::Utils + + # Provides access to the Puppet 3.x runtime (scope, etc.) + # This separation has been made to make it easier to later migrate the evaluator to an improved runtime. + # + include Puppet::Pops::Evaluator::Runtime3Support + + # This constant is not defined as Float::INFINITY in Ruby 1.8.7 (but is available in later version + # Refactor when support is dropped for Ruby 1.8.7. + # + INFINITY = 1.0 / 0.0 + + # Reference to Issues name space makes it easier to refer to issues + # (Issues are shared with the validator). + # + Issues = Puppet::Pops::Issues + + def initialize + @@eval_visitor ||= Puppet::Pops::Visitor.new(self, "eval", 1, 1) + @@lvalue_visitor ||= Puppet::Pops::Visitor.new(self, "lvalue", 1, 1) + @@assign_visitor ||= Puppet::Pops::Visitor.new(self, "assign", 3, 3) + @@string_visitor ||= Puppet::Pops::Visitor.new(self, "string", 1, 1) + + @@type_calculator ||= Puppet::Pops::Types::TypeCalculator.new() + @@type_parser ||= Puppet::Pops::Types::TypeParser.new() + + @@compare_operator ||= Puppet::Pops::Evaluator::CompareOperator.new() + @@relationship_operator ||= Puppet::Pops::Evaluator::RelationshipOperator.new() + + # Initialize the runtime module + Puppet::Pops::Evaluator::Runtime3Support.instance_method(:initialize).bind(self).call() + end + + # @api private + def type_calculator + @@type_calculator + end + + # Polymorphic evaluate - calls eval_TYPE + # + # ## Polymorphic evaluate + # Polymorphic evaluate calls a method on the format eval_TYPE where classname is the last + # part of the class of the given _target_. A search is performed starting with the actual class, continuing + # with each of the _target_ class's super classes until a matching method is found. + # + # # Description + # Evaluates the given _target_ object in the given scope, optionally passing a block which will be + # called with the result of the evaluation. + # + # @overload evaluate(target, scope, {|result| block}) + # @param target [Object] evaluation target - see methods on the pattern assign_TYPE for actual supported types. + # @param scope [Object] the runtime specific scope class where evaluation should take place + # @return [Object] the result of the evaluation + # + # @api + # + def evaluate(target, scope) + begin + @@eval_visitor.visit_this_1(self, target, scope) + rescue StandardError => e + if e.is_a? Puppet::ParseError + raise e + end + fail(Issues::RUNTIME_ERROR, target, {:detail => e.message}, e) + end + end + + # Polymorphic assign - calls assign_TYPE + # + # ## Polymorphic assign + # Polymorphic assign calls a method on the format assign_TYPE where TYPE is the last + # part of the class of the given _target_. A search is performed starting with the actual class, continuing + # with each of the _target_ class's super classes until a matching method is found. + # + # # Description + # Assigns the given _value_ to the given _target_. The additional argument _o_ is the instruction that + # produced the target/value tuple and it is used to set the origin of the result. + # @param target [Object] assignment target - see methods on the pattern assign_TYPE for actual supported types. + # @param value [Object] the value to assign to `target` + # @param o [Puppet::Pops::Model::PopsObject] originating instruction + # @param scope [Object] the runtime specific scope where evaluation should take place + # + # @api + # + def assign(target, value, o, scope) + @@assign_visitor.visit_this_3(self, target, value, o, scope) + end + + def lvalue(o, scope) + @@lvalue_visitor.visit_this_1(self, o, scope) + end + + def string(o, scope) + @@string_visitor.visit_this_1(self, o, scope) + end + + # Call a closure - Can only be called with a Closure (for now), may be refactored later + # to also handle other types of calls (function calls are also handled by CallNamedFunction and CallMethod, they + # could create similar objects to Closure, wait until other types of defines are instantiated - they may behave + # as special cases of calls - i.e. 'new') + # + # @raise ArgumentError, if there are to many or too few arguments + # @raise ArgumentError, if given closure is not a Puppet::Pops::Evaluator::Closure + # + def call(closure, args, scope) + raise ArgumentError, "Can only call a Lambda" unless closure.is_a?(Puppet::Pops::Evaluator::Closure) + pblock = closure.model + parameters = pblock.parameters || [] + + raise ArgumentError, "Too many arguments: #{args.size} for #{parameters.size}" unless args.size <= parameters.size + + # associate values with parameters + merged = parameters.zip(args) + # calculate missing arguments + missing = parameters.slice(args.size, parameters.size - args.size).select {|p| p.value.nil? } + unless missing.empty? + optional = parameters.count { |p| !p.value.nil? } + raise ArgumentError, "Too few arguments; #{args.size} for #{optional > 0 ? ' min ' : ''}#{parameters.size - optional}" + end + + evaluated = merged.collect do |m| + # m can be one of + # m = [Parameter{name => "name", value => nil], "given"] + # | [Parameter{name => "name", value => Expression}, "given"] + # + # "given" is always an optional entry. If a parameter was provided then + # the entry will be in the array, otherwise the m array will be a + # single element.a = [] + given_argument = m[1] + argument_name = m[0].name + default_expression = m[0].value + + value = if default_expression + evaluate(default_expression, scope) + else + given_argument + end + [argument_name, value] + end + + # Store the evaluated name => value associations in a new inner/local/ephemeral scope + # (This is made complicated due to the fact that the implementation of scope is overloaded with + # functionality and an inner ephemeral scope must be used (as opposed to just pushing a local scope + # on a scope "stack"). + + # Ensure variable exists with nil value if error occurs. + # Some ruby implementations does not like creating variable on return + result = nil + begin + scope_memo = get_scope_nesting_level(scope) + # change to create local scope_from - cannot give it file and line - that is the place of the call, not + # "here" + create_local_scope_from(Hash[evaluated], scope) + result = evaluate(pblock.body, scope) + ensure + set_scope_nesting_level(scope, scope_memo) + end + result + end + + protected + + def lvalue_VariableExpression(o, scope) + # evaluate the name + evaluate(o.expr, scope) + end + + # Catches all illegal lvalues + # + def lvalue_Object(o, scope) + fail(Issues::ILLEGAL_ASSIGNMENT, o) + end + + # Assign value to named variable. + # The '$' sign is never part of the name. + # @example In Puppet DSL + # $name = value + # @param name [String] name of variable without $ + # @param value [Object] value to assign to the variable + # @param o [Puppet::Pops::Model::PopsObject] originating instruction + # @param scope [Object] the runtime specific scope where evaluation should take place + # @return [value] + # + def assign_String(name, value, o, scope) + if name =~ /::/ + fail(Issues::CROSS_SCOPE_ASSIGNMENT, o.left_expr, {:name => name}) + end + set_variable(name, value, o, scope) + value + end + + def assign_Numeric(n, value, o, scope) + fail(Issues::ILLEGAL_NUMERIC_ASSIGNMENT, o.left_expr, {:varname => n.to_s}) + end + + # Catches all illegal assignment (e.g. 1 = 2, {'a'=>1} = 2, etc) + # + def assign_Object(name, value, o, scope) + fail(Issues::ILLEGAL_ASSIGNMENT, o) + end + + def eval_Factory(o, scope) + evaluate(o.current, scope) + end + + # Evaluates any object not evaluated to something else to itself. + def eval_Object o, scope + o + end + + # Allows nil to be used as a Nop. + # Evaluates to nil + # TODO: What is the difference between literal undef, nil, and nop? + # + def eval_NilClass(o, scope) + nil + end + + # Evaluates Nop to nil. + # TODO: or is this the same as :undef + # TODO: is this even needed as a separate instruction when there is a literal undef? + def eval_Nop(o, scope) + nil + end + + # Captures all LiteralValues not handled elsewhere. + # + def eval_LiteralValue(o, scope) + o.value + end + + def eval_LiteralDefault(o, scope) + :default + end + + def eval_LiteralUndef(o, scope) + :undef # TODO: or just use nil for this? + end + + # A QualifiedReference (i.e. a capitalized qualified name such as Foo, or Foo::Bar) evaluates to a PType + # + def eval_QualifiedReference(o, scope) + @@type_parser.interpret(o) + end + + def eval_NotExpression(o, scope) + ! is_true?(evaluate(o.expr, scope)) + end + + def eval_UnaryMinusExpression(o, scope) + - coerce_numeric(evaluate(o.expr, scope), o, scope) + end + + # Abstract evaluation, returns array [left, right] with the evaluated result of left_expr and + # right_expr + # @return > array with result of evaluating left and right expressions + # + def eval_BinaryExpression o, scope + [ evaluate(o.left_expr, scope), evaluate(o.right_expr, scope) ] + end + + # Evaluates assignment with operators =, +=, -= and + # + # @example Puppet DSL + # $a = 1 + # $a += 1 + # $a -= 1 + # + def eval_AssignmentExpression(o, scope) + name = lvalue(o.left_expr, scope) + value = evaluate(o.right_expr, scope) + + case o.operator + when :'=' # regular assignment + assign(name, value, o, scope) + + when :'+=' + # if value does not exist, return RHS (note that type check has already been made so correct type is ensured) + if !variable_exists?(name, scope) + return value + end + begin + # Delegate to calculate function to deal with check of LHS, and perform ´+´ as arithmetic or concatenation the + # same way as ArithmeticExpression performs `+`. + assign(name, calculate(get_variable_value(name, o, scope), value, :'+', o.left_expr, o.right_expr, scope), o, scope) + rescue ArgumentError => e + fail(Issues::APPEND_FAILED, o, {:message => e.message}) + end + + when :'-=' + # If an attempt is made to delete values from something that does not exists, the value is :undef (it is guaranteed to not + # include any values the user wants deleted anyway :-) + # + if !variable_exists?(name, scope) + return nil + end + begin + # Delegate to delete function to deal with check of LHS, and perform deletion + assign(name, delete(get_variable_value(name, o, scope), value), o, scope) + rescue ArgumentError => e + fail(Issues::APPEND_FAILED, o, {:message => e.message}) + end + else + fail(Issues::UNSUPPORTED_OPERATOR, o, {:operator => o.operator}) + end + value + end + + ARITHMETIC_OPERATORS = [:'+', :'-', :'*', :'/', :'%', :'<<', :'>>'] + COLLECTION_OPERATORS = [:'+', :'-', :'<<'] + + # Handles binary expression where lhs and rhs are array/hash or numeric and operator is +, - , *, % / << >> + # + def eval_ArithmeticExpression(o, scope) + left, right = eval_BinaryExpression(o, scope) + begin + result = calculate(left, right, o.operator, o.left_expr, o.right_expr, scope) + rescue ArgumentError => e + fail(Issues::RUNTIME_ERROR, o, {:detail => e.message}, e) + end + result + end + + + # Handles binary expression where lhs and rhs are array/hash or numeric and operator is +, - , *, % / << >> + # + def calculate(left, right, operator, left_o, right_o, scope) + unless ARITHMETIC_OPERATORS.include?(operator) + fail(Issues::UNSUPPORTED_OPERATOR, left_o.eContainer, {:operator => o.operator}) + end + + if (left.is_a?(Array) || left.is_a?(Hash)) && COLLECTION_OPERATORS.include?(operator) + # Handle operation on collections + case operator + when :'+' + concatenate(left, right) + when :'-' + delete(left, right) + when :'<<' + unless left.is_a?(Array) + fail(Issues::OPERATOR_NOT_APPLICABLE, left_o, {:operator => operator, :left_value => left}) + end + left + [right] + end + else + # Handle operation on numeric + left = coerce_numeric(left, left_o, scope) + right = coerce_numeric(right, right_o, scope) + begin + if operator == :'%' && (left.is_a?(Float) || right.is_a?(Float)) + # Deny users the fun of seeing severe rounding errors and confusing results + fail(Issues::OPERATOR_NOT_APPLICABLE, left_o, {:operator => operator, :left_value => left}) + end + result = left.send(operator, right) + rescue NoMethodError => e + fail(Issues::OPERATOR_NOT_APPLICABLE, left_o, {:operator => operator, :left_value => left}) + rescue ZeroDivisionError => e + fail(Issues::DIV_BY_ZERO, right_o) + end + if result == INFINITY || result == -INFINITY + fail(Issues::RESULT_IS_INFINITY, left_o, {:operator => operator}) + end + result + end + end + + # Evaluates Puppet DSL ->, ~>, <-, and <~ + def eval_RelationshipExpression(o, scope) + # First level evaluation, reduction to basic data types or puppet types, the relationship operator then translates this + # to the final set of references (turning strings into references, which can not naturally be done by the main evaluator since + # all strings should not be turned into references. + # + real = eval_BinaryExpression(o, scope) + @@relationship_operator.evaluate(real, o, scope) + end + + # Evaluates x[key, key, ...] + # + def eval_AccessExpression(o, scope) + left = evaluate(o.left_expr, scope) + keys = o.keys.nil? ? [] : o.keys.collect {|key| evaluate(key, scope) } + Puppet::Pops::Evaluator::AccessOperator.new(o).access(left, scope, *keys) + end + + # Evaluates <, <=, >, >=, and == + # + def eval_ComparisonExpression o, scope + left, right = eval_BinaryExpression o, scope + + begin + # Left is a type + if left.is_a?(Puppet::Pops::Types::PAbstractType) + case o.operator + when :'==' + @@compare_operator.equals(left,right) + when :'!=' + ! @@compare_operator.equals(left,right) + when :'<' + # left can be assigned to right, but they are not equal + @@type_calculator.assignable?(right, left) && ! @@compare_operator.equals(left,right) + when :'<=' + # left can be assigned to right + @@type_calculator.assignable?(right, left) + when :'>' + # right can be assigned to left, but they are not equal + @@type_calculator.assignable?(left,right) && ! @@compare_operator.equals(left,right) + when :'>=' + # right can be assigned to left + @@type_calculator.assignable?(left, right) + else + fail(Issues::UNSUPPORTED_OPERATOR, o, {:operator => o.operator}) + end + else + case o.operator + when :'==' + @@compare_operator.equals(left,right) + when :'!=' + ! @@compare_operator.equals(left,right) + when :'<' + @@compare_operator.compare(left,right) < 0 + when :'<=' + @@compare_operator.compare(left,right) <= 0 + when :'>' + @@compare_operator.compare(left,right) > 0 + when :'>=' + @@compare_operator.compare(left,right) >= 0 + else + fail(Issues::UNSUPPORTED_OPERATOR, o, {:operator => o.operator}) + end + end + rescue ArgumentError => e + fail(Issues::COMPARISON_NOT_POSSIBLE, o, { + :operator => o.operator, + :left_value => left, + :right_value => right, + :detail => e.message}) + end + end + + # Evaluates matching expressions with type, string or regexp rhs expression. + # If RHS is a type, the =~ matches compatible (assignable?) type. + # + # @example + # x =~ /abc.*/ + # @example + # x =~ "abc.*/" + # @example + # y = "abc" + # x =~ "${y}.*" + # @example + # [1,2,3] =~ Array[Integer[1,10]] + # @return [Boolean] if a match was made or not. Also sets $0..$n to matchdata in current scope. + # + def eval_MatchExpression o, scope + left, pattern = eval_BinaryExpression o, scope + # matches RHS types as instance of for all types except a parameterized Regexp[R] + if pattern.is_a?(Puppet::Pops::Types::PAbstractType) + if pattern.is_a?(Puppet::Pops::Types::PRegexpType) && pattern.pattern + # A qualified PRegexpType, get its ruby regexp + pattern = pattern.regexp + else + # evaluate as instance? + matched = @@type_calculator.instance?(pattern, left) + # convert match result to Boolean true, or false + return o.operator == :'=~' ? !!matched : !matched + end + end + + begin + pattern = Regexp.new(pattern) unless pattern.is_a?(Regexp) + rescue StandardError => e + fail(Issues::MATCH_NOT_REGEXP, o.right_expr, {:detail => e.message}) + end + unless left.is_a?(String) + fail(Issues::MATCH_NOT_STRING, o.left_expr, {:left_value => left}) + end + + matched = pattern.match(left) # nil, or MatchData + set_match_data(matched, o, scope) # creates ephemeral + + # convert match result to Boolean true, or false + o.operator == :'=~' ? !!matched : !matched + end + + # Evaluates Puppet DSL `in` expression + # + def eval_InExpression o, scope + left, right = eval_BinaryExpression o, scope + @@compare_operator.include?(right, left) + end + + # @example + # $a and $b + # b is only evaluated if a is true + # + def eval_AndExpression o, scope + is_true?(evaluate(o.left_expr, scope)) ? is_true?(evaluate(o.right_expr, scope)) : false + end + + # @example + # a or b + # b is only evaluated if a is false + # + def eval_OrExpression o, scope + is_true?(evaluate(o.left_expr, scope)) ? true : is_true?(evaluate(o.right_expr, scope)) + end + + # Evaluates each entry of the literal list and creates a new Array + # @return [Array] with the evaluated content + # + def eval_LiteralList o, scope + o.values.collect {|expr| evaluate(expr, scope)} + end + + # Evaluates each entry of the literal hash and creates a new Hash. + # @return [Hash] with the evaluated content + # + def eval_LiteralHash o, scope + h = Hash.new + o.entries.each {|entry| h[ evaluate(entry.key, scope)]= evaluate(entry.value, scope)} + h + end + + # Evaluates all statements and produces the last evaluated value + # + def eval_BlockExpression o, scope + r = nil + o.statements.each {|s| r = evaluate(s, scope)} + r + end + + # Performs optimized search over case option values, lazily evaluating each + # until there is a match. If no match is found, the case expression's default expression + # is evaluated (it may be nil or Nop if there is no default, thus producing nil). + # If an option matches, the result of evaluating that option is returned. + # @return [Object, nil] what a matched option returns, or nil if nothing matched. + # + def eval_CaseExpression(o, scope) + # memo scope level before evaluating test - don't want a match in the case test to leak $n match vars + # to expressions after the case expression. + # + with_guarded_scope(scope) do + test = evaluate(o.test, scope) + result = nil + the_default = nil + if o.options.find do |co| + # the first case option that matches + if co.values.find do |c| + the_default = co.then_expr if c.is_a? Puppet::Pops::Model::LiteralDefault + is_match?(test, evaluate(c, scope), c, scope) + end + result = evaluate(co.then_expr, scope) + true # the option was picked + end + end + result # an option was picked, and produced a result + else + evaluate(the_default, scope) # evaluate the default (should be a nop/nil) if there is no default). + end + end + end + + # Evaluates a CollectExpression by transforming it into a 3x AST::Collection and then evaluating that. + # This is done because of the complex API between compiler, indirector, backends, and difference between + # collecting virtual resources and exported resources. + # + def eval_CollectExpression o, scope + # The Collect Expression and its contained query expressions are implemented in such a way in + # 3x that it is almost impossible to do anything about them (the AST objects are lazily evaluated, + # and the built structure consists of both higher order functions and arrays with query expressions + # that are either used as a predicate filter, or given to an indirection terminus (such as the Puppet DB + # resource terminus). Unfortunately, the 3x implementation has many inconsistencies that the implementation + # below carries forward. + # + collect_3x = Puppet::Pops::Model::AstTransformer.new().transform(o) + collected = collect_3x.evaluate(scope) + # the 3x returns an instance of Parser::Collector (but it is only registered with the compiler at this + # point and does not contain any valuable information (like the result) + # Dilemma: If this object is returned, it is a first class value in the Puppet Language and we + # need to be able to perform operations on it. We can forbid it from leaking by making CollectExpression + # a non R-value. This makes it possible for the evaluator logic to make use of the Collector. + collected + end + + def eval_ParenthesizedExpression(o, scope) + evaluate(o.expr, scope) + end + + # This evaluates classes, nodes and resource type definitions to nil, since 3x: + # instantiates them, and evaluates their parameters and body. This is achieved by + # providing bridge AST classes in Puppet::Parser::AST::PopsBridge that bridges a + # Pops Program and a Pops Expression. + # + # Since all Definitions are handled "out of band", they are treated as a no-op when + # evaluated. + # + def eval_Definition(o, scope) + nil + end + + def eval_Program(o, scope) + evaluate(o.body, scope) + end + + # Produces Array[PObjectType], an array of resource references + # + def eval_ResourceExpression(o, scope) + exported = o.exported + virtual = o.virtual + type_name = evaluate(o.type_name, scope) + o.bodies.map do |body| + titles = [evaluate(body.title, scope)].flatten + evaluated_parameters = body.operations.map {|op| evaluate(op, scope) } + create_resources(o, scope, virtual, exported, type_name, titles, evaluated_parameters) + end.flatten.compact + end + + def eval_ResourceOverrideExpression(o, scope) + evaluated_resources = evaluate(o.resources, scope) + evaluated_parameters = o.operations.map { |op| evaluate(op, scope) } + create_resource_overrides(o, scope, [evaluated_resources].flatten, evaluated_parameters) + evaluated_resources + end + + # Produces 3x array of parameters + def eval_AttributeOperation(o, scope) + create_resource_parameter(o, scope, o.attribute_name, evaluate(o.value_expr, scope), o.operator) + end + + # Sets default parameter values for a type, produces the type + # + def eval_ResourceDefaultsExpression(o, scope) + type_name = o.type_ref.value # a QualifiedName's string value + evaluated_parameters = o.operations.map {|op| evaluate(op, scope) } + create_resource_defaults(o, scope, type_name, evaluated_parameters) + # Produce the type + evaluate(o.type_ref, scope) + end + + # Evaluates function call by name. + # + def eval_CallNamedFunctionExpression(o, scope) + # The functor expression is not evaluated, it is not possible to select the function to call + # via an expression like $a() + unless o.functor_expr.is_a? Puppet::Pops::Model::QualifiedName + fail(Issues::ILLEGAL_EXPRESSION, o.functor_expr, {:feature=>'function name', :container => o}) + end + name = o.functor_expr.value + assert_function_available(name, o, scope) + evaluated_arguments = o.arguments.collect {|arg| evaluate(arg, scope) } + # wrap lambda in a callable block if it is present + evaluated_arguments << Puppet::Pops::Evaluator::Closure.new(self, o.lambda, scope) if o.lambda + call_function(name, evaluated_arguments, o, scope) do |result| + # prevent functions that are not r-value from leaking its return value + rvalue_function?(name, o, scope) ? result : nil + end + end + + # Evaluation of CallMethodExpression handles a NamedAccessExpression functor (receiver.function_name) + # + def eval_CallMethodExpression(o, scope) + unless o.functor_expr.is_a? Puppet::Pops::Model::NamedAccessExpression + fail(Issues::ILLEGAL_EXPRESSION, o.functor_expr, {:feature=>'function accessor', :container => o}) + end + receiver = evaluate(o.functor_expr.left_expr, scope) + name = o.functor_expr.right_expr + unless name.is_a? Puppet::Pops::Model::QualifiedName + fail(Issues::ILLEGAL_EXPRESSION, o.functor_expr, {:feature=>'function name', :container => o}) + end + name = name.value # the string function name + assert_function_available(name, o, scope) + evaluated_arguments = [receiver] + (o.arguments || []).collect {|arg| evaluate(arg, scope) } + evaluated_arguments << Puppet::Pops::Evaluator::Closure.new(self, o.lambda, scope) if o.lambda + call_function(name, evaluated_arguments, o, scope) do |result| + # prevent functions that are not r-value from leaking its return value + rvalue_function?(name, o, scope) ? result : nil + end + end + + # @example + # $x ? { 10 => true, 20 => false, default => 0 } + # + def eval_SelectorExpression o, scope + # memo scope level before evaluating test - don't want a match in the case test to leak $n match vars + # to expressions after the selector expression. + # + with_guarded_scope(scope) do + test = evaluate(o.left_expr, scope) + selected = o.selectors.find do |s| + candidate = evaluate(s.matching_expr, scope) + candidate == :default || is_match?(test, candidate, s.matching_expr, scope) + end + if selected + evaluate(selected.value_expr, scope) + else + nil + end + end + end + + # Evaluates Puppet DSL `if` + def eval_IfExpression o, scope + with_guarded_scope(scope) do + if is_true?(evaluate(o.test, scope)) + evaluate(o.then_expr, scope) + else + evaluate(o.else_expr, scope) + end + end + end + + # Evaluates Puppet DSL `unless` + def eval_UnlessExpression o, scope + with_guarded_scope(scope) do + unless is_true?(evaluate(o.test, scope)) + evaluate(o.then_expr, scope) + else + evaluate(o.else_expr, scope) + end + end + end + + # Evaluates a variable (getting its value) + # The evaluator is lenient; any expression producing a String is used as a name + # of a variable. + # + def eval_VariableExpression o, scope + # Evaluator is not too fussy about what constitutes a name as long as the result + # is a String and a valid variable name + # + name = evaluate(o.expr, scope) + + # Should be caught by validation, but make this explicit here as well, or mysterious evaluation issues + # may occur. + case name + when String + when Numeric + else + fail(Issues::ILLEGAL_VARIABLE_EXPRESSION, o.expr) + end + # TODO: Check for valid variable name (Task for validator) + # TODO: semantics of undefined variable in scope, this just returns what scope does == value or nil + get_variable_value(name, o, scope) + end + + # Evaluates double quoted strings that may contain interpolation + # + def eval_ConcatenatedString o, scope + o.segments.collect {|expr| string(evaluate(expr, scope), scope)}.join + end + + + # If the wrapped expression is a QualifiedName, it is taken as the name of a variable in scope. + # Note that this is different from the 3.x implementation, where an initial qualified name + # is accepted. (e.g. `"---${var + 1}---"` is legal. This implementation requires such concrete + # syntax to be expressed in a model as `(TextExpression (+ (Variable var) 1)` - i.e. moving the decision to + # the parser. + # + # Semantics; the result of an expression is turned into a string, nil is silently transformed to empty + # string. + # @return [String] the interpolated result + # + def eval_TextExpression o, scope + if o.expr.is_a?(Puppet::Pops::Model::QualifiedName) + # TODO: formalize, when scope returns nil, vs error + string(get_variable_value(o.expr.value, o, scope), scope) + else + string(evaluate(o.expr, scope), scope) + end + end + + def string_Object(o, scope) + o.to_s + end + + def string_Symbol(o, scope) + case o + when :undef + '' + else + o.to_s + end + end + + def string_Array(o, scope) + ['[', o.map {|e| string(e, scope)}.join(', '), ']'].join() + end + + def string_Hash(o, scope) + ['{', o.map {|k,v| string(k, scope) + " => " + string(v, scope)}.join(', '), '}'].join() + end + + def string_Regexp(o, scope) + ['/', o.source, '/'].join() + end + + def string_PAbstractType(o, scope) + @@type_calculator.string(o) + end + + # Produces concatenation / merge of x and y. + # + # When x is an Array, y of type produces: + # + # * Array => concatenation `[1,2], [3,4] => [1,2,3,4]` + # * Hash => concatenation of hash as array `[key, value, key, value, ...]` + # * any other => concatenation of single value + # + # When x is a Hash, y of type produces: + # + # * Array => merge of array interpreted as `[key, value, key, value,...]` + # * Hash => a merge, where entries in `y` overrides + # * any other => error + # + # When x is something else, wrap it in an array first. + # + # When x is nil, an empty array is used instead. + # + # @note to concatenate an Array, nest the array - i.e. `[1,2], [[2,3]]` + # + # @overload concatenate(obj_x, obj_y) + # @param obj_x [Object] object to wrap in an array and concatenate to; see other overloaded methods for return type + # @param ary_y [Object] array to concatenate at end of `ary_x` + # @return [Object] wraps obj_x in array before using other overloaded option based on type of obj_y + # @overload concatenate(ary_x, ary_y) + # @param ary_x [Array] array to concatenate to + # @param ary_y [Array] array to concatenate at end of `ary_x` + # @return [Array] new array with `ary_x` + `ary_y` + # @overload concatenate(ary_x, hsh_y) + # @param ary_x [Array] array to concatenate to + # @param hsh_y [Hash] converted to array form, and concatenated to array + # @return [Array] new array with `ary_x` + `hsh_y` converted to array + # @overload concatenate (ary_x, obj_y) + # @param ary_x [Array] array to concatenate to + # @param obj_y [Object] non array or hash object to add to array + # @return [Array] new array with `ary_x` + `obj_y` added as last entry + # @overload concatenate(hsh_x, ary_y) + # @param hsh_x [Hash] the hash to merge with + # @param ary_y [Array] array interpreted as even numbered sequence of key, value merged with `hsh_x` + # @return [Hash] new hash with `hsh_x` merged with `ary_y` interpreted as hash in array form + # @overload concatenate(hsh_x, hsh_y) + # @param hsh_x [Hash] the hash to merge to + # @param hsh_y [Hash] hash merged with `hsh_x` + # @return [Hash] new hash with `hsh_x` merged with `hsh_y` + # @raise [ArgumentError] when `xxx_x` is neither an Array nor a Hash + # @raise [ArgumentError] when `xxx_x` is a Hash, and `xxx_y` is neither Array nor Hash. + # + def concatenate(x, y) + x = [x] unless x.is_a?(Array) || x.is_a?(Hash) + case x + when Array + y = case y + when Array then y + when Hash then y.to_a + else + [y] + end + x + y # new array with concatenation + when Hash + y = case y + when Hash then y + when Array + # Hash[[a, 1, b, 2]] => {} + # Hash[a,1,b,2] => {a => 1, b => 2} + # Hash[[a,1], [b,2]] => {[a,1] => [b,2]} + # Hash[[[a,1], [b,2]]] => {a => 1, b => 2} + # Use type calcultor to determine if array is Array[Array[?]], and if so use second form + # of call + t = @@type_calculator.infer(y) + if t.element_type.is_a? Puppet::Pops::Types::PArrayType + Hash[y] + else + Hash[*y] + end + else + raise ArgumentError.new("Can only append Array or Hash to a Hash") + end + x.merge y # new hash with overwrite + else + raise ArgumentError.new("Can only append to an Array or a Hash.") + end + end + + # Produces the result x \ y (set difference) + # When `x` is an Array, `y` is transformed to an array and then all matching elements removed from x. + # When `x` is a Hash, all contained keys are removed from x as listed in `y` if it is an Array, or all its keys if it is a Hash. + # The difference is returned. The given `x` and `y` are not modified by this operation. + # @raise [ArgumentError] when `x` is neither an Array nor a Hash + # + def delete(x, y) + result = x.dup + case x + when Array + y = case y + when Array then y + when Hash then y.to_a + else + [y] + end + y.each {|e| result.delete(e) } + when Hash + y = case y + when Array then y + when Hash then y.keys + else + [y] + end + y.each {|e| result.delete(e) } + else + raise ArgumentError.new("Can only delete from an Array or Hash.") + end + result + end + + # Implementation of case option matching. + # + # This is the type of matching performed in a case option, using == for every type + # of value except regular expression where a match is performed. + # + def is_match? left, right, o, scope + if right.is_a?(Regexp) + return false unless left.is_a? String + matched = right.match(left) + set_match_data(matched, o, scope) # creates or clears ephemeral + !!matched # convert to boolean + elsif right.is_a?(Puppet::Pops::Types::PAbstractType) && !left.is_a?(Puppet::Pops::Types::PAbstractType) + # right is a type and left is not - check if left is an instance of the given type + # (The reverse is not terribly meaningful - computing which of the case options that first produces + # an instance of a given type). + # + @@type_calculator.instance?(right, left) + else + # Handle equality the same way as the language '==' operator (case insensitive etc.) + @@compare_operator.equals(left,right) + end + end + + def with_guarded_scope(scope) + scope_memo = get_scope_nesting_level(scope) + begin + yield + ensure + set_scope_nesting_level(scope, scope_memo) + end + end + +end diff --git a/lib/puppet/pops/evaluator/relationship_operator.rb b/lib/puppet/pops/evaluator/relationship_operator.rb new file mode 100644 index 000000000..e81a22b23 --- /dev/null +++ b/lib/puppet/pops/evaluator/relationship_operator.rb @@ -0,0 +1,148 @@ +# The RelationshipOperator implements the semantics of the -> <- ~> <~ operators creating relationships or notification +# relationships between the left and right hand side's references to resources. +# +# This is separate class since a second level of evaluation is required that transforms string in left or right hand +# to type references. The task of "making a relationship" is delegated to the "runtime support" class that is included. +# This is done to separate the concerns of the new evaluator from the 3x runtime; messy logic goes into the runtime support +# module. Later when more is cleaned up this can be simplified further. +# +class Puppet::Pops::Evaluator::RelationshipOperator + + # Provides access to the Puppet 3.x runtime (scope, etc.) + # This separation has been made to make it easier to later migrate the evaluator to an improved runtime. + # + include Puppet::Pops::Evaluator::Runtime3Support + + Issues = Puppet::Pops::Issues + + class IllegalRelationshipOperandError < RuntimeError + attr_reader :operand + def initialize operand + @operand = operand + end + end + + class NotCatalogTypeError < RuntimeError + attr_reader :type + def initialize type + @type = type + end + end + + def initialize + @type_transformer_visitor = Puppet::Pops::Visitor.new(self, "transform", 1, 1) + @type_calculator = Puppet::Pops::Types::TypeCalculator.new() + @type_parser = Puppet::Pops::Types::TypeParser.new() + + @catalog_type = Puppet::Pops::Types::TypeFactory.catalog_entry() + end + + def transform(o, scope) + @type_transformer_visitor.visit_this_1(self, o, scope) + end + + # Catch all non transformable objects + # @api private + def transform_Object(o, scope) + raise IllegalRelationshipOperandError.new(o) + end + + # A string must be a type reference in string format + # @api private + def transform_String(o, scope) + assert_catalog_type(@type_parser.parse(o), scope) + end + + # A qualified name is short hand for a class with this name + # @api private + def transform_QualifiedName(o, scope) + Puppet::Pops::Types::TypeFactory.host_class(o.value) + end + + # Types are what they are, just check the type + # @api private + def transform_PAbstractType(o, scope) + assert_catalog_type(o, scope) + end + + # This transforms a 3x Collector (the result of evaluating a 3x AST::Collection). + # It is passed through verbatim since it is evaluated late by the compiler. At the point + # where the relationship is evaluated, it is simply recorded with the compiler for later evaluation. + # If one of the sides of the relationship is a Collector it is evaluated before the actual + # relationship is formed. (All of this happens at a later point in time. + # + def transform_Collector(o, scope) + o + end + + # Asserts (and returns) the type if it is a PCatalogEntryType + # (A PCatalogEntryType is the base class of PHostClassType, and PResourceType). + # + def assert_catalog_type(o, scope) + unless @type_calculator.assignable?(@catalog_type, o) + raise NotCatalogTypeError.new(o) + end + # TODO must check if this is an abstract PResourceType (i.e. without a type_name) - which should fail ? + # e.g. File -> File (and other similar constructs) - maybe the catalog protects against this since references + # may be to future objects... + o + end + + RELATIONSHIP_OPERATORS = [:'->', :'~>', :'<-', :'<~'] + REVERSE_OPERATORS = [:'<-', :'<~'] + RELATION_TYPE = { + :'->' => :relationship, + :'<-' => :relationship, + :'~>' => :subscription, + :'<~' => :subscription + } + + # Evaluate a relationship. + # TODO: The error reporting is not fine grained since evaluation has already taken place + # There is no references to the original source expressions at this point, only the overall + # relationship expression. (e.g.. the expression may be ['string', func_call(), etc.] -> func_call()) + # To implement this, the general evaluator needs to be able to track each evaluation result and associate + # it with a corresponding expression. This structure should then be passed to the relationship operator. + # + def evaluate (left_right_evaluated, relationship_expression, scope) + # assert operator (should have been validated, but this logic makes assumptions which would + # screw things up royally). Better safe than sorry. + unless RELATIONSHIP_OPERATORS.include?(relationship_expression.operator) + fail(Issues::UNSUPPORTED_OPERATOR, relationship_expression, {:operator => relationship_expression.operator}) + end + + begin + # Turn each side into an array of types (this also asserts their type) + # (note wrap in array first if value is not already an array) + # + # TODO: Later when objects are Puppet Runtime Objects and know their type, it will be more efficient to check/infer + # the type first since a chained operation then does not have to visit each element again. This is not meaningful now + # since inference needs to visit each object each time, and this is what the transformation does anyway). + # + # real is [left, right], and both the left and right may be a single value or an array. In each case all content + # should be flattened, and then transformed to a type. left or right may also be a value that is transformed + # into an array, and thus the resulting left and right must be flattened individually + # + real = left_right_evaluated.collect {|x| [x].flatten.collect {|x| transform(x, scope) }} + real[0].flatten! + real[1].flatten! + + # reverse order if operator is Right to Left + source, target = reverse_operator?(relationship_expression) ? real.reverse : real + + # Add the relationships to the catalog + source.each {|s| target.each {|t| add_relationship(s, t, RELATION_TYPE[relationship_expression.operator], scope) }} + + # Produce the transformed source RHS (if this is a chain, this does not need to be done again) + real.slice(1) + rescue NotCatalogTypeError => e + fail(Issues::ILLEGAL_RELATIONSHIP_OPERAND_TYPE, relationship_expression, {:type => @type_calculator.string(e.type)}) + rescue IllegalRelationshipOperandError => e + fail(Issues::ILLEGAL_RELATIONSHIP_OPERAND_TYPE, relationship_expression, {:operand => e.operand}) + end + end + + def reverse_operator?(o) + REVERSE_OPERATORS.include?(o.operator) + end +end diff --git a/lib/puppet/pops/evaluator/runtime3_support.rb b/lib/puppet/pops/evaluator/runtime3_support.rb new file mode 100644 index 000000000..472827730 --- /dev/null +++ b/lib/puppet/pops/evaluator/runtime3_support.rb @@ -0,0 +1,447 @@ +# 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) + 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.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 being 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) + fail(Puppet::Pops::Issues::UNKNOWN_FUNCTION, o, {:name => name}) unless Puppet::Parser::Functions.function(name) + end + + def call_function(name, args, o, scope) + # Should arguments be mapped from :undef to '' (3x functions expects this - but it is bad) + mapped_args = args.map {|a| a == :undef ? '' : a } + 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), # 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", 1, 1) + 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) + @@convert_visitor.visit_this_1(self, o, scope) + end + + def convert_Object(o, scope) + o + end + + def convert_Array(o, scope) + o.map {|x| convert(x, scope) } + end + + def convert_Hash(o, scope) + result = {} + o.each {|k,v| result[convert(k, scope)] = convert(v, scope) } + result + end + + def convert_Regexp(o, scope) + # Puppet 3x cannot handle parameter values that are reqular expressions. Turn into regexp string in + # source form + o.inspect + end + + def convert_PAbstractType(o, scope) + # Convert all other types to their string forms + o.to_s + end + + def convert_PResourceType(o,scope) + # 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) + # 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_adapter(o, Puppet::Pops::Adapters::SourcePosAdapter) + return [nil, -1] unless source_pos + [source_pos.locator.file, source_pos.line] + 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 +end \ No newline at end of file diff --git a/lib/puppet/pops/issue_reporter.rb b/lib/puppet/pops/issue_reporter.rb index 675b03992..bc9511cbe 100644 --- a/lib/puppet/pops/issue_reporter.rb +++ b/lib/puppet/pops/issue_reporter.rb @@ -1,75 +1,75 @@ class Puppet::Pops::IssueReporter # @param acceptor [Puppet::Pops::Validation::Acceptor] the acceptor containing reported issues # @option options [String] :message (nil) A message text to use as prefix in a single Error message # @option options [Boolean] :emit_warnings (false) A message text to use as prefix in a single Error message # @option options [Boolean] :emit_errors (true) whether errors should be emitted or only given message # @option options [Exception] :exception_class (Puppet::ParseError) The exception to raise # def self.assert_and_report(acceptor, options) return unless acceptor max_errors = Puppet[:max_errors] max_warnings = Puppet[:max_warnings] + 1 max_deprecations = Puppet[:max_deprecations] + 1 emit_warnings = options[:emit_warnings] || false - emit_errors = options[:emit_errors] || true + emit_errors = options[:emit_errors].nil? ? true : !!options[:emit_errors] emit_message = options[:message] emit_exception = options[:exception_class] || Puppet::ParseError # If there are warnings output them warnings = acceptor.warnings if emit_warnings && warnings.size > 0 formatter = Puppet::Pops::Validation::DiagnosticFormatterPuppetStyle.new emitted_w = 0 emitted_dw = 0 acceptor.warnings.each do |w| if w.severity == :deprecation # Do *not* call Puppet.deprecation_warning it is for internal deprecation, not # deprecation of constructs in manifests! (It is not designed for that purpose even if # used throughout the code base). # Puppet.warning(formatter.format(w)) if emitted_dw < max_deprecations emitted_dw += 1 else Puppet.warning(formatter.format(w)) if emitted_w < max_warnings emitted_w += 1 end break if emitted_w > max_warnings && emitted_dw > max_deprecations # but only then end end # If there were errors, report the first found. Use a puppet style formatter. errors = acceptor.errors if errors.size > 0 unless emit_errors raise emit_exception.new(emit_message) end formatter = Puppet::Pops::Validation::DiagnosticFormatterPuppetStyle.new if errors.size == 1 || max_errors <= 1 # raise immediately raise emit_exception.new(format_with_prefix(emit_message, formatter.format(errors[0]))) end emitted = 0 if emit_message Puppet.err(emit_message) end errors.each do |e| Puppet.err(formatter.format(e)) emitted += 1 break if emitted >= max_errors end warnings_message = (emit_warnings && warnings.size > 0) ? ", and #{warnings.size} warnings" : "" giving_up_message = "Found #{errors.size} errors#{warnings_message}. Giving up" exception = emit_exception.new(giving_up_message) exception.file = errors[0].file raise exception end parse_result end def self.format_with_prefix(prefix, message) return message unless prefix [prefix, message].join(' ') end end \ No newline at end of file diff --git a/lib/puppet/pops/issues.rb b/lib/puppet/pops/issues.rb index 61663b167..f5b9017b2 100644 --- a/lib/puppet/pops/issues.rb +++ b/lib/puppet/pops/issues.rb @@ -1,266 +1,434 @@ # Defines classes to deal with issues, and message formatting and defines constants with Issues. # @api public # module Puppet::Pops::Issues # Describes an issue, and can produce a message for an occurrence of the issue. # class Issue # The issue code # @return [Symbol] attr_reader :issue_code # A block producing the message # @return [Proc] attr_reader :message_block # Names that must be bound in an occurrence of the issue to be able to produce a message. # These are the names in addition to requirements stipulated by the Issue formatter contract; i.e. :label`, # and `:semantic`. # attr_reader :arg_names # If this issue can have its severity lowered to :warning, :deprecation, or :ignored attr_writer :demotable # Configures the Issue with required arguments (bound by occurrence), and a block producing a message. def initialize issue_code, *args, &block @issue_code = issue_code @message_block = block @arg_names = args @demotable = true end # Returns true if it is allowed to demote this issue def demotable? @demotable end # Formats a message for an occurrence of the issue with argument bindings passed in a hash. # The hash must contain a LabelProvider bound to the key `label` and the semantic model element # bound to the key `semantic`. All required arguments as specified by `arg_names` must be bound # in the given `hash`. # @api public # def format(hash ={}) # Create a Message Data where all hash keys become methods for convenient interpolation # in issue text. msgdata = MessageData.new(*arg_names) begin # Evaluate the message block in the msg data's binding msgdata.format(hash, &message_block) rescue StandardError => e raise RuntimeError, "Error while reporting issue: #{issue_code}. #{e.message}", caller end end end # Provides a binding of arguments passed to Issue.format to method names available # in the issue's message producing block. # @api private # class MessageData def initialize *argnames singleton = class << self; self end argnames.each do |name| singleton.send(:define_method, name) do @data[name] end end end def format(hash, &block) @data = hash instance_eval &block end # Returns the label provider given as a key in the hash passed to #format. + # If given an argument, calls #label on the label provider (caller would otherwise have to + # call label.label(it) # - def label + def label(it = nil) raise "Label provider key :label must be set to produce the text of the message!" unless @data[:label] - @data[:label] + it.nil? ? @data[:label] : @data[:label].label(it) end # Returns the label provider given as a key in the hash passed to #format. # def semantic raise "Label provider key :semantic must be set to produce the text of the message!" unless @data[:semantic] @data[:semantic] end end # Defines an issue with the given `issue_code`, additional required parameters, and a block producing a message. # The block is evaluated in the context of a MessageData which provides convenient access to all required arguments # via accessor methods. In addition to accessors for specified arguments, these are also available: # * `label` - a `LabelProvider` that provides human understandable names for model elements and production of article (a/an/the). # * `semantic` - the model element for which the issue is reported # # @param issue_code [Symbol] the issue code for the issue used as an identifier, should be the same as the constant # the issue is bound to. # @param args [Symbol] required arguments that must be passed when formatting the message, may be empty # @param block [Proc] a block producing the message string, evaluated in a MessageData scope. The produced string # should not end with a period as additional information may be appended. # # @see MessageData # @api public # def self.issue (issue_code, *args, &block) Issue.new(issue_code, *args, &block) end # Creates a non demotable issue. # @see Issue.issue # def self.hard_issue(issue_code, *args, &block) result = Issue.new(issue_code, *args, &block) result.demotable = false result end # @comment Here follows definitions of issues. The intent is to provide a list from which yardoc can be generated # containing more detailed information / explanation of the issue. # These issues are set as constants, but it is unfortunately not possible for the created object to easily know which # name it is bound to. Instead the constant has to be repeated. (Alternatively, it could be done by instead calling # #const_set on the module, but the extra work required to get yardoc output vs. the extra effort to repeat the name # twice makes it not worth it (if doable at all, since there is no tag to artificially construct a constant, and # the parse tag does not produce any result for a constant assignment). # This is allowed (3.1) and has not yet been deprecated. # @todo configuration # NAME_WITH_HYPHEN = issue :NAME_WITH_HYPHEN, :name do "#{label.a_an_uc(semantic)} may not have a name containing a hyphen. The name '#{name}' is not legal" end # When a variable name contains a hyphen and these are illegal. # It is possible to control if a hyphen is legal in a name or not using the setting TODO # @todo describe the setting # @api public # @todo configuration if this is error or warning # VAR_WITH_HYPHEN = issue :VAR_WITH_HYPHEN, :name do "A variable name may not contain a hyphen. The name '#{name}' is not legal" end # A class, definition, or node may only appear at top level or inside other classes # @todo Is this really true for nodes? Can they be inside classes? Isn't that too late? # @api public # NOT_TOP_LEVEL = hard_issue :NOT_TOP_LEVEL do "Classes, definitions, and nodes may only appear at toplevel or inside other classes" end CROSS_SCOPE_ASSIGNMENT = hard_issue :CROSS_SCOPE_ASSIGNMENT, :name do "Illegal attempt to assign to '#{name}'. Cannot assign to variables in other namespaces" end # Assignment can only be made to certain types of left hand expressions such as variables. ILLEGAL_ASSIGNMENT = hard_issue :ILLEGAL_ASSIGNMENT do "Illegal attempt to assign to '#{label.a_an(semantic)}'. Not an assignable reference" end # Assignment cannot be made to numeric match result variables ILLEGAL_NUMERIC_ASSIGNMENT = issue :ILLEGAL_NUMERIC_ASSIGNMENT, :varname do "Illegal attempt to assign to the numeric match result variable '$#{varname}'. Numeric variables are not assignable" end + APPEND_FAILED = issue :APPEND_FAILED, :message do + "Append assignment += failed with error: #{message}" + end + + DELETE_FAILED = issue :DELETE_FAILED, :message do + "'Delete' assignment -= failed with error: #{message}" + end + # parameters cannot have numeric names, clashes with match result variables ILLEGAL_NUMERIC_PARAMETER = issue :ILLEGAL_NUMERIC_PARAMETER, :name do "The numeric parameter name '$#{varname}' cannot be used (clashes with numeric match result variables)" end # In certain versions of Puppet it may be allowed to assign to a not already assigned key # in an array or a hash. This is an optional validation that may be turned on to prevent accidental # mutation. # ILLEGAL_INDEXED_ASSIGNMENT = issue :ILLEGAL_INDEXED_ASSIGNMENT do "Illegal attempt to assign via [index/key]. Not an assignable reference" end # When indexed assignment ($x[]=) is allowed, the leftmost expression must be # a variable expression. # ILLEGAL_ASSIGNMENT_VIA_INDEX = hard_issue :ILLEGAL_ASSIGNMENT_VIA_INDEX do "Illegal attempt to assign to #{label.a_an(semantic)} via [index/key]. Not an assignable reference" end + # For unsupported operators (e.g. -= in puppet 3). + # + UNSUPPORTED_OPERATOR = hard_issue :UNSUPPORTED_OPERATOR, :operator do + "The operator '#{operator}' in #{label.a_an(semantic)} is not supported." + end + + # For non applicable operators (e.g. << on Hash). + # + OPERATOR_NOT_APPLICABLE = hard_issue :OPERATOR_NOT_APPLICABLE, :operator, :left_value do + "Operator '#{operator}' is not applicable to #{label.a_an(left_value)}." + end + + COMPARISON_NOT_POSSIBLE = hard_issue :COMPARISON_NOT_POSSIBLE, :operator, :left_value, :right_value, :detail do + "Comparison of: #{label(left_value)} #{operator} #{label(right_value)}, is not possible. Caused by '#{detail}'." + end + + MATCH_NOT_REGEXP = hard_issue :MATCH_NOT_REGEXP, :detail do + "Can not convert right match operand to a regular expression. Caused by '#{detail}'." + end + + MATCH_NOT_STRING = hard_issue :MATCH_NOT_STRING, :left_value do + "Left match operand must result in a String value. Got #{label.a_an(left_value)}." + end + # Some expressions/statements may not produce a value (known as right-value, or rvalue). # This may vary between puppet versions. # NOT_RVALUE = issue :NOT_RVALUE do "Invalid use of expression. #{label.a_an_uc(semantic)} does not produce a value" end # Appending to attributes is only allowed in certain types of resource expressions. # ILLEGAL_ATTRIBUTE_APPEND = hard_issue :ILLEGAL_ATTRIBUTE_APPEND, :name, :parent do "Illegal +> operation on attribute #{name}. This operator can not be used in #{label.a_an(parent)}" end ILLEGAL_NAME = hard_issue :ILLEGAL_NAME, :name do - "Illegal name. The given name #{name} does not conform to the naming rule \\A((::)?[a-z0-9]\w*)(::[a-z0-9]\w*)*\\z" + "Illegal name. The given name #{name} does not conform to the naming rule /^((::)?[a-z]\w*)(::[a-z]\w*)*$/" + end + + ILLEGAL_VAR_NAME = hard_issue :ILLEGAL_VAR_NAME, :name do + "Illegal variable name, The given name '#{name}' does not conform to the naming rule /^(::)?(\w+::)*\w+$/" + end + + ILLEGAL_NUMERIC_VAR_NAME = hard_issue :ILLEGAL_NUMERIC_VAR_NAME, :name do + "Illegal numeric variable name, The given name '#{name}' must be a decimal value if it starts with a digit 0-9" end # In case a model is constructed programmatically, it must create valid type references. # ILLEGAL_CLASSREF = hard_issue :ILLEGAL_CLASSREF, :name do "Illegal type reference. The given name '#{name}' does not conform to the naming rule" end # This is a runtime issue - storeconfigs must be on in order to collect exported. This issue should be # set to :ignore when just checking syntax. # @todo should be a :warning by default # RT_NO_STORECONFIGS = issue :RT_NO_STORECONFIGS do "You cannot collect exported resources without storeconfigs being set; the collection will be ignored" end # This is a runtime issue - storeconfigs must be on in order to export a resource. This issue should be # set to :ignore when just checking syntax. # @todo should be a :warning by default # RT_NO_STORECONFIGS_EXPORT = issue :RT_NO_STORECONFIGS_EXPORT do "You cannot collect exported resources without storeconfigs being set; the export is ignored" end # A hostname may only contain letters, digits, '_', '-', and '.'. # ILLEGAL_HOSTNAME_CHARS = hard_issue :ILLEGAL_HOSTNAME_CHARS, :hostname do "The hostname '#{hostname}' contains illegal characters (only letters, digits, '_', '-', and '.' are allowed)" end # A hostname may only contain letters, digits, '_', '-', and '.'. # ILLEGAL_HOSTNAME_INTERPOLATION = hard_issue :ILLEGAL_HOSTNAME_INTERPOLATION do "An interpolated expression is not allowed in a hostname of a node" end # Issues when an expression is used where it is not legal. # E.g. an arithmetic expression where a hostname is expected. # ILLEGAL_EXPRESSION = hard_issue :ILLEGAL_EXPRESSION, :feature, :container do "Illegal expression. #{label.a_an_uc(semantic)} is unacceptable as #{feature} in #{label.a_an(container)}" end + # Issues when an expression is used where it is not legal. + # E.g. an arithmetic expression where a hostname is expected. + # + ILLEGAL_VARIABLE_EXPRESSION = hard_issue :ILLEGAL_VARIABLE_EXPRESSION do + "Illegal variable expression. #{label.a_an_uc(semantic)} did not produce a variable name (String or Numeric)." + end + # Issues when an expression is used illegaly in a query. # query only supports == and !=, and not <, > etc. # ILLEGAL_QUERY_EXPRESSION = hard_issue :ILLEGAL_QUERY_EXPRESSION do "Illegal query expression. #{label.a_an_uc(semantic)} cannot be used in a query" end # If an attempt is made to make a resource default virtual or exported. # NOT_VIRTUALIZEABLE = hard_issue :NOT_VIRTUALIZEABLE do "Resource Defaults are not virtualizable" end # When an attempt is made to use multiple keys (to produce a range in Ruby - e.g. $arr[2,-1]). - # This is currently not supported, but may be in future versions + # This is not supported in 3x, but it allowed in 4x. # UNSUPPORTED_RANGE = issue :UNSUPPORTED_RANGE, :count do "Attempt to use unsupported range in #{label.a_an(semantic)}, #{count} values given for max 1" end DEPRECATED_NAME_AS_TYPE = issue :DEPRECATED_NAME_AS_TYPE, :name do "Resource references should now be capitalized. The given '#{name}' does not have the correct form" end + + ILLEGAL_RELATIONSHIP_OPERAND_TYPE = issue :ILLEGAL_RELATIONSHIP_OPERAND_TYPE, :operand do + "Illegal relationship operand, can not form a relationship with #{label.a_an(operand)}. A Catalog type is required." + end + + NOT_CATALOG_TYPE = issue :NOT_CATALOG_TYPE, :type do + "Illegal relationship operand, can not form a relationship with something of type #{type}. A Catalog type is required." + end + + BAD_STRING_SLICE_ARITY = issue :BAD_STRING_SLICE_ARITY, :actual do + "String supports [] with one or two arguments. Got #{actual}" + end + + BAD_ARRAY_SLICE_ARITY = issue :BAD_ARRAY_SLICE_ARITY, :actual do + "Array supports [] with one or two arguments. Got #{actual}" + end + + BAD_HASH_SLICE_ARITY = issue :BAD_HASH_SLICE_ARITY, :actual do + "Hash supports [] with one or more arguments. Got #{actual}" + end + + BAD_INTEGER_SLICE_ARITY = issue :BAD_INTEGER_SLICE_ARITY, :actual do + "Integer-Type supports [] with one or two arguments (from, to). Got #{actual}" + end + + BAD_INTEGER_SLICE_TYPE = issue :BAD_INTEGER_SLICE_TYPE, :actual do + "Integer-Type [] requires all arguments to be integers (or default). Got #{actual}" + end + + BAD_FLOAT_SLICE_ARITY = issue :BAD_INTEGER_SLICE_ARITY, :actual do + "Float-Type supports [] with one or two arguments (from, to). Got #{actual}" + end + + BAD_FLOAT_SLICE_TYPE = issue :BAD_INTEGER_SLICE_TYPE, :actual do + "Float-Type [] requires all arguments to be floats, or integers (or default). Got #{actual}" + end + + BAD_SLICE_KEY_TYPE = issue :BAD_SLICE_KEY_TYPE, :left_value, :expected_classes, :actual do + expected_text = if expected_classes.size > 1 + "one of #{expected_classes.join(', ')} are" + else + "#{expected_classes[0]} is" + end + "#{label.a_an_uc(left_value)}[] cannot use #{actual} where #{expected_text} expected" + end + + BAD_TYPE_SLICE_TYPE = issue :BAD_TYPE_SLICE_TYPE, :base_type, :actual do + "#{base_type}[] arguments must be types. Got #{actual}" + end + + BAD_TYPE_SLICE_ARITY = issue :BAD_TYPE_SLICE_ARITY, :base_type, :min, :max, :actual do + base_type_label = base_type.is_a?(String) ? base_type : label.a_an_uc(base_type) + if max == -1 || max == 1.0 / 0.0 # Infinity + "#{base_type_label}[] accepts #{min} or more arguments. Got #{actual}" + elsif max + "#{base_type_label}[] accepts #{min} to #{max} arguments. Got #{actual}" + else + "#{base_type_label}[] accepts #{min} #{label.plural_s(min, 'argument')}. Got #{actual}" + end + end + + BAD_TYPE_SPECIALIZATION = hard_issue :BAD_TYPE_SPECIALIZATION, :type, :message do + "Error creating type specialization of #{label.a_an(type)}, #{message}" + end + + ILLEGAL_TYPE_SPECIALIZATION = issue :ILLEGAL_TYPE_SPECIALIZATION, :kind do + "Cannot specialize an already specialized #{kind} type" + end + + ILLEGAL_RESOURCE_SPECIALIZATION = issue :ILLEGAL_RESOURCE_SPECIALIZATION, :actual do + "First argument to Resource[] must be a resource type or a String. Got #{actual}." + end + + ILLEGAL_HOSTCLASS_NAME = hard_issue :ILLEGAL_HOSTCLASS_NAME, :name do + "Illegal Class name in class reference. #{label.a_an_uc(name)} cannot be used where a String is expected" + end + + # Issues when an expression is used where it is not legal. + # E.g. an arithmetic expression where a hostname is expected. + # + ILLEGAL_DEFINITION_NAME = hard_issue :ILLEGAL_DEFINTION_NAME, :name do + "Unacceptable name. The name '#{name}' is unacceptable as the name of #{label.a_an(semantic)}" + end + + NOT_NUMERIC = issue :NOT_NUMERIC, :value do + "The value '#{value}' cannot be converted to Numeric." + end + + UNKNOWN_FUNCTION = issue :UNKNOWN_FUNCTION, :name do + "Unknown function: '#{name}'." + end + + UNKNOWN_VARIABLE = issue :UNKNOWN_VARIABLE, :name do + "Unknown variable: '#{name}'." + end + + RUNTIME_ERROR = issue :RUNTIME_ERROR, :detail do + "Error while evaluating #{label.a_an(semantic)}, #{detail}" + end + + UNKNOWN_RESOURCE_TYPE = issue :UNKNOWN_RESOURCE_TYPE, :type_name do + "Resource type not found: #{type_name.capitalize}" + end + + UNKNOWN_RESOURCE = issue :UNKNOWN_RESOURCE, :type_name, :title do + "Resource not found: #{type_name.capitalize}['#{title}']" + end + + UNKNOWN_RESOURCE_PARAMETER = issue :UNKNOWN_RESOURCE_PARAMETER, :type_name, :title, :param_name do + "The resource #{type_name.capitalize}['#{title}'] does not have a parameter called '#{param_name}'" + end + + DIV_BY_ZERO = hard_issue :DIV_BY_ZERO do + "Division by 0" + end + + RESULT_IS_INFINITY = hard_issue :RESULT_IS_INFINITY, :operator do + "The result of the #{operator} expression is Infinity" + end end diff --git a/lib/puppet/pops/label_provider.rb b/lib/puppet/pops/label_provider.rb index e8a75a784..3581c6183 100644 --- a/lib/puppet/pops/label_provider.rb +++ b/lib/puppet/pops/label_provider.rb @@ -1,71 +1,76 @@ # Provides a label for an object. # This simple implementation calls #to_s on the given object, and handles articles 'a/an/the'. # class Puppet::Pops::LabelProvider VOWELS = %w{a e i o u y} SKIPPED_CHARACTERS = %w{" '} A = "a" AN = "an" # Provides a label for the given object by calling `to_s` on the object. # The intent is for this method to be overridden in concrete label providers. def label o o.to_s end # Produces a label for the given text with indefinite article (a/an) def a_an o text = label(o) "#{article(text)} #{text}" end # Produces a label for the given text with indefinite article (A/An) def a_an_uc o text = label(o) "#{article(text).capitalize} #{text}" end # Produces a label for the given text with *definitie article* (the). def the o "the #{label(o)}" end # Produces a label for the given text with *definitie article* (The). def the_uc o "The #{label(o)}" end + # Appends 's' to (optional) text if count != 1 else an empty string + def plural_s(count, text = '') + count == 1 ? text : "#{text}s" + end + private # Produces an *indefinite article* (a/an) for the given text ('a' if # it starts with a vowel) This is obviously flawed in the general # sense as may labels have punctuation at the start and this method # does not translate punctuation to English words. Also, if a vowel is # pronounced as a consonant, the article should not be "an". # def article s article_for_letter(first_letter_of(s)) end def first_letter_of(string) char = string[0,1] if SKIPPED_CHARACTERS.include? char char = string[1,1] end if char == "" raise Puppet::DevError, "<#{string}> does not appear to contain a word" end char end def article_for_letter(letter) downcased = letter.downcase if VOWELS.include? downcased AN else A end end end diff --git a/lib/puppet/pops/model/ast_transformer.rb b/lib/puppet/pops/model/ast_transformer.rb index 2d4504050..1a64b5195 100644 --- a/lib/puppet/pops/model/ast_transformer.rb +++ b/lib/puppet/pops/model/ast_transformer.rb @@ -1,639 +1,658 @@ require 'puppet/parser/ast' # The receiver of `import(file)` calls; once per imported file, or nil if imports are ignored # # Transforms a Pops::Model to classic Puppet AST. # TODO: Documentation is currently skipped completely (it is only used for Rdoc) # class Puppet::Pops::Model::AstTransformer AST = Puppet::Parser::AST Model = Puppet::Pops::Model attr_reader :importer def initialize(source_file = "unknown-file", importer=nil) @@transform_visitor ||= Puppet::Pops::Visitor.new(nil,"transform",0,0) @@query_transform_visitor ||= Puppet::Pops::Visitor.new(nil,"query",0,0) @@hostname_transform_visitor ||= Puppet::Pops::Visitor.new(nil,"hostname",0,0) @importer = importer @source_file = source_file end # Initialize klass from o (location) and hash (options to created instance). # The object o is used to compute a source location. It may be nil. Source position is merged into # the given options (non surgically). If o is non-nil, the first found source position going up # the containment hierarchy is set. I.e. callers should pass nil if a source position is not wanted # or known to be unobtainable for the object. # # @param o [Object, nil] object from which source position / location is obtained, may be nil # @param klass [Class] the ast class to create an instance of # @param hash [Hash] hash with options for the class to create # def ast(o, klass, hash={}) # create and pass hash with file and line information klass.new(merge_location(hash, o)) end + # THIS IS AN EXPENSIVE OPERATION + # The 3x AST requires line, pos etc. to be recorded directly in the AST nodes and this information + # must be computed. + # (Newer implementation only computes the information that is actually needed; typically when raising an + # exception). + # def merge_location(hash, o) if o pos = {} source_pos = Puppet::Pops::Utils.find_adapter(o, Puppet::Pops::Adapters::SourcePosAdapter) if source_pos pos[:line] = source_pos.line pos[:pos] = source_pos.pos end pos[:file] = @source_file if @source_file hash = hash.merge(pos) end hash end # Transforms pops expressions into AST 3.1 statements/expressions def transform(o) + begin @@transform_visitor.visit_this(self,o) + rescue StandardError => e + loc_data = {} + merge_location(loc_data, o) + raise Puppet::ParseError.new("Error while transforming to Puppet 3 AST: #{e.message}", + loc_data[:file], loc_data[:line], loc_data[:pos], e) + end end # Transforms pops expressions into AST 3.1 query expressions def query(o) @@query_transform_visitor.visit_this(self, o) end # Transforms pops expressions into AST 3.1 hostnames def hostname(o) @@hostname_transform_visitor.visit_this(self, o) end - def transform_LiteralNumber(o) + def transform_LiteralFloat(o) + # Numbers are Names in the AST !! (Name a.k.a BareWord) + ast o, AST::Name, :value => o.value.to_s + end + + def transform_LiteralInteger(o) s = case o.radix when 10 o.value.to_s when 8 "0%o" % o.value when 16 "0x%X" % o.value else "bad radix:" + o.value.to_s end # Numbers are Names in the AST !! (Name a.k.a BareWord) ast o, AST::Name, :value => s end # Transforms all literal values to string (override for those that should not be AST::String) # def transform_LiteralValue(o) ast o, AST::String, :value => o.value.to_s end def transform_LiteralBoolean(o) ast o, AST::Boolean, :value => o.value end def transform_Factory(o) transform(o.current) end def transform_ArithmeticExpression(o) ast o, AST::ArithmeticOperator2, :lval => transform(o.left_expr), :rval=>transform(o.right_expr), :operator => o.operator.to_s end def transform_Array(o) ast nil, AST::ASTArray, :children => o.collect {|x| transform(x) } end # Puppet AST only allows: # * variable[expression] => Hasharray Access # * NAME [expressions] => Resource Reference(s) # * type [epxressions] => Resource Reference(s) # * HashArrayAccesses[expression] => HasharrayAccesses # # i.e. it is not possible to do `func()[3]`, `[1,2,3][$x]`, `{foo=>10, bar=>20}[$x]` etc. since # LHS is not an expression # # Validation for 3.x semantics should validate the illegal cases. This transformation may fail, # or ignore excess information if the expressions are not correct. # This means that the transformation does not have to evaluate the lhs to detect the target expression. # # Hm, this seems to have changed, the LHS (variable) is evaluated if evaluateable, else it is used as is. # def transform_AccessExpression(o) case o.left_expr when Model::QualifiedName ast o, AST::ResourceReference, :type => o.left_expr.value, :title => transform(o.keys) when Model::QualifiedReference ast o, AST::ResourceReference, :type => o.left_expr.value, :title => transform(o.keys) when Model::VariableExpression ast o, AST::HashOrArrayAccess, :variable => transform(o.left_expr), :key => transform(o.keys()[0]) else ast o, AST::HashOrArrayAccess, :variable => transform(o.left_expr), :key => transform(o.keys()[0]) end end # Puppet AST has a complicated structure # LHS can not be an expression, it must be a type (which is downcased). # type = a downcased QualifiedName # def transform_CollectExpression(o) raise "LHS is not a type" unless o.type_expr.is_a? Model::QualifiedReference type = o.type_expr.value().downcase() args = { :type => type } # This somewhat peculiar encoding is used by the 3.1 AST. query = transform(o.query) if query.is_a? Symbol args[:form] = query else args[:form] = query.form args[:query] = query query.type = type end if o.operations.size > 0 args[:override] = transform(o.operations) end ast o, AST::Collection, args end def transform_ExportedQuery(o) if is_nop?(o.expr) result = :exported else result = query(o.expr) result.form = :exported end result end def transform_VirtualQuery(o) if is_nop?(o.expr) result = :virtual else result = query(o.expr) result.form = :virtual end result end # Ensures transformation fails if a 3.1 non supported object is encountered in a query expression # def query_Object(o) raise "Not a valid expression in a collection query: "+o.class.name end # Puppet AST only allows == and !=, and left expr is restricted, but right value is an expression # def query_ComparisonExpression(o) if [:'==', :'!='].include? o.operator ast o, AST::CollExpr, :test1 => query(o.left_expr), :oper => o.operator.to_s, :test2 => transform(o.right_expr) else raise "Not a valid comparison operator in a collection query: " + o.operator.to_s end end def query_AndExpression(o) ast o, AST::CollExpr, :test1 => query(o.left_expr), :oper => 'and', :test2 => query(o.right_expr) end def query_OrExpression(o) ast o, AST::CollExpr, :test1 => query(o.left_expr), :oper => 'or', :test2 => query(o.right_expr) end def query_ParenthesizedExpression(o) result = query(o.expr) # produces CollExpr result.parens = true result end def query_VariableExpression(o) transform(o) end def query_QualifiedName(o) transform(o) end def query_LiteralNumber(o) transform(o) # number to string in correct radix end def query_LiteralString(o) transform(o) end def query_LiteralBoolean(o) transform(o) end def transform_QualifiedName(o) ast o, AST::Name, :value => o.value end def transform_QualifiedReference(o) ast o, AST::Type, :value => o.value end def transform_ComparisonExpression(o) ast o, AST::ComparisonOperator, :operator => o.operator.to_s, :lval => transform(o.left_expr), :rval => transform(o.right_expr) end def transform_AndExpression(o) ast o, AST::BooleanOperator, :operator => 'and', :lval => transform(o.left_expr), :rval => transform(o.right_expr) end def transform_OrExpression(o) ast o, AST::BooleanOperator, :operator => 'or', :lval => transform(o.left_expr), :rval => transform(o.right_expr) end def transform_InExpression(o) ast o, AST::InOperator, :lval => transform(o.left_expr), :rval => transform(o.right_expr) end # This is a complex transformation from a modeled import to a Nop result (where the import took place), # and calls to perform import/parsing etc. during the transformation. # When testing syntax, the @importer does not have to be set, but it is not possible to check # the actual import without inventing a new AST::ImportExpression with nop effect when evaluating. def transform_ImportExpression(o) if importer o.files.each {|f| unless f.is_a? Model::LiteralString raise "Illegal import file expression. Must be a single quoted string" end importer.import(f.value) } end # Crazy stuff # Transformation of "import" needs to parse the other files at the time of transformation. # Then produce a :nop, since nothing should be evaluated. ast o, AST::Nop, {} end - def transform_InstanceReferences(o) - ast o, AST::ResourceReference, :type => o.type_name.value, :title => transform(o.names) - end - # Assignment in AST 3.1 is to variable or hasharray accesses !!! See Bug #16116 def transform_AssignmentExpression(o) args = {:value => transform(o.right_expr) } - args[:append] = true if o.operator == :'+=' + case o.operator + when :'+=' + args[:append] = true + when :'=' + else + raise "The operator #{o.operator} is not supported by Puppet 3." + end args[:name] = case o.left_expr when Model::VariableExpression ast o, AST::Name, {:value => o.left_expr.expr.value } when Model::AccessExpression transform(o.left_expr) else raise "LHS is not an expression that can be assigned to" end ast o, AST::VarDef, args end # Produces (name => expr) or (name +> expr) def transform_AttributeOperation(o) args = { :value => transform(o.value_expr) } args[:add] = true if o.operator == :'+>' args[:param] = o.attribute_name ast o, AST::ResourceParam, args end def transform_LiteralList(o) # Uses default transform of Ruby Array to ASTArray transform(o.values) end # Literal hash has strange behavior in Puppet 3.1. See Bug #19426, and this implementation is bug # compatible def transform_LiteralHash(o) if o.entries.size == 0 ast o, AST::ASTHash, {:value=> {}} else value = {} o.entries.each {|x| value.merge! transform(x) } ast o, AST::ASTHash, {:value=> value} end end # Transforms entry into a hash (they are later merged with strange effects: Bug #19426). # Puppet 3.x only allows: # * NAME # * quotedtext # As keys (quoted text can be an interpolated string which is compared as a key in a less than satisfactory way). # def transform_KeyedEntry(o) value = transform(o.value) key = case o.key when Model::QualifiedName o.key.value when Model::LiteralString transform o.key when Model::LiteralNumber transform o.key when Model::ConcatenatedString transform o.key else raise "Illegal hash key expression of type (#{o.key.class})" end {key => value} end def transform_MatchExpression(o) ast o, AST::MatchOperator, :operator => o.operator.to_s, :lval => transform(o.left_expr), :rval => transform(o.right_expr) end def transform_LiteralString(o) ast o, AST::String, :value => o.value end - # Literal text in a concatenated string - def transform_LiteralText(o) - ast o, AST::String, :value => o.value - end - def transform_LambdaExpression(o) astargs = { :parameters => o.parameters.collect {|p| transform(p) } } astargs.merge!({ :children => transform(o.body) }) if o.body # do not want children if it is nil/nop ast o, AST::Lambda, astargs end def transform_LiteralDefault(o) ast o, AST::Default, :value => :default end def transform_LiteralUndef(o) ast o, AST::Undef, :value => :undef end def transform_LiteralRegularExpression(o) ast o, AST::Regex, :value => o.value end def transform_Nop(o) ast o, AST::Nop end # In the 3.1. grammar this is a hash that is merged with other elements to form a method call # Also in 3.1. grammar there are restrictions on the LHS (that are only there for grammar issues). # def transform_NamedAccessExpression(o) receiver = transform(o.left_expr) name = o.right_expr raise "Unacceptable function/method name" unless name.is_a? Model::QualifiedName {:receiver => receiver, :name => name.value} end def transform_NilClass(o) ast o, AST::Nop, {} end def transform_NotExpression(o) ast o, AST::Not, :value => transform(o.expr) end def transform_VariableExpression(o) # assumes the expression is a QualifiedName ast o, AST::Variable, :value => o.expr.value end # In Puppet 3.1, the ConcatenatedString is responsible for the evaluation and stringification of # expression segments. Expressions and Strings are kept in an array. def transform_TextExpression(o) transform(o.expr) end def transform_UnaryMinusExpression(o) ast o, AST::Minus, :value => transform(o.expr) end # Puppet 3.1 representation of a BlockExpression is an AST::Array - this makes it impossible to differentiate # between a LiteralArray and a Sequence. (Should it return the collected array, or the last expression?) # (A BlockExpression has now been introduced in the AST to solve this). # def transform_BlockExpression(o) children = [] # remove nops resulting from import o.statements.each {|s| r = transform(s); children << r unless is_nop?(r) } ast o, AST::BlockExpression, :children => children # o.statements.collect {|s| transform(s) } end # Interpolated strings are kept in an array of AST (string or other expression). def transform_ConcatenatedString(o) ast o, AST::Concat, :value => o.segments.collect {|x| transform(x)} end def transform_HostClassDefinition(o) parameters = o.parameters.collect {|p| transform(p) } args = { :arguments => parameters, :parent => o.parent_class, } args[:code] = transform(o.body) unless is_nop?(o.body) Puppet::Parser::AST::Hostclass.new(o.name, merge_location(args, o)) end def transform_NodeDefinition(o) # o.host_matches are expressions, and 3.1 AST requires special object AST::HostName # where a HostName is one of NAME, STRING, DEFAULT or Regexp - all of these are strings except regexp # args = { :code => transform(o.body) } args[:parent] = hostname(o.parent) unless is_nop?(o.parent) if(args[:parent].is_a?(Array)) raise "Illegal expression - unacceptable as a node parent" end Puppet::Parser::AST::Node.new(hostname(o.host_matches), merge_location(args, o)) end # Transforms Array of host matching expressions into a (Ruby) array of AST::HostName def hostname_Array(o) o.collect {|x| ast x, AST::HostName, :value => hostname(x) } end def hostname_LiteralValue(o) return o.value end def hostname_QualifiedName(o) return o.value end def hostname_LiteralNumber(o) transform(o) # Number to string with correct radix end def hostname_LiteralDefault(o) return 'default' end def hostname_LiteralRegularExpression(o) ast o, AST::Regex, :value => o.value end def hostname_Object(o) raise "Illegal expression - unacceptable as a node name" end def transform_RelationshipExpression(o) Puppet::Parser::AST::Relationship.new(transform(o.left_expr), transform(o.right_expr), o.operator.to_s, merge_location({}, o)) end def transform_ResourceTypeDefinition(o) parameters = o.parameters.collect {|p| transform(p) } args = { :arguments => parameters } args[:code] = transform(o.body) unless is_nop?(o.body) Puppet::Parser::AST::Definition.new(o.name, merge_location(args, o)) end # Transformation of ResourceOverrideExpression is slightly more involved than a straight forward # transformation. # A ResourceOverrideExppression has "resources" which should be an AccessExpression # on the form QualifiedName[expressions], or QualifiedReference[expressions] to be valid. # It also has a set of attribute operations. # # The AST equivalence is an AST::ResourceOverride with a ResourceReference as its LHS, and # a set of Parameters. # ResourceReference has type as a string, and the expressions representing # the "titles" to be an ASTArray. # def transform_ResourceOverrideExpression(o) resource_ref = o.resources raise "Unacceptable expression for resource override" unless resource_ref.is_a? Model::AccessExpression type = case resource_ref.left_expr when Model::QualifiedName # This is deprecated "Resource references should now be capitalized" - this is caught elsewhere resource_ref.left_expr.value when Model::QualifiedReference resource_ref.left_expr.value else raise "Unacceptable expression for resource override; need NAME or CLASSREF" end result_ref = ast o, AST::ResourceReference, :type => type, :title => transform(resource_ref.keys) # title is one or more expressions, if more than one it should be an ASTArray ast o, AST::ResourceOverride, :object => result_ref, :parameters => transform(o.operations) end # Parameter is a parameter in a definition of some kind. # It is transformed to an array on the form `[name]´, or `[name, value]´. def transform_Parameter(o) if o.value [o.name, transform(o.value)] else [o.name] end end # For non query expressions, parentheses can be dropped in the resulting AST. def transform_ParenthesizedExpression(o) transform(o.expr) end + def transform_Program(o) + transform(o.body) + end + def transform_IfExpression(o) args = { :test => transform(o.test), :statements => transform(o.then_expr) } args[:else] = transform(o.else_expr) # Tests say Nop should be there (unless is_nop? o.else_expr), probably not needed ast o, AST::IfStatement, args end # Unless is not an AST object, instead an AST::IfStatement is used with an AST::Not around the test # def transform_UnlessExpression(o) args = { :test => ast(o, AST::Not, :value => transform(o.test)), :statements => transform(o.then_expr) } # AST 3.1 does not allow else on unless in the grammar, but it is ok since unless is encoded as an if !x args.merge!({:else => transform(o.else_expr)}) unless is_nop?(o.else_expr) ast o, AST::IfStatement, args end # Puppet 3.1 AST only supports calling a function by name (it is not possible to produce a function # that is then called). # rval_required (for an expression) # functor_expr (lhs - the "name" expression) # arguments - list of arguments # def transform_CallNamedFunctionExpression(o) name = o.functor_expr raise "Unacceptable expression for name of function" unless name.is_a? Model::QualifiedName args = { :name => name.value, :arguments => transform(o.arguments), :ftype => o.rval_required ? :rvalue : :statement } args[:pblock] = transform(o.lambda) if o.lambda ast o, AST::Function, args end # Transformation of CallMethodExpression handles a NamedAccessExpression functor and # turns this into a 3.1 AST::MethodCall. # def transform_CallMethodExpression(o) name = o.functor_expr raise "Unacceptable expression for name of function" unless name.is_a? Model::NamedAccessExpression # transform of NamedAccess produces a hash, add arguments to it astargs = transform(name).merge(:arguments => transform(o.arguments)) astargs.merge!(:lambda => transform(o.lambda)) if o.lambda # do not want a Nop as the lambda ast o, AST::MethodCall, astargs end def transform_CaseExpression(o) # Expects expression, AST::ASTArray of AST ast o, AST::CaseStatement, :test => transform(o.test), :options => transform(o.options) end def transform_CaseOption(o) ast o, AST::CaseOpt, :value => transform(o.values), :statements => transform(o.then_expr) end def transform_ResourceBody(o) # expects AST, AST::ASTArray of AST ast o, AST::ResourceInstance, :title => transform(o.title), :parameters => transform(o.operations) end def transform_ResourceDefaultsExpression(o) ast o, AST::ResourceDefaults, :type => o.type_ref.value, :parameters => transform(o.operations) end # Transformation of ResourceExpression requires calling a method on the resulting # AST::Resource if it is virtual or exported # def transform_ResourceExpression(o) raise "Unacceptable type name expression" unless o.type_name.is_a? Model::QualifiedName resource = ast o, AST::Resource, :type => o.type_name.value, :instances => transform(o.bodies) resource.send("#{o.form}=", true) unless o.form == :regular resource end # Transformation of SelectorExpression is limited to certain types of expressions. # This is probably due to constraints in the old grammar rather than any real concerns. def transform_SelectorExpression(o) case o.left_expr when Model::CallNamedFunctionExpression when Model::AccessExpression when Model::VariableExpression when Model::ConcatenatedString else raise "Unacceptable select expression" unless o.left_expr.kind_of? Model::Literal end ast o, AST::Selector, :param => transform(o.left_expr), :values => transform(o.selectors) end def transform_SelectorEntry(o) ast o, AST::ResourceParam, :param => transform(o.matching_expr), :value => transform(o.value_expr) end def transform_Object(o) raise "Unacceptable transform - found an Object without a rule: #{o.class}" end # Nil, nop # Bee bopp a luh-lah, a bop bop boom. # def is_nop?(o) o.nil? || o.is_a?(Model::Nop) end end diff --git a/lib/puppet/pops/model/ast_tree_dumper.rb b/lib/puppet/pops/model/ast_tree_dumper.rb index 624b12267..b5c7ce692 100644 --- a/lib/puppet/pops/model/ast_tree_dumper.rb +++ b/lib/puppet/pops/model/ast_tree_dumper.rb @@ -1,378 +1,382 @@ require 'puppet/parser/ast' # 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::AstTreeDumper < Puppet::Pops::Model::TreeDumper AST = Puppet::Parser::AST Model = Puppet::Pops::Model - def dump_LiteralNumber o + 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_Factory o do_dump(o.current) end def dump_ArithmeticOperator o [o.operator.to_s, do_dump(o.lval), do_dump(o.rval)] end def dump_Relationship o [o.arrow.to_s, do_dump(o.left), do_dump(o.right)] end # Hostname is tricky, it is either a bare word, a string, or default, or regular expression # Least evil, all strings except default are quoted def dump_HostName o result = do_dump o.value unless o.value.is_a? AST::Regex result = result == "default" ? ":default" : "'#{result}'" end result end # x[y] prints as (slice x y) def dump_HashOrArrayAccess o var = o.variable.is_a?(String) ? "$#{o.variable}" : do_dump(o.variable) ["slice", var, do_dump(o.key)] end # The AST Collection knows about exported or virtual query, not the query. def dump_Collection o result = ["collect", do_dump(o.type), :indent, :break] if o.form == :virtual q = ["<| |>"] else q = ["<<| |>>"] end q << do_dump(o.query) unless is_nop?(o.query) q << :indent result << q o.override do |ao| result << :break << do_dump(ao) end result += [:dedent, :dedent ] result end def dump_CollExpr o operator = case o.oper when 'and' '&&' when 'or' '||' else o.oper end [operator, do_dump(o.test1), do_dump(o.test2)] end def dump_ComparisonOperator o [o.operator.to_s, do_dump(o.lval), do_dump(o.rval)] end def dump_Boolean o o.to_s end def dump_BooleanOperator o operator = o.operator == 'and' ? '&&' : '||' [operator, do_dump(o.lval), do_dump(o.rval)] end def dump_InOperator o ["in", do_dump(o.lval), do_dump(o.rval)] end # $x = ... # $x += ... # def dump_VarDef o operator = o.append ? "+=" : "=" [operator, '$' + do_dump(o.name), do_dump(o.value)] end # Produces (name => expr) or (name +> expr) def dump_ResourceParam o operator = o.add ? "+>" : "=>" [do_dump(o.param), operator, do_dump(o.value)] end def dump_Array o o.collect {|e| do_dump(e) } end def dump_ASTArray o ["[]"] + o.children.collect {|x| do_dump(x)} end def dump_ASTHash o ["{}"] + o.value.sort_by{|k,v| k.to_s}.collect {|x| [do_dump(x[0]), do_dump(x[1])]} # ["{}"] + o.value.collect {|x| [do_dump(x[0]), do_dump(x[1])]} end def dump_MatchOperator o [o.operator.to_s, do_dump(o.lval), do_dump(o.rval)] end # Dump a Ruby String in single quotes unless it is a number. def dump_String o if o.is_a? String o # A Ruby String, not quoted elsif Puppet::Pops::Utils.to_n(o.value) o.value # AST::String that is a number without quotes else "'#{o.value}'" # AST::String that is not a number end end def dump_Lambda o result = ["lambda"] result << ["parameters"] + o.parameters.collect {|p| _dump_ParameterArray(p) } if o.parameters.size() > 0 if o.children == [] result << [] # does not have a lambda body else result << do_dump(o.children) end result end def dump_Default o ":default" end def dump_Undef o ":undef" end # Note this is Regex (the AST kind), not Ruby Regexp def dump_Regex o "/#{o.value.source}/" end def dump_Nop o ":nop" end def dump_NilClass o "()" end def dump_Not o ['!', dump(o.value)] end def dump_Variable o "$#{dump(o.value)}" end def dump_Minus o ['-', do_dump(o.value)] end def dump_BlockExpression o ["block"] + o.children.collect {|x| do_dump(x) } end # Interpolated strings are shown as (cat seg0 seg1 ... segN) def dump_Concat o ["cat"] + o.value.collect {|x| x.is_a?(AST::String) ? " "+do_dump(x) : ["str", do_dump(x)]} end def dump_Hostclass o # ok, this is kind of crazy stuff in the AST, information in a context instead of in AST, and # parameters are in a Ruby Array with each parameter being an Array... # context = o.context args = context[:arguments] parent = context[:parent] result = ["class", o.name] result << ["inherits", parent] if parent result << ["parameters"] + args.collect {|p| _dump_ParameterArray(p) } if args && args.size() > 0 if is_nop?(o.code) result << [] else result << do_dump(o.code) end result end def dump_Name o o.value end def dump_Node o context = o.context parent = context[:parent] code = context[:code] result = ["node"] result << ["matches"] + o.names.collect {|m| do_dump(m) } result << ["parent", do_dump(parent)] if !is_nop?(parent) if is_nop?(code) result << [] else result << do_dump(code) end result end def dump_Definition o # ok, this is even crazier that Hostclass. The name of the define does not have an accessor # and some things are in the context (but not the name). Parameters are called arguments and they # are in a Ruby Array where each parameter is an array of 1 or 2 elements. # context = o.context name = o.instance_variable_get("@name") args = context[:arguments] code = context[:code] result = ["define", name] result << ["parameters"] + args.collect {|p| _dump_ParameterArray(p) } if args && args.size() > 0 if is_nop?(code) result << [] else result << do_dump(code) end result end def dump_ResourceReference o result = ["slice", do_dump(o.type)] if o.title.children.size == 1 result << do_dump(o.title[0]) else result << do_dump(o.title.children) end result end def dump_ResourceOverride o result = ["override", do_dump(o.object), :indent] o.parameters.each do |p| result << :break << do_dump(p) end result << :dedent result end # Puppet AST encodes a parameter as a one or two slot Array. # This is not a polymorph dump method. # def _dump_ParameterArray o if o.size == 2 ["=", o[0], do_dump(o[1])] else o[0] end end def dump_IfStatement o result = ["if", do_dump(o.test), :indent, :break, ["then", :indent, do_dump(o.statements), :dedent]] result += [:break, ["else", :indent, do_dump(o.else), :dedent], :dedent] unless is_nop? o.else result end # Produces (invoke name args...) when not required to produce an rvalue, and # (call name args ... ) otherwise. # def dump_Function o # somewhat ugly as Function hides its "ftype" instance variable result = [o.instance_variable_get("@ftype") == :rvalue ? "call" : "invoke", do_dump(o.name)] o.arguments.collect {|a| result << do_dump(a) } result << do_dump(o.pblock) if o.pblock result end def dump_MethodCall o # somewhat ugly as Method call (does the same as function) and hides its "ftype" instance variable result = [o.instance_variable_get("@ftype") == :rvalue ? "call-method" : "invoke-method", [".", do_dump(o.receiver), do_dump(o.name)]] o.arguments.collect {|a| result << do_dump(a) } result << do_dump(o.lambda) if o.lambda result end def dump_CaseStatement o result = ["case", do_dump(o.test), :indent] o.options.each do |s| result << :break << do_dump(s) end result << :dedent end def dump_CaseOpt o result = ["when"] result << o.value.collect {|x| do_dump(x) } # A bit of trickery to get it into the same shape as Pops output if is_nop?(o.statements) result << ["then", []] # Puppet AST has a nop if there is no body else result << ["then", do_dump(o.statements) ] end result end def dump_ResourceInstance o result = [do_dump(o.title), :indent] o.parameters.each do |p| result << :break << do_dump(p) end result << :dedent result end def dump_ResourceDefaults o result = ["resource-defaults", do_dump(o.type), :indent] o.parameters.each do |p| result << :break << do_dump(p) end result << :dedent result end def dump_Resource o if o.exported form = 'exported-' elsif o.virtual form = 'virtual-' else form = '' end result = [form+"resource", do_dump(o.type), :indent] o.instances.each do |b| result << :break << do_dump(b) end result << :dedent result end def dump_Selector o values = o.values values = [values] unless values.instance_of? AST::ASTArray or values.instance_of? Array ["?", do_dump(o.param)] + values.collect {|x| do_dump(x) } end def dump_Object o ['dev-error-no-polymorph-dump-for:', o.class.to_s, o.to_s] end def is_nop? o o.nil? || o.is_a?(Model::Nop) || o.is_a?(AST::Nop) end end diff --git a/lib/puppet/pops/model/factory.rb b/lib/puppet/pops/model/factory.rb index 3cfa15fca..c224a0ba0 100644 --- a/lib/puppet/pops/model/factory.rb +++ b/lib/puppet/pops/model/factory.rb @@ -1,816 +1,930 @@ # 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 popsobj, *args - @current = to_ops(popsobj, *args) + 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 # require 'debugger'; debugger # enable this when in trouble... raise e end end + # Polymorphic interpolate + def interpolate() + begin + @@interpolation_visitor.visit_this(self, current) + rescue =>e + # require 'debugger'; debugger # enable this 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 # @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 # # @param name [String] a valid classname # # @param parameters [Array] may be empty # # @param body [Array, Expression, nil] expression that constitute the body # # @return [Model::HostClassDefinition] configured from the parameters # # # def build_ResourceTypeDefinition(o, name, parameters, body) # build_NamedDefinition(o, name, parameters, body) # o.name = name # parameters.each {|p| o.addParameters(build(p)) } # b = f_build_body(body) # o.body = b.current if b # o # end def build_ResourceOverrideExpression(o, resources, attribute_operations) o.resources = build(resources) attribute_operations.each {|ao| o.addOperations(build(ao)) } o end def build_KeyedEntry(o, k, v) o.key = build(k) o.value = build(v) o end def build_LiteralHash(o, *keyed_entries) keyed_entries.each {|entry| o.addEntries build(entry) } o end def build_LiteralList(o, *values) values.each {|v| o.addValues build(v) } o end - def build_LiteralNumber(o, val, radix) + def build_LiteralFloat(o, val) o.value = val - o.radix = radix o end - def build_InstanceReferences(o, type_name, name_expressions) - o.type_name = build(type_name) - name_expressions.each {|n| o.addNames(build(n)) } + def build_LiteralInteger(o, val, radix) + o.value = val + o.radix = radix o end def build_ImportExpression(o, files) # The argument files has already been built files.each {|f| o.addFiles(to_ops(f)) } 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_Definition(o, parameters, body) + def build_LambdaExpression(o, parameters, body) parameters.each {|p| o.addParameters(build(p)) } b = f_build_body(body) o.body = b.current if b o end def build_NamedDefinition(o, name, parameters, body) - build_Definition(o, 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_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 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) + o.body = to_ops(body) + # non containment + definitions.each { |d| o.addDefinitions(d) } + 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) - current.respond_to?(meth) || super + 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_pos, end_pos) + def record_position(start_locatable, end_locatable) Puppet::Pops::Adapters::SourcePosAdapter.adapt(current) do |a| - a.line = start_pos.line - a.offset = start_pos.offset - a.pos = start_pos.pos - a.length = start_pos.length - if(end_pos.offset && end_pos.length) - a.length = end_pos.offset + end_pos.length - start_pos.offset + if start_locatable && end_locatable + a.locatable = Puppet::Pops::Parser::Locatable::Range.new(start_locatable, end_locatable) + else + a.locatable = Puppet::Pops::Parser::Locatable::Lazy.new(start_locatable) end + +# a.line = start_pos.line +# a.offset = start_pos.offset +# a.pos = start_pos.pos +# a.length = start_pos.length +# if(end_pos.offset && end_pos.length) +# a.length = end_pos.offset + end_pos.length - start_pos.offset +# end end self end # Records the origin file of an element # Does nothing if file is nil. # # @param file [String,nil] the file/path to the origin, may contain URI scheme of file: or some other URI scheme # @return [Factory] returns self # def record_origin(file) return self unless file Puppet::Pops::Adapters::OriginAdapter.adapt(current) do |a| a.origin = file end self end # @return [Puppet::Pops::Adapters::SourcePosAdapter] with location information def loc() Puppet::Pops::Adapters::SourcePosAdapter.adapt(current) end - # Returns documentation string, or nil if not available - # @return [String, nil] associated documentation if available - def doc() - a = Puppet::Pops::Adapters::SourcePosAdapter.adapt(current) - return a.documentation if a - nil - end - - def doc=(doc_string) - a = Puppet::Pops::Adapters::SourcePosAdapter.adapt(current) - a.documentation = doc_string - 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 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, expr) + new(Model::TextExpression, new(expr).interpolate) 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 - # Convert input string to either a qualified name, or a LiteralNumber with radix + 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) - new(Model::LiteralNumber, *n_radix) + 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 - # Used by regular grammar, egrammar creates an AccessExpression instead, and evaluation determines - # if access is to instances or something else. - # - def self.INSTANCE(type_name, name_expressions) - new(Model::InstanceReferences, type_name, name_expressions) - 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, Puppet::Pops::Model::Factory.fqr(type_expr), query_expr, attribute_operations) + new(Model::CollectExpression, type_expr, query_expr, attribute_operations) +# new(Model::CollectExpression, Puppet::Pops::Model::Factory.fqr(type_expr), query_expr, attribute_operations) end def self.IMPORT(files) new(Model::ImportExpression, files) 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) + new(Model::Program, body, definitions) + end + # Builds a BlockExpression if args size > 1, else the single expression/value in args def self.block_or_expression(*args) if args.size > 1 new(Model::BlockExpression, *args) else new(args[0]) end end def self.HOSTCLASS(name, parameters, parent, body) new(Model::HostClassDefinition, name, parameters, parent, body) end def self.DEFINITION(name, parameters, body) new(Model::ResourceTypeDefinition, name, parameters, body) end def self.LAMBDA(parameters, body) new(Model::LambdaExpression, parameters, body) end def self.nop? o o.nil? || o.is_a?(Puppet::Pops::Model::Nop) end # Transforms an array of expressions containing literal name expressions to calls if followed by an # expression, or expression list. Also transforms a "call" to `import` into an ImportExpression. # 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 if name.value() == 'import' memo[-1] = Puppet::Pops::Model::Factory.IMPORT(expr.is_a?(Array) ? expr : [expr]) else 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 end else memo << expr if expr.is_a?(Model::CallNamedFunctionExpression) # Patch rvalue expression function call to statement style. # This is not really required but done to be AST model compliant expr.rval_required = false end end memo end end # Building model equivalences of Ruby objects # Allows passing regular ruby objects to the factory to produce instructions # that when evaluated produce the same thing. def build_String(o) x = Model::LiteralString.new x.value = o; x end def build_NilClass(o) x = Model::Nop.new x end def build_TrueClass(o) x = Model::LiteralBoolean.new x.value = o x end def build_FalseClass(o) x = Model::LiteralBoolean.new x.value = o x end def build_Fixnum(o) - x = Model::LiteralNumber.new + x = Model::LiteralInteger.new x.value = o; x end def build_Float(o) - x = Model::LiteralNumber.new + x = Model::LiteralFloat.new x.value = o; x end def build_Regexp(o) x = Model::LiteralRegularExpression.new x.value = o; x 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 # # @param rval_required [Boolean] if the call must produce a value # def build_CallNamedFunctionExpression(o, name, rval_required, *args) # build_CallExpression(o, name, rval_required, *args) ## o.functor_expr = build(name) ## o.rval_required = rval_required ## args.each {|x| o.addArguments(build(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) - if o.kind_of?(Model::PopsObject) + 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 5f5302be3..6f7d58f75 100644 --- a/lib/puppet/pops/model/model.rb +++ b/lib/puppet/pops/model/model.rb @@ -1,567 +1,492 @@ # # 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. # -# TODO: Anonymous Enums - probably ok, but they can be named (don't know if that is meaningsful) require 'rgen/metamodel_builder' module Puppet::Pops::Model + extend RGen::MetamodelBuilder::ModuleExtension + # A base class for modeled objects that makes them Visitable, and Adaptable. - # @todo currently includes Containment which will not be needed when the corresponding methods - # are added to RGen (in some version after 0.6.2). # class PopsObject < RGen::MetamodelBuilder::MMBase include Puppet::Pops::Visitable include Puppet::Pops::Adaptable include Puppet::Pops::Containment abstract end # @abstract base class for expressions class Expression < PopsObject 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 # An import of one or several files. # class ImportExpression < Expression contains_many_uni 'files', Expression, :lowerBound => 1 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 + 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 it typically used as an entry in a Hash. # class KeyedEntry < PopsObject 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 < PopsObject 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 optional attribute operation sets or appends a value to a named attribute unless - # the value is undef/nil in which case the opereration is a Nop. - # - # This is a new feature proposed to solve the undef as antimatter problem - # @note Currently Unused - # - class OptionalAttributeOperation < AttributeOperation - 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 < PopsObject has_attr 'name', String, :lowerBound => 1 contains_one_uni 'value', Expression end # Abstract base class for definitions. # class Definition < Expression abstract - contains_many_uni 'parameters', Parameter - contains_one_uni 'body', Expression end - # Abstract base class for named definitions. + # Abstract base class for named and parameterized definitions. class NamedDefinition < Definition abstract has_attr 'name', String, :lowerBound => 1 + contains_many_uni 'parameters', Parameter + contains_one_uni 'body', Expression end # A resource type definition (a 'define' in the DSL). # class ResourceTypeDefinition < NamedDefinition - # FUTURE - # contains_one_uni 'producer', Producer 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 < Expression + class NodeDefinition < Definition contains_one_uni 'parent', Expression contains_many_uni 'host_matches', Expression, :lowerBound => 1 contains_one_uni 'body', Expression end # A class definition # class HostClassDefinition < NamedDefinition has_attr 'parent_class', String end # i.e {|parameters| body } - class LambdaExpression < Definition; end + 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 - has_attr 'value', Object, :lowerBound => 1 end # A Regular Expression Literal. # - class LiteralRegularExpression < LiteralValue; end + 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; end + class LiteralString < LiteralValue + has_attr 'value', String, :lowerBound => 1 + end - # A literal text is like a literal string, but has other rules for escaped characters. It - # is used as part of a ConcatenatedString - # - class LiteralText < LiteralValue; 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 LiteralNumber < LiteralValue + 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; end + 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 it 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; end + 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; end + 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 - # A type reference is a reference to a type. - # - class TypeReference < Expression - contains_one_uni 'type_name', QualifiedReference, :lowerBound => 1 - end - - # An instance reference is a reference to one or many named instances of a particular type - # - class InstanceReferences < TypeReference - contains_many_uni 'names', Expression, :lowerBound => 1 - end - # A resource body describes one resource instance # class ResourceBody < PopsObject 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 < PopsObject 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 - # Create Invariant. Future suggested enhancement Puppet Types. - # - class CreateInvariantExpression < Expression - has_attr 'name', String - contains_one_uni 'message_expr', Expression, :lowerBound => 1 - contains_one_uni 'constraint_expr', Expression, :lowerBound => 1 - end - - # Create Attribute. Future suggested enhancement Puppet Types. - # - class CreateAttributeExpression < Expression - has_attr 'name', String, :lowerBound => 1 - - # Should evaluate to name of datatype (String, Integer, Float, Boolean) or an EEnum metadata - # (created by CreateEnumExpression). If omitted, the type is a String. - # - contains_one_uni 'type', Expression - contains_one_uni 'min_expr', Expression - contains_one_uni 'max_expr', Expression - contains_one_uni 'default_value', Expression - contains_one_uni 'input_transformer', Expression - contains_one_uni 'derived_expr', Expression - end - - # Create Attribute. Future suggested enhancement Puppet Types. - # - class CreateEnumExpression < Expression - has_attr 'name', String - contains_one_uni 'values', Expression - end - - # Create Type. Future suggested enhancement Puppet Types. - # - class CreateTypeExpression < Expression - has_attr 'name', String, :lowerBound => 1 - has_attr 'super_name', String - contains_many_uni 'attributes', CreateAttributeExpression - contains_many_uni 'invariants', CreateInvariantExpression - end - - # Create ResourceType. Future suggested enhancement Puppet Types. - # @todo UNFINISHED - # - class CreateResourceType < CreateTypeExpression - # TODO CreateResourceType - # - has features required by the provider - provider invariant? - # - super type must be a ResourceType - end - # A named access expression looks up a named part. (e.g. $a.b) # class NamedAccessExpression < BinaryExpression; end - # A named function definition declares and defines a new function - # Future enhancement. - # - class NamedFunctionDefinition < NamedDefinition; end - - # Future enhancements - Injection - Unfinished - # - module Injection - # A producer expression produces an instance of a type. The instance is initialized - # from an expression (or from the current scope if this expression is missing). - #-- - # new. to handle production of injections - # - class Producer < Expression - contains_one_uni 'type_name', TypeReference, :lowerBound => 1 - contains_one_uni 'instantiation_expr', Expression - end - - # A binding entry binds one capability generically or named, specifies default bindings or - # composition of other bindings. - # - class BindingEntry < PopsObject - contains_one_uni 'key', Expression - contains_one_uni 'value', Expression - end - - # Defines an optionally named binding. - # - class Binding < Expression - contains_one_uni 'title_expr', Expression - contains_many_uni 'bindings', BindingEntry - end - - # An injection provides a value bound in the effective binding scope. The injection - # is based on a type (a capability) and an optional list of instance names (i.e. an InstanceReference). - # Invariants: optional and instantiation are mutually exclusive - # - class InjectExpression < Expression - has_attr 'optional', Boolean - contains_one_uni 'binding', Expression, :lowerBound => 1 - contains_one_uni 'instantiation', Expression - end + class Program < PopsObject + contains_one_uni 'body', Expression + has_many 'definitions', Definition end end diff --git a/lib/puppet/pops/model/model_label_provider.rb b/lib/puppet/pops/model/model_label_provider.rb index 469130de4..9744421e7 100644 --- a/lib/puppet/pops/model/model_label_provider.rb +++ b/lib/puppet/pops/model/model_label_provider.rb @@ -1,75 +1,96 @@ # 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) + if o.is_a?(Class) + o.name + else + @@label_visitor.visit(o) + end end def label_Factory o ; label(o.current) end - def label_Array o ; "Array Object" end - def label_LiteralNumber o ; "Literal Number" 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_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_ImportExpression o ; "'import' expression" end - def label_InstanceReferences o ; "Resource Reference" 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_LiteralText o ; "Text in Interpolated 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_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_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_String o ; "Ruby String" end - def label_Object o ; "Ruby Object" 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_QualifiedReference o ; "Type-Name" 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 end diff --git a/lib/puppet/pops/model/model_tree_dumper.rb b/lib/puppet/pops/model/model_tree_dumper.rb index 24d341cda..14ad52caf 100644 --- a/lib/puppet/pops/model/model_tree_dumper.rb +++ b/lib/puppet/pops/model/model_tree_dumper.rb @@ -1,352 +1,354 @@ # 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_LiteralNumber o + 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_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_ImportExpression o ["import"] + o.files.collect {|f| do_dump(f) } end - def dump_InstanceReferences o - ["instances", do_dump(o.type_name)] + o.names.collect {|n| do_dump(n) } - 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_LiteralText 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_HostClassDefinition o result = ["class", o.name] result << ["inherits", o.parent_class] if o.parent_class result << ["parameters"] + o.parameters.collect {|p| do_dump(p) } if o.parameters.size() > 0 if o.body result << do_dump(o.body) else result << [] end result end def dump_NodeDefinition o result = ["node"] result << ["matches"] + o.host_matches.collect {|m| do_dump(m) } result << ["parent", do_dump(o.parent)] if o.parent if o.body result << do_dump(o.body) else result << [] end result end def dump_ResourceTypeDefinition o result = ["define", o.name] result << ["parameters"] + o.parameters.collect {|p| do_dump(p) } if o.parameters.size() > 0 if o.body result << do_dump(o.body) else result << [] end result end def dump_ResourceOverrideExpression o result = ["override", do_dump(o.resources), :indent] o.operations.each do |p| result << :break << do_dump(p) end result << :dedent result end # Produces parameters as name, or (= name value) def dump_Parameter o if o.value ["=", o.name, do_dump(o.value)] else o.name end end def dump_ParenthesizedExpression o do_dump(o.expr) end + # Hides that Program exists in the output (only its body is shown), the definitions are just + # references to contained classes, resource types, and nodes + def dump_Program(o) + dump(o.body) + end + def dump_IfExpression o result = ["if", do_dump(o.test), :indent, :break, ["then", :indent, do_dump(o.then_expr), :dedent]] result += [:break, ["else", :indent, do_dump(o.else_expr), :dedent], :dedent] unless is_nop? o.else_expr result end def dump_UnlessExpression o result = ["unless", do_dump(o.test), :indent, :break, ["then", :indent, do_dump(o.then_expr), :dedent]] result += [:break, ["else", :indent, do_dump(o.else_expr), :dedent], :dedent] unless is_nop? o.else_expr result end # Produces (invoke name args...) when not required to produce an rvalue, and # (call name args ... ) otherwise. # def dump_CallNamedFunctionExpression o result = [o.rval_required ? "call" : "invoke", do_dump(o.functor_expr)] o.arguments.collect {|a| result << do_dump(a) } result end # def dump_CallNamedFunctionExpression o # result = [o.rval_required ? "call" : "invoke", do_dump(o.functor_expr)] # o.arguments.collect {|a| result << do_dump(a) } # result # end def dump_CallMethodExpression o result = [o.rval_required ? "call-method" : "invoke-method", do_dump(o.functor_expr)] o.arguments.collect {|a| result << do_dump(a) } result << do_dump(o.lambda) if o.lambda result end def dump_CaseExpression o result = ["case", do_dump(o.test), :indent] o.options.each do |s| result << :break << do_dump(s) end result << :dedent end def dump_CaseOption o result = ["when"] result << o.values.collect {|x| do_dump(x) } result << ["then", do_dump(o.then_expr) ] result end def dump_RelationshipExpression o [o.operator.to_s, do_dump(o.left_expr), do_dump(o.right_expr)] end def dump_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_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 b45cd2cc3..c283302b5 100644 --- a/lib/puppet/pops/parser/egrammar.ra +++ b/lib/puppet/pops/parser/egrammar.ra @@ -1,703 +1,713 @@ # 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 LESSEQUAL NOTEQUAL DOT COLON LLCOLLECT RRCOLLECT +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 LCOLLECT RCOLLECT CLASSREF +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 SELBRACE +token LAMBDA SELBRACE +token NUMBER token LOW prechigh left HIGH left SEMIC left PIPE left LPAREN left RPAREN - left AT + left AT ATAT left DOT left CALL - left LBRACK + 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 EQUALS + 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 left LOW preclow rule # Produces [Model::BlockExpression, Model::Expression, nil] depending on multiple statements, single statement or empty program - : statements { result = Factory.block_or_expression(*val[0]) } + : statements { 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 match_rvalue { result = val[0] =~ val[2] ; loc result, val[1] } - | expression NOMATCH match_rvalue { result = val[0].mne 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 # Aleways have the same value literal_expression : array | boolean | default | hash | regex - | text_or_name =LOW # resolves hash key ambiguity (racc W U require this?) + | 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] } # --- IMPORT # IMPORT is handled as a non parenthesized call and is transformed to an ImportExpression. # i.e. there is no special grammar for it - it is just a "call statement". #---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 "A resource default can not be virtual or exported" + error val[1], "A resource default can not be virtual or exported" when :override - error "A resource override can not be virtual or exported" + error val[1], "A resource override can not be virtual or exported" else - error "Expression is not valid as a resource, resource-default, or resource-override" + 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 - error "Defaults are not virtualizable" - when :defaults - error "Defaults are not virtualizable" - when :override - error "Defaults are not virtualizable" + when :resource, :class, :defaults, :override + error val[1], "Defaults are not virtualizable" else - error "Expression is not valid as a resource, resource-default, or resource-override" + 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 "A resource default can not specify a resource name" + error val[1], "A resource default can not specify a resource name" when :override - error "A resource override does not allow override of name of resource" + error val[1], "A resource override does not allow override of name of resource" else - error "Expression is not valid as a resource, resource-default, or resource-override" + error val[1], "Expression is not valid as a resource, resource-default, or resource-override" end loc result, val[0], val[4] } | expression LBRACE attribute_operations endcomma RBRACE { result = case Factory.resource_shape(val[0]) when :resource, :class # This catches deprecated syntax. - error "All resource specifications require names" + error val[1], "All resource specifications require names" when :defaults Factory.RESOURCE_DEFAULTS(val[0], val[2]) when :override # This was only done for override in original - TODO shuld it be here at all Factory.RESOURCE_OVERRIDE(val[0], val[2]) else - error "Expression is not valid as a resource, resource-default, or resource-override" + error val[0], "Expression is not valid as a resource, resource-default, or resource-override" end loc result, val[0], val[4] } | 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 statements RBRACE { - result = Factory.DEFINITION(classname(val[1][:value]), val[2], val[4]) + : 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] - @lexer.indefine = false - } - | DEFINE classname parameter_list LBRACE RBRACE { - result = Factory.DEFINITION(classname(val[1][:value]), val[2], nil) - loc result, val[0], val[4] - @lexer.indefine = false + # New lexer does not keep track of this, this is done in validation + if @lexer.respond_to?(:'indefine=') + @lexer.indefine = false + end } #---HOSTCLASS -# ORIGINAL COMMENT: Our class gets defined in the parent namespace, not our own. -# WAT ??! This is way odd; should get its complete name, classnames do not nest -# Seems like the call to classname makes use of the name scope -# (This is uneccesary, since the parent name is known when evaluating) # # Produces Model::HostClassDefinition # hostclass_expression - : CLASS classname parameter_list classparent LBRACE statements RBRACE { - @lexer.namepop - result = Factory.HOSTCLASS(classname(val[1][:value]), val[2], token_text(val[3]), val[5]) + : 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] } - | CLASS classname parameter_list classparent LBRACE RBRACE { - @lexer.namepop - result = Factory.HOSTCLASS(classname(val[1][:value]), val[2], token_text(val[3]), nil) - loc result, val[0], val[5] - } + + # 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 = Factory.NODE(val[1], val[2], val[4]) + result = add_definition(Factory.NODE(val[1], val[2], val[4])) loc result, val[0], val[5] } | NODE hostnames nodeparent LBRACE RBRACE { - result = Factory.NODE(val[1], val[2], nil) + 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 - : NAME { result = Factory.fqn(val[0][:value]); loc result, val[0] } - | quotedtext { result = val[0] } + : dotted_name { result = val[0] } + | quotedtext { result = val[0] } | DEFAULT { result = Factory.literal(:default); loc result, val[0] } | regex + dotted_name + : NAME { result = Factory.literal(val[0][:value]); loc result, val[0] } + | dotted_name DOT NAME { result = Factory.concat(val[0], '.', val[2][:value]); loc result, val[0], val[2] } + # Produces Expression, since hostname is an Expression nodeparent : nil | INHERITS hostname { result = val[1] } -#---NAMES AND PARAMTERS COMMON TO SEVERAL RULES -# String result +#---NAMES AND PARAMETERS COMMON TO SEVERAL RULES +# Produces String +# classname : NAME { result = val[0] } - | CLASS { 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 +## 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 - : text_or_name FARROW expression { result = Factory.KEY_ENTRY(val[0], val[2]); loc result, val[1] } + : expression FARROW expression { result = Factory.KEY_ENTRY(val[0], val[2]); loc result, val[1] } quotedtext : string | dq_string 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] } +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 6d71e7b7e..12f3f2c89 100644 --- a/lib/puppet/pops/parser/eparser.rb +++ b/lib/puppet/pops/parser/eparser.rb @@ -1,2256 +1,2379 @@ # # 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', 699) +module_eval(<<'...end egrammar.ra/module_eval...', 'egrammar.ra', 709) # Make emacs happy # Local Variables: # mode: ruby # End: ...end egrammar.ra/module_eval... ##### State transition tables begin ### clist = [ -'68,215,228,229,-126,230,202,229,238,87,88,84,79,90,290,94,262,89,-124', -'68,80,82,81,83,68,217,51,53,51,53,224,223,90,239,94,-192,89,90,93,94', -'199,89,86,85,-126,212,72,73,75,74,77,78,276,70,71,51,53,93,-124,68,69', -'202,93,117,-201,54,119,76,87,88,84,79,90,240,94,-192,89,70,71,80,82', -'81,83,68,69,59,229,59,51,53,292,212,117,109,312,119,90,93,94,112,89', -'86,85,111,-201,72,73,75,74,77,78,294,70,71,59,51,53,279,68,69,112,93', -'51,53,111,54,76,87,88,84,79,90,307,94,306,89,70,71,80,82,81,83,112,69', -'112,219,111,59,111,321,218,278,117,112,112,119,93,111,111,117,86,85', -'119,275,72,73,75,74,77,78,220,70,71,221,59,68,68,307,69,306,238,59,189', -'299,300,76,84,79,90,90,94,94,89,89,301,80,82,81,83,51,53,202,68,165', -'51,53,284,64,66,65,67,125,304,93,93,90,236,94,85,89,308,72,73,75,74', -'77,78,310,70,71,222,261,68,236,238,69,54,317,318,260,93,54,76,84,79', -'90,260,94,63,89,63,131,80,82,81,83,68,102,254,327,253,198,113,252,330', -'102,103,238,102,90,93,94,334,89,310,336,337,338,72,73,75,74,77,78,339', -'70,71,99,342,343,344,68,69,91,93,236,63,60,351,76,87,88,84,79,90,352', -'94,353,89,70,71,80,82,81,83,68,69,354,,,,,,,,,,79,90,93,94,,89,86,85', -'80,,72,73,75,74,77,78,,70,71,,,,,,69,,93,,,,,76,68,,72,73,75,74,77,78', -',70,71,,79,90,,94,69,89,,,80,,,76,68,,,,,,,,,,,,79,90,93,94,,89,,,80', -',72,73,75,74,77,78,,70,71,,,,,,69,,93,,,,,76,68,,72,73,75,74,77,78,', -'70,71,,79,90,,94,69,89,,,80,,,76,68,,,,,,,,,,,,,90,93,94,,89,,,,,72', -'73,75,74,77,78,,70,71,,,,,,69,,93,,,,,76,,,72,73,75,74,77,78,,70,71', -',,,,68,69,,,,,,,76,87,88,84,79,90,,94,,89,,,80,82,81,83,68,,,,,,,,,', -',,,90,93,94,,89,86,85,,,72,73,75,74,77,78,,70,71,,,,,,69,,93,,,,68,76', -',,72,73,75,74,77,78,,70,71,90,,94,,89,69,,,,,,68,76,,,,,,,,,,,,90,93', -'94,,89,,,,,72,73,75,74,,,,70,71,,,,,,69,,93,,,,,76,,,72,73,75,74,,,', -'70,71,,,,,68,69,,,,,,,76,87,88,84,79,90,234,94,,89,,,80,82,81,83,,,', -',,,,,,,,,,,93,,,,86,85,,,72,73,75,74,77,78,,70,71,,,,,68,69,,,,,,,76', -'87,88,84,79,90,,94,,89,,,80,82,81,83,,,,,,,,,,,,,,,93,,,,86,85,,,72', -'73,75,74,77,78,,70,71,,,,,68,69,,,,,,,76,87,88,84,79,90,,94,,89,,,80', -'82,81,83,68,,,,,,,,,,,,,90,93,94,,89,86,85,,,72,73,75,74,77,78,,70,71', -',,,,,69,,93,,,,,76,,,,,75,74,,,,70,71,,,,,68,69,,,,,,,76,87,88,84,79', -'90,,94,,89,,,80,82,81,83,68,,,,,,,,,,,,,90,93,94,,89,86,85,,,72,73,75', -'74,77,78,,70,71,,,,,,69,,93,,,,,76,,,,,75,74,,,,70,71,,,,,68,69,,,,', -',,76,87,88,84,79,90,,94,,89,,,80,82,81,83,,,,,,,,,,,,,,,93,,,,86,85', -',,72,73,75,74,77,78,,70,71,,,,,68,69,,,,,,,76,87,88,84,79,90,,94,,89', -',,80,82,81,83,,,,,,,,,,,,,,,93,,,,86,85,,,72,73,75,74,77,78,,70,71,', -',,,68,69,,,,,,,76,87,88,84,79,90,,94,,89,,,80,82,81,83,,,,,,,,,,,,,', -',93,,,,86,85,,,72,73,75,74,77,78,,70,71,,,,,68,69,,,,,,,76,87,88,84', -'79,90,,94,,89,,,80,82,81,83,,,,,,,,,,,,,,,93,,,,86,85,,,72,73,75,74', -'77,78,,70,71,,,,,68,69,,,,,,,76,87,88,84,79,90,,94,,89,,,80,82,81,83', -',,,,,,,,,,,,,,93,,,,86,85,,,72,73,75,74,77,78,,70,71,,,,,68,69,208,', -',,,,76,87,88,84,79,90,,94,,89,,,80,82,81,83,,,,,,,,,,,,,,,93,,,,86,85', -',,72,73,75,74,77,78,,70,71,,,,,68,69,207,,,,,,76,87,88,84,79,90,,94', -',89,,,80,82,81,83,,,,,,,,,,,,,,,93,,,,86,85,,,72,73,75,74,77,78,,70', -'71,,,,,68,69,206,,,,,,76,87,88,84,79,90,,94,,89,,,80,82,81,83,,,,,,', -',,,,,,,,93,,,,86,85,,,72,73,75,74,77,78,,70,71,,,,,68,69,205,,,,,,76', -'87,88,84,79,90,,94,,89,,,80,82,81,83,,,,,,,,,,,,,,,93,,,,86,85,,,72', -'73,75,74,77,78,,70,71,,,,,68,69,,,,,,,76,87,88,84,79,90,,94,,89,,194', -'80,82,81,83,,,,,,,,,,51,53,,,47,93,48,,,86,85,,,72,73,75,74,77,78,,70', -'71,13,,,,,69,38,,44,,46,96,76,45,58,54,,40,57,,,,55,12,51,53,56,,47', -'11,48,,,,,,,59,,,,,,39,,164,13,,,,,,167,184,178,185,46,179,187,180,176', -'174,,169,182,,,,55,12,188,183,181,51,53,11,,47,,48,324,,,59,,,,,186', -'168,,,,,,13,,,,,,38,,44,,46,42,,45,58,54,,40,57,43,,,55,12,51,53,56', -',47,11,48,313,,,,,,59,,,,,,39,,,13,,,,,,38,,44,,46,96,,45,58,54,,40', -'57,,,,55,12,51,53,56,,47,11,48,,,,,,,59,,,,,,39,,,13,,,,,,38,,44,,46', -'96,,45,58,54,,40,57,,,,55,12,51,53,56,,47,11,48,,,,,,,59,,,,,,39,,,13', -',,,,,38,,44,,46,96,,45,58,54,,40,57,,,,55,12,51,53,56,,47,11,48,,,,', -',,59,,,,,,39,,,13,,,,,,38,,44,,46,96,,45,58,54,,40,57,,,,55,12,51,53', -'56,,47,11,48,,,,,,,59,,,,,,39,,,13,,,,,,38,,44,,46,96,,45,58,54,,40', -'57,,,,55,12,51,53,56,,47,11,48,,,,,,,59,,,,,,39,,,13,,,,,,38,,44,,46', -'96,,45,58,54,,40,57,,,,55,12,51,53,56,,47,11,48,,,,,,,59,,,,,,39,,,13', -',,,,,38,,44,,46,96,,45,58,54,,40,57,,,,55,12,51,53,56,,47,11,48,,,,', -',,59,,,,,,39,,,13,,,,,,38,,44,,46,96,,45,58,54,,40,57,,,,55,12,51,53', -'56,,47,11,48,,,,,,,59,,,,,,39,,,13,,,,,,38,,44,,46,96,,45,58,54,,40', -'57,,,,55,12,51,53,56,,47,11,48,,,,,,,59,,,,,,39,,,13,,,,,,38,,44,,46', -'96,,45,58,54,,40,57,,,,55,12,51,53,56,,47,11,48,,,,,,,59,,,,,,39,,,13', -',,,,,38,,44,,46,96,,45,58,54,,40,57,,,,55,12,51,53,56,,47,11,48,,,,', -',,59,,,,,,39,,,13,,,,,,38,,44,,46,96,,45,58,54,,40,57,,,,55,12,51,53', -'56,,47,11,48,,,,,,,59,,,,,,39,,,13,,,,,,38,,44,,46,96,,45,58,54,,40', -'57,,,,55,12,51,53,56,,47,11,48,,,,,,,59,,,,,,39,,,13,,,,,,38,,44,,46', -'96,,45,58,54,,40,57,,,,55,12,51,53,56,,47,11,48,,,,,,,59,,,,,,39,,,13', -',,,,,38,,44,,46,96,,45,58,54,,40,57,,,,55,12,51,53,56,,47,11,48,,,,', -',,59,,,,,,39,,,13,,,,,,38,,44,,46,96,,45,58,54,,40,57,,,,55,12,51,53', -'56,,47,11,48,,,,,,,59,,,,,,39,,,13,,,,,,38,,44,,46,96,,45,58,54,,40', -'57,,,,55,12,51,53,56,,47,11,48,,,,,,,59,,,,,,39,,,13,,,,,,38,,44,,46', -'96,,45,58,54,,40,57,,,,55,12,51,53,56,,47,11,48,,,,,,,59,,,,,,39,,,13', -',,,,,38,,44,,46,96,,45,58,54,,40,57,,,,55,12,51,53,56,,47,11,48,,,,', -',,59,,,,,,39,,,13,,,,,,38,,44,,46,96,,45,58,54,,40,57,,,,55,12,51,53', -'56,,47,11,48,,,,,,,59,,,,,,39,,,13,,,,,,167,184,178,185,46,179,187,180', -'176,174,,169,182,,,,55,12,188,183,181,51,53,11,,47,,48,,,,59,,,,,186', -'168,,,,,,13,,,,,,38,,44,,46,96,,45,58,54,,40,57,,,,55,12,51,53,56,,47', -'11,48,,,,,,,59,,,,,,39,,,13,,,,,,38,,44,,46,96,,45,58,54,,40,57,,,,55', -'12,51,53,56,,47,11,48,,,,,,,59,,,,,,39,,,13,,,,,,38,,44,,46,96,,45,58', -'54,,40,57,,,,55,12,51,53,56,,47,11,48,,,,,,,59,,,,,,39,,,13,,,,,,38', -',44,,46,96,,45,58,54,,40,57,,,,55,12,51,53,56,,47,11,48,,,,,,,59,,,', -',,39,,,13,,,,,,38,,44,,46,96,,45,58,54,,40,57,,,,55,12,51,53,56,,47', -'11,48,,,,,,,59,,,,,,39,,,13,,,,,,38,,44,,46,96,,45,58,54,,40,57,,,,55', -'12,51,53,56,,47,11,48,,,,,,,59,,,,,,39,,,13,,,,,,38,,44,,46,42,,45,58', -'54,,40,57,43,,,55,12,51,53,56,,47,11,48,,,,,,,59,,,,,,39,,,13,196,,', -',,38,,44,,46,96,,45,58,54,,40,57,,,,55,12,51,53,56,,47,11,48,,,,,,,59', -',,,,,39,,,13,,,,,,38,,44,,46,96,,45,58,54,,40,57,,,,55,12,51,53,56,', -'47,11,48,,,,,,,59,,,,,,39,,,13,,,,,,38,,44,,46,96,,45,58,54,,40,57,', -',,55,12,51,53,56,,47,11,48,,,,,,,59,,,,,,39,,,13,204,,,,,38,,44,,46', -'96,,45,58,54,,40,57,,,,55,12,51,53,56,,47,11,48,,,,,,,59,,,,,,39,,,13', -',,,,,38,,44,,46,42,,45,58,54,,40,57,43,,,55,12,51,53,56,,47,11,48,,', -',,,,59,,,,,,39,,,13,,,,,,38,,44,,46,42,,45,58,54,,40,57,43,,,55,12,51', -'53,56,,47,11,48,,,,,,,59,,,,,,39,,,13,,,,,,38,,44,,46,42,,45,58,54,', -'40,57,43,,,55,12,51,53,56,,47,11,48,,,,,,,59,,,,,,39,,,13,,,,,,38,,44', -',46,42,,45,58,54,,40,57,43,,,55,12,51,53,56,,47,11,48,,,,,,,59,,,,,', -'39,,,13,,,,,,38,,44,,46,96,,45,58,54,,40,57,,,,55,12,51,53,56,,47,11', -'48,,,,,,,59,,,,,,39,,,13,,,,,,38,,44,,46,96,,45,58,54,,40,57,,,,55,12', -'51,53,56,,47,11,48,,,,,,,59,,,,,,39,,,13,,,,,,38,,44,,46,96,,45,58,54', -',40,57,,,,55,12,,,56,51,53,11,,47,283,48,,,,59,,,,,,39,,,,,,13,,,,,', -'38,,44,,46,96,,45,58,54,,40,57,,,,55,12,51,53,56,,47,11,48,326,,,,,', -'59,,,,,,39,,,13,,,,,,38,,44,,46,42,,45,58,54,,40,57,43,,,55,12,51,53', -'56,,47,11,48,,,,,,,59,,,,,,39,,,13,,,,,,38,,44,,46,96,,45,58,54,,40', -'57,,,,55,12,51,53,56,,47,11,48,,,,,,,59,,,,,,39,,,13,,,,,,38,,44,,46', -'96,,45,58,54,,40,57,,,,55,12,51,53,56,,47,11,48,266,,,,,,59,,,,,,39', -',,13,,,,,,38,,44,,46,42,,45,58,54,,40,57,43,,,55,12,51,53,56,,47,11', -'48,,,,,,,59,,,,,,39,,,13,,,,,,38,,44,,46,42,,45,58,54,,40,57,43,,,55', -'12,51,53,56,,47,11,48,,,,,,,59,,,,,,39,,,13,,,,,,38,,44,,46,96,,45,58', -'54,,40,57,,,,55,12,51,53,56,,47,11,48,258,,,,,,59,,,,,,39,,,13,,,,,', -'38,,44,,46,42,,45,58,54,,40,57,43,,,55,12,51,53,56,,47,11,48,,,,,,,59', -',,,,,39,,,13,,,,,,38,,44,,46,96,,45,58,54,,40,57,,,,55,12,51,53,56,', -'47,11,48,,,,,,,59,,,,,,39,,,13,,,,,,38,,44,,46,96,,45,58,54,,40,57,', -',,55,12,,,56,51,53,11,,47,123,48,,,,59,,,,,,39,,,,,,13,,,,,,38,,44,', -'46,96,,45,58,54,,40,57,,,,55,12,51,53,56,,47,11,48,,,,,,,59,,,,,,39', -',,13,,,,,,38,,44,,46,96,,45,58,54,,40,57,,,,55,12,51,53,56,,47,11,48', -',,,,,,59,,,,,,39,,,13,,,,,,38,,44,,46,96,,45,58,54,,40,57,,,,55,12,51', -'53,56,,47,11,48,,,,,,,59,,,,,,39,,,13,,,,,,38,,44,,46,96,,45,58,54,', -'40,57,,,,55,12,51,53,56,,47,11,48,,,,,,,59,,,,,,39,,,13,,,,,,38,,44', -',46,96,,45,58,54,,40,57,,,,55,12,51,53,56,,47,11,48,,,,,,,59,,,,,,39', -',,13,,,,,,38,,44,,46,96,,45,58,54,,40,57,,,,55,12,51,53,56,,47,11,48', -',,,,,,59,,,,,,39,,,13,,,,,,38,,44,,46,96,,45,58,54,,40,57,,,,55,12,51', -'53,56,,47,11,48,,,,,,,59,,,,,,39,,,13,,,,,,38,,44,,46,96,,45,58,54,', -'40,57,,,,55,12,51,53,56,,47,11,48,,,,,,,59,,,,,,39,,,13,,,,,,38,,44', -',46,96,,45,58,54,,40,57,,,,55,12,51,53,56,,47,11,48,341,,,,,,59,,,,', -',39,,,13,,,,,,38,,44,,46,42,,45,58,54,,40,57,43,,,55,12,51,53,56,,47', -'11,48,,,,,,,59,,,,,,39,,,13,,,,,,38,,44,,46,96,,45,58,54,,40,57,,,,55', -'12,51,53,56,,47,11,48,,,,,,,59,,,,,,39,,,13,,,,,,38,,44,,46,96,,45,58', -'54,,40,57,,,,55,12,51,53,56,,47,11,48,,,,,,,59,,,,,,39,,,13,,,,,,38', -',44,,46,96,,45,58,54,,40,57,,,,55,12,51,53,56,,47,11,48,346,,,,,,59', -',,,,,39,,,13,,,,,,38,,44,,46,42,,45,58,54,,40,57,43,,,55,12,51,53,56', -',47,11,48,348,,,,,,59,,,,,,39,,,13,,,,,,38,,44,,46,42,,45,58,54,,40', -'57,43,,,55,12,51,53,56,,47,11,48,,,,,,,59,,,,,,39,,,13,,,,,,38,,44,', -'46,42,,45,58,54,61,40,57,43,,,55,12,51,53,56,,47,11,48,350,,,,,,59,', -',,,,39,,,13,,,,,,38,,44,,46,42,,45,58,54,,40,57,43,,,55,12,51,53,56', -',47,11,48,,,,,,,59,,,,,,39,,,13,,,,,,38,,44,,46,42,,45,58,54,,40,57', -'43,,,55,12,51,53,56,,47,11,48,264,,,,,,59,,,,,,39,,,13,,,,,,38,,44,', -'46,42,,45,58,54,,40,57,43,,,55,12,,,56,,,11,,,,248,184,247,185,59,245', -'187,249,243,242,39,244,246,,,,,,188,183,250,248,184,247,185,,245,187', -'249,243,242,,244,246,,,186,251,,188,183,250,248,184,247,185,,245,187', -'249,243,242,,244,246,,,186,251,,188,183,250,,,,,,,,,,,,,,,,186,251' ] - racc_action_table = arr = ::Array.new(4874, nil) +'54,56,-209,240,49,118,51,54,56,117,230,239,334,287,72,229,240,273,54', +'56,-127,-200,210,13,-129,210,235,234,95,39,99,46,94,48,43,250,47,62', +'58,272,41,61,44,45,-209,127,59,12,125,67,60,54,56,11,98,49,127,51,363', +'125,207,63,-127,-200,79,78,-129,40,63,74,75,57,240,228,13,50,73,251', +'227,63,39,325,46,114,48,43,80,47,62,58,303,41,61,44,45,247,118,59,12', +'223,117,60,54,56,11,320,49,319,51,54,56,118,63,118,305,117,72,117,40', +'118,54,56,57,117,225,13,50,320,249,319,95,39,99,46,94,48,43,307,47,62', +'58,65,41,61,44,45,226,127,59,12,125,293,60,54,56,11,98,49,127,51,361', +'125,232,63,118,233,79,78,117,40,63,74,75,57,220,249,13,50,73,220,312', +'63,39,313,46,314,48,43,80,47,62,58,210,41,61,44,45,263,317,59,12,290', +'321,60,54,56,11,323,49,289,51,359,206,247,63,249,330,331,72,286,40,271', +'247,197,57,173,67,13,50,140,341,264,95,39,99,46,94,48,43,249,47,62,58', +'119,41,61,44,45,265,344,59,12,107,108,60,54,56,11,98,49,107,51,269,107', +'348,63,76,77,79,78,323,40,350,74,75,57,351,352,13,50,73,353,104,355', +'39,356,46,357,48,43,80,47,62,58,271,41,61,44,45,67,64,59,12,364,365', +'60,54,56,11,72,49,366,51,367,,,63,,,,72,,40,95,,99,57,94,,13,50,,,,95', +'39,99,46,94,48,101,,47,62,58,,41,61,,98,,,59,12,,,60,54,56,11,98,49', +',51,,,,63,76,77,79,78,,40,,74,75,57,,,13,50,73,,,,39,,46,,48,101,80', +'47,62,58,,41,61,68,70,69,71,59,12,,,60,54,56,11,72,49,,51,,,,63,,,,72', +',40,95,,99,57,94,,13,50,,,,95,39,99,46,94,48,101,,47,62,58,,41,61,,98', +',,59,12,,,60,54,56,11,98,49,,51,74,75,,63,,,,73,,40,,,,57,,,13,50,73', +',,,39,,46,,48,43,,47,62,58,,41,61,44,45,,,59,12,,,60,54,56,11,72,49', +',51,,,,63,,,,72,,40,95,,99,57,94,,13,50,,,,95,39,99,46,94,48,101,,47', +'62,58,,41,61,,98,,,59,12,,,60,54,56,11,98,49,,51,277,,,63,,,,72,,40', +',74,75,57,,,13,50,73,,,95,39,99,46,94,48,43,,47,62,58,,41,61,44,45,', +',59,12,,,60,54,56,11,98,49,,51,,,,63,,,,72,,40,,74,75,57,,,13,50,73', +',,95,39,99,46,94,48,101,,47,62,58,,41,61,,,,,59,12,,,60,54,56,11,98', +'49,,51,,,,63,,,,72,,40,,,,57,,,13,50,73,,,95,39,99,46,94,48,101,,47', +'62,58,,41,61,,,,,59,12,,,60,54,56,11,98,49,,51,,,,63,,,,,,40,,,,57,', +',13,50,,,,,39,,46,,48,101,,47,62,58,,41,61,,,,,59,12,,,60,54,56,11,', +'49,,51,,,,63,,,,,,40,,,,57,,,13,50,,,,,39,,46,,48,101,,47,62,58,,41', +'61,,,,,59,12,,,60,54,56,11,,49,,51,,,,63,,,,,,40,,,,57,,,13,50,,,,,39', +',46,,48,101,,47,62,58,,41,61,,,,,59,12,,,60,54,56,11,,49,,51,,,,63,', +',,,,40,,,,57,,,13,50,,,,,175,192,186,193,48,187,195,188,184,182,,177', +'190,,,,,59,12,196,191,189,54,56,11,,49,,51,,,,63,,,,,194,176,,,,57,', +',13,50,,,,,39,,46,,48,101,,47,62,58,,41,61,,,,,59,12,,,60,54,56,11,', +'49,295,51,,,,63,,,,,,40,,,,57,,,13,50,,,,,39,,46,,48,101,,47,62,58,', +'41,61,,,,,59,12,,,60,54,56,11,,49,130,51,,,,63,,,,,,40,,,,57,,,13,50', +',,,,39,,46,,48,101,,47,62,58,,41,61,,,,,59,12,,,60,54,56,11,,49,132', +'51,,,,63,,,,,,40,,,,57,,,13,50,,,,,39,,46,,48,101,,47,62,58,,41,61,', +',,,59,12,,,60,54,56,11,,49,,51,135,,,63,,,,,,40,,,,57,,,13,50,,,,,39', +',46,,48,101,,47,62,58,,41,61,,,,,59,12,,,60,54,56,11,,49,,51,,,,63,', +',,,,40,,,,57,,,13,50,,,,,39,,46,,48,101,,47,62,58,,41,61,,,,,59,12,', +',60,54,56,11,,49,296,51,,,,63,,,,,,40,,,,57,,,13,50,,,,,39,,46,,48,101', +',47,62,58,,41,61,,,,,59,12,,,60,54,56,11,,49,,51,,,,63,,,,,,40,,,,57', +',,13,50,,,,,39,,46,,48,43,,47,62,58,,41,61,44,45,,,59,12,,,60,54,56', +'11,,49,,51,,,,63,,,,,,40,,,,57,,,13,50,,,,,39,,46,,48,101,,47,62,58', +',41,61,,,,,59,12,,,60,54,56,11,,49,,51,,,,63,,,,,,40,,,,57,,,13,50,', +',,,39,,46,,48,43,,47,62,58,,41,61,44,45,,,59,12,,,60,54,56,11,,49,,51', +',,,63,,,,,,40,,,,57,,,13,50,,,,,39,,46,,48,43,,47,62,58,,41,61,44,45', +',,59,12,,,60,54,56,11,,49,,51,,,,63,,,,,,40,,,,57,,,13,50,,,,,39,,46', +',48,43,,47,62,58,,41,61,44,45,,,59,12,,,60,54,56,11,,49,,51,,,,63,,', +',,,40,,,,57,,,13,50,,,,,39,,46,,48,43,,47,62,58,,41,61,44,45,,,59,12', +',,60,54,56,11,,49,,51,,,,63,,,,,,40,,,,57,,,13,50,,,,,39,,46,,48,43', +',47,62,58,,41,61,44,45,,,59,12,,,60,54,56,11,,49,,51,,,,63,,,,,,40,', +',,57,,,13,50,,,,,39,,46,,48,101,,47,62,58,,41,61,,,,,59,12,,,60,54,56', +'11,,49,,51,,,,63,,,,,,40,,,,57,,,13,50,,,,,39,,46,,48,101,,47,62,58', +',41,61,,,,,59,12,,,60,54,56,11,,49,,51,,,,63,,,,,,40,,,,57,,,13,50,', +',,,39,,46,,48,101,,47,62,58,,41,61,,,,,59,12,,,60,54,56,11,,49,,51,', +',,63,,,,,,40,,,,57,,,13,50,,,,,39,,46,,48,101,,47,62,58,,41,61,,,,,59', +'12,,,60,54,56,11,,49,,51,,,,63,,,,,,40,,,,57,,,13,50,,,,,39,,46,,48', +'101,,47,62,58,,41,61,,,,,59,12,,,60,54,56,11,,49,,51,,,,63,,,,,,40,', +',,57,,,13,50,,,,,39,,46,,48,101,,47,62,58,,41,61,,,,,59,12,,,60,54,56', +'11,,49,,51,,,,63,,,,,,40,,,,57,,,13,50,,,,,39,,46,,48,101,,47,62,58', +',41,61,,,,,59,12,,,60,54,56,11,,49,,51,,,,63,,,,,,40,,,,57,,,13,50,', +',,,39,,46,,48,101,,47,62,58,,41,61,,,,,59,12,,,60,54,56,11,,49,,51,', +',,63,,,,,,40,,,,57,,,13,50,,,,,39,,46,,48,101,,47,62,58,,41,61,,,,,59', +'12,,,60,54,56,11,,49,,51,,,,63,,,,,,40,,,,57,,,13,50,,,,,39,,46,,48', +'101,,47,62,58,,41,61,,,,,59,12,,,60,54,56,11,,49,,51,,,,63,,,,,,40,', +',,57,,,13,50,,,,,39,,46,,48,101,,47,62,58,,41,61,,,,,59,12,,,60,54,56', +'11,,49,,51,,,,63,,,,,,40,,,,57,,,13,50,,,,,39,,46,,48,101,,47,62,58', +',41,61,,,,,59,12,,,60,54,56,11,,49,,51,,,,63,,,,,,40,,,,57,,,13,50,', +',,,39,,46,,48,101,,47,62,58,,41,61,,,,,59,12,,,60,54,56,11,,49,,51,', +',,63,,,,,,40,,,,57,,,13,50,,,,,39,,46,,48,101,,47,62,58,,41,61,,,,,59', +'12,,,60,54,56,11,,49,,51,,,,63,,,,,,40,,,,57,,,13,50,,,,,39,,46,,48', +'101,,47,62,58,,41,61,,,,,59,12,,,60,54,56,11,,49,,51,,,,63,,,,,,40,', +',,57,,,13,50,,,,,39,,46,,48,101,,47,62,58,,41,61,,,,,59,12,,,60,54,56', +'11,,49,,51,,,,63,,,,,,40,,,,57,,,13,50,,,,,39,,46,,48,101,,47,62,58', +',41,61,,,,,59,12,,,60,54,56,11,,49,,51,,,,63,,,,,,40,,,,57,,,13,50,', +',,,39,,46,,48,101,,47,62,58,,41,61,,,,,59,12,,,60,54,56,11,,49,,51,', +',,63,,,,,,40,,,,57,,,13,50,,,,,39,,46,,48,101,,47,62,58,,41,61,,,,,59', +'12,,,60,54,56,11,,49,,51,,,,63,,,,,,40,,,,57,,,13,50,,,,,39,,46,,48', +'101,,47,62,58,,41,61,,,,,59,12,,,60,54,56,11,,49,,51,,,,63,,,,,,40,', +',,57,,,13,50,,,,,39,,46,,48,101,,47,62,58,,41,61,,,,,59,12,,,60,54,56', +'11,,49,,51,,,,63,,,,,,40,,,,57,,,13,50,,,,,39,,46,,48,101,,47,62,58', +',41,61,,,,,59,12,,,60,54,56,11,,49,,51,,,,63,,,,,,40,,,,57,,,13,50,', +',,,39,,46,,48,101,,47,62,58,,41,61,,,,,59,12,,,60,54,56,11,,49,,51,', +',,63,,,,,,40,,,172,57,,,13,50,,,,,39,,46,,48,101,,47,62,58,,41,61,,', +',,59,12,,,60,54,56,11,,49,,51,,,,63,,,,,,40,,,,57,,,13,50,,,,,175,192', +'186,193,48,187,195,188,184,182,,177,190,,,,,59,12,196,191,189,54,56', +'11,,49,,51,340,,,63,,,,,194,176,,,,57,,,13,50,,,,,39,,46,,48,43,,47', +'62,58,,41,61,44,45,,,59,12,,,60,54,56,11,,49,,51,,,,63,,,,,,40,,,,57', +',,13,50,,,,,39,,46,,48,101,,47,62,58,,41,61,,,,,59,12,,,60,54,56,11', +',49,,51,,,,63,,,,,,40,,,,57,,,13,50,,,,,39,,46,,48,101,,47,62,58,,41', +'61,,,,,59,12,,,60,54,56,11,,49,,51,,,,63,,,,,,40,,,,57,,,13,50,,,,,39', +',46,,48,101,,47,62,58,,41,61,,,,,59,12,,,60,54,56,11,,49,,51,,,,63,', +',,,,40,,,,57,,,13,50,,,,,39,,46,,48,43,,47,62,58,,41,61,44,45,,,59,12', +',,60,54,56,11,,49,,51,,,,63,,,,,,40,,,,57,,,13,50,,,,,39,,46,,48,101', +',47,62,58,,41,61,,,,,59,12,,,60,54,56,11,,49,,51,,,,63,,,,,,40,,,,57', +',,13,50,,,,,39,,46,,48,101,,47,62,58,,41,61,,,,,59,12,,,60,,,11,54,56', +',,49,,51,63,,,,,,40,,,,57,,,,50,,13,204,,,,,39,,46,,48,101,,47,62,58', +',41,61,,,,,59,12,,,60,54,56,11,,49,,51,326,,,63,,,,,,40,,,,57,,,13,50', +',,,,39,,46,,48,101,,47,62,58,,41,61,,,,,59,12,,,60,54,56,11,,49,,51', +',,,63,,,,,,40,,,,57,,,13,50,,,,,39,,46,,48,101,,47,62,58,,41,61,,,,', +'59,12,,,60,,,11,54,56,,,49,,51,63,,,,,,40,,,,57,,,,50,,13,212,,,,,39', +',46,,48,101,,47,62,58,,41,61,,,,,59,12,,,60,54,56,11,,49,,51,298,,,63', +',,,,,40,,,,57,,,13,50,,,,,39,,46,,48,101,,47,62,58,,41,61,,,,,59,12', +',,60,54,56,11,,49,,51,,,,63,,,,,,40,,,,57,,,13,50,,,,,39,,46,,48,101', +',47,62,58,,41,61,,,,,59,12,,,60,54,56,11,,49,,51,,,,63,,,,,,40,,,,57', +',,13,50,,,,,39,,46,,48,101,,47,62,58,,41,61,,,,,59,12,,,60,54,56,11', +',49,,51,,,,63,,,,,,40,,,,57,,,13,50,,,,,39,,46,,48,101,,47,62,58,,41', +'61,,,,,59,12,,,60,54,56,11,,49,,51,,,,63,,,,,,40,,,,57,,,13,50,,,,,39', +',46,,48,101,,47,62,58,,41,61,,,,,59,12,,,60,54,56,11,,49,,51,,,,63,', +',,,,40,,,,57,,,13,50,,,,,39,,46,,48,101,,47,62,58,,41,61,,,,,59,12,', +',60,54,56,11,,49,,51,,,,63,,,,,,40,,,,57,,,13,50,,,,,39,,46,,48,101', +',47,62,58,,41,61,,,,,59,12,,,60,54,56,11,,49,,51,,,,63,,,,,,40,,,,57', +',,13,50,,,,,39,,46,,48,101,,47,62,58,,41,61,,,,,59,12,,,60,54,56,11', +',49,,51,275,,,63,,,,,,40,,,,57,,,13,50,,,,,39,,46,,48,43,,47,62,58,', +'41,61,44,45,,,59,12,,,60,54,56,11,,49,,51,,,,63,,,,,,40,,,,57,,,13,50', +',,,,39,,46,,48,101,,47,62,58,,41,61,,,,,59,12,,,60,,,11,,,,,,,,63,,', +',,,40,72,,,57,,,,50,,91,92,93,88,83,95,,99,,94,,,84,86,85,87,,,,,,,', +',,,,,,,,98,,,,90,89,,,76,77,79,78,81,82,,74,75,72,,96,,,73,,,,91,92', +'93,88,83,95,80,99,,94,,,84,86,85,87,,,,,,,,,,,,,,,,98,,,,90,89,,,76', +'77,79,78,81,82,,74,75,,,,,,73,,72,,,,,231,,,80,91,92,93,88,83,95,,99', +',94,,,84,86,85,87,,,,,,,,,,,,,,,,98,,,,90,89,,,76,77,79,78,81,82,,74', +'75,72,,216,,,73,,,,91,92,93,88,83,95,80,99,,94,,,84,86,85,87,,,,,,,', +',,,,,,,,98,,,,90,89,,,76,77,79,78,81,82,,74,75,72,,,,,73,,,,91,92,93', +'88,83,95,80,99,,94,,,84,86,85,87,,,,,,,,,,,,,,,,98,,,,90,89,,,76,77', +'79,78,81,82,,74,75,72,,215,,,73,,,,91,92,93,88,83,95,80,99,,94,,,84', +'86,85,87,,,,,,,,,,,,,,,,98,,,,90,89,,,76,77,79,78,81,82,,74,75,72,,214', +',,73,,,,91,92,93,88,83,95,80,99,,94,,,84,86,85,87,,,,,,,,,,,,,,,,98', +',,,90,89,,,76,77,79,78,81,82,,74,75,72,,213,,,73,,,,91,92,93,88,83,95', +'80,99,,94,,,84,86,85,87,,,,,,,,,,,,,,,,98,,,,90,89,,,76,77,79,78,81', +'82,,74,75,72,,,,,73,,,,91,92,93,88,83,95,80,99,,94,,,84,86,85,87,,,', +',,,,,,,,,,,,98,,,,90,89,,,76,77,79,78,81,82,,74,75,72,,,,,73,,,,91,92', +'93,88,83,95,80,99,,94,,,84,86,85,87,,,,,,,,,,,,,,,,98,,,,90,89,,,76', +'77,79,78,81,82,,74,75,72,,,,,73,,,,91,92,93,88,83,95,80,99,,94,,,84', +'86,85,87,,,,,,,,,,,,,,,,98,,,,90,89,,,76,77,79,78,81,82,,74,75,72,,', +',,73,,,,91,92,93,88,83,95,80,99,,94,,,84,86,85,87,,,,,,,,,,,,,,,,98', +',,,90,89,,,76,77,79,78,81,82,,74,75,72,,,,,73,,,,91,92,93,88,83,95,80', +'99,,94,,,84,86,85,87,,,,,,,,,,,,,,,,98,,,,90,89,,,76,77,79,78,81,82', +',74,75,72,,,,,73,,,,91,92,93,88,83,95,80,99,,94,,,84,86,85,87,72,,,', +',,,,,,,,,,95,98,99,,94,90,89,,,76,77,79,78,81,82,,74,75,,,,,,73,,,98', +',,72,,,,80,76,77,79,78,81,82,,74,75,95,,99,,94,73,,,,,,72,,,,80,,,,', +',,,,83,95,98,99,,94,,,84,,76,77,79,78,81,82,,74,75,,,,,,73,,,98,,,,', +'72,,80,76,77,79,78,81,82,,74,75,,83,95,,99,73,94,,,84,,,,72,,80,,,,', +',,,,,,83,95,98,99,,94,,,84,,76,77,79,78,81,82,,74,75,,,,,,73,,,98,,', +',,72,,80,76,77,79,78,81,82,,74,75,,83,95,,99,73,94,,,84,,,,,,80,,,,', +',,,,,,,,98,,,,,72,,,76,77,79,78,81,82,,74,75,88,83,95,,99,73,94,,,84', +'86,85,87,,,80,,,,,,,,,,,,,98,,,,,72,,,76,77,79,78,81,82,,74,75,88,83', +'95,,99,73,94,,,84,86,85,87,,,80,,,,,,,,,,,,,98,,,,,89,,,76,77,79,78', +'81,82,,74,75,72,,,,,73,,,,91,92,93,88,83,95,80,99,,94,,,84,86,85,87', +',,,,,,,,,,,,,,,98,,,,90,89,,,76,77,79,78,81,82,,74,75,72,,,,,73,,,,91', +'92,93,88,83,95,80,99,,94,,,84,86,85,87,,,,,,,,,,,,,,,,98,,,,90,89,,', +'76,77,79,78,81,82,,74,75,72,,,,,73,,,,91,92,93,88,83,95,80,99,,94,,', +'84,86,85,87,,,,,,,,,,,,,,,,98,,,,90,89,,,76,77,79,78,81,82,,74,75,,', +',,,73,,72,,,,,241,,,80,91,92,93,88,83,95,,99,,94,,,84,86,85,87,,,,,', +',,,,,,,,,,98,,,,90,89,,,76,77,79,78,81,82,,74,75,72,,,,,73,,,,91,92', +'93,88,83,95,80,99,,94,,202,84,86,85,87,,,,,,,,,,,,,,,,98,,,,90,89,,', +'76,77,79,78,81,82,,74,75,,,,,,73,,72,,,,,,,,80,91,92,93,88,83,95,245', +'99,,94,,,84,86,85,87,,,,,,,,,,,,,,,,98,,,,90,89,,,76,77,79,78,81,82', +',74,75,72,,,,,73,,,,91,92,93,88,83,95,80,99,,94,,,84,86,85,87,,,,,,', +',,,,,,,,,98,,,,90,89,,,76,77,79,78,81,82,,74,75,,259,192,258,193,73', +'256,195,260,254,253,,255,257,,80,,,,,196,191,261,259,192,258,193,,256', +'195,260,254,253,,255,257,,,194,262,,,196,191,261,259,192,258,193,,256', +'195,260,254,253,,255,257,,,194,262,,,196,191,261,,,,,,,,,,,,,,,,194', +'262' ] + racc_action_table = arr = ::Array.new(5601, 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 = [ -'161,115,138,203,176,161,102,138,291,161,161,161,161,161,231,161,203', -'161,174,139,161,161,161,161,148,115,215,215,71,71,130,130,139,177,139', -'182,139,148,161,148,102,148,161,161,176,110,161,161,161,161,161,161', -'212,161,161,217,217,139,174,160,161,212,148,215,181,71,215,161,160,160', -'160,160,160,177,160,182,160,148,148,160,160,160,160,147,148,215,267', -'71,70,70,235,114,217,42,267,217,147,160,147,96,147,160,160,96,181,160', -'160,160,160,160,160,237,160,160,217,180,180,214,159,160,42,147,45,45', -'42,70,160,159,159,159,159,159,264,159,264,159,147,147,159,159,159,159', -'275,147,179,122,275,70,179,275,122,213,180,44,178,180,159,44,178,45', -'159,159,45,210,159,159,159,159,159,159,124,159,159,124,180,158,97,304', -'159,304,241,45,92,255,257,159,158,158,158,97,158,97,158,97,259,158,158', -'158,158,221,221,260,95,90,48,48,221,7,7,7,7,48,263,158,97,95,209,95', -'158,95,265,158,158,158,158,158,158,266,158,158,127,202,157,270,271,158', -'221,272,273,200,95,48,158,157,157,157,277,157,132,157,62,60,157,157', -'157,157,146,196,195,289,193,101,43,191,298,299,37,171,36,146,157,146', -'307,146,308,310,311,315,157,157,157,157,157,157,316,157,157,35,322,323', -'325,10,157,10,146,170,5,1,340,157,10,10,10,10,10,345,10,347,10,146,146', -'10,10,10,10,156,146,349,,,,,,,,,,156,156,10,156,,156,10,10,156,,10,10', -'10,10,10,10,,10,10,,,,,,10,,156,,,,,10,155,,156,156,156,156,156,156', -',156,156,,155,155,,155,156,155,,,155,,,156,154,,,,,,,,,,,,154,154,155', -'154,,154,,,154,,155,155,155,155,155,155,,155,155,,,,,,155,,154,,,,,155', -'153,,154,154,154,154,154,154,,154,154,,153,153,,153,154,153,,,153,,', -'154,152,,,,,,,,,,,,,152,153,152,,152,,,,,153,153,153,153,153,153,,153', -'153,,,,,,153,,152,,,,,153,,,152,152,152,152,152,152,,152,152,,,,,303', -'152,,,,,,,152,303,303,303,303,303,,303,,303,,,303,303,303,303,151,,', -',,,,,,,,,,151,303,151,,151,303,303,,,303,303,303,303,303,303,,303,303', -',,,,,303,,151,,,,150,303,,,151,151,151,151,151,151,,151,151,150,,150', -',150,151,,,,,,149,151,,,,,,,,,,,,149,150,149,,149,,,,,150,150,150,150', -',,,150,150,,,,,,150,,149,,,,,150,,,149,149,149,149,,,,149,149,,,,,166', -'149,,,,,,,149,166,166,166,166,166,166,166,,166,,,166,166,166,166,,,', -',,,,,,,,,,,166,,,,166,166,,,166,166,166,166,166,166,,166,166,,,,,297', -'166,,,,,,,166,297,297,297,297,297,,297,,297,,,297,297,297,297,,,,,,', -',,,,,,,,297,,,,297,297,,,297,297,297,297,297,297,,297,297,,,,,296,297', -',,,,,,297,296,296,296,296,296,,296,,296,,,296,296,296,296,145,,,,,,', -',,,,,,145,296,145,,145,296,296,,,296,296,296,296,296,296,,296,296,,', -',,,296,,145,,,,,296,,,,,145,145,,,,145,145,,,,,288,145,,,,,,,145,288', -'288,288,288,288,,288,,288,,,288,288,288,288,144,,,,,,,,,,,,,144,288', -'144,,144,288,288,,,288,288,288,288,288,288,,288,288,,,,,,288,,144,,', -',,288,,,,,144,144,,,,144,144,,,,,286,144,,,,,,,144,286,286,286,286,286', -',286,,286,,,286,286,286,286,,,,,,,,,,,,,,,286,,,,286,286,,,286,286,286', -'286,286,286,,286,286,,,,,190,286,,,,,,,286,190,190,190,190,190,,190', -',190,,,190,190,190,190,,,,,,,,,,,,,,,190,,,,190,190,,,190,190,190,190', -'190,190,,190,190,,,,,282,190,,,,,,,190,282,282,282,282,282,,282,,282', -',,282,282,282,282,,,,,,,,,,,,,,,282,,,,282,282,,,282,282,282,282,282', -'282,,282,282,,,,,128,282,,,,,,,282,128,128,128,128,128,,128,,128,,,128', -'128,128,128,,,,,,,,,,,,,,,128,,,,128,128,,,128,128,128,128,128,128,', -'128,128,,,,,121,128,,,,,,,128,121,121,121,121,121,,121,,121,,,121,121', -'121,121,,,,,,,,,,,,,,,121,,,,121,121,,,121,121,121,121,121,121,,121', -'121,,,,,108,121,108,,,,,,121,108,108,108,108,108,,108,,108,,,108,108', -'108,108,,,,,,,,,,,,,,,108,,,,108,108,,,108,108,108,108,108,108,,108', -'108,,,,,107,108,107,,,,,,108,107,107,107,107,107,,107,,107,,,107,107', -'107,107,,,,,,,,,,,,,,,107,,,,107,107,,,107,107,107,107,107,107,,107', -'107,,,,,106,107,106,,,,,,107,106,106,106,106,106,,106,,106,,,106,106', -'106,106,,,,,,,,,,,,,,,106,,,,106,106,,,106,106,106,106,106,106,,106', -'106,,,,,104,106,104,,,,,,106,104,104,104,104,104,,104,,104,,,104,104', -'104,104,,,,,,,,,,,,,,,104,,,,104,104,,,104,104,104,104,104,104,,104', -'104,,,,,98,104,,,,,,,104,98,98,98,98,98,,98,,98,,98,98,98,98,98,,,,', -',,,,,89,89,,,89,98,89,,,98,98,,,98,98,98,98,98,98,,98,98,89,,,,,98,89', -',89,,89,89,98,89,89,89,,89,89,,,,89,89,208,208,89,,208,89,208,,,,,,', -'89,,,,,,89,,89,208,,,,,,208,208,208,208,208,208,208,208,208,208,,208', -'208,,,,208,208,208,208,208,278,278,208,,278,,278,278,,,208,,,,,208,208', -',,,,,278,,,,,,278,,278,,278,278,,278,278,278,,278,278,278,,,278,278', -'268,268,278,,268,278,268,268,,,,,,278,,,,,,278,,,268,,,,,,268,,268,', -'268,268,,268,268,268,,268,268,,,,268,268,72,72,268,,72,268,72,,,,,,', -'268,,,,,,268,,,72,,,,,,72,,72,,72,72,,72,72,72,,72,72,,,,72,72,73,73', -'72,,73,72,73,,,,,,,72,,,,,,72,,,73,,,,,,73,,73,,73,73,,73,73,73,,73', -'73,,,,73,73,74,74,73,,74,73,74,,,,,,,73,,,,,,73,,,74,,,,,,74,,74,,74', -'74,,74,74,74,,74,74,,,,74,74,75,75,74,,75,74,75,,,,,,,74,,,,,,74,,,75', -',,,,,75,,75,,75,75,,75,75,75,,75,75,,,,75,75,76,76,75,,76,75,76,,,,', -',,75,,,,,,75,,,76,,,,,,76,,76,,76,76,,76,76,76,,76,76,,,,76,76,77,77', -'76,,77,76,77,,,,,,,76,,,,,,76,,,77,,,,,,77,,77,,77,77,,77,77,77,,77', -'77,,,,77,77,78,78,77,,78,77,78,,,,,,,77,,,,,,77,,,78,,,,,,78,,78,,78', -'78,,78,78,78,,78,78,,,,78,78,79,79,78,,79,78,79,,,,,,,78,,,,,,78,,,79', -',,,,,79,,79,,79,79,,79,79,79,,79,79,,,,79,79,80,80,79,,80,79,80,,,,', -',,79,,,,,,79,,,80,,,,,,80,,80,,80,80,,80,80,80,,80,80,,,,80,80,81,81', -'80,,81,80,81,,,,,,,80,,,,,,80,,,81,,,,,,81,,81,,81,81,,81,81,81,,81', -'81,,,,81,81,82,82,81,,82,81,82,,,,,,,81,,,,,,81,,,82,,,,,,82,,82,,82', -'82,,82,82,82,,82,82,,,,82,82,83,83,82,,83,82,83,,,,,,,82,,,,,,82,,,83', -',,,,,83,,83,,83,83,,83,83,83,,83,83,,,,83,83,84,84,83,,84,83,84,,,,', -',,83,,,,,,83,,,84,,,,,,84,,84,,84,84,,84,84,84,,84,84,,,,84,84,85,85', -'84,,85,84,85,,,,,,,84,,,,,,84,,,85,,,,,,85,,85,,85,85,,85,85,85,,85', -'85,,,,85,85,86,86,85,,86,85,86,,,,,,,85,,,,,,85,,,86,,,,,,86,,86,,86', -'86,,86,86,86,,86,86,,,,86,86,87,87,86,,87,86,87,,,,,,,86,,,,,,86,,,87', -',,,,,87,,87,,87,87,,87,87,87,,87,87,,,,87,87,88,88,87,,88,87,88,,,,', -',,87,,,,,,87,,,88,,,,,,88,,88,,88,88,,88,88,88,,88,88,,,,88,88,68,68', -'88,,68,88,68,,,,,,,88,,,,,,88,,,68,,,,,,68,,68,,68,68,,68,68,68,,68', -'68,,,,68,68,261,261,68,,261,68,261,,,,,,,68,,,,,,68,,,261,,,,,,261,', -'261,,261,261,,261,261,261,,261,261,,,,261,261,91,91,261,,91,261,91,', -',,,,,261,,,,,,261,,,91,,,,,,91,91,91,91,91,91,91,91,91,91,,91,91,,,', -'91,91,91,91,91,254,254,91,,254,,254,,,,91,,,,,91,91,,,,,,254,,,,,,254', -',254,,254,254,,254,254,254,,254,254,,,,254,254,93,93,254,,93,254,93', -',,,,,,254,,,,,,254,,,93,,,,,,93,,93,,93,93,,93,93,93,,93,93,,,,93,93', -'94,94,93,,94,93,94,,,,,,,93,,,,,,93,,,94,,,,,,94,,94,,94,94,,94,94,94', -',94,94,,,,94,94,240,240,94,,240,94,240,,,,,,,94,,,,,,94,,,240,,,,,,240', -',240,,240,240,,240,240,240,,240,240,,,,240,240,239,239,240,,239,240', -'239,,,,,,,240,,,,,,240,,,239,,,,,,239,,239,,239,239,,239,239,239,,239', -'239,,,,239,239,236,236,239,,236,239,236,,,,,,,239,,,,,,239,,,236,,,', -',,236,,236,,236,236,,236,236,236,,236,236,,,,236,236,67,67,236,,67,236', -'67,,,,,,,236,,,,,,236,,,67,,,,,,67,,67,,67,67,,67,67,67,,67,67,67,,', -'67,67,99,99,67,,99,67,99,,,,,,,67,,,,,,67,,,99,99,,,,,99,,99,,99,99', -',99,99,99,,99,99,,,,99,99,230,230,99,,230,99,230,,,,,,,99,,,,,,99,,', -'230,,,,,,230,,230,,230,230,,230,230,230,,230,230,,,,230,230,229,229', -'230,,229,230,229,,,,,,,230,,,,,,230,,,229,,,,,,229,,229,,229,229,,229', -'229,229,,229,229,,,,229,229,103,103,229,,103,229,103,,,,,,,229,,,,,', -'229,,,103,103,,,,,103,,103,,103,103,,103,103,103,,103,103,,,,103,103', -'66,66,103,,66,103,66,,,,,,,103,,,,,,103,,,66,,,,,,66,,66,,66,66,,66', -'66,66,,66,66,66,,,66,66,65,65,66,,65,66,65,,,,,,,66,,,,,,66,,,65,,,', -',,65,,65,,65,65,,65,65,65,,65,65,65,,,65,65,64,64,65,,64,65,64,,,,,', -',65,,,,,,65,,,64,,,,,,64,,64,,64,64,,64,64,64,,64,64,64,,,64,64,63,63', -'64,,63,64,63,,,,,,,64,,,,,,64,,,63,,,,,,63,,63,,63,63,,63,63,63,,63', -'63,63,,,63,63,109,109,63,,109,63,109,,,,,,,63,,,,,,63,,,109,,,,,,109', -',109,,109,109,,109,109,109,,109,109,,,,109,109,227,227,109,,227,109', -'227,,,,,,,109,,,,,,109,,,227,,,,,,227,,227,,227,227,,227,227,227,,227', -'227,,,,227,227,222,222,227,,222,227,222,,,,,,,227,,,,,,227,,,222,,,', -',,222,,222,,222,222,,222,222,222,,222,222,,,,222,222,,,222,218,218,222', -',218,218,218,,,,222,,,,,,222,,,,,,218,,,,,,218,,218,,218,218,,218,218', -'218,,218,218,,,,218,218,279,279,218,,279,218,279,279,,,,,,218,,,,,,218', -',,279,,,,,,279,,279,,279,279,,279,279,279,,279,279,279,,,279,279,69', -'69,279,,69,279,69,,,,,,,279,,,,,,279,,,69,,,,,,69,,69,,69,69,,69,69', -'69,,69,69,,,,69,69,207,207,69,,207,69,207,,,,,,,69,,,,,,69,,,207,,,', -',,207,,207,,207,207,,207,207,207,,207,207,,,,207,207,206,206,207,,206', -'207,206,206,,,,,,207,,,,,,207,,,206,,,,,,206,,206,,206,206,,206,206', -'206,,206,206,206,,,206,206,61,61,206,,61,206,61,,,,,,,206,,,,,,206,', -',61,,,,,,61,,61,,61,61,,61,61,61,,61,61,61,,,61,61,164,164,61,,164,61', -'164,,,,,,,61,,,,,,61,,,164,,,,,,164,,164,,164,164,,164,164,164,,164', -'164,,,,164,164,198,198,164,,198,164,198,198,,,,,,164,,,,,,164,,,198', -',,,,,198,,198,,198,198,,198,198,198,,198,198,198,,,198,198,52,52,198', -',52,198,52,,,,,,,198,,,,,,198,,,52,,,,,,52,,52,,52,52,,52,52,52,,52', -'52,,,,52,52,169,169,52,,169,52,169,,,,,,,52,,,,,,52,,,169,,,,,,169,', -'169,,169,169,,169,169,169,,169,169,,,,169,169,,,169,47,47,169,,47,47', -'47,,,,169,,,,,,169,,,,,,47,,,,,,47,,47,,47,47,,47,47,47,,47,47,,,,47', -'47,290,290,47,,290,47,290,,,,,,,47,,,,,,47,,,290,,,,,,290,,290,,290', -'290,,290,290,290,,290,290,,,,290,290,168,168,290,,168,290,168,,,,,,', -'290,,,,,,290,,,168,,,,,,168,,168,,168,168,,168,168,168,,168,168,,,,168', -'168,167,167,168,,167,168,167,,,,,,,168,,,,,,168,,,167,,,,,,167,,167', -',167,167,,167,167,167,,167,167,,,,167,167,41,41,167,,41,167,41,,,,,', -',167,,,,,,167,,,41,,,,,,41,,41,,41,41,,41,41,41,,41,41,,,,41,41,40,40', -'41,,40,41,40,,,,,,,41,,,,,,41,,,40,,,,,,40,,40,,40,40,,40,40,40,,40', -'40,,,,40,40,39,39,40,,39,40,39,,,,,,,40,,,,,,40,,,39,,,,,,39,,39,,39', -'39,,39,39,39,,39,39,,,,39,39,38,38,39,,38,39,38,,,,,,,39,,,,,,39,,,38', -',,,,,38,,38,,38,38,,38,38,38,,38,38,,,,38,38,306,306,38,,306,38,306', -',,,,,,38,,,,,,38,,,306,,,,,,306,,306,,306,306,,306,306,306,,306,306', -',,,306,306,318,318,306,,318,306,318,318,,,,,,306,,,,,,306,,,318,,,,', -',318,,318,,318,318,,318,318,318,,318,318,318,,,318,318,13,13,318,,13', -'318,13,,,,,,,318,,,,,,318,,,13,,,,,,13,,13,,13,13,,13,13,13,,13,13,', -',,13,13,12,12,13,,12,13,12,,,,,,,13,,,,,,13,,,12,,,,,,12,,12,,12,12', -',12,12,12,,12,12,,,,12,12,11,11,12,,11,12,11,,,,,,,12,,,,,,12,,,11,', -',,,,11,,11,,11,11,,11,11,11,,11,11,,,,11,11,334,334,11,,334,11,334,334', -',,,,,11,,,,,,11,,,334,,,,,,334,,334,,334,334,,334,334,334,,334,334,334', -',,334,334,336,336,334,,336,334,336,336,,,,,,334,,,,,,334,,,336,,,,,', -'336,,336,,336,336,,336,336,336,,336,336,336,,,336,336,4,4,336,,4,336', -'4,,,,,,,336,,,,,,336,,,4,,,,,,4,,4,,4,4,,4,4,4,4,4,4,4,,,4,4,337,337', -'4,,337,4,337,337,,,,,,4,,,,,,4,,,337,,,,,,337,,337,,337,337,,337,337', -'337,,337,337,337,,,337,337,0,0,337,,0,337,0,,,,,,,337,,,,,,337,,,0,', -',,,,0,,0,,0,0,,0,0,0,,0,0,0,,,0,0,205,205,0,,205,0,205,205,,,,,,0,,', -',,,0,,,205,,,,,,205,,205,,205,205,,205,205,205,,205,205,205,,,205,205', -',,205,,,205,,,,233,233,233,233,205,233,233,233,233,233,205,233,233,', -',,,,233,233,233,189,189,189,189,,189,189,189,189,189,,189,189,,,233', -'233,,189,189,189,238,238,238,238,,238,238,238,238,238,,238,238,,,189', -'189,,238,238,238,,,,,,,,,,,,,,,,238,238' ] - racc_action_check = arr = ::Array.new(4874, nil) +'0,0,189,211,0,286,0,47,47,286,131,147,286,220,151,131,147,211,188,188', +'182,190,220,0,184,107,139,139,151,0,151,0,151,0,0,185,0,0,0,210,0,0', +'0,0,189,47,0,0,47,141,0,351,351,0,151,351,188,351,351,188,107,0,182', +'190,151,151,184,0,47,151,151,0,278,129,351,0,151,185,129,188,351,278', +'351,43,351,351,151,351,351,351,242,351,351,351,351,178,186,351,351,121', +'186,351,4,4,351,275,4,275,4,223,223,43,351,46,246,43,152,46,351,187', +'225,225,351,187,121,4,351,317,179,317,152,4,152,4,152,4,4,248,4,4,4', +'4,4,4,4,4,123,223,4,4,223,226,4,350,350,4,152,350,225,350,350,225,134', +'4,101,134,152,152,101,4,223,152,152,4,120,252,350,4,152,115,266,225', +'350,268,350,270,350,350,152,350,350,350,271,350,350,350,350,199,274', +'350,350,222,276,350,348,348,350,277,348,221,348,348,106,281,350,282', +'283,284,156,218,350,288,217,97,350,95,66,348,350,64,302,201,156,348', +'156,348,156,348,348,304,348,348,348,44,348,348,348,348,203,311,348,348', +'312,38,348,206,206,348,156,206,204,206,206,37,320,348,156,156,156,156', +'321,348,323,156,156,348,324,328,206,348,156,329,36,335,206,336,206,339', +'206,206,156,206,206,206,208,206,206,206,206,5,1,206,206,354,358,206', +'11,11,206,100,11,360,11,362,,,206,,,,157,,206,100,,100,206,100,,11,206', +',,,157,11,157,11,157,11,11,,11,11,11,,11,11,,100,,,11,11,,,11,12,12', +'11,157,12,,12,,,,11,157,157,157,157,,11,,157,157,11,,,12,11,157,,,,12', +',12,,12,12,157,12,12,12,,12,12,7,7,7,7,12,12,,,12,13,13,12,153,13,,13', +',,,12,,,,149,,12,153,,153,12,153,,13,12,,,,149,13,149,13,149,13,13,', +'13,13,13,,13,13,,153,,,13,13,,,13,331,331,13,149,331,,331,153,153,,13', +',,,153,,13,,,,13,,,331,13,149,,,,331,,331,,331,331,,331,331,331,,331', +'331,331,331,,,331,331,,,331,319,319,331,148,319,,319,,,,331,,,,155,', +'331,148,,148,331,148,,319,331,,,,155,319,155,319,155,319,319,,319,319', +'319,,319,319,,148,,,319,319,,,319,214,214,319,155,214,,214,214,,,319', +',,,154,,319,,155,155,319,,,214,319,155,,,154,214,154,214,154,214,214', +',214,214,214,,214,214,214,214,,,214,214,,,214,39,39,214,154,39,,39,', +',,214,,,,150,,214,,154,154,214,,,39,214,154,,,150,39,150,39,150,39,39', +',39,39,39,,39,39,,,,,39,39,,,39,40,40,39,150,40,,40,,,,39,,,,102,,39', +',,,39,,,40,39,150,,,102,40,102,40,102,40,40,,40,40,40,,40,40,,,,,40', +'40,,,40,41,41,40,102,41,,41,,,,40,,,,,,40,,,,40,,,41,40,,,,,41,,41,', +'41,41,,41,41,41,,41,41,,,,,41,41,,,41,42,42,41,,42,,42,,,,41,,,,,,41', +',,,41,,,42,41,,,,,42,,42,,42,42,,42,42,42,,42,42,,,,,42,42,,,42,215', +'215,42,,215,,215,,,,42,,,,,,42,,,,42,,,215,42,,,,,215,,215,,215,215', +',215,215,215,,215,215,,,,,215,215,,,215,216,216,215,,216,,216,,,,215', +',,,,,215,,,,215,,,216,215,,,,,216,216,216,216,216,216,216,216,216,216', +',216,216,,,,,216,216,216,216,216,303,303,216,,303,,303,,,,216,,,,,216', +'216,,,,216,,,303,216,,,,,303,,303,,303,303,,303,303,303,,303,303,,,', +',303,303,,,303,227,227,303,,227,227,227,,,,303,,,,,,303,,,,303,,,227', +'303,,,,,227,,227,,227,227,,227,227,227,,227,227,,,,,227,227,,,227,49', +'49,227,,49,49,49,,,,227,,,,,,227,,,,227,,,49,227,,,,,49,,49,,49,49,', +'49,49,49,,49,49,,,,,49,49,,,49,50,50,49,,50,50,50,,,,49,,,,,,49,,,,49', +',,50,49,,,,,50,,50,,50,50,,50,50,50,,50,50,,,,,50,50,,,50,51,51,50,', +'51,,51,51,,,50,,,,,,50,,,,50,,,51,50,,,,,51,,51,,51,51,,51,51,51,,51', +'51,,,,,51,51,,,51,55,55,51,,55,,55,,,,51,,,,,,51,,,,51,,,55,51,,,,,55', +',55,,55,55,,55,55,55,,55,55,,,,,55,55,,,55,229,229,55,,229,229,229,', +',,55,,,,,,55,,,,55,,,229,55,,,,,229,,229,,229,229,,229,229,229,,229', +'229,,,,,229,229,,,229,65,65,229,,65,,65,,,,229,,,,,,229,,,,229,,,65', +'229,,,,,65,,65,,65,65,,65,65,65,,65,65,65,65,,,65,65,,,65,231,231,65', +',231,,231,,,,65,,,,,,65,,,,65,,,231,65,,,,,231,,231,,231,231,,231,231', +'231,,231,231,,,,,231,231,,,231,67,67,231,,67,,67,,,,231,,,,,,231,,,', +'231,,,67,231,,,,,67,,67,,67,67,,67,67,67,,67,67,67,67,,,67,67,,,67,68', +'68,67,,68,,68,,,,67,,,,,,67,,,,67,,,68,67,,,,,68,,68,,68,68,,68,68,68', +',68,68,68,68,,,68,68,,,68,69,69,68,,69,,69,,,,68,,,,,,68,,,,68,,,69', +'68,,,,,69,,69,,69,69,,69,69,69,,69,69,69,69,,,69,69,,,69,70,70,69,,70', +',70,,,,69,,,,,,69,,,,69,,,70,69,,,,,70,,70,,70,70,,70,70,70,,70,70,70', +'70,,,70,70,,,70,71,71,70,,71,,71,,,,70,,,,,,70,,,,70,,,71,70,,,,,71', +',71,,71,71,,71,71,71,,71,71,71,71,,,71,71,,,71,247,247,71,,247,,247', +',,,71,,,,,,71,,,,71,,,247,71,,,,,247,,247,,247,247,,247,247,247,,247', +'247,,,,,247,247,,,247,73,73,247,,73,,73,,,,247,,,,,,247,,,,247,,,73', +'247,,,,,73,,73,,73,73,,73,73,73,,73,73,,,,,73,73,,,73,74,74,73,,74,', +'74,,,,73,,,,,,73,,,,73,,,74,73,,,,,74,,74,,74,74,,74,74,74,,74,74,,', +',,74,74,,,74,75,75,74,,75,,75,,,,74,,,,,,74,,,,74,,,75,74,,,,,75,,75', +',75,75,,75,75,75,,75,75,,,,,75,75,,,75,76,76,75,,76,,76,,,,75,,,,,,75', +',,,75,,,76,75,,,,,76,,76,,76,76,,76,76,76,,76,76,,,,,76,76,,,76,77,77', +'76,,77,,77,,,,76,,,,,,76,,,,76,,,77,76,,,,,77,,77,,77,77,,77,77,77,', +'77,77,,,,,77,77,,,77,78,78,77,,78,,78,,,,77,,,,,,77,,,,77,,,78,77,,', +',,78,,78,,78,78,,78,78,78,,78,78,,,,,78,78,,,78,79,79,78,,79,,79,,,', +'78,,,,,,78,,,,78,,,79,78,,,,,79,,79,,79,79,,79,79,79,,79,79,,,,,79,79', +',,79,80,80,79,,80,,80,,,,79,,,,,,79,,,,79,,,80,79,,,,,80,,80,,80,80', +',80,80,80,,80,80,,,,,80,80,,,80,81,81,80,,81,,81,,,,80,,,,,,80,,,,80', +',,81,80,,,,,81,,81,,81,81,,81,81,81,,81,81,,,,,81,81,,,81,82,82,81,', +'82,,82,,,,81,,,,,,81,,,,81,,,82,81,,,,,82,,82,,82,82,,82,82,82,,82,82', +',,,,82,82,,,82,83,83,82,,83,,83,,,,82,,,,,,82,,,,82,,,83,82,,,,,83,', +'83,,83,83,,83,83,83,,83,83,,,,,83,83,,,83,84,84,83,,84,,84,,,,83,,,', +',,83,,,,83,,,84,83,,,,,84,,84,,84,84,,84,84,84,,84,84,,,,,84,84,,,84', +'85,85,84,,85,,85,,,,84,,,,,,84,,,,84,,,85,84,,,,,85,,85,,85,85,,85,85', +'85,,85,85,,,,,85,85,,,85,86,86,85,,86,,86,,,,85,,,,,,85,,,,85,,,86,85', +',,,,86,,86,,86,86,,86,86,86,,86,86,,,,,86,86,,,86,87,87,86,,87,,87,', +',,86,,,,,,86,,,,86,,,87,86,,,,,87,,87,,87,87,,87,87,87,,87,87,,,,,87', +'87,,,87,88,88,87,,88,,88,,,,87,,,,,,87,,,,87,,,88,87,,,,,88,,88,,88', +'88,,88,88,88,,88,88,,,,,88,88,,,88,89,89,88,,89,,89,,,,88,,,,,,88,,', +',88,,,89,88,,,,,89,,89,,89,89,,89,89,89,,89,89,,,,,89,89,,,89,90,90', +'89,,90,,90,,,,89,,,,,,89,,,,89,,,90,89,,,,,90,,90,,90,90,,90,90,90,', +'90,90,,,,,90,90,,,90,91,91,90,,91,,91,,,,90,,,,,,90,,,,90,,,91,90,,', +',,91,,91,,91,91,,91,91,91,,91,91,,,,,91,91,,,91,92,92,91,,92,,92,,,', +'91,,,,,,91,,,,91,,,92,91,,,,,92,,92,,92,92,,92,92,92,,92,92,,,,,92,92', +',,92,93,93,92,,93,,93,,,,92,,,,,,92,,,,92,,,93,92,,,,,93,,93,,93,93', +',93,93,93,,93,93,,,,,93,93,,,93,94,94,93,,94,,94,,,,93,,,,,,93,,,,93', +',,94,93,,,,,94,,94,,94,94,,94,94,94,,94,94,,,,,94,94,,,94,177,177,94', +',177,,177,,,,94,,,,,,94,,,94,94,,,177,94,,,,,177,,177,,177,177,,177', +'177,177,,177,177,,,,,177,177,,,177,96,96,177,,96,,96,,,,177,,,,,,177', +',,,177,,,96,177,,,,,96,96,96,96,96,96,96,96,96,96,,96,96,,,,,96,96,96', +'96,96,290,290,96,,290,,290,290,,,96,,,,,96,96,,,,96,,,290,96,,,,,290', +',290,,290,290,,290,290,290,,290,290,290,290,,,290,290,,,290,98,98,290', +',98,,98,,,,290,,,,,,290,,,,290,,,98,290,,,,,98,,98,,98,98,,98,98,98', +',98,98,,,,,98,98,,,98,99,99,98,,99,,99,,,,98,,,,,,98,,,,98,,,99,98,', +',,,99,,99,,99,99,,99,99,99,,99,99,,,,,99,99,,,99,176,176,99,,176,,176', +',,,99,,,,,,99,,,,99,,,176,99,,,,,176,,176,,176,176,,176,176,176,,176', +'176,,,,,176,176,,,176,289,289,176,,289,,289,,,,176,,,,,,176,,,,176,', +',289,176,,,,,289,,289,,289,289,,289,289,289,,289,289,289,289,,,289,289', +',,289,175,175,289,,175,,175,,,,289,,,,,,289,,,,289,,,175,289,,,,,175', +',175,,175,175,,175,175,175,,175,175,,,,,175,175,,,175,172,172,175,,172', +',172,,,,175,,,,,,175,,,,175,,,172,175,,,,,172,,172,,172,172,,172,172', +'172,,172,172,,,,,172,172,,,172,,,172,104,104,,,104,,104,172,,,,,,172', +',,,172,,,,172,,104,104,,,,,104,,104,,104,104,,104,104,104,,104,104,', +',,,104,104,,,104,279,279,104,,279,,279,279,,,104,,,,,,104,,,,104,,,279', +'104,,,,,279,,279,,279,279,,279,279,279,,279,279,,,,,279,279,,,279,272', +'272,279,,272,,272,,,,279,,,,,,279,,,,279,,,272,279,,,,,272,,272,,272', +'272,,272,272,272,,272,272,,,,,272,272,,,272,,,272,108,108,,,108,,108', +'272,,,,,,272,,,,272,,,,272,,108,108,,,,,108,,108,,108,108,,108,108,108', +',108,108,,,,,108,108,,,108,233,233,108,,233,,233,233,,,108,,,,,,108', +',,,108,,,233,108,,,,,233,,233,,233,233,,233,233,233,,233,233,,,,,233', +'233,,,233,238,238,233,,238,,238,,,,233,,,,,,233,,,,233,,,238,233,,,', +',238,,238,,238,238,,238,238,238,,238,238,,,,,238,238,,,238,240,240,238', +',240,,240,,,,238,,,,,,238,,,,238,,,240,238,,,,,240,,240,,240,240,,240', +'240,240,,240,240,,,,,240,240,,,240,241,241,240,,241,,241,,,,240,,,,', +',240,,,,240,,,241,240,,,,,241,,241,,241,241,,241,241,241,,241,241,,', +',,241,241,,,241,114,114,241,,114,,114,,,,241,,,,,,241,,,,241,,,114,241', +',,,,114,,114,,114,114,,114,114,114,,114,114,,,,,114,114,,,114,265,265', +'114,,265,,265,,,,114,,,,,,114,,,,114,,,265,114,,,,,265,,265,,265,265', +',265,265,265,,265,265,,,,,265,265,,,265,251,251,265,,251,,251,,,,265', +',,,,,265,,,,265,,,251,265,,,,,251,,251,,251,251,,251,251,251,,251,251', +',,,,251,251,,,251,250,250,251,,250,,250,,,,251,,,,,,251,,,,251,,,250', +'251,,,,,250,,250,,250,250,,250,250,250,,250,250,,,,,250,250,,,250,213', +'213,250,,213,,213,213,,,250,,,,,,250,,,,250,,,213,250,,,,,213,,213,', +'213,213,,213,213,213,,213,213,213,213,,,213,213,,,213,72,72,213,,72', +',72,,,,213,,,,,,213,,,,213,,,72,213,,,,,72,,72,,72,72,,72,72,72,,72', +'72,,,,,72,72,,,72,,,72,,,,,,,,72,,,,,,72,128,,,72,,,,72,,128,128,128', +'128,128,128,,128,,128,,,128,128,128,128,,,,,,,,,,,,,,,,128,,,,128,128', +',,128,128,128,128,128,128,,128,128,10,,10,,,128,,,,10,10,10,10,10,10', +'128,10,,10,,,10,10,10,10,,,,,,,,,,,,,,,,10,,,,10,10,,,10,10,10,10,10', +'10,,10,10,,,,,,10,,133,,,,,133,,,10,133,133,133,133,133,133,,133,,133', +',,133,133,133,133,,,,,,,,,,,,,,,,133,,,,133,133,,,133,133,133,133,133', +'133,,133,133,113,,113,,,133,,,,113,113,113,113,113,113,133,113,,113', +',,113,113,113,113,,,,,,,,,,,,,,,,113,,,,113,113,,,113,113,113,113,113', +'113,,113,113,137,,,,,113,,,,137,137,137,137,137,137,113,137,,137,,,137', +'137,137,137,,,,,,,,,,,,,,,,137,,,,137,137,,,137,137,137,137,137,137', +',137,137,112,,112,,,137,,,,112,112,112,112,112,112,137,112,,112,,,112', +'112,112,112,,,,,,,,,,,,,,,,112,,,,112,112,,,112,112,112,112,112,112', +',112,112,111,,111,,,112,,,,111,111,111,111,111,111,112,111,,111,,,111', +'111,111,111,,,,,,,,,,,,,,,,111,,,,111,111,,,111,111,111,111,111,111', +',111,111,109,,109,,,111,,,,109,109,109,109,109,109,111,109,,109,,,109', +'109,109,109,,,,,,,,,,,,,,,,109,,,,109,109,,,109,109,109,109,109,109', +',109,109,316,,,,,109,,,,316,316,316,316,316,316,109,316,,316,,,316,316', +'316,316,,,,,,,,,,,,,,,,316,,,,316,316,,,316,316,316,316,316,316,,316', +'316,310,,,,,316,,,,310,310,310,310,310,310,316,310,,310,,,310,310,310', +'310,,,,,,,,,,,,,,,,310,,,,310,310,,,310,310,310,310,310,310,,310,310', +'309,,,,,310,,,,309,309,309,309,309,309,310,309,,309,,,309,309,309,309', +',,,,,,,,,,,,,,,309,,,,309,309,,,309,309,309,309,309,309,,309,309,198', +',,,,309,,,,198,198,198,198,198,198,309,198,,198,,,198,198,198,198,,', +',,,,,,,,,,,,,198,,,,198,198,,,198,198,198,198,198,198,,198,198,301,', +',,,198,,,,301,301,301,301,301,301,198,301,,301,,,301,301,301,301,,,', +',,,,,,,,,,,,301,,,,301,301,,,301,301,301,301,301,301,,301,301,297,,', +',,301,,,,297,297,297,297,297,297,301,297,,297,,,297,297,297,297,158', +',,,,,,,,,,,,,158,297,158,,158,297,297,,,297,297,297,297,297,297,,297', +'297,,,,,,297,,,158,,,159,,,,297,158,158,158,158,158,158,,158,158,159', +',159,,159,158,,,,,,160,,,,158,,,,,,,,,160,160,159,160,,160,,,160,,159', +'159,159,159,159,159,,159,159,,,,,,159,,,160,,,,,161,,159,160,160,160', +'160,160,160,,160,160,,161,161,,161,160,161,,,161,,,,162,,160,,,,,,,', +',,,162,162,161,162,,162,,,162,,161,161,161,161,161,161,,161,161,,,,', +',161,,,162,,,,,163,,161,162,162,162,162,162,162,,162,162,,163,163,,163', +'162,163,,,163,,,,,,162,,,,,,,,,,,,,163,,,,,164,,,163,163,163,163,163', +'163,,163,163,164,164,164,,164,163,164,,,164,164,164,164,,,163,,,,,,', +',,,,,,164,,,,,165,,,164,164,164,164,164,164,,164,164,165,165,165,,165', +'164,165,,,165,165,165,165,,,164,,,,,,,,,,,,,165,,,,,165,,,165,165,165', +'165,165,165,,165,165,294,,,,,165,,,,294,294,294,294,294,294,165,294', +',294,,,294,294,294,294,,,,,,,,,,,,,,,,294,,,,294,294,,,294,294,294,294', +'294,294,,294,294,167,,,,,294,,,,167,167,167,167,167,167,294,167,,167', +',,167,167,167,167,,,,,,,,,,,,,,,,167,,,,167,167,,,167,167,167,167,167', +'167,,167,167,168,,,,,167,,,,168,168,168,168,168,168,167,168,,168,,,168', +'168,168,168,,,,,,,,,,,,,,,,168,,,,168,168,,,168,168,168,168,168,168', +',168,168,,,,,,168,,169,,,,,169,,,168,169,169,169,169,169,169,,169,,169', +',,169,169,169,169,,,,,,,,,,,,,,,,169,,,,169,169,,,169,169,169,169,169', +'169,,169,169,103,,,,,169,,,,103,103,103,103,103,103,169,103,,103,,103', +'103,103,103,103,,,,,,,,,,,,,,,,103,,,,103,103,,,103,103,103,103,103', +'103,,103,103,,,,,,103,,174,,,,,,,,103,174,174,174,174,174,174,174,174', +',174,,,174,174,174,174,,,,,,,,,,,,,,,,174,,,,174,174,,,174,174,174,174', +'174,174,,174,174,166,,,,,174,,,,166,166,166,166,166,166,174,166,,166', +',,166,166,166,166,,,,,,,,,,,,,,,,166,,,,166,166,,,166,166,166,166,166', +'166,,166,166,,197,197,197,197,166,197,197,197,197,197,,197,197,,166', +',,,,197,197,197,244,244,244,244,,244,244,244,244,244,,244,244,,,197', +'197,,,244,244,244,249,249,249,249,,249,249,249,249,249,,249,249,,,244', +'244,,,249,249,249,,,,,,,,,,,,,,,,249,249' ] + racc_action_check = arr = ::Array.new(5601, 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 = [ - 4691, 297, nil, nil, 4599, 284, nil, 145, nil, nil, - 285, 4461, 4415, 4369, nil, nil, nil, nil, nil, nil, + -2, 300, nil, nil, 100, 287, nil, 335, nil, nil, + 4052, 304, 355, 406, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, - nil, nil, nil, nil, nil, 263, 200, 242, 4231, 4185, - 4139, 4093, 85, 219, 118, 120, nil, 3909, 202, nil, - nil, nil, 3814, nil, nil, nil, nil, nil, nil, nil, - 251, 3676, 238, 3259, 3213, 3167, 3121, 2891, 2474, 3538, - 86, 26, 1692, 1738, 1784, 1830, 1876, 1922, 1968, 2014, - 2060, 2106, 2152, 2198, 2244, 2290, 2336, 2382, 2428, 1505, - 164, 2566, 174, 2661, 2707, 196, 64, 170, 1468, 2937, - nil, 253, -28, 3075, 1409, nil, 1350, 1291, 1232, 3305, - 21, nil, nil, nil, 67, -11, nil, nil, nil, nil, - nil, 1173, 138, nil, 161, nil, nil, 219, 1114, nil, - 26, nil, 236, nil, nil, nil, nil, nil, -5, 13, - nil, nil, nil, nil, 878, 795, 250, 77, 18, 594, - 570, 528, 445, 421, 377, 353, 309, 226, 169, 112, - 53, -6, nil, nil, 3722, nil, 653, 4047, 4001, 3860, - 255, 255, nil, nil, 7, nil, -7, 22, 119, 109, - 113, 53, 24, nil, nil, nil, nil, nil, nil, 4785, - 996, 218, nil, 238, nil, 246, 189, nil, 3768, nil, - 227, nil, 216, -9, nil, 4737, 3630, 3584, 1551, 176, - 127, nil, 27, 143, 109, 24, nil, 53, 3446, nil, - nil, 197, 3397, nil, nil, nil, nil, 3351, nil, 3029, - 2983, 2, nil, 4764, nil, 81, 2845, 102, 4806, 2799, - 2753, 168, nil, nil, nil, nil, nil, nil, nil, nil, - nil, nil, nil, nil, 2615, 158, nil, 175, nil, 126, - 167, 2520, nil, 203, 101, 211, 196, 74, 1646, nil, - 193, 222, 228, 230, nil, 107, nil, 234, 1600, 3492, - nil, nil, 1055, nil, nil, nil, 937, nil, 854, 250, - 3955, -4, nil, nil, nil, nil, 771, 712, 255, 197, - nil, nil, nil, 504, 146, nil, 4277, 264, 243, nil, - 267, 268, nil, nil, nil, 268, 275, nil, 4323, nil, - nil, nil, 263, 280, nil, 281, nil, nil, nil, nil, - nil, nil, nil, nil, 4507, nil, 4553, 4645, nil, nil, - 289, nil, nil, nil, nil, 296, nil, 298, nil, 308, - nil, nil, nil, nil, nil ] + nil, nil, nil, nil, nil, nil, 257, 193, 228, 610, + 661, 712, 763, 75, 199, nil, 77, 5, nil, 1018, + 1069, 1120, nil, nil, nil, 1171, nil, nil, nil, nil, + nil, nil, nil, nil, 229, 1273, 214, 1375, 1426, 1477, + 1528, 1579, 3931, 1681, 1732, 1783, 1834, 1885, 1936, 1987, + 2038, 2089, 2140, 2191, 2242, 2293, 2344, 2395, 2446, 2497, + 2548, 2599, 2650, 2701, 2752, 185, 2854, 215, 2956, 3007, + 303, 128, 671, 5333, 3265, nil, 204, -10, 3421, 4401, + nil, 4344, 4287, 4173, 3676, 154, nil, nil, nil, nil, + 149, 87, nil, 126, nil, nil, nil, nil, 3995, 66, + nil, 3, nil, 4116, 153, nil, nil, 4230, nil, 22, + nil, 37, nil, nil, nil, nil, nil, 4, 507, 416, + 620, 8, 110, 405, 569, 518, 212, 314, 4768, 4811, + 4836, 4881, 4906, 4951, 4996, 5041, 5454, 5155, 5212, 5276, + nil, nil, 3211, nil, 5397, 3160, 3058, 2803, 54, 116, + nil, nil, 9, nil, 13, 24, 60, 83, 16, -9, + 10, nil, nil, nil, nil, nil, nil, 5487, 4629, 150, + nil, 208, nil, 236, 190, nil, 253, nil, 282, nil, + 24, -9, nil, 3880, 559, 814, 865, 181, 182, nil, + -13, 201, 193, 107, nil, 118, 111, 967, nil, 1222, + nil, 1324, nil, 3472, nil, nil, nil, nil, 3523, nil, + 3574, 3625, 78, nil, 5509, nil, 105, 1630, 128, 5531, + 3829, 3778, 163, nil, nil, nil, nil, nil, nil, nil, + nil, nil, nil, nil, nil, 3727, 154, nil, 174, nil, + 115, 157, 3367, nil, 189, 73, 193, 175, 60, 3316, + nil, 172, 203, 207, 209, nil, -31, nil, 209, 3109, + 2905, nil, nil, nil, 5098, nil, nil, 4743, nil, nil, + nil, 4686, 221, 916, 227, nil, nil, nil, nil, 4572, + 4515, 240, 182, nil, nil, nil, 4458, 95, nil, 508, + 256, 238, nil, 264, 268, nil, nil, nil, 268, 272, + nil, 457, nil, nil, nil, 257, 276, nil, nil, 278, + nil, nil, nil, nil, nil, nil, nil, nil, 202, nil, + 151, 49, nil, nil, 294, nil, nil, nil, 295, nil, + 302, nil, 304, nil, nil, nil, nil, nil ] racc_action_default = [ - -203, -204, -1, -2, -3, -4, -7, -9, -10, -15, - -103, -204, -204, -204, -43, -44, -45, -46, -47, -48, - -49, -50, -51, -52, -53, -54, -55, -56, -57, -58, - -59, -60, -61, -62, -63, -68, -69, -73, -204, -204, - -204, -204, -204, -113, -204, -204, -158, -204, -204, -168, - -169, -170, -204, -172, -179, -180, -181, -182, -183, -184, - -204, -204, -6, -204, -204, -204, -204, -204, -204, -204, - -204, -204, -204, -204, -204, -204, -204, -204, -204, -204, - -204, -204, -204, -204, -204, -204, -204, -204, -204, -204, - -204, -121, -116, -203, -203, -27, -204, -34, -204, -204, - -70, -204, -204, -204, -204, -80, -204, -204, -204, -204, - -203, -147, -148, -114, -203, -203, -139, -141, -142, -143, - -144, -41, -204, -161, -204, -164, -165, -204, -176, -171, - -204, 355, -5, -8, -11, -12, -13, -14, -204, -17, - -18, -156, -157, -19, -20, -21, -22, -23, -24, -25, - -26, -28, -29, -30, -31, -32, -33, -35, -36, -37, - -38, -204, -39, -98, -204, -74, -204, -196, -202, -190, - -187, -185, -111, -122, -179, -125, -183, -204, -193, -191, - -199, -181, -182, -189, -194, -195, -197, -198, -200, -121, - -120, -204, -119, -204, -40, -185, -65, -75, -204, -78, - -185, -152, -155, -204, -72, -204, -204, -204, -121, -187, - -203, -149, -204, -204, -204, -204, -145, -204, -204, -159, - -162, -204, -204, -173, -174, -175, -177, -204, -16, -204, - -204, -185, -100, -121, -110, -204, -188, -204, -186, -204, - -204, -185, -124, -126, -190, -191, -192, -193, -196, -199, - -201, -202, -117, -118, -186, -204, -67, -204, -77, -204, - -186, -204, -71, -204, -83, -204, -89, -204, -204, -93, - -187, -185, -204, -204, -133, -204, -150, -185, -204, -204, - -140, -146, -42, -160, -163, -166, -167, -178, -102, -204, - -186, -185, -106, -112, -107, -123, -127, -128, -204, -64, - -76, -79, -153, -154, -83, -82, -204, -204, -89, -88, - -204, -204, -97, -92, -94, -204, -204, -108, -204, -134, - -135, -136, -204, -204, -130, -204, -138, -99, -101, -109, - -115, -66, -81, -84, -204, -87, -204, -204, -104, -105, - -204, -132, -151, -129, -137, -204, -86, -204, -91, -204, - -96, -131, -85, -90, -95 ] + -211, -212, -1, -2, -3, -4, -7, -9, -10, -15, + -105, -212, -212, -212, -44, -45, -46, -47, -48, -49, + -50, -51, -52, -53, -54, -55, -56, -57, -58, -59, + -60, -61, -62, -63, -64, -65, -70, -71, -75, -212, + -212, -212, -212, -212, -115, -117, -212, -212, -162, -212, + -212, -212, -175, -176, -177, -212, -179, -186, -187, -188, + -189, -190, -191, -192, -212, -212, -6, -212, -212, -212, + -212, -212, -212, -212, -212, -212, -212, -212, -212, -212, + -212, -212, -212, -212, -212, -212, -212, -212, -212, -212, + -212, -212, -212, -212, -212, -212, -124, -119, -211, -211, + -27, -212, -34, -212, -212, -72, -212, -212, -212, -212, + -82, -212, -212, -212, -212, -211, -134, -153, -154, -116, + -211, -211, -143, -145, -146, -147, -148, -149, -42, -212, + -165, -212, -168, -212, -212, -171, -172, -183, -178, -212, + 368, -5, -8, -11, -12, -13, -14, -212, -17, -18, + -19, -20, -21, -22, -23, -24, -25, -26, -28, -29, + -30, -31, -32, -33, -35, -36, -37, -38, -39, -212, + -40, -100, -212, -76, -212, -204, -210, -198, -195, -193, + -113, -125, -187, -128, -191, -212, -201, -199, -207, -189, + -190, -197, -202, -203, -205, -206, -208, -124, -123, -212, + -122, -212, -41, -193, -67, -77, -212, -80, -193, -158, + -161, -212, -74, -212, -212, -212, -124, -195, -211, -155, + -212, -212, -212, -212, -151, -212, -212, -212, -163, -212, + -166, -212, -169, -212, -180, -181, -182, -184, -212, -16, + -212, -212, -193, -102, -124, -112, -212, -196, -212, -194, + -212, -212, -193, -127, -129, -198, -199, -200, -201, -204, + -207, -209, -210, -120, -121, -194, -212, -69, -212, -79, + -212, -194, -212, -73, -212, -85, -212, -91, -212, -212, + -95, -195, -193, -212, -212, -137, -212, -156, -193, -211, + -212, -144, -152, -150, -43, -164, -167, -174, -170, -173, + -185, -104, -212, -194, -193, -108, -114, -109, -126, -130, + -131, -212, -66, -78, -81, -159, -160, -85, -84, -212, + -212, -91, -90, -212, -212, -99, -94, -96, -212, -212, + -110, -211, -138, -139, -140, -212, -212, -135, -136, -212, + -142, -101, -103, -111, -118, -68, -83, -86, -212, -89, + -212, -212, -106, -107, -212, -157, -132, -141, -212, -88, + -212, -93, -212, -98, -133, -87, -92, -97 ] racc_goto_table = [ - 2, 3, 100, 95, 97, 98, 114, 163, 129, 126, - 170, 171, 127, 200, 118, 305, 309, 120, 235, 210, - 280, 311, 281, 213, 237, 231, 269, 293, 209, 233, - 104, 106, 107, 108, 142, 142, 62, 140, 143, 121, - 191, 193, 141, 141, 128, 268, 295, 333, 255, 134, - 135, 136, 137, 259, 197, 332, 273, 272, 335, 319, - 121, 139, 214, 162, 144, 145, 146, 147, 148, 149, - 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, - 160, 161, 232, 166, 289, 190, 190, 314, 302, 122, - 124, 121, 133, 132, 298, 121, 1, 226, 227, 225, - nil, 166, nil, nil, nil, nil, nil, nil, nil, 241, - 138, 211, nil, nil, nil, 211, 216, nil, 315, nil, - nil, nil, nil, 277, 316, nil, nil, 270, 271, nil, - 322, nil, nil, nil, nil, nil, nil, nil, nil, nil, - 114, 195, nil, nil, 329, 203, nil, nil, nil, 118, - nil, nil, 120, 291, nil, nil, 161, nil, nil, 104, - 106, 107, 256, nil, nil, nil, nil, nil, nil, nil, + 2, 120, 3, 100, 102, 103, 105, 138, 126, 171, + 136, 124, 179, 208, 322, 246, 218, 178, 318, 336, + 242, 221, 143, 144, 145, 146, 248, 291, 280, 292, + 306, 109, 111, 112, 113, 217, 66, 199, 201, 129, + 131, 128, 128, 133, 244, 324, 308, 137, 279, 347, + 266, 205, 284, 332, 283, 270, 222, 170, 349, 315, + 346, 354, 147, 134, 128, 148, 149, 150, 151, 152, + 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, + 163, 164, 165, 166, 167, 168, 169, 243, 174, 302, + 198, 198, 327, 142, 203, 1, 128, 141, 211, 311, + 128, 237, 238, 236, nil, nil, 174, nil, nil, nil, + nil, nil, nil, 252, nil, nil, nil, 219, 328, nil, + nil, nil, 219, 224, nil, nil, 288, nil, nil, 329, + nil, nil, 282, nil, nil, 335, nil, 281, nil, nil, + nil, 120, nil, nil, nil, nil, nil, nil, nil, 126, + nil, 343, 124, nil, nil, nil, nil, nil, nil, nil, + 304, nil, nil, nil, 169, nil, nil, 109, 111, 112, + nil, nil, nil, 267, nil, nil, nil, nil, nil, nil, + nil, nil, nil, nil, 126, nil, 126, 124, nil, 124, + 300, nil, 299, nil, nil, nil, nil, nil, nil, nil, + nil, nil, nil, nil, nil, nil, 268, 128, 174, nil, + nil, nil, nil, 274, 276, nil, nil, nil, 342, 294, + 285, 294, nil, 297, nil, 133, nil, nil, nil, nil, + 137, nil, 294, 301, nil, nil, nil, nil, nil, 174, + nil, 333, 309, 310, nil, nil, nil, nil, nil, nil, + nil, nil, nil, nil, nil, nil, nil, 294, nil, nil, + nil, nil, nil, nil, 316, nil, nil, nil, nil, nil, + nil, 128, nil, nil, nil, nil, nil, nil, nil, nil, + nil, 345, nil, nil, nil, nil, nil, nil, nil, nil, + 339, 338, nil, nil, nil, 169, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, - nil, nil, 285, 287, 118, 127, 118, 120, nil, 120, - nil, nil, nil, nil, nil, nil, nil, nil, 257, 121, - 166, nil, nil, nil, nil, 263, 265, nil, 328, nil, - 282, 274, nil, nil, 286, nil, nil, nil, nil, 128, - nil, 282, 288, nil, nil, nil, nil, nil, 166, nil, - nil, 296, 297, nil, nil, nil, nil, 320, nil, nil, - nil, nil, nil, nil, nil, nil, 282, nil, nil, nil, - nil, nil, nil, 303, nil, nil, nil, nil, nil, nil, - 121, nil, nil, nil, nil, 331, nil, nil, nil, nil, - nil, nil, nil, nil, nil, nil, nil, nil, 323, 325, - nil, nil, 161, nil, nil, nil, nil, nil, nil, nil, - nil, nil, nil, nil, nil, nil, nil, nil, 104, nil, + nil, 109, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, - nil, nil, nil, nil, nil, nil, nil, nil, 340, nil, - nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, - nil, nil, nil, nil, 345, nil, 347, 349 ] + nil, nil, nil, 338, nil, nil, nil, nil, nil, nil, + nil, nil, nil, nil, nil, nil, nil, nil, 358, nil, + 360, 362 ] racc_goto_check = [ - 2, 3, 37, 9, 9, 9, 62, 49, 75, 71, - 52, 54, 31, 42, 35, 44, 45, 30, 53, 63, - 68, 48, 68, 63, 36, 50, 47, 55, 52, 56, - 9, 9, 9, 9, 31, 31, 5, 12, 12, 9, - 58, 58, 30, 30, 9, 46, 59, 43, 36, 7, - 7, 7, 7, 36, 41, 44, 64, 53, 45, 65, - 9, 9, 67, 13, 9, 9, 9, 9, 9, 9, + 2, 62, 3, 9, 9, 9, 37, 78, 29, 49, + 74, 35, 54, 42, 45, 53, 63, 52, 44, 64, + 50, 63, 7, 7, 7, 7, 36, 70, 47, 70, + 55, 9, 9, 9, 9, 52, 5, 58, 58, 11, + 11, 9, 9, 9, 56, 48, 59, 9, 46, 43, + 36, 41, 66, 67, 53, 36, 69, 12, 45, 72, + 44, 64, 11, 73, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, - 9, 9, 49, 9, 36, 9, 9, 47, 69, 11, - 70, 9, 6, 5, 36, 9, 1, 76, 77, 79, - nil, 9, nil, nil, nil, nil, nil, nil, nil, 54, - 11, 3, nil, nil, nil, 3, 3, nil, 53, nil, - nil, nil, nil, 42, 36, nil, nil, 52, 54, nil, - 36, nil, nil, nil, nil, nil, nil, nil, nil, nil, - 62, 11, nil, nil, 36, 11, nil, nil, nil, 35, - nil, nil, 30, 54, nil, nil, 9, nil, nil, 9, - 9, 9, 37, nil, nil, nil, nil, nil, nil, nil, + 9, 9, 9, 9, 9, 9, 9, 49, 9, 36, + 9, 9, 47, 6, 11, 1, 9, 5, 11, 36, + 9, 79, 80, 82, nil, nil, 9, nil, nil, nil, + nil, nil, nil, 54, nil, nil, nil, 3, 53, nil, + nil, nil, 3, 3, nil, nil, 42, nil, nil, 36, + nil, nil, 54, nil, nil, 36, nil, 52, nil, nil, + nil, 62, nil, nil, nil, nil, nil, nil, nil, 29, + nil, 36, 35, nil, nil, nil, nil, nil, nil, nil, + 54, nil, nil, nil, 9, nil, nil, 9, 9, 9, + nil, nil, nil, 37, nil, nil, nil, nil, nil, nil, + nil, nil, nil, nil, 29, nil, 29, 35, nil, 35, + 78, nil, 74, nil, nil, nil, nil, nil, nil, nil, + nil, nil, nil, nil, nil, nil, 2, 9, 9, nil, + nil, nil, nil, 2, 2, nil, nil, nil, 49, 9, + 3, 9, nil, 9, nil, 9, nil, nil, nil, nil, + 9, nil, 9, 9, nil, nil, nil, nil, nil, 9, + nil, 62, 9, 9, nil, nil, nil, nil, nil, nil, + nil, nil, nil, nil, nil, nil, nil, 9, nil, nil, + nil, nil, nil, nil, 9, nil, nil, nil, nil, nil, + nil, 9, nil, nil, nil, nil, nil, nil, nil, nil, + nil, 37, nil, nil, nil, nil, nil, nil, nil, nil, + 2, 3, nil, nil, nil, 9, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, - nil, nil, 71, 75, 35, 31, 35, 30, nil, 30, - nil, nil, nil, nil, nil, nil, nil, nil, 2, 9, - 9, nil, nil, nil, nil, 2, 2, nil, 49, nil, - 9, 3, nil, nil, 9, nil, nil, nil, nil, 9, - nil, 9, 9, nil, nil, nil, nil, nil, 9, nil, - nil, 9, 9, nil, nil, nil, nil, 62, nil, nil, - nil, nil, nil, nil, nil, nil, 9, nil, nil, nil, - nil, nil, nil, 9, nil, nil, nil, nil, nil, nil, - 9, nil, nil, nil, nil, 37, nil, nil, nil, nil, - nil, nil, nil, nil, nil, nil, nil, nil, 2, 2, - nil, nil, 9, nil, nil, nil, nil, nil, nil, nil, - nil, nil, nil, nil, nil, nil, nil, nil, 9, nil, + nil, 9, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, + nil, nil, nil, 3, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 2, nil, - nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, - nil, nil, nil, nil, 2, nil, 2, 2 ] + 2, 2 ] racc_goto_pointer = [ - nil, 96, 0, 1, nil, 32, 29, -15, nil, -8, - nil, 42, -33, -26, nil, nil, nil, nil, nil, nil, - nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, - -28, -36, nil, nil, nil, -31, -147, -34, nil, nil, - nil, -47, -89, -259, -249, -250, -162, -181, -246, -82, - -139, nil, -81, -152, -80, -209, -137, nil, -53, -192, - nil, nil, -38, -91, -154, -216, nil, -53, -195, -172, - 42, -39, nil, nil, nil, -44, -33, -32, nil, -31 ] + nil, 95, 0, 2, nil, 32, 26, -46, nil, -8, + nil, -10, -37, nil, nil, nil, nil, nil, nil, nil, + nil, nil, nil, nil, nil, nil, nil, nil, nil, -39, + nil, nil, nil, nil, nil, -36, -153, -31, nil, nil, + nil, -55, -94, -270, -257, -263, -167, -187, -233, -85, + -152, nil, -79, -163, -84, -217, -130, nil, -61, -203, + nil, nil, -45, -99, -270, nil, -166, -233, nil, -65, + -196, nil, -212, 12, -41, nil, nil, nil, -48, -38, + -37, nil, -36 ] racc_goto_default = [ - nil, nil, nil, 192, 4, 5, 6, 7, 8, 10, - 9, 267, nil, nil, 14, 35, 15, 16, 17, 18, - 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, - 29, 30, 31, 32, 33, 34, nil, nil, 36, 37, - 101, nil, nil, 105, nil, nil, nil, nil, nil, nil, - nil, 41, nil, nil, nil, 172, nil, 92, nil, 173, - 177, 175, 110, nil, nil, nil, 115, nil, 116, 201, - nil, nil, 49, 50, 52, nil, nil, nil, 130, nil ] + nil, nil, 337, 200, 4, 5, 6, 7, 8, 10, + 9, 278, nil, 14, 36, 15, 16, 17, 18, 19, + 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, + 30, 31, 32, 33, 34, 35, nil, nil, 37, 38, + 106, nil, nil, 110, nil, nil, nil, nil, nil, nil, + nil, 42, nil, nil, nil, 180, nil, 97, nil, 181, + 185, 183, 116, nil, nil, 115, nil, nil, 121, nil, + 122, 123, 209, nil, nil, 52, 53, 55, nil, nil, + nil, 139, nil ] racc_reduce_table = [ 0, 0, :racc_error, - 1, 77, :_reduce_1, - 1, 77, :_reduce_none, - 1, 78, :_reduce_3, - 1, 80, :_reduce_4, - 3, 80, :_reduce_5, - 2, 80, :_reduce_6, - 1, 81, :_reduce_7, - 3, 81, :_reduce_8, + 1, 82, :_reduce_1, 1, 82, :_reduce_none, - 1, 83, :_reduce_10, - 3, 83, :_reduce_11, - 3, 83, :_reduce_12, - 3, 83, :_reduce_13, - 3, 83, :_reduce_14, - 1, 85, :_reduce_none, - 4, 85, :_reduce_16, - 3, 85, :_reduce_17, - 3, 85, :_reduce_18, - 3, 85, :_reduce_19, - 3, 85, :_reduce_20, - 3, 85, :_reduce_21, - 3, 85, :_reduce_22, - 3, 85, :_reduce_23, - 3, 85, :_reduce_24, - 3, 85, :_reduce_25, - 3, 85, :_reduce_26, - 2, 85, :_reduce_27, - 3, 85, :_reduce_28, - 3, 85, :_reduce_29, - 3, 85, :_reduce_30, - 3, 85, :_reduce_31, - 3, 85, :_reduce_32, - 3, 85, :_reduce_33, - 2, 85, :_reduce_34, - 3, 85, :_reduce_35, - 3, 85, :_reduce_36, - 3, 85, :_reduce_37, - 3, 85, :_reduce_38, - 3, 85, :_reduce_39, - 3, 85, :_reduce_40, - 1, 87, :_reduce_41, - 3, 87, :_reduce_42, - 1, 86, :_reduce_none, - 1, 91, :_reduce_none, - 1, 91, :_reduce_none, - 1, 91, :_reduce_none, - 1, 91, :_reduce_none, + 1, 83, :_reduce_3, + 1, 85, :_reduce_4, + 3, 85, :_reduce_5, + 2, 85, :_reduce_6, + 1, 86, :_reduce_7, + 3, 86, :_reduce_8, + 1, 87, :_reduce_none, + 1, 88, :_reduce_10, + 3, 88, :_reduce_11, + 3, 88, :_reduce_12, + 3, 88, :_reduce_13, + 3, 88, :_reduce_14, + 1, 90, :_reduce_none, + 4, 90, :_reduce_16, + 3, 90, :_reduce_17, + 3, 90, :_reduce_18, + 3, 90, :_reduce_19, + 3, 90, :_reduce_20, + 3, 90, :_reduce_21, + 3, 90, :_reduce_22, + 3, 90, :_reduce_23, + 3, 90, :_reduce_24, + 3, 90, :_reduce_25, + 3, 90, :_reduce_26, + 2, 90, :_reduce_27, + 3, 90, :_reduce_28, + 3, 90, :_reduce_29, + 3, 90, :_reduce_30, + 3, 90, :_reduce_31, + 3, 90, :_reduce_32, + 3, 90, :_reduce_33, + 2, 90, :_reduce_34, + 3, 90, :_reduce_35, + 3, 90, :_reduce_36, + 3, 90, :_reduce_37, + 3, 90, :_reduce_38, + 3, 90, :_reduce_39, + 3, 90, :_reduce_40, + 3, 90, :_reduce_41, + 1, 92, :_reduce_42, + 3, 92, :_reduce_43, 1, 91, :_reduce_none, - 1, 91, :_reduce_none, - 1, 91, :_reduce_none, - 1, 91, :_reduce_none, - 1, 91, :_reduce_none, - 1, 91, :_reduce_none, - 1, 92, :_reduce_none, - 1, 92, :_reduce_none, - 1, 92, :_reduce_none, - 1, 92, :_reduce_none, - 1, 92, :_reduce_none, - 1, 92, :_reduce_none, - 1, 92, :_reduce_none, - 1, 92, :_reduce_none, - 1, 107, :_reduce_62, - 1, 107, :_reduce_63, - 5, 90, :_reduce_64, - 3, 90, :_reduce_65, - 6, 90, :_reduce_66, - 4, 90, :_reduce_67, - 1, 90, :_reduce_68, - 1, 94, :_reduce_69, - 2, 94, :_reduce_70, - 4, 114, :_reduce_71, - 3, 114, :_reduce_72, - 1, 114, :_reduce_73, - 3, 115, :_reduce_74, - 2, 113, :_reduce_75, - 3, 117, :_reduce_76, - 2, 117, :_reduce_77, - 2, 116, :_reduce_78, - 4, 116, :_reduce_79, - 2, 97, :_reduce_80, - 5, 119, :_reduce_81, - 4, 119, :_reduce_82, - 0, 120, :_reduce_none, - 2, 120, :_reduce_84, - 4, 120, :_reduce_85, - 3, 120, :_reduce_86, - 6, 98, :_reduce_87, - 5, 98, :_reduce_88, - 0, 121, :_reduce_none, - 4, 121, :_reduce_90, - 3, 121, :_reduce_91, - 5, 96, :_reduce_92, - 1, 122, :_reduce_93, - 2, 122, :_reduce_94, - 5, 123, :_reduce_95, - 4, 123, :_reduce_96, - 1, 124, :_reduce_97, - 1, 89, :_reduce_none, - 4, 89, :_reduce_99, - 1, 126, :_reduce_100, - 3, 126, :_reduce_101, - 3, 125, :_reduce_102, - 1, 84, :_reduce_103, - 6, 84, :_reduce_104, - 6, 84, :_reduce_105, - 5, 84, :_reduce_106, - 5, 84, :_reduce_107, - 5, 84, :_reduce_108, - 4, 131, :_reduce_109, - 1, 132, :_reduce_110, - 1, 128, :_reduce_111, - 3, 128, :_reduce_112, - 1, 127, :_reduce_113, - 2, 127, :_reduce_114, - 6, 95, :_reduce_115, - 2, 95, :_reduce_116, - 3, 133, :_reduce_117, - 3, 133, :_reduce_118, - 1, 134, :_reduce_none, - 1, 134, :_reduce_none, - 0, 130, :_reduce_121, - 1, 130, :_reduce_122, - 3, 130, :_reduce_123, - 1, 136, :_reduce_none, - 1, 136, :_reduce_none, - 1, 136, :_reduce_none, - 3, 135, :_reduce_127, - 3, 135, :_reduce_128, - 6, 99, :_reduce_129, - 5, 99, :_reduce_130, - 7, 100, :_reduce_131, - 6, 100, :_reduce_132, - 1, 140, :_reduce_none, - 2, 140, :_reduce_134, + 1, 95, :_reduce_none, + 1, 95, :_reduce_none, + 1, 95, :_reduce_none, + 1, 95, :_reduce_none, + 1, 95, :_reduce_none, + 1, 95, :_reduce_none, + 1, 95, :_reduce_none, + 1, 95, :_reduce_none, + 1, 95, :_reduce_none, + 1, 95, :_reduce_none, + 1, 96, :_reduce_none, + 1, 96, :_reduce_none, + 1, 96, :_reduce_none, + 1, 96, :_reduce_none, + 1, 96, :_reduce_none, + 1, 96, :_reduce_none, + 1, 96, :_reduce_none, + 1, 96, :_reduce_none, + 1, 96, :_reduce_none, + 1, 111, :_reduce_64, + 1, 111, :_reduce_65, + 5, 94, :_reduce_66, + 3, 94, :_reduce_67, + 6, 94, :_reduce_68, + 4, 94, :_reduce_69, + 1, 94, :_reduce_70, + 1, 98, :_reduce_71, + 2, 98, :_reduce_72, + 4, 119, :_reduce_73, + 3, 119, :_reduce_74, + 1, 119, :_reduce_75, + 3, 120, :_reduce_76, + 2, 118, :_reduce_77, + 3, 122, :_reduce_78, + 2, 122, :_reduce_79, + 2, 121, :_reduce_80, + 4, 121, :_reduce_81, + 2, 101, :_reduce_82, + 5, 124, :_reduce_83, + 4, 124, :_reduce_84, + 0, 125, :_reduce_none, + 2, 125, :_reduce_86, + 4, 125, :_reduce_87, + 3, 125, :_reduce_88, + 6, 102, :_reduce_89, + 5, 102, :_reduce_90, + 0, 126, :_reduce_none, + 4, 126, :_reduce_92, + 3, 126, :_reduce_93, + 5, 100, :_reduce_94, + 1, 127, :_reduce_95, + 2, 127, :_reduce_96, + 5, 128, :_reduce_97, + 4, 128, :_reduce_98, + 1, 129, :_reduce_99, + 1, 93, :_reduce_none, + 4, 93, :_reduce_101, + 1, 131, :_reduce_102, + 3, 131, :_reduce_103, + 3, 130, :_reduce_104, + 1, 89, :_reduce_105, + 6, 89, :_reduce_106, + 6, 89, :_reduce_107, + 5, 89, :_reduce_108, + 5, 89, :_reduce_109, + 5, 89, :_reduce_110, + 4, 136, :_reduce_111, + 1, 137, :_reduce_112, + 1, 133, :_reduce_113, + 3, 133, :_reduce_114, + 1, 132, :_reduce_115, + 2, 132, :_reduce_116, + 1, 132, :_reduce_117, + 6, 99, :_reduce_118, + 2, 99, :_reduce_119, + 3, 138, :_reduce_120, + 3, 138, :_reduce_121, + 1, 139, :_reduce_none, + 1, 139, :_reduce_none, + 0, 135, :_reduce_124, + 1, 135, :_reduce_125, + 3, 135, :_reduce_126, + 1, 141, :_reduce_none, 1, 141, :_reduce_none, 1, 141, :_reduce_none, - 6, 101, :_reduce_137, - 5, 101, :_reduce_138, - 1, 142, :_reduce_139, - 3, 142, :_reduce_140, - 1, 144, :_reduce_141, - 1, 144, :_reduce_142, - 1, 144, :_reduce_143, - 1, 144, :_reduce_none, - 1, 143, :_reduce_none, - 2, 143, :_reduce_146, - 1, 138, :_reduce_147, - 1, 138, :_reduce_148, - 1, 139, :_reduce_149, - 2, 139, :_reduce_150, - 4, 139, :_reduce_151, - 1, 118, :_reduce_152, - 3, 118, :_reduce_153, - 3, 145, :_reduce_154, - 1, 145, :_reduce_155, - 1, 88, :_reduce_none, - 1, 88, :_reduce_none, - 1, 93, :_reduce_158, - 3, 102, :_reduce_159, - 4, 102, :_reduce_160, - 2, 102, :_reduce_161, - 3, 105, :_reduce_162, - 4, 105, :_reduce_163, - 2, 105, :_reduce_164, - 1, 146, :_reduce_165, - 3, 146, :_reduce_166, - 3, 147, :_reduce_167, - 1, 111, :_reduce_none, - 1, 111, :_reduce_none, - 1, 148, :_reduce_170, - 2, 149, :_reduce_171, - 1, 150, :_reduce_172, - 1, 152, :_reduce_173, - 1, 153, :_reduce_174, - 2, 151, :_reduce_175, - 1, 154, :_reduce_176, - 1, 155, :_reduce_177, - 2, 155, :_reduce_178, - 1, 110, :_reduce_179, - 1, 108, :_reduce_180, - 1, 109, :_reduce_181, - 1, 104, :_reduce_182, - 1, 103, :_reduce_183, - 1, 106, :_reduce_184, - 0, 112, :_reduce_none, + 3, 140, :_reduce_130, + 3, 140, :_reduce_131, + 6, 103, :_reduce_132, + 7, 104, :_reduce_133, + 1, 146, :_reduce_134, + 1, 145, :_reduce_none, + 1, 145, :_reduce_none, + 1, 147, :_reduce_none, + 2, 147, :_reduce_138, + 1, 148, :_reduce_none, + 1, 148, :_reduce_none, + 6, 105, :_reduce_141, + 5, 105, :_reduce_142, + 1, 149, :_reduce_143, + 3, 149, :_reduce_144, + 1, 151, :_reduce_145, + 1, 151, :_reduce_146, + 1, 151, :_reduce_147, + 1, 151, :_reduce_none, + 1, 152, :_reduce_149, + 3, 152, :_reduce_150, + 1, 150, :_reduce_none, + 2, 150, :_reduce_152, + 1, 143, :_reduce_153, + 1, 143, :_reduce_154, + 1, 144, :_reduce_155, + 2, 144, :_reduce_156, + 4, 144, :_reduce_157, + 1, 123, :_reduce_158, + 3, 123, :_reduce_159, + 3, 153, :_reduce_160, + 1, 153, :_reduce_161, + 1, 97, :_reduce_162, + 3, 106, :_reduce_163, + 4, 106, :_reduce_164, + 2, 106, :_reduce_165, + 3, 106, :_reduce_166, + 4, 106, :_reduce_167, + 2, 106, :_reduce_168, + 3, 109, :_reduce_169, + 4, 109, :_reduce_170, + 2, 109, :_reduce_171, + 1, 154, :_reduce_172, + 3, 154, :_reduce_173, + 3, 155, :_reduce_174, + 1, 116, :_reduce_none, + 1, 116, :_reduce_none, + 1, 156, :_reduce_177, + 2, 157, :_reduce_178, + 1, 158, :_reduce_179, + 1, 160, :_reduce_180, + 1, 161, :_reduce_181, + 2, 159, :_reduce_182, + 1, 162, :_reduce_183, + 1, 163, :_reduce_184, + 2, 163, :_reduce_185, 1, 112, :_reduce_186, - 0, 129, :_reduce_none, - 1, 129, :_reduce_none, - 1, 137, :_reduce_none, - 1, 137, :_reduce_none, - 1, 137, :_reduce_none, - 1, 137, :_reduce_none, - 1, 137, :_reduce_none, - 1, 137, :_reduce_none, - 1, 137, :_reduce_none, - 1, 137, :_reduce_none, - 1, 137, :_reduce_none, - 1, 137, :_reduce_none, - 1, 137, :_reduce_none, - 1, 137, :_reduce_none, - 1, 137, :_reduce_none, - 1, 137, :_reduce_none, - 0, 79, :_reduce_203 ] - -racc_reduce_n = 204 - -racc_shift_n = 355 + 1, 115, :_reduce_187, + 1, 113, :_reduce_188, + 1, 114, :_reduce_189, + 1, 108, :_reduce_190, + 1, 107, :_reduce_191, + 1, 110, :_reduce_192, + 0, 117, :_reduce_none, + 1, 117, :_reduce_194, + 0, 134, :_reduce_none, + 1, 134, :_reduce_none, + 1, 142, :_reduce_none, + 1, 142, :_reduce_none, + 1, 142, :_reduce_none, + 1, 142, :_reduce_none, + 1, 142, :_reduce_none, + 1, 142, :_reduce_none, + 1, 142, :_reduce_none, + 1, 142, :_reduce_none, + 1, 142, :_reduce_none, + 1, 142, :_reduce_none, + 1, 142, :_reduce_none, + 1, 142, :_reduce_none, + 1, 142, :_reduce_none, + 1, 142, :_reduce_none, + 0, 84, :_reduce_211 ] + +racc_reduce_n = 212 + +racc_shift_n = 368 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, - :LESSEQUAL => 17, - :NOTEQUAL => 18, - :DOT => 19, - :COLON => 20, - :LLCOLLECT => 21, - :RRCOLLECT => 22, - :QMARK => 23, - :LPAREN => 24, - :RPAREN => 25, - :ISEQUAL => 26, - :GREATEREQUAL => 27, - :GREATERTHAN => 28, - :LESSTHAN => 29, - :IF => 30, - :ELSE => 31, - :DEFINE => 32, - :ELSIF => 33, - :VARIABLE => 34, - :CLASS => 35, - :INHERITS => 36, - :NODE => 37, - :BOOLEAN => 38, - :NAME => 39, - :SEMIC => 40, - :CASE => 41, - :DEFAULT => 42, - :AT => 43, - :LCOLLECT => 44, - :RCOLLECT => 45, - :CLASSREF => 46, - :NOT => 47, - :OR => 48, - :AND => 49, - :UNDEF => 50, - :PARROW => 51, - :PLUS => 52, - :MINUS => 53, - :TIMES => 54, - :DIV => 55, - :LSHIFT => 56, - :RSHIFT => 57, - :UMINUS => 58, - :MATCH => 59, - :NOMATCH => 60, - :REGEX => 61, - :IN_EDGE => 62, - :OUT_EDGE => 63, - :IN_EDGE_SUB => 64, - :OUT_EDGE_SUB => 65, - :IN => 66, - :UNLESS => 67, - :PIPE => 68, - :SELBRACE => 69, - :LOW => 70, - :HIGH => 71, - :CALL => 72, - :MODULO => 73, - :TITLE_COLON => 74, - :CASE_COLON => 75 } - -racc_nt_base = 76 + :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, + :LOW => 74, + :HIGH => 75, + :CALL => 76, + :LISTSTART => 77, + :MODULO => 78, + :TITLE_COLON => 79, + :CASE_COLON => 80 } + +racc_nt_base = 81 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", "LOW", "HIGH", "CALL", + "LISTSTART", "MODULO", "TITLE_COLON", "CASE_COLON", "$start", "program", "statements", "nil", "syntactic_statements", "syntactic_statement", "any_expression", "relationship_expression", "resource_expression", "expression", "higher_precedence", "expressions", - "match_rvalue", "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", "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", "dqpre", "dqrval", "dqpost", "dqmid", "text_expression", "dqtail" ] Racc_debug_parser = false ##### State transition tables end ##### # reduce 0 omitted -module_eval(<<'.,.,', 'egrammar.ra', 57) +module_eval(<<'.,.,', 'egrammar.ra', 59) def _reduce_1(val, _values, result) - result = Factory.block_or_expression(*val[0]) + result = create_program(Factory.block_or_expression(*val[0])) result end .,., # reduce 2 omitted -module_eval(<<'.,.,', 'egrammar.ra', 62) +module_eval(<<'.,.,', 'egrammar.ra', 64) def _reduce_3(val, _values, result) result = transform_calls(val[0]) result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 68) +module_eval(<<'.,.,', 'egrammar.ra', 70) def _reduce_4(val, _values, result) result = [val[0]] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 69) +module_eval(<<'.,.,', 'egrammar.ra', 71) def _reduce_5(val, _values, result) result = val[0].push val[2] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 70) +module_eval(<<'.,.,', 'egrammar.ra', 72) def _reduce_6(val, _values, result) result = val[0].push val[1] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 74) +module_eval(<<'.,.,', 'egrammar.ra', 76) def _reduce_7(val, _values, result) result = val[0] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 75) +module_eval(<<'.,.,', 'egrammar.ra', 77) def _reduce_8(val, _values, result) result = aryfy(val[0]).push val[2] result end .,., # reduce 9 omitted -module_eval(<<'.,.,', 'egrammar.ra', 81) +module_eval(<<'.,.,', 'egrammar.ra', 83) def _reduce_10(val, _values, result) result = val[0] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 82) +module_eval(<<'.,.,', 'egrammar.ra', 84) def _reduce_11(val, _values, result) result = val[0].relop(val[1][:value], val[2]); loc result, val[1] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 83) +module_eval(<<'.,.,', 'egrammar.ra', 85) 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', 84) +module_eval(<<'.,.,', 'egrammar.ra', 86) 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', 85) +module_eval(<<'.,.,', 'egrammar.ra', 87) def _reduce_14(val, _values, result) result = val[0].relop(val[1][:value], val[2]); loc result, val[1] result end .,., # reduce 15 omitted -module_eval(<<'.,.,', 'egrammar.ra', 92) +module_eval(<<'.,.,', 'egrammar.ra', 94) def _reduce_16(val, _values, result) result = val[0][*val[2]] ; loc result, val[0], val[3] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 93) +module_eval(<<'.,.,', 'egrammar.ra', 95) def _reduce_17(val, _values, result) result = val[0].in val[2] ; loc result, val[1] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 94) +module_eval(<<'.,.,', 'egrammar.ra', 96) def _reduce_18(val, _values, result) result = val[0] =~ val[2] ; loc result, val[1] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 95) +module_eval(<<'.,.,', 'egrammar.ra', 97) def _reduce_19(val, _values, result) result = val[0].mne val[2] ; loc result, val[1] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 96) +module_eval(<<'.,.,', 'egrammar.ra', 98) def _reduce_20(val, _values, result) result = val[0] + val[2] ; loc result, val[1] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 97) +module_eval(<<'.,.,', 'egrammar.ra', 99) def _reduce_21(val, _values, result) result = val[0] - val[2] ; loc result, val[1] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 98) +module_eval(<<'.,.,', 'egrammar.ra', 100) def _reduce_22(val, _values, result) result = val[0] / val[2] ; loc result, val[1] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 99) +module_eval(<<'.,.,', 'egrammar.ra', 101) def _reduce_23(val, _values, result) result = val[0] * val[2] ; loc result, val[1] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 100) +module_eval(<<'.,.,', 'egrammar.ra', 102) def _reduce_24(val, _values, result) result = val[0] % val[2] ; loc result, val[1] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 101) +module_eval(<<'.,.,', 'egrammar.ra', 103) def _reduce_25(val, _values, result) result = val[0] << val[2] ; loc result, val[1] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 102) +module_eval(<<'.,.,', 'egrammar.ra', 104) def _reduce_26(val, _values, result) result = val[0] >> val[2] ; loc result, val[1] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 103) +module_eval(<<'.,.,', 'egrammar.ra', 105) def _reduce_27(val, _values, result) result = val[1].minus() ; loc result, val[0] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 104) +module_eval(<<'.,.,', 'egrammar.ra', 106) def _reduce_28(val, _values, result) result = val[0].ne val[2] ; loc result, val[1] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 105) +module_eval(<<'.,.,', 'egrammar.ra', 107) def _reduce_29(val, _values, result) result = val[0] == val[2] ; loc result, val[1] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 106) +module_eval(<<'.,.,', 'egrammar.ra', 108) def _reduce_30(val, _values, result) result = val[0] > val[2] ; loc result, val[1] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 107) +module_eval(<<'.,.,', 'egrammar.ra', 109) def _reduce_31(val, _values, result) result = val[0] >= val[2] ; loc result, val[1] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 108) +module_eval(<<'.,.,', 'egrammar.ra', 110) def _reduce_32(val, _values, result) result = val[0] < val[2] ; loc result, val[1] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 109) +module_eval(<<'.,.,', 'egrammar.ra', 111) def _reduce_33(val, _values, result) result = val[0] <= val[2] ; loc result, val[1] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 110) +module_eval(<<'.,.,', 'egrammar.ra', 112) def _reduce_34(val, _values, result) result = val[1].not ; loc result, val[0] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 111) +module_eval(<<'.,.,', 'egrammar.ra', 113) def _reduce_35(val, _values, result) result = val[0].and val[2] ; loc result, val[1] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 112) +module_eval(<<'.,.,', 'egrammar.ra', 114) def _reduce_36(val, _values, result) result = val[0].or val[2] ; loc result, val[1] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 113) +module_eval(<<'.,.,', 'egrammar.ra', 115) def _reduce_37(val, _values, result) result = val[0].set(val[2]) ; loc result, val[1] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 114) +module_eval(<<'.,.,', 'egrammar.ra', 116) def _reduce_38(val, _values, result) result = val[0].plus_set(val[2]) ; loc result, val[1] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 115) +module_eval(<<'.,.,', 'egrammar.ra', 117) def _reduce_39(val, _values, result) - result = val[0].select(*val[2]) ; loc result, val[0] + result = val[0].minus_set(val[2]); loc result, val[1] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 116) +module_eval(<<'.,.,', 'egrammar.ra', 118) def _reduce_40(val, _values, result) - result = val[1].paren() ; loc result, val[0] + result = val[0].select(*val[2]) ; loc result, val[0] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 124) +module_eval(<<'.,.,', 'egrammar.ra', 119) def _reduce_41(val, _values, result) - result = [val[0]] + result = val[1].paren() ; loc result, val[0] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 125) +module_eval(<<'.,.,', 'egrammar.ra', 127) def _reduce_42(val, _values, result) - result = val[0].push(val[2]) + result = [val[0]] result end .,., -# reduce 43 omitted +module_eval(<<'.,.,', 'egrammar.ra', 128) + def _reduce_43(val, _values, result) + result = val[0].push(val[2]) + result + end +.,., # reduce 44 omitted # 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 -module_eval(<<'.,.,', 'egrammar.ra', 155) - def _reduce_62(val, _values, result) +# reduce 62 omitted + +# reduce 63 omitted + +module_eval(<<'.,.,', 'egrammar.ra', 159) + def _reduce_64(val, _values, result) result = val[0] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 156) - def _reduce_63(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 160) + def _reduce_65(val, _values, result) result = val[0] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 164) - def _reduce_64(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 168) + def _reduce_66(val, _values, result) result = Factory.CALL_NAMED(val[0], true, val[2]) loc result, val[0], val[4] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 168) - def _reduce_65(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 172) + def _reduce_67(val, _values, result) result = Factory.CALL_NAMED(val[0], true, []) loc result, val[0], val[2] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 172) - def _reduce_66(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 176) + def _reduce_68(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', 177) - def _reduce_67(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 181) + def _reduce_69(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', 181) - def _reduce_68(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 185) + def _reduce_70(val, _values, result) result = val[0] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 186) - def _reduce_69(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 190) + def _reduce_71(val, _values, result) result = val[0] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 187) - def _reduce_70(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 191) + def _reduce_72(val, _values, result) result = val[0]; val[0].lambda = val[1] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 190) - def _reduce_71(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 194) + def _reduce_73(val, _values, result) result = Factory.CALL_METHOD(val[0], val[2]); loc result, val[1], val[3] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 191) - def _reduce_72(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 195) + def _reduce_74(val, _values, result) result = Factory.CALL_METHOD(val[0], []); loc result, val[1], val[3] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 192) - def _reduce_73(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 196) + def _reduce_75(val, _values, result) result = Factory.CALL_METHOD(val[0], []); loc result, val[0] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 197) - def _reduce_74(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 201) + def _reduce_76(val, _values, result) result = val[0].dot(Factory.fqn(val[2][:value])) loc result, val[1], val[2] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 209) - def _reduce_75(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 213) + def _reduce_77(val, _values, result) result = Factory.LAMBDA(val[0], val[1]) # loc result, val[1] # TODO result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 214) - def _reduce_76(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 218) + def _reduce_78(val, _values, result) result = val[1] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 215) - def _reduce_77(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 219) + def _reduce_79(val, _values, result) result = nil result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 219) - def _reduce_78(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 223) + def _reduce_80(val, _values, result) result = [] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 220) - def _reduce_79(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 224) + def _reduce_81(val, _values, result) result = val[1] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 230) - def _reduce_80(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 234) + def _reduce_82(val, _values, result) result = val[1] loc(result, val[0], val[1]) result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 237) - def _reduce_81(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 241) + def _reduce_83(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', 241) - def _reduce_82(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 245) + def _reduce_84(val, _values, result) result = Factory.IF(val[0], nil, val[3]) loc(result, val[0], (val[3] ? val[3] : val[2])) result end .,., -# reduce 83 omitted +# reduce 85 omitted -module_eval(<<'.,.,', 'egrammar.ra', 249) - def _reduce_84(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 253) + def _reduce_86(val, _values, result) result = val[1] loc(result, val[0], val[1]) result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 253) - def _reduce_85(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 257) + def _reduce_87(val, _values, result) result = Factory.block_or_expression(*val[2]) loc result, val[0], val[3] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 257) - def _reduce_86(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 261) + def _reduce_88(val, _values, result) result = nil # don't think a nop is needed here either result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 266) - def _reduce_87(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 270) + def _reduce_89(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', 270) - def _reduce_88(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 274) + def _reduce_90(val, _values, result) result = Factory.UNLESS(val[1], nil, nil) loc result, val[0], val[4] result end .,., -# reduce 89 omitted +# reduce 91 omitted -module_eval(<<'.,.,', 'egrammar.ra', 280) - def _reduce_90(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 284) + def _reduce_92(val, _values, result) result = Factory.block_or_expression(*val[2]) loc result, val[0], val[3] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 284) - def _reduce_91(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 288) + def _reduce_93(val, _values, result) result = nil # don't think a nop is needed here either result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 292) - def _reduce_92(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 296) + def _reduce_94(val, _values, result) result = Factory.CASE(val[1], *val[3]) loc result, val[0], val[4] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 298) - def _reduce_93(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 302) + def _reduce_95(val, _values, result) result = [val[0]] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 299) - def _reduce_94(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 303) + def _reduce_96(val, _values, result) result = val[0].push val[1] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 304) - def _reduce_95(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 308) + def _reduce_97(val, _values, result) result = Factory.WHEN(val[0], val[3]) loc result, val[1], val[4] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 308) - def _reduce_96(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 312) + def _reduce_98(val, _values, result) result = Factory.WHEN(val[0], nil) loc result, val[1], val[3] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 312) - def _reduce_97(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 316) + def _reduce_99(val, _values, result) result = val[0] result end .,., -# reduce 98 omitted +# reduce 100 omitted -module_eval(<<'.,.,', 'egrammar.ra', 323) - def _reduce_99(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 327) + def _reduce_101(val, _values, result) result = val[1] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 328) - def _reduce_100(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 332) + def _reduce_102(val, _values, result) result = [val[0]] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 329) - def _reduce_101(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 333) + def _reduce_103(val, _values, result) result = val[0].push val[2] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 334) - def _reduce_102(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 338) + def _reduce_104(val, _values, result) result = Factory.MAP(val[0], val[2]) ; loc result, val[1] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 350) - def _reduce_103(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 354) + def _reduce_105(val, _values, result) result = val[0] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 353) - def _reduce_104(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 357) + def _reduce_106(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 "A resource default can not be virtual or exported" + error val[1], "A resource default can not be virtual or exported" when :override - error "A resource override can not be virtual or exported" + error val[1], "A resource override can not be virtual or exported" else - error "Expression is not valid as a resource, resource-default, or resource-override" + 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', 368) - def _reduce_105(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 372) + def _reduce_107(val, _values, result) result = case Factory.resource_shape(val[1]) - when :resource, :class - error "Defaults are not virtualizable" - when :defaults - error "Defaults are not virtualizable" - when :override - error "Defaults are not virtualizable" + when :resource, :class, :defaults, :override + error val[1], "Defaults are not virtualizable" else - error "Expression is not valid as a resource, resource-default, or resource-override" + error val[1], "Expression is not valid as a resource, resource-default, or resource-override" end result end .,., module_eval(<<'.,.,', 'egrammar.ra', 380) - def _reduce_106(val, _values, result) + def _reduce_108(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 "A resource default can not specify a resource name" + error val[1], "A resource default can not specify a resource name" when :override - error "A resource override does not allow override of name of resource" + error val[1], "A resource override does not allow override of name of resource" else - error "Expression is not valid as a resource, resource-default, or resource-override" + 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', 393) - def _reduce_107(val, _values, result) + def _reduce_109(val, _values, result) result = case Factory.resource_shape(val[0]) when :resource, :class # This catches deprecated syntax. - error "All resource specifications require names" + error val[1], "All resource specifications require names" when :defaults Factory.RESOURCE_DEFAULTS(val[0], val[2]) when :override # This was only done for override in original - TODO shuld it be here at all Factory.RESOURCE_OVERRIDE(val[0], val[2]) else - error "Expression is not valid as a resource, resource-default, or resource-override" + 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', 408) - def _reduce_108(val, _values, result) + def _reduce_110(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', 413) - def _reduce_109(val, _values, result) + def _reduce_111(val, _values, result) result = Factory.RESOURCE_BODY(val[0], val[2]) result end .,., module_eval(<<'.,.,', 'egrammar.ra', 415) - def _reduce_110(val, _values, result) + def _reduce_112(val, _values, result) result = val[0] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 418) - def _reduce_111(val, _values, result) + def _reduce_113(val, _values, result) result = [val[0]] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 419) - def _reduce_112(val, _values, result) + def _reduce_114(val, _values, result) result = val[0].push val[2] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 424) - def _reduce_113(val, _values, result) + def _reduce_115(val, _values, result) result = :virtual result end .,., module_eval(<<'.,.,', 'egrammar.ra', 425) - def _reduce_114(val, _values, result) + def _reduce_116(val, _values, result) result = :exported result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 437) - def _reduce_115(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 426) + def _reduce_117(val, _values, result) + result = :exported + result + end +.,., + +module_eval(<<'.,.,', 'egrammar.ra', 438) + def _reduce_118(val, _values, result) result = Factory.COLLECT(val[0], val[1], val[3]) loc result, val[0], val[5] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 441) - def _reduce_116(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 442) + def _reduce_119(val, _values, result) result = Factory.COLLECT(val[0], val[1], []) loc result, val[0], val[1] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 446) - def _reduce_117(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 447) + def _reduce_120(val, _values, result) result = Factory.VIRTUAL_QUERY(val[1]) ; loc result, val[0], val[2] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 447) - def _reduce_118(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 448) + def _reduce_121(val, _values, result) result = Factory.EXPORTED_QUERY(val[1]) ; loc result, val[0], val[2] result end .,., -# reduce 119 omitted +# reduce 122 omitted -# reduce 120 omitted +# reduce 123 omitted -module_eval(<<'.,.,', 'egrammar.ra', 460) - def _reduce_121(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 461) + def _reduce_124(val, _values, result) result = [] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 461) - def _reduce_122(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 462) + def _reduce_125(val, _values, result) result = [val[0]] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 462) - def _reduce_123(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 463) + def _reduce_126(val, _values, result) result = val[0].push(val[2]) result end .,., -# reduce 124 omitted +# reduce 127 omitted -# reduce 125 omitted +# reduce 128 omitted -# reduce 126 omitted +# reduce 129 omitted -module_eval(<<'.,.,', 'egrammar.ra', 478) - def _reduce_127(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 479) + def _reduce_130(val, _values, result) result = Factory.ATTRIBUTE_OP(val[0][:value], :'=>', val[2]) loc result, val[0], val[2] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 482) - def _reduce_128(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 483) + def _reduce_131(val, _values, result) result = Factory.ATTRIBUTE_OP(val[0][:value], :'+>', val[2]) loc result, val[0], val[2] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 492) - def _reduce_129(val, _values, result) - result = Factory.DEFINITION(classname(val[1][:value]), val[2], val[4]) +module_eval(<<'.,.,', 'egrammar.ra', 493) + def _reduce_132(val, _values, result) + result = add_definition(Factory.DEFINITION(classname(val[1][:value]), val[2], val[4])) loc result, val[0], val[5] - @lexer.indefine = false - - result - end -.,., - -module_eval(<<'.,.,', 'egrammar.ra', 497) - def _reduce_130(val, _values, result) - result = Factory.DEFINITION(classname(val[1][:value]), val[2], nil) - loc result, val[0], val[4] - @lexer.indefine = false + # 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', 512) - def _reduce_131(val, _values, result) - @lexer.namepop - result = Factory.HOSTCLASS(classname(val[1][:value]), val[2], token_text(val[3]), val[5]) +module_eval(<<'.,.,', 'egrammar.ra', 507) + def _reduce_133(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', 517) - def _reduce_132(val, _values, result) - @lexer.namepop - result = Factory.HOSTCLASS(classname(val[1][:value]), val[2], token_text(val[3]), nil) - loc result, val[0], val[5] - + def _reduce_134(val, _values, result) + namestack(val[0][:value]) ; result = val[0] result end .,., -# reduce 133 omitted +# reduce 135 omitted -module_eval(<<'.,.,', 'egrammar.ra', 525) - def _reduce_134(val, _values, result) +# reduce 136 omitted + +# reduce 137 omitted + +module_eval(<<'.,.,', 'egrammar.ra', 526) + def _reduce_138(val, _values, result) result = val[1] result end .,., -# reduce 135 omitted +# reduce 139 omitted -# reduce 136 omitted +# reduce 140 omitted -module_eval(<<'.,.,', 'egrammar.ra', 542) - def _reduce_137(val, _values, result) - result = Factory.NODE(val[1], val[2], val[4]) +module_eval(<<'.,.,', 'egrammar.ra', 543) + def _reduce_141(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', 546) - def _reduce_138(val, _values, result) - result = Factory.NODE(val[1], val[2], nil) +module_eval(<<'.,.,', 'egrammar.ra', 547) + def _reduce_142(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', 556) - def _reduce_139(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 557) + def _reduce_143(val, _values, result) result = [result] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 557) - def _reduce_140(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 558) + def _reduce_144(val, _values, result) result = val[0].push(val[2]) result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 562) - def _reduce_141(val, _values, result) - result = Factory.fqn(val[0][:value]); loc result, val[0] +module_eval(<<'.,.,', 'egrammar.ra', 563) + def _reduce_145(val, _values, result) + result = val[0] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 563) - def _reduce_142(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 564) + def _reduce_146(val, _values, result) result = val[0] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 564) - def _reduce_143(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 565) + def _reduce_147(val, _values, result) result = Factory.literal(:default); loc result, val[0] result end .,., -# reduce 144 omitted +# reduce 148 omitted -# reduce 145 omitted +module_eval(<<'.,.,', 'egrammar.ra', 569) + def _reduce_149(val, _values, result) + result = Factory.literal(val[0][:value]); loc result, val[0] + result + end +.,., module_eval(<<'.,.,', 'egrammar.ra', 570) - def _reduce_146(val, _values, result) - result = val[1] + def _reduce_150(val, _values, result) + result = Factory.concat(val[0], '.', val[2][:value]); loc result, val[0], val[2] result end .,., +# reduce 151 omitted + module_eval(<<'.,.,', 'egrammar.ra', 575) - def _reduce_147(val, _values, result) - result = val[0] + def _reduce_152(val, _values, result) + result = val[1] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 576) - def _reduce_148(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 581) + def _reduce_153(val, _values, result) result = val[0] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 580) - def _reduce_149(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 582) + def _reduce_154(val, _values, result) + error val[0], "'class' is not a valid classname" + result + end +.,., + +module_eval(<<'.,.,', 'egrammar.ra', 586) + def _reduce_155(val, _values, result) result = [] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 581) - def _reduce_150(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 587) + def _reduce_156(val, _values, result) result = [] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 582) - def _reduce_151(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 588) + def _reduce_157(val, _values, result) result = val[1] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 586) - def _reduce_152(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 592) + def _reduce_158(val, _values, result) result = [val[0]] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 587) - def _reduce_153(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 593) + def _reduce_159(val, _values, result) result = val[0].push(val[2]) result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 591) - def _reduce_154(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 597) + def _reduce_160(val, _values, result) result = Factory.PARAM(val[0][:value], val[2]) ; loc result, val[0] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 592) - def _reduce_155(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 598) + def _reduce_161(val, _values, result) result = Factory.PARAM(val[0][:value]); loc result, val[0] result end .,., -# reduce 156 omitted +module_eval(<<'.,.,', 'egrammar.ra', 611) + def _reduce_162(val, _values, result) + result = Factory.fqn(val[0][:value]).var ; loc result, val[0] + result + end +.,., -# reduce 157 omitted +module_eval(<<'.,.,', 'egrammar.ra', 617) + def _reduce_163(val, _values, result) + result = Factory.LIST(val[1]); loc result, val[0], val[2] + result + end +.,., -module_eval(<<'.,.,', 'egrammar.ra', 605) - def _reduce_158(val, _values, result) - result = Factory.fqn(val[0][:value]).var ; loc result, val[0] +module_eval(<<'.,.,', 'egrammar.ra', 618) + def _reduce_164(val, _values, result) + result = Factory.LIST(val[1]); loc result, val[0], val[3] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 611) - def _reduce_159(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 619) + def _reduce_165(val, _values, result) + result = Factory.literal([]) ; loc result, val[0] + result + end +.,., + +module_eval(<<'.,.,', 'egrammar.ra', 620) + def _reduce_166(val, _values, result) result = Factory.LIST(val[1]); loc result, val[0], val[2] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 612) - def _reduce_160(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 621) + def _reduce_167(val, _values, result) result = Factory.LIST(val[1]); loc result, val[0], val[3] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 613) - def _reduce_161(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 622) + def _reduce_168(val, _values, result) result = Factory.literal([]) ; loc result, val[0] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 616) - def _reduce_162(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 625) + def _reduce_169(val, _values, result) result = Factory.HASH(val[1]); loc result, val[0], val[2] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 617) - def _reduce_163(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 626) + def _reduce_170(val, _values, result) result = Factory.HASH(val[1]); loc result, val[0], val[3] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 618) - def _reduce_164(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 627) + def _reduce_171(val, _values, result) result = Factory.literal({}) ; loc result, val[0], val[3] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 621) - def _reduce_165(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 630) + def _reduce_172(val, _values, result) result = [val[0]] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 622) - def _reduce_166(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 631) + def _reduce_173(val, _values, result) result = val[0].push val[2] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 625) - def _reduce_167(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 634) + def _reduce_174(val, _values, result) result = Factory.KEY_ENTRY(val[0], val[2]); loc result, val[1] result end .,., -# reduce 168 omitted +# reduce 175 omitted -# reduce 169 omitted +# reduce 176 omitted -module_eval(<<'.,.,', 'egrammar.ra', 631) - def _reduce_170(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 640) + def _reduce_177(val, _values, result) result = Factory.literal(val[0][:value]) ; loc result, val[0] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 632) - def _reduce_171(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 641) + def _reduce_178(val, _values, result) result = Factory.string(val[0], *val[1]) ; loc result, val[0], val[1][-1] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 633) - def _reduce_172(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 642) + def _reduce_179(val, _values, result) result = Factory.literal(val[0][:value]); loc result, val[0] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 634) - def _reduce_173(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 643) + def _reduce_180(val, _values, result) result = Factory.literal(val[0][:value]); loc result, val[0] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 635) - def _reduce_174(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 644) + def _reduce_181(val, _values, result) result = Factory.literal(val[0][:value]); loc result, val[0] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 636) - def _reduce_175(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 645) + def _reduce_182(val, _values, result) result = [val[0]] + val[1] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 637) - def _reduce_176(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 646) + def _reduce_183(val, _values, result) result = Factory.TEXT(val[0]) result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 640) - def _reduce_177(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 649) + def _reduce_184(val, _values, result) result = [val[0]] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 641) - def _reduce_178(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 650) + def _reduce_185(val, _values, result) result = [val[0]] + val[1] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 643) - def _reduce_179(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 652) + def _reduce_186(val, _values, result) + result = Factory.NUMBER(val[0][:value]) ; loc result, val[0] + result + end +.,., + +module_eval(<<'.,.,', 'egrammar.ra', 653) + def _reduce_187(val, _values, result) result = Factory.QNAME_OR_NUMBER(val[0][:value]) ; loc result, val[0] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 644) - def _reduce_180(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 654) + def _reduce_188(val, _values, result) result = Factory.QREF(val[0][:value]) ; loc result, val[0] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 645) - def _reduce_181(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 655) + def _reduce_189(val, _values, result) result = Factory.literal(:undef); loc result, val[0] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 646) - def _reduce_182(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 656) + def _reduce_190(val, _values, result) result = Factory.literal(:default); loc result, val[0] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 651) - def _reduce_183(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 661) + def _reduce_191(val, _values, result) result = Factory.literal(val[0][:value]) ; loc result, val[0] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 654) - def _reduce_184(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 664) + def _reduce_192(val, _values, result) result = Factory.literal(val[0][:value]); loc result, val[0] result end .,., -# reduce 185 omitted +# reduce 193 omitted -module_eval(<<'.,.,', 'egrammar.ra', 660) - def _reduce_186(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 670) + def _reduce_194(val, _values, result) result = nil result end .,., -# reduce 187 omitted - -# reduce 188 omitted - -# reduce 189 omitted - -# reduce 190 omitted - -# reduce 191 omitted - -# reduce 192 omitted - -# reduce 193 omitted - -# reduce 194 omitted - # reduce 195 omitted # reduce 196 omitted # reduce 197 omitted # reduce 198 omitted # reduce 199 omitted # reduce 200 omitted # reduce 201 omitted # reduce 202 omitted -module_eval(<<'.,.,', 'egrammar.ra', 683) - def _reduce_203(val, _values, result) +# reduce 203 omitted + +# reduce 204 omitted + +# reduce 205 omitted + +# reduce 206 omitted + +# reduce 207 omitted + +# reduce 208 omitted + +# reduce 209 omitted + +# reduce 210 omitted + +module_eval(<<'.,.,', 'egrammar.ra', 693) + def _reduce_211(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/epp_parser.rb b/lib/puppet/pops/parser/epp_parser.rb new file mode 100644 index 000000000..15dcd4fe5 --- /dev/null +++ b/lib/puppet/pops/parser/epp_parser.rb @@ -0,0 +1,52 @@ +# The EppParser is a specialized Puppet Parser that starts parsing in Epp Text mode +class Puppet::Pops::Parser::EppParser < Puppet::Pops::Parser::Parser + + # Initializes the epp parser support by creating a new instance of {Puppet::Pops::Parser::Lexer} + # configured to start in Epp Lexing mode. + # @return [void] + # + def initvars + self.lexer = Puppet::Pops::Parser::Lexer.new({:mode => :epp}) + end + + # Parses a file expected to contain epp text/DSL logic. + def parse_file(file) + unless FileTest.exist?(file) + unless file =~ /\.epp$/ + file = file + ".epp" + end + end + @lexer.file = file + _parse() + end + + # Performs the parsing and returns the resulting model. + # The lexer holds state, and this is setup with {#parse_string}, or {#parse_file}. + # + # TODO: Drop support for parsing a ruby file this way (should be done where it is decided + # which file to load/run (i.e. loaders), and initial file to run + # TODO: deal with options containing origin (i.e. parsing a string from externally known location). + # TODO: should return the model, not a Hostclass + # + # @api private + # + def _parse() + begin + @yydebug = false + main = yyparse(@lexer,:scan_epp) + # #Commented out now because this hides problems in the racc grammar while developing + # # TODO include this when test coverage is good enough. + # rescue Puppet::ParseError => except + # except.line ||= @lexer.line + # except.file ||= @lexer.file + # except.pos ||= @lexer.pos + # raise except + # rescue => except + # raise Puppet::ParseError.new(except.message, @lexer.file, @lexer.line, @lexer.pos, except) + end + main.record_origin(@lexer.file) if main + return main + ensure + @lexer.clear + end +end diff --git a/lib/puppet/pops/parser/epp_support.rb b/lib/puppet/pops/parser/epp_support.rb new file mode 100644 index 000000000..3d1013690 --- /dev/null +++ b/lib/puppet/pops/parser/epp_support.rb @@ -0,0 +1,244 @@ +# This module is an integral part of the Lexer. +# It handles scanning of EPP (Embedded Puppet), a form of string/expression interpolation similar to ERB. +# +require 'strscan' +module Puppet::Pops::Parser::EppSupport + + TOKEN_RENDER_STRING = [:RENDER_STRING, nil, 0] + TOKEN_RENDER_EXPR = [:RENDER_EXPR, nil, 0] + + # 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_epp + result = [] + scan_epp {|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 / termporary data + # and to only convert the lexer's internal tokens on demand. It is slightly mroe 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 memebers (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_epp + # 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 "Internal Error: No string or file given to lexer to process." unless scn + + ctx[:epp_mode] = :text + interpolate_epp + + # 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 + if ctx[:epp_position] + lex_error("Unbalanced epp tag, reached without closing tag.", ctx[:epp_position]) + end + + # Signals end of input + yield [false, false] + end + + def interpolate_epp(skip_leading=false) + scn = @scanner + ctx = @lexing_context + eppscanner = EppScanner.new(scn) + before = scn.pos + s = eppscanner.scan(skip_leading) + + case eppscanner.mode + when :text + # Should be at end of scan, or something is terribly wrong + lex_error("Internal error: template scanner returns text mode and is not and end of input") unless @scanner.eos? + if s + # s may be nil if scanned text ends with an epp tag (i.e. no trailing text). + enqueue_completed([:RENDER_STRING, s, scn.pos - before], before) + end + ctx[:epp_open_position] = nil + # do nothing else, scanner is at the end + + when :error + lex_error(eppscanner.message()) + + when :epp + # It is meaningless to render empty string segments, and it is harmful to do this at + # the start of the scan as it prevents specification of parameters with <%- ($x, $y) -%> + # + if s && s.length > 0 + enqueue_completed([:RENDER_STRING, s, scn.pos - before], before) + end + # switch epp_mode to general (embedded) pp logic (non rendered result) + ctx[:epp_mode] = :epp + ctx[:epp_open_position] = scn.pos + + when :expr + # It is meaningless to render an empty string segment + if s && s.length > 0 + enqueue_completed([:RENDER_STRING, s, scn.pos - before], before) + end + enqueue_completed(TOKEN_RENDER_EXPR, before) + # switch mode to "epp expr interpolation" + ctx[:epp_mode] = :expr + ctx[:epp_open_position] = scn.pos + else + lex_error("Internal Error, Unknown mode #{eppscanner.mode} returned by template scanner") + end + nil + end + + # A scanner specialized in processing text with embedded EPP (Embedded Puppet) tags. + # The scanner is initialized with a StringScanner which it mutates as scanning takes place. + # The intent is to use one instance of EppScanner per wanted scan, and this instance represents + # the state after the scan. + # + # @example Sample usage + # a = "some text <% pp code %> some more text" + # scan = StringScanner.new(a) + # eppscan = EppScanner.new(scan) + # str = eppscan.scan + # eppscan.mode # => :epp + # eppscan.lines # => 0 + # eppscan + # + # The scanner supports + # * scanning text until <%, <%-, <%= + # * while scanning text: + # * tokens <%% and %%> are translated to <% and %> respetively and is returned as text. + # * tokens <%# and %> (or ending with -%>) and the enclosed text is a comment and is not included in the returned text + # * text following a comment that ends with -%> gets trailing whitespace (up to and including a line break) trimmed + # and this whitespace is not included in the returned text. + # * The continuation {#mode} is set to one of: + # * `:epp` - for a <% token + # * `:expr` - for a <%= token + # * `:text` - when there was no continuation mode (e.g. when input ends with text) + # * ':error` - if the tokens are unbalanced (reaching the end without a closing matching token). An error message + # is then also available via the method {#message}. + # + # Note that the intent is to use this specialized scanner to scan the text parts, when continuation mode is `:epp` or `:expr` + # the pp lexer should advance scanning (using the string scanner) until it reaches and consumes a `-%>` or '%>´ token. If it + # finds a `-%> token it should pass this on as a `skip_leading` parameter when it performs the next {#scan}. + # + class EppScanner + # The original scanner used by the lexer/container using EppScanner + attr_reader :scanner + + # The resulting mode after the scan. + # The mode is one of `:text` (the initial mode), `:epp` embedded code (no output), `:expr` (embedded + # expression), or `:error` + # + attr_reader :mode + + # An error message if `mode == :error`, `nil` otherwise. + attr_reader :message + + # If the first scan should skip leading whitespace (typically detected by the pp lexer when the + # pp mode end-token is found (i.e. `-%>`) and then passed on to the scanner. + # + attr_reader :skip_leading + + # Creates an EppScanner based on a StringScanner that represents the state where EppScanner should start scanning. + # The given scanner will be mutated (i.e. position moved) to reflect the EppScanner's end state after a scan. + # + def initialize(scanner) + @scanner = scanner + end + + # Scans from the current position in the configured scanner, advances this scanner's position until the end + # of the input, or to the first position after a mode switching token (`<%`, `<%-` or `<%=`). Number of processed + # lines and continuation mode can be obtained via {#lines}, and {#mode}. + # + # @return [String, nil] the scanned and processed text, or nil if at the end of the input. + # + def scan(skip_leading=false) + @mode = :text + @skip_leading = skip_leading + + return nil if scanner.eos? + s = "" + until scanner.eos? + part = @scanner.scan_until(/(<%)|\z/) + if @skip_leading + part.gsub!(/^[ \t]*\r?\n?/,'') + @skip_leading = false + end + # The spec for %%> is to transform it into a literal %>. This is done here, as %%> otherwise would go + # undetected in text mode. (i.e. it is not really necessary to escape %> with %%> in text mode unless + # adding checks stating that a literal %> is illegal in text (unbalanced). + # + part.gsub!(/%%>/, '%>') + s += part + case @scanner.peek(1) + when "" + # at the end + # if s ends with <% then this is an error (unbalanced <% %>) + if s.end_with? "<%" + @mode = :error + @message = "Unbalanced embedded expression - opening <% and reaching end of input" + else + mode = :epp + end + return s + + when "-" + # trim trailing whitespace on same line from accumulated s + # return text and signal switch to pp mode + @scanner.getch # drop the - + s.gsub!(/\r?\n?[ \t]*<%\z/, '') + @mode = :epp + return s + + when "%" + # verbatim text + # keep the scanned <%, and continue scanning after skipping one % + # (i.e. do nothing here) + @scanner.getch # drop the % to get a literal <% in the output + + when "=" + # expression + # return text and signal switch to expression mode + # drop the scanned <%, and skip past -%>, or %>, but also skip %%> + @scanner.getch # drop the = + s.slice!(-2..-1) + @mode = :expr + return s + + when "#" + # template comment + # drop the scanned <%, and skip past -%>, or %>, but also skip %%> + s.slice!(-2..-1) + + # unless there is an immediate termination i.e. <%#%> scan for the next %> that is not + # preceded by a % (i.e. skip %%>) + part = scanner.scan_until(/[^%]%>/) + unless part + @message = "Reaching end after opening <%# without seeing %>" + @mode = :error + return s + end + @skip_leading = true if part.end_with?("-%>") + # Continue scanning for more text + + else + # Switch to pp after having removed the <% + s.slice!(-2..-1) + @mode = :epp + return s + end + end + end + end + +end \ No newline at end of file diff --git a/lib/puppet/pops/parser/evaluating_parser.rb b/lib/puppet/pops/parser/evaluating_parser.rb index 3a6cdbb66..fba96cbc9 100644 --- a/lib/puppet/pops/parser/evaluating_parser.rb +++ b/lib/puppet/pops/parser/evaluating_parser.rb @@ -1,162 +1,185 @@ # Does not support "import" and parsing ruby files # class Puppet::Pops::Parser::EvaluatingParser def initialize() @parser = Puppet::Pops::Parser::Parser.new() end def parse_string(s, file_source = 'unknown') @file_source = file_source clear() # Handling of syntax error can be much improved (in general), now it bails out of the parser # and does not have as rich information (when parsing a string), need to update it with the file source # (ideally, a syntax error should be entered as an issue, and not just thrown - but that is a general problem # and an improvement that can be made in the eparser (rather than here). # Also a possible improvement (if the YAML parser returns positions) is to provide correct output of position. # begin assert_and_report(@parser.parse_string(s)) rescue Puppet::ParseError => e - e.file = @file_source unless e.file + # TODO: This is not quite right, why does not the exception have the correct file? + e.file = @file_source unless e.file.is_a?(String) && !e.file.empty? raise e end end def parse_file(file) @file_source = file clear() assert_and_report(@parser.parse_file(file)) end def evaluate_string(scope, s, file_source='unknown') evaluate(scope, parse_string(s, file_source)) end def evaluate_file(file) evaluate(parse_file(file)) end def clear() @acceptor = nil end def evaluate(scope, model) return nil unless model ast = Puppet::Pops::Model::AstTransformer.new(@file_source, nil).transform(model) return nil unless ast ast.safeevaluate(scope) end + def validate(parse_result) + resulting_acceptor = acceptor() + validator(resulting_acceptor).validate(parse_result) + resulting_acceptor + end + def acceptor() - @acceptor ||= Puppet::Pops::Validation::Acceptor.new - @acceptor + Puppet::Pops::Validation::Acceptor.new end - def validator() - @validator ||= Puppet::Pops::Validation::ValidatorFactory_3_1.new().validator(acceptor) + def validator(acceptor) + Puppet::Pops::Validation::ValidatorFactory_3_1.new().validator(acceptor) end def assert_and_report(parse_result) return nil unless parse_result # make sure the result has an origin (if parsed from a string) unless Puppet::Pops::Adapters::OriginAdapter.get(parse_result.model) Puppet::Pops::Adapters::OriginAdapter.adapt(parse_result.model).origin = @file_source end - validator.validate(parse_result) + validation_result = validate(parse_result) max_errors = Puppet[:max_errors] max_warnings = Puppet[:max_warnings] + 1 max_deprecations = Puppet[:max_deprecations] + 1 # If there are warnings output them - warnings = acceptor.warnings + warnings = validation_result.warnings if warnings.size > 0 formatter = Puppet::Pops::Validation::DiagnosticFormatterPuppetStyle.new emitted_w = 0 emitted_dw = 0 - acceptor.warnings.each {|w| + validation_result.warnings.each {|w| if w.severity == :deprecation # Do *not* call Puppet.deprecation_warning it is for internal deprecation, not # deprecation of constructs in manifests! (It is not designed for that purpose even if # used throughout the code base). # Puppet.warning(formatter.format(w)) if emitted_dw < max_deprecations emitted_dw += 1 else Puppet.warning(formatter.format(w)) if emitted_w < max_warnings emitted_w += 1 end break if emitted_w > max_warnings && emitted_dw > max_deprecations # but only then } end # If there were errors, report the first found. Use a puppet style formatter. - errors = acceptor.errors + errors = validation_result.errors if errors.size > 0 formatter = Puppet::Pops::Validation::DiagnosticFormatterPuppetStyle.new if errors.size == 1 || max_errors <= 1 # raise immediately - require 'debugger'; debugger raise Puppet::ParseError.new(formatter.format(errors[0])) end emitted = 0 errors.each do |e| Puppet.err(formatter.format(e)) emitted += 1 break if emitted >= max_errors end warnings_message = warnings.size > 0 ? ", and #{warnings.size} warnings" : "" giving_up_message = "Found #{errors.size} errors#{warnings_message}. Giving up" exception = Puppet::ParseError.new(giving_up_message) exception.file = errors[0].file raise exception end parse_result end def quote(x) self.class.quote(x) end # Translates an already parsed string that contains control characters, quotes # and backslashes into a quoted string where all such constructs have been escaped. # Parsing the return value of this method using the puppet parser should yield # exactly the same string as the argument passed to this method # # The method makes an exception for the two character sequences \$ and \s. They # will not be escaped since they have a special meaning in puppet syntax. # + # TODO: Handle \uXXXX characters ?? + # # @param x [String] The string to quote and "unparse" # @return [String] The quoted string # def self.quote(x) escaped = '"' p = nil x.each_char do |c| case p when nil # do nothing when "\t" escaped << '\\t' when "\n" escaped << '\\n' when "\f" escaped << '\\f' # TODO: \cx is a range of characters - skip for now # when "\c" # escaped << '\\c' when '"' escaped << '\\"' when '\\' escaped << if c == '$' || c == 's'; p; else '\\\\'; end # don't escape \ when followed by s or $ else escaped << p end p = c end escaped << p unless p.nil? escaped << '"' end + + # This is a temporary solution to making it possible to use the new evaluator. The main class + # will eventually have this behavior instead of using transformation to Puppet 3.x AST + class Transitional < Puppet::Pops::Parser::EvaluatingParser + + def evaluate(scope, model) + return nil unless model + @@evaluator ||= Puppet::Pops::Evaluator::EvaluatorImpl.new() + @@evaluator.evaluate(model, scope) + end + + def validator(acceptor) + Puppet::Pops::Validation::ValidatorFactory_4_0.new().validator(acceptor) + end + + end end diff --git a/lib/puppet/pops/parser/grammar.ra b/lib/puppet/pops/parser/grammar.ra deleted file mode 100644 index 7352bdbfb..000000000 --- a/lib/puppet/pops/parser/grammar.ra +++ /dev/null @@ -1,746 +0,0 @@ -# vim: syntax=ruby - -# Parser using the Pops model -# This grammar is a half step between the current 3.1. grammar and egrammar. -# FIXME! Keep as reference until egrammar is proven to work. - -class Puppet::Pops::Impl::Parser::Parser - -token STRING DQPRE DQMID DQPOST -token LBRACK RBRACK LBRACE RBRACE SYMBOL FARROW COMMA TRUE -token FALSE EQUALS APPENDS LESSEQUAL NOTEQUAL DOT COLON LLCOLLECT RRCOLLECT -token QMARK LPAREN RPAREN ISEQUAL GREATEREQUAL GREATERTHAN LESSTHAN -token IF ELSE IMPORT DEFINE ELSIF VARIABLE CLASS INHERITS NODE BOOLEAN -token NAME SEMIC CASE DEFAULT AT 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 - -prechigh - left DOT -# left LBRACE -# left LCOLLECT LLCOLLECT - right NOT - nonassoc UMINUS - left IN MATCH NOMATCH - left TIMES DIV - left MINUS PLUS - left LSHIFT RSHIFT - left NOTEQUAL ISEQUAL - left GREATEREQUAL GREATERTHAN LESSTHAN LESSEQUAL - left AND - left OR -# left IN_EDGE OUT_EDGE IN_EDGE_SUB OUT_EDGE_SUB -preclow - -rule -# Produces [Model::BlockExpression, Model::Expression, nil] depending on multiple statements, single statement or empty -program - : statements { result = Factory.block_or_expression(*val[0]) } - | nil - -# 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 -statements - : statement { result = [val[0]]} - | statements statement { result = val[0].push val[1] } - -# Removed validation construct regarding "top level statements" as it did not seem to catch all problems -# and relied on a "top-level-ness" encoded in the abstract syntax tree objects -# -# The main list of valid statements -# Produces Model::Expression -# -statement - : resource - | virtual_resource - | collection - | assignment - | casestatement - | if_expression - | unless_expression - | import - | call_named_function - | definition - | hostclass - | nodedef - | resource_override - | append - | relationship - | call_method_with_lambda - -keyword - : AND - | CASE - | CLASS - | DEFAULT - | DEFINE - | ELSE - | ELSIF - | IF - | IN - | IMPORT - | INHERITS - | NODE - | OR - | UNDEF - | UNLESS - -# Produces Model::RelationshipExpression -relationship - : relationship_side edge relationship_side { result = val[0].relop(val[1][:value], val[2]); loc result, val[1] } - | relationship edge relationship_side { result = val[0].relop(val[1][:value], val[2]); loc result, val[1] } - -# Produces Model::Expression -relationship_side - : resource - | resourceref - | collection - | variable - | quotedtext - | selector - | casestatement - | hasharrayaccesses - -# Produces String -edge - : IN_EDGE - | OUT_EDGE - | IN_EDGE_SUB - | OUT_EDGE_SUB - -# Produces Model::CallNamedFunctionExpression -call_named_function - : NAME LPAREN expressions RPAREN { result = Factory.CALL_NAMED(val[0][:value], false, val[2]) ; loc result, val[0], val[3] } - | NAME LPAREN expressions COMMA RPAREN { result = Factory.CALL_NAMED(val[0][:value], false, val[2]) ; loc result, val[0], val[4] } - | NAME LPAREN RPAREN { result = Factory.CALL_NAMED(val[0][:value], false, []) ; loc result, val[0], val[2] } - | NAME func_call_args { result = Factory.CALL_NAMED(val[0][:value], false, val[1]) ; loc result, val[0] } - -call_method_with_lambda - : call_method { result = val[0] } - | call_method lambda { result = val[0]; val[0].lambda = val[1] } - -call_method - : 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 { result = Factory.CALL_METHOD(val[0], []); loc result, val[0] } - -named_access - : named_access_lval DOT NAME { - result = val[0].dot(Factory.fqn(val[2][:value])) - loc result, val[1], val[2] - } - -# Obviously not ideal, it is not possible to use literal array or hash as lhs -# These must be assigned to a variable - this is also an issue in other places -# -named_access_lval - : variable - | hasharrayaccesses - | selector - | quotedtext - | call_named_rval_function - -lambda - : LAMBDA lambda_parameter_list statements RBRACE { - result = Factory.LAMBDA(val[1], val[2]) - loc result, val[0], val[3] - } - | LAMBDA lambda_parameter_list RBRACE { - result = Factory.LAMBDA(val[1], nil) - loc result, val[0], val[2] - } -# Produces Array -lambda_parameter_list - : PIPE PIPE { result = [] } - | PIPE parameters endcomma PIPE { result = val[1] } - -# Produces Array -func_call_args - : rvalue { result = [val[0]] } - | func_call_args COMMA rvalue { result = val[0].push(val[2]) } - -# Produces Array -expressions - : expression { result = [val[0]] } - | expressions comma expression { result = val[0].push(val[2]) } - - -# Produces [Model::ResourceExpression, Model::ResourceDefaultsExpression] -resource - : classname LBRACE resourceinstances endsemi RBRACE { - result = Factory.RESOURCE(Factory.fqn(token_text(val[0])), val[2]) - loc result, val[0], val[4] - } - | classname LBRACE attribute_operations endcomma RBRACE { - # This is a deprecated syntax. - # It also fails hard - TODO: create model and validate this case - error "All resource specifications require names" - } - | type LBRACE attribute_operations endcomma RBRACE { - # a defaults setting for a type - result = Factory.RESOURCE_DEFAULTS(val[0], val[2]) - loc result, val[0], val[4] - } - -# Override a value set elsewhere in the configuration. -# Produces Model::ResourceOverrideExpression -resource_override - : resourceref LBRACE attribute_operations endcomma RBRACE { - @lexer.commentpop - result = Factory.RESOURCE_OVERRIDE(val[0], val[2]) - loc result, val[0], val[4] - } - -# Exported and virtual resources; these don't get sent to the client -# unless they get collected elsewhere in the db. -# The original had validation here; checking if storeconfigs is on; this is moved to a validation step -# Also, validation was performed if an attempt was made to virtualize or export a resource defaults -# this is also now deferred to validation -# Produces [Model::ResourceExpression, Model::ResourceDefaultsExpression] -virtual_resource - : at resource { - val[1].form = val[0] # :virtual, :exported, (or :regular) - result = val[1] - } - -# Produces Symbol corresponding to resource form -at - : AT { result = :virtual } - | AT AT { result = :exported } - -# A collection statement. Currently supports no arguments at all, but eventually -# will, I assume. -# -# Produces Model::CollectExpression -# -collection - : type collect_query LBRACE attribute_operations endcomma RBRACE { - @lexer.commentpop - result = Factory.COLLECT(val[0].value.downcase, val[1], val[3]) - loc result, val[0], val[5] - } - | type collect_query { - result = Factory.COLLECT(val[0].value.downcase, 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] } - -# ORIGINAL COMMENT: A mini-language for handling collection comparisons. This is organized -# to avoid the need for precedence indications. -# (New implementation is slightly different; and when finished, it may be possible to streamline the -# grammar - the difference is mostly in evaluation, not in grammar) -# -optional_query - : nil - | query - -# ORIGINAL: Had a weird list structure where AND and OR where at the same level, and hence, there was the -# need to keep track of where parenthesis were (to get order correct). -# -# This is now not needed as AND has higher precedence than OR, and parenthesis are low in precedence - -query - : predicate_lval ISEQUAL expression { result = (val[0] == val[2]) ; loc result, val[1] } - | predicate_lval NOTEQUAL expression { result = (val[0].ne(val[2])) ; loc result, val[1] } - | LPAREN query RPAREN { result = val[1] } - | query AND query { result = val[0].and(val[2]) ; loc result, val[1] } - | query OR query { result = val[0].or(val[2]) ; loc result, val[1] } - - -# Produces Model::VariableExpression, or Model::QualifiedName -predicate_lval - : variable - | name - -resourceinst - : resourcename COLON attribute_operations endcomma { result = Factory.RESOURCE_BODY(val[0], val[2]) } - -resourceinstances - : resourceinst { result = [val[0]] } - | resourceinstances SEMIC resourceinst { result = val[0].push val[2] } - - -resourcename - : quotedtext - | name - | type - | selector - | variable - | array - | hasharrayaccesses - -# Assignment, only assignment to variable is legal, but parser builds expression for [] = anyway to -# enable a better error message -assignment - : VARIABLE EQUALS expression { result = Factory.var(Factory.fqn(val[0][:value])).set(val[2]) ; loc result, val[1] } - | hasharrayaccess EQUALS expression { result val[0].set(val[2]); loc result, val[1] } - -append - : VARIABLE APPENDS expression { result = Factory.var(val[0][:value]).plus_set(val[1]) ; loc result, val[1] } - -# Produces Array -attribute_operations - : { result = [] } - | attribute_operation { result = [val[0]] } - | attribute_operations COMMA attribute_operation { result = val[0].push(val[2]) } - -# Produces String -attribute_name - : NAME - | keyword - | BOOLEAN - -# Several grammar issues here: the addparam did not allow keyword and booleans as names. -# In this version, the wrong combinations are validated instead of producing syntax errors -# (Can give nicer error message +> is not applicable to...) -# WAT - Boolean as attribute name? -# 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] - } - -# Produces Model::CallNamedFunction -call_named_rval_function - : NAME LPAREN expressions RPAREN { result = Factory.CALL_NAMED(val[0][:value], true, val[2]) ; loc result, val[0], val[3] } - | NAME LPAREN RPAREN { result = Factory.CALL_NAMED(val[0][:value], true, []) ; loc result, val[0], val[2] } - -quotedtext - : STRING { result = Factory.literal(val[0][:value]) ; loc result, val[0] } - | 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] } -text_expression : expression { result = Factory.TEXT(val[0]) } - -dqrval - : text_expression dqtail { result = [val[0]] + val[1] } - -dqtail - : dqpost { result = [val[0]] } - | dqmid dqrval { result = [val[0]] + val[1] } - - -# Reference to Resource (future also reference to other instances of other types than Resources). -# First form (lower case name) is deprecated (deprecation message handled in validation). Note that -# this requires use of token NAME since a rule call to name causes shift reduce conflict with -# a function call NAME NAME (calling function with NAME as argument e.g. foo bar). -# -# Produces InstanceReference -resourceref - : NAME LBRACK expressions RBRACK { - # Would want to use rule name here, but can't (need a NAME with higher precedence), so must - # create a QualifiedName instance here for NAME - result = Factory.INSTANCE(Factory.QNAME_OR_NUMBER(val[0][:value]), val[2]); - loc result, val[0], val[2][-1] - } - | type LBRACK expressions RBRACK { - result = Factory.INSTANCE(val[0], val[2]); - loc result, val[0], val[2][-1] - } - -# Changed from Puppet 3x where there is no else part on unless -# -unless_expression - : UNLESS expression LBRACE statements RBRACE unless_else { - @lexer.commentpop - 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 { - @lexer.commentpop - 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 { - @lexer.commentpop - result = Factory.block_or_expression(*val[2]) - loc result, val[0], val[3] - } - | ELSE LBRACE RBRACE { - @lexer.commentpop - result = nil # don't think a nop is needed here either - } - -# Produces Model::IfExpression -if_expression - : IF if_expression_part { - result = val[1] - } - -# Produces Model::IfExpression -if_expression_part - : expression LBRACE statements RBRACE else { - @lexer.commentpop - 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_expression_part { result = val[1] } - | ELSE LBRACE statements RBRACE { - @lexer.commentpop - result = Factory.block_or_expression(*val[2]) - loc result, val[0], val[3] - } - | ELSE LBRACE RBRACE { - @lexer.commentpop - result = nil # don't think a nop is needed here either - } - -# Produces Model::Expression -expression - : rvalue - | hash - | expression IN expression { result = val[0].in val[2] ; loc result, val[1] } - | expression MATCH match_rvalue { result = val[0] =~ val[2] ; loc result, val[1] } - | expression NOMATCH match_rvalue { 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 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] } - | LPAREN expression RPAREN { result = val[1] ; } - | call_method_with_lambda - -match_rvalue - : regex - | quotedtext - -# Produces Model::CaseExpression -casestatement - : CASE expression LBRACE case_options RBRACE { - @lexer.commentpop - 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 - : case_values COLON LBRACE statements RBRACE { - @lexer.commentpop - result = Factory.WHEN(val[0], val[3]) - loc result, val[1], val[4] - } - | case_values COLON LBRACE RBRACE { - @lexer.commentpop - result = Factory.WHEN(val[0], nil) - loc result, val[1], val[3] - } - -# Produces Array mostly literals -case_values - : selectable { result = [val[0]] } - | case_values COMMA selectable { result = val[0].push val[2] } - -# Produces Model::SelectorExpression -selector - : selectable QMARK selector_entries { result = val[0].select(*val[2]) ; loc result, val[1] } - -# Produces Array -selector_entries - : selector_entry { result = [val[0]] } - | LBRACE selector_entry_list endcomma RBRACE { - @lexer.commentpop - 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 -selector_entry - : selectable FARROW rvalue { result = Factory.MAP(val[0], val[2]) ; loc result, val[1] } - -# Produces Model::Expression (most of the literals) -selectable - : name - | type - | quotedtext - | variable - | call_named_rval_function - | boolean - | undef - | hasharrayaccess - | default - | regex - - - -# Produces nil (noop) -import - : IMPORT strings { - error "Import not supported in this version of the parser", \ - :line => stmt.context[:line], :file => stmt.context[:file] - result = nil - } - -# IMPORT (T.B DEPRECATED IN PUPPET WHEN IT HAS BEEN FIGURED OUT HOW TO SUPPORT -# THE THINGS IMPORTS ARE USED FOR. -# BOLDLY DECIDED TO SKIP THIS COMPLETELY IN THIS IMPLEMENTATION - will trigger an error -# -# These are only used for importing, no interpolation -string - : STRING { result = [val[0][:value]] } - -strings - : string - | strings COMMA string { result = val[0].push val[2] } - -# Produces Model::Definition -definition - : DEFINE classname parameter_list LBRACE statements RBRACE { - @lexer.commentpop - result = Factory.DEFINITION(classname(val[1][:value]), val[2], val[4]) - loc result, val[0], val[5] - @lexer.indefine = false - } - | DEFINE classname parameter_list LBRACE RBRACE { - @lexer.commentpop - result = Factory.DEFINITION(classname(val[1][:value]), val[2], nil) - loc result, val[0], val[4] - @lexer.indefine = false - } - -# ORIGINAL COMMENT: Our class gets defined in the parent namespace, not our own. -# WAT ??! This is way odd; should get its complete name, classnames do not nest -# Seems like the call to classname makes use of the name scope -# (This is uneccesary, since the parent name is known when evaluating) -# -# Produces Model::HostClassDefinition -# -hostclass - : CLASS classname parameter_list classparent LBRACE statements RBRACE { - @lexer.commentpop - @lexer.namepop - result = Factory.HOSTCLASS(classname(val[1][:value]), val[2], token_text(val[3]), val[5]) - loc result, val[0], val[6] - } - | CLASS classname parameter_list classparent LBRACE RBRACE { - @lexer.commentpop - @lexer.namepop - result = Factory.HOSTCLASS(classname(val[1][:value]), val[2], token_text(val[3]), nil) - loc result, val[0], val[5] - } - -# Produces Model::NodeDefinition -nodedef - : NODE hostnames nodeparent LBRACE statements RBRACE { - @lexer.commentpop - result = Factory.NODE(val[1], val[2], val[4]) - loc result, val[0], val[5] - } - | NODE hostnames nodeparent LBRACE RBRACE { - @lexer.commentpop - result = Factory.NODE(val[1], val[2], nil) - loc result, val[0], val[4] - } - -# String result -classname - : NAME { result = val[0] } - | CLASS { result = val[0] } - -# 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 - : nodename { result = [result] } - | hostnames COMMA nodename { result = val[0].push(val[2]) } - -# Produces Model::LiteralExpression -# -nodename - : hostname - -# Produces a LiteralExpression (string, :default, or regexp) -hostname - : NAME { result = Factory.fqn(val[0][:value]); loc result, val[0] } - | STRING { result = Factory.literal(val[0][:value]); loc result, val[0] } - | DEFAULT { result = Factory.literal(:default); loc result, val[0] } - | regex - - -# 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] } - -# Produces Expression, since hostname is an Expression -nodeparent - : nil - | INHERITS hostname { result = val[1] } - -# 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 - -rvalue - : quotedtext - | name - | type - | boolean - | selector - | variable - | array - | hasharrayaccesses - | resourceref - | call_named_rval_function - | undef - -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] } - - -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 - : key FARROW expression { result = Factory.KEY_ENTRY(val[0], val[2]); loc result, val[1] } - -key - : NAME { result = Factory.literal(val[0][:value]) ; loc result, val[0] } - | quotedtext { result = val[0] } - -# NOTE: Limitation that LHS is a variable, means that it is not possible to do foo(10)[2] without -# using an intermediate variable -# -hasharrayaccess - : variable LBRACK expression RBRACK { result = val[0][val[2]]; loc result, val[0], val[3] } - -hasharrayaccesses - : hasharrayaccess - | hasharrayaccesses LBRACK expression RBRACK { result = val[0][val[2]] ; loc result, val[1], val[3] } - -# Produces Model::VariableExpression -variable : VARIABLE { result = Factory.fqn(val[0][:value]).var ; loc result, val[0] } -undef : UNDEF { result = Factory.literal(:undef); 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] } - -default - : DEFAULT { result = Factory.literal(:default); loc result, val[0] } - -boolean - # Assumes lexer produces a Boolean value for booleans, or this will go wrong (e.g. produce. LiteralString) - : BOOLEAN { result = Factory.literal(val[0][:value]) ; loc result, val[0] } - -regex - : REGEX { result = Factory.literal(val[0][:value]); loc result, val[0] } - -# ---Special markers & syntactic sugar - -# WAT !!!! this means array can be [1=>2=>3], func (1=>2=>3), and other retarded constructs -# TODO: Remove the FARROW (investigate if there is any validity) -comma - : FARROW - | COMMA - -endcomma - : # - | COMMA { result = nil } - -endsemi - : # - | SEMIC - -nil - : { result = nil} - -## Empty list - not really needed? TODO: Check if this can be removed -#empty_list -# : { result = [] } - -end - ----- header ---- -require 'puppet' -require 'puppet/util/loadedfile' -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/heredoc_support.rb b/lib/puppet/pops/parser/heredoc_support.rb new file mode 100644 index 000000000..eda606569 --- /dev/null +++ b/lib/puppet/pops/parser/heredoc_support.rb @@ -0,0 +1,190 @@ +module Puppet::Pops::Parser::HeredocSupport + + # Pattern for heredoc `@(endtag[:syntax][/escapes]) + # Produces groups for endtag (group 1), syntax (group 2), and escapes (group 3) + # + PATTERN_HEREDOC = %r{@\(([^:/\r\n\)]+)(?::[:blank:]*([a-z][a-zA-Z0-9_+]+)[:blank:]*)?(?:/((?:\w|[$])*)[:blank:]*)?\)} + + + def heredoc + scn = @scanner + ctx = @lexing_context + locator = @locator + before = scn.pos + + # scanner is at position before @( + # find end of the heredoc spec + str = scn.scan_until(/\)/) || lexer.lex_error("Unclosed parenthesis after '@(' followed by '#{followed_by}'") + pos_after_heredoc = scn.pos + + # Note: allows '+' as separator in syntax, but this needs validation as empty segments are not allowed + unless md = str.match(PATTERN_HEREDOC) + lex_error("Invalid syntax in heredoc expected @(endtag[:syntax][/escapes])") + end + endtag = md[1] + syntax = md[2] || '' + escapes = md[3] + + endtag.strip! + + # Is this a dq string style heredoc? (endtag enclosed in "") + if endtag =~ /^"(.*)"$/ + dqstring_style = true + endtag = $1.strip + end + + lexer.lex_error("Missing endtag in heredoc") unless endtag.length >= 1 + + resulting_escapes = [] + if escapes + escapes = "trnsuL$" if escapes.length < 1 + + escapes = escapes.split('') + unless escapes.length == escapes.uniq.length + lex_error("An escape char for @() may only appear once. Got '#{escapes.join(', ')}") + end + resulting_escapes = ["\\"] + escapes.each do |e| + case e + when "t", "r", "n", "s", "u", "$" + resulting_escapes << e + when "L" + resulting_escapes += ["\n", "\r\n"] + else + lex_error("Invalid heredoc escape char. Only t, r, n, s, u, L, $ allowed. Got '#{e}'") + end + end + end + + # Produce a heredoc token to make the syntax available to the grammar + enqueue_completed([:HEREDOC, syntax, pos_after_heredoc - before], before) + + # If this is the second or subsequent heredoc on the line, the lexing context's :newline_jump contains + # the position after the \n where the next heredoc text should scan. If not set, this is the first + # and it should start scanning after the first found \n (or if not found == error). + + if ctx[:newline_jump] + scn.pos = lexing_context[:newline_jump] + else + scn.scan_until(/\n/) || lex_error("Heredoc without any following lines of text") + end + # offset 0 for the heredoc, and its line number + heredoc_offset = scn.pos + heredoc_line = locator.line_for_offset(heredoc_offset)-1 + + # Compute message to emit if there is no end (to make it refer to the opening heredoc position). + eof_message = positioned_message("Heredoc without end-tagged line") + + # Text from this position (+ lexing contexts offset for any preceding heredoc) is heredoc until a line + # that terminates the heredoc is found. + + # (Endline in EBNF form): WS* ('|' WS*)? ('-' WS*)? endtag WS* \r? (\n|$) + endline_pattern = /([[:blank:]]*)(?:([|])[[:blank:]]*)?(?:(\-)[[:blank:]]*)?#{Regexp.escape(endtag)}[[:blank:]]*\r?(?:\n|\z)/ + lines = [] + while !scn.eos? do + one_line = scn.scan_until(/(?:\n|\z)/) || lexer.lex_error_without_pos(eof_message) + if md = one_line.match(endline_pattern) + leading = md[1] + has_margin = md[2] == '|' + remove_break = md[3] == '-' + # Record position where next heredoc (from same line as current @()) should start scanning for content + ctx[:newline_jump] = scn.pos + + # Process captured lines - remove leading, and trailing newline + str = heredoc_text(lines, leading, has_margin, remove_break) + + # Use a new lexer instance configured with a sub-locator to enable correct positioning + sublexer = self.class.new() + locator = SubLocator.sub_locator(str, locator.file, heredoc_line, heredoc_offset, leading.length()) + sublexer.lex_unquoted_string(str, locator, resulting_escapes, dqstring_style) + sublexer.interpolate_uq_to(self) + # Continue scan after @(...) + scn.pos = pos_after_heredoc + return + else + lines << one_line + end + end + lex_error_without_pos(eof_message) + end + + # Produces the heredoc text string given the individual (unprocessed) lines as an array. + # @param lines [Array] unprocessed lines of text in the heredoc w/o terminating line + # @param leading [String] the leading text up (up to pipe or other terminating char) + # @param has_margin [Boolean] if the left margin should be adjusted as indicated by `leading` + # @param remove_break [Boolean] if the line break (\r?\n) at the end of the last line should be removed or not + # + def heredoc_text(lines, leading, has_margin, remove_break) + if has_margin + leading_pattern = /^#{Regexp.escape(leading)}/ + lines = lines.collect {|s| s.gsub(leading_pattern, '') } + end + result = lines.join('') + result.gsub!(/\r?\n$/, '') if remove_break + result + end + + # A Sublocator locates a concrete locator (subspace) in a virtual space. + # The `leading_line_count` is the (virtual) number of lines preceding the first line in the concrete locator. + # The `leading_offset` is the (virtual) byte offset of the first byte in the concrete locator. + # The `leading_line_offset` is the (virtual) offset / margin in characters for each line. + # + # This illustrates characters in the sublocator (`.`) inside the subspace (`X`): + # + # 1:XXXXXXXX + # 2:XXXX.... .. ... .. + # 3:XXXX. . .... .. + # 4:XXXX............ + # + # This sublocator would be configured with leading_line_count = 1, + # leading_offset=8, and leading_line_offset=4 + # + # Note that leading_offset must be the same for all lines and measured in characters. + # + class SubLocator < Puppet::Pops::Parser::Locator + def self.sub_locator(string, file, leading_line_count, leading_offset, leading_line_offset) + self.new(Puppet::Pops::Parser::Locator.locator(string, file), + leading_line_count, + leading_offset, + leading_line_offset) + end + + def initialize(locator, leading_line_count, leading_offset, leading_line_offset) + @locator = locator + @leading_line_count = leading_line_count + @leading_offset = leading_offset + @leading_line_offset = leading_line_offset + end + + def file + @locator.file + end + + def string + @locator.string + end + + # Given offset is offset in the subspace + def line_for_offset(offset) + @locator.line_for_offset(offset) + @leading_line_count + end + + # Given offset is offset in the subspace + def offset_on_line(offset) + @locator.offset_on_line + @leading_line_offset + end + + # Given offset is offset in the subspace + def char_offset(offset) + effective_line = @locator.line_for_offset(offset) + locator.char_offset(offset) + (effective_line * @leading_line_offset) + @leading_offset + end + + # Given offsets are offsets in the subspace + def char_length(offset, end_offset) + effective_line = @locator.line_for_offset(end_offset) - @locator.line_for_offset(offset) + locator.char_length(offset, end_offset) + (effective_line * @leading_line_offset) + end + end + +end \ No newline at end of file diff --git a/lib/puppet/pops/parser/interpolation_support.rb b/lib/puppet/pops/parser/interpolation_support.rb new file mode 100644 index 000000000..696709acb --- /dev/null +++ b/lib/puppet/pops/parser/interpolation_support.rb @@ -0,0 +1,227 @@ +# This module is an integral part of the Lexer. +# It defines interpolation support +# PERFORMANCE NOTE: There are 4 very similar methods in this module that are designed to be as +# performant as possible. While it is possible to parameterize them into one common method, the overhead +# of passing parameters and evaluating conditional logic has a negative impact on performance. +# +module Puppet::Pops::Parser::InterpolationSupport + + PATTERN_VARIABLE = %r{(::)?(\w+::)*\w+} + + # This is the starting point for a double quoted string with possible interpolation + # The structure mimics that of the grammar. + # The logic is explicit (where the former implementation used parameters/strucures) given to a + # generic handler. + # (This is both easier to understand and faster). + # + def interpolate_dq + scn = @scanner + ctx = @lexing_context + before = scn.pos + # skip the leading " by doing a scan since the slurp_dqstring uses last matched when there is an error + scn.scan(/"/) + value,terminator = slurp_dqstring() + text = value + after = scn.pos + while true + case terminator + when '"' + # simple case, there was no interpolation, return directly + return emit_completed([:STRING, text, scn.pos-before], before) + when '${' + count = ctx[:brace_count] + ctx[:brace_count] += 1 + # The ${ terminator is counted towards the string part + enqueue_completed([:DQPRE, text, scn.pos-before], before) + # Lex expression tokens until a closing (balanced) brace count is reached + enqueue_until count + break + when '$' + if varname = scn.scan(PATTERN_VARIABLE) + # The $ is counted towards the variable + enqueue_completed([:DQPRE, text, after-before-1], before) + enqueue_completed([:VARIABLE, varname, scn.pos - after + 1], after -1) + break + else + # false $ variable start + text += value + value,terminator = slurp_dqstring() + after = scn.pos + end + end + end + interpolate_tail_dq + # return the first enqueued token and shift the queue + @token_queue.shift + end + + def interpolate_tail_dq + scn = @scanner + ctx = @lexing_context + before = scn.pos + value,terminator = slurp_dqstring + text = value + after = scn.pos + while true + case terminator + when '"' + # simple case, there was no further interpolation, return directly + enqueue_completed([:DQPOST, text, scn.pos-before], before) + return + when '${' + count = ctx[:brace_count] + ctx[:brace_count] += 1 + # The ${ terminator is counted towards the string part + enqueue_completed([:DQMID, text, scn.pos-before], before) + # Lex expression tokens until a closing (balanced) brace count is reached + enqueue_until count + break + when '$' + if varname = scn.scan(PATTERN_VARIABLE) + # The $ is counted towards the variable + enqueue_completed([:DQMID, text, after-before-1], before) + enqueue_completed([:VARIABLE, varname, scn.pos - after +1], after -1) + break + else + # false $ variable start + text += value + value,terminator = self.send(slurpfunc) + after = scn.pos + end + end + end + interpolate_tail_dq + end + + # This is the starting point for a un-quoted string with possible interpolation + # The logic is explicit (where the former implementation used parameters/strucures) given to a + # generic handler. + # (This is both easier to understand and faster). + # + def interpolate_uq + scn = @scanner + ctx = @lexing_context + before = scn.pos + value,terminator = slurp_uqstring() + text = value + after = scn.pos + while true + case terminator + when '' + # simple case, there was no interpolation, return directly + enqueue_completed([:STRING, text, scn.pos-before], before) + return + when '${' + count = ctx[:brace_count] + ctx[:brace_count] += 1 + # The ${ terminator is counted towards the string part + enqueue_completed([:DQPRE, text, scn.pos-before], before) + # Lex expression tokens until a closing (balanced) brace count is reached + enqueue_until count + break + when '$' + if varname = scn.scan(PATTERN_VARIABLE) + # The $ is counted towards the variable + enqueue_completed([:DQPRE, text, after-before-1], before) + enqueue_completed([:VARIABLE, varname, scn.pos - after + 1], after -1) + break + else + # false $ variable start + text += value + value,terminator = slurp_uqstring() + after = scn.pos + end + end + end + interpolate_tail_uq + nil + end + + def interpolate_tail_uq + scn = @scanner + ctx = @lexing_context + before = scn.pos + value,terminator = slurp_uqstring + text = value + after = scn.pos + while true + case terminator + when '' + # simple case, there was no further interpolation, return directly + enqueue_completed([:DQPOST, text, scn.pos-before], before) + return + when '${' + count = ctx[:brace_count] + ctx[:brace_count] += 1 + # The ${ terminator is counted towards the string part + enqueue_completed([:DQMID, text, scn.pos-before], before) + # Lex expression tokens until a closing (balanced) brace count is reached + enqueue_until count + break + when '$' + if varname = scn.scan(PATTERN_VARIABLE) + # The $ is counted towards the variable + enqueue_completed([:DQMID, text, after-before-1], before) + enqueue_completed([:VARIABLE, varname, scn.pos - after +1], after -1) + break + else + # false $ variable start + text += value + value,terminator = slurp_uqstring + after = scn.pos + end + end + end + interpolate_tail_uq + end + + # Enqueues lexed tokens until either end of input, or the given brace_count is reached + # + def enqueue_until brace_count + scn = @scanner + ctx = @lexing_context + queue = @token_queue + + scn.skip(self.class::PATTERN_WS) + queue_size = queue.size + until scn.eos? do + if token = lex_token + token_name = token[0] + ctx[:after] = token_name + if token_name == :RBRACE && ctx[:brace_count] == brace_count + if queue.size - queue_size == 1 + # Single token is subject to replacement + queue[-1] = transform_to_variable(queue[-1]) + end + return + end + queue << token + else + scn.skip(self.class::PATTERN_WS) + end + end + end + + def transform_to_variable(token) + token_name = token[0] + if [:NUMBER, :NAME].include?(token_name) || self.class::KEYWORD_NAMES[token_name] + t = token[1] + ta = t.token_array + [:VARIABLE, self.class::TokenValue.new([:VARIABLE, ta[1], ta[2]], t.offset, t.locator)] + else + token + end + end + + # Interpolates unquoted string and transfers the result to the given lexer + # (This is used when a second lexer instance is used to lex a substring) + # + def interpolate_uq_to(lexer) + interpolate_uq + queue = @token_queue + until queue.empty? do + lexer.enqueue(queue.shift) + end + end + +end \ No newline at end of file diff --git a/lib/puppet/pops/parser/lexer.rb b/lib/puppet/pops/parser/lexer.rb index 994c4643f..39e3c4099 100644 --- a/lib/puppet/pops/parser/lexer.rb +++ b/lib/puppet/pops/parser/lexer.rb @@ -1,862 +1,753 @@ # the scanner/lexer require 'forwardable' require 'strscan' require 'puppet' require 'puppet/util/methodhelper' module Puppet class LexError < RuntimeError; end end class Puppet::Pops::Parser::Lexer extend Forwardable attr_reader :file, :lexing_context, :token_queue attr_reader :locator attr_accessor :indefine alias :indefine? :indefine def lex_error msg raise Puppet::LexError.new(msg) end class Token ALWAYS_ACCEPTABLE = Proc.new { |context| true } include Puppet::Util::MethodHelper attr_accessor :regex, :name, :string, :skip, :skip_text alias skip? skip # @overload initialize(string) # @param string [String] a literal string token matcher # @param name [String] the token name (what it is known as in the grammar) # @param options [Hash] see {#set_options} # @overload initialize(regex) # @param regex [Regexp] a regular expression token text matcher # @param name [String] the token name (what it is known as in the grammar) # @param options [Hash] see {#set_options} # def initialize(string_or_regex, name, options = {}) if string_or_regex.is_a?(String) @name, @string = name, string_or_regex @regex = Regexp.new(Regexp.escape(string_or_regex)) else @name, @regex = name, string_or_regex end set_options(options) @acceptable_when = ALWAYS_ACCEPTABLE end # @return [String] human readable token reference; the String if literal, else the token name def to_s string or @name.to_s end # @return [Boolean] if the token is acceptable in the given context or not. - # this implementation always returns true. - # @param context [Hash] ? ? ? + # @param context [Hash] the lexing context # def acceptable?(context={}) @acceptable_when.call(context) end # Defines when the token is able to match. # This provides context that cannot be expressed otherwise, such as feature flags. # # @param block [Proc] a proc that given a context returns a boolean def acceptable_when(block) @acceptable_when = block end end # Maintains a list of tokens. class TokenList extend Forwardable attr_reader :regex_tokens, :string_tokens def_delegator :@tokens, :[] # Adds a new token to the set of recognized tokens # @param name [String] the token name # @param regex [Regexp, String] source text token matcher, a litral string or regular expression # @param options [Hash] see {Token::set_options} # @param block [Proc] optional block set as the created tokens `convert` method # @raise [ArgumentError] if the token with the given name is already defined # def add_token(name, regex, options = {}, &block) raise(ArgumentError, "Token #{name} already exists") if @tokens.include?(name) token = Token.new(regex, name, options) @tokens[token.name] = token if token.string @string_tokens << token @tokens_by_string[token.string] = token else @regex_tokens << token end token.meta_def(:convert, &block) if block_given? token end # Creates an empty token list # def initialize @tokens = {} @regex_tokens = [] @string_tokens = [] @tokens_by_string = {} end # Look up a token by its literal (match) value, rather than name. # @param string [String, nil] the literal match string to obtain a {Token} for, or nil if it does not exist. def lookup(string) @tokens_by_string[string] end # Adds tokens from a hash where key is a matcher (literal string or regexp) and the # value is the token's name # @param hash [Hash<{String => Symbol}, Hash<{Regexp => Symbol}] map token text matcher to token name # @return [void] # def add_tokens(hash) hash.each do |regex, name| add_token(name, regex) end end # Sort literal (string-) tokens by length, so we know once we match, we're done. # This helps avoid the O(n^2) nature of token matching. # The tokens are sorted in place. # @return [void] def sort_tokens @string_tokens.sort! { |a, b| b.string.length <=> a.string.length } end # Yield each token name and value in turn. def each @tokens.each {|name, value| yield name, value } end end TOKENS = TokenList.new TOKENS.add_tokens( '[' => :LBRACK, ']' => :RBRACK, # '{' => :LBRACE, # Specialized to handle lambda and brace count # '}' => :RBRACE, # Specialized to handle brace count '(' => :LPAREN, ')' => :RPAREN, '=' => :EQUALS, '+=' => :APPENDS, + '-=' => :DELETES, '==' => :ISEQUAL, '>=' => :GREATEREQUAL, '>' => :GREATERTHAN, '<' => :LESSTHAN, '<=' => :LESSEQUAL, '!=' => :NOTEQUAL, '!' => :NOT, ',' => :COMMA, '.' => :DOT, ':' => :COLON, '@' => :AT, '|' => :PIPE, '<<|' => :LLCOLLECT, '|>>' => :RRCOLLECT, '->' => :IN_EDGE, '<-' => :OUT_EDGE, '~>' => :IN_EDGE_SUB, '<~' => :OUT_EDGE_SUB, '<|' => :LCOLLECT, '|>' => :RCOLLECT, ';' => :SEMIC, '?' => :QMARK, '\\' => :BACKSLASH, '=>' => :FARROW, '+>' => :PARROW, '+' => :PLUS, '-' => :MINUS, '/' => :DIV, '*' => :TIMES, '%' => :MODULO, '<<' => :LSHIFT, '>>' => :RSHIFT, '=~' => :MATCH, '!~' => :NOMATCH, %r{((::){0,1}[A-Z][-\w]*)+} => :CLASSREF, "" => :STRING, "" => :DQPRE, "" => :DQMID, "" => :DQPOST, "" => :BOOLEAN, "