Please review pull request #1584: (#11331) A whole new parser and some iteration goodness opened by (zaphod42)
Description:
This implements a new parser that provides an implementation of many parts of ARM-2
as well as some better error reporting.
Diff follows:
diff --git a/Gemfile b/Gemfile
index 5de4d54..88cee31 100644
--- a/Gemfile
+++ b/Gemfile
@@ -15,8 +15,10 @@ platforms :ruby do
gem 'pry', :group => :development
gem 'yard', :group => :development
gem 'redcarpet', :group => :development
+ gem "racc", "~> 1.4", :group => :development
end
+
group(:development, :test) do
gem "puppet", *location_for('file://.')
gem "facter", *location_for(ENV['FACTER_LOCATION'] || '~> 1.6')
diff --git a/lib/puppet/defaults.rb b/lib/puppet/defaults.rb
index a83fc4b..f73960f 100644
--- a/lib/puppet/defaults.rb
+++ b/lib/puppet/defaults.rb
@@ -1505,7 +1505,43 @@ module Puppet
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
}
+
)
define_settings(:puppetdoc,
:document_all => {
diff --git a/lib/puppet/error.rb b/lib/puppet/error.rb
index dceeea3..2e49863 100644
--- a/lib/puppet/error.rb
+++ b/lib/puppet/error.rb
@@ -12,18 +12,29 @@ 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
+ attr_accessor :line, :file, :pos
- def initialize(message, file=nil, line=nil, original=nil)
+ # 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
@line = line
+ @pos = pos
end
-
def to_s
msg = super
- if @file and @line
+ 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
diff --git a/lib/puppet/node/environment.rb b/lib/puppet/node/environment.rb
index 25e429a..087cd5c 100644
--- a/lib/puppet/node/environment.rb
+++ b/lib/puppet/node/environment.rb
@@ -1,6 +1,7 @@
require 'puppet/util'
require 'puppet/util/cacher'
require 'monitor'
+require 'puppet/parser/parser_factory'
# Just define it, so this class has fewer load dependencies.
class Puppet::Node
@@ -217,7 +218,8 @@ def validate_dirs(dirs)
def perform_initial_import
return empty_parse_result if Puppet.settings[:ignoreimport]
- parser = Puppet::Parser::Parser.new(self)
+# parser = Puppet::Parser::Parser.new(self)
+ parser = Puppet::Parser::ParserFactory.parser(self)
if code = Puppet.settings.uninterpolated_value(:code, name.to_s) and code != ""
parser.string = code
else
diff --git a/lib/puppet/parser/ast.rb b/lib/puppet/parser/ast.rb
index 3cc1a8e..438cb49 100644
--- a/lib/puppet/parser/ast.rb
+++ b/lib/puppet/parser/ast.rb
@@ -14,7 +14,7 @@ class Puppet::Parser::AST
include Puppet::Util::MethodHelper
include Puppet::Util::Docs
- attr_accessor :parent, :scope, :file, :line
+ attr_accessor :parent, :scope, :file, :line, :pos
def inspect
"( #{self.class} #{self.to_s} #{@children.inspect} )"
@@ -110,8 +110,10 @@ def evaluate_match(value, scope)
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'
diff --git a/lib/puppet/parser/ast/arithmetic_operator.rb b/lib/puppet/parser/ast/arithmetic_operator.rb
index 68cf021..9b4fe76 100644
--- a/lib/puppet/parser/ast/arithmetic_operator.rb
+++ b/lib/puppet/parser/ast/arithmetic_operator.rb
@@ -11,23 +11,67 @@ def each
[@lval,@rval,@operator].each { |child| yield child }
end
- # Returns a boolean which is the result of the boolean operation
- # of lval and rval operands
+ # Produces an object which is the result of the applying the operator to the of lval and rval operands.
+ # * Supports +, -, *, /, %, and <<, >> on numeric strings.
+ # * Supports + on arrays (concatenate), and hashes (merge)
+ # * Supports << on arrays (append)
+ #
def evaluate(scope)
# evaluate the operands, should return a boolean value
- lval = @lval.safeevaluate(scope)
- lval = Puppet::Parser::Scope.number?(lval)
- if lval == nil
- raise ArgumentError, "left operand of #{@operator} is not a number"
+ left = @lval.safeevaluate(scope)
+ right = @rval.safeevaluate(scope)
+
+ if left.is_a?(Array) || right.is_a?(Array)
+ eval_array(left, right)
+ elsif left.is_a?(Hash) || right.is_a?(Hash)
+ eval_hash(left, right)
+ else
+ eval_numeric(left, right)
end
- rval = @rval.safeevaluate(scope)
- rval = Puppet::Parser::Scope.number?(rval)
- if rval == nil
- raise ArgumentError, "right operand of #{@operator} is not a number"
+ end
+
+ # Concatenates (+) two arrays, or appends (<<) any object to a newly created array.
+ #
+ def eval_array(left, right)
+ assert_concatenation_supported()
+
+ raise ArgumentError, "operator #{@operator} is not applicable when one of the operands is an Array." unless %w{+ <<}.include?(@operator)
+ raise ArgumentError, "left operand of #{@operator} must be an Array" unless left.is_a?(Array)
+ if @operator == '+'
+ raise ArgumentError, "right operand of #{@operator} must be an Array when left is an Array." unless right.is_a?(Array)
+ return left + right
end
+ # only append case remains, left asserted to be an array, and right may be any object
+ # wrapping right in an array and adding it ensures a new copy (operator << mutates).
+ #
+ left + [right]
+ end
+
+ # Merges two hashes.
+ #
+ def eval_hash(left, right)
+ assert_concatenation_supported()
+
+ raise ArgumentError, "operator #{@operator} is not applicable when one of the operands is an Hash." unless @operator == '+'
+ raise ArgumentError, "left operand of #{@operator} must be an Hash" unless left.is_a?(Hash)
+ raise ArgumentError, "right operand of #{@operator} must be an Hash" unless right.is_a?(Hash)
+ # merge produces a merged copy
+ left.merge(right)
+ end
+
+ def eval_numeric(left, right)
+ left = Puppet::Parser::Scope.number?(left)
+ right = Puppet::Parser::Scope.number?(right)
+ raise ArgumentError, "left operand of #{@operator} is not a number" unless left != nil
+ raise ArgumentError, "right operand of #{@operator} is not a number" unless right != nil
# compute result
- lval.send(@operator, rval)
+ left.send(@operator, right)
+ end
+
+ def assert_concatenation_supported
+ return if Puppet[:parser] == 'future'
+ raise ParseError.new("Unsupported Operation: Array concatenation available with '--parser future' setting only.")
end
def initialize(hash)
diff --git a/lib/puppet/parser/ast/astarray.rb b/lib/puppet/parser/ast/astarray.rb
index 7283a1f..42a47a1 100644
--- a/lib/puppet/parser/ast/astarray.rb
+++ b/lib/puppet/parser/ast/astarray.rb
@@ -9,7 +9,7 @@ class Puppet::Parser::AST
class ASTArray < Branch
include Enumerable
- # Return a child by index. Probably never used.
+ # Return a child by index. Used (at least) by tests.
def [](index)
@children[index]
end
diff --git a/lib/puppet/parser/ast/block_expression.rb b/lib/puppet/parser/ast/block_expression.rb
new file mode 100644
index 0000000..7f75ceb
--- /dev/null
+++ b/lib/puppet/parser/ast/block_expression.rb
@@ -0,0 +1,41 @@
+require 'puppet/parser/ast/branch'
+
+class Puppet::Parser::AST
+ class BlockExpression < Branch
+ include Enumerable
+
+ # Evaluate contained expressions, produce result of the last
+ def evaluate(scope)
+ result = nil
+ @children.each do |child|
+ # Skip things that respond to :instantiate (classes, nodes,
+ # and definitions), because they have already been
+ # instantiated.
+ if !child.respond_to?(:instantiate)
+ result = child.safeevaluate(scope)
+ end
+ end
+ result
+ end
+
+ # Return a child by index.
+ def [](index)
+ @children[index]
+ end
+
+ def push(*ary)
+ ary.each { |child|
+ #Puppet.debug "adding %s(%s) of type %s to %s" %
+ # [child, child.object_id, child.class.to_s.sub(/.+::/,''),
+ # self.object_id]
+ @children.push(child)
+ }
+
+ self
+ end
+
+ def to_s
+ "[" + @children.collect { |c| c.to_s }.join(', ') + "]"
+ end
+ end
+end
diff --git a/lib/puppet/parser/ast/function.rb b/lib/puppet/parser/ast/function.rb
index f6916b7..5d2dc6f 100644
--- a/lib/puppet/parser/ast/function.rb
+++ b/lib/puppet/parser/ast/function.rb
@@ -6,7 +6,7 @@ class Function < AST::Branch
associates_doc
- attr_accessor :name, :arguments
+ attr_accessor :name, :arguments, :pblock
def evaluate(scope)
# Make sure it's a defined function
@@ -16,8 +16,16 @@ def evaluate(scope)
case @ftype
when :rvalue
raise Puppet::ParseError, "Function '#{@name}' does not return a value" unless Puppet::Parser::Functions.rvalue?(@name)
+
when :statement
- if Puppet::Parser::Functions.rvalue?(@name)
+ # It is harmless to produce an ignored rvalue, the alternative is to mark functions
+ # as appropriate for both rvalue and statements
+ # Keeping the old behavior when a pblock is not present. This since it is not known
+ # if the lambda contains a statement or not (at least not without a costly search).
+ # The purpose of the check is to protect a user for producing a meaningless rvalue where the
+ # operation has no side effects.
+ #
+ if !pblock && Puppet::Parser::Functions.rvalue?(@name)
raise Puppet::ParseError,
"Function '#{@name}' must be the value of a statement"
end
@@ -28,6 +36,9 @@ def evaluate(scope)
# We don't need to evaluate the name, because it's plaintext
args = @arguments.safeevaluate(scope).map { |x| x == :undef ? '' : x }
+ # append a puppet lambda (unevaluated) if it is defined
+ args << pblock if pblock
+
scope.send("function_#{@name}", args)
end
diff --git a/lib/puppet/parser/ast/lambda.rb b/lib/puppet/parser/ast/lambda.rb
new file mode 100644
index 0000000..00c0e86
--- /dev/null
+++ b/lib/puppet/parser/ast/lambda.rb
@@ -0,0 +1,107 @@
+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<Array<String,String>>] 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
+ merged = parameters.zip(args)
+ missing = merged.select { |e| !e[1] && e[0].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|
+ # Ruby 1.8.7 zip seems to produce a different result than Ruby 1.9.3 in some situations
+ n = m[0].is_a?(Array) ? m[0][0] : m[0]
+ v = m[1] || (m[0][1]).safeevaluate(scope) # given value or default expression value
+ [n, v]
+ 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").
+ begin
+ elevel = scope.ephemeral_level
+ scope.ephemeral_from(Hash[evaluated], file, line)
+ result = safeevaluate(scope)
+ ensure
+ scope.unset_ephemeral_var(elevel)
+ result ||= nil
+ 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
+ end
+end
diff --git a/lib/puppet/parser/ast/leaf.rb b/lib/puppet/parser/ast/leaf.rb
index 2371ae7..ce5441d 100644
--- a/lib/puppet/parser/ast/leaf.rb
+++ b/lib/puppet/parser/ast/leaf.rb
@@ -148,7 +148,6 @@ def array_index_or_key(object, key)
def evaluate(scope)
object = evaluate_container(scope)
accesskey = evaluate_key(scope)
-
raise Puppet::ParseError, "#{variable} is not an hash or array when accessing it with #{accesskey}" unless object.is_a?(Hash) or object.is_a?(Array)
object[array_index_or_key(object, accesskey)] || :undef
diff --git a/lib/puppet/parser/ast/method_call.rb b/lib/puppet/parser/ast/method_call.rb
new file mode 100644
index 0000000..d613674
--- /dev/null
+++ b/lib/puppet/parser/ast/method_call.rb
@@ -0,0 +1,77 @@
+require 'puppet/parser/ast/branch'
+require 'puppet/parser/methods'
+
+class Puppet::Parser::AST
+ # An AST object to call a method
+ class MethodCall < AST::Branch
+
+ associates_doc
+
+ # An AST that evaluates to the object the method is applied to
+ # @return [Puppet::Parser::AST]
+ attr_accessor :receiver
+
+ # The name of the method
+ # @return [String]
+ attr_accessor :name
+
+ # The arguments to evaluate as arguments to the method.
+ # @return [Array<Puppet::Parser::AST>]
+ attr_accessor :arguments
+
+ # An optional lambda/block that will be yielded to by the called method (if it supports this)
+ # @return [Puppet::Parser::AST::Lambda]
+ attr_accessor :lambda
+
+ # Evaluates the method call and returns what the called method/function returns.
+ # The evaluation evaluates all arguments in the calling scope and then delegates
+ # to a "method" instance produced by Puppet::Parser::Methods for this method call.
+ # @see Puppet::Parser::Methods
+ # @return [Object] what the called method/function returns
+ def evaluate(scope)
+ # Make sure it's a defined method for the receiver
+ r = @receiver.evaluate(scope)
+ raise Puppet::ParseError, "No object to apply method #{@name} to" unless r
+ m = Puppet::Parser::Methods.find_method(scope, r, @name)
+ raise Puppet::ParseError, "Unknown method #{@name} for #{r}" unless m
+
+ # Now check if rvalue is required (in expressions)
+ case @ftype
+ when :rvalue
+ raise Puppet::ParseError, "Method '#{@name}' does not return a value" unless m.is_rvalue?
+ when :statement
+ # When used as a statement, ignore if it produces a rvalue (it is simply not used)
+ else
+ raise Puppet::DevError, "Invalid method type #{@ftype.inspect}"
+ end
+
+ # Evaluate arguments
+ args = @arguments ? @arguments.safeevaluate(scope).map { |x| x == :undef ? '' : x } : []
+
+ # There is no need to evaluate the name, since it is a literal ruby string
+
+ # call the method (it is already bound to the receiver and name)
+ m.invoke(scope, args, @lambda)
+ end
+
+ def initialize(hash)
+ @ftype = hash[:ftype] || :rvalue
+ hash.delete(:ftype) if hash.include? :ftype
+
+ super(hash)
+
+ # Lastly, check the parity
+ end
+
+ # Sets this method call in statement mode where a produced rvalue is ignored.
+ # @return [void]
+ def ignore_rvalue
+ @ftype = :statement
+ end
+
+ def to_s
+ args = arguments.is_a?(ASTArray) ? arguments.to_s.gsub(/\[(.*)\]/,'\1') : arguments
+ "#{@receiver.to_s}.#{name} (#{args})" + (@lambda ? " #{@lambda.to_s}" : '')
+ end
+ end
+end
diff --git a/lib/puppet/parser/ast/vardef.rb b/lib/puppet/parser/ast/vardef.rb
index 1374c73..07999f3 100644
--- a/lib/puppet/parser/ast/vardef.rb
+++ b/lib/puppet/parser/ast/vardef.rb
@@ -21,6 +21,13 @@ def evaluate(scope)
scope.setvar(name,value, :file => file, :line => line, :append => @append)
end
end
+ if @append
+ # Produce resulting value from append operation
+ scope[name]
+ else
+ # Produce assigned value
+ value
+ end
end
def each
diff --git a/lib/puppet/parser/e_parser_adapter.rb b/lib/puppet/parser/e_parser_adapter.rb
new file mode 100644
index 0000000..beec147
--- /dev/null
+++ b/lib/puppet/parser/e_parser_adapter.rb
@@ -0,0 +1,120 @@
+require 'puppet/pops'
+
+module Puppet; module Parser; end; end;
+# Adapts an egrammar/eparser to respond to the public API of the classic parser
+#
+class Puppet::Parser::EParserAdapter
+
+ def initialize(classic_parser)
+ @classic_parser = classic_parser
+ @file = ''
+ @string = ''
+ @use = :undefined
+ end
+
+ def file=(file)
+ @classic_parser.file = file
+ @file = file
+ @use = :file
+ end
+
+ def parse(string = nil)
+ if @file =~ /\.rb$/
+ return parse_ruby_file
+ else
+ self.string= string if string
+ parser = Puppet::Pops::Parser::Parser.new()
+ parse_result = if @use == :string
+ parser.parse_string(@string)
+ else
+ parser.parse_file(@file)
+ end
+ # Compute the 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.
+ #
+ source_file = @file || "unknown-source-location"
+
+ # Validate
+ validate(parse_result)
+
+ # Transform the result, but only if not nil
+ parse_result = Puppet::Pops::Model::AstTransformer.new(source_file, @classic_parser).transform(parse_result) if parse_result
+ if parse_result && !parse_result.is_a?(Puppet::Parser::AST::BlockExpression)
+ # Need to transform again, if result is not wrapped in something iterable when handed off to
+ # a new Hostclass as its code.
+ parse_result = Puppet::Parser::AST::BlockExpression.new(:children => [parse_result]) if parse_result
+ end
+ end
+
+ Puppet::Parser::AST::Hostclass.new('', :code => parse_result)
+ end
+
+ def validate(parse_result)
+ # TODO: This is too many hoops to jump through... ugly API
+ # could reference a ValidatorFactory.validator_3_1(acceptor) instead.
+ # and let the factory abstract the rest.
+ #
+ return unless parse_result
+
+ acceptor = Puppet::Pops::Validation::Acceptor.new
+ validator = Puppet::Pops::Validation::ValidatorFactory_3_1.new().validator(acceptor)
+ validator.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
+ if warnings.size > 0
+ formatter = Puppet::Pops::Validation::DiagnosticFormatterPuppetStyle.new
+ emitted_w = 0
+ emitted_dw = 0
+ acceptor.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
+ if errors.size > 0
+ formatter = Puppet::Pops::Validation::DiagnosticFormatterPuppetStyle.new
+ if errors.size == 1 || max_errors <= 1
+ # raise immediately
+ raise Puppet::ParseError.new(formatter.format(errors[0]))
+ end
+ emitted = 0
+ errors.each do |e|
+ Puppet.err(formatter.format(e))
+ emitted += 1
+ break if emitted >= max_errors
+ end
+ warnings_message = warnings.size > 0 ? ", and #{warnings.size} warnings" : ""
+ giving_up_message = "Found #{errors.size} errors#{warnings_message}. Giving up"
+ exception = Puppet::ParseError.new(giving_up_message)
+ exception.file = errors[0].file
+ raise exception
+ end
+ end
+
+ def string=(string)
+ @classic_parser.string = string
+ @string = string
+ @use = :string
+ end
+
+ def parse_ruby_file
+ @classic_parser.parse
+ end
+end
diff --git a/lib/puppet/parser/files.rb b/lib/puppet/parser/files.rb
index 18bae6f..b0239f2 100644
--- a/lib/puppet/parser/files.rb
+++ b/lib/puppet/parser/files.rb
@@ -1,11 +1,12 @@
require 'puppet/module'
-require 'puppet/parser/parser'
+#require 'puppet/parser/parser'
# This is a silly central module for finding
# different kinds of files while parsing. This code
# doesn't really belong in the Puppet::Module class,
# but it doesn't really belong anywhere else, either.
-module Puppet::Parser::Files
+module Puppet; module Parser; module Files
+
module_function
# Return a list of manifests (as absolute filenames) that match +pat+
@@ -85,4 +86,4 @@ def split_file_path(path)
path.split(File::SEPARATOR, 2) unless path == "" or Puppet::Util.absolute_path?(path)
end
-end
+end; end; end
diff --git a/lib/puppet/parser/functions/collect.rb b/lib/puppet/parser/functions/collect.rb
new file mode 100644
index 0000000..e0b27d4
--- /dev/null
+++ b/lib/puppet/parser/functions/collect.rb
@@ -0,0 +1,41 @@
+Puppet::Parser::Functions::newfunction(
+:collect,
+: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.collect |$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]`.
+
+ *Examples*
+
+ # Turns hash into array of values
+ $a.collect |$x|{ $x[1] }
+
+ # Turns hash into array of keys
+ $a.collect |$x| { $x[0] }
+
+ Since 3.2
+ ENDHEREDOC
+
+ require 'puppet/parser/ast/lambda'
+ raise ArgumentError, ("collect(): wrong number of arguments (#{args.length}; must be 2)") if args.length != 2
+ receiver = args[0]
+ pblock = args[1]
+ raise ArgumentError, ("collect(): wrong argument type (#{args[1].class}; must be a parameterized block.") unless pblock.is_a? Puppet::Parser::AST::Lambda
+ case receiver
+ when Array
+ when Hash
+ else
+ raise ArgumentError, ("collect(): wrong argument type (#{args[0].class}; must be an Array or a Hash.")
+ end
+
+ receiver.collect {|x| pblock.call(self, x) }
+end
diff --git a/lib/puppet/parser/functions/each.rb b/lib/puppet/parser/functions/each.rb
new file mode 100644
index 0000000..1c089e5
--- /dev/null
+++ b/lib/puppet/parser/functions/each.rb
@@ -0,0 +1,96 @@
+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}" }
+
+ Since 3.2
+ 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
+ result = nil
+ 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
+ result = nil
+ 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
+
+ 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
+
+ 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.")
+ end
+end
diff --git a/lib/puppet/parser/functions/foreach.rb b/lib/puppet/parser/functions/foreach.rb
new file mode 100644
index 0000000..1307209
--- /dev/null
+++ b/lib/puppet/parser/functions/foreach.rb
@@ -0,0 +1,96 @@
+Puppet::Parser::Functions::newfunction(
+:foreach,
+: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.foreach {|$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.foreach {|$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.foreach {|$entry| ..."key ${$entry[0]}, value ${$entry[1]}" }
+ $a.foreach {|$key, $value| ..."key ${key}, value ${value}" }
+
+ Since 3.2
+ 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
+ result = nil
+ 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
+ result = nil
+ 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
+
+ raise ArgumentError, ("foreach(): wrong number of arguments (#{args.length}; must be 2)") if args.length != 2
+ receiver = args[0]
+ pblock = args[1]
+ raise ArgumentError, ("foreach(): wrong argument type (#{args[1].class}; must be a parameterized block.") unless pblock.is_a? Puppet::Parser::AST::Lambda
+
+ case receiver
+ when Array
+ foreach_Array(receiver, self, pblock)
+ when Hash
+ foreach_Hash(receiver, self, pblock)
+ else
+ raise ArgumentError, ("foreach(): wrong argument type (#{args[0].class}; must be an Array or a Hash.")
+ end
+end
diff --git a/lib/puppet/parser/functions/reduce.rb b/lib/puppet/parser/functions/reduce.rb
new file mode 100644
index 0000000..0cc22bb
--- /dev/null
+++ b/lib/puppet/parser/functions/reduce.rb
@@ -0,0 +1,74 @@
+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
+ 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
+ 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/reject.rb b/lib/puppet/parser/functions/reject.rb
new file mode 100644
index 0000000..0297bff
--- /dev/null
+++ b/lib/puppet/parser/functions/reject.rb
@@ -0,0 +1,39 @@
+Puppet::Parser::Functions::newfunction(
+:reject,
+: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 entires for which the block did *not* evaluate 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.reject |$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]`.
+
+ *Examples*
+
+ # selects all that does not end with berry
+ $a = ["rasberry", "blueberry", "orange"]
+ $a.reject |$x| { $x =~ /berry$/ }
+
+ Since 3.2
+ ENDHEREDOC
+
+ require 'puppet/parser/ast/lambda'
+ raise ArgumentError, ("reject(): wrong number of arguments (#{args.length}; must be 2)") if args.length != 2
+ receiver = args[0]
+ pblock = args[1]
+ raise ArgumentError, ("reject(): wrong argument type (#{args[1].class}; must be a parameterized block.") unless pblock.is_a? Puppet::Parser::AST::Lambda
+ case receiver
+ when Array
+ when Hash
+ else
+ raise ArgumentError, ("reject(): wrong argument type (#{args[0].class}; must be an Array or a Hash.")
+ end
+
+ receiver.reject {|x| pblock.call(self, x) }
+end
diff --git a/lib/puppet/parser/functions/select.rb b/lib/puppet/parser/functions/select.rb
new file mode 100644
index 0000000..03884d3
--- /dev/null
+++ b/lib/puppet/parser/functions/select.rb
@@ -0,0 +1,39 @@
+Puppet::Parser::Functions::newfunction(
+:select,
+: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 entires 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.select |$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]`.
+
+ *Examples*
+
+ # selects all that end with berry
+ $a = ["raspberry", "blueberry", "orange"]
+ $a.select |$x| { $x =~ /berry$/ }
+
+ Since 3.2
+ ENDHEREDOC
+
+ require 'puppet/parser/ast/lambda'
+ raise ArgumentError, ("select(): wrong number of arguments (#{args.length}; must be 2)") if args.length != 2
+ receiver = args[0]
+ pblock = args[1]
+ raise ArgumentError, ("select(): wrong argument type (#{args[1].class}; must be a parameterized block.") unless pblock.is_a? Puppet::Parser::AST::Lambda
+ case receiver
+ when Array
+ when Hash
+ else
+ raise ArgumentError, ("select(): wrong argument type (#{args[0].class}; must be an Array or a Hash.")
+ end
+
+ receiver.select {|x| pblock.call(self, x) }
+end
diff --git a/lib/puppet/parser/functions/slice.rb b/lib/puppet/parser/functions/slice.rb
new file mode 100644
index 0000000..41b693d
--- /dev/null
+++ b/lib/puppet/parser/functions/slice.rb
@@ -0,0 +1,96 @@
+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 should be an Array or a Hash, and the second
+ 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:
+
+ |$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
+ 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 an 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
+
+ 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/grammar.ra b/lib/puppet/parser/grammar.ra
index a809a14..509f36f 100644
--- a/lib/puppet/parser/grammar.ra
+++ b/lib/puppet/parser/grammar.ra
@@ -32,7 +32,7 @@ program: statements_and_declarations
| nil
statements_and_declarations: statement_or_declaration {
- result = ast AST::ASTArray, :children => (val[0] ? [val[0]] : [])
+ result = ast AST::BlockExpression, :children => (val[0] ? [val[0]] : [])
}
| statements_and_declarations statement_or_declaration {
if val[1]
@@ -553,7 +553,7 @@ caseopt: casevalues COLON LBRACE statements RBRACE {
AST::CaseOpt,
:value => val[0],
- :statements => ast(AST::ASTArray)
+ :statements => ast(AST::BlockExpression)
)
}
diff --git a/lib/puppet/parser/lexer.rb b/lib/puppet/parser/lexer.rb
index 6ae4870..cfaa34d 100644
--- a/lib/puppet/parser/lexer.rb
+++ b/lib/puppet/parser/lexer.rb
@@ -20,6 +20,15 @@ class Puppet::Parser::Lexer
attr_accessor :line, :indefine
alias :indefine? :indefine
+ # Returns the position on the line.
+ # This implementation always returns nil. It is here for API reasons in Puppet::Error
+ # which needs to support both --parser current, and --parser future.
+ #
+ def pos
+ # Make the lexer comply with newer API. It does not produce a pos...
+ nil
+ end
+
def lex_error msg
raise Puppet::LexError.new(msg)
end
diff --git a/lib/puppet/parser/methods.rb b/lib/puppet/parser/methods.rb
new file mode 100644
index 0000000..af19b2a
--- /dev/null
+++ b/lib/puppet/parser/methods.rb
@@ -0,0 +1,69 @@
+require 'puppet/util/autoload'
+require 'puppet/parser/scope'
+require 'puppet/parser/functions'
+require 'monitor'
+
+# A module for handling finding and invoking methods (functions invokable as a method).
+# A method call on the form:
+#
+# $a.meth(1,2,3] {|...| ...}
+#
+# will lookup a function called 'meth' and call it with the arguments ($a, 1, 2, 3, <lambda>)
+#
+# @see Puppet::Parser::AST::Lambda
+# @see Puppet::Parser::AST::MethodCall
+#
+# @api public
+# @since 3.2
+#
+module Puppet::Parser::Methods
+ Environment = Puppet::Node::Environment
+ # Represents an invokable method configured to be invoked for a given object.
+ #
+ class Method
+ def initialize(receiver, obj, method_name, rvalue)
+ @receiver = receiver
+ @o = obj
+ @method_name = method_name
+ @rvalue = rvalue
+ end
+
+ # Invoke this method's function in the given scope with the given arguments and parameterized block.
+ # A method call on the form:
+ #
+ # $a.meth(1,2,3) {|...| ...}
+ #
+ # results in the equivalent:
+ #
+ # meth($a, 1, 2, 3, {|...| ... })
+ #
+ # @param scope [Puppet::Parser::Scope] the scope the call takes place in
+ # @param args [Array<Object>] arguments 1..n to pass to the function
+ # @param pblock [Puppet::Parser::AST::Lambda] optional parameterized block to pass as the last argument
+ # to the called function
+ #
+ def invoke(scope, args=[], pblock=nil)
+ arguments = [@o] + args
+ arguments << pblock if pblock
+ @receiver.send(@method_name, arguments)
+ end
+
+ # @return [Boolean] whether the method function produces an rvalue or not.
+ def is_rvalue?
+ @rvalue
+ end
+ end
+
+ class << self
+ include Puppet::Util
+ end
+
+ # Finds a function and returns an instance of Method configured to perform invocation.
+ # @return [Method, nil] configured method or nil if method not found
+ def self.find_method(scope, receiver, name)
+ fname = Puppet::Parser::Functions.function(name)
+ rvalue = Puppet::Parser::Functions.rvalue?(name)
+ return Method.new(scope, receiver, fname, rvalue) if fname
+ nil
+ end
+end
diff --git a/lib/puppet/parser/parser.rb b/lib/puppet/parser/parser.rb
index c756701..4a472c9 100644
--- a/lib/puppet/parser/parser.rb
+++ b/lib/puppet/parser/parser.rb
@@ -34,104 +34,109 @@ class Parser < Racc::Parser
##### State transition tables begin ###
clist = [
-'9,15,199,200,204,181,-130,104,-112,106,301,372,91,291,370,168,172,181',
-'300,-197,-184,180,107,180,163,165,169,171,39,205,47,1,332,10,13,180',
-'21,25,35,331,44,48,2,9,15,16,103,164,167,34,334,174,175,157,158,160',
-'161,340,166,170,31,-130,-130,-130,-130,159,8,162,9,15,311,39,104,47',
-'1,348,10,13,330,21,25,35,376,44,48,2,9,15,16,-178,377,292,34,397,282',
-'319,54,304,305,120,55,168,172,31,282,307,185,16,320,8,163,165,169,171',
-'39,-123,47,1,345,10,13,180,21,25,35,358,44,48,2,9,15,16,315,164,167',
-'34,395,174,175,157,158,160,161,314,166,170,31,9,15,304,305,159,8,162',
-'187,372,312,39,370,47,1,257,10,13,212,21,25,35,212,44,48,2,9,15,16,211',
-'299,329,34,211,214,82,54,294,214,185,55,168,172,31,80,81,282,366,336',
-'8,163,165,169,171,39,290,47,1,289,10,13,54,21,25,35,55,44,48,2,9,15',
-'16,31,164,167,34,354,174,175,157,158,160,161,31,166,170,31,9,15,166',
-'170,159,8,162,166,170,159,39,-185,47,1,159,10,13,121,21,25,35,342,44',
-'48,2,9,15,16,-179,-96,343,34,384,58,-180,212,212,25,236,-178,-129,48',
-'31,9,15,16,211,211,8,34,54,214,214,39,55,47,1,353,10,13,31,21,25,35',
-'259,44,48,2,9,15,16,166,170,-177,34,278,58,350,159,303,25,236,304,305',
-'48,31,9,15,16,308,257,8,34,282,307,256,39,255,47,1,253,10,13,31,21,25',
-'35,249,44,48,2,9,15,16,247,312,361,34,322,58,241,93,363,25,236,321,220',
-'48,31,282,283,16,285,106,8,34,282,283,208,39,369,47,1,-122,10,13,31',
-'21,25,35,122,44,48,2,9,15,16,121,66,97,34,373,99,346,157,158,-183,282',
-'307,166,170,31,9,15,-181,-179,159,8,162,375,249,31,149,151,154,125,128',
-'58,134,135,139,141,145,380,152,155,381,9,15,16,257,138,143,146,394,58',
-'93,-182,-180,25,236,-177,-184,48,31,9,15,16,385,127,130,34,82,122,93',
-'39,386,47,1,121,10,13,31,21,25,35,-228,44,48,2,9,15,16,120,97,388,34',
-'99,58,54,-179,390,25,196,-180,44,48,31,110,393,16,108,97,8,34,99,31',
-'100,39,57,47,1,56,10,13,31,21,25,35,399,44,48,2,245,31,16,400,,,34,',
-',168,172,85,86,87,83,,,31,163,165,169,171,,8,174,175,157,158,,,,166',
-'170,-38,-38,-38,-38,,159,,162,164,167,172,,174,175,157,158,160,161,163',
-'166,170,246,-40,-40,-40,-40,159,,162,,168,172,85,86,87,83,,,,163,165',
-'169,171,,,174,175,157,158,160,161,,166,170,-44,-44,-44,-44,,159,,162',
-'164,167,,,174,175,157,158,160,161,,166,170,173,,,,,159,,162,,168,172',
-',,,,,,,163,165,169,171,,,174,175,157,158,,9,15,166,170,66,,72,,,159',
-',162,164,167,,,174,175,157,158,160,161,59,166,170,9,15,,,114,159,-197',
-'162,58,,,,25,69,,,48,,,,16,64,113,,34,9,15,77,,66,,72,,58,,31,,25,69',
-',,48,9,15,,16,66,59,72,34,,,,,,,,,58,,31,,25,69,59,,48,,,,16,64,,,34',
-'58,,77,,25,69,,,48,,31,,16,64,9,15,34,,66,77,72,,,,9,15,,31,66,,72,',
-',,,,59,,,,,,,,157,158,59,58,,166,170,25,69,,,48,159,58,162,16,64,25',
-'69,34,,48,77,,,16,64,9,15,34,31,66,77,72,,,,9,15,,31,66,,72,,,,9,15',
-'59,287,66,,72,,,,,,59,58,,,,25,69,,,48,59,58,,16,64,25,69,34,,48,77',
-'58,,16,64,25,69,34,31,48,77,,,16,64,9,15,34,31,66,77,72,,,,9,15,,31',
-'66,,72,,,,9,15,59,,66,,72,,,,,,59,58,,,,25,69,,,48,59,58,,16,64,25,69',
-'34,,48,77,58,,16,64,25,69,34,31,48,77,,,16,64,9,15,34,31,66,77,72,,',
-',9,15,,31,66,,72,,,,9,15,59,,66,179,72,,,,,,59,58,,,,25,69,,,48,59,58',
-',16,64,25,69,34,,48,77,58,,16,64,25,69,34,31,48,77,,,16,64,9,15,34,31',
-'66,77,72,,,,9,15,,31,66,,72,,,,9,15,59,,66,,72,,,,,,59,58,,,,25,69,',
-',48,59,58,,16,64,25,69,34,,48,77,58,,16,64,25,69,34,31,48,77,,,16,64',
-'9,15,34,31,66,77,72,,,,9,15,,31,66,,72,,,,9,15,59,,66,,72,,,,,,59,58',
-',,,25,69,,,48,59,58,,16,64,25,69,34,,48,77,58,,16,64,25,69,34,31,48',
-'77,,,16,64,9,15,34,31,66,77,72,,,,9,15,,31,66,,72,,,,9,15,59,,66,,72',
-',,,,,59,58,,,,25,69,,,48,59,58,,16,64,25,69,34,,48,77,58,,16,64,25,69',
-'34,31,48,77,,,16,64,9,15,34,31,66,77,72,,,,9,15,,31,66,,72,,,,9,15,59',
-',66,,72,,,,,,59,58,,,,25,69,,,48,59,58,,16,64,25,69,34,,48,77,58,,16',
-'64,25,69,34,31,48,77,,,16,64,9,15,34,31,66,77,72,,,,9,15,,31,66,,72',
-',,,,,59,,,,,,,,,,59,58,9,15,,25,69,,,48,,58,,16,64,25,69,34,,48,77,',
-',16,64,,,34,31,,77,,,,,58,54,,31,25,196,,44,48,9,15,,16,66,,72,34,,',
-'9,15,,,66,,72,,31,,9,15,59,,66,,,,,,9,15,59,58,66,,,25,69,,,48,,58,',
-'16,64,25,69,34,,48,77,58,,16,64,25,69,34,31,48,77,58,,16,,25,69,34,31',
-'48,9,15,,16,66,,72,34,31,,9,15,,,66,,72,,31,,9,15,59,,66,,72,,,,,,59',
-'58,,,,25,69,,,48,59,58,,16,64,25,69,34,,48,77,58,,16,64,25,69,34,31',
-'48,77,,,16,64,9,15,34,31,66,77,72,,,,9,15,,31,66,,72,,,,9,15,59,,,,238',
-',,,,,59,58,,,,25,69,,,48,,58,,16,64,25,69,34,,48,77,58,,16,64,25,236',
-'34,31,48,77,,,16,,9,15,34,31,66,,72,,,,,,,31,,,,9,15,,,66,59,72,,,,',
-',,,,,58,,,,25,69,59,243,48,9,15,,16,64,,,34,58,,77,,25,69,,,48,,31,',
-'16,64,,,34,9,15,77,,66,179,72,,58,,31,,25,236,,,48,9,15,,16,66,59,72',
-'34,,,,,,,,,58,,31,,25,69,59,,48,,,,16,64,,,34,58,,77,,25,69,,,48,,31',
-',16,64,9,15,34,,66,77,72,,,,9,15,,31,66,,72,,,,9,15,59,,66,,72,,,,,',
-'59,58,,,,25,69,,,48,59,58,,16,64,25,69,34,,48,77,58,,16,64,25,69,34',
-'31,48,77,,,16,64,9,15,34,31,66,77,72,,,,9,15,,31,66,,174,175,157,158',
-'160,161,59,166,170,,,,,,159,,162,58,,,,25,69,,,48,,58,,16,64,25,236',
-'34,,48,77,168,172,16,,,,34,31,,163,165,169,171,,,,,31,,,,,,,,,,,,,,',
-'164,167,,,174,175,157,158,160,161,,166,170,168,172,,,,159,,162,,163',
-'165,169,171,,,,,168,172,,,,,,,,163,165,169,171,,,164,167,,,174,175,157',
-'158,160,161,,166,170,,,,,,159,,162,174,175,157,158,160,161,,166,170',
-'168,172,,,,159,,162,,163,165,169,171,,,,,,,,,168,172,,,,,,,,163,165',
-'169,171,,,174,175,157,158,160,161,,166,170,,,172,,,159,,162,164,167',
-'163,,174,175,157,158,160,161,,166,170,168,172,,,,159,,162,,163,165,169',
-'171,,,,174,175,157,158,160,161,,166,170,,,,,,159,,162,167,,,174,175',
-'157,158,160,161,,166,170,168,172,,,,159,,162,,163,165,169,171,174,175',
-'157,158,160,161,,166,170,,172,,,,159,,162,,163,164,167,,,174,175,157',
-'158,160,161,,166,170,168,172,,,,159,,162,,163,165,169,171,174,175,157',
-'158,160,161,,166,170,,172,,,,159,,162,,163,164,167,,,174,175,157,158',
-'160,161,,166,170,168,172,,,,159,,162,,163,165,169,171,174,175,157,158',
-'160,161,,166,170,,,,,,159,,162,,,164,167,,,174,175,157,158,160,161,',
-'166,170,168,172,,,,159,,162,,163,165,169,171,,,,,,,,,,,,,,,,,,,,164',
-'167,,,174,175,157,158,160,161,,166,170,168,172,,,,159,,162,260,163,165',
-'169,171,,,,,,,,,,,,,,,,,,,,164,167,,,174,175,157,158,160,161,,166,170',
-'168,172,,,,159,,162,,163,165,169,171,,,,,168,172,,,,,,,,163,165,169',
-'171,,,164,167,,,174,175,157,158,160,161,,166,170,,,,,,159,,162,174,175',
-'157,158,160,161,,166,170,168,172,,,,159,,162,,163,165,169,171,,,,,,',
-',,,,,,,,,,,,,164,167,,,174,175,157,158,160,161,,166,170,,,,,,159,,162',
-'149,151,154,125,128,,134,135,139,222,230,,152,225,,,,,,138,143,224,149',
-'151,154,125,128,,134,135,139,222,223,,152,225,,127,130,,,138,143,224',
-'149,151,154,125,128,,134,135,139,222,230,,152,225,,127,130,,,138,143',
-'224,149,151,154,125,128,,134,135,139,222,223,,152,225,,127,130,,,138',
-'143,224,149,151,154,125,128,,134,135,139,222,223,,152,225,,127,130,',
-',138,143,224,149,151,154,125,128,,134,135,139,222,230,,152,225,,127',
-'130,,,138,143,224,,,,,,,,,,,,,,,,127,130' ]
- racc_action_table = arr = ::Array.new(2533, nil)
+'35,36,199,198,246,159,-130,86,-112,82,277,357,361,379,356,215,210,159',
+'276,-197,360,158,85,158,211,213,212,214,39,248,48,49,267,33,50,158,51',
+'37,26,-129,40,46,30,35,36,32,84,217,216,31,398,203,204,206,205,208,209',
+'-122,201,202,52,-130,-130,-130,-130,200,38,207,35,36,278,39,86,48,49',
+'350,33,50,90,51,37,26,89,40,46,30,35,36,32,-178,94,253,31,257,280,257',
+'364,274,273,92,93,215,210,52,257,255,226,336,62,38,211,213,212,214,39',
+'338,48,49,254,33,50,339,51,37,26,347,40,46,30,35,36,32,-180,217,216',
+'31,310,203,204,206,205,208,209,341,201,202,52,35,36,274,273,200,38,207',
+'223,303,-178,39,304,48,49,-177,33,50,185,51,37,26,95,40,46,30,35,36',
+'32,190,-184,281,31,397,189,344,90,201,202,226,89,215,210,52,200,357',
+'250,32,356,38,211,213,212,214,39,-179,48,49,268,33,50,90,51,37,26,89',
+'40,46,30,35,36,32,185,217,216,31,395,203,204,206,205,208,209,190,201',
+'202,52,119,189,201,202,200,38,207,201,202,200,39,119,48,49,200,33,50',
+'185,51,37,26,267,40,46,30,35,36,32,190,185,90,31,392,189,89,119,265',
+'375,118,337,190,120,52,257,280,189,302,-96,38,118,257,263,120,39,-185',
+'48,49,52,33,50,52,51,37,26,-123,40,46,30,35,36,32,52,103,118,31,252',
+'120,279,206,205,251,257,280,201,202,52,250,243,243,262,200,38,207,257',
+'263,52,137,135,139,134,136,80,132,140,141,177,168,240,131,162,35,36',
+'82,32,180,142,130,163,271,94,-184,274,273,203,204,206,205,-183,52,-181',
+'201,202,62,138,144,-180,353,200,39,207,48,49,354,33,50,330,51,37,26',
+'-182,40,46,30,35,36,32,-177,52,-179,31,367,203,204,206,205,157,368,370',
+'201,202,52,35,36,371,372,200,38,207,122,327,326,39,110,48,49,109,33',
+'50,267,51,37,26,334,40,46,30,35,36,32,91,81,62,31,377,80,90,323,-179',
+'37,128,382,40,46,52,35,36,32,383,-180,38,31,385,61,-228,39,387,48,49',
+'388,33,50,52,51,37,26,323,40,46,30,35,36,32,319,110,393,31,308,80,90',
+'317,305,37,128,158,40,46,52,53,399,32,400,,38,31,,,,39,,48,49,,33,50',
+'52,51,37,26,,40,46,30,230,,32,,,,31,,,215,210,56,57,58,59,,,52,211,213',
+'212,214,,38,203,204,206,205,208,209,,201,202,56,57,58,59,,200,,207,217',
+'216,210,,203,204,206,205,208,209,211,201,202,228,-38,-38,-38,-38,200',
+',207,,215,210,-44,-44,-44,-44,,,,211,213,212,214,,,203,204,206,205,208',
+'209,,201,202,-40,-40,-40,-40,,200,,207,217,216,,,203,204,206,205,208',
+'209,,201,202,229,,,,,200,,207,,215,210,206,205,,,,201,202,211,213,212',
+'214,,200,,207,,,,35,36,,,103,,104,,,,,,217,216,,,203,204,206,205,208',
+'209,102,201,202,35,36,,,103,200,104,207,80,,,,37,77,,,46,,,,32,101,102',
+',31,35,36,100,,103,,104,,80,,52,,37,77,,,46,,,,32,101,102,,31,35,36',
+'100,,103,,104,,80,,52,,37,77,,,46,,,,32,101,102,,31,35,36,100,,103,',
+'104,,80,,52,,37,77,,,46,,,,32,101,102,,31,35,36,100,,103,,104,,80,,52',
+',37,77,,,46,,,,32,101,102,,31,35,36,100,,103,,104,,80,,52,,37,77,,,46',
+',,,32,101,102,,31,35,36,100,,103,,104,,80,,52,,37,77,,,46,,,,32,101',
+'102,,31,35,36,100,,103,,104,,80,,52,,37,77,,,46,,,,32,101,102,,31,35',
+'36,100,,103,,104,,80,,52,,37,77,,,46,,,,32,101,102,,31,35,36,100,,103',
+',104,,80,,52,,37,77,,,46,,,,32,101,102,,31,35,36,100,,103,,104,,80,',
+'52,,37,77,,,46,,,,32,101,102,,31,35,36,100,,103,,104,,80,,52,,37,77',
+',,46,35,36,,32,101,102,155,31,,,100,,,35,36,,80,103,52,,37,77,,,46,',
+',,32,101,35,36,31,80,103,100,104,37,231,,,46,,52,,32,80,,,31,37,77,102',
+',46,35,36,,32,103,52,104,31,80,,35,36,37,77,,,46,358,52,,32,101,102',
+',31,,,100,35,36,,,,80,,52,,37,77,,,46,,80,,32,101,37,231,31,,46,100',
+',,32,,,,31,52,80,,,,37,231,,,46,52,,,32,35,36,,31,103,,104,,,,,,,,52',
+',,35,36,,,103,102,104,,,,,,,,,,80,,,,37,77,102,,46,,,,32,101,,,31,80',
+',100,,37,77,,,46,,52,,32,101,35,36,31,,103,100,104,,,,35,36,,52,103',
+',104,,,,35,36,102,,103,161,104,,,,,,102,80,,,,37,77,,,46,102,80,,32',
+'101,37,77,31,,46,100,80,,32,101,37,77,31,52,46,100,,,32,101,35,36,31',
+'52,103,100,104,,,,35,36,,52,103,,104,,,,35,36,102,,103,,104,,,,,,102',
+'80,,,,37,77,,,46,102,80,,32,101,37,77,31,,46,100,80,,32,101,37,77,31',
+'52,46,100,,,32,101,35,36,31,52,103,100,104,,,,35,36,,52,78,,-197,,,',
+'35,36,102,,103,,104,,,,,,63,80,,,,37,77,,,46,102,80,,32,101,37,77,31',
+',46,100,80,,32,,37,77,31,52,46,,,,32,101,35,36,31,52,103,100,104,,,',
+'35,36,,52,103,,104,,,,35,36,102,,103,,104,,,,,,102,80,,,,37,77,,,46',
+'102,80,,32,101,37,77,31,,46,100,80,,32,101,37,77,31,52,46,100,,,32,101',
+'35,36,31,52,103,100,104,,,,35,36,,52,103,,104,,,,35,36,102,,103,,104',
+',,,,,102,80,,,,37,77,,,46,102,80,,32,101,37,77,31,,46,100,80,,32,101',
+'37,77,31,52,46,100,,,32,101,35,36,31,52,103,100,104,,,,35,36,,52,103',
+',104,,,,35,36,102,,103,,104,,,,,,102,80,,,,37,77,,,46,102,80,,32,101',
+'37,77,31,,46,100,80,,32,101,37,77,31,52,46,100,,,32,101,35,36,31,52',
+'103,100,104,,,,35,36,,52,103,161,104,,,,35,36,102,,103,,104,,,,,,102',
+'80,,,,37,77,,,46,102,80,,32,101,37,77,31,,46,100,80,,32,101,37,77,31',
+'52,46,100,,,32,101,35,36,31,52,103,100,104,,,,35,36,,52,,,,,,,35,36',
+'102,,,,234,,,,35,36,,80,103,,,37,77,,,46,,80,,32,101,37,231,31,,46,100',
+'80,,32,,37,231,31,52,46,,80,,32,,37,231,31,52,46,35,36,,32,103,,104',
+'31,52,,35,36,,,103,,104,,52,,35,36,102,,,,,,,,,,102,80,,,,37,77,,,46',
+',80,,32,101,37,77,31,,46,100,80,,32,101,37,231,31,52,46,100,,,32,,35',
+'36,31,52,103,,104,,,,,,,52,,,,35,36,,,103,102,104,,,,,,,,,,80,,,,37',
+'77,102,260,46,,35,36,32,101,103,,31,80,,100,,37,77,,,46,,52,,32,101',
+'35,36,31,,103,100,104,,,,,,80,52,,,37,77,,,46,,102,,32,,210,,31,,,,',
+'80,211,,,37,77,52,,46,,,,32,101,215,210,31,,,100,,,,211,213,212,214',
+'52,203,204,206,205,208,209,,201,202,210,,,,,200,,207,211,217,216,,,203',
+'204,206,205,208,209,,201,202,215,210,,,,200,,207,,211,213,212,214,203',
+'204,206,205,208,209,,201,202,,210,,,,200,,207,,211,217,216,,,203,204',
+'206,205,208,209,,201,202,215,210,,,,200,,207,,211,213,212,214,203,204',
+'206,205,208,209,,201,202,,,,,,200,,207,,,217,216,,,203,204,206,205,208',
+'209,,201,202,215,210,,,,200,,207,,211,213,212,214,203,204,206,205,208',
+'209,,201,202,,,,,,200,,207,,,217,216,,,203,204,206,205,208,209,,201',
+'202,215,210,,,,200,,207,301,211,213,212,214,,,,,215,210,,,,,,,,211,213',
+'212,214,,,217,216,,,203,204,206,205,208,209,,201,202,,,,,,200,,207,203',
+'204,206,205,208,209,,201,202,215,210,,,,200,,207,,211,213,212,214,,',
+',,,,,,215,210,,,,,,,,211,213,212,214,,,203,204,206,205,208,209,,201',
+'202,,,,,,200,,207,217,216,,,203,204,206,205,208,209,,201,202,215,210',
+',,,200,,207,,211,213,212,214,,,,,,,,,,,,,,,,,,,,,216,,,203,204,206,205',
+'208,209,,201,202,215,210,,,,200,,207,,211,213,212,214,,,,,,,,,,,,,,',
+',,,,,217,216,,,203,204,206,205,208,209,,201,202,215,210,,,,200,,207',
+',211,213,212,214,,,,,215,210,,,,,,,,211,213,212,214,,,217,216,,,203',
+'204,206,205,208,209,,201,202,,,,,,200,,207,203,204,206,205,208,209,',
+'201,202,215,210,,,,200,,207,,211,213,212,214,,,,,,,,,,,,,,,,,,,,217',
+'216,,,203,204,206,205,208,209,,201,202,215,210,,,,200,,207,,211,213',
+'212,214,,,,,,,,,,,,,,,,,,,,217,216,,,203,204,206,205,208,209,,201,202',
+',,,,,200,,207,137,135,139,134,136,,132,140,141,148,179,,131,133,,,,',
+',142,130,143,137,135,139,134,136,,132,140,141,148,146,,131,133,,138',
+'144,,,142,130,143,137,135,139,134,136,,132,140,141,148,146,,131,133',
+',138,144,,,142,130,143,137,135,139,134,136,,132,140,141,148,179,,131',
+'133,,138,144,,,142,130,143,137,135,139,134,136,,132,140,141,148,179',
+',131,133,,138,144,,,142,130,143,137,135,139,134,136,,132,140,141,148',
+'146,,131,133,,138,144,,,142,130,143,,,,,,,,,,,,,,,,138,144' ]
+ racc_action_table = arr = ::Array.new(2588, nil)
idx = 0
clist.each do |str|
str.split(',', -1).each do |i|
@@ -141,113 +146,113 @@ class Parser < Racc::Parser
end
clist = [
-'0,0,89,89,95,69,321,27,145,27,210,322,18,184,322,89,89,196,210,196,18',
-'145,27,69,89,89,89,89,0,95,0,0,251,0,0,196,0,0,0,250,0,0,0,253,253,0',
-'27,89,89,0,253,89,89,89,89,89,89,258,89,89,0,321,321,321,321,89,0,89',
-'289,289,226,253,68,253,253,289,253,253,249,253,253,253,326,253,253,253',
-'393,393,253,68,326,191,253,393,242,242,26,226,226,247,26,191,191,253',
-'286,286,289,26,242,253,191,191,191,191,393,243,393,393,286,393,393,236',
-'393,393,393,302,393,393,393,390,390,393,235,191,191,393,390,191,191',
-'191,191,191,191,230,191,191,393,72,72,302,302,191,393,191,72,373,228',
-'390,373,390,390,221,390,390,306,390,390,390,212,390,390,390,277,277',
-'390,306,206,248,390,212,306,10,1,198,212,72,1,248,248,390,10,10,317',
-'317,254,390,248,248,248,248,277,182,277,277,182,277,277,13,277,277,277',
-'13,277,277,277,299,299,277,170,248,248,277,299,248,248,248,248,248,248',
-'166,248,248,277,376,376,262,262,248,277,248,266,266,262,299,155,299',
-'299,266,299,299,153,299,299,299,279,299,299,299,350,350,299,148,146',
-'283,299,350,376,144,103,107,376,376,142,141,376,299,367,367,376,103',
-'107,299,376,294,103,107,350,294,350,350,294,350,350,376,350,350,350',
-'140,350,350,350,173,173,350,261,261,137,350,173,367,293,261,217,367',
-'367,217,217,367,350,246,246,367,219,133,350,367,219,219,132,173,126',
-'173,173,123,173,173,367,173,173,173,122,173,173,173,245,245,173,119',
-'309,310,173,245,246,111,205,313,246,246,244,105,246,173,244,244,246',
-'177,101,173,246,177,177,100,245,319,245,245,320,245,245,246,245,245',
-'245,88,245,245,245,57,57,245,79,57,205,245,323,205,288,280,280,78,288',
-'288,280,280,245,324,324,75,74,280,245,280,324,332,205,57,57,57,57,57',
-'57,57,57,57,57,57,333,57,57,335,383,383,57,338,57,57,57,383,324,204',
-'71,70,324,324,65,63,324,57,84,84,324,355,57,57,324,58,53,21,383,360',
-'383,383,50,383,383,324,383,383,383,366,383,383,383,30,30,383,47,204',
-'368,383,204,84,84,40,372,84,84,36,84,84,383,33,377,84,28,21,383,84,21',
-'204,24,30,3,30,30,2,30,30,84,30,30,30,396,30,30,30,116,21,30,398,,,30',
-',,116,116,11,11,11,11,,,30,116,116,116,116,,30,265,265,265,265,,,,265',
-'265,52,52,52,52,,265,,265,116,116,275,,116,116,116,116,116,116,275,116',
-'116,117,7,7,7,7,116,,116,,117,117,19,19,19,19,,,,117,117,117,117,,,275',
-'275,275,275,275,275,,275,275,17,17,17,17,,275,,275,117,117,,,117,117',
-'117,117,117,117,,117,117,62,,,,,117,,117,,62,62,,,,,,,,62,62,62,62,',
-',264,264,264,264,,160,160,264,264,160,,160,,,264,,264,62,62,,,62,62',
-'62,62,62,62,160,62,62,35,35,,,35,62,35,62,160,,,,160,160,,,160,,,,160',
-'160,35,,160,181,181,160,,181,,181,,35,,160,,35,35,,,35,39,39,,35,39',
-'181,39,35,,,,,,,,,181,,35,,181,181,39,,181,,,,181,181,,,181,39,,181',
-',39,39,,,39,,181,,39,39,370,370,39,,370,39,370,,,,44,44,,39,44,,44,',
-',,,,370,,,,,,,,281,281,44,370,,281,281,370,370,,,370,281,44,281,370',
-'370,44,44,370,,44,370,,,44,44,180,180,44,370,180,44,180,,,,175,175,',
-'44,175,,175,,,,174,174,180,180,174,,174,,,,,,175,180,,,,180,180,,,180',
-'174,175,,180,180,175,175,180,,175,180,174,,175,175,174,174,175,180,174',
-'175,,,174,174,15,15,174,175,15,174,15,,,,59,59,,174,59,,59,,,,199,199',
-'15,,199,,199,,,,,,59,15,,,,15,15,,,15,199,59,,15,15,59,59,15,,59,15',
-'199,,59,59,199,199,59,15,199,59,,,199,199,64,64,199,59,64,199,64,,,',
-'172,172,,199,172,,172,,,,66,66,64,,66,66,66,,,,,,172,64,,,,64,64,,,64',
-'66,172,,64,64,172,172,64,,172,64,66,,172,172,66,66,172,64,66,172,,,66',
-'66,8,8,66,172,8,66,8,,,,171,171,,66,171,,171,,,,169,169,8,,169,,169',
-',,,,,171,8,,,,8,8,,,8,169,171,,8,8,171,171,8,,171,8,169,,171,171,169',
-'169,171,8,169,171,,,169,169,168,168,169,171,168,169,168,,,,167,167,',
-'169,167,,167,,,,330,330,168,,330,,330,,,,,,167,168,,,,168,168,,,168',
-'330,167,,168,168,167,167,168,,167,168,330,,167,167,330,330,167,168,330',
-'167,,,330,330,165,165,330,167,165,330,165,,,,77,77,,330,77,,77,,,,164',
-'164,165,,164,,164,,,,,,77,165,,,,165,165,,,165,164,77,,165,165,77,77',
-'165,,77,165,164,,77,77,164,164,77,165,164,77,,,164,164,80,80,164,77',
-'80,164,80,,,,81,81,,164,81,,81,,,,82,82,80,,82,,82,,,,,,81,80,,,,80',
-'80,,,80,82,81,,80,80,81,81,80,,81,80,82,,81,81,82,82,81,80,82,81,,,82',
-'82,163,163,82,81,163,82,163,,,,91,91,,82,91,,91,,,,,,163,,,,,,,,,,91',
-'163,92,92,,163,163,,,163,,91,,163,163,91,91,163,,91,163,,,91,91,,,91',
-'163,,91,,,,,92,92,,91,92,92,,92,92,162,162,,92,162,,162,92,,,161,161',
-',,161,,161,,92,,315,315,162,,315,,,,,,241,241,161,162,241,,,162,162',
-',,162,,161,,162,162,161,161,162,,161,162,315,,161,161,315,315,161,162',
-'315,161,241,,315,,241,241,315,161,241,104,104,,241,104,,104,241,315',
-',314,314,,,314,,314,,241,,159,159,104,,159,,159,,,,,,314,104,,,,104',
-'104,,,104,159,314,,104,104,314,314,104,,314,104,159,,314,314,159,159',
-'314,104,159,314,,,159,159,158,158,159,314,158,159,158,,,,157,157,,159',
-'157,,157,,,,110,110,158,,,,110,,,,,,157,158,,,,158,158,,,158,,157,,158',
-'158,157,157,158,,157,158,110,,157,157,110,110,157,158,110,157,,,110',
-',259,259,110,157,259,,259,,,,,,,110,,,,113,113,,,113,259,113,,,,,,,',
-',,259,,,,259,259,113,113,259,238,238,,259,259,,,259,113,,259,,113,113',
-',,113,,259,,113,113,,,113,114,114,113,,114,114,114,,238,,113,,238,238',
-',,238,284,284,,238,284,114,284,238,,,,,,,,,114,,238,,114,114,284,,114',
-',,,114,114,,,114,284,,114,,284,284,,,284,,114,,284,284,121,121,284,',
-'121,284,121,,,,291,291,,284,291,,291,,,,301,301,121,,301,,301,,,,,,291',
-'121,,,,121,121,,,121,301,291,,121,121,291,291,121,,291,121,301,,291',
-'291,301,301,291,121,301,291,,,301,301,300,300,301,291,300,301,300,,',
-',255,255,,301,255,,276,276,276,276,276,276,300,276,276,,,,,,276,,276',
-'300,,,,300,300,,,300,,255,,300,300,255,255,300,,255,300,378,378,255',
-',,,255,300,,378,378,378,378,,,,,255,,,,,,,,,,,,,,,378,378,,,378,378',
-'378,378,378,378,,378,378,189,189,,,,378,,378,,189,189,189,189,,,,,357',
-'357,,,,,,,,357,357,357,357,,,189,189,,,189,189,189,189,189,189,,189',
-'189,,,,,,189,,189,357,357,357,357,357,357,,357,357,356,356,,,,357,,357',
-',356,356,356,356,,,,,,,,,349,349,,,,,,,,349,349,349,349,,,356,356,356',
-'356,356,356,,356,356,,,269,,,356,,356,349,349,269,,349,349,349,349,349',
-'349,,349,349,268,268,,,,349,,349,,268,268,268,268,,,,269,269,269,269',
-'269,269,,269,269,,,,,,269,,269,268,,,268,268,268,268,268,268,,268,268',
-'364,364,,,,268,,268,,364,364,364,364,267,267,267,267,267,267,,267,267',
-',273,,,,267,,267,,273,364,364,,,364,364,364,364,364,364,,364,364,178',
-'178,,,,364,,364,,178,178,178,178,273,273,273,273,273,273,,273,273,,272',
-',,,273,,273,,272,178,178,,,178,178,178,178,178,178,,178,178,190,190',
-',,,178,,178,,190,190,190,190,272,272,272,272,272,272,,272,272,,,,,,272',
-',272,,,190,190,,,190,190,190,190,190,190,,190,190,202,202,,,,190,,190',
-',202,202,202,202,,,,,,,,,,,,,,,,,,,,202,202,,,202,202,202,202,202,202',
-',202,202,156,156,,,,202,,202,156,156,156,156,156,,,,,,,,,,,,,,,,,,,',
-'156,156,,,156,156,156,156,156,156,,156,156,341,341,,,,156,,156,,341',
-'341,341,341,,,,,271,271,,,,,,,,271,271,271,271,,,341,341,,,341,341,341',
-'341,341,341,,341,341,,,,,,341,,341,271,271,271,271,271,271,,271,271',
-'344,344,,,,271,,271,,344,344,344,344,,,,,,,,,,,,,,,,,,,,344,344,,,344',
-'344,344,344,344,344,,344,344,,,,,,344,,344,108,108,108,108,108,,108',
-'108,108,108,108,,108,108,,,,,,108,108,108,106,106,106,106,106,,106,106',
-'106,106,106,,106,106,,108,108,,,106,106,106,312,312,312,312,312,,312',
-'312,312,312,312,,312,312,,106,106,,,312,312,312,257,257,257,257,257',
-',257,257,257,257,257,,257,257,,312,312,,,257,257,257,256,256,256,256',
-'256,,256,256,256,256,256,,256,256,,257,257,,,256,256,256,220,220,220',
-'220,220,,220,220,220,220,220,,220,220,,256,256,,,220,220,220,,,,,,,',
-',,,,,,,,220,220' ]
- racc_action_check = arr = ::Array.new(2533, nil)
+'0,0,97,97,115,77,262,28,168,28,186,354,313,345,354,97,97,128,186,128',
+'313,168,28,77,97,97,97,97,0,115,0,0,178,0,0,128,0,0,0,177,0,0,0,391',
+'391,0,28,97,97,0,391,97,97,97,97,97,97,254,97,97,0,262,262,262,262,97',
+'0,97,304,304,191,391,68,391,391,304,391,391,49,391,391,391,49,391,391',
+'391,2,2,391,68,33,153,391,259,259,315,315,191,191,33,33,153,153,391',
+'154,154,304,259,175,391,153,153,153,153,2,263,2,2,154,2,2,264,2,2,2',
+'275,2,2,2,229,229,2,173,153,153,2,229,153,153,153,153,153,153,266,153',
+'153,2,104,104,275,275,153,2,153,104,222,171,229,222,229,229,170,229',
+'229,84,229,229,229,34,229,229,229,383,383,229,84,34,195,229,383,84,269',
+'29,288,288,104,29,195,195,229,288,310,270,29,310,229,195,195,195,195',
+'383,169,383,383,166,383,383,50,383,383,383,50,383,383,383,382,382,383',
+'185,195,195,383,382,195,195,195,195,195,195,185,195,195,383,51,185,290',
+'290,195,383,195,289,289,290,382,248,382,382,289,382,382,272,382,382',
+'382,165,382,382,382,372,372,382,272,85,326,382,372,272,326,246,164,326',
+'51,261,85,51,382,261,261,85,221,163,382,248,221,221,248,372,162,372',
+'372,201,372,372,51,372,372,372,155,372,372,372,81,81,372,248,81,246',
+'372,149,246,192,287,287,146,192,192,287,287,372,145,114,113,160,287',
+'372,287,160,160,246,81,81,81,81,81,81,81,81,81,81,81,112,81,81,306,306',
+'87,81,83,81,81,81,181,80,79,181,181,292,292,292,292,76,81,75,292,292',
+'73,81,81,71,307,292,306,292,306,306,309,306,306,249,306,306,306,69,306',
+'306,306,319,319,306,67,202,66,306,319,291,291,291,291,64,320,321,291',
+'291,306,55,55,323,324,291,306,291,53,245,244,319,48,319,319,41,319,319',
+'343,319,319,319,255,319,319,319,327,327,319,30,27,25,319,327,55,55,243',
+'23,55,55,357,55,55,319,60,60,55,360,22,319,55,362,21,364,327,366,327',
+'327,369,327,327,55,327,327,327,370,327,327,327,228,228,327,241,240,376',
+'327,228,60,60,235,225,60,60,231,60,60,327,1,394,60,396,,327,60,,,,228',
+',228,228,,228,228,60,228,228,228,,228,228,228,108,,228,,,,228,,,108',
+'108,20,20,20,20,,,228,108,108,108,108,,228,294,294,294,294,294,294,',
+'294,294,19,19,19,19,,294,,294,108,108,296,,108,108,108,108,108,108,296',
+'108,108,105,5,5,5,5,108,,108,,105,105,9,9,9,9,,,,105,105,105,105,,,296',
+'296,296,296,296,296,,296,296,7,7,7,7,,296,,296,105,105,,,105,105,105',
+'105,105,105,,105,105,107,,,,,105,,105,,107,107,286,286,,,,286,286,107',
+'107,107,107,,286,,286,,,,204,204,,,204,,204,,,,,,107,107,,,107,107,107',
+'107,107,107,204,107,107,36,36,,,36,107,36,107,204,,,,204,204,,,204,',
+',,204,204,36,,204,38,38,204,,38,,38,,36,,204,,36,36,,,36,,,,36,36,38',
+',36,39,39,36,,39,,39,,38,,36,,38,38,,,38,,,,38,38,39,,38,40,40,38,,40',
+',40,,39,,38,,39,39,,,39,,,,39,39,40,,39,214,214,39,,214,,214,,40,,39',
+',40,40,,,40,,,,40,40,214,,40,213,213,40,,213,,213,,214,,40,,214,214',
+',,214,,,,214,214,213,,214,212,212,214,,212,,212,,213,,214,,213,213,',
+',213,,,,213,213,212,,213,211,211,213,,211,,211,,212,,213,,212,212,,',
+'212,,,,212,212,211,,212,210,210,212,,210,,210,,211,,212,,211,211,,,211',
+',,,211,211,210,,211,209,209,211,,209,,209,,210,,211,,210,210,,,210,',
+',,210,210,209,,210,356,356,210,,356,,356,,209,,210,,209,209,,,209,,',
+',209,209,356,,209,63,63,209,,63,,63,,356,,209,,356,356,,,356,361,361',
+',356,356,63,63,356,,,356,,,317,317,,63,317,356,,63,63,,,63,,,,63,63',
+'208,208,63,361,208,63,208,361,361,,,361,,63,,361,317,,,361,317,317,208',
+',317,207,207,,317,207,361,207,317,208,,311,311,208,208,,,208,311,317',
+',208,208,207,,208,,,208,363,363,,,,207,,208,,207,207,,,207,,311,,207',
+'207,311,311,207,,311,207,,,311,,,,311,207,363,,,,363,363,,,363,311,',
+',363,305,305,,363,305,,305,,,,,,,,363,,,206,206,,,206,305,206,,,,,,',
+',,,305,,,,305,305,206,,305,,,,305,305,,,305,206,,305,,206,206,,,206',
+',305,,206,206,205,205,206,,205,206,205,,,,215,215,,206,215,,215,,,,78',
+'78,205,,78,78,78,,,,,,215,205,,,,205,205,,,205,78,215,,205,205,215,215',
+'205,,215,205,78,,215,215,78,78,215,205,78,215,,,78,78,203,203,78,215',
+'203,78,203,,,,371,371,,78,371,,371,,,,200,200,203,,200,,200,,,,,,371',
+'203,,,,203,203,,,203,200,371,,203,203,371,371,203,,371,203,200,,371',
+'371,200,200,371,203,200,371,,,200,200,199,199,200,371,199,200,199,,',
+',26,26,,200,26,,26,,,,251,251,199,,251,,251,,,,,,26,199,,,,199,199,',
+',199,251,26,,199,199,26,26,199,,26,199,251,,26,,251,251,26,199,251,',
+',,251,251,86,86,251,26,86,251,86,,,,252,252,,251,252,,252,,,,92,92,86',
+',92,,92,,,,,,252,86,,,,86,86,,,86,92,252,,86,86,252,252,86,,252,86,92',
+',252,252,92,92,252,86,92,252,,,92,92,93,93,92,252,93,92,93,,,,94,94',
+',92,94,,94,,,,95,95,93,,95,,95,,,,,,94,93,,,,93,93,,,93,95,94,,93,93',
+'94,94,93,,94,93,95,,94,94,95,95,94,93,95,94,,,95,95,216,216,95,94,216',
+'95,216,,,,100,100,,95,100,,100,,,,101,101,216,,101,,101,,,,,,100,216',
+',,,216,216,,,216,101,100,,216,216,100,100,216,,100,216,101,,100,100',
+'101,101,100,216,101,100,,,101,101,102,102,101,100,102,101,102,,,,103',
+'103,,101,103,103,103,,,,256,256,102,,256,,256,,,,,,103,102,,,,102,102',
+',,102,256,103,,102,102,103,103,102,,103,102,256,,103,103,256,256,103',
+'102,256,103,,,256,256,217,217,256,103,217,256,217,,,,234,234,,256,,',
+',,,,109,109,217,,,,109,,,,265,265,,217,265,,,217,217,,,217,,234,,217',
+'217,234,234,217,,234,217,109,,234,,109,109,234,217,109,,265,,109,,265',
+'265,109,234,265,276,276,,265,276,,276,265,109,,277,277,,,277,,277,,265',
+',230,230,276,,,,,,,,,,277,276,,,,276,276,,,276,,277,,276,276,277,277',
+'276,,277,276,230,,277,277,230,230,277,276,230,277,,,230,,159,159,230',
+'277,159,,159,,,,,,,230,,,,158,158,,,158,159,158,,,,,,,,,,159,,,,159',
+'159,158,158,159,,157,157,159,159,157,,159,158,,159,,158,158,,,158,,159',
+',158,158,62,62,158,,62,158,62,,,,,,157,158,,,157,157,,,157,,62,,157',
+',298,,157,,,,,62,298,,,62,62,157,,62,,,,62,62,335,335,62,,,62,,,,335',
+'335,335,335,62,298,298,298,298,298,298,,298,298,295,,,,,298,,298,295',
+'335,335,,,335,335,335,335,335,335,,335,335,390,390,,,,335,,335,,390',
+'390,390,390,295,295,295,295,295,295,,295,295,,297,,,,295,,295,,297,390',
+'390,,,390,390,390,390,390,390,,390,390,156,156,,,,390,,390,,156,156',
+'156,156,297,297,297,297,297,297,,297,297,,,,,,297,,297,,,156,156,,,156',
+'156,156,156,156,156,,156,156,194,194,,,,156,,156,,194,194,194,194,293',
+'293,293,293,293,293,,293,293,,,,,,293,,293,,,194,194,,,194,194,194,194',
+'194,194,,194,194,220,220,,,,194,,194,220,220,220,220,220,,,,,348,348',
+',,,,,,,348,348,348,348,,,220,220,,,220,220,220,220,220,220,,220,220',
+',,,,,220,,220,348,348,348,348,348,348,,348,348,349,349,,,,348,,348,',
+'349,349,349,349,,,,,,,,,333,333,,,,,,,,333,333,333,333,,,349,349,349',
+'349,349,349,,349,349,,,,,,349,,349,333,333,,,333,333,333,333,333,333',
+',333,333,300,300,,,,333,,333,,300,300,300,300,,,,,,,,,,,,,,,,,,,,,300',
+',,300,300,300,300,300,300,,300,300,352,352,,,,300,,300,,352,352,352',
+'352,,,,,,,,,,,,,,,,,,,,352,352,,,352,352,352,352,352,352,,352,352,196',
+'196,,,,352,,352,,196,196,196,196,,,,,299,299,,,,,,,,299,299,299,299',
+',,196,196,,,196,196,196,196,196,196,,196,196,,,,,,196,,196,299,299,299',
+'299,299,299,,299,299,193,193,,,,299,,299,,193,193,193,193,,,,,,,,,,',
+',,,,,,,,,193,193,,,193,193,193,193,193,193,,193,193,332,332,,,,193,',
+'193,,332,332,332,332,,,,,,,,,,,,,,,,,,,,332,332,,,332,332,332,332,332',
+'332,,332,332,,,,,,332,,332,268,268,268,268,268,,268,268,268,268,268',
+',268,268,,,,,,268,268,268,180,180,180,180,180,,180,180,180,180,180,',
+'180,180,,268,268,,,180,180,180,61,61,61,61,61,,61,61,61,61,61,,61,61',
+',180,180,,,61,61,61,267,267,267,267,267,,267,267,267,267,267,,267,267',
+',61,61,,,267,267,267,82,82,82,82,82,,82,82,82,82,82,,82,82,,267,267',
+',,82,82,82,250,250,250,250,250,,250,250,250,250,250,,250,250,,82,82',
+',,250,250,250,,,,,,,,,,,,,,,,250,250' ]
+ racc_action_check = arr = ::Array.new(2588, nil)
idx = 0
clist.each do |str|
str.split(',', -1).each do |i|
@@ -257,326 +262,312 @@ class Parser < Racc::Parser
end
racc_action_pointer = [
- -2, 146, 461, 494, nil, nil, nil, 508, 1023, nil,
- 175, 464, nil, 171, nil, 889, nil, 540, -3, 518,
- nil, 455, nil, nil, 500, nil, 60, 1, 486, nil,
- 471, nil, nil, 468, nil, 676, 464, nil, nil, 718,
- 460, nil, nil, nil, 775, nil, nil, 474, nil, nil,
- 456, nil, 486, 432, nil, nil, nil, 385, 449, 899,
- nil, nil, 616, 422, 956, 421, 976, nil, 66, -1,
- 418, 417, 145, nil, 385, 384, nil, 1167, 376, 384,
- 1224, 1234, 1244, nil, 446, nil, nil, nil, 359, -2,
- nil, 1301, 1325, nil, nil, -8, nil, nil, nil, nil,
- 371, 358, nil, 244, 1439, 351, 2376, 245, 2354, nil,
- 1526, 341, nil, 1590, 1640, nil, 508, 562, nil, 335,
- nil, 1704, 305, 325, nil, nil, 289, nil, nil, nil,
- nil, nil, 308, 311, nil, nil, nil, 283, nil, nil,
- 286, 250, 249, nil, 244, -3, 239, nil, 238, nil,
- nil, nil, nil, 244, nil, 221, 2207, 1516, 1506, 1459,
- 651, 1377, 1367, 1291, 1177, 1157, 167, 1100, 1090, 1043,
- 156, 1033, 966, 299, 842, 832, nil, 358, 2072, nil,
- 822, 701, 192, nil, 2, nil, nil, nil, nil, 1854,
- 2117, 84, nil, nil, nil, nil, 11, nil, 146, 909,
- nil, nil, 2162, nil, 437, 352, 168, nil, nil, nil,
- -8, nil, 144, nil, nil, nil, nil, 266, nil, 315,
- 2464, 149, nil, nil, nil, nil, 48, nil, 144, nil,
- 91, nil, nil, nil, nil, 121, 97, nil, 1615, nil,
- nil, 1397, 83, 92, 351, 342, 317, 97, 170, 63,
- 14, 20, nil, 41, 185, 1781, 2442, 2420, 48, 1573,
- nil, 244, 175, nil, 595, 487, 180, 2004, 1982, 1962,
- nil, 2269, 2094, 2049, nil, 541, 1736, 170, nil, 245,
- 342, 742, nil, 256, 1657, nil, 93, nil, 389, 66,
- nil, 1714, nil, 302, 247, nil, nil, nil, nil, 213,
- 1771, 1724, 100, nil, nil, nil, 140, nil, nil, 336,
- 340, nil, 2398, 346, 1449, 1387, nil, 181, nil, 348,
- 353, -2, -20, 385, 403, nil, 70, nil, nil, nil,
- 1110, nil, 378, 401, nil, 420, nil, nil, 421, nil,
- nil, 2252, nil, nil, 2314, nil, nil, nil, nil, 1937,
- 256, nil, nil, nil, nil, 442, 1916, 1871, nil, nil,
- 450, nil, nil, nil, 2027, nil, 460, 274, 469, nil,
- 765, nil, 476, 124, nil, nil, 231, 484, 1809, nil,
- nil, nil, nil, 428, nil, nil, nil, nil, nil, nil,
- 127, nil, nil, 84, nil, nil, 503, nil, 510, nil,
+ -2, 490, 84, nil, nil, 507, nil, 539, nil, 517,
+ nil, nil, nil, nil, nil, nil, nil, nil, nil, 485,
+ 463, 447, 428, 417, nil, 428, 1304, 425, 1, 146,
+ 388, nil, nil, 84, 153, nil, 675, nil, 700, 725,
+ 750, 395, nil, nil, nil, nil, nil, nil, 413, 42,
+ 171, 231, nil, 411, nil, 402, nil, nil, nil, nil,
+ 445, 2453, 1832, 950, 386, nil, 368, 366, 66, 359,
+ nil, 345, nil, 359, nil, 339, 337, -1, 1180, 330,
+ 346, 299, 2497, 339, 140, 238, 1361, 337, nil, nil,
+ nil, nil, 1381, 1428, 1438, 1448, nil, -2, nil, nil,
+ 1505, 1515, 1562, 1572, 145, 561, nil, 615, 507, 1649,
+ nil, nil, 328, 297, 296, -8, nil, nil, nil, nil,
+ nil, nil, nil, nil, nil, nil, nil, nil, 11, nil,
+ nil, nil, nil, nil, nil, nil, nil, nil, nil, nil,
+ nil, nil, nil, nil, nil, 307, 261, nil, nil, 297,
+ nil, nil, nil, 84, 93, 274, 1954, 1811, 1785, 1768,
+ 315, nil, 264, 257, 228, 242, 184, nil, -3, 178,
+ 138, 133, nil, 109, nil, 102, nil, 16, 20, nil,
+ 2431, 305, nil, nil, nil, 194, -8, nil, nil, nil,
+ nil, 48, 303, 2324, 1999, 170, 2262, nil, nil, 1294,
+ 1247, 228, 328, 1227, 650, 1160, 1113, 1022, 997, 900,
+ 875, 850, 825, 800, 775, 1170, 1495, 1629, nil, nil,
+ 2044, 272, 146, nil, nil, 472, nil, nil, 470, 127,
+ 1721, 462, nil, nil, 1639, 471, nil, nil, nil, nil,
+ 474, 467, nil, 404, 376, 404, 266, nil, 242, 369,
+ 2519, 1314, 1371, nil, 34, 400, 1582, nil, nil, 82,
+ nil, 265, -2, 108, 112, 1659, 134, 2475, 2409, 172,
+ 180, nil, 226, nil, nil, 100, 1701, 1711, nil, nil,
+ nil, nil, nil, nil, nil, nil, 579, 256, 123, 180,
+ 175, 341, 303, 1976, 486, 1886, 540, 1931, 1842, 2279,
+ 2172, nil, nil, nil, 66, 1096, 341, 360, nil, 366,
+ 160, 1032, nil, 0, nil, 84, nil, 980, nil, 384,
+ 374, 388, nil, 391, 399, nil, 227, 427, nil, nil,
+ nil, nil, 2369, 2127, nil, 1864, nil, nil, nil, nil,
+ nil, nil, nil, 409, nil, 4, nil, nil, 2061, 2106,
+ nil, nil, 2217, nil, -20, nil, 925, 435, nil, nil,
+ 442, 967, 445, 1050, 447, nil, 449, nil, nil, 436,
+ 433, 1237, 256, nil, nil, nil, 468, nil, nil, nil,
+ nil, nil, 213, 170, nil, nil, nil, nil, nil, nil,
+ 1909, 41, nil, nil, 482, nil, 484, nil, nil, nil,
nil ]
racc_action_default = [
- -206, -241, -74, -241, -18, -7, -19, -8, -241, -124,
- -221, -20, -9, -198, -186, -241, -98, -10, -239, -241,
- -11, -241, -177, -12, -241, -129, -241, -178, -39, -13,
- -1, -229, -182, -241, -96, -97, -41, -14, -2, -241,
- -42, -15, -3, -181, -241, -43, -16, -241, -185, -183,
- -45, -17, -6, -206, -198, -197, -75, -109, -221, -241,
- -142, -141, -241, -239, -241, -59, -241, -66, -60, -97,
- -63, -61, -241, -64, -58, -67, -62, -241, -68, -65,
- -241, -241, -241, -49, -241, -46, -47, -48, -206, -241,
- -125, -241, -241, -203, -205, -206, -199, -202, -201, -204,
- -241, -241, -73, -206, -241, -77, -109, -206, -119, -4,
- -241, -53, -54, -241, -241, -134, -241, -241, -188, -190,
- -187, -241, -207, -241, -208, -25, -94, -29, -27, -92,
- -35, -113, -241, -227, -23, -31, -110, -100, -33, -32,
- -241, -114, -101, -21, -103, -97, -34, -104, -99, -28,
- -102, -26, -22, -105, -30, -24, -241, -241, -241, -241,
+ -206, -241, -1, -2, -3, -6, -7, -8, -9, -10,
+ -11, -12, -13, -14, -15, -16, -17, -18, -19, -20,
+ -241, -39, -41, -42, -43, -45, -97, -241, -178, -241,
+ -74, -96, -98, -221, -239, -124, -241, -129, -241, -241,
+ -241, -241, -177, -181, -182, -183, -185, -186, -241, -241,
+ -198, -241, -229, -241, -4, -241, -46, -47, -48, -49,
+ -241, -119, -241, -241, -53, -54, -58, -59, -60, -61,
+ -62, -63, -64, -65, -66, -67, -68, -97, -241, -239,
+ -221, -109, -109, -77, -206, -206, -241, -241, -73, -197,
+ -198, -75, -241, -241, -241, -241, -125, -241, -141, -142,
+ -241, -241, -241, -241, -241, -241, -134, -241, -241, -241,
+ -187, -188, -190, -206, -206, -206, -199, -201, -202, -203,
+ -204, -205, 401, -37, -38, -39, -40, -44, -97, -36,
+ -21, -22, -23, -24, -25, -26, -27, -28, -29, -30,
+ -31, -32, -33, -34, -35, -227, -112, -113, -114, -241,
+ -117, -118, -120, -241, -241, -52, -56, -241, -241, -241,
+ -241, -224, -24, -34, -94, -227, -241, -92, -97, -99,
+ -100, -101, -102, -103, -104, -105, -110, -114, -227, -112,
+ -119, -241, -80, -81, -83, -206, -241, -89, -90, -97,
+ -221, -241, -241, -106, -108, -241, -107, -126, -127, -241,
-241, -241, -241, -241, -241, -241, -241, -241, -241, -241,
- -241, -241, -241, -241, -241, -241, -160, -241, -56, -224,
- -241, -241, -241, -233, -241, -236, -237, -232, -153, -106,
- -108, -241, -40, -44, -37, -39, -97, -38, -206, -241,
- -127, -126, -107, -36, -241, -241, -241, -215, 401, -83,
- -241, -221, -206, -90, -97, -89, -80, -241, -81, -241,
- -119, -227, -114, -112, -34, -24, -241, -117, -227, -118,
- -112, -120, -184, -178, -180, -241, -97, -179, -241, -171,
- -172, -241, -241, -52, -241, -241, -241, -241, -241, -214,
- -241, -227, -211, -241, -241, -95, -109, -228, -241, -241,
- -163, -149, -148, -143, -151, -152, -150, -155, -162, -157,
- -144, -161, -159, -156, -145, -158, -154, -5, -133, -241,
- -146, -147, -225, -226, -241, -222, -241, -123, -241, -241,
- -230, -241, -238, -241, -241, -217, -128, -200, -216, -241,
- -241, -241, -241, -78, -86, -85, -241, -226, -131, -227,
- -241, -79, -228, -241, -241, -241, -174, -227, -55, -226,
- -50, -222, -137, -241, -241, -165, -241, -169, -189, -240,
- -241, -209, -228, -241, -192, -241, -69, -93, -227, -111,
- -70, -115, -132, -223, -57, -122, -130, -234, -231, -235,
- -241, -219, -218, -220, -196, -241, -87, -88, -84, -82,
- -241, -71, -121, -72, -116, -176, -226, -241, -241, -51,
- -241, -136, -241, -137, -166, -164, -241, -241, -213, -212,
- -210, -191, -91, -241, -194, -195, -76, -175, -173, -138,
- -241, -135, -170, -241, -193, -140, -241, -168, -241, -139,
+ -241, -241, -241, -241, -241, -241, -241, -241, -153, -160,
+ -241, -241, -241, -232, -233, -241, -236, -237, -241, -241,
+ -241, -97, -171, -172, -241, -241, -178, -179, -180, -184,
+ -241, -241, -208, -207, -206, -241, -241, -215, -241, -241,
+ -228, -241, -241, -240, -50, -226, -241, -225, -55, -241,
+ -123, -241, -222, -226, -241, -95, -241, -228, -109, -241,
+ -227, -78, -241, -85, -86, -241, -241, -241, -79, -131,
+ -226, -238, -128, -143, -144, -145, -146, -147, -148, -149,
+ -150, -151, -152, -154, -155, -156, -157, -158, -159, -161,
+ -162, -163, -222, -230, -241, -241, -5, -241, -133, -241,
+ -137, -241, -165, -241, -169, -227, -174, -241, -189, -241,
+ -241, -227, -211, -214, -241, -217, -241, -241, -200, -216,
+ -72, -121, -116, -115, -51, -57, -122, -130, -223, -69,
+ -93, -70, -111, -227, -71, -241, -82, -84, -87, -88,
+ -231, -234, -235, -132, -137, -136, -241, -241, -164, -166,
+ -241, -241, -241, -241, -226, -176, -241, -192, -209, -241,
+ -228, -241, -241, -218, -219, -220, -241, -196, -91, -76,
+ -135, -138, -241, -241, -170, -173, -175, -191, -210, -212,
+ -213, -241, -194, -195, -241, -140, -241, -168, -193, -139,
-167 ]
racc_goto_table = [
- 27, 30, 112, 115, 22, 109, 279, 53, 235, 252,
- 183, 133, 240, 50, 118, 147, 227, 90, 371, 88,
- 258, 228, 94, 129, 96, 325, 101, 177, 123, 194,
- 27, 192, 38, 84, 22, 102, 217, 203, 32, 192,
- 226, 92, 43, 50, 193, 105, 324, 362, 359, 239,
- 201, 317, 193, 254, 119, 126, 293, 142, 95, 206,
- 221, 137, 367, 198, 298, 219, 250, 251, 32, 391,
- 153, 111, 43, 352, 242, 244, 182, 24, 323, nil,
- nil, nil, nil, nil, 27, 124, 335, 18, 22, nil,
- nil, nil, 27, 197, nil, 32, 22, 50, nil, 43,
- nil, 197, nil, 374, nil, 50, nil, 213, 310, nil,
- 233, 213, nil, nil, 22, 313, nil, 18, nil, nil,
- 124, nil, 32, nil, nil, 28, 43, 207, 227, nil,
- 32, nil, 355, 309, 43, nil, 235, nil, 333, nil,
- 316, 286, 288, nil, 327, 302, nil, nil, 32, nil,
- nil, nil, 43, nil, nil, 28, nil, nil, nil, nil,
- nil, nil, nil, nil, nil, 339, nil, 270, nil, nil,
- nil, 274, nil, 27, nil, nil, nil, 22, nil, nil,
- nil, nil, nil, nil, nil, nil, 50, nil, nil, nil,
- nil, nil, nil, nil, nil, nil, 360, 232, nil, nil,
- nil, 296, nil, nil, 368, 94, 94, 297, 318, 195,
- 338, 32, nil, 147, 328, 43, 213, 195, nil, 379,
- 227, 337, 327, 396, nil, 382, 398, 347, nil, nil,
- 295, nil, nil, nil, nil, nil, nil, 45, 233, nil,
- nil, nil, 22, nil, nil, 27, 233, nil, nil, 22,
- 22, nil, 109, 27, nil, 142, nil, 22, 50, 137,
- 18, nil, nil, nil, nil, 235, 50, 45, 153, 387,
- nil, nil, nil, nil, 392, nil, 32, 27, nil, nil,
- 43, 22, 365, 32, 32, nil, nil, 43, 43, nil,
- 50, 32, nil, 32, 150, 43, nil, 43, 28, 27,
- 351, nil, nil, 22, nil, nil, nil, nil, nil, nil,
- 213, nil, 50, nil, nil, 32, nil, nil, nil, 43,
- nil, 45, nil, nil, 233, 232, nil, nil, 22, 45,
- nil, nil, 18, 232, 389, nil, nil, 32, nil, nil,
- 18, 43, nil, nil, nil, nil, nil, nil, nil, nil,
- 27, 383, nil, nil, 22, 40, nil, nil, 109, 36,
- nil, nil, 32, 50, 18, nil, 43, 233, nil, nil,
- 28, 22, nil, nil, nil, nil, 233, nil, 28, nil,
- 22, nil, nil, 27, nil, 40, 18, 22, 32, 36,
- 27, nil, 43, 27, 22, nil, 50, 22, nil, nil,
- nil, nil, 28, 50, nil, 32, 50, nil, nil, 43,
- 45, 232, 148, nil, 32, 49, 144, nil, 43, nil,
- nil, 32, nil, nil, 28, 43, nil, 186, 32, nil,
- nil, 32, 43, nil, nil, 43, nil, 18, nil, 40,
- nil, nil, nil, 36, nil, 49, nil, 40, nil, nil,
- nil, 36, nil, nil, 232, nil, nil, nil, nil, nil,
- nil, nil, 215, 232, nil, 237, 215, nil, nil, 234,
- 18, nil, 49, nil, nil, 28, nil, 18, nil, nil,
- 18, nil, 45, nil, nil, nil, nil, nil, nil, nil,
- 45, nil, 150, nil, nil, nil, nil, nil, nil, 49,
- nil, nil, nil, nil, nil, nil, nil, 49, 28, nil,
- nil, nil, nil, nil, 45, 28, nil, nil, 28, nil,
- nil, nil, nil, nil, nil, 49, nil, nil, 40, nil,
- nil, nil, 36, nil, nil, nil, 45, nil, nil, nil,
- nil, nil, nil, nil, nil, nil, nil, nil, nil, nil,
- nil, nil, nil, nil, nil, nil, nil, nil, nil, nil,
+ 28, 2, 28, 42, 224, 42, 54, 65, 106, 233,
+ 113, 114, 235, 121, 116, 44, 174, 44, 43, 111,
+ 43, 249, 167, 96, 307, 309, 3, 322, 145, 87,
+ 25, 88, 25, 165, 178, 176, 176, 83, 312, 355,
+ 123, 266, 181, 191, 311, 129, 127, 331, 126, 154,
+ 24, 127, 24, 126, 269, 28, 346, 124, 42, 232,
+ 28, 197, 124, 42, 160, 55, 60, 241, 244, 315,
+ 44, 264, 192, 43, 112, 44, 164, 324, 43, 115,
+ 245, 171, 363, 380, 170, 25, 329, 188, 188, 221,
+ 25, 320, 321, 64, 373, 222, 44, 1, nil, 43,
+ nil, nil, nil, nil, nil, 24, nil, nil, nil, 236,
+ 24, 175, 42, nil, nil, 366, nil, nil, 34, 359,
+ 34, nil, nil, 376, 44, nil, nil, 43, nil, nil,
+ nil, 172, nil, 314, 316, nil, nil, 235, 258, 242,
+ 242, 247, nil, 275, 259, 261, 345, 270, nil, nil,
+ nil, nil, nil, nil, 389, nil, nil, nil, nil, nil,
+ nil, nil, nil, 284, 285, nil, nil, nil, nil, nil,
+ nil, nil, nil, nil, nil, nil, nil, nil, 394, 396,
+ nil, nil, nil, nil, nil, nil, 282, nil, 188, nil,
+ nil, 362, nil, nil, nil, nil, nil, 369, nil, nil,
+ 174, nil, nil, nil, 351, nil, 340, nil, 121, 328,
+ 121, 318, nil, nil, 314, nil, nil, nil, nil, 378,
+ 343, 342, 176, nil, nil, nil, nil, 239, 28, 28,
+ 236, 42, 42, 42, 236, nil, nil, 42, nil, nil,
+ nil, nil, nil, 44, 44, 44, 43, 43, 43, 44,
+ nil, nil, 43, nil, nil, nil, nil, nil, 25, 25,
+ nil, nil, nil, 386, 384, 171, 235, nil, 170, nil,
+ 325, nil, nil, nil, nil, 188, nil, nil, 24, 24,
+ 44, nil, nil, 43, 23, nil, 23, 374, nil, nil,
+ nil, nil, nil, nil, nil, 175, nil, nil, 365, 45,
+ nil, 45, nil, nil, nil, nil, 28, nil, nil, 42,
+ 54, 236, nil, nil, 42, 172, nil, nil, nil, 28,
+ nil, 44, 42, nil, 43, 381, 44, 28, nil, 43,
+ 42, nil, nil, nil, 44, nil, 25, 43, nil, 23,
+ nil, nil, 44, nil, 23, 43, 34, 34, 239, 25,
+ nil, nil, 239, nil, 45, nil, 24, 25, nil, 45,
+ nil, 236, nil, 236, 42, 169, 42, nil, 22, 24,
+ 22, nil, 28, 391, nil, 42, 44, 24, 44, 43,
+ 45, 43, 28, 28, nil, 42, 42, 44, 227, nil,
+ 43, 28, nil, 237, 42, 54, nil, 44, 44, nil,
+ 43, 43, 25, 21, nil, 21, 44, nil, 45, 43,
+ nil, nil, 25, 25, nil, nil, nil, nil, nil, nil,
+ nil, 25, 24, 22, 34, nil, nil, nil, 22, 239,
+ nil, nil, 24, 24, nil, nil, nil, 34, nil, nil,
+ nil, 24, nil, nil, nil, 34, nil, nil, nil, 173,
+ nil, nil, 187, 187, nil, nil, nil, nil, 125, nil,
+ nil, nil, nil, 125, nil, nil, nil, nil, nil, nil,
+ nil, nil, nil, nil, nil, nil, nil, 238, nil, 239,
+ nil, 239, nil, nil, nil, nil, nil, nil, nil, nil,
+ 34, nil, nil, nil, nil, nil, nil, nil, nil, nil,
+ 34, 34, nil, nil, nil, nil, nil, nil, nil, 34,
+ nil, nil, 23, 23, 237, nil, nil, nil, 237, nil,
+ nil, nil, nil, nil, nil, nil, nil, 45, 45, 45,
+ nil, nil, nil, 45, nil, nil, nil, nil, nil, nil,
+ nil, nil, nil, nil, nil, nil, nil, nil, nil, 169,
+ nil, nil, nil, 187, nil, nil, nil, nil, nil, nil,
+ nil, nil, nil, nil, 45, nil, nil, nil, nil, nil,
nil, nil, nil, nil, nil, nil, nil, nil, nil, nil,
- nil, 215, nil, nil, nil, nil, nil, nil, nil, nil,
- nil, nil, nil, nil, nil, nil, nil, 45, 49, nil,
- nil, nil, nil, 237, nil, nil, nil, 234, nil, nil,
- 40, 237, nil, nil, 36, 234, nil, nil, 40, nil,
- 148, nil, 36, nil, 144, nil, nil, nil, nil, nil,
- 45, nil, nil, nil, nil, nil, nil, 45, nil, nil,
- 45, nil, 40, nil, nil, nil, 36, nil, nil, nil,
- nil, nil, nil, nil, 186, nil, nil, nil, nil, nil,
- nil, nil, nil, 49, 40, nil, nil, nil, 36, nil,
- 49, 49, nil, nil, nil, 215, nil, nil, 49, nil,
- 49, nil, nil, nil, nil, nil, nil, nil, nil, 237,
- nil, 62, nil, 234, nil, nil, nil, nil, 89, nil,
- nil, nil, 49, nil, nil, nil, nil, nil, nil, nil,
- nil, nil, nil, nil, nil, 40, nil, nil, nil, 36,
- nil, nil, 116, nil, 49, nil, nil, 117, nil, nil,
- nil, nil, 237, nil, nil, nil, 234, nil, nil, nil,
- nil, 237, 156, nil, nil, 234, nil, 176, 40, 49,
- nil, nil, 36, nil, nil, 40, nil, nil, 40, 36,
- 188, nil, 36, 189, 190, 191, nil, nil, nil, nil,
- nil, nil, nil, nil, 202, 49, nil, nil, nil, nil,
+ nil, nil, nil, nil, nil, nil, nil, nil, 227, nil,
+ 23, nil, nil, nil, nil, 237, 22, 22, 238, nil,
+ nil, nil, 238, 23, nil, 45, nil, nil, nil, nil,
+ 45, 23, nil, nil, nil, nil, nil, nil, 45, nil,
+ nil, nil, nil, nil, nil, nil, 45, nil, nil, nil,
+ nil, 21, 21, 173, nil, 97, nil, 105, 107, 108,
+ 187, nil, nil, nil, nil, 237, nil, 237, nil, nil,
+ nil, nil, nil, nil, nil, nil, 23, nil, nil, nil,
+ 45, 153, 45, nil, nil, nil, 23, 23, nil, nil,
+ nil, 45, nil, nil, 22, 23, nil, nil, nil, 238,
+ nil, 45, 45, nil, nil, nil, nil, 22, nil, nil,
+ 45, 193, 194, 195, 196, 22, nil, nil, nil, 218,
+ 219, 220, nil, nil, nil, nil, nil, nil, nil, 21,
nil, nil, nil, nil, nil, nil, nil, nil, nil, nil,
- nil, nil, 49, nil, nil, nil, nil, nil, nil, nil,
- nil, 49, nil, nil, 248, nil, nil, nil, 49, nil,
- nil, nil, nil, nil, nil, 49, nil, nil, 49, nil,
+ nil, nil, 21, nil, nil, nil, nil, nil, nil, 238,
+ 21, 238, nil, nil, nil, nil, nil, nil, nil, nil,
+ 22, nil, nil, nil, nil, nil, nil, nil, nil, nil,
+ 22, 22, nil, nil, nil, nil, nil, nil, nil, 22,
nil, nil, nil, nil, nil, nil, nil, nil, nil, nil,
+ nil, nil, nil, nil, nil, 21, nil, nil, nil, nil,
+ nil, nil, nil, nil, nil, 21, 21, nil, nil, nil,
+ nil, nil, nil, nil, 21, nil, nil, nil, 97, 283,
+ nil, nil, 286, 287, 288, 289, 290, 291, 292, 293,
+ 294, 295, 296, 297, 298, 299, 300, nil, nil, nil,
nil, nil, nil, nil, nil, nil, nil, nil, nil, nil,
- 261, 262, 263, 264, 265, 266, 267, 268, 269, nil,
- 271, 272, 273, nil, 275, 276, nil, 280, 281, nil,
nil, nil, nil, nil, nil, nil, nil, nil, nil, nil,
nil, nil, nil, nil, nil, nil, nil, nil, nil, nil,
- nil, nil, 89, nil, nil, nil, nil, nil, nil, nil,
+ 332, 333, nil, nil, nil, 335, nil, nil, nil, nil,
nil, nil, nil, nil, nil, nil, nil, nil, nil, nil,
+ nil, nil, nil, nil, nil, 348, 349, 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, nil, 352, 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, nil, 341, nil, nil, nil, nil, nil, nil, nil,
nil, nil, nil, nil, nil, nil, nil, nil, nil, nil,
- nil, nil, nil, nil, nil, nil, nil, 344, nil, nil,
- nil, nil, nil, nil, 349, nil, nil, nil, nil, nil,
- nil, nil, nil, 356, 357, nil, nil, nil, nil, nil,
- nil, nil, nil, nil, nil, nil, nil, 364, nil, nil,
nil, nil, nil, nil, nil, nil, nil, nil, nil, nil,
- nil, nil, nil, 378, nil, nil, nil, nil, nil, nil,
+ nil, nil, nil, nil, nil, 107, 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, nil, nil, nil, nil, nil, nil, nil, nil,
- nil, nil, nil, 116 ]
+ 390 ]
racc_goto_check = [
- 35, 2, 31, 62, 34, 4, 5, 40, 69, 83,
- 86, 43, 71, 28, 73, 37, 56, 60, 63, 40,
- 44, 45, 65, 53, 79, 67, 35, 29, 75, 22,
- 35, 8, 3, 23, 34, 6, 48, 22, 36, 8,
- 48, 23, 38, 28, 10, 47, 66, 59, 49, 70,
- 61, 72, 10, 42, 74, 41, 76, 35, 77, 78,
- 43, 34, 33, 75, 80, 29, 81, 82, 36, 63,
- 28, 30, 38, 84, 29, 29, 85, 1, 5, nil,
- nil, nil, nil, nil, 35, 3, 5, 55, 34, nil,
- nil, nil, 35, 6, nil, 36, 34, 28, nil, 38,
- nil, 6, nil, 67, nil, 28, nil, 34, 44, nil,
- 35, 34, nil, nil, 34, 44, nil, 55, nil, nil,
- 3, nil, 36, nil, nil, 24, 38, 3, 56, nil,
- 36, nil, 5, 45, 38, nil, 69, nil, 44, nil,
- 71, 29, 29, nil, 69, 48, nil, nil, 36, nil,
- nil, nil, 38, nil, nil, 24, nil, nil, nil, nil,
- nil, nil, nil, nil, nil, 56, nil, 65, nil, nil,
- nil, 65, nil, 35, nil, nil, nil, 34, nil, nil,
- nil, nil, nil, nil, nil, nil, 28, nil, nil, nil,
- nil, nil, nil, nil, nil, nil, 44, 55, nil, nil,
- nil, 60, nil, nil, 44, 65, 65, 79, 31, 24,
- 43, 36, nil, 37, 73, 38, 34, 24, nil, 83,
- 56, 53, 69, 5, nil, 44, 5, 86, nil, nil,
- 3, nil, nil, nil, nil, nil, nil, 27, 35, nil,
- nil, nil, 34, nil, nil, 35, 35, nil, nil, 34,
- 34, nil, 4, 35, nil, 35, nil, 34, 28, 34,
- 55, nil, nil, nil, nil, 69, 28, 27, 28, 71,
- nil, nil, nil, nil, 69, nil, 36, 35, nil, nil,
- 38, 34, 31, 36, 36, nil, nil, 38, 38, nil,
- 28, 36, nil, 36, 27, 38, nil, 38, 24, 35,
- 40, nil, nil, 34, nil, nil, nil, nil, nil, nil,
- 34, nil, 28, nil, nil, 36, nil, nil, nil, 38,
- nil, 27, nil, nil, 35, 55, nil, nil, 34, 27,
- nil, nil, 55, 55, 62, nil, nil, 36, nil, nil,
- 55, 38, nil, nil, nil, nil, nil, nil, nil, nil,
- 35, 2, nil, nil, 34, 26, nil, nil, 4, 25,
- nil, nil, 36, 28, 55, nil, 38, 35, nil, nil,
- 24, 34, nil, nil, nil, nil, 35, nil, 24, nil,
- 34, nil, nil, 35, nil, 26, 55, 34, 36, 25,
- 35, nil, 38, 35, 34, nil, 28, 34, nil, nil,
- nil, nil, 24, 28, nil, 36, 28, nil, nil, 38,
- 27, 55, 26, nil, 36, 39, 25, nil, 38, nil,
- nil, 36, nil, nil, 24, 38, nil, 26, 36, nil,
- nil, 36, 38, nil, nil, 38, nil, 55, nil, 26,
- nil, nil, nil, 25, nil, 39, nil, 26, nil, nil,
- nil, 25, nil, nil, 55, nil, nil, nil, nil, nil,
- nil, nil, 25, 55, nil, 26, 25, nil, nil, 25,
- 55, nil, 39, nil, nil, 24, nil, 55, nil, nil,
- 55, nil, 27, nil, nil, nil, nil, nil, nil, nil,
- 27, nil, 27, nil, nil, nil, nil, nil, nil, 39,
- nil, nil, nil, nil, nil, nil, nil, 39, 24, nil,
- nil, nil, nil, nil, 27, 24, nil, nil, 24, nil,
- nil, nil, nil, nil, nil, 39, nil, nil, 26, nil,
- nil, nil, 25, nil, nil, nil, 27, nil, nil, nil,
- nil, nil, nil, nil, nil, nil, nil, nil, nil, nil,
+ 35, 2, 35, 34, 86, 34, 4, 31, 62, 71,
+ 40, 40, 69, 65, 79, 36, 37, 36, 38, 73,
+ 38, 44, 53, 60, 5, 5, 3, 83, 45, 35,
+ 28, 6, 28, 43, 43, 56, 56, 47, 67, 63,
+ 22, 44, 48, 48, 66, 22, 10, 59, 8, 29,
+ 27, 10, 27, 8, 44, 35, 49, 6, 34, 70,
+ 35, 61, 6, 34, 29, 23, 23, 75, 75, 72,
+ 36, 42, 29, 38, 74, 36, 41, 76, 38, 77,
+ 78, 35, 33, 63, 34, 28, 80, 34, 34, 29,
+ 28, 81, 82, 30, 84, 85, 36, 1, nil, 38,
+ nil, nil, nil, nil, nil, 27, nil, nil, nil, 35,
+ 27, 28, 34, nil, nil, 5, nil, nil, 55, 67,
+ 55, nil, nil, 5, 36, nil, nil, 38, nil, nil,
+ nil, 27, nil, 69, 71, nil, nil, 69, 31, 3,
+ 3, 3, nil, 48, 29, 29, 44, 45, nil, nil,
+ nil, nil, nil, nil, 83, nil, nil, nil, nil, nil,
+ nil, nil, nil, 65, 65, nil, nil, nil, nil, nil,
+ nil, nil, nil, nil, nil, nil, nil, nil, 5, 5,
+ nil, nil, nil, nil, nil, nil, 60, nil, 34, nil,
+ nil, 44, nil, nil, nil, nil, nil, 44, nil, nil,
+ 37, nil, nil, nil, 86, nil, 53, nil, 65, 79,
+ 65, 73, nil, nil, 69, nil, nil, nil, nil, 44,
+ 43, 56, 56, nil, nil, nil, nil, 55, 35, 35,
+ 35, 34, 34, 34, 35, nil, nil, 34, nil, nil,
+ nil, nil, nil, 36, 36, 36, 38, 38, 38, 36,
+ nil, nil, 38, nil, nil, nil, nil, nil, 28, 28,
+ nil, nil, nil, 71, 69, 35, 69, nil, 34, nil,
+ 3, nil, nil, nil, nil, 34, nil, nil, 27, 27,
+ 36, nil, nil, 38, 26, nil, 26, 40, nil, nil,
+ nil, nil, nil, nil, nil, 28, nil, nil, 31, 39,
+ nil, 39, nil, nil, nil, nil, 35, nil, nil, 34,
+ 4, 35, nil, nil, 34, 27, nil, nil, nil, 35,
+ nil, 36, 34, nil, 38, 62, 36, 35, nil, 38,
+ 34, nil, nil, nil, 36, nil, 28, 38, nil, 26,
+ nil, nil, 36, nil, 26, 38, 55, 55, 55, 28,
+ nil, nil, 55, nil, 39, nil, 27, 28, nil, 39,
+ nil, 35, nil, 35, 34, 26, 34, nil, 25, 27,
+ 25, nil, 35, 2, nil, 34, 36, 27, 36, 38,
+ 39, 38, 35, 35, nil, 34, 34, 36, 26, nil,
+ 38, 35, nil, 26, 34, 4, nil, 36, 36, nil,
+ 38, 38, 28, 24, nil, 24, 36, nil, 39, 38,
+ nil, nil, 28, 28, nil, nil, nil, nil, nil, nil,
+ nil, 28, 27, 25, 55, nil, nil, nil, 25, 55,
+ nil, nil, 27, 27, nil, nil, nil, 55, nil, nil,
+ nil, 27, nil, nil, nil, 55, nil, nil, nil, 25,
+ nil, nil, 25, 25, nil, nil, nil, nil, 24, nil,
+ nil, nil, nil, 24, nil, nil, nil, nil, nil, nil,
+ nil, nil, nil, nil, nil, nil, nil, 25, nil, 55,
+ nil, 55, nil, nil, nil, nil, nil, nil, nil, nil,
+ 55, nil, nil, nil, nil, nil, nil, nil, nil, nil,
+ 55, 55, nil, nil, nil, nil, nil, nil, nil, 55,
+ nil, nil, 26, 26, 26, nil, nil, nil, 26, nil,
+ nil, nil, nil, nil, nil, nil, nil, 39, 39, 39,
+ nil, nil, nil, 39, nil, nil, nil, nil, nil, nil,
+ nil, nil, nil, nil, nil, nil, nil, nil, nil, 26,
+ nil, nil, nil, 25, nil, nil, nil, nil, nil, nil,
+ nil, nil, nil, nil, 39, nil, nil, nil, nil, nil,
nil, nil, nil, nil, nil, nil, nil, nil, nil, nil,
+ nil, nil, nil, nil, nil, nil, nil, nil, 26, nil,
+ 26, nil, nil, nil, nil, 26, 25, 25, 25, nil,
+ nil, nil, 25, 26, nil, 39, nil, nil, nil, nil,
+ 39, 26, nil, nil, nil, nil, nil, nil, 39, nil,
+ nil, nil, nil, nil, nil, nil, 39, nil, nil, nil,
+ nil, 24, 24, 25, nil, 32, nil, 32, 32, 32,
+ 25, nil, nil, nil, nil, 26, nil, 26, nil, nil,
+ nil, nil, nil, nil, nil, nil, 26, nil, nil, nil,
+ 39, 32, 39, nil, nil, nil, 26, 26, nil, nil,
+ nil, 39, nil, nil, 25, 26, nil, nil, nil, 25,
+ nil, 39, 39, nil, nil, nil, nil, 25, nil, nil,
+ 39, 32, 32, 32, 32, 25, nil, nil, nil, 32,
+ 32, 32, nil, nil, nil, nil, nil, nil, nil, 24,
nil, nil, nil, nil, nil, nil, nil, nil, nil, nil,
- nil, 25, nil, nil, nil, nil, nil, nil, nil, nil,
- nil, nil, nil, nil, nil, nil, nil, 27, 39, nil,
- nil, nil, nil, 26, nil, nil, nil, 25, nil, nil,
- 26, 26, nil, nil, 25, 25, nil, nil, 26, nil,
- 26, nil, 25, nil, 25, nil, nil, nil, nil, nil,
- 27, nil, nil, nil, nil, nil, nil, 27, nil, nil,
- 27, nil, 26, nil, nil, nil, 25, nil, nil, nil,
- nil, nil, nil, nil, 26, nil, nil, nil, nil, nil,
- nil, nil, nil, 39, 26, nil, nil, nil, 25, nil,
- 39, 39, nil, nil, nil, 25, nil, nil, 39, nil,
- 39, nil, nil, nil, nil, nil, nil, nil, nil, 26,
- nil, 32, nil, 25, nil, nil, nil, nil, 32, nil,
- nil, nil, 39, nil, nil, nil, nil, nil, nil, nil,
- nil, nil, nil, nil, nil, 26, nil, nil, nil, 25,
- nil, nil, 32, nil, 39, nil, nil, 32, nil, nil,
- nil, nil, 26, nil, nil, nil, 25, nil, nil, nil,
- nil, 26, 32, nil, nil, 25, nil, 32, 26, 39,
- nil, nil, 25, nil, nil, 26, nil, nil, 26, 25,
- 32, nil, 25, 32, 32, 32, nil, nil, nil, nil,
- nil, nil, nil, nil, 32, 39, nil, nil, nil, nil,
+ nil, nil, 24, nil, nil, nil, nil, nil, nil, 25,
+ 24, 25, nil, nil, nil, nil, nil, nil, nil, nil,
+ 25, nil, nil, nil, nil, nil, nil, nil, nil, nil,
+ 25, 25, nil, nil, nil, nil, nil, nil, nil, 25,
nil, nil, nil, nil, nil, nil, nil, nil, nil, nil,
- nil, nil, 39, nil, nil, nil, nil, nil, nil, nil,
- nil, 39, nil, nil, 32, nil, nil, nil, 39, nil,
- nil, nil, nil, nil, nil, 39, nil, nil, 39, nil,
+ nil, nil, nil, nil, nil, 24, nil, nil, nil, nil,
+ nil, nil, nil, nil, nil, 24, 24, nil, nil, nil,
+ nil, nil, nil, nil, 24, nil, nil, nil, 32, 32,
+ nil, nil, 32, 32, 32, 32, 32, 32, 32, 32,
+ 32, 32, 32, 32, 32, 32, 32, nil, nil, nil,
nil, nil, nil, nil, nil, nil, nil, nil, nil, nil,
nil, nil, nil, nil, nil, nil, nil, nil, nil, nil,
- 32, 32, 32, 32, 32, 32, 32, 32, 32, nil,
- 32, 32, 32, nil, 32, 32, nil, 32, 32, nil,
nil, nil, nil, nil, nil, nil, nil, nil, nil, nil,
+ 32, 32, nil, nil, nil, 32, nil, nil, nil, nil,
nil, nil, nil, nil, nil, nil, nil, nil, nil, nil,
- nil, nil, 32, nil, nil, nil, nil, nil, nil, nil,
+ nil, nil, nil, nil, nil, 32, 32, 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, 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, nil,
- nil, nil, 32, nil, nil, nil, nil, nil, nil, nil,
- nil, nil, nil, nil, nil, nil, nil, nil, nil, nil,
- nil, nil, nil, nil, nil, nil, nil, 32, nil, nil,
nil, nil, nil, nil, 32, nil, nil, nil, nil, nil,
- nil, nil, nil, 32, 32, nil, nil, nil, nil, nil,
- nil, nil, nil, nil, nil, nil, nil, 32, nil, nil,
nil, nil, nil, nil, nil, nil, nil, nil, nil, nil,
- nil, nil, nil, 32, 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,
nil, nil, nil, nil, nil, nil, nil, nil, nil, nil,
- nil, nil, nil, 32 ]
+ nil, nil, nil, nil, nil, 32, nil, nil, nil, nil,
+ nil, nil, nil, nil, nil, nil, nil, nil, nil, nil,
+ 32 ]
racc_goto_pointer = [
- nil, 77, 1, 32, -25, -167, 9, nil, -53, nil,
- -40, nil, nil, nil, nil, nil, nil, nil, nil, nil,
- nil, nil, -55, 22, 125, 359, 355, 237, 13, -39,
- 36, -33, 673, -255, 4, 0, 38, -42, 42, 415,
- 6, -2, -73, -46, -113, -87, nil, 18, -67, -258,
- nil, nil, nil, -34, nil, 87, -92, nil, nil, -265,
- 2, -39, -36, -304, nil, 1, -200, -221, nil, -102,
- -61, -98, -187, -33, 7, -25, -142, 37, -36, 3,
- -141, -56, -55, -113, -221, 4, -62, nil ]
+ nil, 97, 1, 26, 4, -204, 2, nil, -7, nil,
+ -9, nil, nil, nil, nil, nil, nil, nil, nil, nil,
+ nil, nil, -15, 46, 403, 368, 284, 50, 30, -14,
+ 67, -19, 599, -233, 3, 0, 15, -65, 18, 299,
+ -39, -5, -93, -48, -124, -33, nil, 9, -42, -216,
+ nil, nil, nil, -59, nil, 118, -46, nil, nil, -203,
+ -13, -36, -31, -271, nil, -38, -186, -192, nil, -97,
+ -50, -100, -165, -29, 26, -46, -167, 28, -35, -37,
+ -162, -152, -151, -216, -232, -9, -100, nil ]
racc_goto_default = [
- nil, nil, 277, 216, 42, nil, 52, 5, 7, 12,
- 17, 20, 23, 29, 37, 41, 46, 51, 4, 6,
- 11, 131, 19, nil, 67, 70, 74, 76, 79, nil,
- nil, 61, 178, 284, 65, 68, 71, 73, 75, 78,
- 3, nil, nil, nil, nil, nil, 26, nil, nil, 218,
- 306, 209, 210, nil, 132, 63, 136, 140, 229, 231,
- nil, nil, nil, nil, 60, 14, nil, nil, 326, 33,
+ nil, nil, 306, 182, 4, nil, 5, 6, 7, 8,
+ 9, 10, 11, 12, 13, 14, 15, 16, 17, 18,
+ 19, 147, 20, nil, 74, 71, 66, 70, 73, nil,
+ nil, 98, 156, 256, 67, 68, 69, 72, 75, 76,
+ 27, nil, nil, nil, nil, nil, 29, nil, nil, 183,
+ 272, 184, 186, nil, 166, 79, 150, 149, 151, 152,
+ nil, nil, nil, nil, 99, 47, nil, nil, 313, 41,
nil, nil, nil, nil, nil, nil, nil, nil, nil, nil,
- 98, nil, nil, nil, nil, nil, nil, 184 ]
+ 117, nil, nil, nil, nil, nil, nil, 225 ]
racc_reduce_table = [
0, 0, :racc_error,
@@ -1089,7 +1080,7 @@ class Parser < Racc::Parser
module_eval(<<'.,.,', 'grammar.ra', 34)
def _reduce_3(val, _values, result)
- result = ast AST::ASTArray, :children => (val[0] ? [val[0]] : [])
+ result = ast AST::BlockExpression, :children => (val[0] ? [val[0]] : [])
result
end
@@ -2083,7 +2074,7 @@ def _reduce_168(val, _values, result)
AST::CaseOpt,
:value => val[0],
- :statements => ast(AST::ASTArray)
+ :statements => ast(AST::BlockExpression)
)
result
diff --git a/lib/puppet/parser/parser_factory.rb b/lib/puppet/parser/parser_factory.rb
new file mode 100644
index 0000000..bf80c12
--- /dev/null
+++ b/lib/puppet/parser/parser_factory.rb
@@ -0,0 +1,62 @@
+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)
+ 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
+
+ # 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
+ 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 =>e
+ 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/parser_support.rb b/lib/puppet/parser/parser_support.rb
index c7c0057..69226a5 100644
--- a/lib/puppet/parser/parser_support.rb
+++ b/lib/puppet/parser/parser_support.rb
@@ -38,6 +38,11 @@ def aryfy(arg)
ast AST::ASTArray, :children => [arg]
end
+ # Create an AST block containing a single element
+ def block(arg)
+ ast AST::BlockExpression, :children => [arg]
+ end
+
# Create an AST object, and automatically add the file and line information if
# available.
def ast(klass, hash = {})
@@ -146,9 +151,10 @@ def parse(string = nil)
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, except)
+ raise Puppet::ParseError.new(except.message, @lexer.file, @lexer.line, nil, except)
end
end
# Store the results as the top-level class.
@@ -166,6 +172,6 @@ def parse_ruby_file
main_object.instance_eval(File.read(self.file))
# Then extract any types that were created.
- Puppet::Parser::AST::ASTArray.new :children => main_object.instance_eval { @__created_ast_objects__ }
+ Puppet::Parser::AST::BlockExpression.new :children => main_object.instance_eval { @__created_ast_objects__ }
end
end
diff --git a/lib/puppet/parser/scope.rb b/lib/puppet/parser/scope.rb
index de6abbb..d6fade4 100644
--- a/lib/puppet/parser/scope.rb
+++ b/lib/puppet/parser/scope.rb
@@ -2,7 +2,7 @@
# such.
require 'forwardable'
-require 'puppet/parser/parser'
+require 'puppet/parser'
require 'puppet/parser/templatewrapper'
require 'puppet/resource/type_collection_helper'
@@ -42,9 +42,10 @@ class Puppet::Parser::Scope
class Ephemeral
extend Forwardable
- def initialize(parent=nil)
+ def initialize(parent=nil, local=false)
@symbols = {}
@parent = parent
+ @local_scope = local
end
def_delegators :@symbols, :include?, :delete, :[]=
@@ -56,6 +57,13 @@ def [](name)
@parent[name]
end
end
+ def is_local_scope?
+ @local_scope
+ end
+ # @return [Ephemeral, Hash, nil]
+ def parent
+ @parent
+ end
end
# Initialize a new scope suitable for parser function testing. This method
@@ -171,7 +179,8 @@ def initialize(compiler, options = {})
# for $0..$xy capture variables of regexes
# this is actually implemented as a stack, with each ephemeral scope
# shadowing the previous one
- @ephemeral = [ Ephemeral.new ]
+ # TODO: UPDATE YARDOC
+ @ephemeral = [ Ephemeral.new(@symtable) ]
# 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
@@ -259,7 +268,11 @@ def lookupvar(name, options = {})
# Save the originating scope for the request
options[:origin] = self unless options[:origin]
- table = ephemeral?(name) ? @ephemeral.last : @symtable
+
+ # TODO: Cleanup comments/dead code, new design, the ephemeral refers to @symtable as "parent"
+ #
+ table = @ephemeral.last
+ # table = ephemeral?(name) ? @ephemeral.last : @symtable
if name =~ /^(.*)::(.+)$/
begin
@@ -270,9 +283,16 @@ def lookupvar(name, options = {})
nil
end
# If the value is present and either we are top/node scope or originating scope...
- elsif (ephemeral_include?(name) or table.include?(name)) and (compiler and self == compiler.topscope or (resource and resource.type == "Node") or self == options[:origin])
+ # TODO: This is F* up. If the name is an ephemeral name, table = the last ephemeral, otherwise it
+ # is the @symtable. This means that it ephmeral_include and table.include will produce the same answer
+ # unless it is possible to manually assign $0, $1 etc.
+ # ephemeral_include searches all ephemerals, but does not lookup in symtable, whereas [] on ephemeral
+ # searches up the ephemeral chain (including the scode's @symtable).
+ # TODO: change and/or use to && / || since and/or evaluates lhs and rhs
+ #
+ elsif (ephemeral_include?(name) || @symtable.include?(name)) && (compiler && self == compiler.topscope || (resource && resource.type == "Node") || self == options[:origin])
table[name]
- elsif resource and resource.type == "Class" and parent_type = resource.resource_type.parent
+ elsif resource and resource.type == "Class" && parent_type = resource.resource_type.parent
qualified_scope(parent_type).lookupvar(name, options.merge({:origin => nil}))
elsif parent
parent.lookupvar(name, options)
@@ -361,11 +381,20 @@ def define_settings(type, params)
# It's preferred that you use self[]= instead of this; only use this
# when you need to set options.
def setvar(name, value, options = {})
+ if name =~ /^[0-9]+$/
+ raise Puppet::ParseError.new("Cannot assign to a numeric match result variable '$#{name}'") unless options[:ephemeral]
+ end
unless name.is_a? String
raise Puppet::DevError, "Scope variable name is a #{name.class}, not a string"
end
-
- table = options[:ephemeral] ? @ephemeral.last : @symtable
+ # TODO: consider removing possibility to pass :ephemeral option (only used within this class)
+ #
+ table = effective_symtable options[:ephemeral]
+ # table = options[:ephemeral] || @ephemeral.last.is_local_scope?() ? @ephemeral.last : @symtable
+
+ # TODO: F* up design, this may write in the shadow of other ephemeral scopes...
+ # this because :ephemeral an be given as option to write in topmost/last ephemeral while
+ # the real scope to write in is a local ephemeral or the scope's symtable.
if table.include?(name)
if options[:append]
error = Puppet::ParseError.new("Cannot append, variable #{name} is defined in this scope")
@@ -379,9 +408,26 @@ def setvar(name, value, options = {})
if options[:append]
table[name] = append_value(undef_as('', self[name]), value)
- else
+ else
table[name] = value
end
+ table[name]
+ end
+
+ # 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 [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
+
+ 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
@@ -407,7 +453,7 @@ def append_value(bound_value, new_value)
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"
+ raise ArgumentError, "Trying to append to a hash with something which is not a hash is unsupported"
end
bound_value + new_value
end
@@ -425,7 +471,7 @@ def to_s
# remove ephemeral scope up to level
def unset_ephemeral_var(level=:all)
if level == :all
- @ephemeral = [ Ephemeral.new ]
+ @ephemeral = [ Ephemeral.new(@symtable)]
else
# If we ever drop 1.8.6 and lower, this should be replaced by a single
# pop-with-a-count - or if someone more ambitious wants to monkey-patch
@@ -436,32 +482,41 @@ def unset_ephemeral_var(level=:all)
end
end
- # check if name exists in one of the ephemeral scope.
+ # check if name exists in one of the ephemeral scopes.
def ephemeral_include?(name)
@ephemeral.any? {|eph| eph.include?(name) }
end
- # is name an ephemeral variable?
+ # 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)
- name =~ /^\d+$/
+ @ephemeral.last.is_local_scope? || name =~ /^\d+$/
end
def ephemeral_level
@ephemeral.size
end
- def new_ephemeral
- @ephemeral.push(Ephemeral.new(@ephemeral.last))
+ def new_ephemeral(local_scope = false)
+ @ephemeral.push(Ephemeral.new(@ephemeral.last, local_scope))
end
def ephemeral_from(match, file = nil, line = nil)
- raise(ArgumentError,"Invalid regex match data") unless match.is_a?(MatchData)
-
- new_ephemeral
-
- 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)
+ case match
+ when Hash
+ # Create local scope ephemeral and set all values from hash
+ new_ephemeral true
+ match.each {|k,v| setvar(k, v, :file => file, :line => line, :ephemeral => true) }
+ 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
end
end
diff --git a/lib/puppet/parser/type_loader.rb b/lib/puppet/parser/type_loader.rb
index fdb6ced..852d474 100644
--- a/lib/puppet/parser/type_loader.rb
+++ b/lib/puppet/parser/type_loader.rb
@@ -1,6 +1,7 @@
require 'find'
require 'forwardable'
require 'puppet/node/environment'
+require 'puppet/parser/parser_factory'
class Puppet::Parser::TypeLoader
extend Forwardable
@@ -137,7 +138,8 @@ def try_load_fqname(type, fqname)
def parse_file(file)
Puppet.debug("importing '#{file}' in environment #{environment}")
- parser = Puppet::Parser::Parser.new(environment)
+# parser = Puppet::Parser::Parser.new(environment)
+ parser = Puppet::Parser::ParserFactory.parser(environment)
parser.file = file
return parser.parse
end
diff --git a/lib/puppet/pops.rb b/lib/puppet/pops.rb
new file mode 100644
index 0000000..b0a36aa
--- /dev/null
+++ b/lib/puppet/pops.rb
@@ -0,0 +1,40 @@
+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/model/model'
+
+ 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 Parser
+ require 'puppet/pops/parser/eparser'
+ require 'puppet/pops/parser/parser_support'
+ require 'puppet/pops/parser/lexer'
+ end
+
+ module Validation
+ require 'puppet/pops/validation/checker3_1'
+ require 'puppet/pops/validation/validator_factory_3_1'
+ end
+ end
+end
diff --git a/lib/puppet/pops/adaptable.rb b/lib/puppet/pops/adaptable.rb
new file mode 100644
index 0000000..86cc97a
--- /dev/null
+++ b/lib/puppet/pops/adaptable.rb
@@ -0,0 +1,190 @@
+# Adaptable is a mix-in module that adds adaptability to a class.
+# This means that an adapter can
+# associate itself with an instance of the class and store additional data/have behavior.
+#
+# This mechanism should be used when there is a desire to keep implementation concerns separate.
+# In Ruby it is always possible to open and modify a class or instance to teach it new tricks, but it
+# is however not possible to do this for two different versions of some service at the same time.
+# The Adaptable pattern is also good when only a few of the objects of some class needs to have extra
+# information (again possible in Ruby by adding instance variables dynamically). In fact, the implementation
+# of Adaptable does just that; it adds an instance variable named after the adapter class and keeps an
+# instance of this class in this slot.
+#
+# @note the implementation details; the fact that an instance variable is used to keep the adapter
+# instance data should not
+# be exploited as the implementation of _being adaptable_ may change in the future.
+# @api private
+#
+module Puppet::Pops::Adaptable
+ # Base class for an Adapter.
+ #
+ # A typical adapter just defines some accessors.
+ #
+ # A more advanced adapter may need to setup the adapter based on the object it is adapting.
+ # @example Making Duck adaptable
+ # class Duck
+ # include Puppet::Pops::Adaptable
+ # end
+ # @example Giving a Duck a nick name
+ # class NickNameAdapter < Puppet::Pops::Adaptable::Adapter
+ # attr_accessor :nick_name
+ # end
+ # d = Duck.new
+ # NickNameAdapter.adapt(d).nick_name = "Daffy"
+ # NickNameAdapter.get(d).nick_name # => "Daffy"
+ #
+ # @example Giving a Duck a more elaborate nick name
+ # class NickNameAdapter < Puppet::Pops::Adaptable::Adapter
+ # attr_accessor :nick_name, :object
+ # def initialize o
+ # @object = o
+ # @nick_name = "Yo"
+ # end
+ # def nick_name
+ # "#{@nick_name}, the #{o.class.name}"
+ # end
+ # def NickNameAdapter.create_adapter(o)
+ # x = new o
+ # x
+ # end
+ # end
+ # d = Duck.new
+ # n = NickNameAdapter.adapt(d)
+ # n.nick_name # => "Yo, the Duck"
+ # n.nick_name = "Daffy"
+ # n.nick_name # => "Daffy, the Duck"
+ # @example Using a block to set values
+ # NickNameAdapter.adapt(o) { |a| a.nick_name = "Buddy!" }
+ # NickNameAdapter.adapt(o) { |a, o| a.nick_name = "You're the best #{o.class.name} I met."}
+ #
+ class Adapter
+ # Returns an existing adapter for the given object, or nil, if the object is not
+ # adapted.
+ #
+ # @param o [Adaptable] object to get adapter from
+ # @return [Adapter<self>] an adapter of the same class as the receiver of #get
+ # @return [nil] if the given object o has not been adapted by the receiving adapter
+ # @raise [ArgumentError] if the object is not adaptable
+ #
+ def self.get(o)
+ attr_name = :"@#{instance_var_name(self.name)}"
+ if existing = o.instance_variable_defined?(attr_name)
+ o.instance_variable_get(attr_name)
+ else
+ nil
+ end
+ end
+
+ # Returns an existing adapter for the given object, or creates a new adapter if the
+ # object has not been adapted, or the adapter has been cleared.
+ #
+ # @example Using a block to set values
+ # NickNameAdapter.adapt(o) { |a| a.nick_name = "Buddy!" }
+ # NickNameAdapter.adapt(o) { |a, o| a.nick_name = "Your the best #{o.class.name} I met."}
+ # @overload adapt(o)
+ # @overload adapt(o, {|adapter| block})
+ # @overload adapt(o, {|adapter, o| block})
+ # @param o [Adaptable] object to add adapter to
+ # @yieldparam adapter [Adapter<self>] the created adapter
+ # @yieldparam o [Adaptable] optional, the given adaptable
+ # @param block [Proc] optional, evaluated in the context of the adapter (existing or new)
+ # @return [Adapter<self>] an adapter of the same class as the receiver of the call
+ # @raise [ArgumentError] if the given object o is not adaptable
+ #
+ def self.adapt(o, &block)
+ attr_name = :"@#{instance_var_name(self.name)}"
+ adapter = if existing = o.instance_variable_defined?(attr_name) && value = o.instance_variable_get(attr_name)
+ value
+ else
+ associate_adapter(create_adapter(o), o)
+ end
+ if block_given?
+ case block.arity
+ when 1
+ block.call(adapter)
+ else
+ block.call(adapter, o)
+ end
+ end
+ adapter
+ end
+
+ # Creates a new adapter, associates it with the given object and returns the adapter.
+ #
+ # @example Using a block to set values
+ # NickNameAdapter.adapt_new(o) { |a| a.nick_name = "Buddy!" }
+ # NickNameAdapter.adapt_new(o) { |a, o| a.nick_name = "Your the best #{o.class.name} I met."}
+ # This is used when a fresh adapter is wanted instead of possible returning an
+ # existing adapter as in the case of {Adapter.adapt}.
+ # @overload adapt_new(o)
+ # @overload adapt_new(o, {|adapter| block})
+ # @overload adapt_new(o, {|adapter, o| block})
+ # @yieldparam adapter [Adapter<self>] the created adapter
+ # @yieldparam o [Adaptable] optional, the given adaptable
+ # @param o [Adaptable] object to add adapter to
+ # @param block [Proc] optional, evaluated in the context of the new adapter
+ # @return [Adapter<self>] an adapter of the same class as the receiver of the call
+ # @raise [ArgumentError] if the given object o is not adaptable
+ #
+ def self.adapt_new(o, &block)
+ adapter = associate_adapter(create_adapter(o), o)
+ if block_given?
+ case block.arity
+ when 1
+ block.call(adapter)
+ else
+ block.call(adapter, o)
+ end
+ end
+ adapter
+ end
+
+ # Clears the adapter set in the given object o. Returns any set adapter or nil.
+ # @param o [Adaptable] the object where the adapter should be cleared
+ # @return [Adapter] if an adapter was set
+ # @return [nil] if the adapter has not been set
+ #
+ def self.clear(o)
+ attr_name = :"@#{instance_var_name(self.name)}"
+ if o.instance_variable_defined?(attr_name)
+ o.send(:remove_instance_variable, attr_name)
+ else
+ nil
+ end
+ end
+
+ # This base version creates an instance of the class (i.e. an instance of the concrete subclass
+ # of Adapter). A Specialization may want to create an adapter instance specialized for the given target
+ # object.
+ # @param o [Adaptable] The object to adapt. This implementation ignores this variable, but a
+ # specialization may want to initialize itself differently depending on the object it is adapting.
+ # @return [Adapter<self>] instance of the subclass of Adapter receiving the call
+ #
+ def self.create_adapter(o)
+ new
+ end
+
+ # Associates the given adapter with the given target object
+ # @param adapter [Adapter] the adapter to associate with the given object _o_
+ # @param o [Adaptable] the object to adapt
+ # @return [adapter] the given adapter
+ #
+ def self.associate_adapter(adapter, o)
+ attr_name = :"@#{instance_var_name(adapter.class.name)}"
+ o.instance_variable_set(attr_name, adapter)
+ adapter
+ end
+
+ # Returns a suitable instance variable name given a class name.
+ # The returned string is the fully qualified name of a class with '::' replaced by '_' since
+ # '::' is not allowed in an instance variable name.
+ # @param name [String] the fully qualified name of a class
+ # @return [String] the name with all '::' replaced by '_'
+ # @api private
+ # @private
+ #
+ def self.instance_var_name(name)
+ name.split("::").join('_')
+ end
+ end
+end
diff --git a/lib/puppet/pops/adapters.rb b/lib/puppet/pops/adapters.rb
new file mode 100644
index 0000000..07b3a1c
--- /dev/null
+++ b/lib/puppet/pops/adapters.rb
@@ -0,0 +1,65 @@
+# 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
+ # 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).
+ #
+ # @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
+
+ # @return [Fixnum] The position on the start_line (in characters) starting from 0
+ attr_accessor :pos
+
+ # @return [Fixnum] The (start) offset of source text characters
+ # (starting from 0) representing the adapted object.
+ # Value may be nil
+ attr_accessor :offset
+
+ # @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
+ 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/containment.rb b/lib/puppet/pops/containment.rb
new file mode 100644
index 0000000..a019044
--- /dev/null
+++ b/lib/puppet/pops/containment.rb
@@ -0,0 +1,37 @@
+# FIXME: This module should be updated when a newer version of RGen (>0.6.2) adds required meta model "e-method" supports.
+#
+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
+ 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)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/puppet/pops/issues.rb b/lib/puppet/pops/issues.rb
new file mode 100644
index 0000000..1440c90
--- /dev/null
+++ b/lib/puppet/pops/issues.rb
@@ -0,0 +1,258 @@
+# 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)
+ # Evaluate the message block in the msg data's binding
+ msgdata.format(hash, &message_block)
+ 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.
+ #
+ def label
+ raise "Label provider key :label must be set to produce the text of the message!" unless @data[:label]
+ @data[:label]
+ 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 contain 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
+
+ # 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
+
+ # 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
+
+ # 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 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
+ #
+ 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
+end
diff --git a/lib/puppet/pops/label_provider.rb b/lib/puppet/pops/label_provider.rb
new file mode 100644
index 0000000..e8a75a7
--- /dev/null
+++ b/lib/puppet/pops/label_provider.rb
@@ -0,0 +1,71 @@
+# 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
+
+ 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
new file mode 100644
index 0000000..a9ce994
--- /dev/null
+++ b/lib/puppet/pops/model/ast_transformer.rb
@@ -0,0 +1,636 @@
+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<Puppet::Parser::AST>] 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
+
+ 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)
+ @@transform_visitor.visit_this(self,o)
+ 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)
+ 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::ArithmeticOperator, :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 == :'+='
+
+ 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] = transform(o.parent) unless is_nop?(o.parent)
+ 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_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
+ result = 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 a if !x
+ args.merge!({:else => transform(o.else_expr)}) unless is_nop?(o.else_expr)
+ result = 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
new file mode 100644
index 0000000..ffe8fd8
--- /dev/null
+++ b/lib/puppet/pops/model/ast_tree_dumper.rb
@@ -0,0 +1,378 @@
+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
+ 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 n = 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
new file mode 100644
index 0000000..ec2b055
--- /dev/null
+++ b/lib/puppet/pops/model/factory.rb
@@ -0,0 +1,807 @@
+# 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
+
+ # Shared build_visitor, since there are many instances of Factory being used
+ @@build_visitor = Puppet::Pops::Visitor.new(self, "build")
+ # 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)
+ 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
+
+ # 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<Model::Parameter>] may be empty
+ # @param parent_class_name [String, nil] a valid classname referencing a parent class, optional.
+ # @param body [Array<Expression>, 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<Model::Parameter>] may be empty
+ # # @param body [Array<Expression>, 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)
+ 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)) }
+ 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
+ # @param body [nil] unchanged, produces nil
+ # @param body [Array<Expression>] turns into a BlockExpression
+ # @param body [Expression] produces the given expression
+ # @param body [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)
+ 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)
+ o.name = name
+ o
+ end
+
+ # @param o [Model::NodeDefinition]
+ # @param hosts [Array<Expression>] 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_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
+
+ 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
+ end
+
+ # Records the position (start -> end) and computes the resulting length.
+ #
+ def record_position(start_pos, end_pos)
+ unless start_pos.offset
+ require 'debugger'; debugger; 1
+ end
+ 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
+ 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
+ # @returns [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 a 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 defauls for the referenced type
+ # * `Name[] { }` => `:override`, ioverrides nstances 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)
+ 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.QNAME_OR_NUMBER(name)
+ if n_radix = Puppet::Pops::Utils.to_n_with_radix(name)
+ new(Model::LiteralNumber, *n_radix)
+ 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)
+ 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
+
+ # 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])
+ end
+ else
+ memo << expr
+ end
+ if expr.is_a?(Model::CallNamedFunctionExpression)
+ # patch expression function call to statement style
+ # TODO: This is kind of meaningless, but to make it compatible...
+ expr.rval_required = false
+ 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.value = o;
+ x
+ end
+
+ def build_Float(o)
+ x = Model::LiteralNumber.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
+
+ # Checks if the object is already a model object, or build it
+ def to_ops(o, *args)
+ if o.kind_of?(Model::PopsObject)
+ o
+ else
+ build(o, *args)
+ end
+ end
+end
diff --git a/lib/puppet/pops/model/model.rb b/lib/puppet/pops/model/model.rb
new file mode 100644
index 0000000..82a9490
--- /dev/null
+++ b/lib/puppet/pops/model/model.rb
@@ -0,0 +1,567 @@
+#
+# 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 a 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
+ # 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
+ 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.
+ class NamedDefinition < Definition
+ abstract
+ has_attr 'name', String, :lowerBound => 1
+ 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
+ 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
+
+ # 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
+
+ # A Literal String
+ #
+ class LiteralString < LiteralValue; 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
+
+ # 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
+ has_attr 'radix', Integer, :lowerBound => 1, :defaultValueLiteral => "10"
+ end
+
+ # The DSL `undef`.
+ #
+ class LiteralUndef < Literal; end
+
+ # The DSL `default`
+ class LiteralDefault < Literal; end
+
+ # DSL `true` or `false`
+ class LiteralBoolean < LiteralValue; 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
+
+ # 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
+
+ # 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
+ 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
+ end
+end
diff --git a/lib/puppet/pops/model/model_label_provider.rb b/lib/puppet/pops/model/model_label_provider.rb
new file mode 100644
index 0000000..b48e7b4
--- /dev/null
+++ b/lib/puppet/pops/model/model_label_provider.rb
@@ -0,0 +1,75 @@
+# 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.
+ def label o
+ @@label_visitor.visit(o)
+ 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_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_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_QualifiedName o ; "Name" end
+ def label_QualifiedReference o ; "Type Name" end
+
+end
diff --git a/lib/puppet/pops/model/model_tree_dumper.rb b/lib/puppet/pops/model/model_tree_dumper.rb
new file mode 100644
index 0000000..24d341c
--- /dev/null
+++ b/lib/puppet/pops/model/model_tree_dumper.rb
@@ -0,0 +1,352 @@
+# 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
+ 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
+
+ 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/model/tree_dumper.rb b/lib/puppet/pops/model/tree_dumper.rb
new file mode 100644
index 0000000..f0a7aba
--- /dev/null
+++ b/lib/puppet/pops/model/tree_dumper.rb
@@ -0,0 +1,59 @@
+# Base class for formatted textual dump of a "model"
+#
+class Puppet::Pops::Model::TreeDumper
+ attr_accessor :indent_count
+ def initialize initial_indentation = 0
+ @@dump_visitor ||= Puppet::Pops::Visitor.new(nil,"dump",0,0)
+ @indent_count = initial_indentation
+ end
+
+ def dump(o)
+ format(do_dump(o))
+ end
+
+ def do_dump(o)
+ @@dump_visitor.visit_this(self, o)
+ end
+
+ def indent
+ " " * indent_count
+ end
+
+ def format(x)
+ result = ""
+ parts = format_r(x)
+ parts.each_index do |i|
+ if i > 0
+ # separate with space unless previous ends with whitepsace or (
+ result << ' ' if parts[i] != ")" && parts[i-1] !~ /.*(?:\s+|\()$/ && parts[i] !~ /\s+/
+ end
+ result << parts[i]
+ end
+ result
+ end
+
+ def format_r(x)
+ result = []
+ case x
+ when :break
+ result << "\n" + indent
+ when :indent
+ @indent_count += 1
+ when :dedent
+ @indent_count -= 1
+ when Array
+ result << '('
+ result += x.collect {|a| format_r(a) }.flatten
+ result << ')'
+ when Symbol
+ result << x.to_s # Allows Symbols in arrays e.g. ["text", =>, "text"]
+ else
+ result << x
+ end
+ result
+ 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
new file mode 100644
index 0000000..bb6a5db
--- /dev/null
+++ b/lib/puppet/pops/parser/egrammar.ra
@@ -0,0 +1,723 @@
+# 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 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 NOT OR AND UNDEF PARROW PLUS MINUS TIMES DIV LSHIFT RSHIFT UMINUS
+token MATCH NOMATCH REGEX IN_EDGE OUT_EDGE IN_EDGE_SUB OUT_EDGE_SUB
+token IN UNLESS PIPE
+token LAMBDA SELBRACE
+token LOW
+
+prechigh
+ left HIGH
+ left SEMIC
+ left PIPE
+ left LPAREN
+ left RPAREN
+ left AT
+ left DOT
+ left CALL
+ left LBRACK
+ 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
+ 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]) }
+ | 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<Model::Expression>
+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 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 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<Model::Expression>
+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?)
+ | 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
+ : lambda_j8
+ | lambda_ruby
+
+# Java8-like lambda with parameters to the left of the body
+lambda_j8
+ : lambda_parameter_list optional_farrow lambda_rest {
+ result = Factory.LAMBDA(val[0], val[2])
+# loc result, val[1] # TODO
+ }
+
+lambda_rest
+ : LBRACE statements RBRACE { result = val[1] }
+ | LBRACE RBRACE { result = nil }
+
+optional_farrow
+ : nil
+ | FARROW
+
+# Ruby-like lambda with parameters inside the body
+#
+lambda_ruby
+ : 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<Model::Parameter>
+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<Model::CaseOption>
+ 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<Model::SelectorEntry>
+ #
+ selector_entries
+ : selector_entry
+ | SELBRACE selector_entry_list endcomma RBRACE {
+ result = val[1]
+ }
+
+ # Produces Array<Model::SelectorEntry>
+ 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"
+ when :override
+ error "A resource override can not be virtual or exported"
+ else
+ error "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"
+ else
+ error "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"
+ when :override
+ error "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"
+ 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"
+ 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"
+ 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 }
+
+#---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<Model::AttributeOperation>
+#
+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])
+ 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
+ }
+
+#---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])
+ 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]
+ }
+
+ # 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])
+ loc result, val[0], val[5]
+ }
+ | NODE hostnames nodeparent LBRACE RBRACE {
+ result = 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<Model::LiteralExpression>
+ #
+ 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] }
+ | DEFAULT { result = Factory.literal(:default); loc result, val[0] }
+ | regex
+
+ # Produces Expression, since hostname is an Expression
+ nodeparent
+ : nil
+ | INHERITS hostname { result = val[1] }
+
+#---NAMES AND PARAMTERS COMMON TO SEVERAL RULES
+# String result
+classname
+ : NAME { result = val[0] }
+ | CLASS { result = val[0] }
+
+# Produces Array<Model::Parameter>
+parameter_list
+ : nil { result = [] }
+ | LPAREN RPAREN { result = [] }
+ | LPAREN parameters endcomma RPAREN { result = val[1] }
+
+# Produces Array<Model::Parameter>
+parameters
+ : parameter { result = [val[0]] }
+ | parameters COMMA parameter { result = val[0].push(val[2]) }
+
+# Produces Model::Parameter
+parameter
+ : VARIABLE EQUALS expression { result = Factory.PARAM(val[0][:value], val[2]) ; loc result, val[0] }
+ | VARIABLE { result = Factory.PARAM(val[0][:value]); loc result, val[0] }
+
+#--RESTRICTED EXPRESSIONS
+# i.e. where one could have expected an expression, but the set is limited
+
+# What is allowed RHS of match operators (see expression)
+match_rvalue
+ : regex
+ | text_or_name
+
+#--VARIABLE
+#
+variable
+ : VARIABLE { result = Factory.fqn(val[0][:value]).var ; loc result, val[0] }
+
+#---LITERALS (dynamic and static)
+#
+
+array
+ : LBRACK expressions RBRACK { result = Factory.LIST(val[1]); loc result, val[0], val[2] }
+ | LBRACK expressions COMMA RBRACK { result = Factory.LIST(val[1]); loc result, val[0], val[3] }
+ | LBRACK RBRACK { result = Factory.literal([]) ; loc result, val[0] }
+
+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] }
+
+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] }
+
+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/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/eparser.rb b/lib/puppet/pops/parser/eparser.rb
new file mode 100644
index 0000000..26fea92
--- /dev/null
+++ b/lib/puppet/pops/parser/eparser.rb
@@ -0,0 +1,2300 @@
+#
+# 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/util/loadedfile'
+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', 719)
+
+# Make emacs happy
+# Local Variables:
+# mode: ruby
+# End:
+...end egrammar.ra/module_eval...
+##### State transition tables begin ###
+
+clist = [
+'68,-132,112,207,220,235,344,51,53,87,88,84,79,90,234,94,-130,89,51,53',
+'80,82,81,83,234,283,224,269,222,115,233,223,321,114,207,234,244,204',
+'93,-198,-207,-132,86,85,54,299,72,73,75,74,77,78,68,70,71,54,-130,316',
+'202,315,69,87,88,84,79,90,59,94,76,89,51,53,80,82,81,83,245,59,115,-198',
+'-207,301,114,115,115,51,53,114,114,115,93,330,291,114,86,85,105,104',
+'72,73,75,74,77,78,68,70,71,120,225,227,122,226,69,87,88,84,79,90,68',
+'94,76,89,54,297,80,82,81,83,68,59,115,90,268,94,114,89,243,51,53,105',
+'104,90,93,94,128,89,86,85,68,267,72,73,75,74,77,78,93,70,71,84,79,90',
+'68,94,69,89,93,306,80,82,81,83,76,192,54,90,316,94,315,89,309,70,71',
+'105,104,310,93,207,69,51,53,85,68,168,72,73,75,74,77,78,93,70,71,84',
+'79,90,313,94,69,89,51,53,80,82,81,83,76,105,68,68,317,51,53,229,228',
+'319,120,63,263,122,93,90,90,94,94,89,89,241,72,73,75,74,77,78,243,70',
+'71,120,59,326,122,327,69,68,241,91,93,93,120,267,76,122,87,88,84,79',
+'90,259,94,59,89,70,71,80,82,81,83,68,69,63,59,64,66,65,67,134,258,257',
+'336,79,90,93,94,243,89,86,85,80,243,72,73,75,74,77,78,116,70,71,241',
+'339,217,341,106,69,217,93,282,286,319,346,347,76,348,72,73,75,74,77',
+'78,68,70,71,349,99,352,353,354,69,285,63,60,79,90,361,94,76,89,362,363',
+'80,364,,,68,,,,,,,,,,,,79,90,93,94,,89,,,80,,72,73,75,74,77,78,68,70',
+'71,,,,,,69,,93,,,90,,94,76,89,72,73,75,74,77,78,68,70,71,,,,,,69,,,',
+'79,90,93,94,76,89,,,80,,72,73,75,74,77,78,68,70,71,,,,,,69,,93,,,90',
+',94,76,89,72,73,75,74,77,78,68,70,71,,,,,,69,87,88,84,79,90,93,94,76',
+'89,,,80,82,81,83,68,,,,,70,71,,,,,,69,90,93,94,,89,86,85,,,72,73,75',
+'74,77,78,68,70,71,,,,,,69,,93,,,90,,94,76,89,72,73,75,74,77,78,68,70',
+'71,,,,,,69,,,,,90,93,94,76,89,,,,,72,73,75,74,,,,70,71,,,,,,69,,93,',
+',,,,76,,72,73,75,74,,,68,70,71,,,,,,69,87,88,84,79,90,239,94,76,89,',
+',80,82,81,83,,,,,,,,,,,,,,,93,,,,86,85,,,72,73,75,74,77,78,68,70,71',
+',,,,,69,87,88,84,79,90,,94,76,89,,,80,82,81,83,,,,,,,,,,,,,,,93,,,,86',
+'85,,,72,73,75,74,77,78,68,70,71,,,,,,69,87,88,84,79,90,,94,76,89,68',
+',80,82,81,83,,,,,,,,90,,94,,89,,,93,,,,86,85,,,72,73,75,74,77,78,,70',
+'71,,93,,,,69,,,,,,75,74,76,,68,70,71,,,,,,69,87,88,84,79,90,,94,76,89',
+'68,,80,82,81,83,,,,,,,,90,,94,,89,,,93,,,,86,85,,,72,73,75,74,77,78',
+',70,71,,93,,,,69,,,,,,75,74,76,,68,70,71,,,,,,69,87,88,84,79,90,,94',
+'76,89,,,80,82,81,83,,,,,,,,,,,,,,,93,,,,86,85,,,72,73,75,74,77,78,68',
+'70,71,,,,,,69,87,88,84,79,90,,94,76,89,,,80,82,81,83,,,,,,,,,,,,,,,93',
+',,,86,85,,,72,73,75,74,77,78,68,70,71,,,,,,69,87,88,84,79,90,,94,76',
+'89,,,80,82,81,83,,,,,,,,,,,,,,,93,,,,86,85,,,72,73,75,74,77,78,68,70',
+'71,,,,,,69,87,88,84,79,90,,94,76,89,,,80,82,81,83,,,,,,,,,,,,,,,93,',
+',,86,85,,,72,73,75,74,77,78,68,70,71,,,,,,69,87,88,84,79,90,,94,76,89',
+',,80,82,81,83,,,,,,,,,,,,,,,93,,,,86,85,,,72,73,75,74,77,78,,70,71,',
+',,,,69,68,,213,,,,,76,,87,88,84,79,90,,94,,89,,,80,82,81,83,,,,,,,,',
+',,,,,,93,,,,86,85,,,72,73,75,74,77,78,,70,71,,,,,,69,68,,212,,,,,76',
+',87,88,84,79,90,,94,,89,,,80,82,81,83,,,,,,,,,,,,,,,93,,,,86,85,,,72',
+'73,75,74,77,78,,70,71,,,,,,69,68,,211,,,,,76,,87,88,84,79,90,,94,,89',
+',,80,82,81,83,,,,,,,,,,,,,,,93,,,,86,85,,,72,73,75,74,77,78,,70,71,',
+',,,,69,68,,210,,,,,76,,87,88,84,79,90,,94,,89,,,80,82,81,83,,,,,,,,',
+',,,,,,93,,,,86,85,,,72,73,75,74,77,78,68,70,71,,,,,,69,87,88,84,79,90',
+',94,76,89,,197,80,82,81,83,,,,,,,,,,,,,,,93,,,,86,85,,,72,73,75,74,77',
+'78,,70,71,51,53,,,47,69,48,,,,,,,76,,,,,,,,,13,,,,,,38,,44,,46,96,,45',
+'58,54,,40,57,,,,55,12,,,56,51,53,11,,47,,48,,,,59,,,,,,39,,,167,,,13',
+',,,,,170,187,181,188,46,182,190,183,179,177,,172,185,,,,55,12,191,186',
+'184,51,53,11,,47,,48,333,,,59,,,,,189,171,,,,,,13,,,,,,38,,44,,46,42',
+',45,58,54,,40,57,43,,,55,12,51,53,56,,47,11,48,322,,,,,,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,,,,,,170,187,181,188,46,182,190,183,179,177,,172,185,,,,55,12,191',
+'186,184,51,53,11,,47,,48,308,,,59,,,,,189,171,,,,,,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,,,,,,,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,199,,,,,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,209,,,,,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,290,48,,,,59,,,,,,39,,,,,,13,,,,,,38',
+',44,,46,96,,45,58,54,,40,57,,,,55,12,51,53,56,,47,11,48,335,,,,,,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,273,,,,,,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,271,,,,,,59,,,,,,39,,,13,,,,,,38,,44,,46,42,,45,58,54',
+',40,57,43,,,55,12,51,53,56,,47,11,48,265,,,,,,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,126,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,351,,,,,,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,356,,,,,,59,,,,',
+',39,,,13,,,,,,38,,44,,46,42,,45,58,54,,40,57,43,,,55,12,51,53,56,,47',
+'11,48,358,,,,,,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,360,,,,,,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,,,56,,,11,,,,253,187,252,188,59,250,190,254,248,247',
+'39,249,251,,,,,,191,186,255,253,187,252,188,,250,190,254,248,247,,249',
+'251,,,189,256,,191,186,255,253,187,252,188,,250,190,254,248,247,,249',
+'251,,,189,256,,191,186,255,,,,,,,,,,,,,,,,189,256' ]
+ racc_action_table = arr = ::Array.new(4804, 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 = [
+'164,179,42,105,118,164,316,70,70,164,164,164,164,164,208,164,177,164',
+'71,71,164,164,164,164,274,217,125,208,118,42,141,125,274,42,217,141',
+'180,105,164,185,184,179,164,164,70,240,164,164,164,164,164,164,163,164',
+'164,71,177,271,103,271,164,163,163,163,163,163,70,163,164,163,220,220',
+'163,163,163,163,180,71,96,185,184,242,96,182,282,226,226,182,282,181',
+'163,282,226,181,163,163,306,306,163,163,163,163,163,163,162,163,163',
+'220,127,130,220,127,163,162,162,162,162,162,97,162,163,162,226,236,162',
+'162,162,162,151,220,44,97,207,97,44,97,246,48,48,199,199,151,162,151',
+'48,151,162,162,161,205,162,162,162,162,162,162,97,162,162,161,161,161',
+'142,161,162,161,151,260,161,161,161,161,162,92,48,142,313,142,313,142',
+'264,151,151,36,36,266,161,267,151,183,183,161,160,90,161,161,161,161',
+'161,161,142,161,161,160,160,160,270,160,161,160,45,45,160,160,160,160',
+'161,104,150,95,272,222,222,133,133,273,183,135,200,183,160,150,95,150',
+'95,150,95,277,160,160,160,160,160,160,278,160,160,45,183,279,45,280',
+'160,10,214,10,150,95,222,284,160,222,10,10,10,10,10,198,10,45,10,150',
+'150,10,10,10,10,159,150,62,222,7,7,7,7,60,196,194,296,159,159,10,159',
+'174,159,10,10,159,298,10,10,10,10,10,10,43,10,10,173,305,113,307,37',
+'10,117,159,215,219,317,319,320,10,324,159,159,159,159,159,159,158,159',
+'159,325,35,331,332,334,159,218,5,1,158,158,350,158,159,158,355,357,158',
+'359,,,157,,,,,,,,,,,,157,157,158,157,,157,,,157,,158,158,158,158,158',
+'158,155,158,158,,,,,,158,,157,,,155,,155,158,155,157,157,157,157,157',
+'157,156,157,157,,,,,,157,,,,156,156,155,156,157,156,,,156,,155,155,155',
+'155,155,155,149,155,155,,,,,,155,,156,,,149,,149,155,149,156,156,156',
+'156,156,156,312,156,156,,,,,,156,312,312,312,312,312,149,312,156,312',
+',,312,312,312,312,154,,,,,149,149,,,,,,149,154,312,154,,154,312,312',
+',,312,312,312,312,312,312,153,312,312,,,,,,312,,154,,,153,,153,312,153',
+'154,154,154,154,154,154,152,154,154,,,,,,154,,,,,152,153,152,154,152',
+',,,,153,153,153,153,,,,153,153,,,,,,153,,152,,,,,,153,,152,152,152,152',
+',,169,152,152,,,,,,152,169,169,169,169,169,169,169,152,169,,,169,169',
+'169,169,,,,,,,,,,,,,,,169,,,,169,169,,,169,169,169,169,169,169,304,169',
+'169,,,,,,169,304,304,304,304,304,,304,169,304,,,304,304,304,304,,,,',
+',,,,,,,,,,304,,,,304,304,,,304,304,304,304,304,304,303,304,304,,,,,',
+'304,303,303,303,303,303,,303,304,303,148,,303,303,303,303,,,,,,,,148',
+',148,,148,,,303,,,,303,303,,,303,303,303,303,303,303,,303,303,,148,',
+',,303,,,,,,148,148,303,,295,148,148,,,,,,148,295,295,295,295,295,,295',
+'148,295,147,,295,295,295,295,,,,,,,,147,,147,,147,,,295,,,,295,295,',
+',295,295,295,295,295,295,,295,295,,147,,,,295,,,,,,147,147,295,,293',
+'147,147,,,,,,147,293,293,293,293,293,,293,147,293,,,293,293,293,293',
+',,,,,,,,,,,,,,293,,,,293,293,,,293,293,293,293,293,293,193,293,293,',
+',,,,293,193,193,193,193,193,,193,293,193,,,193,193,193,193,,,,,,,,,',
+',,,,,193,,,,193,193,,,193,193,193,193,193,193,289,193,193,,,,,,193,289',
+'289,289,289,289,,289,193,289,,,289,289,289,289,,,,,,,,,,,,,,,289,,,',
+'289,289,,,289,289,289,289,289,289,131,289,289,,,,,,289,131,131,131,131',
+'131,,131,289,131,,,131,131,131,131,,,,,,,,,,,,,,,131,,,,131,131,,,131',
+'131,131,131,131,131,124,131,131,,,,,,131,124,124,124,124,124,,124,131',
+'124,,,124,124,124,124,,,,,,,,,,,,,,,124,,,,124,124,,,124,124,124,124',
+'124,124,,124,124,,,,,,124,111,,111,,,,,124,,111,111,111,111,111,,111',
+',111,,,111,111,111,111,,,,,,,,,,,,,,,111,,,,111,111,,,111,111,111,111',
+'111,111,,111,111,,,,,,111,110,,110,,,,,111,,110,110,110,110,110,,110',
+',110,,,110,110,110,110,,,,,,,,,,,,,,,110,,,,110,110,,,110,110,110,110',
+'110,110,,110,110,,,,,,110,109,,109,,,,,110,,109,109,109,109,109,,109',
+',109,,,109,109,109,109,,,,,,,,,,,,,,,109,,,,109,109,,,109,109,109,109',
+'109,109,,109,109,,,,,,109,107,,107,,,,,109,,107,107,107,107,107,,107',
+',107,,,107,107,107,107,,,,,,,,,,,,,,,107,,,,107,107,,,107,107,107,107',
+'107,107,98,107,107,,,,,,107,98,98,98,98,98,,98,107,98,,98,98,98,98,98',
+',,,,,,,,,,,,,,98,,,,98,98,,,98,98,98,98,98,98,,98,98,89,89,,,89,98,89',
+',,,,,,98,,,,,,,,,89,,,,,,89,,89,,89,89,,89,89,89,,89,89,,,,89,89,,,89',
+'213,213,89,,213,,213,,,,89,,,,,,89,,,89,,,213,,,,,,213,213,213,213,213',
+'213,213,213,213,213,,213,213,,,,213,213,213,213,213,285,285,213,,285',
+',285,285,,,213,,,,,213,213,,,,,,285,,,,,,285,,285,,285,285,,285,285',
+'285,,285,285,285,,,285,285,275,275,285,,275,285,275,275,,,,,,285,,,',
+',,285,,,275,,,,,,275,,275,,275,275,,275,275,275,,275,275,,,,275,275',
+'72,72,275,,72,275,72,,,,,,,275,,,,,,275,,,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,167,167',
+'86,,167,86,167,,,,,,,86,,,,,,86,,,167,,,,,,167,,167,,167,167,,167,167',
+'167,,167,167,,,,167,167,88,88,167,,88,167,88,,,,,,,167,,,,,,167,,,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,268,268',
+'68,,268,68,268,,,,,,,68,,,,,,68,,,268,,,,,,268,,268,,268,268,,268,268',
+'268,,268,268,,,,268,268,91,91,268,,91,268,91,,,,,,,268,,,,,,268,,,91',
+',,,,,91,91,91,91,91,91,91,91,91,91,,91,91,,,,91,91,91,91,91,263,263',
+'91,,263,,263,263,,,91,,,,,91,91,,,,,,263,,,,,,263,,263,,263,263,,263',
+'263,263,,263,263,263,,,263,263,93,93,263,,93,263,93,,,,,,,263,,,,,,263',
+',,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,259',
+'259,94,,259,94,259,,,,,,,94,,,,,,94,,,259,,,,,,259,,259,,259,259,,259',
+'259,259,,259,259,,,,259,259,245,245,259,,245,259,245,,,,,,,259,,,,,',
+'259,,,245,,,,,,245,,245,,245,245,,245,245,245,,245,245,,,,245,245,244',
+'244,245,,244,245,244,,,,,,,245,,,,,,245,,,244,,,,,,244,,244,,244,244',
+',244,244,244,,244,244,,,,244,244,67,67,244,,67,244,67,,,,,,,244,,,,',
+',244,,,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,241,241,99,,241,99,241,,,,,,,99,,,,,,99,,,241,,,,,,241,,241,',
+'241,241,,241,241,241,,241,241,,,,241,241,235,235,241,,235,241,235,,',
+',,,,241,,,,,,241,,,235,,,,,,235,,235,,235,235,,235,235,235,,235,235',
+',,,235,235,234,234,235,,234,235,234,,,,,,,235,,,,,,235,,,234,,,,,,234',
+',234,,234,234,,234,234,234,,234,234,,,,234,234,106,106,234,,106,234',
+'106,,,,,,,234,,,,,,234,,,106,106,,,,,106,,106,,106,106,,106,106,106',
+',106,106,,,,106,106,66,66,106,,66,106,66,,,,,,,106,,,,,,106,,,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,112,112,63,,112,63,112,,,,,,,63,,',
+',,,63,,,112,,,,,,112,,112,,112,112,,112,112,112,,112,112,,,,112,112',
+'232,232,112,,232,112,232,,,,,,,112,,,,,,112,,,232,,,,,,232,,232,,232',
+'232,,232,232,232,,232,232,,,,232,232,227,227,232,,227,232,227,,,,,,',
+'232,,,,,,232,,,227,,,,,,227,,227,,227,227,,227,227,227,,227,227,,,,227',
+'227,,,227,223,223,227,,223,223,223,,,,227,,,,,,227,,,,,,223,,,,,,223',
+',223,,223,223,,223,223,223,,223,223,,,,223,223,286,286,223,,286,223',
+'286,286,,,,,,223,,,,,,223,,,286,,,,,,286,,286,,286,286,,286,286,286',
+',286,286,286,,,286,286,69,69,286,,69,286,69,,,,,,,286,,,,,,286,,,69',
+',,,,,69,,69,,69,69,,69,69,69,,69,69,,,,69,69,212,212,69,,212,69,212',
+',,,,,,69,,,,,,69,,,212,,,,,,212,,212,,212,212,,212,212,212,,212,212',
+',,,212,212,211,211,212,,211,212,211,211,,,,,,212,,,,,,212,,,211,,,,',
+',211,,211,,211,211,,211,211,211,,211,211,211,,,211,211,61,61,211,,61',
+'211,61,,,,,,,211,,,,,,211,,,61,,,,,,61,,61,,61,61,,61,61,61,,61,61,61',
+',,61,61,210,210,61,,210,61,210,210,,,,,,61,,,,,,61,,,210,,,,,,210,,210',
+',210,210,,210,210,210,,210,210,210,,,210,210,203,203,210,,203,210,203',
+'203,,,,,,210,,,,,,210,,,203,,,,,,203,,203,,203,203,,203,203,203,,203',
+'203,203,,,203,203,52,52,203,,52,203,52,,,,,,,203,,,,,,203,,,52,,,,,',
+'52,,52,,52,52,,52,52,52,,52,52,,,,52,52,172,172,52,,172,52,172,,,,,',
+',52,,,,,,52,,,172,,,,,,172,,172,,172,172,,172,172,172,,172,172,,,,172',
+'172,,,172,47,47,172,,47,47,47,,,,172,,,,,,172,,,,,,47,,,,,,47,,47,,47',
+'47,,47,47,47,,47,47,,,,47,47,297,297,47,,297,47,297,,,,,,,47,,,,,,47',
+',,297,,,,,,297,,297,,297,297,,297,297,297,,297,297,,,,297,297,171,171',
+'297,,171,297,171,,,,,,,297,,,,,,297,,,171,,,,,,171,,171,,171,171,,171',
+'171,171,,171,171,,,,171,171,170,170,171,,170,171,170,,,,,,,171,,,,,',
+'171,,,170,,,,,,170,,170,,170,170,,170,170,170,,170,170,,,,170,170,41',
+'41,170,,41,170,41,,,,,,,170,,,,,,170,,,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,315,315,38,,315,38,315,,,,,,,38,,,,,,38,,,315,,,,,,315,,315,,315',
+'315,,315,315,315,,315,315,,,,315,315,327,327,315,,327,315,327,327,,',
+',,,315,,,,,,315,,,327,,,,,,327,,327,,327,327,,327,327,327,,327,327,327',
+',,327,327,13,13,327,,13,327,13,,,,,,,327,,,,,,327,,,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,344',
+'344,11,,344,11,344,344,,,,,,11,,,,,,11,,,344,,,,,,344,,344,,344,344',
+',344,344,344,,344,344,344,,,344,344,346,346,344,,346,344,346,346,,,',
+',,344,,,,,,344,,,346,,,,,,346,,346,,346,346,,346,346,346,,346,346,346',
+',,346,346,4,4,346,,4,346,4,,,,,,,346,,,,,,346,,,4,,,,,,4,,4,,4,4,,4',
+'4,4,4,4,4,4,,,4,4,347,347,4,,347,4,347,347,,,,,,4,,,,,,4,,,347,,,,,',
+'347,,347,,347,347,,347,347,347,,347,347,347,,,347,347,0,0,347,,0,347',
+'0,,,,,,,347,,,,,,347,,,0,,,,,,0,,0,,0,0,,0,0,0,,0,0,0,,,0,0,87,87,0',
+',87,0,87,,,,,,,0,,,,,,0,,,87,,,,,,87,,87,,87,87,,87,87,87,,87,87,,,',
+'87,87,,,87,,,87,,,,192,192,192,192,87,192,192,192,192,192,87,192,192',
+',,,,,192,192,192,238,238,238,238,,238,238,238,238,238,,238,238,,,192',
+'192,,238,238,238,243,243,243,243,,243,243,243,243,243,,243,243,,,238',
+'238,,243,243,243,,,,,,,,,,,,,,,,243,243' ]
+ racc_action_check = arr = ::Array.new(4804, 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 = [
+ 4621, 340, nil, nil, 4529, 327, nil, 219, nil, nil,
+ 247, 4391, 4345, 4299, nil, nil, nil, nil, nil, nil,
+ nil, nil, nil, nil, nil, nil, nil, nil, nil, nil,
+ nil, nil, nil, nil, nil, 309, 115, 288, 4161, 4115,
+ 4069, 4023, -6, 262, 95, 208, nil, 3839, 135, nil,
+ nil, nil, 3744, nil, nil, nil, nil, nil, nil, nil,
+ 285, 3606, 267, 3189, 3143, 3097, 3051, 2775, 2358, 3468,
+ 5, 16, 1576, 1622, 1668, 1714, 1760, 1806, 1852, 1898,
+ 1944, 1990, 2036, 2082, 2128, 2174, 2220, 4667, 2312, 1386,
+ 154, 2450, 165, 2545, 2591, 213, 43, 112, 1327, 2821,
+ nil, nil, nil, 47, 149, -31, 3005, 1275, nil, 1214,
+ 1153, 1092, 3235, 286, nil, nil, nil, 290, -8, nil,
+ nil, nil, nil, nil, 1031, 19, nil, 99, nil, nil,
+ 98, 979, nil, 219, nil, 215, nil, nil, nil, nil,
+ nil, 23, 156, nil, nil, nil, nil, 771, 701, 427,
+ 212, 122, 527, 503, 475, 375, 399, 347, 323, 271,
+ 186, 142, 98, 46, -6, nil, nil, 2266, nil, 579,
+ 3977, 3931, 3790, 268, 281, nil, nil, 5, nil, -10,
+ 25, 54, 48, 187, 29, 28, nil, nil, nil, nil,
+ nil, nil, 4694, 875, 242, nil, 264, nil, 255, 71,
+ 220, nil, nil, 3698, nil, 137, nil, 117, 2, nil,
+ 3652, 3560, 3514, 1435, 214, 280, nil, 0, 330, 309,
+ 68, nil, 219, 3376, nil, nil, 83, 3327, nil, nil,
+ nil, nil, 3281, nil, 2959, 2913, 111, nil, 4715, nil,
+ 36, 2867, 72, 4736, 2729, 2683, 124, nil, nil, nil,
+ nil, nil, nil, nil, nil, nil, nil, nil, nil, 2637,
+ 142, nil, nil, 2499, 171, nil, 117, 153, 2404, nil,
+ 197, 26, 211, 194, 12, 1530, nil, 197, 232, 240,
+ 243, nil, 49, nil, 247, 1484, 3422, nil, nil, 927,
+ nil, nil, nil, 823, nil, 753, 279, 3885, 286, nil,
+ nil, nil, nil, 683, 631, 300, 28, 302, nil, nil,
+ nil, nil, 451, 145, nil, 4207, -2, 287, nil, 311,
+ 312, nil, nil, nil, 313, 323, nil, 4253, nil, nil,
+ nil, 309, 326, nil, 327, nil, nil, nil, nil, nil,
+ nil, nil, nil, nil, 4437, nil, 4483, 4575, nil, nil,
+ 334, nil, nil, nil, nil, 338, nil, 339, nil, 341,
+ nil, nil, nil, nil, nil ]
+
+racc_action_default = [
+ -209, -210, -1, -2, -3, -4, -7, -9, -10, -15,
+ -109, -210, -210, -210, -43, -44, -45, -46, -47, -48,
+ -49, -50, -51, -52, -53, -54, -55, -56, -57, -58,
+ -59, -60, -61, -62, -63, -68, -69, -73, -210, -210,
+ -210, -210, -210, -119, -210, -210, -164, -210, -210, -174,
+ -175, -176, -210, -178, -185, -186, -187, -188, -189, -190,
+ -210, -210, -6, -210, -210, -210, -210, -210, -210, -210,
+ -210, -210, -210, -210, -210, -210, -210, -210, -210, -210,
+ -210, -210, -210, -210, -210, -210, -210, -210, -210, -210,
+ -210, -127, -122, -209, -209, -27, -210, -34, -210, -210,
+ -70, -75, -76, -209, -210, -210, -210, -210, -86, -210,
+ -210, -210, -210, -209, -153, -154, -120, -209, -209, -145,
+ -147, -148, -149, -150, -41, -210, -167, -210, -170, -171,
+ -210, -182, -177, -210, 365, -5, -8, -11, -12, -13,
+ -14, -210, -17, -18, -162, -163, -19, -20, -21, -22,
+ -23, -24, -25, -26, -28, -29, -30, -31, -32, -33,
+ -35, -36, -37, -38, -210, -39, -104, -210, -74, -210,
+ -202, -208, -196, -193, -191, -117, -128, -185, -131, -189,
+ -210, -199, -197, -205, -187, -188, -195, -200, -201, -203,
+ -204, -206, -127, -126, -210, -125, -210, -40, -191, -65,
+ -210, -80, -81, -210, -84, -191, -158, -161, -210, -72,
+ -210, -210, -210, -127, -193, -209, -155, -210, -210, -210,
+ -210, -151, -210, -210, -165, -168, -210, -210, -179, -180,
+ -181, -183, -210, -16, -210, -210, -191, -106, -127, -116,
+ -210, -194, -210, -192, -210, -210, -191, -130, -132, -196,
+ -197, -198, -199, -202, -205, -207, -208, -123, -124, -192,
+ -210, -67, -77, -210, -210, -83, -210, -192, -210, -71,
+ -210, -89, -210, -95, -210, -210, -99, -193, -191, -210,
+ -210, -139, -210, -156, -191, -210, -210, -146, -152, -42,
+ -166, -169, -172, -173, -184, -108, -210, -192, -191, -112,
+ -118, -113, -129, -133, -134, -210, -64, -210, -79, -82,
+ -85, -159, -160, -89, -88, -210, -210, -95, -94, -210,
+ -210, -103, -98, -100, -210, -210, -114, -210, -140, -141,
+ -142, -210, -210, -136, -210, -144, -105, -107, -115, -121,
+ -66, -78, -87, -90, -210, -93, -210, -210, -110, -111,
+ -210, -138, -157, -135, -143, -210, -92, -210, -97, -210,
+ -102, -137, -91, -96, -101 ]
+
+racc_goto_table = [
+ 2, 117, 100, 95, 97, 98, 3, 132, 129, 166,
+ 174, 240, 130, 205, 314, 318, 123, 121, 215, 173,
+ 1, 276, 218, 320, 287, 62, 288, 242, 194, 196,
+ 107, 109, 110, 111, 145, 145, 125, 143, 146, 124,
+ 214, 144, 144, 236, 131, 137, 138, 139, 140, 275,
+ 300, 260, 279, 238, 343, 302, 342, 141, 266, 345,
+ 124, 142, 262, 200, 147, 148, 149, 150, 151, 152,
+ 153, 154, 155, 156, 157, 158, 159, 160, 161, 162,
+ 163, 164, 135, 169, 323, 193, 193, 237, 198, 296,
+ 280, 124, 328, 219, 203, 208, 311, 127, 124, 305,
+ 165, 136, 231, 232, 169, 230, nil, nil, nil, 201,
+ nil, 246, nil, nil, nil, 324, nil, nil, nil, 216,
+ nil, nil, nil, 216, 221, 284, nil, nil, nil, nil,
+ nil, 325, 278, nil, nil, nil, nil, 331, 117, nil,
+ nil, 277, nil, nil, nil, nil, nil, nil, nil, nil,
+ nil, 338, nil, nil, 123, 121, nil, 298, nil, 164,
+ nil, nil, 107, 109, 110, 261, nil, nil, nil, nil,
+ nil, nil, nil, nil, nil, nil, nil, nil, nil, nil,
+ nil, nil, nil, nil, nil, nil, 292, 294, nil, nil,
+ 130, 123, 121, 123, 121, nil, nil, nil, nil, nil,
+ nil, nil, nil, 264, 124, 169, nil, nil, nil, nil,
+ 270, 272, nil, nil, nil, 289, nil, 337, nil, 293,
+ nil, 281, nil, nil, 131, nil, 289, 295, nil, nil,
+ nil, nil, nil, 169, nil, nil, 303, 304, nil, 329,
+ nil, nil, nil, nil, nil, nil, nil, nil, nil, nil,
+ nil, 289, nil, nil, nil, nil, nil, nil, nil, nil,
+ 312, nil, nil, 307, nil, nil, nil, 124, nil, nil,
+ nil, nil, 340, nil, nil, nil, nil, nil, nil, nil,
+ nil, nil, nil, nil, nil, 332, 334, nil, nil, 164,
+ nil, nil, nil, nil, nil, nil, nil, nil, nil, nil,
+ nil, nil, nil, nil, nil, nil, nil, 107, nil, nil,
+ nil, nil, nil, nil, nil, nil, nil, nil, nil, nil,
+ nil, nil, nil, nil, nil, nil, nil, 350, nil, nil,
+ nil, nil, nil, nil, nil, nil, nil, nil, nil, nil,
+ nil, nil, nil, nil, 355, nil, 357, 359 ]
+
+racc_goto_check = [
+ 2, 65, 37, 9, 9, 9, 3, 78, 74, 52,
+ 57, 56, 31, 45, 47, 48, 30, 35, 66, 55,
+ 1, 50, 66, 51, 71, 5, 71, 36, 61, 61,
+ 9, 9, 9, 9, 31, 31, 11, 12, 12, 9,
+ 55, 30, 30, 53, 9, 7, 7, 7, 7, 49,
+ 58, 36, 56, 59, 46, 62, 47, 11, 36, 48,
+ 9, 9, 44, 43, 9, 9, 9, 9, 9, 9,
+ 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
+ 9, 9, 5, 9, 50, 9, 9, 52, 11, 36,
+ 67, 9, 68, 70, 42, 11, 72, 73, 9, 36,
+ 13, 6, 79, 80, 9, 82, nil, nil, nil, 3,
+ nil, 57, nil, nil, nil, 56, nil, nil, nil, 3,
+ nil, nil, nil, 3, 3, 45, nil, nil, nil, nil,
+ nil, 36, 57, nil, nil, nil, nil, 36, 65, nil,
+ nil, 55, nil, nil, nil, nil, nil, nil, nil, nil,
+ nil, 36, nil, nil, 30, 35, nil, 57, nil, 9,
+ nil, nil, 9, 9, 9, 37, nil, nil, nil, nil,
+ nil, nil, nil, nil, nil, nil, nil, nil, nil, nil,
+ nil, nil, nil, nil, nil, nil, 74, 78, nil, nil,
+ 31, 30, 35, 30, 35, nil, nil, nil, nil, nil,
+ nil, nil, nil, 2, 9, 9, nil, nil, nil, nil,
+ 2, 2, nil, nil, nil, 9, nil, 52, nil, 9,
+ nil, 3, nil, nil, 9, nil, 9, 9, nil, nil,
+ nil, nil, nil, 9, nil, nil, 9, 9, nil, 65,
+ nil, nil, nil, nil, nil, nil, nil, nil, nil, nil,
+ nil, 9, nil, nil, nil, nil, nil, nil, nil, nil,
+ 9, nil, nil, 2, 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, nil, nil, 9, nil, nil,
+ nil, nil, nil, nil, nil, nil, nil, nil, nil, nil,
+ nil, nil, nil, nil, nil, nil, nil, 2, nil, nil,
+ nil, nil, nil, nil, nil, nil, nil, nil, nil, nil,
+ nil, nil, nil, nil, 2, nil, 2, 2 ]
+
+racc_goto_pointer = [
+ nil, 20, 0, 6, nil, 21, 38, -19, nil, -8,
+ nil, -11, -33, 11, nil, nil, nil, nil, nil, nil,
+ nil, nil, nil, nil, nil, nil, nil, nil, nil, nil,
+ -29, -36, nil, nil, nil, -28, -147, -34, nil, nil,
+ nil, nil, -10, -40, -138, -92, -261, -257, -258, -163,
+ -191, -251, -80, -124, nil, -72, -162, -81, -191, -116,
+ nil, -65, -188, nil, nil, -43, -95, -125, -190, nil,
+ -25, -196, -171, 49, -40, nil, nil, nil, -45, -31,
+ -30, nil, -28 ]
+
+racc_goto_default = [
+ nil, nil, nil, 195, 4, 5, 6, 7, 8, 10,
+ 9, 274, 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, 102, 103, nil, nil, nil, 108, nil, nil, nil,
+ nil, nil, nil, nil, 41, nil, nil, nil, 175, nil,
+ 92, nil, 176, 180, 178, 113, nil, nil, nil, 118,
+ nil, 119, 206, nil, nil, 49, 50, 52, nil, nil,
+ nil, 133, nil ]
+
+racc_reduce_table = [
+ 0, 0, :racc_error,
+ 1, 78, :_reduce_1,
+ 1, 78, :_reduce_none,
+ 1, 79, :_reduce_3,
+ 1, 81, :_reduce_4,
+ 3, 81, :_reduce_5,
+ 2, 81, :_reduce_6,
+ 1, 82, :_reduce_7,
+ 3, 82, :_reduce_8,
+ 1, 83, :_reduce_none,
+ 1, 84, :_reduce_10,
+ 3, 84, :_reduce_11,
+ 3, 84, :_reduce_12,
+ 3, 84, :_reduce_13,
+ 3, 84, :_reduce_14,
+ 1, 86, :_reduce_none,
+ 4, 86, :_reduce_16,
+ 3, 86, :_reduce_17,
+ 3, 86, :_reduce_18,
+ 3, 86, :_reduce_19,
+ 3, 86, :_reduce_20,
+ 3, 86, :_reduce_21,
+ 3, 86, :_reduce_22,
+ 3, 86, :_reduce_23,
+ 3, 86, :_reduce_24,
+ 3, 86, :_reduce_25,
+ 3, 86, :_reduce_26,
+ 2, 86, :_reduce_27,
+ 3, 86, :_reduce_28,
+ 3, 86, :_reduce_29,
+ 3, 86, :_reduce_30,
+ 3, 86, :_reduce_31,
+ 3, 86, :_reduce_32,
+ 3, 86, :_reduce_33,
+ 2, 86, :_reduce_34,
+ 3, 86, :_reduce_35,
+ 3, 86, :_reduce_36,
+ 3, 86, :_reduce_37,
+ 3, 86, :_reduce_38,
+ 3, 86, :_reduce_39,
+ 3, 86, :_reduce_40,
+ 1, 88, :_reduce_41,
+ 3, 88, :_reduce_42,
+ 1, 87, :_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, 92, :_reduce_none,
+ 1, 92, :_reduce_none,
+ 1, 93, :_reduce_none,
+ 1, 93, :_reduce_none,
+ 1, 93, :_reduce_none,
+ 1, 93, :_reduce_none,
+ 1, 93, :_reduce_none,
+ 1, 93, :_reduce_none,
+ 1, 93, :_reduce_none,
+ 1, 93, :_reduce_none,
+ 1, 108, :_reduce_62,
+ 1, 108, :_reduce_63,
+ 5, 91, :_reduce_64,
+ 3, 91, :_reduce_65,
+ 6, 91, :_reduce_66,
+ 4, 91, :_reduce_67,
+ 1, 91, :_reduce_68,
+ 1, 95, :_reduce_69,
+ 2, 95, :_reduce_70,
+ 4, 115, :_reduce_71,
+ 3, 115, :_reduce_72,
+ 1, 115, :_reduce_73,
+ 3, 116, :_reduce_74,
+ 1, 114, :_reduce_none,
+ 1, 114, :_reduce_none,
+ 3, 117, :_reduce_77,
+ 3, 121, :_reduce_78,
+ 2, 121, :_reduce_79,
+ 1, 120, :_reduce_none,
+ 1, 120, :_reduce_none,
+ 4, 118, :_reduce_82,
+ 3, 118, :_reduce_83,
+ 2, 119, :_reduce_84,
+ 4, 119, :_reduce_85,
+ 2, 98, :_reduce_86,
+ 5, 123, :_reduce_87,
+ 4, 123, :_reduce_88,
+ 0, 124, :_reduce_none,
+ 2, 124, :_reduce_90,
+ 4, 124, :_reduce_91,
+ 3, 124, :_reduce_92,
+ 6, 99, :_reduce_93,
+ 5, 99, :_reduce_94,
+ 0, 125, :_reduce_none,
+ 4, 125, :_reduce_96,
+ 3, 125, :_reduce_97,
+ 5, 97, :_reduce_98,
+ 1, 126, :_reduce_99,
+ 2, 126, :_reduce_100,
+ 5, 127, :_reduce_101,
+ 4, 127, :_reduce_102,
+ 1, 128, :_reduce_103,
+ 1, 90, :_reduce_none,
+ 4, 90, :_reduce_105,
+ 1, 130, :_reduce_106,
+ 3, 130, :_reduce_107,
+ 3, 129, :_reduce_108,
+ 1, 85, :_reduce_109,
+ 6, 85, :_reduce_110,
+ 6, 85, :_reduce_111,
+ 5, 85, :_reduce_112,
+ 5, 85, :_reduce_113,
+ 5, 85, :_reduce_114,
+ 4, 135, :_reduce_115,
+ 1, 136, :_reduce_116,
+ 1, 132, :_reduce_117,
+ 3, 132, :_reduce_118,
+ 1, 131, :_reduce_119,
+ 2, 131, :_reduce_120,
+ 6, 96, :_reduce_121,
+ 2, 96, :_reduce_122,
+ 3, 137, :_reduce_123,
+ 3, 137, :_reduce_124,
+ 1, 138, :_reduce_none,
+ 1, 138, :_reduce_none,
+ 0, 134, :_reduce_127,
+ 1, 134, :_reduce_128,
+ 3, 134, :_reduce_129,
+ 1, 140, :_reduce_none,
+ 1, 140, :_reduce_none,
+ 1, 140, :_reduce_none,
+ 3, 139, :_reduce_133,
+ 3, 139, :_reduce_134,
+ 6, 100, :_reduce_135,
+ 5, 100, :_reduce_136,
+ 7, 101, :_reduce_137,
+ 6, 101, :_reduce_138,
+ 1, 144, :_reduce_none,
+ 2, 144, :_reduce_140,
+ 1, 145, :_reduce_none,
+ 1, 145, :_reduce_none,
+ 6, 102, :_reduce_143,
+ 5, 102, :_reduce_144,
+ 1, 146, :_reduce_145,
+ 3, 146, :_reduce_146,
+ 1, 148, :_reduce_147,
+ 1, 148, :_reduce_148,
+ 1, 148, :_reduce_149,
+ 1, 148, :_reduce_none,
+ 1, 147, :_reduce_none,
+ 2, 147, :_reduce_152,
+ 1, 142, :_reduce_153,
+ 1, 142, :_reduce_154,
+ 1, 143, :_reduce_155,
+ 2, 143, :_reduce_156,
+ 4, 143, :_reduce_157,
+ 1, 122, :_reduce_158,
+ 3, 122, :_reduce_159,
+ 3, 149, :_reduce_160,
+ 1, 149, :_reduce_161,
+ 1, 89, :_reduce_none,
+ 1, 89, :_reduce_none,
+ 1, 94, :_reduce_164,
+ 3, 103, :_reduce_165,
+ 4, 103, :_reduce_166,
+ 2, 103, :_reduce_167,
+ 3, 106, :_reduce_168,
+ 4, 106, :_reduce_169,
+ 2, 106, :_reduce_170,
+ 1, 150, :_reduce_171,
+ 3, 150, :_reduce_172,
+ 3, 151, :_reduce_173,
+ 1, 112, :_reduce_none,
+ 1, 112, :_reduce_none,
+ 1, 152, :_reduce_176,
+ 2, 153, :_reduce_177,
+ 1, 154, :_reduce_178,
+ 1, 156, :_reduce_179,
+ 1, 157, :_reduce_180,
+ 2, 155, :_reduce_181,
+ 1, 158, :_reduce_182,
+ 1, 159, :_reduce_183,
+ 2, 159, :_reduce_184,
+ 1, 111, :_reduce_185,
+ 1, 109, :_reduce_186,
+ 1, 110, :_reduce_187,
+ 1, 105, :_reduce_188,
+ 1, 104, :_reduce_189,
+ 1, 107, :_reduce_190,
+ 0, 113, :_reduce_none,
+ 1, 113, :_reduce_192,
+ 0, 133, :_reduce_none,
+ 1, 133, :_reduce_none,
+ 1, 141, :_reduce_none,
+ 1, 141, :_reduce_none,
+ 1, 141, :_reduce_none,
+ 1, 141, :_reduce_none,
+ 1, 141, :_reduce_none,
+ 1, 141, :_reduce_none,
+ 1, 141, :_reduce_none,
+ 1, 141, :_reduce_none,
+ 1, 141, :_reduce_none,
+ 1, 141, :_reduce_none,
+ 1, 141, :_reduce_none,
+ 1, 141, :_reduce_none,
+ 1, 141, :_reduce_none,
+ 1, 141, :_reduce_none,
+ 0, 80, :_reduce_209 ]
+
+racc_reduce_n = 210
+
+racc_shift_n = 365
+
+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,
+ :LAMBDA => 69,
+ :SELBRACE => 70,
+ :LOW => 71,
+ :HIGH => 72,
+ :CALL => 73,
+ :MODULO => 74,
+ :TITLE_COLON => 75,
+ :CASE_COLON => 76 }
+
+racc_nt_base = 77
+
+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",
+ "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",
+ "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",
+ "LOW",
+ "HIGH",
+ "CALL",
+ "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",
+ "type",
+ "undef",
+ "name",
+ "quotedtext",
+ "endcomma",
+ "lambda",
+ "call_method_expression",
+ "named_access",
+ "lambda_j8",
+ "lambda_ruby",
+ "lambda_parameter_list",
+ "optional_farrow",
+ "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",
+ "classparent",
+ "classnameordefault",
+ "hostnames",
+ "nodeparent",
+ "hostname",
+ "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)
+ def _reduce_1(val, _values, result)
+ result = Factory.block_or_expression(*val[0])
+ result
+ end
+.,.,
+
+# reduce 2 omitted
+
+module_eval(<<'.,.,', 'egrammar.ra', 62)
+ def _reduce_3(val, _values, result)
+ result = transform_calls(val[0])
+ result
+ end
+.,.,
+
+module_eval(<<'.,.,', 'egrammar.ra', 68)
+ def _reduce_4(val, _values, result)
+ result = [val[0]]
+ result
+ end
+.,.,
+
+module_eval(<<'.,.,', 'egrammar.ra', 69)
+ def _reduce_5(val, _values, result)
+ result = val[0].push val[2]
+ result
+ end
+.,.,
+
+module_eval(<<'.,.,', 'egrammar.ra', 70)
+ def _reduce_6(val, _values, result)
+ result = val[0].push val[1]
+ result
+ end
+.,.,
+
+module_eval(<<'.,.,', 'egrammar.ra', 74)
+ def _reduce_7(val, _values, result)
+ result = val[0]
+ result
+ end
+.,.,
+
+module_eval(<<'.,.,', 'egrammar.ra', 75)
+ def _reduce_8(val, _values, result)
+ result = aryfy(val[0]).push val[2]
+ result
+ end
+.,.,
+
+# reduce 9 omitted
+
+module_eval(<<'.,.,', 'egrammar.ra', 81)
+ def _reduce_10(val, _values, result)
+ result = val[0]
+ result
+ end
+.,.,
+
+module_eval(<<'.,.,', 'egrammar.ra', 82)
+ 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)
+ 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)
+ 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)
+ 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)
+ def _reduce_16(val, _values, result)
+ result = val[0][*val[2]] ; loc result, val[0], val[3]
+ result
+ end
+.,.,
+
+module_eval(<<'.,.,', 'egrammar.ra', 93)
+ def _reduce_17(val, _values, result)
+ result = val[0].in val[2] ; loc result, val[1]
+ result
+ end
+.,.,
+
+module_eval(<<'.,.,', 'egrammar.ra', 94)
+ def _reduce_18(val, _values, result)
+ result = val[0] =~ val[2] ; loc result, val[1]
+ result
+ end
+.,.,
+
+module_eval(<<'.,.,', 'egrammar.ra', 95)
+ def _reduce_19(val, _values, result)
+ result = val[0].mne val[2] ; loc result, val[1]
+ result
+ end
+.,.,
+
+module_eval(<<'.,.,', 'egrammar.ra', 96)
+ def _reduce_20(val, _values, result)
+ result = val[0] + val[2] ; loc result, val[1]
+ result
+ end
+.,.,
+
+module_eval(<<'.,.,', 'egrammar.ra', 97)
+ def _reduce_21(val, _values, result)
+ result = val[0] - val[2] ; loc result, val[1]
+ result
+ end
+.,.,
+
+module_eval(<<'.,.,', 'egrammar.ra', 98)
+ def _reduce_22(val, _values, result)
+ result = val[0] / val[2] ; loc result, val[1]
+ result
+ end
+.,.,
+
+module_eval(<<'.,.,', 'egrammar.ra', 99)
+ def _reduce_23(val, _values, result)
+ result = val[0] * val[2] ; loc result, val[1]
+ result
+ end
+.,.,
+
+module_eval(<<'.,.,', 'egrammar.ra', 100)
+ def _reduce_24(val, _values, result)
+ result = val[0] % val[2] ; loc result, val[1]
+ result
+ end
+.,.,
+
+module_eval(<<'.,.,', 'egrammar.ra', 101)
+ def _reduce_25(val, _values, result)
+ result = val[0] << val[2] ; loc result, val[1]
+ result
+ end
+.,.,
+
+module_eval(<<'.,.,', 'egrammar.ra', 102)
+ def _reduce_26(val, _values, result)
+ result = val[0] >> val[2] ; loc result, val[1]
+ result
+ end
+.,.,
+
+module_eval(<<'.,.,', 'egrammar.ra', 103)
+ def _reduce_27(val, _values, result)
+ result = val[1].minus() ; loc result, val[0]
+ result
+ end
+.,.,
+
+module_eval(<<'.,.,', 'egrammar.ra', 104)
+ def _reduce_28(val, _values, result)
+ result = val[0].ne val[2] ; loc result, val[1]
+ result
+ end
+.,.,
+
+module_eval(<<'.,.,', 'egrammar.ra', 105)
+ def _reduce_29(val, _values, result)
+ result = val[0] == val[2] ; loc result, val[1]
+ result
+ end
+.,.,
+
+module_eval(<<'.,.,', 'egrammar.ra', 106)
+ def _reduce_30(val, _values, result)
+ result = val[0] > val[2] ; loc result, val[1]
+ result
+ end
+.,.,
+
+module_eval(<<'.,.,', 'egrammar.ra', 107)
+ def _reduce_31(val, _values, result)
+ result = val[0] >= val[2] ; loc result, val[1]
+ result
+ end
+.,.,
+
+module_eval(<<'.,.,', 'egrammar.ra', 108)
+ def _reduce_32(val, _values, result)
+ result = val[0] < val[2] ; loc result, val[1]
+ result
+ end
+.,.,
+
+module_eval(<<'.,.,', 'egrammar.ra', 109)
+ def _reduce_33(val, _values, result)
+ result = val[0] <= val[2] ; loc result, val[1]
+ result
+ end
+.,.,
+
+module_eval(<<'.,.,', 'egrammar.ra', 110)
+ def _reduce_34(val, _values, result)
+ result = val[1].not ; loc result, val[0]
+ result
+ end
+.,.,
+
+module_eval(<<'.,.,', 'egrammar.ra', 111)
+ def _reduce_35(val, _values, result)
+ result = val[0].and val[2] ; loc result, val[1]
+ result
+ end
+.,.,
+
+module_eval(<<'.,.,', 'egrammar.ra', 112)
+ def _reduce_36(val, _values, result)
+ result = val[0].or val[2] ; loc result, val[1]
+ result
+ end
+.,.,
+
+module_eval(<<'.,.,', 'egrammar.ra', 113)
+ def _reduce_37(val, _values, result)
+ result = val[0].set(val[2]) ; loc result, val[1]
+ result
+ end
+.,.,
+
+module_eval(<<'.,.,', 'egrammar.ra', 114)
+ def _reduce_38(val, _values, result)
+ result = val[0].plus_set(val[2]) ; loc result, val[1]
+ result
+ end
+.,.,
+
+module_eval(<<'.,.,', 'egrammar.ra', 115)
+ def _reduce_39(val, _values, result)
+ result = val[0].select(*val[2]) ; loc result, val[0]
+ result
+ end
+.,.,
+
+module_eval(<<'.,.,', 'egrammar.ra', 116)
+ def _reduce_40(val, _values, result)
+ result = val[1].paren() ; loc result, val[0]
+ result
+ end
+.,.,
+
+module_eval(<<'.,.,', 'egrammar.ra', 124)
+ def _reduce_41(val, _values, result)
+ result = [val[0]]
+ result
+ end
+.,.,
+
+module_eval(<<'.,.,', 'egrammar.ra', 125)
+ def _reduce_42(val, _values, result)
+ result = val[0].push(val[2])
+ result
+ end
+.,.,
+
+# reduce 43 omitted
+
+# 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)
+ result = val[0]
+ result
+ end
+.,.,
+
+module_eval(<<'.,.,', 'egrammar.ra', 156)
+ def _reduce_63(val, _values, result)
+ result = val[0]
+ result
+ end
+.,.,
+
+module_eval(<<'.,.,', 'egrammar.ra', 164)
+ def _reduce_64(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)
+ 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)
+ 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)
+ 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)
+ result = val[0]
+ result
+ end
+.,.,
+
+module_eval(<<'.,.,', 'egrammar.ra', 186)
+ def _reduce_69(val, _values, result)
+ result = val[0]
+ result
+ end
+.,.,
+
+module_eval(<<'.,.,', 'egrammar.ra', 187)
+ def _reduce_70(val, _values, result)
+ result = val[0]; val[0].lambda = val[1]
+ result
+ end
+.,.,
+
+module_eval(<<'.,.,', 'egrammar.ra', 190)
+ def _reduce_71(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)
+ 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)
+ result = Factory.CALL_METHOD(val[0], []); loc result, val[0]
+ result
+ end
+.,.,
+
+module_eval(<<'.,.,', 'egrammar.ra', 197)
+ def _reduce_74(val, _values, result)
+ result = val[0].dot(Factory.fqn(val[2][:value]))
+ loc result, val[1], val[2]
+
+ result
+ end
+.,.,
+
+# reduce 75 omitted
+
+# reduce 76 omitted
+
+module_eval(<<'.,.,', 'egrammar.ra', 212)
+ def _reduce_77(val, _values, result)
+ result = Factory.LAMBDA(val[0], val[2])
+# loc result, val[1] # TODO
+
+ result
+ end
+.,.,
+
+module_eval(<<'.,.,', 'egrammar.ra', 217)
+ def _reduce_78(val, _values, result)
+ result = val[1]
+ result
+ end
+.,.,
+
+module_eval(<<'.,.,', 'egrammar.ra', 218)
+ def _reduce_79(val, _values, result)
+ result = nil
+ result
+ end
+.,.,
+
+# reduce 80 omitted
+
+# reduce 81 omitted
+
+module_eval(<<'.,.,', 'egrammar.ra', 228)
+ def _reduce_82(val, _values, result)
+ result = Factory.LAMBDA(val[1], val[2])
+ loc result, val[0], val[3]
+
+ result
+ end
+.,.,
+
+module_eval(<<'.,.,', 'egrammar.ra', 232)
+ def _reduce_83(val, _values, result)
+ result = Factory.LAMBDA(val[1], nil)
+ loc result, val[0], val[2]
+
+ result
+ end
+.,.,
+
+module_eval(<<'.,.,', 'egrammar.ra', 238)
+ def _reduce_84(val, _values, result)
+ result = []
+ result
+ end
+.,.,
+
+module_eval(<<'.,.,', 'egrammar.ra', 239)
+ def _reduce_85(val, _values, result)
+ result = val[1]
+ result
+ end
+.,.,
+
+module_eval(<<'.,.,', 'egrammar.ra', 249)
+ def _reduce_86(val, _values, result)
+ result = val[1]
+ loc(result, val[0], val[1])
+
+ result
+ end
+.,.,
+
+module_eval(<<'.,.,', 'egrammar.ra', 256)
+ def _reduce_87(val, _values, result)
+ result = Factory.IF(val[0], Factory.block_or_expression(*val[2]), val[4])
+ loc(result, val[0], (val[4] ? val[4] : val[3]))
+
+ result
+ end
+.,.,
+
+module_eval(<<'.,.,', 'egrammar.ra', 260)
+ def _reduce_88(val, _values, result)
+ result = Factory.IF(val[0], nil, val[3])
+ loc(result, val[0], (val[3] ? val[3] : val[2]))
+
+ result
+ end
+.,.,
+
+# reduce 89 omitted
+
+module_eval(<<'.,.,', 'egrammar.ra', 268)
+ def _reduce_90(val, _values, result)
+ result = val[1]
+ loc(result, val[0], val[1])
+
+ result
+ end
+.,.,
+
+module_eval(<<'.,.,', 'egrammar.ra', 272)
+ def _reduce_91(val, _values, result)
+ result = Factory.block_or_expression(*val[2])
+ loc result, val[0], val[3]
+
+ result
+ end
+.,.,
+
+module_eval(<<'.,.,', 'egrammar.ra', 276)
+ def _reduce_92(val, _values, result)
+ result = nil # don't think a nop is needed here either
+
+ result
+ end
+.,.,
+
+module_eval(<<'.,.,', 'egrammar.ra', 285)
+ def _reduce_93(val, _values, result)
+ result = Factory.UNLESS(val[1], Factory.block_or_expression(*val[3]), val[5])
+ loc result, val[0], val[4]
+
+ result
+ end
+.,.,
+
+module_eval(<<'.,.,', 'egrammar.ra', 289)
+ def _reduce_94(val, _values, result)
+ result = Factory.UNLESS(val[1], nil, nil)
+ loc result, val[0], val[4]
+
+ result
+ end
+.,.,
+
+# reduce 95 omitted
+
+module_eval(<<'.,.,', 'egrammar.ra', 299)
+ def _reduce_96(val, _values, result)
+ result = Factory.block_or_expression(*val[2])
+ loc result, val[0], val[3]
+
+ result
+ end
+.,.,
+
+module_eval(<<'.,.,', 'egrammar.ra', 303)
+ def _reduce_97(val, _values, result)
+ result = nil # don't think a nop is needed here either
+
+ result
+ end
+.,.,
+
+module_eval(<<'.,.,', 'egrammar.ra', 311)
+ def _reduce_98(val, _values, result)
+ result = Factory.CASE(val[1], *val[3])
+ loc result, val[0], val[4]
+
+ result
+ end
+.,.,
+
+module_eval(<<'.,.,', 'egrammar.ra', 317)
+ def _reduce_99(val, _values, result)
+ result = [val[0]]
+ result
+ end
+.,.,
+
+module_eval(<<'.,.,', 'egrammar.ra', 318)
+ def _reduce_100(val, _values, result)
+ result = val[0].push val[1]
+ result
+ end
+.,.,
+
+module_eval(<<'.,.,', 'egrammar.ra', 323)
+ def _reduce_101(val, _values, result)
+ result = Factory.WHEN(val[0], val[3])
+ loc result, val[1], val[4]
+
+ result
+ end
+.,.,
+
+module_eval(<<'.,.,', 'egrammar.ra', 327)
+ def _reduce_102(val, _values, result)
+ result = Factory.WHEN(val[0], nil)
+ loc result, val[1], val[3]
+
+ result
+ end
+.,.,
+
+module_eval(<<'.,.,', 'egrammar.ra', 331)
+ def _reduce_103(val, _values, result)
+ result = val[0]
+ result
+ end
+.,.,
+
+# reduce 104 omitted
+
+module_eval(<<'.,.,', 'egrammar.ra', 342)
+ def _reduce_105(val, _values, result)
+ result = val[1]
+
+ result
+ end
+.,.,
+
+module_eval(<<'.,.,', 'egrammar.ra', 347)
+ def _reduce_106(val, _values, result)
+ result = [val[0]]
+ result
+ end
+.,.,
+
+module_eval(<<'.,.,', 'egrammar.ra', 348)
+ def _reduce_107(val, _values, result)
+ result = val[0].push val[2]
+ result
+ end
+.,.,
+
+module_eval(<<'.,.,', 'egrammar.ra', 353)
+ def _reduce_108(val, _values, result)
+ result = Factory.MAP(val[0], val[2]) ; loc result, val[1]
+ result
+ end
+.,.,
+
+module_eval(<<'.,.,', 'egrammar.ra', 369)
+ def _reduce_109(val, _values, result)
+ result = val[0]
+
+ result
+ end
+.,.,
+
+module_eval(<<'.,.,', 'egrammar.ra', 372)
+ def _reduce_110(val, _values, result)
+ result = case Factory.resource_shape(val[1])
+ when :resource, :class
+ tmp = Factory.RESOURCE(Factory.fqn(token_text(val[1])), val[3])
+ tmp.form = val[0]
+ tmp
+ when :defaults
+ error "A resource default can not be virtual or exported"
+ when :override
+ error "A resource override can not be virtual or exported"
+ else
+ error "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', 387)
+ def _reduce_111(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"
+ else
+ error "Expression is not valid as a resource, resource-default, or resource-override"
+ end
+
+ result
+ end
+.,.,
+
+module_eval(<<'.,.,', 'egrammar.ra', 399)
+ def _reduce_112(val, _values, result)
+ result = case Factory.resource_shape(val[0])
+ when :resource, :class
+ Factory.RESOURCE(Factory.fqn(token_text(val[0])), val[2])
+ when :defaults
+ error "A resource default can not specify a resource name"
+ when :override
+ error "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"
+ end
+ loc result, val[0], val[4]
+
+ result
+ end
+.,.,
+
+module_eval(<<'.,.,', 'egrammar.ra', 412)
+ def _reduce_113(val, _values, result)
+ result = case Factory.resource_shape(val[0])
+ when :resource, :class
+ # This catches deprecated syntax.
+ error "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"
+ end
+ loc result, val[0], val[4]
+
+ result
+ end
+.,.,
+
+module_eval(<<'.,.,', 'egrammar.ra', 427)
+ def _reduce_114(val, _values, result)
+ result = Factory.RESOURCE(Factory.fqn(token_text(val[0])), val[2])
+ loc result, val[0], val[4]
+
+ result
+ end
+.,.,
+
+module_eval(<<'.,.,', 'egrammar.ra', 432)
+ def _reduce_115(val, _values, result)
+ result = Factory.RESOURCE_BODY(val[0], val[2])
+ result
+ end
+.,.,
+
+module_eval(<<'.,.,', 'egrammar.ra', 434)
+ def _reduce_116(val, _values, result)
+ result = val[0]
+ result
+ end
+.,.,
+
+module_eval(<<'.,.,', 'egrammar.ra', 437)
+ def _reduce_117(val, _values, result)
+ result = [val[0]]
+ result
+ end
+.,.,
+
+module_eval(<<'.,.,', 'egrammar.ra', 438)
+ def _reduce_118(val, _values, result)
+ result = val[0].push val[2]
+ result
+ end
+.,.,
+
+module_eval(<<'.,.,', 'egrammar.ra', 443)
+ def _reduce_119(val, _values, result)
+ result = :virtual
+ result
+ end
+.,.,
+
+module_eval(<<'.,.,', 'egrammar.ra', 444)
+ def _reduce_120(val, _values, result)
+ result = :exported
+ result
+ end
+.,.,
+
+module_eval(<<'.,.,', 'egrammar.ra', 456)
+ def _reduce_121(val, _values, result)
+ result = Factory.COLLECT(val[0], val[1], val[3])
+ loc result, val[0], val[5]
+
+ result
+ end
+.,.,
+
+module_eval(<<'.,.,', 'egrammar.ra', 460)
+ def _reduce_122(val, _values, result)
+ result = Factory.COLLECT(val[0], val[1], [])
+ loc result, val[0], val[1]
+
+ result
+ end
+.,.,
+
+module_eval(<<'.,.,', 'egrammar.ra', 465)
+ def _reduce_123(val, _values, result)
+ result = Factory.VIRTUAL_QUERY(val[1]) ; loc result, val[0], val[2]
+ result
+ end
+.,.,
+
+module_eval(<<'.,.,', 'egrammar.ra', 466)
+ def _reduce_124(val, _values, result)
+ result = Factory.EXPORTED_QUERY(val[1]) ; loc result, val[0], val[2]
+ result
+ end
+.,.,
+
+# reduce 125 omitted
+
+# reduce 126 omitted
+
+module_eval(<<'.,.,', 'egrammar.ra', 479)
+ def _reduce_127(val, _values, result)
+ result = []
+ result
+ end
+.,.,
+
+module_eval(<<'.,.,', 'egrammar.ra', 480)
+ def _reduce_128(val, _values, result)
+ result = [val[0]]
+ result
+ end
+.,.,
+
+module_eval(<<'.,.,', 'egrammar.ra', 481)
+ def _reduce_129(val, _values, result)
+ result = val[0].push(val[2])
+ result
+ end
+.,.,
+
+# reduce 130 omitted
+
+# reduce 131 omitted
+
+# reduce 132 omitted
+
+module_eval(<<'.,.,', 'egrammar.ra', 497)
+ def _reduce_133(val, _values, result)
+ result = Factory.ATTRIBUTE_OP(val[0][:value], :'=>', val[2])
+ loc result, val[0], val[2]
+
+ result
+ end
+.,.,
+
+module_eval(<<'.,.,', 'egrammar.ra', 501)
+ def _reduce_134(val, _values, result)
+ result = Factory.ATTRIBUTE_OP(val[0][:value], :'+>', val[2])
+ loc result, val[0], val[2]
+
+ result
+ end
+.,.,
+
+module_eval(<<'.,.,', 'egrammar.ra', 511)
+ def _reduce_135(val, _values, result)
+ result = 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', 516)
+ def _reduce_136(val, _values, result)
+ result = Factory.DEFINITION(classname(val[1][:value]), val[2], nil)
+ loc result, val[0], val[4]
+ @lexer.indefine = false
+
+ result
+ end
+.,.,
+
+module_eval(<<'.,.,', 'egrammar.ra', 531)
+ def _reduce_137(val, _values, result)
+ @lexer.namepop
+ result = 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', 536)
+ def _reduce_138(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]
+
+ result
+ end
+.,.,
+
+# reduce 139 omitted
+
+module_eval(<<'.,.,', 'egrammar.ra', 544)
+ def _reduce_140(val, _values, result)
+ result = val[1]
+ result
+ end
+.,.,
+
+# reduce 141 omitted
+
+# reduce 142 omitted
+
+module_eval(<<'.,.,', 'egrammar.ra', 561)
+ def _reduce_143(val, _values, result)
+ result = Factory.NODE(val[1], val[2], val[4])
+ loc result, val[0], val[5]
+
+ result
+ end
+.,.,
+
+module_eval(<<'.,.,', 'egrammar.ra', 565)
+ def _reduce_144(val, _values, result)
+ result = Factory.NODE(val[1], val[2], nil)
+ loc result, val[0], val[4]
+
+ result
+ end
+.,.,
+
+module_eval(<<'.,.,', 'egrammar.ra', 575)
+ def _reduce_145(val, _values, result)
+ result = [result]
+ result
+ end
+.,.,
+
+module_eval(<<'.,.,', 'egrammar.ra', 576)
+ def _reduce_146(val, _values, result)
+ result = val[0].push(val[2])
+ result
+ end
+.,.,
+
+module_eval(<<'.,.,', 'egrammar.ra', 581)
+ def _reduce_147(val, _values, result)
+ result = Factory.fqn(val[0][:value]); loc result, val[0]
+ result
+ end
+.,.,
+
+module_eval(<<'.,.,', 'egrammar.ra', 582)
+ def _reduce_148(val, _values, result)
+ result = val[0]
+ result
+ end
+.,.,
+
+module_eval(<<'.,.,', 'egrammar.ra', 583)
+ def _reduce_149(val, _values, result)
+ result = Factory.literal(:default); loc result, val[0]
+ result
+ end
+.,.,
+
+# reduce 150 omitted
+
+# reduce 151 omitted
+
+module_eval(<<'.,.,', 'egrammar.ra', 589)
+ def _reduce_152(val, _values, result)
+ result = val[1]
+ result
+ end
+.,.,
+
+module_eval(<<'.,.,', 'egrammar.ra', 594)
+ def _reduce_153(val, _values, result)
+ result = val[0]
+ result
+ end
+.,.,
+
+module_eval(<<'.,.,', 'egrammar.ra', 595)
+ def _reduce_154(val, _values, result)
+ result = val[0]
+ result
+ end
+.,.,
+
+module_eval(<<'.,.,', 'egrammar.ra', 599)
+ def _reduce_155(val, _values, result)
+ result = []
+ result
+ end
+.,.,
+
+module_eval(<<'.,.,', 'egrammar.ra', 600)
+ def _reduce_156(val, _values, result)
+ result = []
+ result
+ end
+.,.,
+
+module_eval(<<'.,.,', 'egrammar.ra', 601)
+ def _reduce_157(val, _values, result)
+ result = val[1]
+ result
+ end
+.,.,
+
+module_eval(<<'.,.,', 'egrammar.ra', 605)
+ def _reduce_158(val, _values, result)
+ result = [val[0]]
+ result
+ end
+.,.,
+
+module_eval(<<'.,.,', 'egrammar.ra', 606)
+ def _reduce_159(val, _values, result)
+ result = val[0].push(val[2])
+ result
+ end
+.,.,
+
+module_eval(<<'.,.,', 'egrammar.ra', 610)
+ def _reduce_160(val, _values, result)
+ result = Factory.PARAM(val[0][:value], val[2]) ; loc result, val[0]
+ result
+ end
+.,.,
+
+module_eval(<<'.,.,', 'egrammar.ra', 611)
+ def _reduce_161(val, _values, result)
+ result = Factory.PARAM(val[0][:value]); loc result, val[0]
+ result
+ end
+.,.,
+
+# reduce 162 omitted
+
+# reduce 163 omitted
+
+module_eval(<<'.,.,', 'egrammar.ra', 624)
+ def _reduce_164(val, _values, result)
+ result = Factory.fqn(val[0][:value]).var ; loc result, val[0]
+ result
+ end
+.,.,
+
+module_eval(<<'.,.,', 'egrammar.ra', 630)
+ def _reduce_165(val, _values, result)
+ result = Factory.LIST(val[1]); loc result, val[0], val[2]
+ result
+ end
+.,.,
+
+module_eval(<<'.,.,', 'egrammar.ra', 631)
+ def _reduce_166(val, _values, result)
+ result = Factory.LIST(val[1]); loc result, val[0], val[3]
+ result
+ end
+.,.,
+
+module_eval(<<'.,.,', 'egrammar.ra', 632)
+ def _reduce_167(val, _values, result)
+ result = Factory.literal([]) ; loc result, val[0]
+ result
+ end
+.,.,
+
+module_eval(<<'.,.,', 'egrammar.ra', 635)
+ def _reduce_168(val, _values, result)
+ result = Factory.HASH(val[1]); loc result, val[0], val[2]
+ result
+ end
+.,.,
+
+module_eval(<<'.,.,', 'egrammar.ra', 636)
+ def _reduce_169(val, _values, result)
+ result = Factory.HASH(val[1]); loc result, val[0], val[3]
+ result
+ end
+.,.,
+
+module_eval(<<'.,.,', 'egrammar.ra', 637)
+ def _reduce_170(val, _values, result)
+ result = Factory.literal({}) ; loc result, val[0], val[3]
+ result
+ end
+.,.,
+
+module_eval(<<'.,.,', 'egrammar.ra', 640)
+ def _reduce_171(val, _values, result)
+ result = [val[0]]
+ result
+ end
+.,.,
+
+module_eval(<<'.,.,', 'egrammar.ra', 641)
+ def _reduce_172(val, _values, result)
+ result = val[0].push val[2]
+ result
+ end
+.,.,
+
+module_eval(<<'.,.,', 'egrammar.ra', 644)
+ def _reduce_173(val, _values, result)
+ result = Factory.KEY_ENTRY(val[0], val[2]); loc result, val[1]
+ result
+ end
+.,.,
+
+# reduce 174 omitted
+
+# reduce 175 omitted
+
+module_eval(<<'.,.,', 'egrammar.ra', 650)
+ def _reduce_176(val, _values, result)
+ result = Factory.literal(val[0][:value]) ; loc result, val[0]
+ result
+ end
+.,.,
+
+module_eval(<<'.,.,', 'egrammar.ra', 651)
+ def _reduce_177(val, _values, result)
+ result = Factory.string(val[0], *val[1]) ; loc result, val[0], val[1][-1]
+ result
+ end
+.,.,
+
+module_eval(<<'.,.,', 'egrammar.ra', 652)
+ def _reduce_178(val, _values, result)
+ result = Factory.literal(val[0][:value]); loc result, val[0]
+ result
+ end
+.,.,
+
+module_eval(<<'.,.,', 'egrammar.ra', 653)
+ def _reduce_179(val, _values, result)
+ result = Factory.literal(val[0][:value]); loc result, val[0]
+ result
+ end
+.,.,
+
+module_eval(<<'.,.,', 'egrammar.ra', 654)
+ def _reduce_180(val, _values, result)
+ result = Factory.literal(val[0][:value]); loc result, val[0]
+ result
+ end
+.,.,
+
+module_eval(<<'.,.,', 'egrammar.ra', 655)
+ def _reduce_181(val, _values, result)
+ result = [val[0]] + val[1]
+ result
+ end
+.,.,
+
+module_eval(<<'.,.,', 'egrammar.ra', 656)
+ def _reduce_182(val, _values, result)
+ result = Factory.TEXT(val[0])
+ result
+ end
+.,.,
+
+module_eval(<<'.,.,', 'egrammar.ra', 659)
+ def _reduce_183(val, _values, result)
+ result = [val[0]]
+ result
+ end
+.,.,
+
+module_eval(<<'.,.,', 'egrammar.ra', 660)
+ def _reduce_184(val, _values, result)
+ result = [val[0]] + val[1]
+ result
+ end
+.,.,
+
+module_eval(<<'.,.,', 'egrammar.ra', 662)
+ def _reduce_185(val, _values, result)
+ result = Factory.QNAME_OR_NUMBER(val[0][:value]) ; loc result, val[0]
+ result
+ end
+.,.,
+
+module_eval(<<'.,.,', 'egrammar.ra', 663)
+ def _reduce_186(val, _values, result)
+ result = Factory.QREF(val[0][:value]) ; loc result, val[0]
+ result
+ end
+.,.,
+
+module_eval(<<'.,.,', 'egrammar.ra', 664)
+ def _reduce_187(val, _values, result)
+ result = Factory.literal(:undef); loc result, val[0]
+ result
+ end
+.,.,
+
+module_eval(<<'.,.,', 'egrammar.ra', 665)
+ def _reduce_188(val, _values, result)
+ result = Factory.literal(:default); loc result, val[0]
+ result
+ end
+.,.,
+
+module_eval(<<'.,.,', 'egrammar.ra', 670)
+ def _reduce_189(val, _values, result)
+ result = Factory.literal(val[0][:value]) ; loc result, val[0]
+ result
+ end
+.,.,
+
+module_eval(<<'.,.,', 'egrammar.ra', 673)
+ def _reduce_190(val, _values, result)
+ result = Factory.literal(val[0][:value]); loc result, val[0]
+ result
+ end
+.,.,
+
+# reduce 191 omitted
+
+module_eval(<<'.,.,', 'egrammar.ra', 679)
+ def _reduce_192(val, _values, result)
+ result = nil
+ result
+ end
+.,.,
+
+# 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
+
+# reduce 203 omitted
+
+# reduce 204 omitted
+
+# reduce 205 omitted
+
+# reduce 206 omitted
+
+# reduce 207 omitted
+
+# reduce 208 omitted
+
+module_eval(<<'.,.,', 'egrammar.ra', 702)
+ def _reduce_209(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/grammar.ra b/lib/puppet/pops/parser/grammar.ra
new file mode 100644
index 0000000..7352bdb
--- /dev/null
+++ b/lib/puppet/pops/parser/grammar.ra
@@ -0,0 +1,746 @@
+# 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<Model::Expression>
+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<Model::Parameter>
+lambda_parameter_list
+ : PIPE PIPE { result = [] }
+ | PIPE parameters endcomma PIPE { result = val[1] }
+
+# Produces Array<Model::Expression>
+func_call_args
+ : rvalue { result = [val[0]] }
+ | func_call_args COMMA rvalue { result = val[0].push(val[2]) }
+
+# Produces Array<Model::Expression>
+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<Model::AttributeOperation>
+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<Model::CaseOption>
+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<Expression> 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<Model::SelectorEntry>
+selector_entries
+ : selector_entry { result = [val[0]] }
+ | LBRACE selector_entry_list endcomma RBRACE {
+ @lexer.commentpop
+ result = val[1]
+ }
+
+# Produces Array<Model::SelectorEntry>
+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<Model::LiteralExpression>
+#
+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<Model::Parameter>
+parameter_list
+ : nil { result = [] }
+ | LPAREN RPAREN { result = [] }
+ | LPAREN parameters endcomma RPAREN { result = val[1] }
+
+# Produces Array<Model::Parameter>
+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/lexer.rb b/lib/puppet/pops/parser/lexer.rb
new file mode 100644
index 0000000..106f407
--- /dev/null
+++ b/lib/puppet/pops/parser/lexer.rb
@@ -0,0 +1,842 @@
+# 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
+
+ # @param string_or_regex[String] a literal string token matcher
+ # @param string_or_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] ? ? ?
+ #
+ 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
+ '}' => :RBRACE,
+ '(' => :LPAREN,
+ ')' => :RPAREN,
+ '=' => :EQUALS,
+ '+=' => :APPENDS,
+ '==' => :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>" => :STRING,
+ "<dqstring up to first interpolation>" => :DQPRE,
+ "<dqstring between two interpolations>" => :DQMID,
+ "<dqstring after final interpolation>" => :DQPOST,
+ "<boolean>" => :BOOLEAN,
+ "<lambda start>" => :LAMBDA, # A LBRACE followed by '|'
+ "<select start>" => :SELBRACE # A QMARK followed by '{'
+ )
+
+ module Contextual
+ QUOTE_TOKENS = [:DQPRE,:DQMID]
+ REGEX_INTRODUCING_TOKENS = [:NODE,:LBRACE, :SELBRACE, :RBRACE,:MATCH,:NOMATCH,:COMMA]
+
+ NOT_INSIDE_QUOTES = Proc.new do |context|
+ !QUOTE_TOKENS.include? context[:after]
+ end
+
+ INSIDE_QUOTES = Proc.new do |context|
+ QUOTE_TOKENS.include? context[:after]
+ end
+
+ IN_REGEX_POSITION = Proc.new do |context|
+ REGEX_INTRODUCING_TOKENS.include? context[:after]
+ end
+
+ IN_STRING_INTERPOLATION = Proc.new do |context|
+ context[:string_interpolation_depth] > 0
+ end
+
+ DASHED_VARIABLES_ALLOWED = Proc.new do |context|
+ Puppet[:allow_variables_with_dashes]
+ end
+
+ VARIABLE_AND_DASHES_ALLOWED = Proc.new do |context|
+ Contextual::DASHED_VARIABLES_ALLOWED.call(context) and TOKENS[:VARIABLE].acceptable?(context)
+ end
+ end
+
+ # LBRACE needs look ahead to differentiate between '{' and a '{'
+ # followed by a '|' (start of lambda) The racc grammar can only do one
+ # token lookahead.
+ #
+ TOKENS.add_token :LBRACE, /\{/ do | lexer, value |
+ if lexer.match?(/[ \t\r]*\|/)
+ [TOKENS[:LAMBDA], value]
+ elsif lexer.lexing_context[:after] == :QMARK
+ [TOKENS[:SELBRACE], value]
+ else
+ [TOKENS[:LBRACE], value]
+ end
+ end
+
+ # Numbers are treated separately from names, so that they may contain dots.
+ TOKENS.add_token :NUMBER, %r{\b(?:0[xX][0-9A-Fa-f]+|0?\d+(?:\.\d+)?(?:[eE]-?\d+)?)\b} do |lexer, value|
+ lexer.assert_numeric(value)
+ [TOKENS[:NAME], value]
+ end
+ TOKENS[:NUMBER].acceptable_when Contextual::NOT_INSIDE_QUOTES
+
+ TOKENS.add_token :NAME, %r{((::)?[a-z0-9][-\w]*)(::[a-z0-9][-\w]*)*} do |lexer, value|
+ # A name starting with a number must be a valid numeric string (not that
+ # NUMBER token captures those names that do not comply with the name rule.
+ if value =~ /^[0-9].*$/
+ lexer.assert_numeric(value)
+ end
+
+ string_token = self
+ # we're looking for keywords here
+ if tmp = KEYWORDS.lookup(value)
+ string_token = tmp
+ if [:TRUE, :FALSE].include?(string_token.name)
+ value = eval(value)
+ string_token = TOKENS[:BOOLEAN]
+ end
+ end
+ [string_token, value]
+ end
+ [:NAME, :CLASSREF].each do |name_token|
+ TOKENS[name_token].acceptable_when Contextual::NOT_INSIDE_QUOTES
+ end
+
+ TOKENS.add_token :COMMENT, %r{#.*}, :skip => true do |lexer,value|
+ value.sub!(/# ?/,'')
+ [self, value]
+ end
+
+ TOKENS.add_token :MLCOMMENT, %r{/\*(.*?)\*/}m, :skip => true do |lexer, value|
+ value.sub!(/^\/\* ?/,'')
+ value.sub!(/ ?\*\/$/,'')
+ [self,value]
+ end
+
+ TOKENS.add_token :REGEX, %r{/[^/\n]*/} do |lexer, value|
+ # Make sure we haven't matched an escaped /
+ while value[-2..-2] == '\\'
+ other = lexer.scan_until(%r{/})
+ value += other
+ end
+ regex = value.sub(%r{\A/}, "").sub(%r{/\Z}, '').gsub("\\/", "/")
+ [self, Regexp.new(regex)]
+ end
+ TOKENS[:REGEX].acceptable_when Contextual::IN_REGEX_POSITION
+
+ TOKENS.add_token :RETURN, "\n", :skip => true, :skip_text => true
+
+ TOKENS.add_token :SQUOTE, "'" do |lexer, value|
+ [TOKENS[:STRING], lexer.slurpstring(value,["'"],:ignore_invalid_escapes).first ]
+ end
+
+ DQ_initial_token_types = {'$' => :DQPRE,'"' => :STRING}
+ DQ_continuation_token_types = {'$' => :DQMID,'"' => :DQPOST}
+
+ TOKENS.add_token :DQUOTE, /"/ do |lexer, value|
+ lexer.tokenize_interpolated_string(DQ_initial_token_types)
+ end
+
+ TOKENS.add_token :DQCONT, /\}/ do |lexer, value|
+ lexer.tokenize_interpolated_string(DQ_continuation_token_types)
+ end
+ TOKENS[:DQCONT].acceptable_when Contextual::IN_STRING_INTERPOLATION
+
+ TOKENS.add_token :DOLLAR_VAR_WITH_DASH, %r{\$(?:::)?(?:[-\w]+::)*[-\w]+} do |lexer, value|
+ lexer.warn_if_variable_has_hyphen(value)
+
+ [TOKENS[:VARIABLE], value[1..-1]]
+ end
+ TOKENS[:DOLLAR_VAR_WITH_DASH].acceptable_when Contextual::DASHED_VARIABLES_ALLOWED
+
+ TOKENS.add_token :DOLLAR_VAR, %r{\$(::)?(\w+::)*\w+} do |lexer, value|
+ [TOKENS[:VARIABLE],value[1..-1]]
+ end
+
+ TOKENS.add_token :VARIABLE_WITH_DASH, %r{(?:::)?(?:[-\w]+::)*[-\w]+} do |lexer, value|
+ lexer.warn_if_variable_has_hyphen(value)
+ # If the varname (following $, or ${ is followed by (, it is a function call, and not a variable
+ # reference.
+ #
+ if lexer.match?(%r{[ \t\r]*\(})
+ [TOKENS[:NAME],value]
+ else
+ [TOKENS[:VARIABLE], value]
+ end
+ end
+ TOKENS[:VARIABLE_WITH_DASH].acceptable_when Contextual::VARIABLE_AND_DASHES_ALLOWED
+
+ TOKENS.add_token :VARIABLE, %r{(::)?(\w+::)*\w+} do |lexer, value|
+ # If the varname (following $, or ${ is followed by (, it is a function call, and not a variable
+ # reference.
+ #
+ if lexer.match?(%r{[ \t\r]*\(})
+ [TOKENS[:NAME],value]
+ else
+ [TOKENS[:VARIABLE],value]
+ end
+
+ end
+ TOKENS[:VARIABLE].acceptable_when Contextual::INSIDE_QUOTES
+
+ TOKENS.sort_tokens
+
+ @@pairs = {
+ "{" => "}",
+ "(" => ")",
+ "[" => "]",
+ "<|" => "|>",
+ "<<|" => "|>>",
+ "|" => "|"
+ }
+
+ KEYWORDS = TokenList.new
+ KEYWORDS.add_tokens(
+ "case" => :CASE,
+ "class" => :CLASS,
+ "default" => :DEFAULT,
+ "define" => :DEFINE,
+ # "import" => :IMPORT,
+ "if" => :IF,
+ "elsif" => :ELSIF,
+ "else" => :ELSE,
+ "inherits" => :INHERITS,
+ "node" => :NODE,
+ "and" => :AND,
+ "or" => :OR,
+ "undef" => :UNDEF,
+ "false" => :FALSE,
+ "true" => :TRUE,
+ "in" => :IN,
+ "unless" => :UNLESS
+ )
+
+ def clear
+ initvars
+ end
+
+ def expected
+ return nil if @expected.empty?
+ name = @expected[-1]
+ TOKENS.lookup(name) or lex_error "Internal Lexer Error: Could not find expected token #{name}"
+ end
+
+ # scan the whole file
+ # basically just used for testing
+ def fullscan
+ array = []
+
+ self.scan { |token, str|
+ # Ignore any definition nesting problems
+ @indefine = false
+ array.push([token,str])
+ }
+ array
+ end
+
+ def file=(file)
+ @file = file
+ contents = File.exists?(file) ? File.read(file) : ""
+ @scanner = StringScanner.new(contents)
+ @locator = Locator.new(contents, multibyte?)
+ end
+
+ def_delegator :@token_queue, :shift, :shift_token
+
+ def find_string_token
+ # We know our longest string token is three chars, so try each size in turn
+ # until we either match or run out of chars. This way our worst-case is three
+ # tries, where it is otherwise the number of string token we have. Also,
+ # the lookups are optimized hash lookups, instead of regex scans.
+ #
+ s = @scanner.peek(3)
+ token = TOKENS.lookup(s[0,3]) || TOKENS.lookup(s[0,2]) || TOKENS.lookup(s[0,1])
+ [ token, token && @scanner.scan(token.regex) ]
+ end
+
+ # Find the next token that matches a regex. We look for these first.
+ def find_regex_token
+ best_token = nil
+ best_length = 0
+
+ # I tried optimizing based on the first char, but it had
+ # a slightly negative affect and was a good bit more complicated.
+ TOKENS.regex_tokens.each do |token|
+ if length = @scanner.match?(token.regex) and token.acceptable?(lexing_context)
+ # We've found a longer match
+ if length > best_length
+ best_length = length
+ best_token = token
+ end
+ end
+ end
+
+ return best_token, @scanner.scan(best_token.regex) if best_token
+ end
+
+ # Find the next token, returning the string and the token.
+ def find_token
+ shift_token || find_regex_token || find_string_token
+ end
+
+ def initialize
+ @multibyte = init_multibyte
+ initvars
+ end
+
+ def assert_numeric(value)
+ if value =~ /^0[xX].*$/
+ lex_error (positioned_message("Not a valid hex number #{value}")) unless value =~ /^0[xX][0-9A-Fa-f]+$/
+ elsif value =~ /^0[^.].*$/
+ lex_error(positioned_message("Not a valid octal number #{value}")) unless value =~ /^0[0-7]+$/
+ else
+ lex_error(positioned_message("Not a valid decimal number #{value}")) unless value =~ /0?\d+(?:\.\d+)?(?:[eE]-?\d+)?/
+ end
+ end
+
+ # Returns true if ruby version >= 1.9.3 since regexp supports multi-byte matches and expanded
+ # character categories like [[:blank:]].
+ #
+ # This implementation will fail if there are more than 255 minor or micro versions of ruby
+ #
+ def init_multibyte
+ numver = RUBY_VERSION.split(".").collect {|s| s.to_i }
+ return true if (numver[0] << 16 | numver[1] << 8 | numver[2]) >= (1 << 16 | 9 << 8 | 3)
+ false
+ end
+
+ def multibyte?
+ @multibyte
+ end
+
+ def initvars
+ @previous_token = nil
+ @scanner = nil
+ @file = nil
+
+ # AAARRGGGG! okay, regexes in ruby are bloody annoying
+ # no one else has "\n" =~ /\s/
+
+ if multibyte?
+ # Skip all kinds of space, and CR, but not newlines
+ @skip = %r{[[:blank:]\r]+}
+ else
+ @skip = %r{[ \t\r]+}
+ end
+
+ @namestack = []
+ @token_queue = []
+ @indefine = false
+ @expected = []
+ @lexing_context = {
+ :after => nil,
+ :start_of_line => true,
+ :offset => 0, # byte offset before where token starts
+ :end_offset => 0, # byte offset after scanned token
+ :string_interpolation_depth => 0
+ }
+ end
+
+ # Make any necessary changes to the token and/or value.
+ def munge_token(token, value)
+ # A token may already have been munged (converted and positioned)
+ #
+ return token, value if value.is_a? Hash
+
+ skip if token.skip_text
+
+ return if token.skip
+
+ token, value = token.convert(self, value) if token.respond_to?(:convert)
+
+ return unless token
+
+ return if token.skip
+
+ # If the conversion performed the munging/positioning
+ return token, value if value.is_a? Hash
+
+ pos_hash = position_in_source
+ pos_hash[:value] = value
+
+ # Add one to pos, first char on line is 1
+ return token, pos_hash
+ end
+
+ # Returns a hash with the current position in source based on the current lexing context
+ #
+ def position_in_source
+ pos = @locator.pos_on_line(lexing_context[:offset])
+ offset = @locator.char_offset(lexing_context[:offset])
+ length = @locator.char_length(lexing_context[:offset], lexing_context[:end_offset])
+ start_line = @locator.line_for_offset(lexing_context[:offset])
+
+ return { :line => start_line, :pos => pos, :offset => offset, :length => length}
+ end
+
+ def pos
+ @locator.pos_on_line(lexing_context[:offset])
+ end
+
+ # Handling the namespace stack
+ def_delegator :@namestack, :pop, :namepop
+
+ # This value might have :: in it, but we don't care -- it'll be handled
+ # normally when joining, and when popping we want to pop this full value,
+ # however long the namespace is.
+ def_delegator :@namestack, :<<, :namestack
+
+ # Collect the current namespace.
+ def namespace
+ @namestack.join("::")
+ end
+
+ def_delegator :@scanner, :rest
+ # this is the heart of the lexer
+ def scan
+ #Puppet.debug("entering scan")
+ lex_error "Internal Error: No string or file given to lexer to process." unless @scanner
+
+ # Skip any initial whitespace.
+ skip
+
+ until token_queue.empty? and @scanner.eos? do
+ yielded = false
+ offset = @scanner.pos
+ matched_token, value = find_token
+ end_offset = @scanner.pos
+
+ # error out if we didn't match anything at all
+ lex_error "Could not match #{@scanner.rest[/^(\S+|\s+|.*)/]}" unless matched_token
+
+ newline = matched_token.name == :RETURN
+
+ lexing_context[:start_of_line] = newline
+ lexing_context[:offset] = offset
+ lexing_context[:end_offset] = end_offset
+
+ final_token, token_value = munge_token(matched_token, value)
+ # update end position since munging may have moved the end offset
+ lexing_context[:end_offset] = @scanner.pos
+
+ unless final_token
+ skip
+ next
+ end
+
+ lexing_context[:after] = final_token.name unless newline
+ lexing_context[:string_interpolation_depth] += 1 if final_token.name == :DQPRE
+ lexing_context[:string_interpolation_depth] -= 1 if final_token.name == :DQPOST
+
+ value = token_value[:value]
+
+ if match = @@pairs[value] and final_token.name != :DQUOTE and final_token.name != :SQUOTE
+ @expected << match
+ elsif exp = @expected[-1] and exp == value and final_token.name != :DQUOTE and final_token.name != :SQUOTE
+ @expected.pop
+ end
+
+ yield [final_token.name, token_value]
+
+ if @previous_token
+ namestack(value) if @previous_token.name == :CLASS and value != '{'
+
+ if @previous_token.name == :DEFINE
+ if indefine?
+ msg = "Cannot nest definition #{value} inside #{@indefine}"
+ self.indefine = false
+ raise Puppet::ParseError, msg
+ end
+
+ @indefine = value
+ end
+ end
+ @previous_token = final_token
+ skip
+ end
+ # Cannot reset @scanner to nil here - it is needed to answer questions about context after
+ # completed parsing.
+ # Seems meaningless to do this. Everything will be gc anyway.
+ #@scanner = nil
+
+ # This indicates that we're done parsing.
+ yield [false,false]
+ end
+
+ # Skip any skipchars in our remaining string.
+ def skip
+ @scanner.skip(@skip)
+ end
+
+ def match? r
+ @scanner.match?(r)
+ end
+
+ # Provide some limited access to the scanner, for those
+ # tokens that need it.
+ def_delegator :@scanner, :scan_until
+
+ # we've encountered the start of a string...
+ # slurp in the rest of the string and return it
+ def slurpstring(terminators,escapes=%w{ \\ $ ' " r n t s }+["\n"],ignore_invalid_escapes=false)
+ # we search for the next quote that isn't preceded by a
+ # backslash; the caret is there to match empty strings
+ last = @scanner.matched
+ tmp_offset = @scanner.pos
+ str = @scanner.scan_until(/([^\\]|^|[^\\])([\\]{2})*[#{terminators}]/) || lex_error(positioned_message("Unclosed quote after #{format_quote(last)} followed by '#{followed_by}'"))
+ str.gsub!(/\\(.)/m) {
+ ch = $1
+ if escapes.include? ch
+ case ch
+ when 'r'; "\r"
+ when 'n'; "\n"
+ when 't'; "\t"
+ when 's'; " "
+ when "\n"; ''
+ else ch
+ end
+ else
+ Puppet.warning(positioned_message("Unrecognized escape sequence '\\#{ch}'")) unless ignore_invalid_escapes
+ "\\#{ch}"
+ end
+ }
+ [ str[0..-2],str[-1,1] ]
+ end
+
+ # Formats given message by appending file, line and position if available.
+ def positioned_message msg
+ result = [msg]
+ result << "in file #{file}" if file
+ result << "at line #{line}:#{pos}" if line
+ result.join(" ")
+ end
+
+ # Returns "<eof>" if at end of input, else the following 5 characters with \n \r \t escaped
+ def followed_by
+ return "<eof>" if @scanner.eos?
+ result = @scanner.rest[0,5] + "..."
+ result.gsub!("\t", '\t')
+ result.gsub!("\n", '\n')
+ result.gsub!("\r", '\r')
+ result
+ end
+
+ def format_quote q
+ if q == "'"
+ '"\'"'
+ else
+ "'#{q}'"
+ end
+ end
+
+ def tokenize_interpolated_string(token_type,preamble='')
+ # Expecting a (possibly empty) stretch of text terminated by end of string ", a variable $, or expression ${
+ # The length of this part includes the start and terminating characters.
+ value,terminator = slurpstring('"$')
+
+ # Advanced after '{' if this is in expression ${} interpolation
+ braced = terminator == '$' && @scanner.scan(/\{/)
+ # make offset to end_ofset be the length of the pre expression string including its start and terminating chars
+ lexing_context[:end_offset] = @scanner.pos
+
+ token_queue << [TOKENS[token_type[terminator]],position_in_source().merge!({:value => preamble+value})]
+ variable_regex = if Puppet[:allow_variables_with_dashes]
+ TOKENS[:VARIABLE_WITH_DASH].regex
+ else
+ TOKENS[:VARIABLE].regex
+ end
+ if terminator != '$' or braced
+ return token_queue.shift
+ end
+
+ tmp_offset = @scanner.pos
+ if var_name = @scanner.scan(variable_regex)
+ lexing_context[:offset] = tmp_offset
+ lexing_context[:end_offset] = @scanner.pos
+ warn_if_variable_has_hyphen(var_name)
+ # If the varname after ${ is followed by (, it is a function call, and not a variable
+ # reference.
+ #
+ if braced && @scanner.match?(%r{[ \t\r]*\(})
+ token_queue << [TOKENS[:NAME], position_in_source().merge!({:value=>var_name})]
+ else
+ token_queue << [TOKENS[:VARIABLE],position_in_source().merge!({:value=>var_name})]
+ end
+ lexing_context[:offset] = @scanner.pos
+ tokenize_interpolated_string(DQ_continuation_token_types)
+ else
+ tokenize_interpolated_string(token_type, replace_false_start_with_text(terminator))
+ end
+ end
+
+ def replace_false_start_with_text(appendix)
+ last_token = token_queue.pop
+ value = last_token.last
+ if value.is_a? Hash
+ value[:value] + appendix
+ else
+ value + appendix
+ end
+ end
+
+ # just parse a string, not a whole file
+ def string=(string)
+ @scanner = StringScanner.new(string)
+ @locator = Locator.new(string, multibyte?)
+ end
+
+ def warn_if_variable_has_hyphen(var_name)
+ if var_name.include?('-')
+ Puppet.deprecation_warning("Using `-` in variable names is deprecated at #{file || '<string>'}:#{line}. See http://links.puppetlabs.com/puppet-hyphenated-variable-deprecation")
+ end
+ end
+
+ # Returns the line number (starting from 1) for the current position
+ # in the scanned text (at the end of the last produced, but not necessarily
+ # consumed.
+ #
+ def line
+ return 1 unless lexing_context && locator
+ locator.line_for_offset(lexing_context[:end_offset])
+ end
+
+ # Helper class that keeps track of where line breaks are located and can answer questions about positions.
+ #
+ class Locator
+ attr_reader :line_index
+ attr_reader :string
+
+ # Create a locator based on a content string, and a boolean indicating if ruby version support multi-byte strings
+ # or not.
+ #
+ def initialize(string, multibyte)
+ @string = string
+ @multibyte = multibyte
+ compute_line_index
+ end
+
+ # Returns whether this a ruby version that supports multi-byte strings or not
+ #
+ def multibyte?
+ @multibyte
+ end
+
+ # Computes the start offset for each line.
+ #
+ def compute_line_index
+ scanner = StringScanner.new(@string)
+ result = [0] # first line starts at 0
+ while scanner.scan_until(/\n/)
+ result << scanner.pos
+ end
+ @line_index = result
+ end
+
+ # Returns the line number (first line is 1) for the given offset
+ def line_for_offset(offset)
+ if line_nbr = line_index.index {|x| x > offset}
+ return line_nbr
+ end
+ # If not found it is after last
+ return line_index.size
+ end
+
+ # Returns the offset on line (first offset on a line is 0).
+ #
+ def offset_on_line(offset)
+ line_offset = line_index[line_for_offset(offset)-1]
+ if multibyte?
+ @string.byteslice(line_offset, offset-line_offset).length
+ else
+ offset - line_offset
+ end
+ end
+
+ # Returns the position on line (first position on a line is 1)
+ def pos_on_line(offset)
+ offset_on_line(offset) +1
+ end
+
+ # Returns the character offset for a given byte offset
+ def char_offset(byte_offset)
+ if multibyte?
+ @string.byteslice(0, byte_offset).length
+ else
+ byte_offset
+ end
+ end
+
+ # Returns the length measured in number of characters from the given start and end byte offseta
+ def char_length(offset, end_offset)
+ if multibyte?
+ @string.byteslice(offset, end_offset - offset).length
+ else
+ end_offset - offset
+ end
+ end
+ end
+end
diff --git a/lib/puppet/pops/parser/makefile b/lib/puppet/pops/parser/makefile
new file mode 100644
index 0000000..f521747
--- /dev/null
+++ b/lib/puppet/pops/parser/makefile
@@ -0,0 +1,13 @@
+
+#parser.rb: grammar.ra
+# racc -o$@ grammar.ra
+#
+#grammar.output: grammar.ra
+# racc -v -o$@ grammar.ra
+#
+
+eparser.rb: egrammar.ra
+ racc -o$@ egrammar.ra
+
+egrammar.output: egrammar.ra
+ racc -v -o$@ egrammar.ra
\ No newline at end of file
diff --git a/lib/puppet/pops/parser/parser_support.rb b/lib/puppet/pops/parser/parser_support.rb
new file mode 100644
index 0000000..db35aad
--- /dev/null
+++ b/lib/puppet/pops/parser/parser_support.rb
@@ -0,0 +1,203 @@
+require 'puppet/parser/functions'
+require 'puppet/parser/files'
+require 'puppet/resource/type_collection'
+require 'puppet/resource/type_collection_helper'
+require 'puppet/resource/type'
+require 'monitor'
+
+# Supporting logic for the parser.
+# This supporting logic has slightly different responsibilities compared to the original Puppet::Parser::Parser.
+# It is only concerned with parsing.
+#
+class Puppet::Pops::Parser::Parser
+ # Note that the name of the contained class and the file name (currently parser_support.rb)
+ # needs to be different as the class is generated by Racc, and this file (parser_support.rb) is included as a mix in
+ #
+
+ # Simplify access to the Model factory
+ # Note that the parser/parser support does not have direct knowledge about the Model.
+ # All model construction/manipulation is made by the Factory.
+ #
+ Factory = Puppet::Pops::Model::Factory
+ Model = Puppet::Pops::Model
+
+ include Puppet::Resource::TypeCollectionHelper
+
+ attr_accessor :lexer
+
+ # Returns the token text of the given lexer token, or nil, if token is nil
+ def token_text t
+ return t if t.nil?
+ t = t.current if t.respond_to?(:current)
+ return t.value if t.is_a? Model::QualifiedName
+
+ # else it is a lexer token
+ t[:value]
+ end
+
+ # Produces the fully qualified name, with the full (current) namespace for a given name.
+ #
+ # This is needed because class bodies are lazily evaluated and an inner class' container(s) may not
+ # have been evaluated before some external reference is made to the inner class; its must therefore know its complete name
+ # before evaluation-time.
+ #
+ def classname(name)
+ [@lexer.namespace, name].join("::").sub(/^::/, '')
+ end
+
+ # Reinitializes variables (i.e. creates a new lexer instance
+ #
+ def clear
+ initvars
+ end
+
+ # Raises a Parse error.
+ def error(message, options = {})
+ except = Puppet::ParseError.new(message)
+ except.line = options[:line] || @lexer.line
+ except.file = options[:file] || @lexer.file
+ except.pos = options[:pos] || @lexer.pos
+
+ raise except
+ end
+
+ # Parses a file expected to contain pp DSL logic.
+ def parse_file(file)
+ unless FileTest.exist?(file)
+ unless file =~ /\.pp$/
+ file = file + ".pp"
+ end
+ end
+ @lexer.file = file
+ _parse()
+ end
+
+ def initialize()
+ # Since the parser is not responsible for importing (removed), and does not perform linking,
+ # and there is no syntax that requires knowing if something referenced exists, it is safe
+ # to assume that no environment is needed when parsing. (All that comes later).
+ #
+ initvars
+ end
+
+ # Initializes the parser support by creating a new instance of {Puppet::Pops::Parser::Lexer}
+ # @return [void]
+ #
+ def initvars
+ @lexer = Puppet::Pops::Parser::Lexer.new
+ end
+
+ # This is a callback from the generated grammar (when an error occurs while parsing)
+ # TODO Picks up origin information from the lexer, probably needs this from the caller instead
+ # (for code strings, and when start line is not line 1 in a code string (or file), etc.)
+ #
+ def on_error(token,value,stack)
+ if token == 0 # denotes end of file
+ value = 'end of file'
+ else
+ value = "'#{value[:value]}'"
+ end
+ error = "Syntax error at #{value}"
+
+ # The 'expected' is only of value at end of input, otherwise any parse error involving a
+ # start of a pair will be reported as expecting the close of the pair - e.g. "$x.each |$x {", would
+ # report that "seeing the '{', the '}' is expected. That would be wrong.
+ # Real "expected" tokens are very difficult to compute (would require parsing of racc output data). Output of the stack
+ # could help, but can require extensive backtracking and produce many options.
+ #
+ if token == 0 && brace = @lexer.expected
+ error += "; expected '#{brace}'"
+ end
+
+ except = Puppet::ParseError.new(error)
+ except.line = @lexer.line
+ except.file = @lexer.file if @lexer.file
+ except.pos = @lexer.pos
+
+ raise except
+ end
+
+ # Parses a String of pp DSL code.
+ # @todo make it possible to pass a given origin
+ #
+ def parse_string(code)
+ @lexer.string = code
+ _parse()
+ end
+
+ # Mark the factory wrapped model object with location information
+ # @todo the lexer produces :line for token, but no offset or length
+ # @return [Puppet::Pops::Model::Factory] the given factory
+ # @api private
+ #
+ def loc(factory, start_token, end_token = nil)
+ factory.record_position(sourcepos(start_token), sourcepos(end_token))
+ end
+
+ # Associate documentation with the factory wrapped model object.
+ # @return [Puppet::Pops::Model::Factory] the given factory
+ # @api private
+ def doc factory, doc_string
+ factory.doc = doc_string
+ end
+
+ def sourcepos(o)
+ if !o
+ Puppet::Pops::Adapters::SourcePosAdapter.new
+ elsif o.is_a? Puppet::Pops::Model::Factory
+ # It is a built model element with loc set returns start at pos 0
+ o.loc
+ else
+ loc = Puppet::Pops::Adapters::SourcePosAdapter.new
+ # It must be a token
+ loc.line = o[:line]
+ loc.pos = o[:pos]
+ loc.offset = o[:offset]
+ loc.length = o[:length]
+ loc
+ end
+ end
+
+ def aryfy(o)
+ o = [o] unless o.is_a?(Array)
+ o
+ end
+
+ # Transforms an array of expressions containing literal name expressions to calls if followed by an
+ # expression, or expression list
+ #
+ def transform_calls(expressions)
+ Factory.transform_calls(expressions)
+ end
+
+ # Performs the parsing and returns the resulting model.
+ # The lexer holds state, and this is setup with {#parse_string}, or {#parse_file}.
+ #
+ # TODO: Drop support for parsing a ruby file this way (should be done where it is decided
+ # which file to load/run (i.e. loaders), and initial file to run
+ # TODO: deal with options containing origin (i.e. parsing a string from externally known location).
+ # TODO: should return the model, not a Hostclass
+ #
+ # @api private
+ #
+ def _parse()
+ begin
+ @yydebug = false
+ main = yyparse(@lexer,:scan)
+ # #Commented out now because this hides problems in the racc grammar while developing
+ # # TODO include this when test coverage is good enough.
+ # rescue Puppet::ParseError => except
+ # except.line ||= @lexer.line
+ # except.file ||= @lexer.file
+ # except.pos ||= @lexer.pos
+ # raise except
+ # rescue => except
+ # raise Puppet::ParseError.new(except.message, @lexer.file, @lexer.line, @lexer.pos, except)
+ end
+ main.record_origin(@lexer.file) if main
+ return main
+ ensure
+ @lexer.clear
+ end
+
+end
diff --git a/lib/puppet/pops/patterns.rb b/lib/puppet/pops/patterns.rb
new file mode 100644
index 0000000..d18384f
--- /dev/null
+++ b/lib/puppet/pops/patterns.rb
@@ -0,0 +1,35 @@
+# The Patterns module contains common regular expression patters for the Puppet DSL language
+module Puppet::Pops::Patterns
+
+ # NUMERIC matches hex, octal, decimal, and floating point and captures three parts
+ # 0 = entire matched number, leading and trailing whitespace included
+ # 1 = hexadecimal number
+ # 2 = non hex integer portion, possibly with leading 0 (octal)
+ # 3 = floating point part, starts with ".", decimals and optional exponent
+ #
+ # Thus, a hex number has group 1 value, an octal value has group 2 (if it starts with 0), and no group 3
+ # and a floating point value has group 2 and group 3.
+ #
+ NUMERIC = %r{^\s*(?:(0[xX][0-9A-Fa-f]+)|(0?\d+)((?:\.\d+)?(?:[eE]-?\d+)?))\s*$}
+
+ # ILLEGAL_P3_1_HOSTNAME matches if a hostname contains illegal characters.
+ # This check does not prevent pathological names like 'a....b', '.....', "---". etc.
+ ILLEGAL_HOSTNAME_CHARS = %r{[^-\w.]}
+
+ # NAME matches a name the same way as the lexer.
+ # This name includes hyphen, which may be illegal in variables, and names in general.
+ NAME = %r{((::)?[a-z0-9][-\w]*)(::[a-z0-9][-\w]*)*}
+
+ # CLASSREF_EXT matches a class reference the same way as the lexer - i.e. the external source form
+ # where each part must start with a capital letter A-Z.
+ # This name includes hyphen, which may be illegal in some cases.
+ #
+ CLASSREF_EXT = %r{((::){0,1}[A-Z][-\w]*)+}
+
+ # CLASSREF matches a class reference the way it is represented internall in the
+ # model (i.e. in lower case).
+ # This name includes hyphen, which may be illegal in some cases.
+ #
+ CLASSREF = %r{((::){0,1}[a-z][-\w]*)+}
+
+end
diff --git a/lib/puppet/pops/utils.rb b/lib/puppet/pops/utils.rb
new file mode 100644
index 0000000..104a269
--- /dev/null
+++ b/lib/puppet/pops/utils.rb
@@ -0,0 +1,104 @@
+# Provides utility methods
+module Puppet::Pops::Utils
+ # Can the given o be converted to numeric? (or is numeric already)
+ # Accepts a leading '::'
+ # Returns a boolean if the value is numeric
+ # If testing if value can be converted it is more efficient to call {#to_n} or {#to_n_with_radix} directly
+ # and check if value is nil.
+ def self.is_numeric?(o)
+ case o
+ when Numeric, Integer, Fixnum, Float
+ !!o
+ else
+ !!Puppet::Pops::Patterns::NUMERIC.match(relativize_name(o.to_s))
+ end
+ end
+
+ # To LiteralNumber with radix, or nil if not a number.
+ # If the value is already a number it is returned verbatim with a radix of 10.
+ # @param o [String, Number] a string containing a number in octal, hex, integer (decimal) or floating point form
+ # @return [Array<Number, Integer>, nil] array with converted number and radix, or nil if not possible to convert
+ # @api public
+ #
+ def self.to_n_with_radix o
+ begin
+ case o
+ when String
+ match = Puppet::Pops::Patterns::NUMERIC.match(relativize_name(o))
+ if !match
+ nil
+ elsif match[3].to_s.length > 0
+ # Use default radix (default is decimal == 10) for floats
+ [Float(match[0]), 10]
+ else
+ # Set radix (default is decimal == 10)
+ radix = 10
+ if match[1].to_s.length > 0
+ radix = 16
+ elsif match[2].to_s.length > 0 && match[2][0] == '0'
+ radix = 8
+ end
+ [Integer(match[0], radix), radix]
+ end
+ when Numeric, Fixnum, Integer, Float
+ # Impossible to calculate radix, assume decimal
+ [o, 10]
+ else
+ nil
+ end
+ rescue ArgumentError
+ nil
+ end
+ end
+
+ # To Numeric (or already numeric)
+ # Returns nil if value is not numeric, else an Integer or Float
+ # A leading '::' is accepted (and ignored)
+ #
+ def self.to_n o
+ begin
+ case o
+ when String
+ match = Puppet::Pops::Patterns::NUMERIC.match(relativize_name(o))
+ if !match
+ nil
+ elsif match[3].to_s.length > 0
+ Float(match[0])
+ else
+ Integer(match[0])
+ end
+ when Numeric, Fixnum, Integer, Float
+ o
+ else
+ nil
+ end
+ rescue ArgumentError
+ nil
+ end
+ end
+
+ # is the name absolute (i.e. starts with ::)
+ def self.is_absolute? name
+ name.start_with? "::"
+ end
+
+ def self.name_to_segments name
+ name.split("::")
+ end
+
+ def self.relativize_name name
+ is_absolute?(name) ? name[2..-1] : name
+ end
+
+ # Finds an adapter for o or for one of its containers, or nil, if none of the containers
+ # was adapted with the given adapter.
+ # This method can only be used with objects that respond to `:eContainer`.
+ # with true, and Adaptable#adapters.
+ #
+ def self.find_adapter(o, adapter)
+ return nil unless o
+ a = adapter.get(o)
+ return a if a
+ return find_adapter(o.eContainer, adapter)
+ end
+end
diff --git a/lib/puppet/pops/validation.rb b/lib/puppet/pops/validation.rb
new file mode 100644
index 0000000..5e9aa2b
--- /dev/null
+++ b/lib/puppet/pops/validation.rb
@@ -0,0 +1,297 @@
+# A module with base functionality for validation of a model.
+#
+# * SeverityProducer - produces a severity (:error, :warning, :ignore) for a given Issue
+# * DiagnosticProducer - produces a Diagnostic which binds an Issue to an occurrence of that issue
+# * Acceptor - the receiver/sink/collector of computed diagnostics
+# * DiagnosticFormatter - produces human readable output for a Diagnostic
+#
+module Puppet::Pops::Validation
+ # Decides on the severity of a given issue.
+ # The produced severity is one of `:error`, `:warning`, or `:ignore`.
+ # By default, a severity of `:error` is produced for all issues. To configure the severity
+ # of an issue call `#severity=(issue, level)`.
+ #
+ class SeverityProducer
+ # Creates a new instance where all issues are diagnosed as :error unless overridden.
+ #
+ def initialize
+ # If diagnose is not set, the default is returned by the block
+ @severities = Hash.new :error
+ end
+
+ # Returns the severity of the given issue.
+ # @returns [Symbol] severity level :error, :warning, or :ignore
+ #
+ def severity issue
+ assert_issue(issue)
+ @severities[issue]
+ end
+
+ def [] issue
+ severity issue
+ end
+
+ # Override a default severity with the given severity level.
+ #
+ # @param issue [Puppet::Pops::Issues::Issue] the issue for which to set severity
+ # @param level [Symbol] the severity level (:error, :warning, or :ignore).
+ #
+ def []= issue, level
+ assert_issue(issue)
+ assert_severity(level)
+ raise Puppet::DevError.new("Attempt to demote the hard issue '#{issue.issue_code}' to #{level}") unless issue.demotable? || level == :error
+ @severities[issue] = level
+ end
+
+ # Returns true if the issue should be reported or not.
+ # @returns [Boolean] this implementation returns true for errors and warnings
+ #
+ def should_report? issue
+ diagnose = self[issue]
+ diagnose == :error || diagnose == :warning || diagnose == :deprecation
+ end
+
+ def assert_issue issue
+ raise Puppet::DevError.new("Attempt to get validation severity for something that is not an Issue. (Got #{issue.class})") unless issue.is_a? Puppet::Pops::Issues::Issue
+ end
+
+ def assert_severity level
+ raise Puppet::DevError.new("Illegal severity level: #{option}") unless [:ignore, :warning, :error, :deprecation].include? level
+ end
+ end
+
+ # A producer of diagnostics.
+ # An producer of diagnostics is given each issue occurrence as they are found by a diagnostician/validator. It then produces
+ # a Diagnostic, which it passes on to a configured Acceptor.
+ #
+ # This class exists to aid a diagnostician/validator which will typically first check if a particular issue
+ # will be accepted at all (before checking for an occurrence of the issue; i.e. to perform check avoidance for expensive checks).
+ # A validator passes an instance of Issue, the semantic object (the "culprit"), a hash with arguments, and an optional
+ # exception. The semantic object is used to determine the location of the occurrence of the issue (file/line), and it
+ # sets keys in the given argument hash that may be used in the formatting of the issue message.
+ #
+ class DiagnosticProducer
+
+ # A producer of severity for a given issue
+ # @return [SeverityProducer]
+ #
+ attr_reader :severity_producer
+
+ # A producer of labels for objects involved in the issue
+ # @return [LabelProvider]
+ #
+ attr_reader :label_provider
+ # Initializes this producer.
+ #
+ # @param acceptor [Acceptor] a sink/collector of diagnostic results
+ # @param severity_producer [SeverityProducer] the severity producer to use to determine severity of a given issue
+ # @param label_provider [LabelProvider] a provider of model element type to human readable label
+ #
+ def initialize(acceptor, severity_producer, label_provider)
+ @acceptor = acceptor
+ @severity_producer = severity_producer
+ @label_provider = label_provider
+ end
+
+ def accept(issue, semantic, arguments={}, except=nil)
+ return unless will_accept? issue
+
+ # Set label provider unless caller provided a special label provider
+ arguments[:label] ||= @label_provider
+ arguments[:semantic] ||= semantic
+
+ # A detail message is always provided, but is blank by default.
+ arguments[:detail] ||= ''
+
+ origin_adapter = Puppet::Pops::Utils.find_adapter(semantic, Puppet::Pops::Adapters::OriginAdapter)
+ file = origin_adapter ? origin_adapter.origin : nil
+ source_pos = Puppet::Pops::Utils.find_adapter(semantic, Puppet::Pops::Adapters::SourcePosAdapter)
+ severity = @severity_producer.severity(issue)
+ @acceptor.accept(Diagnostic.new(severity, issue, file, source_pos, arguments))
+ end
+
+ def will_accept? issue
+ @severity_producer.should_report? issue
+ end
+ end
+
+ class Diagnostic
+ attr_reader :severity
+ attr_reader :issue
+ attr_reader :arguments
+ attr_reader :exception
+ attr_reader :file
+ attr_reader :source_pos
+ def initialize severity, issue, file, source_pos, arguments={}, exception=nil
+ @severity = severity
+ @issue = issue
+ @file = file
+ @source_pos = source_pos
+ @arguments = arguments
+ @exception = exception
+ end
+ end
+
+ # Formats a diagnostic for output.
+ # Produces a diagnostic output typical for a compiler (suitable for interpretation by tools)
+ # The format is:
+ # `file:line:pos: Message`, where pos, line and file are included if available.
+ #
+ class DiagnosticFormatter
+ def format diagnostic
+ "#{loc(diagnostic)} #{format_severity(diagnostic)}#{format_message(diagnostic)}"
+ end
+
+ def format_message diagnostic
+ diagnostic.issue.format(diagnostic.arguments)
+ end
+
+ # This produces "Deprecation notice: " prefix if the diagnostic has :deprecation severity, otherwise "".
+ # The idea is that all other diagnostics are emitted with the methods Puppet.err (or an exception), and
+ # Puppet.warning.
+ # @note Note that it is not a good idea to use Puppet.deprecation_warning as it is for internal deprecation.
+ #
+ def format_severity diagnostic
+ diagnostic.severity == :deprecation ? "Deprecation notice: " : ""
+ end
+
+ def format_location diagnostic
+ file = diagnostic.file
+ line = diagnostic.source_pos.line
+ pos = diagnostic.source_pos.pos
+ if file && line && pos
+ "#{file}:#{line}:#{pos}:"
+ elsif file && line
+ "#{file}:#{line}:"
+ elsif file
+ "#{file}:"
+ else
+ ""
+ end
+ end
+ end
+
+ # Produces a diagnostic output in the "puppet style", where the location is appended with an "at ..." if the
+ # location is known.
+ #
+ class DiagnosticFormatterPuppetStyle < DiagnosticFormatter
+ def format diagnostic
+ if (location = format_location diagnostic) != ""
+ "#{format_severity(diagnostic)}#{format_message(diagnostic)}#{location}"
+ else
+ format_message(diagnostic)
+ end
+ end
+
+ # The somewhat (machine) unusable format in current use by puppet.
+ # have to be used here for backwards compatibility.
+ def format_location diagnostic
+ file = diagnostic.file
+ line = diagnostic.source_pos.line
+ pos = diagnostic.source_pos.pos
+ if file && line && pos
+ " at #{file}:#{line}:#{pos}"
+ elsif file and line
+ " at #{file}:#{line}"
+ elsif line && pos
+ " at line #{line}:#{pos}"
+ elsif line
+ " at line #{line}"
+ elsif file
+ " in #{file}"
+ else
+ ""
+ end
+ end
+ end
+
+ # An acceptor of diagnostics.
+ # An acceptor of diagnostics is given each issue as they are found by a diagnostician/validator. An
+ # acceptor can collect all found issues, or decide to collect a few and then report, or give up as the first issue
+ # if found.
+ # This default implementation collects all diagnostics in the order they are produced, and can then
+ # answer questions about what was diagnosed.
+ #
+ class Acceptor
+
+ # All diagnstic in the order they were issued
+ attr_reader :diagnostics
+
+ # The number of :warning severity issues + number of :deprecation severity issues
+ attr_reader :warning_count
+
+ # The number of :error severity issues
+ attr_reader :error_count
+ # Initializes this diagnostics acceptor.
+ # By default, the acceptor is configured with a default severity producer.
+ # @param severity_producer [SeverityProducer] the severity producer to use to determine severity of an issue
+ #
+ # TODO add semantic_label_provider
+ #
+ def initialize()
+ @diagnostics = []
+ @error_count = 0
+ @warning_count = 0
+ end
+
+ # Returns true when errors have been diagnosed.
+ def errors?
+ @error_count > 0
+ end
+
+ # Returns true when warnings have been diagnosed.
+ def warnings?
+ @warning_count > 0
+ end
+
+ # Returns true when errors and/or warnings have been diagnosed.
+ def errors_or_warnings?
+ errors? || warnings?
+ end
+
+ # Returns the diagnosed errors in the order thwy were reported.
+ def errors
+ @diagnostics.select {|d| d.severity == :error }
+ end
+
+ # Returns the diagnosed warnings in the order thwy were reported.
+ # (This includes :warning and :deprecation severity)
+ def warnings
+ @diagnostics.select {|d| d.severity == :warning || d.severity == :deprecation }
+ end
+
+ def errors_and_warnings
+ @diagnostics.select {|d| d.severity != :ignored}
+ end
+
+ # Returns the ignored diagnostics in the order thwy were reported (if reported at all)
+ def ignored
+ @diagnostics.select {|d| d.severity == :ignore }
+ end
+
+ # Add a diagnostic to the set of diagnostics
+ def accept(diagnostic)
+ self.send(diagnostic.severity, diagnostic)
+ end
+
+ private
+
+ def ignore diagnostic
+ @diagnostics << diagnostic
+ end
+
+ def error diagnostic
+ @diagnostics << diagnostic
+ @error_count += 1
+ end
+
+ def warning diagnostic
+ @diagnostics << diagnostic
+ @warning_count += 1
+ end
+
+ def deprecation diagnostic
+ warning diagnostic
+ end
+ end
+end
diff --git a/lib/puppet/pops/validation/checker3_1.rb b/lib/puppet/pops/validation/checker3_1.rb
new file mode 100644
index 0000000..69df726
--- /dev/null
+++ b/lib/puppet/pops/validation/checker3_1.rb
@@ -0,0 +1,551 @@
+# A Validator validates a model.
+#
+# Validation is performed on each model element in isolation. Each method should validate the model element's state
+# but not validate its referenced/contained elements except to check their validity in their respective role.
+# The intent is to drive the validation with a tree iterator that visits all elements in a model.
+#
+#
+# TODO: Add validation of multiplicities - this is a general validation that can be checked for all
+# Model objects via their metamodel. (I.e an extra call to multiplicity check in polymorph check).
+# This is however mostly valuable when validating model to model transformations, and is therefore T.B.D
+#
+class Puppet::Pops::Validation::Checker3_1
+ Issues = Puppet::Pops::Issues
+ Model = Puppet::Pops::Model
+
+ attr_reader :acceptor
+ # Initializes the validator with a diagnostics producer. This object must respond to
+ # `:will_accept?` and `:accept`.
+ #
+ def initialize(diagnostics_producer)
+ @@check_visitor ||= Puppet::Pops::Visitor.new(nil, "check", 0, 0)
+ @@rvalue_visitor ||= Puppet::Pops::Visitor.new(nil, "rvalue", 0, 0)
+ @@hostname_visitor ||= Puppet::Pops::Visitor.new(nil, "hostname", 1, 1)
+ @@assignment_visitor ||= Puppet::Pops::Visitor.new(nil, "assign", 0, 1)
+ @@query_visitor ||= Puppet::Pops::Visitor.new(nil, "query", 0, 0)
+ @@top_visitor ||= Puppet::Pops::Visitor.new(nil, "top", 1, 1)
+ @@relation_visitor ||= Puppet::Pops::Visitor.new(nil, "relation", 1, 1)
+
+ @acceptor = diagnostics_producer
+ end
+
+ # Validates the entire model by visiting each model element and calling `check`.
+ # The result is collected (or acted on immediately) by the configured diagnostic provider/acceptor
+ # given when creating this Checker.
+ #
+ def validate(model)
+ # tree iterate the model, and call check for each element
+ check(model)
+ model.eAllContents.each {|m| check(m) }
+ end
+
+ # Performs regular validity check
+ def check(o)
+ @@check_visitor.visit_this(self, o)
+ end
+
+ # Performs check if this is a vaid hostname expression
+ def hostname(o, semantic)
+ @@hostname_visitor.visit_this(self, o, semantic)
+ end
+
+ # Performs check if this is valid as a query
+ def query(o)
+ @@query_visitor.visit_this(self, o)
+ end
+
+ # Performs check if this is valid as a relationship side
+ def relation(o, container)
+ @@relation_visitor.visit_this(self, o, container)
+ end
+
+ # Performs check if this is valid as a rvalue
+ def rvalue(o)
+ @@rvalue_visitor.visit_this(self, o)
+ end
+
+ # Performs check if this is valid as a container of a definition (class, define, node)
+ def top(o, definition)
+ @@top_visitor.visit_this(self, o, definition)
+ end
+
+ # Checks the LHS of an assignment (is it assignable?).
+ # If args[0] is true, assignment via index is checked.
+ #
+ def assign(o, *args)
+ @@assignment_visitor.visit_this(self, o, *args)
+ end
+
+ #---ASSIGNMENT CHECKS
+
+ def assign_VariableExpression(o, *args)
+ varname_string = varname_to_s(o.expr)
+ if varname_string =~ /^[0-9]+$/
+ acceptor.accept(Issues::ILLEGAL_NUMERIC_ASSIGNMENT, o, :varname => varname_string)
+ end
+ # Can not assign to something in another namespace (i.e. a '::' in the name is not legal)
+ if acceptor.will_accept? Issues::CROSS_SCOPE_ASSIGNMENT
+ if varname_string =~ /::/
+ acceptor.accept(Issues::CROSS_SCOPE_ASSIGNMENT, o, :name => varname_string)
+ end
+ end
+ # TODO: Could scan for reassignment of the same variable if done earlier in the same container
+ # Or if assigning to a parameter (more work).
+ # TODO: Investigate if there are invalid cases for += assignment
+ end
+
+ def assign_AccessExpression(o, *args)
+ # Are indexed assignments allowed at all ? $x[x] = '...'
+ if acceptor.will_accept? Issues::ILLEGAL_INDEXED_ASSIGNMENT
+ acceptor.accept(Issues::ILLEGAL_INDEXED_ASSIGNMENT, o)
+ else
+ # Then the left expression must be assignable-via-index
+ assign(o.left_expr, true)
+ end
+ end
+
+ def assign_Object(o, *args)
+ # Can not assign to anything else (differentiate if this is via index or not)
+ # i.e. 10 = 'hello' vs. 10['x'] = 'hello' (the root is reported as being in error in both cases)
+ #
+ acceptor.accept(args[0] ? Issues::ILLEGAL_ASSIGNMENT_VIA_INDEX : Issues::ILLEGAL_ASSIGNMENT, o)
+ end
+
+ #---CHECKS
+
+ def check_Object(o)
+ end
+
+ def check_Factory(o)
+ check(o.current)
+ end
+
+ def check_AccessExpression(o)
+ # Check multiplicity of keys
+ case o.left_expr
+ when Model::QualifiedName
+ # allows many keys, but the name should really be a QualifiedReference
+ acceptor.accept(Issues::DEPRECATED_NAME_AS_TYPE, o, :name => o.value)
+ when Model::QualifiedReference
+ # ok, allows many - this is a resource reference
+
+ else
+ # i.e. for any other expression that may produce an array or hash
+ if o.keys.size > 1
+ acceptor.accept(Issues::UNSUPPORTED_RANGE, o, :count => o.keys.size)
+ end
+ if o.keys.size < 1
+ acceptor.accept(Issues::MISSING_INDEX, o)
+ end
+ end
+ end
+
+ def check_AssignmentExpression(o)
+ assign(o.left_expr)
+ rvalue(o.right_expr)
+ end
+
+ # Checks that operation with :+> is contained in a ResourceOverride or Collector.
+ #
+ # Parent of an AttributeOperation can be one of:
+ # * CollectExpression
+ # * ResourceOverride
+ # * ResourceBody (ILLEGAL this is a regular resource expression)
+ # * ResourceDefaults (ILLEGAL)
+ #
+ def check_AttributeOperation(o)
+ if o.operator == :'+>'
+ # Append operator use is constrained
+ parent = o.eContainer
+ unless parent.is_a?(Model::CollectExpression) || parent.is_a?(Model::ResourceOverrideExpression)
+ acceptor.accept(Issues::ILLEGAL_ATTRIBUTE_APPEND, o, {:name=>o.attribute_name, :parent=>parent})
+ end
+ end
+ rvalue(o.value_expr)
+ end
+
+ def check_BinaryExpression(o)
+ rvalue(o.left_expr)
+ rvalue(o.right_expr)
+ end
+
+ def check_CallNamedFunctionExpression(o)
+ unless o.functor_expr.is_a? Model::QualifiedName
+ acceptor.accept(Issues::ILLEGAL_EXPRESSION, o.functor_expr, :feature => 'function name', :container => o)
+ end
+ end
+
+ def check_MethodCallExpression(o)
+ unless o.functor_expr.is_a? Model::QualifiedName
+ acceptor.accept(Issues::ILLEGAL_EXPRESSION, o.functor_expr, :feature => 'function name', :container => o)
+ end
+ end
+
+ def check_CaseExpression(o)
+ # There should only be one LiteralDefault case option value
+ # TODO: Implement this check
+ end
+
+ def check_CollectExpression(o)
+ unless o.type_expr.is_a? Model::QualifiedReference
+ acceptor.accept(Issues::ILLEGAL_EXPRESSION, o.type_expr, :feature=> 'type name', :container => o)
+ end
+
+ # If a collect expression tries to collect exported resources and storeconfigs is not on
+ # then it will not work... This was checked in the parser previously. This is a runtime checking
+ # thing as opposed to a language thing.
+ if acceptor.will_accept?(Issues::RT_NO_STORECONFIGS) && o.query.is_a?(Model::ExportedQuery)
+ acceptor.accept(Issues::RT_NO_STORECONFIGS, o)
+ end
+ end
+
+ # Only used for function names, grammar should not be able to produce something faulty, but
+ # check anyway if model is created programatically (it will fail in transformation to AST for sure).
+ def check_NamedAccessExpression(o)
+ name = o.right_expr
+ unless name.is_a? Model::QualifiedName
+ acceptor.accept(Issues::ILLEGAL_EXPRESSION, name, :feature=> 'function name', :container => o.eContainer)
+ end
+ end
+
+ # for 'class' and 'define'
+ def check_NamedDefinition(o)
+ top(o.eContainer, o)
+ if (acceptor.will_accept? Issues::NAME_WITH_HYPHEN) && o.name.include?('-')
+ acceptor.accept(Issues::NAME_WITH_HYPHEN, o, {:name => o.name})
+ end
+ end
+
+ def check_ImportExpression(o)
+ o.files.each do |f|
+ unless f.is_a? Model::LiteralString
+ acceptor.accept(Issues::ILLEGAL_EXPRESSION, f, :feature => 'file name', :container => o)
+ end
+ end
+ end
+
+ def check_InstanceReference(o)
+ # TODO: Original warning is :
+ # Puppet.warning addcontext("Deprecation notice: Resource references should now be capitalized")
+ # This model element is not used in the egrammar.
+ # Either implement checks or deprecate the use of InstanceReference (the same is acheived by
+ # transformation of AccessExpression when used where an Instance/Resource reference is allowed.
+ #
+ end
+
+ # Restrictions on hash key are because of the strange key comparisons/and merge rules in the AST evaluation
+ # (Even the allowed ones are handled in a strange way).
+ #
+ def transform_KeyedEntry(o)
+ case o.key
+ when Model::QualifiedName
+ when Model::LiteralString
+ when Model::LiteralNumber
+ when Model::ConcatenatedString
+ else
+ acceptor.accept(Issues::ILLEGAL_EXPRESSION, o.key, :feature => 'hash key', :container => o.eContainer)
+ end
+ end
+
+ # A Lambda is a Definition, but it may appear in other scopes that top scope (Which check_Definition asserts).
+ #
+ def check_LambdaExpression(o)
+ end
+
+ def check_NodeDefinition(o)
+ # Check that hostnames are valid hostnames (or regular expressons)
+ hostname(o.host_matches, o)
+ top(o.eContainer, o)
+ end
+
+ # Asserts that value is a valid QualifiedName. No additional checking is made, objects that use
+ # a QualifiedName as a name should check the validity - this since a QualifiedName is used as a BARE WORD
+ # and then additional chars may be valid (like a hyphen).
+ #
+ def check_QualifiedName(o)
+ # Is this a valid qualified name?
+ if o.value !~ Puppet::Pops::Patterns::NAME
+ acceptor.accept(Issues::ILLEGAL_NAME, o, {:name=>o.value})
+ end
+ end
+
+ # Checks that the value is a valid UpperCaseWord (a CLASSREF), and optionally if it contains a hypen.
+ # DOH: QualifiedReferences are created with LOWER CASE NAMES at parse time
+ def check_QualifiedReference(o)
+ # Is this a valid qualified name?
+ if o.value !~ Puppet::Pops::Patterns::CLASSREF
+ acceptor.accept(Issues::ILLEGAL_CLASSREF, o, {:name=>o.value})
+ elsif (acceptor.will_accept? Issues::NAME_WITH_HYPHEN) && o.value.include?('-')
+ acceptor.accept(Issues::NAME_WITH_HYPHEN, o, {:name => o.value})
+ end
+ end
+
+ def check_QueryExpression(o)
+ rvalue(o.expr) if o.expr # is optional
+ end
+
+ def relation_Object(o, rel_expr)
+ acceptor.accept(Issues::ILLEGAL_EXPRESSION, o, {:feature => o.eContainingFeature, :container => rel_expr})
+ end
+
+ def relation_AccessExpression(o, rel_expr); end
+
+ def relation_CollectExpression(o, rel_expr); end
+
+ def relation_VariableExpression(o, rel_expr); end
+
+ def relation_LiteralString(o, rel_expr); end
+
+ def relation_ConcatenatedStringExpression(o, rel_expr); end
+
+ def relation_SelectorExpression(o, rel_expr); end
+
+ def relation_CaseExpression(o, rel_expr); end
+
+ def relation_ResourceExpression(o, rel_expr); end
+
+ def relation_RelationshipExpression(o, rel_expr); end
+
+ def check_Parameter(o)
+ if o.name =~ /^[0-9]+$/
+ acceptor.accept(Issues::ILLEGAL_NUMERIC_PARAMETER, o, :name => o.name)
+ end
+ end
+
+ #relationship_side: resource
+ # | resourceref
+ # | collection
+ # | variable
+ # | quotedtext
+ # | selector
+ # | casestatement
+ # | hasharrayaccesses
+
+ def check_RelationshipExpression(o)
+ relation(o.left_expr, o)
+ relation(o.right_expr, o)
+ end
+
+ def check_ResourceExpression(o)
+ # A resource expression must have a lower case NAME as its type e.g. 'file { ... }'
+ unless o.type_name.is_a? Model::QualifiedName
+ acceptor.accept(Issues::ILLEGAL_EXPRESSION, o.type_name, :feature => 'resource type', :container => o)
+ end
+
+ # This is a runtime check - the model is valid, but will have runtime issues when evaluated
+ # and storeconfigs is not set.
+ if acceptor.will_accept?(Issues::RT_NO_STORECONFIGS) && o.exported
+ acceptor.accept(Issues::RT_NO_STORECONFIGS_EXPORT, o)
+ end
+ end
+
+ def check_ResourceDefaultsExpression(o)
+ if o.form && o.form != :regular
+ acceptor.accept(Issues::NOT_VIRTUALIZEABLE, o)
+ end
+ end
+
+ # 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 select_SelectorExpression(o)
+ case o.left_expr
+ when Model::CallNamedFunctionExpression
+ when Model::AccessExpression
+ when Model::VariableExpression
+ when Model::ConcatenatedString
+ else
+ acceptor.accept(Issues::ILLEGAL_EXPRESSION, o.left_expr, :feature => 'left operand', :container => o)
+ end
+ end
+
+ def check_UnaryExpression(o)
+ rvalue(o.expr)
+ end
+
+ def check_UnlessExpression(o)
+ # TODO: Unless may not have an elsif
+ # TODO: 3.x unless may not have an else
+ end
+
+ def check_VariableExpression(o)
+ # The expression must be a qualified name
+ if !o.expr.is_a? Model::QualifiedName
+ acceptor.accept(Issues::ILLEGAL_EXPRESSION, o, :feature => 'name', :container => o)
+ else
+ # Note, that if it later becomes illegal with hyphen in any name, this special check
+ # can be skipped in favor of the check in QualifiedName, which is now not done if contained in
+ # a VariableExpression
+ name = o.expr.value
+ if (acceptor.will_accept? Issues::VAR_WITH_HYPHEN) && name.include?('-')
+ acceptor.accept(Issues::VAR_WITH_HYPHEN, o, {:name => name})
+ end
+ end
+ end
+
+ #--- HOSTNAME CHECKS
+
+ # Transforms Array of host matching expressions into a (Ruby) array of AST::HostName
+ def hostname_Array(o, semantic)
+ o.each {|x| hostname x, semantic }
+ end
+
+ def hostname_String(o, semantic)
+ # The 3.x checker only checks for illegal characters - if matching /[^-\w.]/ the name is invalid,
+ # but this allows pathological names like "a..b......c", "----"
+ # TODO: Investigate if more illegal hostnames should be flagged.
+ #
+ if o =~ Puppet::Pops::Patterns::ILLEGAL_HOSTNAME_CHARS
+ acceptor.accept(Issues::ILLEGAL_HOSTNAME_CHARS, semantic, :hostname => o)
+ end
+ end
+
+ def hostname_LiteralValue(o, semantic)
+ hostname_String(o.value.to_s, o)
+ end
+
+ def hostname_ConcatenatedString(o, semantic)
+ # Puppet 3.1. only accepts a concatenated string without interpolated expressions
+ if the_expr = o.segments.index {|s| s.is_a?(Model::TextExpression) }
+ acceptor.accept(Issues::ILLEGAL_HOSTNAME_INTERPOLATION, o.segments[the_expr].expr)
+ elsif o.segments.size() != 1
+ # corner case, bad model, concatenation of several plain strings
+ acceptor.accept(Issues::ILLEGAL_HOSTNAME_INTERPOLATION, o)
+ else
+ # corner case, may be ok, but lexer may have replaced with plain string, this is
+ # here if it does not
+ hostname_String(o.segments[0], o.segments[0])
+ end
+ end
+
+ def hostname_QualifiedName(o, semantic)
+ hostname_String(o.value.to_s, o)
+ end
+
+ def hostname_QualifiedReference(o, semantic)
+ hostname_String(o.value.to_s, o)
+ end
+
+ def hostname_LiteralNumber(o, semantic)
+ # always ok
+ end
+
+ def hostname_LiteralDefault(o, semantic)
+ # always ok
+ end
+
+ def hostname_LiteralRegularExpression(o, semantic)
+ # always ok
+ end
+
+ def hostname_Object(o, semantic)
+ acceptor.accept(Issues::ILLEGAL_EXPRESSION, o, {:feature=>'hostname', :container=>semantic})
+ end
+
+ #---QUERY CHECKS
+
+ # Anything not explicitly allowed is flagged as error.
+ def query_Object(o)
+ acceptor.accept(Issues::ILLEGAL_QUERY_EXPRESSION, o)
+ end
+
+ # Puppet AST only allows == and !=
+ #
+ def query_ComparisonExpression(o)
+ acceptor.accept(Issues::ILLEGAL_QUERY_EXPRESSION, o) unless [:'==', :'!='].include? o.operator
+ end
+
+ # Allows AND, OR, and checks if left/right are allowed in query.
+ def query_BooleanExpression(o)
+ query o.left_expr
+ query o.right_expr
+ end
+
+ def query_ParenthesizedExpression(o)
+ query(o.expr)
+ end
+
+ def query_VariableExpression(o); end
+
+ def query_QualifiedName(o); end
+
+ def query_LiteralNumber(o); end
+
+ def query_LiteralString(o); end
+
+ def query_LiteralBoolean(o); end
+
+ #---RVALUE CHECKS
+
+ # By default, all expressions are reported as being rvalues
+ # Implement specific rvalue checks for those that are not.
+ #
+ def rvalue_Expression(o); end
+
+ def rvalue_ImportExpression(o) ; acceptor.accept(Issues::NOT_RVALUE, o) ; end
+
+ def rvalue_BlockExpression(o) ; acceptor.accept(Issues::NOT_RVALUE, o) ; end
+
+ def rvalue_CaseExpression(o) ; acceptor.accept(Issues::NOT_RVALUE, o) ; end
+
+ def rvalue_IfExpression(o) ; acceptor.accept(Issues::NOT_RVALUE, o) ; end
+
+ def rvalue_UnlessExpression(o) ; acceptor.accept(Issues::NOT_RVALUE, o) ; end
+
+ def rvalue_ResourceExpression(o) ; acceptor.accept(Issues::NOT_RVALUE, o) ; end
+
+ def rvalue_ResourceDefaultsExpression(o); acceptor.accept(Issues::NOT_RVALUE, o) ; end
+
+ def rvalue_ResourceOverrideExpression(o); acceptor.accept(Issues::NOT_RVALUE, o) ; end
+
+ def rvalue_CollectExpression(o) ; acceptor.accept(Issues::NOT_RVALUE, o) ; end
+
+ def rvalue_Definition(o) ; acceptor.accept(Issues::NOT_RVALUE, o) ; end
+
+ def rvalue_NodeDefinition(o) ; acceptor.accept(Issues::NOT_RVALUE, o) ; end
+
+ def rvalue_UnaryExpression(o) ; rvalue o.expr ; end
+
+ #---TOP CHECK
+
+ def top_NilClass(o, definition)
+ # ok, reached the top, no more parents
+ end
+
+ def top_Object(o, definition)
+ # fail, reached a container that is not top level
+ acceptor.accept(Issues::NOT_TOP_LEVEL, definition)
+ end
+
+ def top_BlockExpression(o, definition)
+ # ok, if this is a block representing the body of a class, or is top level
+ top o.eContainer, definition
+ end
+
+ def top_HostClassDefinition(o, definition)
+ # ok, stop scanning parents
+ end
+
+ # A LambdaExpression is a BlockExpression, and this method is needed to prevent the polymorph method for BlockExpression
+ # to accept a lambda.
+ # A lambda can not iteratively create classes, nodes or defines as the lambda does not have a closure.
+ #
+ def top_LambdaExpression(o, definition)
+ # fail, stop scanning parents
+ acceptor.accept(Issues::NOT_TOP_LEVEL, definition)
+ end
+
+ #--- NON POLYMORPH, NON CHECKING CODE
+
+ # Produces string part of something named, or nil if not a QualifiedName or QualifiedReference
+ #
+ def varname_to_s(o)
+ case o
+ when Model::QualifiedName
+ o.value
+ when Model::QualifiedReference
+ o.value
+ else
+ nil
+ end
+ end
+end
diff --git a/lib/puppet/pops/validation/validator_factory_3_1.rb b/lib/puppet/pops/validation/validator_factory_3_1.rb
new file mode 100644
index 0000000..ee5ca3e
--- /dev/null
+++ b/lib/puppet/pops/validation/validator_factory_3_1.rb
@@ -0,0 +1,41 @@
+# Configures validation suitable for 3.1 + iteration
+#
+class Puppet::Pops::Validation::ValidatorFactory_3_1
+ Issues = Puppet::Pops::Issues
+
+ # Produces a validator with the given acceptor as the recipient of produced diagnostics.
+ #
+ def validator acceptor
+ checker(diagnostic_producer(acceptor))
+ end
+
+ # Produces the diagnostics producer to use given an acceptor as the recipient of produced diagnostics
+ #
+ def diagnostic_producer acceptor
+ Puppet::Pops::Validation::DiagnosticProducer.new(acceptor, severity_producer(), label_provider())
+ end
+
+ # Produces the checker to use
+ def checker diagnostic_producer
+ Puppet::Pops::Validation::Checker3_1.new(diagnostic_producer)
+ end
+
+ # Produces the label provider to use
+ def label_provider
+ Puppet::Pops::Model::ModelLabelProvider.new()
+ end
+
+ # Produces the severity producer to use
+ def severity_producer
+ p = Puppet::Pops::Validation::SeverityProducer.new
+
+ # Configure each issue that should **not** be an error
+ #
+ p[Issues::RT_NO_STORECONFIGS_EXPORT] = :warning
+ p[Issues::RT_NO_STORECONFIGS] = :warning
+ p[Issues::NAME_WITH_HYPHEN] = :deprecation
+ p[Issues::DEPRECATED_NAME_AS_TYPE] = :deprecation
+
+ p
+ end
+end
diff --git a/lib/puppet/pops/visitable.rb b/lib/puppet/pops/visitable.rb
new file mode 100644
index 0000000..2f72e37
--- /dev/null
+++ b/lib/puppet/pops/visitable.rb
@@ -0,0 +1,6 @@
+# Visitable is a mix-in module that makes a class visitable by a Visitor
+module Puppet::Pops::Visitable
+ def accept(visitor, *arguments)
+ visitor.visit(self, *arguments)
+ end
+end
diff --git a/lib/puppet/pops/visitor.rb b/lib/puppet/pops/visitor.rb
new file mode 100644
index 0000000..d5526ac
--- /dev/null
+++ b/lib/puppet/pops/visitor.rb
@@ -0,0 +1,50 @@
+# A Visitor performs delegation to a given receiver based on the configuration of the Visitor.
+# A new visitor is created with a given receiver, a method prefix, min, and max argument counts.
+# e.g.
+# vistor = Visitor.new(self, "visit_from", 1, 1)
+# will make the visitor call "self.visit_from_CLASS(x)" where CLASS is resolved to the given
+# objects class, or one of is ancestors, the first class for which there is an implementation of
+# a method will be selected.
+#
+# Raises RuntimeError if there are too few or too many arguments, or if the receiver is not
+# configured to handle a given visiting object.
+#
+class Puppet::Pops::Visitor
+ attr_reader :receiver, :message, :min_args, :max_args, :cache
+ def initialize(receiver, message, min_args=0, max_args=nil)
+ raise ArgumentError.new("min_args must be >= 0") if min_args < 0
+ raise ArgumentError.new("max_args must be >= min_args or nil") if max_args && max_args < min_args
+
+ @receiver = receiver
+ @message = message
+ @min_args = min_args
+ @max_args = max_args
+ @cache = Hash.new
+ end
+
+ # Visit the configured receiver
+ def visit(thing, *args)
+ visit_this(@receiver, thing, *args)
+ end
+
+ # Visit an explicit receiver
+ def visit_this(receiver, thing, *args)
+ raise "Visitor Error: Too few arguments passed. min = #{@min_args}" unless args.length >= @min_args
+ if @max_args
+ raise "Visitor Error: Too many arguments passed. max = #{@max_args}" unless args.length <= @max_args
+ end
+ if method_name = @cache[thing.class]
+ return receiver.send(method_name, thing, *args)
+ else
+ thing.class.ancestors().each do |ancestor|
+ method_name = :"#{@message}_#{ancestor.name.split("::").last}"
+ # DEBUG OUTPUT
+ # puts "Visitor checking: #{receiver.class}.#{method_name}, responds to: #{@receiver.respond_to? method_name}"
+ next unless receiver.respond_to? method_name
+ @cache[thing.class] = method_name
+ return receiver.send(method_name, thing, *args)
+ end
+ end
+ raise "Visitor Error: the configured receiver (#{receiver.class}) can't handle instance of: #{thing.class}"
+ end
+end
diff --git a/lib/puppet/resource/type.rb b/lib/puppet/resource/type.rb
index 3f08a15..e20c9c0 100644
--- a/lib/puppet/resource/type.rb
+++ b/lib/puppet/resource/type.rb
@@ -1,8 +1,9 @@
-require 'puppet/parser/parser'
+require 'puppet/parser'
require 'puppet/util/warnings'
require 'puppet/util/errors'
require 'puppet/util/inline_docs'
require 'puppet/parser/ast/leaf'
+require 'puppet/parser/ast/block_expression'
require 'puppet/dsl'
class Puppet::Resource::Type
@@ -190,14 +191,15 @@ def merge(other)
return
end
- array_class = Puppet::Parser::AST::ASTArray
- self.code = array_class.new(:children => [self.code]) unless self.code.is_a?(array_class)
-
- if other.code.is_a?(array_class)
- code.children += other.code.children
- else
- code.children << other.code
- end
+ self.code = Puppet::Parser::AST::BlockExpression.new(:children => [self.code, other.code])
+# array_class = Puppet::Parser::AST::ASTArray
+# self.code = array_class.new(:children => [self.code]) unless self.code.is_a?(array_class)
+#
+# if other.code.is_a?(array_class)
+# code.children += other.code.children
+# else
+# code.children << other.code
+# end
end
# Make an instance of the resource type, and place it in the catalog
diff --git a/lib/puppet/util/monkey_patches.rb b/lib/puppet/util/monkey_patches.rb
index e9943ee..cbcbc65 100644
--- a/lib/puppet/util/monkey_patches.rb
+++ b/lib/puppet/util/monkey_patches.rb
@@ -126,6 +126,25 @@ def drop(n)
slice(n, length - n) or []
end unless method_defined? :drop
+
+ # Array does not have a to_hash method and Hash uses this instead of checking with "respond_to?" if an
+ # array can convert itself or not. (This is a bad thing in Hash). When Array is extended with a method_missing,
+ # (like when using the RGen package 0.6.1 where meta methods are available on arrays), this trips up the
+ # Hash implementation.
+ # This patch simply does what the regular to_hash does, and it is accompanied by a respond_to? method that
+ # returns false for :to_hash.
+ # This is really ugly, and should be removed when the implementation in lib/rgen/array_extension.rb is
+ # fixed to handle to_hash correctly.
+ def to_hash
+ raise NoMethodError.new
+ end
+
+ # @see #to_hash
+ def respond_to? m
+ return false if m == :to_hash
+ super
+ end
+
end
diff --git a/spec/fixtures/unit/pops/parser/lexer/aliastest.pp b/spec/fixtures/unit/pops/parser/lexer/aliastest.pp
new file mode 100644
index 0000000..f2b6159
--- /dev/null
+++ b/spec/fixtures/unit/pops/parser/lexer/aliastest.pp
@@ -0,0 +1,16 @@
+file { "a file":
+ path => "/tmp/aliastest",
+ ensure => file
+}
+
+file { "another":
+ path => "/tmp/aliastest2",
+ ensure => file,
+ require => File["a file"]
+}
+
+file { "a third":
+ path => "/tmp/aliastest3",
+ ensure => file,
+ require => File["/tmp/aliastest"]
+}
diff --git a/spec/fixtures/unit/pops/parser/lexer/append.pp b/spec/fixtures/unit/pops/parser/lexer/append.pp
new file mode 100644
index 0000000..20cbda6
--- /dev/null
+++ b/spec/fixtures/unit/pops/parser/lexer/append.pp
@@ -0,0 +1,11 @@
+$var=['/tmp/file1','/tmp/file2']
+
+class arraytest {
+ $var += ['/tmp/file3', '/tmp/file4']
+ file {
+ $var:
+ content => "test"
+ }
+}
+
+include arraytest
diff --git a/spec/fixtures/unit/pops/parser/lexer/argumentdefaults.pp b/spec/fixtures/unit/pops/parser/lexer/argumentdefaults.pp
new file mode 100644
index 0000000..eac9dd7
--- /dev/null
+++ b/spec/fixtures/unit/pops/parser/lexer/argumentdefaults.pp
@@ -0,0 +1,14 @@
+# $Id$
+
+define testargs($file, $mode = 755) {
+ file { $file: ensure => file, mode => $mode }
+}
+
+testargs { "testingname":
+ file => "/tmp/argumenttest1"
+}
+
+testargs { "testingother":
+ file => "/tmp/argumenttest2",
+ mode => 644
+}
diff --git a/spec/fixtures/unit/pops/parser/lexer/arithmetic_expression.pp b/spec/fixtures/unit/pops/parser/lexer/arithmetic_expression.pp
new file mode 100644
index 0000000..2d27d7d
--- /dev/null
+++ b/spec/fixtures/unit/pops/parser/lexer/arithmetic_expression.pp
@@ -0,0 +1,8 @@
+
+$one = 1.30
+$two = 2.034e-2
+
+$result = ((( $two + 2) / $one) + 4 * 5.45) - (6 << 7) + (0x800 + -9)
+
+
+notice("result is $result == 1295.87692307692")
diff --git a/spec/fixtures/unit/pops/parser/lexer/arraytrailingcomma.pp b/spec/fixtures/unit/pops/parser/lexer/arraytrailingcomma.pp
new file mode 100644
index 0000000..a410f95
--- /dev/null
+++ b/spec/fixtures/unit/pops/parser/lexer/arraytrailingcomma.pp
@@ -0,0 +1,3 @@
+file {
+ ["/tmp/arraytrailingcomma1","/tmp/arraytrailingcomma2", ]: content => "tmp"
+}
diff --git a/spec/fixtures/unit/pops/parser/lexer/casestatement.pp b/spec/fixtures/unit/pops/parser/lexer/casestatement.pp
new file mode 100644
index 0000000..66ecd72
--- /dev/null
+++ b/spec/fixtures/unit/pops/parser/lexer/casestatement.pp
@@ -0,0 +1,65 @@
+# $Id$
+
+$var = "value"
+
+case $var {
+ "nope": {
+ file { "/tmp/fakefile": mode => 644, ensure => file }
+ }
+ "value": {
+ file { "/tmp/existsfile": mode => 755, ensure => file }
+ }
+}
+
+$ovar = "yayness"
+
+case $ovar {
+ "fooness": {
+ file { "/tmp/nostillexistsfile": mode => 644, ensure => file }
+ }
+ "booness", "yayness": {
+ case $var {
+ "nep": {
+ file { "/tmp/noexistsfile": mode => 644, ensure => file }
+ }
+ "value": {
+ file { "/tmp/existsfile2": mode => 755, ensure => file }
+ }
+ }
+ }
+}
+
+case $ovar {
+ "fooness": {
+ file { "/tmp/nostillexistsfile": mode => 644, ensure => file }
+ }
+ default: {
+ file { "/tmp/existsfile3": mode => 755, ensure => file }
+ }
+}
+
+$bool = true
+
+case $bool {
+ true: {
+ file { "/tmp/existsfile4": mode => 755, ensure => file }
+ }
+}
+
+$yay = yay
+$a = yay
+$b = boo
+
+case $yay {
+ $a: { file { "/tmp/existsfile5": mode => 755, ensure => file } }
+ $b: { file { "/tmp/existsfile5": mode => 644, ensure => file } }
+ default: { file { "/tmp/existsfile5": mode => 711, ensure => file } }
+
+}
+
+$regexvar = "exists regex"
+case $regexvar {
+ "no match": { file { "/tmp/existsfile6": mode => 644, ensure => file } }
+ /(.*) regex$/: { file { "/tmp/${1}file6": mode => 755, ensure => file } }
+ default: { file { "/tmp/existsfile6": mode => 711, ensure => file } }
+}
diff --git a/spec/fixtures/unit/pops/parser/lexer/classheirarchy.pp b/spec/fixtures/unit/pops/parser/lexer/classheirarchy.pp
new file mode 100644
index 0000000..36619d8
--- /dev/null
+++ b/spec/fixtures/unit/pops/parser/lexer/classheirarchy.pp
@@ -0,0 +1,15 @@
+# $Id$
+
+class base {
+ file { "/tmp/classheir1": ensure => file, mode => 755 }
+}
+
+class sub1 inherits base {
+ file { "/tmp/classheir2": ensure => file, mode => 755 }
+}
+
+class sub2 inherits base {
+ file { "/tmp/classheir3": ensure => file, mode => 755 }
+}
+
+include sub1, sub2
diff --git a/spec/fixtures/unit/pops/parser/lexer/classincludes.pp b/spec/fixtures/unit/pops/parser/lexer/classincludes.pp
new file mode 100644
index 0000000..bd5b44e
--- /dev/null
+++ b/spec/fixtures/unit/pops/parser/lexer/classincludes.pp
@@ -0,0 +1,17 @@
+# $Id$
+
+class base {
+ file { "/tmp/classincludes1": ensure => file, mode => 755 }
+}
+
+class sub1 inherits base {
+ file { "/tmp/classincludes2": ensure => file, mode => 755 }
+}
+
+class sub2 inherits base {
+ file { "/tmp/classincludes3": ensure => file, mode => 755 }
+}
+
+$sub = "sub2"
+
+include sub1, $sub
diff --git a/spec/fixtures/unit/pops/parser/lexer/classpathtest.pp b/spec/fixtures/unit/pops/parser/lexer/classpathtest.pp
new file mode 100644
index 0000000..5803333
--- /dev/null
+++ b/spec/fixtures/unit/pops/parser/lexer/classpathtest.pp
@@ -0,0 +1,11 @@
+# $Id$
+
+define mytype {
+ file { "/tmp/classtest": ensure => file, mode => 755 }
+}
+
+class testing {
+ mytype { "componentname": }
+}
+
+include testing
diff --git a/spec/fixtures/unit/pops/parser/lexer/collection.pp b/spec/fixtures/unit/pops/parser/lexer/collection.pp
new file mode 100644
index 0000000..bc29510
--- /dev/null
+++ b/spec/fixtures/unit/pops/parser/lexer/collection.pp
@@ -0,0 +1,10 @@
+class one {
+ @file { "/tmp/colltest1": content => "one" }
+ @file { "/tmp/colltest2": content => "two" }
+}
+
+class two {
+ File <| content == "one" |>
+}
+
+include one, two
diff --git a/spec/fixtures/unit/pops/parser/lexer/collection_override.pp b/spec/fixtures/unit/pops/parser/lexer/collection_override.pp
new file mode 100644
index 0000000..b1b39ab
--- /dev/null
+++ b/spec/fixtures/unit/pops/parser/lexer/collection_override.pp
@@ -0,0 +1,8 @@
+@file {
+ "/tmp/collection":
+ content => "whatever"
+}
+
+File<| |> {
+ mode => 0600
+}
diff --git a/spec/fixtures/unit/pops/parser/lexer/collection_within_virtual_definitions.pp b/spec/fixtures/unit/pops/parser/lexer/collection_within_virtual_definitions.pp
new file mode 100644
index 0000000..3c21468
--- /dev/null
+++ b/spec/fixtures/unit/pops/parser/lexer/collection_within_virtual_definitions.pp
@@ -0,0 +1,20 @@
+define test($name) {
+ file {"/tmp/collection_within_virtual_definitions1_$name.txt":
+ content => "File name $name\n"
+ }
+ Test2 <||>
+}
+
+define test2() {
+ file {"/tmp/collection_within_virtual_definitions2_$name.txt":
+ content => "This is a test\n"
+ }
+}
+
+node default {
+ @test {"foo":
+ name => "foo"
+ }
+ @test2 {"foo2": }
+ Test <||>
+}
diff --git a/spec/fixtures/unit/pops/parser/lexer/componentmetaparams.pp b/spec/fixtures/unit/pops/parser/lexer/componentmetaparams.pp
new file mode 100644
index 0000000..7d9f0c2
--- /dev/null
+++ b/spec/fixtures/unit/pops/parser/lexer/componentmetaparams.pp
@@ -0,0 +1,11 @@
+file { "/tmp/component1":
+ ensure => file
+}
+
+define thing {
+ file { $name: ensure => file }
+}
+
+thing { "/tmp/component2":
+ require => File["/tmp/component1"]
+}
diff --git a/spec/fixtures/unit/pops/parser/lexer/componentrequire.pp b/spec/fixtures/unit/pops/parser/lexer/componentrequire.pp
new file mode 100644
index 0000000..a61d205
--- /dev/null
+++ b/spec/fixtures/unit/pops/parser/lexer/componentrequire.pp
@@ -0,0 +1,8 @@
+define testfile($mode) {
+ file { $name: mode => $mode, ensure => present }
+}
+
+testfile { "/tmp/testing_component_requires2": mode => 755 }
+
+file { "/tmp/testing_component_requires1": mode => 755, ensure => present,
+ require => Testfile["/tmp/testing_component_requires2"] }
diff --git a/spec/fixtures/unit/pops/parser/lexer/deepclassheirarchy.pp b/spec/fixtures/unit/pops/parser/lexer/deepclassheirarchy.pp
new file mode 100644
index 0000000..249e633
--- /dev/null
+++ b/spec/fixtures/unit/pops/parser/lexer/deepclassheirarchy.pp
@@ -0,0 +1,23 @@
+# $Id$
+
+class base {
+ file { "/tmp/deepclassheir1": ensure => file, mode => 755 }
+}
+
+class sub1 inherits base {
+ file { "/tmp/deepclassheir2": ensure => file, mode => 755 }
+}
+
+class sub2 inherits sub1 {
+ file { "/tmp/deepclassheir3": ensure => file, mode => 755 }
+}
+
+class sub3 inherits sub2 {
+ file { "/tmp/deepclassheir4": ensure => file, mode => 755 }
+}
+
+class sub4 inherits sub3 {
+ file { "/tmp/deepclassheir5": ensure => file, mode => 755 }
+}
+
+include sub4
diff --git a/spec/fixtures/unit/pops/parser/lexer/defineoverrides.pp b/spec/fixtures/unit/pops/parser/lexer/defineoverrides.pp
new file mode 100644
index 0000000..c68b139
--- /dev/null
+++ b/spec/fixtures/unit/pops/parser/lexer/defineoverrides.pp
@@ -0,0 +1,17 @@
+# $Id$
+
+$file = "/tmp/defineoverrides1"
+
+define myfile($mode) {
+ file { $name: ensure => file, mode => $mode }
+}
+
+class base {
+ myfile { $file: mode => 644 }
+}
+
+class sub inherits base {
+ Myfile[$file] { mode => 755, } # test the end-comma
+}
+
+include sub
diff --git a/spec/fixtures/unit/pops/parser/lexer/emptyclass.pp b/spec/fixtures/unit/pops/parser/lexer/emptyclass.pp
new file mode 100644
index 0000000..48047e7
--- /dev/null
+++ b/spec/fixtures/unit/pops/parser/lexer/emptyclass.pp
@@ -0,0 +1,9 @@
+# $Id$
+
+define component {
+}
+
+class testing {
+}
+
+include testing
diff --git a/spec/fixtures/unit/pops/parser/lexer/emptyexec.pp b/spec/fixtures/unit/pops/parser/lexer/emptyexec.pp
new file mode 100644
index 0000000..847a30d
--- /dev/null
+++ b/spec/fixtures/unit/pops/parser/lexer/emptyexec.pp
@@ -0,0 +1,3 @@
+exec { "touch /tmp/emptyexectest":
+ path => "/usr/bin:/bin"
+}
diff --git a/spec/fixtures/unit/pops/parser/lexer/emptyifelse.pp b/spec/fixtures/unit/pops/parser/lexer/emptyifelse.pp
new file mode 100644
index 0000000..598b486
--- /dev/null
+++ b/spec/fixtures/unit/pops/parser/lexer/emptyifelse.pp
@@ -0,0 +1,9 @@
+
+if false {
+} else {
+ # nothing here
+}
+
+if true {
+ # still nothing
+}
diff --git a/spec/fixtures/unit/pops/parser/lexer/falsevalues.pp b/spec/fixtures/unit/pops/parser/lexer/falsevalues.pp
new file mode 100644
index 0000000..2143b79
--- /dev/null
+++ b/spec/fixtures/unit/pops/parser/lexer/falsevalues.pp
@@ -0,0 +1,3 @@
+$value = false
+
+file { "/tmp/falsevalues$value": ensure => file }
diff --git a/spec/fixtures/unit/pops/parser/lexer/filecreate.pp b/spec/fixtures/unit/pops/parser/lexer/filecreate.pp
new file mode 100644
index 0000000..d7972c2
--- /dev/null
+++ b/spec/fixtures/unit/pops/parser/lexer/filecreate.pp
@@ -0,0 +1,11 @@
+# $Id$
+
+file {
+ "/tmp/createatest": ensure => file, mode => 755;
+ "/tmp/createbtest": ensure => file, mode => 755
+}
+
+file {
+ "/tmp/createctest": ensure => file;
+ "/tmp/createdtest": ensure => file;
+}
diff --git a/spec/fixtures/unit/pops/parser/lexer/fqdefinition.pp b/spec/fixtures/unit/pops/parser/lexer/fqdefinition.pp
new file mode 100644
index 0000000..ddb0675
--- /dev/null
+++ b/spec/fixtures/unit/pops/parser/lexer/fqdefinition.pp
@@ -0,0 +1,5 @@
+define one::two($ensure) {
+ file { "/tmp/fqdefinition": ensure => $ensure }
+}
+
+one::two { "/tmp/fqdefinition": ensure => file }
diff --git a/spec/fixtures/unit/pops/parser/lexer/fqparents.pp b/spec/fixtures/unit/pops/parser/lexer/fqparents.pp
new file mode 100644
index 0000000..ee2f654
--- /dev/null
+++ b/spec/fixtures/unit/pops/parser/lexer/fqparents.pp
@@ -0,0 +1,11 @@
+class base {
+ class one {
+ file { "/tmp/fqparent1": ensure => file }
+ }
+}
+
+class two::three inherits base::one {
+ file { "/tmp/fqparent2": ensure => file }
+}
+
+include two::three
diff --git a/spec/fixtures/unit/pops/parser/lexer/funccomma.pp b/spec/fixtures/unit/pops/parser/lexer/funccomma.pp
new file mode 100644
index 0000000..32e34f9
--- /dev/null
+++ b/spec/fixtures/unit/pops/parser/lexer/funccomma.pp
@@ -0,0 +1,5 @@
+@file {
+ ["/tmp/funccomma1","/tmp/funccomma2"]: content => "1"
+}
+
+realize( File["/tmp/funccomma1"], File["/tmp/funccomma2"] , )
diff --git a/spec/fixtures/unit/pops/parser/lexer/hash.pp b/spec/fixtures/unit/pops/parser/lexer/hash.pp
new file mode 100644
index 0000000..d332498
--- /dev/null
+++ b/spec/fixtures/unit/pops/parser/lexer/hash.pp
@@ -0,0 +1,33 @@
+
+$hash = { "file" => "/tmp/myhashfile1" }
+
+file {
+ $hash["file"]:
+ ensure => file, content => "content";
+}
+
+$hash2 = { "a" => { key => "/tmp/myhashfile2" }}
+
+file {
+ $hash2["a"][key]:
+ ensure => file, content => "content";
+}
+
+define test($a = { "b" => "c" }) {
+ file {
+ $a["b"]:
+ ensure => file, content => "content"
+ }
+}
+
+test {
+ "test":
+ a => { "b" => "/tmp/myhashfile3" }
+}
+
+$hash3 = { mykey => "/tmp/myhashfile4" }
+$key = "mykey"
+
+file {
+ $hash3[$key]: ensure => file, content => "content"
+}
diff --git a/spec/fixtures/unit/pops/parser/lexer/ifexpression.pp b/spec/fixtures/unit/pops/parser/lexer/ifexpression.pp
new file mode 100644
index 0000000..29a6372
--- /dev/null
+++ b/spec/fixtures/unit/pops/parser/lexer/ifexpression.pp
@@ -0,0 +1,12 @@
+$one = 1
+$two = 2
+
+if ($one < $two) and (($two < 3) or ($two == 2)) {
+ notice("True!")
+}
+
+if "test regex" =~ /(.*) regex/ {
+ file {
+ "/tmp/${1}iftest": ensure => file, mode => 0755
+ }
+}
diff --git a/spec/fixtures/unit/pops/parser/lexer/implicititeration.pp b/spec/fixtures/unit/pops/parser/lexer/implicititeration.pp
new file mode 100644
index 0000000..6f34cb2
--- /dev/null
+++ b/spec/fixtures/unit/pops/parser/lexer/implicititeration.pp
@@ -0,0 +1,15 @@
+# $Id$
+
+$files = ["/tmp/iterationatest", "/tmp/iterationbtest"]
+
+file { $files: ensure => file, mode => 755 }
+
+file { ["/tmp/iterationctest", "/tmp/iterationdtest"]:
+ ensure => file,
+ mode => 755
+}
+
+file {
+ ["/tmp/iterationetest", "/tmp/iterationftest"]: ensure => file, mode => 755;
+ ["/tmp/iterationgtest", "/tmp/iterationhtest"]: ensure => file, mode => 755;
+}
diff --git a/spec/fixtures/unit/pops/parser/lexer/multilinecomments.pp b/spec/fixtures/unit/pops/parser/lexer/multilinecomments.pp
new file mode 100644
index 0000000..f9819c0
--- /dev/null
+++ b/spec/fixtures/unit/pops/parser/lexer/multilinecomments.pp
@@ -0,0 +1,10 @@
+
+/*
+file {
+ "/tmp/multilinecomments": content => "pouet"
+}
+*/
+
+/* and another one for #2333, the whitespace after the
+end comment is here on purpose */
+
diff --git a/spec/fixtures/unit/pops/parser/lexer/multipleclass.pp b/spec/fixtures/unit/pops/parser/lexer/multipleclass.pp
new file mode 100644
index 0000000..ae02edc
--- /dev/null
+++ b/spec/fixtures/unit/pops/parser/lexer/multipleclass.pp
@@ -0,0 +1,9 @@
+class one {
+ file { "/tmp/multipleclassone": content => "one" }
+}
+
+class one {
+ file { "/tmp/multipleclasstwo": content => "two" }
+}
+
+include one
diff --git a/spec/fixtures/unit/pops/parser/lexer/multipleinstances.pp b/spec/fixtures/unit/pops/parser/lexer/multipleinstances.pp
new file mode 100644
index 0000000..2f9b3c2
--- /dev/null
+++ b/spec/fixtures/unit/pops/parser/lexer/multipleinstances.pp
@@ -0,0 +1,7 @@
+# $Id$
+
+file {
+ "/tmp/multipleinstancesa": ensure => file, mode => 755;
+ "/tmp/multipleinstancesb": ensure => file, mode => 755;
+ "/tmp/multipleinstancesc": ensure => file, mode => 755;
+}
diff --git a/spec/fixtures/unit/pops/parser/lexer/multisubs.pp b/spec/fixtures/unit/pops/parser/lexer/multisubs.pp
new file mode 100644
index 0000000..bcec69e
--- /dev/null
+++ b/spec/fixtures/unit/pops/parser/lexer/multisubs.pp
@@ -0,0 +1,13 @@
+class base {
+ file { "/tmp/multisubtest": content => "base", mode => 644 }
+}
+
+class sub1 inherits base {
+ File["/tmp/multisubtest"] { mode => 755 }
+}
+
+class sub2 inherits base {
+ File["/tmp/multisubtest"] { content => sub2 }
+}
+
+include sub1, sub2
diff --git a/spec/fixtures/unit/pops/parser/lexer/namevartest.pp b/spec/fixtures/unit/pops/parser/lexer/namevartest.pp
new file mode 100644
index 0000000..dbee1c3
--- /dev/null
+++ b/spec/fixtures/unit/pops/parser/lexer/namevartest.pp
@@ -0,0 +1,9 @@
+define filetest($mode, $ensure = file) {
+ file { $name:
+ mode => $mode,
+ ensure => $ensure
+ }
+}
+
+filetest { "/tmp/testfiletest": mode => 644}
+filetest { "/tmp/testdirtest": mode => 755, ensure => directory}
diff --git a/spec/fixtures/unit/pops/parser/lexer/scopetest.pp b/spec/fixtures/unit/pops/parser/lexer/scopetest.pp
new file mode 100644
index 0000000..3314917
--- /dev/null
+++ b/spec/fixtures/unit/pops/parser/lexer/scopetest.pp
@@ -0,0 +1,13 @@
+
+$mode = 640
+
+define thing {
+ file { "/tmp/$name": ensure => file, mode => $mode }
+}
+
+class testing {
+ $mode = 755
+ thing {scopetest: }
+}
+
+include testing
diff --git a/spec/fixtures/unit/pops/parser/lexer/selectorvalues.pp b/spec/fixtures/unit/pops/parser/lexer/selectorvalues.pp
new file mode 100644
index 0000000..d80d26c
--- /dev/null
+++ b/spec/fixtures/unit/pops/parser/lexer/selectorvalues.pp
@@ -0,0 +1,49 @@
+$value1 = ""
+$value2 = true
+$value3 = false
+$value4 = yay
+
+$test = "yay"
+
+$mode1 = $value1 ? {
+ "" => 755,
+ default => 644
+}
+
+$mode2 = $value2 ? {
+ true => 755,
+ default => 644
+}
+
+$mode3 = $value3 ? {
+ false => 755,
+ default => 644
+}
+
+$mode4 = $value4 ? {
+ $test => 755,
+ default => 644
+}
+
+$mode5 = yay ? {
+ $test => 755,
+ default => 644
+}
+
+$mode6 = $mode5 ? {
+ 755 => 755
+}
+
+$mode7 = "test regex" ? {
+ /regex$/ => 755,
+ default => 644
+}
+
+
+file { "/tmp/selectorvalues1": ensure => file, mode => $mode1 }
+file { "/tmp/selectorvalues2": ensure => file, mode => $mode2 }
+file { "/tmp/selectorvalues3": ensure => file, mode => $mode3 }
+file { "/tmp/selectorvalues4": ensure => file, mode => $mode4 }
+file { "/tmp/selectorvalues5": ensure => file, mode => $mode5 }
+file { "/tmp/selectorvalues6": ensure => file, mode => $mode6 }
+file { "/tmp/selectorvalues7": ensure => file, mode => $mode7 }
diff --git a/spec/fixtures/unit/pops/parser/lexer/simpledefaults.pp b/spec/fixtures/unit/pops/parser/lexer/simpledefaults.pp
new file mode 100644
index 0000000..63d199a
--- /dev/null
+++ b/spec/fixtures/unit/pops/parser/lexer/simpledefaults.pp
@@ -0,0 +1,5 @@
+# $Id$
+
+File { mode => 755 }
+
+file { "/tmp/defaulttest": ensure => file }
diff --git a/spec/fixtures/unit/pops/parser/lexer/simpleselector.pp b/spec/fixtures/unit/pops/parser/lexer/simpleselector.pp
new file mode 100644
index 0000000..8b9bc72
--- /dev/null
+++ b/spec/fixtures/unit/pops/parser/lexer/simpleselector.pp
@@ -0,0 +1,38 @@
+# $Id$
+
+$var = "value"
+
+file { "/tmp/snippetselectatest":
+ ensure => file,
+ mode => $var ? {
+ nottrue => 641,
+ value => 755
+ }
+}
+
+file { "/tmp/snippetselectbtest":
+ ensure => file,
+ mode => $var ? {
+ nottrue => 644,
+ default => 755
+ }
+}
+
+$othervar = "complex value"
+
+file { "/tmp/snippetselectctest":
+ ensure => file,
+ mode => $othervar ? {
+ "complex value" => 755,
+ default => 644
+ }
+}
+$anothervar = Yayness
+
+file { "/tmp/snippetselectdtest":
+ ensure => file,
+ mode => $anothervar ? {
+ Yayness => 755,
+ default => 644
+ }
+}
diff --git a/spec/fixtures/unit/pops/parser/lexer/singleary.pp b/spec/fixtures/unit/pops/parser/lexer/singleary.pp
new file mode 100644
index 0000000..9ce56dd
--- /dev/null
+++ b/spec/fixtures/unit/pops/parser/lexer/singleary.pp
@@ -0,0 +1,19 @@
+# $Id$
+
+file { "/tmp/singleary1":
+ ensure => file
+}
+
+file { "/tmp/singleary2":
+ ensure => file
+}
+
+file { "/tmp/singleary3":
+ ensure => file,
+ require => [File["/tmp/singleary1"], File["/tmp/singleary2"]]
+}
+
+file { "/tmp/singleary4":
+ ensure => file,
+ require => [File["/tmp/singleary1"]]
+}
diff --git a/spec/fixtures/unit/pops/parser/lexer/singlequote.pp b/spec/fixtures/unit/pops/parser/lexer/singlequote.pp
new file mode 100644
index 0000000..dc876a2
--- /dev/null
+++ b/spec/fixtures/unit/pops/parser/lexer/singlequote.pp
@@ -0,0 +1,11 @@
+# $Id$
+
+file { "/tmp/singlequote1":
+ ensure => file,
+ content => 'a $quote'
+}
+
+file { "/tmp/singlequote2":
+ ensure => file,
+ content => 'some "\yayness\"'
+}
diff --git a/spec/fixtures/unit/pops/parser/lexer/singleselector.pp b/spec/fixtures/unit/pops/parser/lexer/singleselector.pp
new file mode 100644
index 0000000..520a140
--- /dev/null
+++ b/spec/fixtures/unit/pops/parser/lexer/singleselector.pp
@@ -0,0 +1,22 @@
+$value1 = ""
+$value2 = true
+$value3 = false
+$value4 = yay
+
+$test = "yay"
+
+$mode1 = $value1 ? {
+ "" => 755
+}
+
+$mode2 = $value2 ? {
+ true => 755
+}
+
+$mode3 = $value3 ? {
+ default => 755
+}
+
+file { "/tmp/singleselector1": ensure => file, mode => $mode1 }
+file { "/tmp/singleselector2": ensure => file, mode => $mode2 }
+file { "/tmp/singleselector3": ensure => file, mode => $mode3 }
diff --git a/spec/fixtures/unit/pops/parser/lexer/subclass_name_duplication.pp b/spec/fixtures/unit/pops/parser/lexer/subclass_name_duplication.pp
new file mode 100755
index 0000000..10f1d75
--- /dev/null
+++ b/spec/fixtures/unit/pops/parser/lexer/subclass_name_duplication.pp
@@ -0,0 +1,11 @@
+#!/usr/bin/env puppet
+
+class one::fake {
+ file { "/tmp/subclass_name_duplication1": ensure => present }
+}
+
+class two::fake {
+ file { "/tmp/subclass_name_duplication2": ensure => present }
+}
+
+include one::fake, two::fake
diff --git a/spec/fixtures/unit/pops/parser/lexer/tag.pp b/spec/fixtures/unit/pops/parser/lexer/tag.pp
new file mode 100644
index 0000000..e6e770d
--- /dev/null
+++ b/spec/fixtures/unit/pops/parser/lexer/tag.pp
@@ -0,0 +1,9 @@
+# $Id$
+
+$variable = value
+
+tag yayness, rahness
+
+tag booness, $variable
+
+file { "/tmp/settestingness": ensure => file }
diff --git a/spec/fixtures/unit/pops/parser/lexer/tagged.pp b/spec/fixtures/unit/pops/parser/lexer/tagged.pp
new file mode 100644
index 0000000..7bf90a6
--- /dev/null
+++ b/spec/fixtures/unit/pops/parser/lexer/tagged.pp
@@ -0,0 +1,35 @@
+# $Id$
+
+tag testing
+tag(funtest)
+
+class tagdefine {
+ $path = tagged(tagdefine) ? {
+ true => "true", false => "false"
+ }
+
+ file { "/tmp/taggeddefine$path": ensure => file }
+}
+
+include tagdefine
+
+$yayness = tagged(yayness) ? {
+ true => "true", false => "false"
+}
+
+$funtest = tagged(testing) ? {
+ true => "true", false => "false"
+}
+
+$both = tagged(testing, yayness) ? {
+ true => "true", false => "false"
+}
+
+$bothtrue = tagged(testing, testing) ? {
+ true => "true", false => "false"
+}
+
+file { "/tmp/taggedyayness$yayness": ensure => file }
+file { "/tmp/taggedtesting$funtest": ensure => file }
+file { "/tmp/taggedboth$both": ensure => file }
+file { "/tmp/taggedbothtrue$bothtrue": ensure => file }
diff --git a/spec/fixtures/unit/pops/parser/lexer/virtualresources.pp b/spec/fixtures/unit/pops/parser/lexer/virtualresources.pp
new file mode 100644
index 0000000..a29406b
--- /dev/null
+++ b/spec/fixtures/unit/pops/parser/lexer/virtualresources.pp
@@ -0,0 +1,14 @@
+class one {
+ @file { "/tmp/virtualtest1": content => "one" }
+ @file { "/tmp/virtualtest2": content => "two" }
+ @file { "/tmp/virtualtest3": content => "three" }
+ @file { "/tmp/virtualtest4": content => "four" }
+}
+
+class two {
+ File <| content == "one" |>
+ realize File["/tmp/virtualtest2"]
+ realize(File["/tmp/virtualtest3"], File["/tmp/virtualtest4"])
+}
+
+include one, two
diff --git a/spec/integration/parser/collector_spec.rb b/spec/integration/parser/collector_spec.rb
index cb210b3..49ce745 100755
--- a/spec/integration/parser/collector_spec.rb
+++ b/spec/integration/parser/collector_spec.rb
@@ -64,7 +64,7 @@ def expect_the_message_to_be(expected_messages, code, node = Puppet::Node.new('t
expect_the_message_to_be(["the message", "different message"], <<-MANIFEST)
@notify { "testing": message => "different message", withpath => true }
@notify { "other": message => "the message" }
- @notify { "yet another": message => "the message", withpath => true }
+ @notify { "yet another": message => "the message", withpath => true }
Notify <| (title == "testing" or message == "the message") and withpath == true |>
MANIFEST
diff --git a/spec/integration/parser/compiler_spec.rb b/spec/integration/parser/compiler_spec.rb
index 9a4deb2..6126421 100755
--- a/spec/integration/parser/compiler_spec.rb
+++ b/spec/integration/parser/compiler_spec.rb
@@ -1,7 +1,8 @@
#! /usr/bin/env ruby
require 'spec_helper'
+require 'puppet/parser/parser_factory'
-describe Puppet::Parser::Compiler do
+describe "Puppet::Parser::Compiler" do
before :each do
@node = Puppet::Node.new "testnode"
@@ -13,297 +14,322 @@
Puppet.settings.clear
end
- it "should be able to determine the configuration version from a local version control repository" do
- pending("Bug #14071 about semantics of Puppet::Util::Execute on Windows", :if => Puppet.features.microsoft_windows?) do
- # This should always work, because we should always be
- # in the puppet repo when we run this.
- version = %x{git rev-parse HEAD}.chomp
+ # shared because tests are invoked both for classic and future parser
+ #
+ shared_examples_for "the compiler" do
+ it "should be able to determine the configuration version from a local version control repository" do
+ pending("Bug #14071 about semantics of Puppet::Util::Execute on Windows", :if => Puppet.features.microsoft_windows?) do
+ # This should always work, because we should always be
+ # in the puppet repo when we run this.
+ version = %x{git rev-parse HEAD}.chomp
- Puppet.settings[:config_version] = 'git rev-parse HEAD'
+ Puppet.settings[:config_version] = 'git rev-parse HEAD'
- @parser = Puppet::Parser::Parser.new "development"
- @compiler = Puppet::Parser::Compiler.new(@node)
+ # @parser = Puppet::Parser::Parser.new "development"
+ @parser = Puppet::Parser::ParserFactory.parser "development"
+ @compiler = Puppet::Parser::Compiler.new(@node)
- @compiler.catalog.version.should == version
+ @compiler.catalog.version.should == version
+ end
end
- end
-
- it "should not create duplicate resources when a class is referenced both directly and indirectly by the node classifier (4792)" do
- Puppet[:code] = <<-PP
- class foo
- {
- notify { foo_notify: }
- include bar
- }
- class bar
- {
- notify { bar_notify: }
- }
- PP
-
- @node.stubs(:classes).returns(['foo', 'bar'])
-
- catalog = Puppet::Parser::Compiler.compile(@node)
-
- catalog.resource("Notify[foo_notify]").should_not be_nil
- catalog.resource("Notify[bar_notify]").should_not be_nil
- end
- describe "when resolving class references" do
- it "should favor local scope, even if there's an included class in topscope" do
+ it "should not create duplicate resources when a class is referenced both directly and indirectly by the node classifier (4792)" do
Puppet[:code] = <<-PP
- class experiment {
- class baz {
- }
- notify {"x" : require => Class[Baz] }
+ class foo
+ {
+ notify { foo_notify: }
+ include bar
}
- class baz {
+ class bar
+ {
+ notify { bar_notify: }
}
- include baz
- include experiment
- include experiment::baz
PP
- catalog = Puppet::Parser::Compiler.compile(Puppet::Node.new("mynode"))
+ @node.stubs(:classes).returns(['foo', 'bar'])
- notify_resource = catalog.resource( "Notify[x]" )
+ catalog = Puppet::Parser::Compiler.compile(@node)
- notify_resource[:require].title.should == "Experiment::Baz"
+ catalog.resource("Notify[foo_notify]").should_not be_nil
+ catalog.resource("Notify[bar_notify]").should_not be_nil
end
- it "should favor local scope, even if there's an unincluded class in topscope" do
- Puppet[:code] = <<-PP
- class experiment {
+ describe "when resolving class references" do
+ it "should favor local scope, even if there's an included class in topscope" do
+ Puppet[:code] = <<-PP
+ class experiment {
+ class baz {
+ }
+ notify {"x" : require => Class[Baz] }
+ }
class baz {
}
- notify {"x" : require => Class[Baz] }
- }
- class baz {
- }
- include experiment
- include experiment::baz
- PP
+ include baz
+ include experiment
+ include experiment::baz
+ PP
- catalog = Puppet::Parser::Compiler.compile(Puppet::Node.new("mynode"))
+ catalog = Puppet::Parser::Compiler.compile(Puppet::Node.new("mynode"))
- notify_resource = catalog.resource( "Notify[x]" )
+ notify_resource = catalog.resource( "Notify[x]" )
- notify_resource[:require].title.should == "Experiment::Baz"
- end
- end
- describe "(ticket #13349) when explicitly specifying top scope" do
- ["class {'::bar::baz':}", "include ::bar::baz"].each do |include|
- describe "with #{include}" do
- it "should find the top level class" do
- Puppet[:code] = <<-MANIFEST
- class { 'foo::test': }
- class foo::test {
- #{include}
- }
- class bar::baz {
- notify { 'good!': }
- }
- class foo::bar::baz {
- notify { 'bad!': }
+ notify_resource[:require].title.should == "Experiment::Baz"
+ end
+
+ it "should favor local scope, even if there's an unincluded class in topscope" do
+ Puppet[:code] = <<-PP
+ class experiment {
+ class baz {
}
- MANIFEST
+ notify {"x" : require => Class[Baz] }
+ }
+ class baz {
+ }
+ include experiment
+ include experiment::baz
+ PP
- catalog = Puppet::Parser::Compiler.compile(Puppet::Node.new("mynode"))
+ catalog = Puppet::Parser::Compiler.compile(Puppet::Node.new("mynode"))
- catalog.resource("Class[Bar::Baz]").should_not be_nil
- catalog.resource("Notify[good!]").should_not be_nil
- catalog.resource("Class[Foo::Bar::Baz]").should be_nil
- catalog.resource("Notify[bad!]").should be_nil
+ notify_resource = catalog.resource( "Notify[x]" )
+
+ notify_resource[:require].title.should == "Experiment::Baz"
+ end
+ end
+ describe "(ticket #13349) when explicitly specifying top scope" do
+ ["class {'::bar::baz':}", "include ::bar::baz"].each do |include|
+ describe "with #{include}" do
+ it "should find the top level class" do
+ Puppet[:code] = <<-MANIFEST
+ class { 'foo::test': }
+ class foo::test {
+ #{include}
+ }
+ class bar::baz {
+ notify { 'good!': }
+ }
+ class foo::bar::baz {
+ notify { 'bad!': }
+ }
+ MANIFEST
+
+ catalog = Puppet::Parser::Compiler.compile(Puppet::Node.new("mynode"))
+
+ catalog.resource("Class[Bar::Baz]").should_not be_nil
+ catalog.resource("Notify[good!]").should_not be_nil
+ catalog.resource("Class[Foo::Bar::Baz]").should be_nil
+ catalog.resource("Notify[bad!]").should be_nil
+ end
end
end
end
- end
- it "should recompute the version after input files are re-parsed" do
- Puppet[:code] = 'class foo { }'
- Time.stubs(:now).returns(1)
- node = Puppet::Node.new('mynode')
- Puppet::Parser::Compiler.compile(node).version.should == 1
- Time.stubs(:now).returns(2)
- Puppet::Parser::Compiler.compile(node).version.should == 1 # no change because files didn't change
- Puppet::Resource::TypeCollection.any_instance.stubs(:stale?).returns(true).then.returns(false) # pretend change
- Puppet::Parser::Compiler.compile(node).version.should == 2
- end
+ it "should recompute the version after input files are re-parsed" do
+ Puppet[:code] = 'class foo { }'
+ Time.stubs(:now).returns(1)
+ node = Puppet::Node.new('mynode')
+ Puppet::Parser::Compiler.compile(node).version.should == 1
+ Time.stubs(:now).returns(2)
+ Puppet::Parser::Compiler.compile(node).version.should == 1 # no change because files didn't change
+ Puppet::Resource::TypeCollection.any_instance.stubs(:stale?).returns(true).then.returns(false) # pretend change
+ Puppet::Parser::Compiler.compile(node).version.should == 2
+ end
- ['class', 'define', 'node'].each do |thing|
- it "should not allow #{thing} inside evaluated conditional constructs" do
- Puppet[:code] = <<-PP
- if true {
- #{thing} foo {
+ ['class', 'define', 'node'].each do |thing|
+ it "should not allow #{thing} inside evaluated conditional constructs" do
+ Puppet[:code] = <<-PP
+ if true {
+ #{thing} foo {
+ }
+ notify { decoy: }
}
- notify { decoy: }
- }
- PP
+ PP
- begin
- Puppet::Parser::Compiler.compile(Puppet::Node.new("mynode"))
- raise "compilation should have raised Puppet::Error"
- rescue Puppet::Error => e
- e.message.should =~ /at line 2/
+ begin
+ Puppet::Parser::Compiler.compile(Puppet::Node.new("mynode"))
+ raise "compilation should have raised Puppet::Error"
+ rescue Puppet::Error => e
+ e.message.should =~ /at line 2/
+ end
end
end
- end
- it "should not allow classes inside unevaluated conditional constructs" do
- Puppet[:code] = <<-PP
- if false {
- class foo {
+ it "should not allow classes inside unevaluated conditional constructs" do
+ Puppet[:code] = <<-PP
+ if false {
+ class foo {
+ }
}
- }
- PP
-
- lambda { Puppet::Parser::Compiler.compile(Puppet::Node.new("mynode")) }.should raise_error(Puppet::Error)
- end
+ PP
- describe "when defining relationships" do
- def extract_name(ref)
- ref.sub(/File\[(\w+)\]/, '\1')
+ lambda { Puppet::Parser::Compiler.compile(Puppet::Node.new("mynode")) }.should raise_error(Puppet::Error)
end
- let(:node) { Puppet::Node.new('mynode') }
- let(:code) do
- <<-MANIFEST
- file { [a,b,c]:
- mode => 0644,
- }
- file { [d,e]:
- mode => 0755,
- }
- MANIFEST
- end
- let(:expected_relationships) { [] }
- let(:expected_subscriptions) { [] }
+ describe "when defining relationships" do
+ def extract_name(ref)
+ ref.sub(/File\[(\w+)\]/, '\1')
+ end
- before :each do
- Puppet[:code] = code
- end
+ let(:node) { Puppet::Node.new('mynode') }
+ let(:code) do
+ <<-MANIFEST
+ file { [a,b,c]:
+ mode => 0644,
+ }
+ file { [d,e]:
+ mode => 0755,
+ }
+ MANIFEST
+ end
+ let(:expected_relationships) { [] }
+ let(:expected_subscriptions) { [] }
- after :each do
- catalog = described_class.compile(node)
+ before :each do
+ Puppet[:code] = code
+ end
- resources = catalog.resources.select { |res| res.type == 'File' }
+ after :each do
+ catalog = Puppet::Parser::Compiler.compile(node)
- actual_relationships, actual_subscriptions = [:before, :notify].map do |relation|
- resources.map do |res|
- dependents = Array(res[relation])
- dependents.map { |ref| [res.title, extract_name(ref)] }
- end.inject(&:concat)
- end
+ resources = catalog.resources.select { |res| res.type == 'File' }
- actual_relationships.should =~ expected_relationships
- actual_subscriptions.should =~ expected_subscriptions
- end
+ actual_relationships, actual_subscriptions = [:before, :notify].map do |relation|
+ resources.map do |res|
+ dependents = Array(res[relation])
+ dependents.map { |ref| [res.title, extract_name(ref)] }
+ end.inject(&:concat)
+ end
- it "should create a relationship" do
- code << "File[a] -> File[b]"
+ actual_relationships.should =~ expected_relationships
+ actual_subscriptions.should =~ expected_subscriptions
+ end
- expected_relationships << ['a','b']
- end
+ it "should create a relationship" do
+ code << "File[a] -> File[b]"
- it "should create a subscription" do
- code << "File[a] ~> File[b]"
+ expected_relationships << ['a','b']
+ end
- expected_subscriptions << ['a', 'b']
- end
+ it "should create a subscription" do
+ code << "File[a] ~> File[b]"
- it "should create relationships using title arrays" do
- code << "File[a,b] -> File[c,d]"
+ expected_subscriptions << ['a', 'b']
+ end
- expected_relationships.concat [
- ['a', 'c'],
- ['b', 'c'],
- ['a', 'd'],
- ['b', 'd'],
- ]
- end
+ it "should create relationships using title arrays" do
+ code << "File[a,b] -> File[c,d]"
- it "should create relationships using collection expressions" do
- code << "File <| mode == 0644 |> -> File <| mode == 0755 |>"
-
- expected_relationships.concat [
- ['a', 'd'],
- ['b', 'd'],
- ['c', 'd'],
- ['a', 'e'],
- ['b', 'e'],
- ['c', 'e'],
- ]
- end
+ expected_relationships.concat [
+ ['a', 'c'],
+ ['b', 'c'],
+ ['a', 'd'],
+ ['b', 'd'],
+ ]
+ end
- it "should create relationships using resource names" do
- code << "'File[a]' -> 'File[b]'"
+ it "should create relationships using collection expressions" do
+ code << "File <| mode == 0644 |> -> File <| mode == 0755 |>"
+
+ expected_relationships.concat [
+ ['a', 'd'],
+ ['b', 'd'],
+ ['c', 'd'],
+ ['a', 'e'],
+ ['b', 'e'],
+ ['c', 'e'],
+ ]
+ end
- expected_relationships << ['a', 'b']
- end
+ it "should create relationships using resource names" do
+ code << "'File[a]' -> 'File[b]'"
- it "should create relationships using variables" do
- code << <<-MANIFEST
- $var = File[a]
- $var -> File[b]
- MANIFEST
+ expected_relationships << ['a', 'b']
+ end
- expected_relationships << ['a', 'b']
- end
+ it "should create relationships using variables" do
+ code << <<-MANIFEST
+ $var = File[a]
+ $var -> File[b]
+ MANIFEST
- it "should create relationships using case statements" do
- code << <<-MANIFEST
- $var = 10
- case $var {
- 10: {
- file { s1: }
- }
- 12: {
- file { s2: }
- }
- }
- ->
- case $var + 2 {
- 10: {
- file { t1: }
+ expected_relationships << ['a', 'b']
+ end
+
+ it "should create relationships using case statements" do
+ code << <<-MANIFEST
+ $var = 10
+ case $var {
+ 10: {
+ file { s1: }
+ }
+ 12: {
+ file { s2: }
+ }
}
- 12: {
- file { t2: }
+ ->
+ case $var + 2 {
+ 10: {
+ file { t1: }
+ }
+ 12: {
+ file { t2: }
+ }
}
- }
- MANIFEST
+ MANIFEST
- expected_relationships << ['s1', 't2']
- end
+ expected_relationships << ['s1', 't2']
+ end
- it "should create relationships using array members" do
- code << <<-MANIFEST
- $var = [ [ [ File[a], File[b] ] ] ]
- $var[0][0][0] -> $var[0][0][1]
- MANIFEST
+ it "should create relationships using array members" do
+ code << <<-MANIFEST
+ $var = [ [ [ File[a], File[b] ] ] ]
+ $var[0][0][0] -> $var[0][0][1]
+ MANIFEST
- expected_relationships << ['a', 'b']
- end
+ expected_relationships << ['a', 'b']
+ end
- it "should create relationships using hash members" do
- code << <<-MANIFEST
- $var = {'foo' => {'bar' => {'source' => File[a], 'target' => File[b]}}}
- $var[foo][bar][source] -> $var[foo][bar][target]
- MANIFEST
+ it "should create relationships using hash members" do
+ code << <<-MANIFEST
+ $var = {'foo' => {'bar' => {'source' => File[a], 'target' => File[b]}}}
+ $var[foo][bar][source] -> $var[foo][bar][target]
+ MANIFEST
- expected_relationships << ['a', 'b']
- end
+ expected_relationships << ['a', 'b']
+ end
- it "should create relationships using resource declarations" do
- code << "file { l: } -> file { r: }"
+ it "should create relationships using resource declarations" do
+ code << "file { l: } -> file { r: }"
- expected_relationships << ['l', 'r']
+ expected_relationships << ['l', 'r']
+ end
+
+ it "should chain relationships" do
+ code << "File[a] -> File[b] ~> File[c] <- File[d] <~ File[e]"
+
+ expected_relationships << ['a', 'b'] << ['d', 'c']
+ expected_subscriptions << ['b', 'c'] << ['e', 'd']
+ end
end
+ end
- it "should chain relationships" do
- code << "File[a] -> File[b] ~> File[c] <- File[d] <~ File[e]"
+ describe 'using classic parser' do
+ before :each do
+ Puppet[:parser] = 'current'
+ end
+ it_behaves_like 'the compiler' do
+ end
+ end
- expected_relationships << ['a', 'b'] << ['d', 'c']
- expected_subscriptions << ['b', 'c'] << ['e', 'd']
+ describe 'using future parser' do
+ # have absolutely no clue to why this is needed - if not required here (even if required by used classes)
+ # the tests will fail with error that rgen/ecore/ruby_to_ecore cannot be found...
+ # TODO: Solve this mystery !
+ require 'rgen/metamodel_builder'
+
+ before :each do
+ Puppet[:parser] = 'future'
end
+ it_behaves_like 'the compiler'
end
end
diff --git a/spec/integration/parser/parser_spec.rb b/spec/integration/parser/parser_spec.rb
index 4274303..759643a 100755
--- a/spec/integration/parser/parser_spec.rb
+++ b/spec/integration/parser/parser_spec.rb
@@ -1,11 +1,12 @@
#! /usr/bin/env ruby
require 'spec_helper'
+require 'puppet/parser/parser_factory'
-describe Puppet::Parser::Parser do
+describe "Puppet::Parser::Parser" do
module ParseMatcher
class ParseAs
def initialize(klass)
- @parser = Puppet::Parser::Parser.new "development"
+ @parser = Puppet::Parser::ParserFactory.parser("development")
@class = klass
end
@@ -38,7 +39,7 @@ def parse_as(klass)
class ParseWith
def initialize(block)
- @parser = Puppet::Parser::Parser.new "development"
+ @parser = Puppet::Parser::ParserFactory.parser("development")
@block = block
end
@@ -74,76 +75,193 @@ def parse_with(&block)
before :each do
@resource_type_collection = Puppet::Resource::TypeCollection.new("env")
- @parser = Puppet::Parser::Parser.new "development"
- end
-
- describe "when parsing comments before statement" do
- it "should associate the documentation to the statement AST node" do
- ast = @parser.parse("""
- # comment
- class test {}
- """)
+ @parser = Puppet::Parser::ParserFactory.parser("development")
- ast.code[0].should be_a(Puppet::Parser::AST::Hostclass)
- ast.code[0].name.should == 'test'
- ast.code[0].instantiate('')[0].doc.should == "comment\n"
- end
+# @parser = Puppet::Parser::Parser.new "development"
end
+ shared_examples_for 'a puppet parser' do
+ describe "when parsing comments before statement" do
+ it "should associate the documentation to the statement AST node" do
+ if Puppet[:parser] == 'future'
+ pending "egrammar does not yet process comments"
+ end
+ ast = @parser.parse("""
+ # comment
+ class test {}
+ """)
- describe "when parsing" do
- it "should be able to parse normal left to right relationships" do
- "Notify[foo] -> Notify[bar]".should parse_as(Puppet::Parser::AST::Relationship)
+ ast.code[0].should be_a(Puppet::Parser::AST::Hostclass)
+ ast.code[0].name.should == 'test'
+ ast.code[0].instantiate('')[0].doc.should == "comment\n"
+ end
end
- it "should be able to parse right to left relationships" do
- "Notify[foo] <- Notify[bar]".should parse_as(Puppet::Parser::AST::Relationship)
- end
+ describe "when parsing" do
+ it "should be able to parse normal left to right relationships" do
+ "Notify[foo] -> Notify[bar]".should parse_as(Puppet::Parser::AST::Relationship)
+ end
- it "should be able to parse normal left to right subscriptions" do
- "Notify[foo] ~> Notify[bar]".should parse_as(Puppet::Parser::AST::Relationship)
- end
+ it "should be able to parse right to left relationships" do
+ "Notify[foo] <- Notify[bar]".should parse_as(Puppet::Parser::AST::Relationship)
+ end
- it "should be able to parse right to left subscriptions" do
- "Notify[foo] <~ Notify[bar]".should parse_as(Puppet::Parser::AST::Relationship)
- end
+ it "should be able to parse normal left to right subscriptions" do
+ "Notify[foo] ~> Notify[bar]".should parse_as(Puppet::Parser::AST::Relationship)
+ end
+
+ it "should be able to parse right to left subscriptions" do
+ "Notify[foo] <~ Notify[bar]".should parse_as(Puppet::Parser::AST::Relationship)
+ end
- it "should correctly set the arrow type of a relationship" do
- "Notify[foo] <~ Notify[bar]".should parse_with { |rel| rel.arrow == "<~" }
+ it "should correctly set the arrow type of a relationship" do
+ "Notify[foo] <~ Notify[bar]".should parse_with { |rel| rel.arrow == "<~" }
+ end
+
+ it "should be able to parse deep hash access" do
+ %q{
+ $hash = { 'a' => { 'b' => { 'c' => 'it works' } } }
+ $out = $hash['a']['b']['c']
+ }.should parse_with { |v| v.value.is_a?(Puppet::Parser::AST::ASTHash) }
+ end
+
+ it "should fail if asked to parse '$foo::::bar'" do
+ expect { @parser.parse("$foo::::bar") }.to raise_error(Puppet::ParseError, /Syntax error at ':'/)
+ end
+
+ describe "function calls" do
+ it "should be able to pass an array to a function" do
+ "my_function([1,2,3])".should parse_with { |fun|
+ fun.is_a?(Puppet::Parser::AST::Function) &&
+ fun.arguments[0].evaluate(stub 'scope') == ['1','2','3']
+ }
+ end
+
+ it "should be able to pass a hash to a function" do
+ "my_function({foo => bar})".should parse_with { |fun|
+ fun.is_a?(Puppet::Parser::AST::Function) &&
+ fun.arguments[0].evaluate(stub 'scope') == {'foo' => 'bar'}
+ }
+ end
+ end
+
+ describe "collections" do
+ it "should find resources according to an expression" do
+ %q{ File <| mode == 0700 + 0050 + 0050 |> }.should parse_with { |coll|
+ coll.is_a?(Puppet::Parser::AST::Collection) &&
+ coll.query.evaluate(stub 'scope').first == ["mode", "==", 0700 + 0050 + 0050]
+ }
+ end
+ end
end
+ end
- it "should be able to parse deep hash access" do
- %q{
- $hash = { 'a' => { 'b' => { 'c' => 'it works' } } }
- $out = $hash['a']['b']['c']
- }.should parse_with { |v| v.value.is_a?(Puppet::Parser::AST::ASTHash) }
+ describe 'using classic parser' do
+ before :each do
+ Puppet[:parser] = 'current'
end
+ it_behaves_like 'a puppet parser'
+ end
- it "should fail if asked to parse '$foo::::bar'" do
- expect { @parser.parse("$foo::::bar") }.to raise_error(Puppet::ParseError, /Syntax error at ':'/)
+ describe 'using future parser' do
+ before :each do
+ Puppet[:parser] = 'future'
end
+ it_behaves_like 'a puppet parser'
- describe "function calls" do
- it "should be able to pass an array to a function" do
- "my_function([1,2,3])".should parse_with { |fun|
- fun.is_a?(Puppet::Parser::AST::Function) &&
- fun.arguments[0].evaluate(stub 'scope') == ['1','2','3']
- }
+ context 'more detailed errors should be generated' do
+ before :each do
+ Puppet[:parser] = 'future'
+ @resource_type_collection = Puppet::Resource::TypeCollection.new("env")
+ @parser = Puppet::Parser::ParserFactory.parser("development")
+ end
+
+ it 'should flag illegal type references' do
+ source = <<-SOURCE.gsub(/^ {8}/,'')
+ 1+1 { "title": }
+ SOURCE
+ # This error message is currently produced by the parser, and is not as detailed as desired
+ # It references position 16 at the closing '}'
+ expect { @parser.parse(source) }.to raise_error(/Expression is not valid as a resource.*line 1:16/)
+ end
+
+ it 'should flag illegal type references and get position correct' do
+ source = <<-SOURCE.gsub(/^ {8}/,'')
+ 1+1 { "title":
+ }
+ SOURCE
+ # This error message is currently produced by the parser, and is not as detailed as desired
+ # It references position 16 at the closing '}'
+ expect { @parser.parse(source) }.to raise_error(/Expression is not valid as a resource.*line 2:3/)
end
- it "should be able to pass a hash to a function" do
- "my_function({foo => bar})".should parse_with { |fun|
- fun.is_a?(Puppet::Parser::AST::Function) &&
- fun.arguments[0].evaluate(stub 'scope') == {'foo' => 'bar'}
+ it 'should flag illegal use of non r-value producing if' do
+ source = <<-SOURCE.gsub(/^ {8}/,'')
+ $a = if true {
+ false
}
+ SOURCE
+ expect { @parser.parse(source) }.to raise_error(/An 'if' statement does not produce a value at line 1:6/)
end
- end
- describe "collections" do
- it "should find resources according to an expression" do
- %q{ File <| mode == 0700 + 0050 + 0050 |> }.should parse_with { |coll|
- coll.is_a?(Puppet::Parser::AST::Collection) &&
- coll.query.evaluate(stub 'scope').first == ["mode", "==", 0700 + 0050 + 0050]
+ it 'should flag illegal use of non r-value producing case' do
+ source = <<-SOURCE.gsub(/^ {8}/,'')
+ $a = case true {
+ false :{ }
}
+ SOURCE
+ expect { @parser.parse(source) }.to raise_error(/A 'case' statement does not produce a value at line 1:6/)
+ end
+
+ it 'should flag illegal use of non r-value producing <| |>' do
+ expect { @parser.parse("$a = file <| |>") }.to raise_error(/A Virtual Query does not produce a value at line 1:6/)
+ end
+
+ it 'should flag illegal use of non r-value producing <<| |>>' do
+ expect { @parser.parse("$a = file <<| |>>") }.to raise_error(/An Exported Query does not produce a value at line 1:6/)
+ end
+
+ it 'should flag illegal use of non r-value producing define' do
+ Puppet.expects(:err).with("Invalid use of expression. A 'define' expression does not produce a value at line 1:6")
+ Puppet.expects(:err).with("Classes, definitions, and nodes may only appear at toplevel or inside other classes at line 1:6")
+ expect { @parser.parse("$a = define foo { }") }.to raise_error(/2 errors/)
+ end
+
+ it 'should flag illegal use of non r-value producing class' do
+ Puppet.expects(:err).with("Invalid use of expression. A Host Class Definition does not produce a value at line 1:6")
+ Puppet.expects(:err).with("Classes, definitions, and nodes may only appear at toplevel or inside other classes at line 1:6")
+ expect { @parser.parse("$a = class foo { }") }.to raise_error(/2 errors/)
+ end
+
+ it 'unclosed quote should be flagged for start position of string' do
+ source = <<-SOURCE.gsub(/^ {8}/,'')
+ $a = "xx
+ yyy
+ SOURCE
+ expect { @parser.parse(source) }.to raise_error(/Unclosed quote after '"' followed by 'xx\\nyy\.\.\.' at line 1:6/)
+ end
+
+ it 'can produce multiple errors and raise a summary exception' do
+ source = <<-SOURCE.gsub(/^ {8}/,'')
+ $a = node x { }
+ SOURCE
+ Puppet.expects(:err).with("Invalid use of expression. A Node Definition does not produce a value at line 1:6")
+ Puppet.expects(:err).with("Classes, definitions, and nodes may only appear at toplevel or inside other classes at line 1:6")
+ expect { @parser.parse(source) }.to raise_error(/2 errors/)
+ end
+
+ it 'can produce detailed error for a bad hostname' do
+ source = <<-SOURCE.gsub(/^ {8}/,'')
+ node 'macbook+owned+by+name' { }
+ SOURCE
+ expect { @parser.parse(source) }.to raise_error(/The hostname 'macbook\+owned\+by\+name' contains illegal characters.*at line 1:6/)
+ end
+
+ it 'can produce detailed error for a hostname with interpolation' do
+ source = <<-SOURCE.gsub(/^ {8}/,'')
+ $name = 'fred'
+ node "macbook-owned-by$name" { }
+ SOURCE
+ expect { @parser.parse(source) }.to raise_error(/An interpolated expression is not allowed in a hostname of a node at line 2:24/)
end
end
end
diff --git a/spec/integration/parser/scope_spec.rb b/spec/integration/parser/scope_spec.rb
index 66d29a4..8df5439 100644
--- a/spec/integration/parser/scope_spec.rb
+++ b/spec/integration/parser/scope_spec.rb
@@ -182,7 +182,7 @@ class bar inherits foo {
class c {
notify { 'something': message => "$a::b" }
}
-
+
class a { }
node default {
diff --git a/spec/unit/node/environment_spec.rb b/spec/unit/node/environment_spec.rb
index f7aacc2..754be7b 100755
--- a/spec/unit/node/environment_spec.rb
+++ b/spec/unit/node/environment_spec.rb
@@ -6,6 +6,7 @@
require 'puppet/node/environment'
require 'puppet/util/execution'
require 'puppet_spec/modules'
+require 'puppet/parser/parser_factory'
describe Puppet::Node::Environment do
let(:env) { Puppet::Node::Environment.new("testing") }
@@ -15,438 +16,454 @@
Puppet::Node::Environment.clear
end
- it "should use the filetimeout for the ttl for the modulepath" do
- Puppet::Node::Environment.attr_ttl(:modulepath).should == Integer(Puppet[:filetimeout])
- end
-
- it "should use the filetimeout for the ttl for the module list" do
- Puppet::Node::Environment.attr_ttl(:modules).should == Integer(Puppet[:filetimeout])
- end
-
- it "should use the default environment if no name is provided while initializing an environment" do
- Puppet[:environment] = "one"
- Puppet::Node::Environment.new.name.should == :one
- end
-
- it "should treat environment instances as singletons" do
- Puppet::Node::Environment.new("one").should equal(Puppet::Node::Environment.new("one"))
- end
-
- it "should treat an environment specified as names or strings as equivalent" do
- Puppet::Node::Environment.new(:one).should equal(Puppet::Node::Environment.new("one"))
- end
-
- it "should return its name when converted to a string" do
- Puppet::Node::Environment.new(:one).to_s.should == "one"
- end
-
- it "should just return any provided environment if an environment is provided as the name" do
- one = Puppet::Node::Environment.new(:one)
- Puppet::Node::Environment.new(one).should equal(one)
- end
-
- describe "when managing known resource types" do
- before do
- @collection = Puppet::Resource::TypeCollection.new(env)
- env.stubs(:perform_initial_import).returns(Puppet::Parser::AST::Hostclass.new(''))
- Thread.current[:known_resource_types] = nil
- end
-
- it "should create a resource type collection if none exists" do
- Puppet::Resource::TypeCollection.expects(:new).with(env).returns @collection
- env.known_resource_types.should equal(@collection)
- end
-
- it "should reuse any existing resource type collection" do
- env.known_resource_types.should equal(env.known_resource_types)
- end
-
- it "should perform the initial import when creating a new collection" do
- env.expects(:perform_initial_import).returns(Puppet::Parser::AST::Hostclass.new(''))
- env.known_resource_types
- end
-
- it "should return the same collection even if stale if it's the same thread" do
- Puppet::Resource::TypeCollection.stubs(:new).returns @collection
- env.known_resource_types.stubs(:stale?).returns true
-
- env.known_resource_types.should equal(@collection)
+ shared_examples_for 'the environment' do
+ it "should use the filetimeout for the ttl for the modulepath" do
+ Puppet::Node::Environment.attr_ttl(:modulepath).should == Integer(Puppet[:filetimeout])
end
-
- it "should return the current thread associated collection if there is one" do
- Thread.current[:known_resource_types] = @collection
-
- env.known_resource_types.should equal(@collection)
+
+ it "should use the filetimeout for the ttl for the module list" do
+ Puppet::Node::Environment.attr_ttl(:modules).should == Integer(Puppet[:filetimeout])
end
-
- it "should give to all threads using the same environment the same collection if the collection isn't stale" do
- @original_thread_type_collection = Puppet::Resource::TypeCollection.new(env)
- Puppet::Resource::TypeCollection.expects(:new).with(env).returns @original_thread_type_collection
- env.known_resource_types.should equal(@original_thread_type_collection)
-
- @original_thread_type_collection.expects(:require_reparse?).returns(false)
- Puppet::Resource::TypeCollection.stubs(:new).with(env).returns @collection
-
- t = Thread.new {
- env.known_resource_types.should equal(@original_thread_type_collection)
- }
- t.join
- end
-
- it "should generate a new TypeCollection if the current one requires reparsing" do
- old_type_collection = env.known_resource_types
- old_type_collection.stubs(:require_reparse?).returns true
- Thread.current[:known_resource_types] = nil
- new_type_collection = env.known_resource_types
-
- new_type_collection.should be_a Puppet::Resource::TypeCollection
- new_type_collection.should_not equal(old_type_collection)
- end
- end
-
- it "should validate the modulepath directories" do
- real_file = tmpdir('moduledir')
- path = %W[/one /two #{real_file}].join(File::PATH_SEPARATOR)
-
- Puppet[:modulepath] = path
-
- env.modulepath.should == [real_file]
- end
-
- it "should prefix the value of the 'PUPPETLIB' environment variable to the module path if present" do
- Puppet::Util.withenv("PUPPETLIB" => %w{/l1 /l2}.join(File::PATH_SEPARATOR)) do
- module_path = %w{/one /two}.join(File::PATH_SEPARATOR)
- env.expects(:validate_dirs).with(%w{/l1 /l2 /one /two}).returns %w{/l1 /l2 /one /two}
- env.expects(:[]).with(:modulepath).returns module_path
-
- env.modulepath.should == %w{/l1 /l2 /one /two}
- end
- end
-
- describe "when validating modulepath or manifestdir directories" do
- before :each do
- @path_one = tmpdir("path_one")
- @path_two = tmpdir("path_one")
- sep = File::PATH_SEPARATOR
- Puppet[:modulepath] = "#{@path_one}#{sep}#{@path_two}"
+
+ it "should use the default environment if no name is provided while initializing an environment" do
+ Puppet[:environment] = "one"
+ Puppet::Node::Environment.new.name.should == :one
end
-
- it "should not return non-directories" do
- FileTest.expects(:directory?).with(@path_one).returns true
- FileTest.expects(:directory?).with(@path_two).returns false
-
- env.validate_dirs([@path_one, @path_two]).should == [@path_one]
+
+ it "should treat environment instances as singletons" do
+ Puppet::Node::Environment.new("one").should equal(Puppet::Node::Environment.new("one"))
end
-
- it "should use the current working directory to fully-qualify unqualified paths" do
- FileTest.stubs(:directory?).returns true
-
- two = File.expand_path(File.join(Dir.getwd, "two"))
- env.validate_dirs([@path_one, 'two']).should == [@path_one, two]
+
+ it "should treat an environment specified as names or strings as equivalent" do
+ Puppet::Node::Environment.new(:one).should equal(Puppet::Node::Environment.new("one"))
end
- end
-
- describe "when modeling a specific environment" do
- it "should have a method for returning the environment name" do
- Puppet::Node::Environment.new("testing").name.should == :testing
+
+ it "should return its name when converted to a string" do
+ Puppet::Node::Environment.new(:one).to_s.should == "one"
end
-
- it "should provide an array-like accessor method for returning any environment-specific setting" do
- env.should respond_to(:[])
+
+ it "should just return any provided environment if an environment is provided as the name" do
+ one = Puppet::Node::Environment.new(:one)
+ Puppet::Node::Environment.new(one).should equal(one)
end
-
- it "should ask the Puppet settings instance for the setting qualified with the environment name" do
- Puppet.settings.set_value(:server, "myval", :testing)
- env[:server].should == "myval"
+
+ describe "when managing known resource types" do
+ before do
+ @collection = Puppet::Resource::TypeCollection.new(env)
+ env.stubs(:perform_initial_import).returns(Puppet::Parser::AST::Hostclass.new(''))
+ Thread.current[:known_resource_types] = nil
+ end
+
+ it "should create a resource type collection if none exists" do
+ Puppet::Resource::TypeCollection.expects(:new).with(env).returns @collection
+ env.known_resource_types.should equal(@collection)
+ end
+
+ it "should reuse any existing resource type collection" do
+ env.known_resource_types.should equal(env.known_resource_types)
+ end
+
+ it "should perform the initial import when creating a new collection" do
+ env.expects(:perform_initial_import).returns(Puppet::Parser::AST::Hostclass.new(''))
+ env.known_resource_types
+ end
+
+ it "should return the same collection even if stale if it's the same thread" do
+ Puppet::Resource::TypeCollection.stubs(:new).returns @collection
+ env.known_resource_types.stubs(:stale?).returns true
+
+ env.known_resource_types.should equal(@collection)
+ end
+
+ it "should return the current thread associated collection if there is one" do
+ Thread.current[:known_resource_types] = @collection
+
+ env.known_resource_types.should equal(@collection)
+ end
+
+ it "should give to all threads using the same environment the same collection if the collection isn't stale" do
+ @original_thread_type_collection = Puppet::Resource::TypeCollection.new(env)
+ Puppet::Resource::TypeCollection.expects(:new).with(env).returns @original_thread_type_collection
+ env.known_resource_types.should equal(@original_thread_type_collection)
+
+ @original_thread_type_collection.expects(:require_reparse?).returns(false)
+ Puppet::Resource::TypeCollection.stubs(:new).with(env).returns @collection
+
+ t = Thread.new {
+ env.known_resource_types.should equal(@original_thread_type_collection)
+ }
+ t.join
+ end
+
+ it "should generate a new TypeCollection if the current one requires reparsing" do
+ old_type_collection = env.known_resource_types
+ old_type_collection.stubs(:require_reparse?).returns true
+ Thread.current[:known_resource_types] = nil
+ new_type_collection = env.known_resource_types
+
+ new_type_collection.should be_a Puppet::Resource::TypeCollection
+ new_type_collection.should_not equal(old_type_collection)
+ end
end
-
- it "should be able to return an individual module that exists in its module path" do
- env.stubs(:modules).returns [Puppet::Module.new('one', "/one", mock("env"))]
-
- mod = env.module('one')
- mod.should be_a(Puppet::Module)
- mod.name.should == 'one'
+
+ it "should validate the modulepath directories" do
+ real_file = tmpdir('moduledir')
+ path = %W[/one /two #{real_file}].join(File::PATH_SEPARATOR)
+
+ Puppet[:modulepath] = path
+
+ env.modulepath.should == [real_file]
end
-
- it "should not return a module if the module doesn't exist" do
- env.stubs(:modules).returns [Puppet::Module.new('one', "/one", mock("env"))]
-
- env.module('two').should be_nil
+
+ it "should prefix the value of the 'PUPPETLIB' environment variable to the module path if present" do
+ Puppet::Util.withenv("PUPPETLIB" => %w{/l1 /l2}.join(File::PATH_SEPARATOR)) do
+ module_path = %w{/one /two}.join(File::PATH_SEPARATOR)
+ env.expects(:validate_dirs).with(%w{/l1 /l2 /one /two}).returns %w{/l1 /l2 /one /two}
+ env.expects(:[]).with(:modulepath).returns module_path
+
+ env.modulepath.should == %w{/l1 /l2 /one /two}
+ end
end
-
- it "should return nil if asked for a module that does not exist in its path" do
- modpath = tmpdir('modpath')
- env.modulepath = [modpath]
-
- env.module("one").should be_nil
+
+ describe "when validating modulepath or manifestdir directories" do
+ before :each do
+ @path_one = tmpdir("path_one")
+ @path_two = tmpdir("path_one")
+ sep = File::PATH_SEPARATOR
+ Puppet[:modulepath] = "#{@path_one}#{sep}#{@path_two}"
+ end
+
+ it "should not return non-directories" do
+ FileTest.expects(:directory?).with(@path_one).returns true
+ FileTest.expects(:directory?).with(@path_two).returns false
+
+ env.validate_dirs([@path_one, @path_two]).should == [@path_one]
+ end
+
+ it "should use the current working directory to fully-qualify unqualified paths" do
+ FileTest.stubs(:directory?).returns true
+
+ two = File.expand_path(File.join(Dir.getwd, "two"))
+ env.validate_dirs([@path_one, 'two']).should == [@path_one, two]
+ end
end
-
- describe "module data" do
- before do
- dir = tmpdir("deep_path")
-
- @first = File.join(dir, "first")
- @second = File.join(dir, "second")
- Puppet[:modulepath] = "#{@first}#{File::PATH_SEPARATOR}#{@second}"
-
- FileUtils.mkdir_p(@first)
- FileUtils.mkdir_p(@second)
+
+ describe "when modeling a specific environment" do
+ it "should have a method for returning the environment name" do
+ Puppet::Node::Environment.new("testing").name.should == :testing
end
-
- describe "#modules_by_path" do
- it "should return an empty list if there are no modules" do
- env.modules_by_path.should == {
- @first => [],
- @second => []
- }
- end
-
- it "should include modules even if they exist in multiple dirs in the modulepath" do
- modpath1 = File.join(@first, "foo")
- FileUtils.mkdir_p(modpath1)
- modpath2 = File.join(@second, "foo")
- FileUtils.mkdir_p(modpath2)
-
- env.modules_by_path.should == {
- @first => [Puppet::Module.new('foo', modpath1, env)],
- @second => [Puppet::Module.new('foo', modpath2, env)]
- }
- end
-
- it "should ignore modules with invalid names" do
- FileUtils.mkdir_p(File.join(@first, 'foo'))
- FileUtils.mkdir_p(File.join(@first, 'foo2'))
- FileUtils.mkdir_p(File.join(@first, 'foo-bar'))
- FileUtils.mkdir_p(File.join(@first, 'foo_bar'))
- FileUtils.mkdir_p(File.join(@first, 'foo=bar'))
- FileUtils.mkdir_p(File.join(@first, 'foo bar'))
- FileUtils.mkdir_p(File.join(@first, 'foo.bar'))
- FileUtils.mkdir_p(File.join(@first, '-foo'))
- FileUtils.mkdir_p(File.join(@first, 'foo-'))
- FileUtils.mkdir_p(File.join(@first, 'foo--bar'))
-
- env.modules_by_path[@first].collect{|mod| mod.name}.sort.should == %w{foo foo-bar foo2 foo_bar}
- end
-
+
+ it "should provide an array-like accessor method for returning any environment-specific setting" do
+ env.should respond_to(:[])
end
-
- describe "#module_requirements" do
- it "should return a list of what modules depend on other modules" do
- PuppetSpec::Modules.create(
- 'foo',
- @first,
- :metadata => {
- :author => 'puppetlabs',
- :dependencies => [{ 'name' => 'puppetlabs/bar', "version_requirement" => ">= 1.0.0" }]
- }
- )
- PuppetSpec::Modules.create(
- 'bar',
- @second,
- :metadata => {
- :author => 'puppetlabs',
- :dependencies => [{ 'name' => 'puppetlabs/foo', "version_requirement" => "<= 2.0.0" }]
- }
- )
- PuppetSpec::Modules.create(
- 'baz',
- @first,
- :metadata => {
- :author => 'puppetlabs',
- :dependencies => [{ 'name' => 'puppetlabs/bar', "version_requirement" => "3.0.0" }]
- }
- )
- PuppetSpec::Modules.create(
- 'alpha',
- @first,
- :metadata => {
- :author => 'puppetlabs',
- :dependencies => [{ 'name' => 'puppetlabs/bar', "version_requirement" => "~3.0.0" }]
- }
- )
-
- env.module_requirements.should == {
- 'puppetlabs/alpha' => [],
- 'puppetlabs/foo' => [
- {
- "name" => "puppetlabs/bar",
- "version" => "9.9.9",
- "version_requirement" => "<= 2.0.0"
- }
- ],
- 'puppetlabs/bar' => [
- {
- "name" => "puppetlabs/alpha",
- "version" => "9.9.9",
- "version_requirement" => "~3.0.0"
- },
- {
- "name" => "puppetlabs/baz",
- "version" => "9.9.9",
- "version_requirement" => "3.0.0"
- },
- {
- "name" => "puppetlabs/foo",
- "version" => "9.9.9",
- "version_requirement" => ">= 1.0.0"
- }
- ],
- 'puppetlabs/baz' => []
- }
- end
+
+ it "should ask the Puppet settings instance for the setting qualified with the environment name" do
+ Puppet.settings.set_value(:server, "myval", :testing)
+ env[:server].should == "myval"
end
-
- describe ".module_by_forge_name" do
- it "should find modules by forge_name" do
- mod = PuppetSpec::Modules.create(
- 'baz',
- @first,
- :metadata => {:author => 'puppetlabs'},
- :environment => env
- )
- env.module_by_forge_name('puppetlabs/baz').should == mod
- end
-
- it "should not find modules with same name by the wrong author" do
- mod = PuppetSpec::Modules.create(
- 'baz',
- @first,
- :metadata => {:author => 'sneakylabs'},
- :environment => env
- )
- env.module_by_forge_name('puppetlabs/baz').should == nil
- end
-
- it "should return nil when the module can't be found" do
- env.module_by_forge_name('ima/nothere').should be_nil
- end
+
+ it "should be able to return an individual module that exists in its module path" do
+ env.stubs(:modules).returns [Puppet::Module.new('one', "/one", mock("env"))]
+
+ mod = env.module('one')
+ mod.should be_a(Puppet::Module)
+ mod.name.should == 'one'
end
-
- describe ".modules" do
- it "should return an empty list if there are no modules" do
- env.modules.should == []
+
+ it "should not return a module if the module doesn't exist" do
+ env.stubs(:modules).returns [Puppet::Module.new('one', "/one", mock("env"))]
+
+ env.module('two').should be_nil
+ end
+
+ it "should return nil if asked for a module that does not exist in its path" do
+ modpath = tmpdir('modpath')
+ env.modulepath = [modpath]
+
+ env.module("one").should be_nil
+ end
+
+ describe "module data" do
+ before do
+ dir = tmpdir("deep_path")
+
+ @first = File.join(dir, "first")
+ @second = File.join(dir, "second")
+ Puppet[:modulepath] = "#{@first}#{File::PATH_SEPARATOR}#{@second}"
+
+ FileUtils.mkdir_p(@first)
+ FileUtils.mkdir_p(@second)
end
-
- it "should return a module named for every directory in each module path" do
- %w{foo bar}.each do |mod_name|
- FileUtils.mkdir_p(File.join(@first, mod_name))
+
+ describe "#modules_by_path" do
+ it "should return an empty list if there are no modules" do
+ env.modules_by_path.should == {
+ @first => [],
+ @second => []
+ }
end
- %w{bee baz}.each do |mod_name|
- FileUtils.mkdir_p(File.join(@second, mod_name))
+
+ it "should include modules even if they exist in multiple dirs in the modulepath" do
+ modpath1 = File.join(@first, "foo")
+ FileUtils.mkdir_p(modpath1)
+ modpath2 = File.join(@second, "foo")
+ FileUtils.mkdir_p(modpath2)
+
+ env.modules_by_path.should == {
+ @first => [Puppet::Module.new('foo', modpath1, env)],
+ @second => [Puppet::Module.new('foo', modpath2, env)]
+ }
+ end
+
+ it "should ignore modules with invalid names" do
+ FileUtils.mkdir_p(File.join(@first, 'foo'))
+ FileUtils.mkdir_p(File.join(@first, 'foo2'))
+ FileUtils.mkdir_p(File.join(@first, 'foo-bar'))
+ FileUtils.mkdir_p(File.join(@first, 'foo_bar'))
+ FileUtils.mkdir_p(File.join(@first, 'foo=bar'))
+ FileUtils.mkdir_p(File.join(@first, 'foo bar'))
+ FileUtils.mkdir_p(File.join(@first, 'foo.bar'))
+ FileUtils.mkdir_p(File.join(@first, '-foo'))
+ FileUtils.mkdir_p(File.join(@first, 'foo-'))
+ FileUtils.mkdir_p(File.join(@first, 'foo--bar'))
+
+ env.modules_by_path[@first].collect{|mod| mod.name}.sort.should == %w{foo foo-bar foo2 foo_bar}
end
- env.modules.collect{|mod| mod.name}.sort.should == %w{foo bar bee baz}.sort
+
end
-
- it "should remove duplicates" do
- FileUtils.mkdir_p(File.join(@first, 'foo'))
- FileUtils.mkdir_p(File.join(@second, 'foo'))
-
- env.modules.collect{|mod| mod.name}.sort.should == %w{foo}
+
+ describe "#module_requirements" do
+ it "should return a list of what modules depend on other modules" do
+ PuppetSpec::Modules.create(
+ 'foo',
+ @first,
+ :metadata => {
+ :author => 'puppetlabs',
+ :dependencies => [{ 'name' => 'puppetlabs/bar', "version_requirement" => ">= 1.0.0" }]
+ }
+ )
+ PuppetSpec::Modules.create(
+ 'bar',
+ @second,
+ :metadata => {
+ :author => 'puppetlabs',
+ :dependencies => [{ 'name' => 'puppetlabs/foo', "version_requirement" => "<= 2.0.0" }]
+ }
+ )
+ PuppetSpec::Modules.create(
+ 'baz',
+ @first,
+ :metadata => {
+ :author => 'puppetlabs',
+ :dependencies => [{ 'name' => 'puppetlabs/bar', "version_requirement" => "3.0.0" }]
+ }
+ )
+ PuppetSpec::Modules.create(
+ 'alpha',
+ @first,
+ :metadata => {
+ :author => 'puppetlabs',
+ :dependencies => [{ 'name' => 'puppetlabs/bar', "version_requirement" => "~3.0.0" }]
+ }
+ )
+
+ env.module_requirements.should == {
+ 'puppetlabs/alpha' => [],
+ 'puppetlabs/foo' => [
+ {
+ "name" => "puppetlabs/bar",
+ "version" => "9.9.9",
+ "version_requirement" => "<= 2.0.0"
+ }
+ ],
+ 'puppetlabs/bar' => [
+ {
+ "name" => "puppetlabs/alpha",
+ "version" => "9.9.9",
+ "version_requirement" => "~3.0.0"
+ },
+ {
+ "name" => "puppetlabs/baz",
+ "version" => "9.9.9",
+ "version_requirement" => "3.0.0"
+ },
+ {
+ "name" => "puppetlabs/foo",
+ "version" => "9.9.9",
+ "version_requirement" => ">= 1.0.0"
+ }
+ ],
+ 'puppetlabs/baz' => []
+ }
+ end
end
-
- it "should ignore modules with invalid names" do
- FileUtils.mkdir_p(File.join(@first, 'foo'))
- FileUtils.mkdir_p(File.join(@first, 'foo2'))
- FileUtils.mkdir_p(File.join(@first, 'foo-bar'))
- FileUtils.mkdir_p(File.join(@first, 'foo_bar'))
- FileUtils.mkdir_p(File.join(@first, 'foo=bar'))
- FileUtils.mkdir_p(File.join(@first, 'foo bar'))
-
- env.modules.collect{|mod| mod.name}.sort.should == %w{foo foo-bar foo2 foo_bar}
+
+ describe ".module_by_forge_name" do
+ it "should find modules by forge_name" do
+ mod = PuppetSpec::Modules.create(
+ 'baz',
+ @first,
+ :metadata => {:author => 'puppetlabs'},
+ :environment => env
+ )
+ env.module_by_forge_name('puppetlabs/baz').should == mod
+ end
+
+ it "should not find modules with same name by the wrong author" do
+ mod = PuppetSpec::Modules.create(
+ 'baz',
+ @first,
+ :metadata => {:author => 'sneakylabs'},
+ :environment => env
+ )
+ env.module_by_forge_name('puppetlabs/baz').should == nil
+ end
+
+ it "should return nil when the module can't be found" do
+ env.module_by_forge_name('ima/nothere').should be_nil
+ end
end
-
- it "should create modules with the correct environment" do
- FileUtils.mkdir_p(File.join(@first, 'foo'))
- env.modules.each {|mod| mod.environment.should == env }
+
+ describe ".modules" do
+ it "should return an empty list if there are no modules" do
+ env.modules.should == []
+ end
+
+ it "should return a module named for every directory in each module path" do
+ %w{foo bar}.each do |mod_name|
+ FileUtils.mkdir_p(File.join(@first, mod_name))
+ end
+ %w{bee baz}.each do |mod_name|
+ FileUtils.mkdir_p(File.join(@second, mod_name))
+ end
+ env.modules.collect{|mod| mod.name}.sort.should == %w{foo bar bee baz}.sort
+ end
+
+ it "should remove duplicates" do
+ FileUtils.mkdir_p(File.join(@first, 'foo'))
+ FileUtils.mkdir_p(File.join(@second, 'foo'))
+
+ env.modules.collect{|mod| mod.name}.sort.should == %w{foo}
+ end
+
+ it "should ignore modules with invalid names" do
+ FileUtils.mkdir_p(File.join(@first, 'foo'))
+ FileUtils.mkdir_p(File.join(@first, 'foo2'))
+ FileUtils.mkdir_p(File.join(@first, 'foo-bar'))
+ FileUtils.mkdir_p(File.join(@first, 'foo_bar'))
+ FileUtils.mkdir_p(File.join(@first, 'foo=bar'))
+ FileUtils.mkdir_p(File.join(@first, 'foo bar'))
+
+ env.modules.collect{|mod| mod.name}.sort.should == %w{foo foo-bar foo2 foo_bar}
+ end
+
+ it "should create modules with the correct environment" do
+ FileUtils.mkdir_p(File.join(@first, 'foo'))
+ env.modules.each {|mod| mod.environment.should == env }
+ end
+
end
-
+ end
+
+ it "should cache the module list" do
+ env.modulepath = %w{/a}
+ Dir.expects(:entries).once.with("/a").returns %w{foo}
+
+ env.modules
+ env.modules
end
end
-
- it "should cache the module list" do
- env.modulepath = %w{/a}
- Dir.expects(:entries).once.with("/a").returns %w{foo}
-
- env.modules
- env.modules
- end
- end
-
- describe Puppet::Node::Environment::Helper do
- before do
- @helper = Object.new
- @helper.extend(Puppet::Node::Environment::Helper)
- end
-
- it "should be able to set and retrieve the environment as a symbol" do
- @helper.environment = :foo
- @helper.environment.name.should == :foo
- end
-
- it "should accept an environment directly" do
- @helper.environment = Puppet::Node::Environment.new(:foo)
- @helper.environment.name.should == :foo
+
+ describe Puppet::Node::Environment::Helper do
+ before do
+ @helper = Object.new
+ @helper.extend(Puppet::Node::Environment::Helper)
+ end
+
+ it "should be able to set and retrieve the environment as a symbol" do
+ @helper.environment = :foo
+ @helper.environment.name.should == :foo
+ end
+
+ it "should accept an environment directly" do
+ @helper.environment = Puppet::Node::Environment.new(:foo)
+ @helper.environment.name.should == :foo
+ end
+
+ it "should accept an environment as a string" do
+ @helper.environment = 'foo'
+ @helper.environment.name.should == :foo
+ end
end
-
- it "should accept an environment as a string" do
- @helper.environment = 'foo'
- @helper.environment.name.should == :foo
+
+ describe "when performing initial import" do
+ before do
+ @parser = Puppet::Parser::ParserFactory.parser("test")
+# @parser = Puppet::Parser::EParserAdapter.new(Puppet::Parser::Parser.new("test")) # TODO: FIX PARSER FACTORY
+ Puppet::Parser::ParserFactory.stubs(:parser).returns @parser
+ end
+
+ it "should set the parser's string to the 'code' setting and parse if code is available" do
+ Puppet.settings[:code] = "my code"
+ @parser.expects(:string=).with "my code"
+ @parser.expects(:parse)
+ env.instance_eval { perform_initial_import }
+ end
+
+ it "should set the parser's file to the 'manifest' setting and parse if no code is available and the manifest is available" do
+ filename = tmpfile('myfile')
+ File.open(filename, 'w'){|f| }
+ Puppet.settings[:manifest] = filename
+ @parser.expects(:file=).with filename
+ @parser.expects(:parse)
+ env.instance_eval { perform_initial_import }
+ end
+
+ it "should pass the manifest file to the parser even if it does not exist on disk" do
+ filename = tmpfile('myfile')
+ Puppet.settings[:code] = ""
+ Puppet.settings[:manifest] = filename
+ @parser.expects(:file=).with(filename).once
+ @parser.expects(:parse).once
+ env.instance_eval { perform_initial_import }
+ end
+
+ it "should fail helpfully if there is an error importing" do
+ File.stubs(:exist?).returns true
+ env.stubs(:known_resource_types).returns Puppet::Resource::TypeCollection.new(env)
+ @parser.expects(:file=).once
+ @parser.expects(:parse).raises ArgumentError
+ lambda { env.instance_eval { perform_initial_import } }.should raise_error(Puppet::Error)
+ end
+
+ it "should not do anything if the ignore_import settings is set" do
+ Puppet.settings[:ignoreimport] = true
+ @parser.expects(:string=).never
+ @parser.expects(:file=).never
+ @parser.expects(:parse).never
+ env.instance_eval { perform_initial_import }
+ end
+
+ it "should mark the type collection as needing a reparse when there is an error parsing" do
+ @parser.expects(:parse).raises Puppet::ParseError.new("Syntax error at ...")
+ env.stubs(:known_resource_types).returns Puppet::Resource::TypeCollection.new(env)
+
+ lambda { env.instance_eval { perform_initial_import } }.should raise_error(Puppet::Error, /Syntax error at .../)
+ env.known_resource_types.require_reparse?.should be_true
+ end
end
end
-
- describe "when performing initial import" do
- before do
- @parser = Puppet::Parser::Parser.new("test")
- Puppet::Parser::Parser.stubs(:new).returns @parser
- end
-
- it "should set the parser's string to the 'code' setting and parse if code is available" do
- Puppet.settings[:code] = "my code"
- @parser.expects(:string=).with "my code"
- @parser.expects(:parse)
- env.instance_eval { perform_initial_import }
- end
-
- it "should set the parser's file to the 'manifest' setting and parse if no code is available and the manifest is available" do
- filename = tmpfile('myfile')
- File.open(filename, 'w'){|f| }
- Puppet.settings[:manifest] = filename
- @parser.expects(:file=).with filename
- @parser.expects(:parse)
- env.instance_eval { perform_initial_import }
- end
-
- it "should pass the manifest file to the parser even if it does not exist on disk" do
- filename = tmpfile('myfile')
- Puppet.settings[:code] = ""
- Puppet.settings[:manifest] = filename
- @parser.expects(:file=).with(filename).once
- @parser.expects(:parse).once
- env.instance_eval { perform_initial_import }
- end
-
- it "should fail helpfully if there is an error importing" do
- File.stubs(:exist?).returns true
- env.stubs(:known_resource_types).returns Puppet::Resource::TypeCollection.new(env)
- @parser.expects(:file=).once
- @parser.expects(:parse).raises ArgumentError
- lambda { env.instance_eval { perform_initial_import } }.should raise_error(Puppet::Error)
- end
-
- it "should not do anything if the ignore_import settings is set" do
- Puppet.settings[:ignoreimport] = true
- @parser.expects(:string=).never
- @parser.expects(:file=).never
- @parser.expects(:parse).never
- env.instance_eval { perform_initial_import }
+ describe 'with classic parser' do
+ before :each do
+ Puppet[:parser] = 'current'
end
-
- it "should mark the type collection as needing a reparse when there is an error parsing" do
- @parser.expects(:parse).raises Puppet::ParseError.new("Syntax error at ...")
- env.stubs(:known_resource_types).returns Puppet::Resource::TypeCollection.new(env)
-
- lambda { env.instance_eval { perform_initial_import } }.should raise_error(Puppet::Error, /Syntax error at .../)
- env.known_resource_types.require_reparse?.should be_true
+ it_behaves_like 'the environment'
+ end
+ describe 'with future parser' do
+ before :each do
+ Puppet[:parser] = 'future'
end
+ it_behaves_like 'the environment'
end
+
end
diff --git a/spec/unit/parser/ast/arithmetic_operator_spec.rb b/spec/unit/parser/ast/arithmetic_operator_spec.rb
index ae42fc9..cdd01e3 100755
--- a/spec/unit/parser/ast/arithmetic_operator_spec.rb
+++ b/spec/unit/parser/ast/arithmetic_operator_spec.rb
@@ -61,4 +61,100 @@
operator.evaluate(@scope).should == 4.33
end
+ context "when applied to array" do
+ before :each do
+ Puppet[:parser] = 'future'
+ end
+
+ it "+ should concatenate an array" do
+ one = stub 'one', :safeevaluate => [1,2,3]
+ two = stub 'two', :safeevaluate => [4,5]
+ operator = ast::ArithmeticOperator.new :lval => one, :operator => "+", :rval => two
+ operator.evaluate(@scope).should == [1,2,3,4,5]
+ end
+
+ it "<< should append array to an array" do
+ one = stub 'one', :safeevaluate => [1,2,3]
+ two = stub 'two', :safeevaluate => [4,5]
+ operator = ast::ArithmeticOperator.new :lval => one, :operator => "<<", :rval => two
+ operator.evaluate(@scope).should == [1,2,3, [4,5]]
+ end
+
+ it "<< should append object to an array" do
+ one = stub 'one', :safeevaluate => [1,2,3]
+ two = stub 'two', :safeevaluate => 'a b c'
+ operator = ast::ArithmeticOperator.new :lval => one, :operator => "<<", :rval => two
+ operator.evaluate(@scope).should == [1,2,3, 'a b c']
+ end
+
+ context "and input is invalid" do
+ it "should raise error for + if left is not an array" do
+ one = stub 'one', :safeevaluate => 4
+ two = stub 'two', :safeevaluate => [4,5]
+ operator = ast::ArithmeticOperator.new :lval => one, :operator => "+", :rval => two
+ lambda { operator.evaluate(@scope).should == [1,2,3,4,5] }.should raise_error(/left/)
+ end
+
+ it "should raise error for << if left is not an array" do
+ one = stub 'one', :safeevaluate => 4
+ two = stub 'two', :safeevaluate => [4,5]
+ operator = ast::ArithmeticOperator.new :lval => one, :operator => "<<", :rval => two
+ lambda { operator.evaluate(@scope).should == [1,2,3,4,5] }.should raise_error(/left/)
+ end
+
+ it "should raise error for + if right is not an array" do
+ one = stub 'one', :safeevaluate => [1,2]
+ two = stub 'two', :safeevaluate => 45
+ operator = ast::ArithmeticOperator.new :lval => one, :operator => "+", :rval => two
+ lambda { operator.evaluate(@scope).should == [1,2,3,4,5] }.should raise_error(/right/)
+ end
+
+ %w{ - * / % >>}.each do |op|
+ it "should raise error for '#{op}'" do
+ one = stub 'one', :safeevaluate => [1,2,3]
+ two = stub 'two', :safeevaluate => [4,5]
+ operator = ast::ArithmeticOperator.new :lval => @one, :operator => op, :rval => @two
+ lambda { operator.evaluate(@scope).should == [1,2,3,4,5] }.should raise_error
+ end
+ end
+ end
+
+ context "when applied to hash" do
+ before :each do
+ Puppet[:parser] = 'future'
+ end
+
+ it "+ should merge two hashes" do
+ one = stub 'one', :safeevaluate => {'a' => 1, 'b' => 2}
+ two = stub 'two', :safeevaluate => {'c' => 3 }
+ operator = ast::ArithmeticOperator.new :lval => one, :operator => "+", :rval => two
+ operator.evaluate(@scope).should == {'a' => 1, 'b' => 2, 'c' => 3}
+ end
+
+ context "and input is invalid" do
+ it "should raise error for + if left is not a hash" do
+ one = stub 'one', :safeevaluate => 4
+ two = stub 'two', :safeevaluate => {'a' => 1}
+ operator = ast::ArithmeticOperator.new :lval => one, :operator => "+", :rval => two
+ lambda { operator.evaluate(@scope).should == [1,2,3,4,5] }.should raise_error(/left/)
+ end
+
+ it "should raise error for + if right is not a hash" do
+ one = stub 'one', :safeevaluate => {'a' => 1}
+ two = stub 'two', :safeevaluate => 1
+ operator = ast::ArithmeticOperator.new :lval => one, :operator => "+", :rval => two
+ lambda { operator.evaluate(@scope).should == {'a'=>1, 1=>nil} }.should raise_error(/right/)
+ end
+
+ %w{ - * / % << >>}.each do |op|
+ it "should raise error for '#{op}'" do
+ one = stub 'one', :safeevaluate => {'a' => 1, 'b' => 2}
+ two = stub 'two', :safeevaluate => {'c' => 3 }
+ operator = ast::ArithmeticOperator.new :lval => @one, :operator => op, :rval => @two
+ lambda { operator.evaluate(@scope).should == [1,2,3,4,5] }.should raise_error
+ end
+ end
+ end
+ end
+ end
end
diff --git a/spec/unit/parser/compiler_spec.rb b/spec/unit/parser/compiler_spec.rb
index 2c4b522..d64e57e 100755
--- a/spec/unit/parser/compiler_spec.rb
+++ b/spec/unit/parser/compiler_spec.rb
@@ -152,7 +152,7 @@ def resource(type, title)
it "should transform node class hashes into a class list" do
node = Puppet::Node.new("mynode")
- node.classes = {'foo'=>{'one'=>'1'}, 'bar'=>{'two'=>'2'}}
+ node.classes = {'foo'=>{'one'=>'p1'}, 'bar'=>{'two'=>'p2'}}
compiler = Puppet::Parser::Compiler.new(node)
compiler.classlist.should =~ ['foo', 'bar']
@@ -231,7 +231,7 @@ def compile_stub(*except)
end
it "should evaluate any parameterized classes named in the node" do
- classes = {'foo'=>{'1'=>'one'}, 'bar'=>{'2'=>'two'}}
+ classes = {'foo'=>{'p1'=>'one'}, 'bar'=>{'p2'=>'two'}}
@node.stubs(:classes).returns(classes)
@compiler.expects(:evaluate_classes).with(classes, @compiler.topscope)
@compiler.compile
@@ -609,7 +609,7 @@ def add_resource(name, parent = nil)
# Define the given class with default parameters
def define_class(name, parameters)
@node.classes[name] = parameters
- klass = Puppet::Resource::Type.new(:hostclass, name, :arguments => {'1' => @ast_obj, '2' => @ast_obj})
+ klass = Puppet::Resource::Type.new(:hostclass, name, :arguments => {'p1' => @ast_obj, 'p2' => @ast_obj})
@compiler.topscope.known_resource_types.add klass
end
@@ -627,20 +627,20 @@ def compile
it "should provide default values for parameters that have no values specified" do
define_class('foo', {})
compile()
- @catalog.resource(:class, 'foo')['1'].should == "foo"
+ @catalog.resource(:class, 'foo')['p1'].should == "foo"
end
it "should use any provided values" do
- define_class('foo', {'1' => 'real_value'})
+ define_class('foo', {'p1' => 'real_value'})
compile()
- @catalog.resource(:class, 'foo')['1'].should == "real_value"
+ @catalog.resource(:class, 'foo')['p1'].should == "real_value"
end
it "should support providing some but not all values" do
- define_class('foo', {'1' => 'real_value'})
+ define_class('foo', {'p1' => 'real_value'})
compile()
- @catalog.resource(:class, 'Foo')['1'].should == "real_value"
- @catalog.resource(:class, 'Foo')['2'].should == "foo"
+ @catalog.resource(:class, 'Foo')['p1'].should == "real_value"
+ @catalog.resource(:class, 'Foo')['p2'].should == "foo"
end
it "should ensure each node class is in catalog and has appropriate tags" do
@@ -648,7 +648,7 @@ def compile
@node.classes = klasses
ast_obj = Puppet::Parser::AST::String.new(:value => 'foo')
klasses.each do |name|
- klass = Puppet::Resource::Type.new(:hostclass, name, :arguments => {'1' => ast_obj, '2' => ast_obj})
+ klass = Puppet::Resource::Type.new(:hostclass, name, :arguments => {'p1' => ast_obj, 'p2' => ast_obj})
@compiler.topscope.known_resource_types.add klass
end
catalog = @compiler.compile
@@ -659,11 +659,11 @@ def compile
end
it "should fail if required parameters are missing" do
- klass = {'foo'=>{'1'=>'one'}}
+ klass = {'foo'=>{'a'=>'one'}}
@node.classes = klass
- klass = Puppet::Resource::Type.new(:hostclass, 'foo', :arguments => {'1' => nil, '2' => nil})
+ klass = Puppet::Resource::Type.new(:hostclass, 'foo', :arguments => {'a' => nil, 'b' => nil})
@compiler.topscope.known_resource_types.add klass
- lambda { @compiler.compile }.should raise_error(Puppet::ParseError, "Must pass 2 to Class[Foo]")
+ lambda { @compiler.compile }.should raise_error(Puppet::ParseError, "Must pass b to Class[Foo]")
end
it "should fail if invalid parameters are passed" do
diff --git a/spec/unit/parser/eparser_adapter_spec.rb b/spec/unit/parser/eparser_adapter_spec.rb
new file mode 100644
index 0000000..0516334
--- /dev/null
+++ b/spec/unit/parser/eparser_adapter_spec.rb
@@ -0,0 +1,407 @@
+#! /usr/bin/env ruby
+require 'spec_helper'
+require 'puppet/parser/e_parser_adapter'
+
+describe Puppet::Parser do
+
+ Puppet::Parser::AST
+
+ before :each do
+ @known_resource_types = Puppet::Resource::TypeCollection.new("development")
+ @classic_parser = Puppet::Parser::Parser.new "development"
+ @parser = Puppet::Parser::EParserAdapter.new(@classic_parser)
+ @classic_parser.stubs(:known_resource_types).returns @known_resource_types
+ @true_ast = Puppet::Parser::AST::Boolean.new :value => true
+ end
+
+ it "should require an environment at initialization" do
+ expect {
+ Puppet::Parser::EParserAdapter.new
+ }.to raise_error(ArgumentError, /wrong number of arguments/)
+ end
+
+ describe "when parsing append operator" do
+
+ it "should not raise syntax errors" do
+ expect { @parser.parse("$var += something") }.to_not raise_error
+ end
+
+ it "should raise syntax error on incomplete syntax " do
+ expect {
+ @parser.parse("$var += ")
+ }.to raise_error(Puppet::ParseError, /Syntax error at end of file/)
+ end
+
+ it "should create ast::VarDef with append=true" do
+ vardef = @parser.parse("$var += 2").code[0]
+ vardef.should be_a(Puppet::Parser::AST::VarDef)
+ vardef.append.should == true
+ end
+
+ it "should work with arrays too" do
+ vardef = @parser.parse("$var += ['test']").code[0]
+ vardef.should be_a(Puppet::Parser::AST::VarDef)
+ vardef.append.should == true
+ end
+
+ end
+
+ describe "when parsing selector" do
+ it "should support hash access on the left hand side" do
+ expect { @parser.parse("$h = { 'a' => 'b' } $a = $h['a'] ? { 'b' => 'd', default => undef }") }.to_not raise_error
+ end
+ end
+
+ describe "parsing 'unless'" do
+ it "should create the correct ast objects" do
+ Puppet::Parser::AST::Not.expects(:new).with { |h| h[:value].is_a?(Puppet::Parser::AST::Boolean) }
+ @parser.parse("unless false { $var = 1 }")
+ end
+
+ it "should not raise an error with empty statements" do
+ expect { @parser.parse("unless false { }") }.to_not raise_error
+ end
+
+ #test for bug #13296
+ it "should not override 'unless' as a parameter inside resources" do
+ lambda { @parser.parse("exec {'/bin/echo foo': unless => '/usr/bin/false',}") }.should_not raise_error
+ end
+ end
+
+ describe "when parsing parameter names" do
+ Puppet::Parser::Lexer::KEYWORDS.sort_tokens.each do |keyword|
+ it "should allow #{keyword} as a keyword" do
+ lambda { @parser.parse("exec {'/bin/echo foo': #{keyword} => '/usr/bin/false',}") }.should_not raise_error
+ end
+ end
+ end
+
+ describe "when parsing 'if'" do
+ it "not, it should create the correct ast objects" do
+ Puppet::Parser::AST::Not.expects(:new).with { |h| h[:value].is_a?(Puppet::Parser::AST::Boolean) }
+ @parser.parse("if ! true { $var = 1 }")
+ end
+
+ it "boolean operation, it should create the correct ast objects" do
+ Puppet::Parser::AST::BooleanOperator.expects(:new).with {
+ |h| h[:rval].is_a?(Puppet::Parser::AST::Boolean) and h[:lval].is_a?(Puppet::Parser::AST::Boolean) and h[:operator]=="or"
+ }
+ @parser.parse("if true or true { $var = 1 }")
+
+ end
+
+ it "comparison operation, it should create the correct ast objects" do
+ Puppet::Parser::AST::ComparisonOperator.expects(:new).with {
+ |h| h[:lval].is_a?(Puppet::Parser::AST::Name) and h[:rval].is_a?(Puppet::Parser::AST::Name) and h[:operator]=="<"
+ }
+ @parser.parse("if 1 < 2 { $var = 1 }")
+
+ end
+
+ end
+
+ describe "when parsing if complex expressions" do
+ it "should create a correct ast tree" do
+ aststub = stub_everything 'ast'
+ Puppet::Parser::AST::ComparisonOperator.expects(:new).with {
+ |h| h[:rval].is_a?(Puppet::Parser::AST::Name) and h[:lval].is_a?(Puppet::Parser::AST::Name) and h[:operator]==">"
+ }.returns(aststub)
+ Puppet::Parser::AST::ComparisonOperator.expects(:new).with {
+ |h| h[:rval].is_a?(Puppet::Parser::AST::Name) and h[:lval].is_a?(Puppet::Parser::AST::Name) and h[:operator]=="=="
+ }.returns(aststub)
+ Puppet::Parser::AST::BooleanOperator.expects(:new).with {
+ |h| h[:rval]==aststub and h[:lval]==aststub and h[:operator]=="and"
+ }
+ @parser.parse("if (1 > 2) and (1 == 2) { $var = 1 }")
+ end
+
+ it "should raise an error on incorrect expression" do
+ expect {
+ @parser.parse("if (1 > 2 > ) or (1 == 2) { $var = 1 }")
+ }.to raise_error(Puppet::ParseError, /Syntax error at '\)'/)
+ end
+
+ end
+
+ describe "when parsing resource references" do
+
+ it "should not raise syntax errors" do
+ expect { @parser.parse('exec { test: param => File["a"] }') }.to_not raise_error
+ end
+
+ it "should not raise syntax errors with multiple references" do
+ expect { @parser.parse('exec { test: param => File["a","b"] }') }.to_not raise_error
+ end
+
+ it "should create an ast::ResourceReference" do
+ # NOTE: In egrammar, type and name are unified immediately to lower case whereas the regular grammar
+ # keeps the UC name in some contexts - it gets downcased later as the name of the type is in lower case.
+ #
+ Puppet::Parser::AST::ResourceReference.expects(:new).with { |arg|
+ arg[:line]==1 and arg[:pos] ==25 and arg[:type]=="file" and arg[:title].is_a?(Puppet::Parser::AST::ASTArray)
+ }
+ @parser.parse('exec { test: command => File["a","b"] }')
+ end
+ end
+
+ describe "when parsing resource overrides" do
+
+ it "should not raise syntax errors" do
+ expect { @parser.parse('Resource["title"] { param => value }') }.to_not raise_error
+ end
+
+ it "should not raise syntax errors with multiple overrides" do
+ expect { @parser.parse('Resource["title1","title2"] { param => value }') }.to_not raise_error
+ end
+
+ it "should create an ast::ResourceOverride" do
+ ro = @parser.parse('Resource["title1","title2"] { param => value }').code[0]
+ ro.should be_a(Puppet::Parser::AST::ResourceOverride)
+ ro.line.should == 1
+ ro.object.should be_a(Puppet::Parser::AST::ResourceReference)
+ ro.parameters[0].should be_a(Puppet::Parser::AST::ResourceParam)
+ end
+
+ end
+
+ describe "when parsing if statements" do
+
+ it "should not raise errors with empty if" do
+ expect { @parser.parse("if true { }") }.to_not raise_error
+ end
+
+ it "should not raise errors with empty else" do
+ expect { @parser.parse("if false { notice('if') } else { }") }.to_not raise_error
+ end
+
+ it "should not raise errors with empty if and else" do
+ expect { @parser.parse("if false { } else { }") }.to_not raise_error
+ end
+
+ it "should create a nop node for empty branch" do
+ Puppet::Parser::AST::Nop.expects(:new).twice
+ @parser.parse("if true { }")
+ end
+
+ it "should create a nop node for empty else branch" do
+ Puppet::Parser::AST::Nop.expects(:new)
+ @parser.parse("if true { notice('test') } else { }")
+ end
+
+ it "should build a chain of 'ifs' if there's an 'elsif'" do
+ expect { @parser.parse(<<-PP) }.to_not raise_error
+ if true { notice('test') } elsif true {} else { }
+ PP
+ end
+
+ end
+
+ describe "when parsing function calls" do
+ it "should not raise errors with no arguments" do
+ expect { @parser.parse("tag()") }.to_not raise_error
+ end
+
+ it "should not raise errors with rvalue function with no args" do
+ expect { @parser.parse("$a = template()") }.to_not raise_error
+ end
+
+ it "should not raise errors with arguments" do
+ expect { @parser.parse("notice(1)") }.to_not raise_error
+ end
+
+ it "should not raise errors with multiple arguments" do
+ expect { @parser.parse("notice(1,2)") }.to_not raise_error
+ end
+
+ it "should not raise errors with multiple arguments and a trailing comma" do
+ expect { @parser.parse("notice(1,2,)") }.to_not raise_error
+ end
+
+ end
+
+ describe "when parsing arrays" do
+ it "should parse an array" do
+ expect { @parser.parse("$a = [1,2]") }.to_not raise_error
+ end
+
+ it "should not raise errors with a trailing comma" do
+ expect { @parser.parse("$a = [1,2,]") }.to_not raise_error
+ end
+
+ it "should accept an empty array" do
+ expect { @parser.parse("$var = []\n") }.to_not raise_error
+ end
+ end
+
+ describe "when parsing classes" do
+ before :each do
+ @krt = Puppet::Resource::TypeCollection.new("development")
+ @classic_parser = Puppet::Parser::Parser.new "development"
+ @parser = Puppet::Parser::EParserAdapter.new(@classic_parser)
+ @classic_parser.stubs(:known_resource_types).returns @krt
+ end
+
+ it "should not create new classes" do
+ @parser.parse("class foobar {}").code[0].should be_a(Puppet::Parser::AST::Hostclass)
+ @krt.hostclass("foobar").should be_nil
+ end
+
+ it "should correctly set the parent class when one is provided" do
+ @parser.parse("class foobar inherits yayness {}").code[0].instantiate('')[0].parent.should == "yayness"
+ end
+
+ it "should correctly set the parent class for multiple classes at a time" do
+ statements = @parser.parse("class foobar inherits yayness {}\nclass boo inherits bar {}").code
+ statements[0].instantiate('')[0].parent.should == "yayness"
+ statements[1].instantiate('')[0].parent.should == "bar"
+ end
+
+ it "should define the code when some is provided" do
+ @parser.parse("class foobar { $var = val }").code[0].code.should_not be_nil
+ end
+
+ it "should accept parameters with trailing comma" do
+ @parser.parse("file { '/example': ensure => file, }").should be
+ end
+
+ it "should accept parametrized classes with trailing comma" do
+ @parser.parse("class foobar ($var1 = 0,) { $var = val }").code[0].code.should_not be_nil
+ end
+
+ it "should define parameters when provided" do
+ foobar = @parser.parse("class foobar($biz,$baz) {}").code[0].instantiate('')[0]
+ foobar.arguments.should == {"biz" => nil, "baz" => nil}
+ end
+ end
+
+ describe "when parsing resources" do
+ before :each do
+ @krt = Puppet::Resource::TypeCollection.new("development")
+ @classic_parser = Puppet::Parser::Parser.new "development"
+ @parser = Puppet::Parser::EParserAdapter.new(@classic_parser)
+ @classic_parser.stubs(:known_resource_types).returns @krt
+ end
+
+ it "should be able to parse class resources" do
+ @krt.add(Puppet::Resource::Type.new(:hostclass, "foobar", :arguments => {"biz" => nil}))
+ expect { @parser.parse("class { foobar: biz => stuff }") }.to_not raise_error
+ end
+
+ it "should correctly mark exported resources as exported" do
+ @parser.parse("@@file { '/file': }").code[0].exported.should be_true
+ end
+
+ it "should correctly mark virtual resources as virtual" do
+ @parser.parse("@file { '/file': }").code[0].virtual.should be_true
+ end
+ end
+
+ describe "when parsing nodes" do
+ it "should be able to parse a node with a single name" do
+ node = @parser.parse("node foo { }").code[0]
+ node.should be_a Puppet::Parser::AST::Node
+ node.names.length.should == 1
+ node.names[0].value.should == "foo"
+ end
+
+ it "should be able to parse a node with two names" do
+ node = @parser.parse("node foo, bar { }").code[0]
+ node.should be_a Puppet::Parser::AST::Node
+ node.names.length.should == 2
+ node.names[0].value.should == "foo"
+ node.names[1].value.should == "bar"
+ end
+
+ it "should be able to parse a node with three names" do
+ node = @parser.parse("node foo, bar, baz { }").code[0]
+ node.should be_a Puppet::Parser::AST::Node
+ node.names.length.should == 3
+ node.names[0].value.should == "foo"
+ node.names[1].value.should == "bar"
+ node.names[2].value.should == "baz"
+ end
+ end
+
+ it "should fail if trying to collect defaults" do
+ expect {
+ @parser.parse("@Port { protocols => tcp }")
+ }.to raise_error(Puppet::ParseError, /Defaults are not virtualizable/)
+ end
+
+ context "when parsing collections" do
+ it "should parse basic collections" do
+ @parser.parse("Port <| |>").code.
+ should be_all {|x| x.is_a? Puppet::Parser::AST::Collection }
+ end
+
+ it "should parse fully qualified collections" do
+ @parser.parse("Port::Range <| |>").code.
+ should be_all {|x| x.is_a? Puppet::Parser::AST::Collection }
+ end
+ end
+
+ it "should not assign to a fully qualified variable" do
+ expect {
+ @parser.parse("$one::two = yay")
+ }.to raise_error(Puppet::ParseError, /Cannot assign to variables in other namespaces/)
+ end
+
+ it "should parse assignment of undef" do
+ tree = @parser.parse("$var = undef")
+ tree.code.children[0].should be_an_instance_of Puppet::Parser::AST::VarDef
+ tree.code.children[0].value.should be_an_instance_of Puppet::Parser::AST::Undef
+ end
+
+ it "should treat classes as case insensitive" do
+ @classic_parser.known_resource_types.import_ast(@parser.parse("class yayness {}"), '')
+ @classic_parser.known_resource_types.hostclass('yayness').
+ should == @classic_parser.find_hostclass("", "YayNess")
+ end
+
+ it "should treat defines as case insensitive" do
+ @classic_parser.known_resource_types.import_ast(@parser.parse("define funtest {}"), '')
+ @classic_parser.known_resource_types.hostclass('funtest').
+ should == @classic_parser.find_hostclass("", "fUntEst")
+ end
+ context "when parsing method calls" do
+ it "should parse method call with one param lambda" do
+ expect { @parser.parse("$a.foreach {|$a| debug $a }") }.to_not raise_error
+ end
+ it "should parse method call with two param lambda" do
+ expect { @parser.parse("$a.foreach {|$a,$b| debug $a }") }.to_not raise_error
+ end
+ it "should parse method call with two param lambda and default value" do
+ expect { @parser.parse("$a.foreach {|$a,$b=1| debug $a }") }.to_not raise_error
+ end
+ it "should parse method call without lambda (statement)" do
+ expect { @parser.parse("$a.foreach") }.to_not raise_error
+ end
+ it "should parse method call without lambda (expression)" do
+ expect { @parser.parse("$x = $a.foreach + 1") }.to_not raise_error
+ end
+ context "a receiver expression of type" do
+ it "variable should be allowed" do
+ expect { @parser.parse("$a.foreach") }.to_not raise_error
+ end
+ it "hasharrayaccess should be allowed" do
+ expect { @parser.parse("$a[0][1].foreach") }.to_not raise_error
+ end
+ it "quoted text should be allowed" do
+ expect { @parser.parse("\"monkey\".foreach") }.to_not raise_error
+ expect { @parser.parse("'monkey'.foreach") }.to_not raise_error
+ end
+ it "selector text should be allowed" do
+ expect { @parser.parse("$a ? { 'banana'=>[1,2,3]}.foreach") }.to_not raise_error
+ end
+ it "function call should be allowed" do
+ expect { @parser.parse("duh(1,2,3).foreach") }.to_not raise_error
+ end
+ it "method call should be allowed" do
+ expect { @parser.parse("$a.foo.bar") }.to_not raise_error
+ end
+ it "chained method calls with lambda should be allowed" do
+ expect { @parser.parse("$a.foo{||}.bar{||}") }.to_not raise_error
+ end
+ end
+ end
+end
diff --git a/spec/unit/parser/methods/collect_spec.rb b/spec/unit/parser/methods/collect_spec.rb
new file mode 100644
index 0000000..7d32596
--- /dev/null
+++ b/spec/unit/parser/methods/collect_spec.rb
@@ -0,0 +1,120 @@
+require 'puppet'
+require 'spec_helper'
+require 'puppet_spec/compiler'
+
+describe 'the collect method' do
+ include PuppetSpec::Compiler
+
+ before :all do
+ # enable switching back
+ @saved_parser = Puppet[:parser]
+ end
+ after :all do
+ # switch back to original
+ Puppet[:parser] = @saved_parser
+ end
+ before :each do
+ node = Puppet::Node.new("floppy", :environment => 'production')
+ @compiler = Puppet::Parser::Compiler.new(node)
+ @scope = Puppet::Parser::Scope.new(@compiler)
+ @topscope = @scope.compiler.topscope
+ @scope.parent = @topscope
+ end
+
+ context "using future parser" do
+ before :each do
+ Puppet[:parser] = "future"
+ end
+ context "in Ruby style should be callable as" do
+ it 'collect on an array (multiplying each value by 2)' do
+ catalog = compile_to_catalog(<<-MANIFEST)
+ $a = [1,2,3]
+ $a.collect {|$x| $x*2}.foreach {|$v|
+ file { "/file_$v": ensure => present }
+ }
+ MANIFEST
+
+ catalog.resource(:file, "/file_2")['ensure'].should == 'present'
+ catalog.resource(:file, "/file_4")['ensure'].should == 'present'
+ catalog.resource(:file, "/file_6")['ensure'].should == 'present'
+ end
+
+ it 'collect on a hash selecting keys' do
+ catalog = compile_to_catalog(<<-MANIFEST)
+ $a = {'a'=>1,'b'=>2,'c'=>3}
+ $a.collect {|$x| $x[0]}.foreach {|$k|
+ file { "/file_$k": ensure => present }
+ }
+ MANIFEST
+
+ catalog.resource(:file, "/file_a")['ensure'].should == 'present'
+ catalog.resource(:file, "/file_b")['ensure'].should == 'present'
+ catalog.resource(:file, "/file_c")['ensure'].should == 'present'
+ end
+ it 'foreach on a hash selecting value' do
+ catalog = compile_to_catalog(<<-MANIFEST)
+ $a = {'a'=>1,'b'=>2,'c'=>3}
+ $a.collect {|$x| $x[1]}.foreach {|$k|
+ file { "/file_$k": ensure => present }
+ }
+ MANIFEST
+
+ catalog.resource(:file, "/file_1")['ensure'].should == 'present'
+ catalog.resource(:file, "/file_2")['ensure'].should == 'present'
+ catalog.resource(:file, "/file_3")['ensure'].should == 'present'
+ end
+ end
+ context "in Java style should be callable as" do
+ shared_examples_for 'java style' do
+ it 'collect on an array (multiplying each value by 2)' do
+ catalog = compile_to_catalog(<<-MANIFEST)
+ $a = [1,2,3]
+ $a.collect |$x| #{farr}{ $x*2}.foreach |$v| #{farr}{
+ file { "/file_$v": ensure => present }
+ }
+ MANIFEST
+
+ catalog.resource(:file, "/file_2")['ensure'].should == 'present'
+ catalog.resource(:file, "/file_4")['ensure'].should == 'present'
+ catalog.resource(:file, "/file_6")['ensure'].should == 'present'
+ end
+
+ it 'collect on a hash selecting keys' do
+ catalog = compile_to_catalog(<<-MANIFEST)
+ $a = {'a'=>1,'b'=>2,'c'=>3}
+ $a.collect |$x| #{farr}{ $x[0]}.foreach |$k| #{farr}{
+ file { "/file_$k": ensure => present }
+ }
+ MANIFEST
+
+ catalog.resource(:file, "/file_a")['ensure'].should == 'present'
+ catalog.resource(:file, "/file_b")['ensure'].should == 'present'
+ catalog.resource(:file, "/file_c")['ensure'].should == 'present'
+ end
+
+ it 'foreach on a hash selecting value' do
+ catalog = compile_to_catalog(<<-MANIFEST)
+ $a = {'a'=>1,'b'=>2,'c'=>3}
+ $a.collect |$x| #{farr} {$x[1]}.foreach |$k|#{farr}{
+ file { "/file_$k": ensure => present }
+ }
+ MANIFEST
+
+ catalog.resource(:file, "/file_1")['ensure'].should == 'present'
+ catalog.resource(:file, "/file_2")['ensure'].should == 'present'
+ catalog.resource(:file, "/file_3")['ensure'].should == 'present'
+ end
+ end
+ describe 'without fat arrow' do
+ it_should_behave_like 'java style' do
+ let(:farr) { '' }
+ end
+ end
+ describe 'with fat arrow' do
+ it_should_behave_like 'java style' do
+ let(:farr) { '=>' }
+ end
+ end
+ end
+ end
+end
diff --git a/spec/unit/parser/methods/each_spec.rb b/spec/unit/parser/methods/each_spec.rb
new file mode 100644
index 0000000..b8ab5f6
--- /dev/null
+++ b/spec/unit/parser/methods/each_spec.rb
@@ -0,0 +1,105 @@
+require 'puppet'
+require 'spec_helper'
+require 'puppet_spec/compiler'
+require 'rubygems'
+
+describe 'methods' do
+ include PuppetSpec::Compiler
+
+ before :all do
+ # enable switching back
+ @saved_parser = Puppet[:parser]
+ end
+ after :all do
+ # switch back to original
+ Puppet[:parser] = @saved_parser
+ end
+
+ before :each do
+ node = Puppet::Node.new("floppy", :environment => 'production')
+ @compiler = Puppet::Parser::Compiler.new(node)
+ @scope = Puppet::Parser::Scope.new(@compiler)
+ @topscope = @scope.compiler.topscope
+ @scope.parent = @topscope
+ Puppet[:parser] = 'future'
+ end
+
+ context "should be callable as" do
+ it 'each on an array selecting each value' do
+ catalog = compile_to_catalog(<<-MANIFEST)
+ $a = [1,2,3]
+ $a.each |$v| {
+ file { "/file_$v": ensure => present }
+ }
+ MANIFEST
+
+ catalog.resource(:file, "/file_1")['ensure'].should == 'present'
+ catalog.resource(:file, "/file_2")['ensure'].should == 'present'
+ catalog.resource(:file, "/file_3")['ensure'].should == 'present'
+ end
+ it 'each on an array selecting each value - function call style' do
+ catalog = compile_to_catalog(<<-MANIFEST)
+ $a = [1,2,3]
+ foreach ($a) |$index, $v| => {
+ file { "/file_$v": ensure => present }
+ }
+ MANIFEST
+
+ catalog.resource(:file, "/file_1")['ensure'].should == 'present'
+ catalog.resource(:file, "/file_2")['ensure'].should == 'present'
+ catalog.resource(:file, "/file_3")['ensure'].should == 'present'
+ end
+
+ it 'each on an array with index' do
+ catalog = compile_to_catalog(<<-MANIFEST)
+ $a = [present, absent, present]
+ $a.each |$k,$v| {
+ file { "/file_${$k+1}": ensure => $v }
+ }
+ MANIFEST
+
+ catalog.resource(:file, "/file_1")['ensure'].should == 'present'
+ catalog.resource(:file, "/file_2")['ensure'].should == 'absent'
+ catalog.resource(:file, "/file_3")['ensure'].should == 'present'
+ end
+
+ it 'each on a hash selecting entries' do
+ catalog = compile_to_catalog(<<-MANIFEST)
+ $a = {'a'=>'present','b'=>'absent','c'=>'present'}
+ $a.each |$e| {
+ file { "/file_${e[0]}": ensure => $e[1] }
+ }
+ MANIFEST
+
+ catalog.resource(:file, "/file_a")['ensure'].should == 'present'
+ catalog.resource(:file, "/file_b")['ensure'].should == 'absent'
+ catalog.resource(:file, "/file_c")['ensure'].should == 'present'
+ end
+ it 'each on a hash selecting key and value' do
+ catalog = compile_to_catalog(<<-MANIFEST)
+ $a = {'a'=>present,'b'=>absent,'c'=>present}
+ $a.each |$k, $v| {
+ file { "/file_$k": ensure => $v }
+ }
+ MANIFEST
+
+ catalog.resource(:file, "/file_a")['ensure'].should == 'present'
+ catalog.resource(:file, "/file_b")['ensure'].should == 'absent'
+ catalog.resource(:file, "/file_c")['ensure'].should == 'present'
+ end
+ end
+ context "should produce receiver" do
+ it 'each checking produced value using single expression' do
+ catalog = compile_to_catalog(<<-MANIFEST)
+ $a = [1, 3, 2]
+ $b = $a.each |$x| { $x }
+ file { "/file_${b[1]}":
+ ensure => present
+ }
+ MANIFEST
+
+ catalog.resource(:file, "/file_3")['ensure'].should == 'present'
+ end
+
+ end
+end
diff --git a/spec/unit/parser/methods/foreach_spec.rb b/spec/unit/parser/methods/foreach_spec.rb
new file mode 100755
index 0000000..b85296a
--- /dev/null
+++ b/spec/unit/parser/methods/foreach_spec.rb
@@ -0,0 +1,106 @@
+require 'puppet'
+require 'spec_helper'
+require 'puppet_spec/compiler'
+require 'rubygems'
+
+describe 'methods' do
+ include PuppetSpec::Compiler
+
+ before :all do
+ # enable switching back
+ @saved_parser = Puppet[:parser]
+ # These tests only work with future parser
+ end
+ after :all do
+ # switch back to original
+ Puppet[:parser] = @saved_parser
+ end
+
+ before :each do
+ node = Puppet::Node.new("floppy", :environment => 'production')
+ @compiler = Puppet::Parser::Compiler.new(node)
+ @scope = Puppet::Parser::Scope.new(@compiler)
+ @topscope = @scope.compiler.topscope
+ @scope.parent = @topscope
+ Puppet[:parser] = 'future'
+ end
+
+ context "should be callable as" do
+ it 'foreach on an array selecting each value' do
+ catalog = compile_to_catalog(<<-MANIFEST)
+ $a = [1,2,3]
+ $a.foreach {|$v|
+ file { "/file_$v": ensure => present }
+ }
+ MANIFEST
+
+ catalog.resource(:file, "/file_1")['ensure'].should == 'present'
+ catalog.resource(:file, "/file_2")['ensure'].should == 'present'
+ catalog.resource(:file, "/file_3")['ensure'].should == 'present'
+ end
+ it 'foreach on an array selecting each value - function call style' do
+ catalog = compile_to_catalog(<<-MANIFEST)
+ $a = [1,2,3]
+ foreach ($a) {|$v|
+ file { "/file_$v": ensure => present }
+ }
+ MANIFEST
+
+ catalog.resource(:file, "/file_1")['ensure'].should == 'present'
+ catalog.resource(:file, "/file_2")['ensure'].should == 'present'
+ catalog.resource(:file, "/file_3")['ensure'].should == 'present'
+ end
+
+ it 'foreach on an array with index' do
+ catalog = compile_to_catalog(<<-MANIFEST)
+ $a = [present, absent, present]
+ $a.foreach {|$k,$v|
+ file { "/file_${$k+1}": ensure => $v }
+ }
+ MANIFEST
+
+ catalog.resource(:file, "/file_1")['ensure'].should == 'present'
+ catalog.resource(:file, "/file_2")['ensure'].should == 'absent'
+ catalog.resource(:file, "/file_3")['ensure'].should == 'present'
+ end
+
+ it 'foreach on a hash selecting entries' do
+ catalog = compile_to_catalog(<<-MANIFEST)
+ $a = {'a'=>'present','b'=>'absent','c'=>'present'}
+ $a.foreach {|$e|
+ file { "/file_${e[0]}": ensure => $e[1] }
+ }
+ MANIFEST
+
+ catalog.resource(:file, "/file_a")['ensure'].should == 'present'
+ catalog.resource(:file, "/file_b")['ensure'].should == 'absent'
+ catalog.resource(:file, "/file_c")['ensure'].should == 'present'
+ end
+ it 'foreach on a hash selecting key and value' do
+ catalog = compile_to_catalog(<<-MANIFEST)
+ $a = {'a'=>present,'b'=>absent,'c'=>present}
+ $a.foreach {|$k, $v|
+ file { "/file_$k": ensure => $v }
+ }
+ MANIFEST
+
+ catalog.resource(:file, "/file_a")['ensure'].should == 'present'
+ catalog.resource(:file, "/file_b")['ensure'].should == 'absent'
+ catalog.resource(:file, "/file_c")['ensure'].should == 'present'
+ end
+ end
+ context "should produce receiver" do
+ it 'each checking produced value using single expression' do
+ catalog = compile_to_catalog(<<-MANIFEST)
+ $a = [1, 3, 2]
+ $b = $a.each |$x| { $x }
+ file { "/file_${b[1]}":
+ ensure => present
+ }
+ MANIFEST
+
+ catalog.resource(:file, "/file_3")['ensure'].should == 'present'
+ end
+
+ end
+end
diff --git a/spec/unit/parser/methods/reduce_spec.rb b/spec/unit/parser/methods/reduce_spec.rb
new file mode 100644
index 0000000..99ecfd1
--- /dev/null
+++ b/spec/unit/parser/methods/reduce_spec.rb
@@ -0,0 +1,67 @@
+require 'puppet'
+require 'spec_helper'
+require 'puppet_spec/compiler'
+
+describe 'the reduce method' do
+ include PuppetSpec::Compiler
+
+ before :all do
+ # enable switching back
+ @saved_parser = Puppet[:parser]
+ # These tests only work with future parser
+ end
+ after :all do
+ # switch back to original
+ Puppet[:parser] = @saved_parser
+ end
+
+ before :each do
+ node = Puppet::Node.new("floppy", :environment => 'production')
+ @compiler = Puppet::Parser::Compiler.new(node)
+ @scope = Puppet::Parser::Scope.new(@compiler)
+ @topscope = @scope.compiler.topscope
+ @scope.parent = @topscope
+ Puppet[:parser] = 'future'
+ end
+
+ context "should be callable as" do
+ it 'reduce on an array' do
+ catalog = compile_to_catalog(<<-MANIFEST)
+ $a = [1,2,3]
+ $b = $a.reduce {|$memo, $x| $memo + $x }
+ file { "/file_$b": ensure => present }
+ MANIFEST
+
+ catalog.resource(:file, "/file_6")['ensure'].should == 'present'
+ end
+ it 'reduce on an array with start value' do
+ catalog = compile_to_catalog(<<-MANIFEST)
+ $a = [1,2,3]
+ $b = $a.reduce(4) {|$memo, $x| $memo + $x }
+ file { "/file_$b": ensure => present }
+ MANIFEST
+
+ catalog.resource(:file, "/file_10")['ensure'].should == 'present'
+ end
+ it 'reduce on a hash' do
+ catalog = compile_to_catalog(<<-MANIFEST)
+ $a = {a=>1, b=>2, c=>3}
+ $start = [ignored, 4]
+ $b = $a.reduce {|$memo, $x| ['sum', $memo[1] + $x[1]] }
+ file { "/file_${$b[0]}_${$b[1]}": ensure => present }
+ MANIFEST
+
+ catalog.resource(:file, "/file_sum_6")['ensure'].should == 'present'
+ end
+ it 'reduce on a hash with start value' do
+ catalog = compile_to_catalog(<<-MANIFEST)
+ $a = {a=>1, b=>2, c=>3}
+ $start = ['ignored', 4]
+ $b = $a.reduce($start) {|$memo, $x| ['sum', $memo[1] + $x[1]] }
+ file { "/file_${$b[0]}_${$b[1]}": ensure => present }
+ MANIFEST
+
+ catalog.resource(:file, "/file_sum_10")['ensure'].should == 'present'
+ end
+ end
+end
diff --git a/spec/unit/parser/methods/reject_spec.rb b/spec/unit/parser/methods/reject_spec.rb
new file mode 100644
index 0000000..792e019
--- /dev/null
+++ b/spec/unit/parser/methods/reject_spec.rb
@@ -0,0 +1,52 @@
+require 'puppet'
+require 'spec_helper'
+require 'puppet_spec/compiler'
+
+describe 'the reject method' do
+ include PuppetSpec::Compiler
+
+ before :all do
+ # enable switching back
+ @saved_parser = Puppet[:parser]
+ # These tests only work with future parser
+ Puppet[:parser] = 'future'
+ end
+ after :all do
+ # switch back to original
+ Puppet[:parser] = @saved_parser
+ end
+
+ before :each do
+ node = Puppet::Node.new("floppy", :environment => 'production')
+ @compiler = Puppet::Parser::Compiler.new(node)
+ @scope = Puppet::Parser::Scope.new(@compiler)
+ @topscope = @scope.compiler.topscope
+ @scope.parent = @topscope
+ Puppet[:parser] = 'future'
+ end
+
+ context "should be callable as" do
+ it 'reject on an array (no berries)' do
+ catalog = compile_to_catalog(<<-MANIFEST)
+ $a = ['strawberry','blueberry','orange']
+ $a.reject {|$x| $x =~ /berry$/}.foreach {|$v|
+ file { "/file_$v": ensure => present }
+ }
+ MANIFEST
+
+ catalog.resource(:file, "/file_orange")['ensure'].should == 'present'
+ catalog.resource(:file, "/file_strawberry").should == nil
+ end
+ it 'reject on an array (no berries)' do
+ catalog = compile_to_catalog(<<-MANIFEST)
+ $a = ['strawberry','blueberry','orange']
+ $a.reject {|$x| $foo = $x =~ /berry$/}.foreach {|$v|
+ file { "/file_$v": ensure => present }
+ }
+ MANIFEST
+
+ catalog.resource(:file, "/file_orange")['ensure'].should == 'present'
+ catalog.resource(:file, "/file_strawberry").should == nil
+ end
+ end
+end
diff --git a/spec/unit/parser/methods/select_spec.rb b/spec/unit/parser/methods/select_spec.rb
new file mode 100644
index 0000000..88cef12
--- /dev/null
+++ b/spec/unit/parser/methods/select_spec.rb
@@ -0,0 +1,41 @@
+require 'puppet'
+require 'spec_helper'
+require 'puppet_spec/compiler'
+
+describe 'the select method' do
+ include PuppetSpec::Compiler
+
+ before :all do
+ # enable switching back
+ @saved_parser = Puppet[:parser]
+ # These tests only work with future parser
+ Puppet[:parser] = 'future'
+ end
+ after :all do
+ # switch back to original
+ Puppet[:parser] = @saved_parser
+ end
+
+ before :each do
+ node = Puppet::Node.new("floppy", :environment => 'production')
+ @compiler = Puppet::Parser::Compiler.new(node)
+ @scope = Puppet::Parser::Scope.new(@compiler)
+ @topscope = @scope.compiler.topscope
+ @scope.parent = @topscope
+ Puppet[:parser] = 'future'
+ end
+
+ context "should be callable as" do
+ it 'select on an array (all berries)' do
+ catalog = compile_to_catalog(<<-MANIFEST)
+ $a = ['strawberry','blueberry','orange']
+ $a.select {|$x| $x =~ /berry$/}.foreach {|$v|
+ file { "/file_$v": ensure => present }
+ }
+ MANIFEST
+
+ catalog.resource(:file, "/file_strawberry")['ensure'].should == 'present'
+ catalog.resource(:file, "/file_blueberry")['ensure'].should == 'present'
+ end
+ end
+end
diff --git a/spec/unit/parser/methods/slice_spec.rb b/spec/unit/parser/methods/slice_spec.rb
new file mode 100644
index 0000000..b213415
--- /dev/null
+++ b/spec/unit/parser/methods/slice_spec.rb
@@ -0,0 +1,97 @@
+require 'puppet'
+require 'spec_helper'
+require 'puppet_spec/compiler'
+require 'rubygems'
+
+describe 'methods' do
+ include PuppetSpec::Compiler
+
+ before :all do
+ # enable switching back
+ @saved_parser = Puppet[:parser]
+ # These tests only work with future parser
+ Puppet[:parser] = 'future'
+ end
+ after :all do
+ # switch back to original
+ Puppet[:parser] = @saved_parser
+ end
+
+ before :each do
+ node = Puppet::Node.new("floppy", :environment => 'production')
+ @compiler = Puppet::Parser::Compiler.new(node)
+ @scope = Puppet::Parser::Scope.new(@compiler)
+ @topscope = @scope.compiler.topscope
+ @scope.parent = @topscope
+ Puppet[:parser] = 'future'
+ end
+
+ context "should be callable on array as" do
+
+ it 'slice with explicit parameters' do
+ catalog = compile_to_catalog(<<-MANIFEST)
+ $a = [1, present, 2, absent, 3, present]
+ $a.slice(2) |$k,$v| {
+ file { "/file_${$k}": ensure => $v }
+ }
+ MANIFEST
+
+ catalog.resource(:file, "/file_1")['ensure'].should == 'present'
+ catalog.resource(:file, "/file_2")['ensure'].should == 'absent'
+ catalog.resource(:file, "/file_3")['ensure'].should == 'present'
+ end
+ it 'slice with one parameter' do
+ catalog = compile_to_catalog(<<-MANIFEST)
+ $a = [1, present, 2, absent, 3, present]
+ $a.slice(2) |$k| {
+ file { "/file_${$k[0]}": ensure => $k[1] }
+ }
+ MANIFEST
+
+ catalog.resource(:file, "/file_1")['ensure'].should == 'present'
+ catalog.resource(:file, "/file_2")['ensure'].should == 'absent'
+ catalog.resource(:file, "/file_3")['ensure'].should == 'present'
+ end
+ it 'slice with shorter last slice' do
+ catalog = compile_to_catalog(<<-MANIFEST)
+ $a = [1, present, 2, present, 3, absent]
+ $a.slice(4) |$a, $b, $c, $d| {
+ file { "/file_$a.$c": ensure => $b }
+ }
+ MANIFEST
+
+ catalog.resource(:file, "/file_1.2")['ensure'].should == 'present'
+ catalog.resource(:file, "/file_3.")['ensure'].should == 'absent'
+ end
+ end
+ context "should be callable on hash as" do
+
+ it 'slice with explicit parameters, missing are empty' do
+ catalog = compile_to_catalog(<<-MANIFEST)
+ $a = {1=>present, 2=>present, 3=>absent}
+ $a.slice(2) |$a,$b| {
+ file { "/file_${a[0]}.${b[0]}": ensure => $a[1] }
+ }
+ MANIFEST
+
+ catalog.resource(:file, "/file_1.2")['ensure'].should == 'present'
+ catalog.resource(:file, "/file_3.")['ensure'].should == 'absent'
+ end
+
+ end
+ context "when called without a block" do
+ it "should produce an array with the result" do
+ catalog = compile_to_catalog(<<-MANIFEST)
+ $a = [1, present, 2, absent, 3, present]
+ $a.slice(2).each |$k| {
+ file { "/file_${$k[0]}": ensure => $k[1] }
+ }
+ MANIFEST
+
+ catalog.resource(:file, "/file_1")['ensure'].should == 'present'
+ catalog.resource(:file, "/file_2")['ensure'].should == 'absent'
+ catalog.resource(:file, "/file_3")['ensure'].should == 'present'
+
+ end
+ end
+end
diff --git a/spec/unit/parser/parser_spec.rb b/spec/unit/parser/parser_spec.rb
index 7ff0c17..07ab678 100755
--- a/spec/unit/parser/parser_spec.rb
+++ b/spec/unit/parser/parser_spec.rb
@@ -100,11 +100,11 @@
Puppet::Parser::AST::Not.expects(:new).with { |h| h[:value].is_a?(Puppet::Parser::AST::Boolean) }
@parser.parse("unless false { $var = 1 }")
end
-
+
it "should not raise an error with empty statements" do
expect { @parser.parse("unless false { }") }.to_not raise_error
end
-
+
#test for bug #13296
it "should not override 'unless' as a parameter inside resources" do
lambda { @parser.parse("exec {'/bin/echo foo': unless => '/usr/bin/false',}") }.should_not raise_error
diff --git a/spec/unit/parser/scope_spec.rb b/spec/unit/parser/scope_spec.rb
index 646f4b7..8540ad2 100755
--- a/spec/unit/parser/scope_spec.rb
+++ b/spec/unit/parser/scope_spec.rb
@@ -495,6 +495,27 @@ def create_class_scope(name)
end
end
+ context "when using ephemeral as local scope" do
+ it "should store all variables in local scope" do
+ @scope.new_ephemeral true
+ @scope.setvar("apple", :fruit)
+ @scope["apple"].should == :fruit
+ end
+
+ it "should remove all local scope variables on unset" do
+ @scope.new_ephemeral true
+ @scope.setvar("apple", :fruit)
+ @scope["apple"].should == :fruit
+ @scope.unset_ephemeral_var
+ @scope["apple"].should == nil
+ end
+ it "should be created from a hash" do
+ @scope.ephemeral_from({ "apple" => :fruit, "strawberry" => :berry})
+ @scope["apple"].should == :fruit
+ @scope["strawberry"].should == :berry
+ end
+ end
+
describe "when setting ephemeral vars from matches" do
before :each do
@match = stub 'match', :is_a? => true
diff --git a/spec/unit/parser/type_loader_spec.rb b/spec/unit/parser/type_loader_spec.rb
index e15c05f..2938d45 100755
--- a/spec/unit/parser/type_loader_spec.rb
+++ b/spec/unit/parser/type_loader_spec.rb
@@ -2,6 +2,8 @@
require 'spec_helper'
require 'puppet/parser/type_loader'
+require 'puppet/parser/parser_factory'
+require 'puppet/parser/e_parser_adapter'
require 'puppet_spec/modules'
require 'puppet_spec/files'
@@ -9,222 +11,246 @@
include PuppetSpec::Modules
include PuppetSpec::Files
- before do
- @loader = Puppet::Parser::TypeLoader.new(:myenv)
- Puppet.expects(:deprecation_warning).never
- end
-
- it "should support an environment" do
- loader = Puppet::Parser::TypeLoader.new(:myenv)
- loader.environment.name.should == :myenv
- end
-
- it "should include the Environment Helper" do
- @loader.class.ancestors.should be_include(Puppet::Node::Environment::Helper)
- end
-
- it "should delegate its known resource types to its environment" do
- @loader.known_resource_types.should be_instance_of(Puppet::Resource::TypeCollection)
- end
-
- describe "when loading names from namespaces" do
- it "should do nothing if the name to import is an empty string" do
- @loader.expects(:name2files).never
- @loader.try_load_fqname(:hostclass, "") { |filename, modname| raise :should_not_occur }.should be_nil
+ shared_examples_for 'the typeloader' do
+ before do
+ @loader = Puppet::Parser::TypeLoader.new(:myenv)
+ Puppet.expects(:deprecation_warning).never
end
- it "should attempt to import each generated name" do
- @loader.expects(:import).with("foo/bar",nil).returns([])
- @loader.expects(:import).with("foo",nil).returns([])
- @loader.try_load_fqname(:hostclass, "foo::bar") { |f| false }
+ it "should support an environment" do
+ loader = Puppet::Parser::TypeLoader.new(:myenv)
+ loader.environment.name.should == :myenv
end
- end
- describe "when importing" do
- before do
- Puppet::Parser::Files.stubs(:find_manifests).returns ["modname", %w{file}]
- Puppet::Parser::Parser.any_instance.stubs(:parse).returns(Puppet::Parser::AST::Hostclass.new(''))
- Puppet::Parser::Parser.any_instance.stubs(:file=)
+ it "should include the Environment Helper" do
+ @loader.class.ancestors.should be_include(Puppet::Node::Environment::Helper)
end
- it "should return immediately when imports are being ignored" do
- Puppet::Parser::Files.expects(:find_manifests).never
- Puppet[:ignoreimport] = true
- @loader.import("foo").should be_nil
+ it "should delegate its known resource types to its environment" do
+ @loader.known_resource_types.should be_instance_of(Puppet::Resource::TypeCollection)
end
- it "should find all manifests matching the file or pattern" do
- Puppet::Parser::Files.expects(:find_manifests).with { |pat, opts| pat == "myfile" }.returns ["modname", %w{one}]
- @loader.import("myfile")
- end
+ describe "when loading names from namespaces" do
+ it "should do nothing if the name to import is an empty string" do
+ @loader.expects(:name2files).never
+ @loader.try_load_fqname(:hostclass, "") { |filename, modname| raise :should_not_occur }.should be_nil
+ end
- it "should use the directory of the current file if one is set" do
- Puppet::Parser::Files.expects(:find_manifests).with { |pat, opts| opts[:cwd] == make_absolute("/current") }.returns ["modname", %w{one}]
- @loader.import("myfile", make_absolute("/current/file"))
+ it "should attempt to import each generated name" do
+ @loader.expects(:import).with("foo/bar",nil).returns([])
+ @loader.expects(:import).with("foo",nil).returns([])
+ @loader.try_load_fqname(:hostclass, "foo::bar") { |f| false }
+ end
end
- it "should pass the environment when looking for files" do
- Puppet::Parser::Files.expects(:find_manifests).with { |pat, opts| opts[:environment] == @loader.environment }.returns ["modname", %w{one}]
- @loader.import("myfile")
- end
+ describe "when importing" do
+ before do
+ Puppet::Parser::Files.stubs(:find_manifests).returns ["modname", %w{file}]
+ parser_class.any_instance.stubs(:parse).returns(Puppet::Parser::AST::Hostclass.new(''))
+ parser_class.any_instance.stubs(:file=)
+ end
- it "should fail if no files are found" do
- Puppet::Parser::Files.expects(:find_manifests).returns [nil, []]
- lambda { @loader.import("myfile") }.should raise_error(Puppet::ImportError)
- end
+ it "should return immediately when imports are being ignored" do
+ Puppet::Parser::Files.expects(:find_manifests).never
+ Puppet[:ignoreimport] = true
+ @loader.import("foo").should be_nil
+ end
- it "should parse each found file" do
- Puppet::Parser::Files.expects(:find_manifests).returns ["modname", [make_absolute("/one")]]
- @loader.expects(:parse_file).with(make_absolute("/one")).returns(Puppet::Parser::AST::Hostclass.new(''))
- @loader.import("myfile")
- end
+ it "should find all manifests matching the file or pattern" do
+ Puppet::Parser::Files.expects(:find_manifests).with { |pat, opts| pat == "myfile" }.returns ["modname", %w{one}]
+ @loader.import("myfile")
+ end
- it "should make each file qualified before attempting to parse it" do
- Puppet::Parser::Files.expects(:find_manifests).returns ["modname", %w{one}]
- @loader.expects(:parse_file).with(make_absolute("/current/one")).returns(Puppet::Parser::AST::Hostclass.new(''))
- @loader.import("myfile", make_absolute("/current/file"))
- end
+ it "should use the directory of the current file if one is set" do
+ Puppet::Parser::Files.expects(:find_manifests).with { |pat, opts| opts[:cwd] == make_absolute("/current") }.returns ["modname", %w{one}]
+ @loader.import("myfile", make_absolute("/current/file"))
+ end
- it "should not attempt to import files that have already been imported" do
- Puppet::Parser::Files.expects(:find_manifests).returns ["modname", %w{/one}]
- Puppet::Parser::Parser.any_instance.expects(:parse).once.returns(Puppet::Parser::AST::Hostclass.new(''))
- @loader.import("myfile")
+ it "should pass the environment when looking for files" do
+ Puppet::Parser::Files.expects(:find_manifests).with { |pat, opts| opts[:environment] == @loader.environment }.returns ["modname", %w{one}]
+ @loader.import("myfile")
+ end
- # This will fail if it tries to reimport the file.
- @loader.import("myfile")
- end
- end
+ it "should fail if no files are found" do
+ Puppet::Parser::Files.expects(:find_manifests).returns [nil, []]
+ lambda { @loader.import("myfile") }.should raise_error(Puppet::ImportError)
+ end
- describe "when importing all" do
- before do
- @base = tmpdir("base")
+ it "should parse each found file" do
+ Puppet::Parser::Files.expects(:find_manifests).returns ["modname", [make_absolute("/one")]]
+ @loader.expects(:parse_file).with(make_absolute("/one")).returns(Puppet::Parser::AST::Hostclass.new(''))
+ @loader.import("myfile")
+ end
- # Create two module path directories
- @modulebase1 = File.join(@base, "first")
- FileUtils.mkdir_p(@modulebase1)
- @modulebase2 = File.join(@base, "second")
- FileUtils.mkdir_p(@modulebase2)
+ it "should make each file qualified before attempting to parse it" do
+ Puppet::Parser::Files.expects(:find_manifests).returns ["modname", %w{one}]
+ @loader.expects(:parse_file).with(make_absolute("/current/one")).returns(Puppet::Parser::AST::Hostclass.new(''))
+ @loader.import("myfile", make_absolute("/current/file"))
+ end
- Puppet[:modulepath] = "#{@modulebase1}#{File::PATH_SEPARATOR}#{@modulebase2}"
- end
+ it "should not attempt to import files that have already been imported" do
+ @loader = Puppet::Parser::TypeLoader.new(:myenv)
- def mk_module(basedir, name)
- PuppetSpec::Modules.create(name, basedir)
+ Puppet::Parser::Files.expects(:find_manifests).returns ["modname", %w{/one}]
+ parser_class.any_instance.expects(:parse).once.returns(Puppet::Parser::AST::Hostclass.new(''))
+ other_parser_class.any_instance.expects(:parse).never.returns(Puppet::Parser::AST::Hostclass.new(''))
+ @loader.import("myfile")
+
+ # This will fail if it tries to reimport the file.
+ @loader.import("myfile")
+ end
end
- # We have to pass the base path so that we can
- # write to modules that are in the second search path
- def mk_manifests(base, mod, type, files)
- exts = {"ruby" => ".rb", "puppet" => ".pp"}
- files.collect do |file|
- name = mod.name + "::" + file.gsub("/", "::")
- path = File.join(base, mod.name, "manifests", file + exts[type])
- FileUtils.mkdir_p(File.split(path)[0])
-
- # write out the class
- if type == "ruby"
- File.open(path, "w") { |f| f.print "hostclass '#{name}' do\nend" }
- else
- File.open(path, "w") { |f| f.print "class #{name} {}" }
+ describe "when importing all" do
+ before do
+ @base = tmpdir("base")
+
+ # Create two module path directories
+ @modulebase1 = File.join(@base, "first")
+ FileUtils.mkdir_p(@modulebase1)
+ @modulebase2 = File.join(@base, "second")
+ FileUtils.mkdir_p(@modulebase2)
+
+ Puppet[:modulepath] = "#{@modulebase1}#{File::PATH_SEPARATOR}#{@modulebase2}"
+ end
+
+ def mk_module(basedir, name)
+ PuppetSpec::Modules.create(name, basedir)
+ end
+
+ # We have to pass the base path so that we can
+ # write to modules that are in the second search path
+ def mk_manifests(base, mod, type, files)
+ exts = {"ruby" => ".rb", "puppet" => ".pp"}
+ files.collect do |file|
+ name = mod.name + "::" + file.gsub("/", "::")
+ path = File.join(base, mod.name, "manifests", file + exts[type])
+ FileUtils.mkdir_p(File.split(path)[0])
+
+ # write out the class
+ if type == "ruby"
+ File.open(path, "w") { |f| f.print "hostclass '#{name}' do\nend" }
+ else
+ File.open(path, "w") { |f| f.print "class #{name} {}" }
+ end
+ name
end
- name
end
- end
- it "should load all puppet manifests from all modules in the specified environment" do
- @module1 = mk_module(@modulebase1, "one")
- @module2 = mk_module(@modulebase2, "two")
+ it "should load all puppet manifests from all modules in the specified environment" do
+ @module1 = mk_module(@modulebase1, "one")
+ @module2 = mk_module(@modulebase2, "two")
- mk_manifests(@modulebase1, @module1, "puppet", %w{a b})
- mk_manifests(@modulebase2, @module2, "puppet", %w{c d})
+ mk_manifests(@modulebase1, @module1, "puppet", %w{a b})
+ mk_manifests(@modulebase2, @module2, "puppet", %w{c d})
- @loader.import_all
+ @loader.import_all
- @loader.environment.known_resource_types.hostclass("one::a").should be_instance_of(Puppet::Resource::Type)
- @loader.environment.known_resource_types.hostclass("one::b").should be_instance_of(Puppet::Resource::Type)
- @loader.environment.known_resource_types.hostclass("two::c").should be_instance_of(Puppet::Resource::Type)
- @loader.environment.known_resource_types.hostclass("two::d").should be_instance_of(Puppet::Resource::Type)
- end
+ @loader.environment.known_resource_types.hostclass("one::a").should be_instance_of(Puppet::Resource::Type)
+ @loader.environment.known_resource_types.hostclass("one::b").should be_instance_of(Puppet::Resource::Type)
+ @loader.environment.known_resource_types.hostclass("two::c").should be_instance_of(Puppet::Resource::Type)
+ @loader.environment.known_resource_types.hostclass("two::d").should be_instance_of(Puppet::Resource::Type)
+ end
- it "should load all ruby manifests from all modules in the specified environment" do
- Puppet.expects(:deprecation_warning).at_least(1)
+ it "should load all ruby manifests from all modules in the specified environment" do
+ Puppet.expects(:deprecation_warning).at_least(1)
- @module1 = mk_module(@modulebase1, "one")
- @module2 = mk_module(@modulebase2, "two")
+ @module1 = mk_module(@modulebase1, "one")
+ @module2 = mk_module(@modulebase2, "two")
- mk_manifests(@modulebase1, @module1, "ruby", %w{a b})
- mk_manifests(@modulebase2, @module2, "ruby", %w{c d})
+ mk_manifests(@modulebase1, @module1, "ruby", %w{a b})
+ mk_manifests(@modulebase2, @module2, "ruby", %w{c d})
- @loader.import_all
+ @loader.import_all
- @loader.environment.known_resource_types.hostclass("one::a").should be_instance_of(Puppet::Resource::Type)
- @loader.environment.known_resource_types.hostclass("one::b").should be_instance_of(Puppet::Resource::Type)
- @loader.environment.known_resource_types.hostclass("two::c").should be_instance_of(Puppet::Resource::Type)
- @loader.environment.known_resource_types.hostclass("two::d").should be_instance_of(Puppet::Resource::Type)
- end
+ @loader.environment.known_resource_types.hostclass("one::a").should be_instance_of(Puppet::Resource::Type)
+ @loader.environment.known_resource_types.hostclass("one::b").should be_instance_of(Puppet::Resource::Type)
+ @loader.environment.known_resource_types.hostclass("two::c").should be_instance_of(Puppet::Resource::Type)
+ @loader.environment.known_resource_types.hostclass("two::d").should be_instance_of(Puppet::Resource::Type)
+ end
- it "should not load manifests from duplicate modules later in the module path" do
- @module1 = mk_module(@modulebase1, "one")
+ it "should not load manifests from duplicate modules later in the module path" do
+ @module1 = mk_module(@modulebase1, "one")
- # duplicate
- @module2 = mk_module(@modulebase2, "one")
+ # duplicate
+ @module2 = mk_module(@modulebase2, "one")
- mk_manifests(@modulebase1, @module1, "puppet", %w{a})
- mk_manifests(@modulebase2, @module2, "puppet", %w{c})
+ mk_manifests(@modulebase1, @module1, "puppet", %w{a})
+ mk_manifests(@modulebase2, @module2, "puppet", %w{c})
- @loader.import_all
+ @loader.import_all
- @loader.environment.known_resource_types.hostclass("one::c").should be_nil
- end
+ @loader.environment.known_resource_types.hostclass("one::c").should be_nil
+ end
- it "should load manifests from subdirectories" do
- @module1 = mk_module(@modulebase1, "one")
+ it "should load manifests from subdirectories" do
+ @module1 = mk_module(@modulebase1, "one")
- mk_manifests(@modulebase1, @module1, "puppet", %w{a a/b a/b/c})
+ mk_manifests(@modulebase1, @module1, "puppet", %w{a a/b a/b/c})
- @loader.import_all
+ @loader.import_all
- @loader.environment.known_resource_types.hostclass("one::a::b").should be_instance_of(Puppet::Resource::Type)
- @loader.environment.known_resource_types.hostclass("one::a::b::c").should be_instance_of(Puppet::Resource::Type)
+ @loader.environment.known_resource_types.hostclass("one::a::b").should be_instance_of(Puppet::Resource::Type)
+ @loader.environment.known_resource_types.hostclass("one::a::b::c").should be_instance_of(Puppet::Resource::Type)
+ end
end
- end
- describe "when parsing a file" do
- before do
- @parser = Puppet::Parser::Parser.new(@loader.environment)
- @parser.stubs(:parse).returns(Puppet::Parser::AST::Hostclass.new(''))
- @parser.stubs(:file=)
- Puppet::Parser::Parser.stubs(:new).with(@loader.environment).returns @parser
- end
+ describe "when parsing a file" do
+ before do
+ @parser = Puppet::Parser::ParserFactory.parser(@loader.environment)
+ @parser.class.should == parser_class
+ @parser.stubs(:parse).returns(Puppet::Parser::AST::Hostclass.new(''))
+ @parser.stubs(:file=)
+ Puppet::Parser::ParserFactory.stubs(:parser).with(@loader.environment).returns @parser
+ end
- it "should create a new parser instance for each file using the current environment" do
- Puppet::Parser::Parser.expects(:new).with(@loader.environment).returns @parser
- @loader.parse_file("/my/file")
- end
+ it "should create a new parser instance for each file using the current environment" do
+ Puppet::Parser::ParserFactory.expects(:parser).with(@loader.environment).returns @parser
+ @loader.parse_file("/my/file")
+ end
- it "should assign the parser its file and parse" do
- @parser.expects(:file=).with("/my/file")
- @parser.expects(:parse).returns(Puppet::Parser::AST::Hostclass.new(''))
- @loader.parse_file("/my/file")
+ it "should assign the parser its file and parse" do
+ @parser.expects(:file=).with("/my/file")
+ @parser.expects(:parse).returns(Puppet::Parser::AST::Hostclass.new(''))
+ @loader.parse_file("/my/file")
+ end
end
- end
- it "should be able to add classes to the current resource type collection" do
- file = tmpfile("simple_file.pp")
- File.open(file, "w") { |f| f.puts "class foo {}" }
- @loader.import(file)
+ it "should be able to add classes to the current resource type collection" do
+ file = tmpfile("simple_file.pp")
+ File.open(file, "w") { |f| f.puts "class foo {}" }
+ @loader.import(file)
- @loader.known_resource_types.hostclass("foo").should be_instance_of(Puppet::Resource::Type)
- end
+ @loader.known_resource_types.hostclass("foo").should be_instance_of(Puppet::Resource::Type)
+ end
- describe "when deciding where to look for files" do
- { 'foo' => ['foo'],
- 'foo::bar' => ['foo/bar', 'foo'],
- 'foo::bar::baz' => ['foo/bar/baz', 'foo/bar', 'foo']
- }.each do |fqname, expected_paths|
- it "should look for #{fqname.inspect} in #{expected_paths.inspect}" do
- @loader.instance_eval { name2files(fqname) }.should == expected_paths
+ describe "when deciding where to look for files" do
+ { 'foo' => ['foo'],
+ 'foo::bar' => ['foo/bar', 'foo'],
+ 'foo::bar::baz' => ['foo/bar/baz', 'foo/bar', 'foo']
+ }.each do |fqname, expected_paths|
+ it "should look for #{fqname.inspect} in #{expected_paths.inspect}" do
+ @loader.instance_eval { name2files(fqname) }.should == expected_paths
+ end
end
end
end
+ describe 'when using the classic parser' do
+ before :each do
+ Puppet[:parser] = 'current'
+ end
+ it_should_behave_like 'the typeloader' do
+ let(:parser_class) { Puppet::Parser::Parser}
+ let(:other_parser_class) { Puppet::Parser::EParserAdapter}
+ end
+ end
+ describe 'when using the future parser' do
+ before :each do
+ Puppet[:parser] = 'future'
+ end
+ it_should_behave_like 'the typeloader' do
+ let(:parser_class) { Puppet::Parser::EParserAdapter}
+ let(:other_parser_class) { Puppet::Parser::Parser}
+ end
+ end
end
diff --git a/spec/unit/pops/adaptable_spec.rb b/spec/unit/pops/adaptable_spec.rb
new file mode 100644
index 0000000..e9b5b80
--- /dev/null
+++ b/spec/unit/pops/adaptable_spec.rb
@@ -0,0 +1,143 @@
+#! /usr/bin/env ruby
+require 'spec_helper'
+require 'puppet/pops'
+
+describe Puppet::Pops::Adaptable::Adapter do
+ class ValueAdapter < Puppet::Pops::Adaptable::Adapter
+ attr_accessor :value
+ end
+
+ class OtherAdapter < Puppet::Pops::Adaptable::Adapter
+ attr_accessor :value
+ def OtherAdapter.create_adapter(o)
+ x = new
+ x.value="I am calling you Daffy."
+ x
+ end
+ end
+
+ module Farm
+ class FarmAdapter < Puppet::Pops::Adaptable::Adapter
+ attr_accessor :value
+ end
+ end
+
+ class Duck
+ include Puppet::Pops::Adaptable
+ end
+
+ it "should create specialized adapter instance on call to adapt" do
+ d = Duck.new
+ a = ValueAdapter.adapt(d)
+ a.class.should == ValueAdapter
+ end
+
+ it "should produce the same instance on multiple adaptations" do
+ d = Duck.new
+ a = ValueAdapter.adapt(d)
+ a.value = 10
+ b = ValueAdapter.adapt(d)
+ b.value.should == 10
+ end
+
+ it "should return the correct adapter if there are several" do
+ d = Duck.new
+ other = OtherAdapter.adapt(d)
+ a = ValueAdapter.adapt(d)
+ a.value = 10
+ b = ValueAdapter.adapt(d)
+ b.value.should == 10
+ end
+
+ it "should allow specialization to override creating" do
+ d = Duck.new
+ a = OtherAdapter.adapt(d)
+ a.value.should == "I am calling you Daffy."
+ end
+
+ it "should create a new adapter overriding existing" do
+ d = Duck.new
+ a = OtherAdapter.adapt(d)
+ a.value.should == "I am calling you Daffy."
+ a.value = "Something different"
+ a.value.should == "Something different"
+ b = OtherAdapter.adapt(d)
+ b.value.should == "Something different"
+ b = OtherAdapter.adapt_new(d)
+ b.value.should == "I am calling you Daffy."
+ end
+
+ it "should not create adapter on get" do
+ d = Duck.new
+ a = OtherAdapter.get(d)
+ a.should == nil
+ end
+
+ it "should return same adapter from get after adapt" do
+ d = Duck.new
+ a = OtherAdapter.get(d)
+ a.should == nil
+ a = OtherAdapter.adapt(d)
+ a.value.should == "I am calling you Daffy."
+ b = OtherAdapter.get(d)
+ b.value.should == "I am calling you Daffy."
+ a.should == b
+ end
+
+ it "should handle adapters in nested namespaces" do
+ d = Duck.new
+ a = Farm::FarmAdapter.get(d)
+ a.should == nil
+ a = Farm::FarmAdapter.adapt(d)
+ a.value = 10
+ b = Farm::FarmAdapter.get(d)
+ b.value.should == 10
+ # Test implementation detail
+ d.instance_variables.include?(:@Farm_FarmAdapter).should == true
+ end
+
+ it "should be able to clear the adapter" do
+ d = Duck.new
+ a = OtherAdapter.adapt(d)
+ a.value.should == "I am calling you Daffy."
+ # The adapter cleared should be returned
+ OtherAdapter.clear(d).value.should == "I am calling you Daffy."
+ OtherAdapter.get(d).should == nil
+ end
+
+ context "When adapting with #adapt it" do
+ it "should be possible to pass a block to configure the adapter" do
+ d = Duck.new
+ a = OtherAdapter.adapt(d) do |x|
+ x.value = "Donald"
+ end
+ a.value.should == "Donald"
+ end
+
+ it "should be possible to pass a block to configure the adapter and get the adapted" do
+ d = Duck.new
+ a = OtherAdapter.adapt(d) do |x, o|
+ x.value = "Donald, the #{o.class.name}"
+ end
+ a.value.should == "Donald, the Duck"
+ end
+ end
+
+ context "When adapting with #adapt_new it" do
+ it "should be possible to pass a block to configure the adapter" do
+ d = Duck.new
+ a = OtherAdapter.adapt_new(d) do |x|
+ x.value = "Donald"
+ end
+ a.value.should == "Donald"
+ end
+
+ it "should be possible to pass a block to configure the adapter and get the adapted" do
+ d = Duck.new
+ a = OtherAdapter.adapt_new(d) do |x, o|
+ x.value = "Donald, the #{o.class.name}"
+ end
+ a.value.should == "Donald, the Duck"
+ end
+ end
+end
diff --git a/spec/unit/pops/containment_spec.rb b/spec/unit/pops/containment_spec.rb
new file mode 100644
index 0000000..da5fae7
--- /dev/null
+++ b/spec/unit/pops/containment_spec.rb
@@ -0,0 +1,25 @@
+require 'puppet/pops'
+require File.join(File.dirname(__FILE__), 'factory_rspec_helper')
+
+describe Puppet::Pops::Containment do
+ include FactoryRspecHelper
+
+ it "Should return an Enumerable if eAllContents is called without arguments" do
+ (literal(1) + literal(2)).current.eAllContents.is_a?(Enumerable).should == true
+ end
+
+ it "Should return all content" do
+ # Note the top object is not included (an ArithmeticOperation with + operator)
+ (literal(1) + literal(2) + literal(3)).current.eAllContents.collect {|x| x}.size.should == 4
+ end
+
+ it "Should return containing feature" do
+ left = literal(1)
+ right = literal(2)
+ op = left + right
+
+ #pending "eContainingFeature does not work on _uni containments in RGen < 0.6.1"
+ left.current.eContainingFeature.should == :left_expr
+ right.current.eContainingFeature.should == :right_expr
+ end
+end
diff --git a/spec/unit/pops/factory_rspec_helper.rb b/spec/unit/pops/factory_rspec_helper.rb
new file mode 100644
index 0000000..b11f6ee
--- /dev/null
+++ b/spec/unit/pops/factory_rspec_helper.rb
@@ -0,0 +1,77 @@
+require 'puppet/pops'
+
+module FactoryRspecHelper
+ def literal(x)
+ Puppet::Pops::Model::Factory.literal(x)
+ end
+
+ def block(*args)
+ Puppet::Pops::Model::Factory.block(*args)
+ end
+
+ def var(x)
+ Puppet::Pops::Model::Factory.var(x)
+ end
+
+ def fqn(x)
+ Puppet::Pops::Model::Factory.fqn(x)
+ end
+
+ def string(*args)
+ Puppet::Pops::Model::Factory.string(*args)
+ end
+
+ def text(x)
+ Puppet::Pops::Model::Factory.text(x)
+ end
+
+ def minus(x)
+ Puppet::Pops::Model::Factory.minus(x)
+ end
+
+ def IF(test, then_expr, else_expr=nil)
+ Puppet::Pops::Model::Factory.IF(test, then_expr, else_expr)
+ end
+
+ def UNLESS(test, then_expr, else_expr=nil)
+ Puppet::Pops::Model::Factory.UNLESS(test, then_expr, else_expr)
+ end
+
+ def CASE(test, *options)
+ Puppet::Pops::Model::Factory.CASE(test, *options)
+ end
+
+ def WHEN(values, block)
+ Puppet::Pops::Model::Factory.WHEN(values, block)
+ end
+
+ def respond_to? method
+ if Puppet::Pops::Model::Factory.respond_to? method
+ true
+ else
+ super
+ end
+ end
+
+ def method_missing(method, *args, &block)
+ if Puppet::Pops::Model::Factory.respond_to? method
+ Puppet::Pops::Model::Factory.send(method, *args, &block)
+ else
+ super
+ end
+ end
+
+ # i.e. Selector Entry 1 => 'hello'
+ def MAP(match, value)
+ Puppet::Pops::Model::Factory.MAP(match, value)
+ end
+
+ def dump(x)
+ Puppet::Pops::Model::ModelTreeDumper.new.dump(x)
+ end
+
+ def unindent x
+ (x.gsub /^#{x[/\A\s*/]}/, '').chomp
+ end
+ factory ||= Puppet::Pops::Model::Factory
+end
diff --git a/spec/unit/pops/factory_spec.rb b/spec/unit/pops/factory_spec.rb
new file mode 100644
index 0000000..3cefbc7
--- /dev/null
+++ b/spec/unit/pops/factory_spec.rb
@@ -0,0 +1,329 @@
+#! /usr/bin/env ruby
+require 'spec_helper'
+require 'puppet/pops'
+require File.join(File.dirname(__FILE__), '/factory_rspec_helper')
+
+# This file contains testing of the pops model factory
+#
+
+describe Puppet::Pops::Model::Factory do
+ include FactoryRspecHelper
+
+ context "When factory methods are invoked they should produce expected results" do
+ it "tests #var should create a VariableExpression" do
+ var('a').current.class.should == Puppet::Pops::Model::VariableExpression
+ end
+
+ it "tests #fqn should create a QualifiedName" do
+ fqn('a').current.class.should == Puppet::Pops::Model::QualifiedName
+ end
+
+ it "tests #QNAME should create a QualifiedName" do
+ QNAME('a').current.class.should == Puppet::Pops::Model::QualifiedName
+ end
+
+ it "tests #QREF should create a QualifiedReference" do
+ QREF('a').current.class.should == Puppet::Pops::Model::QualifiedReference
+ end
+
+ it "tests #block should create a BlockExpression" do
+ block().current.is_a?(Puppet::Pops::Model::BlockExpression).should == true
+ end
+
+ it "should create a literal undef on :undef" do
+ literal(:undef).current.class.should == Puppet::Pops::Model::LiteralUndef
+ end
+
+ it "should create a literal default on :default" do
+ literal(:default).current.class.should == Puppet::Pops::Model::LiteralDefault
+ end
+ end
+
+ context "When calling block_or_expression" do
+ it "A single expression should produce identical output" do
+ block_or_expression(literal(1) + literal(2)).current.is_a?(Puppet::Pops::Model::ArithmeticExpression).should == true
+ end
+
+ it "Multiple expressions should produce a block expression" do
+ built = block_or_expression(literal(1) + literal(2), literal(2) + literal(3)).current
+ built.is_a?(Puppet::Pops::Model::BlockExpression).should == true
+ built.statements.size.should == 2
+ end
+ end
+
+ context "When processing calls with CALL_NAMED" do
+ it "Should be possible to state that r-value is required" do
+ built = CALL_NAMED("foo", true, []).current
+ built.is_a?(Puppet::Pops::Model::CallNamedFunctionExpression).should == true
+ built.rval_required.should == true
+ end
+
+ it "Should produce a call expression without arguments" do
+ built = CALL_NAMED("foo", false, []).current
+ built.is_a?(Puppet::Pops::Model::CallNamedFunctionExpression).should == true
+ built.functor_expr.is_a?(Puppet::Pops::Model::QualifiedName).should == true
+ built.functor_expr.value.should == "foo"
+ built.rval_required.should == false
+ built.arguments.size.should == 0
+ end
+
+ it "Should produce a call expression with one argument" do
+ built = CALL_NAMED("foo", false, [literal(1) + literal(2)]).current
+ built.is_a?(Puppet::Pops::Model::CallNamedFunctionExpression).should == true
+ built.functor_expr.is_a?(Puppet::Pops::Model::QualifiedName).should == true
+ built.functor_expr.value.should == "foo"
+ built.rval_required.should == false
+ built.arguments.size.should == 1
+ built.arguments[0].is_a?(Puppet::Pops::Model::ArithmeticExpression).should == true
+ end
+
+ it "Should produce a call expression with two arguments" do
+ built = CALL_NAMED("foo", false, [literal(1) + literal(2), literal(1) + literal(2)]).current
+ built.is_a?(Puppet::Pops::Model::CallNamedFunctionExpression).should == true
+ built.functor_expr.is_a?(Puppet::Pops::Model::QualifiedName).should == true
+ built.functor_expr.value.should == "foo"
+ built.rval_required.should == false
+ built.arguments.size.should == 2
+ built.arguments[0].is_a?(Puppet::Pops::Model::ArithmeticExpression).should == true
+ built.arguments[1].is_a?(Puppet::Pops::Model::ArithmeticExpression).should == true
+ end
+ end
+
+ context "When creating attribute operations" do
+ it "Should produce an attribute operation for =>" do
+ built = ATTRIBUTE_OP("aname", :'=>', 'x').current
+ built.is_a?(Puppet::Pops::Model::AttributeOperation)
+ built.operator.should == :'=>'
+ built.attribute_name.should == "aname"
+ built.value_expr.is_a?(Puppet::Pops::Model::LiteralString).should == true
+ end
+
+ it "Should produce an attribute operation for +>" do
+ built = ATTRIBUTE_OP("aname", :'+>', 'x').current
+ built.is_a?(Puppet::Pops::Model::AttributeOperation)
+ built.operator.should == :'+>'
+ built.attribute_name.should == "aname"
+ built.value_expr.is_a?(Puppet::Pops::Model::LiteralString).should == true
+ end
+ end
+
+ context "When processing RESOURCE" do
+ it "Should create a Resource body" do
+ built = RESOURCE_BODY("title", [ATTRIBUTE_OP('aname', :'=>', 'x')]).current
+ built.is_a?(Puppet::Pops::Model::ResourceBody).should == true
+ built.title.is_a?(Puppet::Pops::Model::LiteralString).should == true
+ built.operations.size.should == 1
+ built.operations[0].class.should == Puppet::Pops::Model::AttributeOperation
+ built.operations[0].attribute_name.should == 'aname'
+ end
+
+ it "Should create a RESOURCE without a resource body" do
+ bodies = []
+ built = RESOURCE("rtype", bodies).current
+ built.class.should == Puppet::Pops::Model::ResourceExpression
+ built.bodies.size.should == 0
+ end
+
+ it "Should create a RESOURCE with 1 resource body" do
+ bodies = [] << RESOURCE_BODY('title', [])
+ built = RESOURCE("rtype", bodies).current
+ built.class.should == Puppet::Pops::Model::ResourceExpression
+ built.bodies.size.should == 1
+ built.bodies[0].title.value.should == 'title'
+ end
+
+ it "Should create a RESOURCE with 2 resource bodies" do
+ bodies = [] << RESOURCE_BODY('title', []) << RESOURCE_BODY('title2', [])
+ built = RESOURCE("rtype", bodies).current
+ built.class.should == Puppet::Pops::Model::ResourceExpression
+ built.bodies.size.should == 2
+ built.bodies[0].title.value.should == 'title'
+ built.bodies[1].title.value.should == 'title2'
+ end
+ end
+
+ context "When processing simple literals" do
+ it "Should produce a literal boolean from a boolean" do
+ built = literal(true).current
+ built.class.should == Puppet::Pops::Model::LiteralBoolean
+ built.value.should == true
+ built = literal(false).current
+ built.class.should == Puppet::Pops::Model::LiteralBoolean
+ built.value.should == false
+ end
+ end
+
+ context "When processing COLLECT" do
+ it "should produce a virtual query" do
+ built = VIRTUAL_QUERY(fqn('a') == literal(1)).current
+ built.class.should == Puppet::Pops::Model::VirtualQuery
+ built.expr.class.should == Puppet::Pops::Model::ComparisonExpression
+ built.expr.operator.should == :'=='
+ end
+
+ it "should produce an export query" do
+ built = EXPORTED_QUERY(fqn('a') == literal(1)).current
+ built.class.should == Puppet::Pops::Model::ExportedQuery
+ built.expr.class.should == Puppet::Pops::Model::ComparisonExpression
+ built.expr.operator.should == :'=='
+ end
+
+ it "should produce a collect expression" do
+ q = VIRTUAL_QUERY(fqn('a') == literal(1))
+ built = COLLECT(literal('t'), q, [ATTRIBUTE_OP('name', :'=>', 3)]).current
+ built.class.should == Puppet::Pops::Model::CollectExpression
+ built.operations.size.should == 1
+ end
+
+ it "should produce a collect expression without attribute operations" do
+ q = VIRTUAL_QUERY(fqn('a') == literal(1))
+ built = COLLECT(literal('t'), q, []).current
+ built.class.should == Puppet::Pops::Model::CollectExpression
+ built.operations.size.should == 0
+ end
+ end
+
+ context "When processing concatenated string(iterpolation)" do
+ it "should handle 'just a string'" do
+ built = string('blah blah').current
+ built.class.should == Puppet::Pops::Model::ConcatenatedString
+ built.segments.size == 1
+ built.segments[0].class.should == Puppet::Pops::Model::LiteralString
+ built.segments[0].value.should == "blah blah"
+ end
+
+ it "should handle one expression in the middle" do
+ built = string('blah blah', TEXT(literal(1)+literal(2)), 'blah blah').current
+ built.class.should == Puppet::Pops::Model::ConcatenatedString
+ built.segments.size == 3
+ built.segments[0].class.should == Puppet::Pops::Model::LiteralString
+ built.segments[0].value.should == "blah blah"
+ built.segments[1].class.should == Puppet::Pops::Model::TextExpression
+ built.segments[1].expr.class.should == Puppet::Pops::Model::ArithmeticExpression
+ built.segments[2].class.should == Puppet::Pops::Model::LiteralString
+ built.segments[2].value.should == "blah blah"
+ end
+
+ it "should handle one expression at the end" do
+ built = string('blah blah', TEXT(literal(1)+literal(2))).current
+ built.class.should == Puppet::Pops::Model::ConcatenatedString
+ built.segments.size == 2
+ built.segments[0].class.should == Puppet::Pops::Model::LiteralString
+ built.segments[0].value.should == "blah blah"
+ built.segments[1].class.should == Puppet::Pops::Model::TextExpression
+ built.segments[1].expr.class.should == Puppet::Pops::Model::ArithmeticExpression
+ end
+
+ it "should handle only one expression" do
+ built = string(TEXT(literal(1)+literal(2))).current
+ built.class.should == Puppet::Pops::Model::ConcatenatedString
+ built.segments.size == 1
+ built.segments[0].class.should == Puppet::Pops::Model::TextExpression
+ built.segments[0].expr.class.should == Puppet::Pops::Model::ArithmeticExpression
+ end
+
+ it "should handle several expressions" do
+ built = string(TEXT(literal(1)+literal(2)), TEXT(literal(1)+literal(2))).current
+ built.class.should == Puppet::Pops::Model::ConcatenatedString
+ built.segments.size == 2
+ built.segments[0].class.should == Puppet::Pops::Model::TextExpression
+ built.segments[0].expr.class.should == Puppet::Pops::Model::ArithmeticExpression
+ built.segments[1].class.should == Puppet::Pops::Model::TextExpression
+ built.segments[1].expr.class.should == Puppet::Pops::Model::ArithmeticExpression
+ end
+
+ it "should handle no expression" do
+ built = string().current
+ built.class.should == Puppet::Pops::Model::ConcatenatedString
+ built.segments.size == 0
+ end
+ end
+
+ context "When processing instance / resource references" do
+ it "should produce an InstanceReference without a reference" do
+ built = INSTANCE(QREF('a'), []).current
+ built.class.should == Puppet::Pops::Model::InstanceReferences
+ built.names.size.should == 0
+ end
+
+ it "should produce an InstanceReference with one reference" do
+ built = INSTANCE(QREF('a'), [QNAME('b')]).current
+ built.class.should == Puppet::Pops::Model::InstanceReferences
+ built.names.size.should == 1
+ built.names[0].value.should == 'b'
+ end
+
+ it "should produce an InstanceReference with two references" do
+ built = INSTANCE(QREF('a'), [QNAME('b'), QNAME('c')]).current
+ built.class.should == Puppet::Pops::Model::InstanceReferences
+ built.names.size.should == 2
+ built.names[0].value.should == 'b'
+ built.names[1].value.should == 'c'
+ end
+ end
+
+ context "When processing UNLESS" do
+ it "should create an UNLESS expression with then part" do
+ built = UNLESS(true, literal(1), nil).current
+ built.class.should == Puppet::Pops::Model::UnlessExpression
+ built.test.class.should == Puppet::Pops::Model::LiteralBoolean
+ built.then_expr.class.should == Puppet::Pops::Model::LiteralNumber
+ built.else_expr.class.should == Puppet::Pops::Model::Nop
+ end
+
+ it "should create an UNLESS expression with then and else parts" do
+ built = UNLESS(true, literal(1), literal(2)).current
+ built.class.should == Puppet::Pops::Model::UnlessExpression
+ built.test.class.should == Puppet::Pops::Model::LiteralBoolean
+ built.then_expr.class.should == Puppet::Pops::Model::LiteralNumber
+ built.else_expr.class.should == Puppet::Pops::Model::LiteralNumber
+ end
+ end
+
+ context "When processing IF" do
+ it "should create an IF expression with then part" do
+ built = IF(true, literal(1), nil).current
+ built.class.should == Puppet::Pops::Model::IfExpression
+ built.test.class.should == Puppet::Pops::Model::LiteralBoolean
+ built.then_expr.class.should == Puppet::Pops::Model::LiteralNumber
+ built.else_expr.class.should == Puppet::Pops::Model::Nop
+ end
+
+ it "should create an IF expression with then and else parts" do
+ built = IF(true, literal(1), literal(2)).current
+ built.class.should == Puppet::Pops::Model::IfExpression
+ built.test.class.should == Puppet::Pops::Model::LiteralBoolean
+ built.then_expr.class.should == Puppet::Pops::Model::LiteralNumber
+ built.else_expr.class.should == Puppet::Pops::Model::LiteralNumber
+ end
+ end
+
+ context "When processing a Parameter" do
+ it "should create a Parameter" do
+ # PARAM(name, expr)
+ # PARAM(name)
+ #
+ end
+ end
+
+ # LIST, HASH, KEY_ENTRY
+ context "When processing Definition" do
+ # DEFINITION(classname, arguments, statements)
+ # should accept empty arguments, and no statements
+ end
+
+ context "When processing Hostclass" do
+ # HOSTCLASS(classname, arguments, parent, statements)
+ # parent may be passed as a nop /nil - check this works, should accept empty statements (nil)
+ # should accept empty arguments
+
+ end
+
+ context "When processing Node" do
+ end
+
+ # Tested in the evaluator test already, but should be here to test factory assumptions
+ #
+ # TODO: CASE / WHEN
+ # TODO: MAP
+end
diff --git a/spec/unit/pops/issues_spec.rb b/spec/unit/pops/issues_spec.rb
new file mode 100644
index 0000000..091bd9c
--- /dev/null
+++ b/spec/unit/pops/issues_spec.rb
@@ -0,0 +1,26 @@
+#! /usr/bin/env ruby
+require 'spec_helper'
+require 'puppet/pops'
+
+describe "Puppet::Pops::Issues" do
+ include Puppet::Pops::Issues
+
+ it "should have an issue called NAME_WITH_HYPHEN" do
+ x = Puppet::Pops::Issues::NAME_WITH_HYPHEN
+ x.class.should == Puppet::Pops::Issues::Issue
+ x.issue_code.should == :NAME_WITH_HYPHEN
+ end
+
+ it "should should format a message that requires an argument" do
+ x = Puppet::Pops::Issues::NAME_WITH_HYPHEN
+ x.format(:name => 'Boo-Hoo',
+ :label => Puppet::Pops::Model::ModelLabelProvider.new,
+ :semantic => "dummy"
+ ).should == "A Ruby String may not have a name contain a hyphen. The name 'Boo-Hoo' is not legal"
+ end
+
+ it "should should format a message that does not require an argument" do
+ x = Puppet::Pops::Issues::NOT_TOP_LEVEL
+ x.format().should == "Classes, definitions, and nodes may only appear at toplevel or inside other classes"
+ end
+end
diff --git a/spec/unit/pops/label_provider_spec.rb b/spec/unit/pops/label_provider_spec.rb
new file mode 100644
index 0000000..3e82266
--- /dev/null
+++ b/spec/unit/pops/label_provider_spec.rb
@@ -0,0 +1,42 @@
+require 'spec_helper'
+require 'puppet/pops'
+
+describe Puppet::Pops::LabelProvider do
+ let(:labeler) { Puppet::Pops::LabelProvider.new }
+
+ it "prefixes words that start with a vowel with an 'an'" do
+ labeler.a_an('owl').should == 'an owl'
+ end
+
+ it "prefixes words that start with a consonant with an 'a'" do
+ labeler.a_an('bear').should == 'a bear'
+ end
+
+ it "prefixes non-word characters with an 'a'" do
+ labeler.a_an('[] expression').should == 'a [] expression'
+ end
+
+ it "ignores a single quote leading the word" do
+ labeler.a_an("'owl'").should == "an 'owl'"
+ end
+
+ it "ignores a double quote leading the word" do
+ labeler.a_an('"owl"').should == 'an "owl"'
+ end
+
+ it "capitalizes the indefinite article for a word when requested" do
+ labeler.a_an_uc('owl').should == 'An owl'
+ end
+
+ it "raises an error when missing a character to work with" do
+ expect {
+ labeler.a_an('"')
+ }.to raise_error(Puppet::DevError, /<"> does not appear to contain a word/)
+ end
+
+ it "raises an error when given an empty string" do
+ expect {
+ labeler.a_an('')
+ }.to raise_error(Puppet::DevError, /<> does not appear to contain a word/)
+ end
+end
diff --git a/spec/unit/pops/model/ast_transformer_spec.rb b/spec/unit/pops/model/ast_transformer_spec.rb
new file mode 100644
index 0000000..f2b1c9b
--- /dev/null
+++ b/spec/unit/pops/model/ast_transformer_spec.rb
@@ -0,0 +1,65 @@
+require 'spec_helper'
+
+require File.join(File.dirname(__FILE__), '/../factory_rspec_helper')
+require 'puppet/pops'
+
+describe Puppet::Pops::Model::AstTransformer do
+ include FactoryRspecHelper
+
+ let(:filename) { "the-file.pp" }
+ let(:transformer) { Puppet::Pops::Model::AstTransformer.new(filename) }
+
+ context "literal numbers" do
+ it "converts a decimal number to a string Name" do
+ ast = transform(QNAME_OR_NUMBER("10"))
+
+ ast.should be_kind_of Puppet::Parser::AST::Name
+ ast.value.should == "10"
+ end
+
+ it "converts an octal number to a string Name" do
+ ast = transform(QNAME_OR_NUMBER("020"))
+
+ ast.should be_kind_of Puppet::Parser::AST::Name
+ ast.value.should == "020"
+ end
+
+ it "converts a hex number to a string Name" do
+ ast = transform(QNAME_OR_NUMBER("0x20"))
+
+ ast.should be_kind_of Puppet::Parser::AST::Name
+ ast.value.should == "0x20"
+ end
+
+ it "converts an unknown radix to an error string" do
+ ast = transform(Puppet::Pops::Model::Factory.new(Puppet::Pops::Model::LiteralNumber, 3, 2))
+
+ ast.should be_kind_of Puppet::Parser::AST::Name
+ ast.value.should == "bad radix:3"
+ end
+ end
+
+ it "preserves the file location" do
+ model = literal(1)
+ model.record_position(location(3, 1, 10), location(3, 2, 11))
+
+ ast = transform(model)
+
+ ast.file.should == filename
+ ast.line.should == 3
+ ast.pos.should == 1
+ end
+
+ def transform(model)
+ transformer.transform(model)
+ end
+
+ def location(line, column, offset)
+ position = Puppet::Pops::Adapters::SourcePosAdapter.new
+ position.line = line
+ position.pos = column
+ position.offset = offset
+
+ position
+ end
+end
diff --git a/spec/unit/pops/model/model_spec.rb b/spec/unit/pops/model/model_spec.rb
new file mode 100644
index 0000000..5014a64
--- /dev/null
+++ b/spec/unit/pops/model/model_spec.rb
@@ -0,0 +1,37 @@
+#! /usr/bin/env ruby
+require 'spec_helper'
+require 'puppet/pops'
+
+describe Puppet::Pops::Model do
+ it "should be possible to create an instance of a model object" do
+ nop = Puppet::Pops::Model::Nop.new
+ nop.class.should == Puppet::Pops::Model::Nop
+ end
+end
+
+describe Puppet::Pops::Model::Factory do
+ Factory = Puppet::Pops::Model::Factory
+ Model = Puppet::Pops::Model
+
+ it "construct an arithmetic expression" do
+ x = Factory.literal(10) + Factory.literal(20)
+ x.is_a?(Factory).should == true
+ current = x.current
+ current.is_a?(Model::ArithmeticExpression).should == true
+ current.operator.should == :'+'
+ current.left_expr.class.should == Model::LiteralNumber
+ current.right_expr.class.should == Model::LiteralNumber
+ current.left_expr.value.should == 10
+ current.right_expr.value.should == 20
+ end
+
+ it "should be easy to compare using a model tree dumper" do
+ x = Factory.literal(10) + Factory.literal(20)
+ Puppet::Pops::Model::ModelTreeDumper.new.dump(x.current).should == "(+ 10 20)"
+ end
+
+ it "builder should apply precedence" do
+ x = Factory.literal(2) * Factory.literal(10) + Factory.literal(20)
+ Puppet::Pops::Model::ModelTreeDumper.new.dump(x.current).should == "(+ (* 2 10) 20)"
+ end
+end
diff --git a/spec/unit/pops/parser/lexer_spec.rb b/spec/unit/pops/parser/lexer_spec.rb
new file mode 100755
index 0000000..d5232e9
--- /dev/null
+++ b/spec/unit/pops/parser/lexer_spec.rb
@@ -0,0 +1,884 @@
+#! /usr/bin/env ruby
+require 'spec_helper'
+
+require 'puppet/pops'
+
+# This is a special matcher to match easily lexer output
+RSpec::Matchers.define :be_like do |*expected|
+ match do |actual|
+ diffable
+ expected.zip(actual).all? { |e,a| !e or a[0] == e or (e.is_a? Array and a[0] == e[0] and (a[1] == e[1] or (a[1].is_a?(Hash) and a[1][:value] == e[1]))) }
+ end
+end
+__ = nil
+
+module EgrammarLexerSpec
+ def self.tokens_scanned_from(s)
+ lexer = Puppet::Pops::Parser::Lexer.new
+ lexer.string = s
+ lexer.fullscan[0..-2]
+ end
+end
+
+describe Puppet::Pops::Parser::Lexer do
+ include EgrammarLexerSpec
+
+ describe "when reading strings" do
+ before { @lexer = Puppet::Pops::Parser::Lexer.new }
+
+ it "should increment the line count for every carriage return in the string" do
+ @lexer.string = "'this\nis\natest'"
+ @lexer.fullscan[0..-2]
+
+ line = @lexer.line
+ line.should == 3
+ end
+
+ it "should not increment the line count for escapes in the string" do
+ @lexer.string = "'this\\nis\\natest'"
+ @lexer.fullscan[0..-2]
+
+ @lexer.line.should == 1
+ end
+
+ it "should not think the terminator is escaped, when preceeded by an even number of backslashes" do
+ @lexer.string = "'here\nis\nthe\nstring\\\\'with\nextra\njunk"
+ @lexer.fullscan[0..-2]
+
+ @lexer.line.should == 6
+ end
+
+ {
+ 'r' => "\r",
+ 'n' => "\n",
+ 't' => "\t",
+ 's' => " "
+ }.each do |esc, expected_result|
+ it "should recognize \\#{esc} sequence" do
+ @lexer.string = "\\#{esc}'"
+ @lexer.slurpstring("'")[0].should == expected_result
+ end
+ end
+ end
+end
+
+describe Puppet::Pops::Parser::Lexer::Token, "when initializing" do
+ it "should create a regex if the first argument is a string" do
+ Puppet::Pops::Parser::Lexer::Token.new("something", :NAME).regex.should == %r{something}
+ end
+
+ it "should set the string if the first argument is one" do
+ Puppet::Pops::Parser::Lexer::Token.new("something", :NAME).string.should == "something"
+ end
+
+ it "should set the regex if the first argument is one" do
+ Puppet::Pops::Parser::Lexer::Token.new(%r{something}, :NAME).regex.should == %r{something}
+ end
+end
+
+describe Puppet::Pops::Parser::Lexer::TokenList do
+ before do
+ @list = Puppet::Pops::Parser::Lexer::TokenList.new
+ end
+
+ it "should have a method for retrieving tokens by the name" do
+ token = @list.add_token :name, "whatever"
+ @list[:name].should equal(token)
+ end
+
+ it "should have a method for retrieving string tokens by the string" do
+ token = @list.add_token :name, "whatever"
+ @list.lookup("whatever").should equal(token)
+ end
+
+ it "should add tokens to the list when directed" do
+ token = @list.add_token :name, "whatever"
+ @list[:name].should equal(token)
+ end
+
+ it "should have a method for adding multiple tokens at once" do
+ @list.add_tokens "whatever" => :name, "foo" => :bar
+ @list[:name].should_not be_nil
+ @list[:bar].should_not be_nil
+ end
+
+ it "should fail to add tokens sharing a name with an existing token" do
+ @list.add_token :name, "whatever"
+ expect { @list.add_token :name, "whatever" }.to raise_error(ArgumentError)
+ end
+
+ it "should set provided options on tokens being added" do
+ token = @list.add_token :name, "whatever", :skip_text => true
+ token.skip_text.should == true
+ end
+
+ it "should define any provided blocks as a :convert method" do
+ token = @list.add_token(:name, "whatever") do "foo" end
+ token.convert.should == "foo"
+ end
+
+ it "should store all string tokens in the :string_tokens list" do
+ one = @list.add_token(:name, "1")
+ @list.string_tokens.should be_include(one)
+ end
+
+ it "should store all regex tokens in the :regex_tokens list" do
+ one = @list.add_token(:name, %r{one})
+ @list.regex_tokens.should be_include(one)
+ end
+
+ it "should not store string tokens in the :regex_tokens list" do
+ one = @list.add_token(:name, "1")
+ @list.regex_tokens.should_not be_include(one)
+ end
+
+ it "should not store regex tokens in the :string_tokens list" do
+ one = @list.add_token(:name, %r{one})
+ @list.string_tokens.should_not be_include(one)
+ end
+
+ it "should sort the string tokens inversely by length when asked" do
+ one = @list.add_token(:name, "1")
+ two = @list.add_token(:other, "12")
+ @list.sort_tokens
+ @list.string_tokens.should == [two, one]
+ end
+end
+
+describe Puppet::Pops::Parser::Lexer::TOKENS do
+ before do
+ @lexer = Puppet::Pops::Parser::Lexer.new
+ end
+
+ {
+ :LBRACK => '[',
+ :RBRACK => ']',
+# :LBRACE => '{',
+ :RBRACE => '}',
+ :LPAREN => '(',
+ :RPAREN => ')',
+ :EQUALS => '=',
+ :ISEQUAL => '==',
+ :GREATEREQUAL => '>=',
+ :GREATERTHAN => '>',
+ :LESSTHAN => '<',
+ :LESSEQUAL => '<=',
+ :NOTEQUAL => '!=',
+ :NOT => '!',
+ :COMMA => ',',
+ :DOT => '.',
+ :COLON => ':',
+ :AT => '@',
+ :LLCOLLECT => '<<|',
+ :RRCOLLECT => '|>>',
+ :LCOLLECT => '<|',
+ :RCOLLECT => '|>',
+ :SEMIC => ';',
+ :QMARK => '?',
+ :BACKSLASH => '\\',
+ :FARROW => '=>',
+ :PARROW => '+>',
+ :APPENDS => '+=',
+ :PLUS => '+',
+ :MINUS => '-',
+ :DIV => '/',
+ :TIMES => '*',
+ :LSHIFT => '<<',
+ :RSHIFT => '>>',
+ :MATCH => '=~',
+ :NOMATCH => '!~',
+ :IN_EDGE => '->',
+ :OUT_EDGE => '<-',
+ :IN_EDGE_SUB => '~>',
+ :OUT_EDGE_SUB => '<~',
+ :PIPE => '|',
+ }.each do |name, string|
+ it "should have a token named #{name.to_s}" do
+ Puppet::Pops::Parser::Lexer::TOKENS[name].should_not be_nil
+ end
+
+ it "should match '#{string}' for the token #{name.to_s}" do
+ Puppet::Pops::Parser::Lexer::TOKENS[name].string.should == string
+ end
+ end
+
+ {
+ "case" => :CASE,
+ "class" => :CLASS,
+ "default" => :DEFAULT,
+ "define" => :DEFINE,
+# "import" => :IMPORT, # done as a function in egrammar
+ "if" => :IF,
+ "elsif" => :ELSIF,
+ "else" => :ELSE,
+ "inherits" => :INHERITS,
+ "node" => :NODE,
+ "and" => :AND,
+ "or" => :OR,
+ "undef" => :UNDEF,
+ "false" => :FALSE,
+ "true" => :TRUE,
+ "in" => :IN,
+ "unless" => :UNLESS,
+ }.each do |string, name|
+ it "should have a keyword named #{name.to_s}" do
+ Puppet::Pops::Parser::Lexer::KEYWORDS[name].should_not be_nil
+ end
+
+ it "should have the keyword for #{name.to_s} set to #{string}" do
+ Puppet::Pops::Parser::Lexer::KEYWORDS[name].string.should == string
+ end
+ end
+
+ # These tokens' strings don't matter, just that the tokens exist.
+ [:STRING, :DQPRE, :DQMID, :DQPOST, :BOOLEAN, :NAME, :NUMBER, :COMMENT, :MLCOMMENT,
+ :LBRACE, :LAMBDA,
+ :RETURN, :SQUOTE, :DQUOTE, :VARIABLE].each do |name|
+ it "should have a token named #{name.to_s}" do
+ Puppet::Pops::Parser::Lexer::TOKENS[name].should_not be_nil
+ end
+ end
+end
+
+describe Puppet::Pops::Parser::Lexer::TOKENS[:CLASSREF] do
+ before { @token = Puppet::Pops::Parser::Lexer::TOKENS[:CLASSREF] }
+
+ it "should match against single upper-case alpha-numeric terms" do
+ @token.regex.should =~ "One"
+ end
+
+ it "should match against upper-case alpha-numeric terms separated by double colons" do
+ @token.regex.should =~ "One::Two"
+ end
+
+ it "should match against many upper-case alpha-numeric terms separated by double colons" do
+ @token.regex.should =~ "One::Two::Three::Four::Five"
+ end
+
+ it "should match against upper-case alpha-numeric terms prefixed by double colons" do
+ @token.regex.should =~ "::One"
+ end
+end
+
+describe Puppet::Pops::Parser::Lexer::TOKENS[:NAME] do
+ before { @token = Puppet::Pops::Parser::Lexer::TOKENS[:NAME] }
+
+ it "should match against lower-case alpha-numeric terms" do
+ @token.regex.should =~ "one-two"
+ end
+
+ it "should return itself and the value if the matched term is not a keyword" do
+ Puppet::Pops::Parser::Lexer::KEYWORDS.expects(:lookup).returns(nil)
+ @token.convert(stub("lexer"), "myval").should == [Puppet::Pops::Parser::Lexer::TOKENS[:NAME], "myval"]
+ end
+
+ it "should return the keyword token and the value if the matched term is a keyword" do
+ keyword = stub 'keyword', :name => :testing
+ Puppet::Pops::Parser::Lexer::KEYWORDS.expects(:lookup).returns(keyword)
+ @token.convert(stub("lexer"), "myval").should == [keyword, "myval"]
+ end
+
+ it "should return the BOOLEAN token and 'true' if the matched term is the string 'true'" do
+ keyword = stub 'keyword', :name => :TRUE
+ Puppet::Pops::Parser::Lexer::KEYWORDS.expects(:lookup).returns(keyword)
+ @token.convert(stub('lexer'), "true").should == [Puppet::Pops::Parser::Lexer::TOKENS[:BOOLEAN], true]
+ end
+
+ it "should return the BOOLEAN token and 'false' if the matched term is the string 'false'" do
+ keyword = stub 'keyword', :name => :FALSE
+ Puppet::Pops::Parser::Lexer::KEYWORDS.expects(:lookup).returns(keyword)
+ @token.convert(stub('lexer'), "false").should == [Puppet::Pops::Parser::Lexer::TOKENS[:BOOLEAN], false]
+ end
+
+ it "should match against lower-case alpha-numeric terms separated by double colons" do
+ @token.regex.should =~ "one::two"
+ end
+
+ it "should match against many lower-case alpha-numeric terms separated by double colons" do
+ @token.regex.should =~ "one::two::three::four::five"
+ end
+
+ it "should match against lower-case alpha-numeric terms prefixed by double colons" do
+ @token.regex.should =~ "::one"
+ end
+
+ it "should match against nested terms starting with numbers" do
+ @token.regex.should =~ "::1one::2two::3three"
+ end
+end
+
+describe Puppet::Pops::Parser::Lexer::TOKENS[:NUMBER] do
+ before do
+ @token = Puppet::Pops::Parser::Lexer::TOKENS[:NUMBER]
+ @regex = @token.regex
+ end
+
+ it "should match against numeric terms" do
+ @regex.should =~ "2982383139"
+ end
+
+ it "should match against float terms" do
+ @regex.should =~ "29823.235"
+ end
+
+ it "should match against hexadecimal terms" do
+ @regex.should =~ "0xBEEF0023"
+ end
+
+ it "should match against float with exponent terms" do
+ @regex.should =~ "10e23"
+ end
+
+ it "should match against float terms with negative exponents" do
+ @regex.should =~ "10e-23"
+ end
+
+ it "should match against float terms with fractional parts and exponent" do
+ @regex.should =~ "1.234e23"
+ end
+end
+
+describe Puppet::Pops::Parser::Lexer::TOKENS[:COMMENT] do
+ before { @token = Puppet::Pops::Parser::Lexer::TOKENS[:COMMENT] }
+
+ it "should match against lines starting with '#'" do
+ @token.regex.should =~ "# this is a comment"
+ end
+
+ it "should be marked to get skipped" do
+ @token.skip?.should be_true
+ end
+
+ it "'s block should return the comment without the #" do
+ @token.convert(@lexer,"# this is a comment")[1].should == "this is a comment"
+ end
+end
+
+describe Puppet::Pops::Parser::Lexer::TOKENS[:MLCOMMENT] do
+ before do
+ @token = Puppet::Pops::Parser::Lexer::TOKENS[:MLCOMMENT]
+ @lexer = stub 'lexer', :line => 0
+ end
+
+ it "should match against lines enclosed with '/*' and '*/'" do
+ @token.regex.should =~ "/* this is a comment */"
+ end
+
+ it "should match multiple lines enclosed with '/*' and '*/'" do
+ @token.regex.should =~ """/*
+ this is a comment
+ */"""
+ end
+
+# # TODO: REWRITE THIS TEST TO NOT BE BASED ON INTERNALS
+# it "should increase the lexer current line number by the amount of lines spanned by the comment" do
+# @lexer.expects(:line=).with(2)
+# @token.convert(@lexer, "1\n2\n3")
+# end
+
+ it "should not greedily match comments" do
+ match = @token.regex.match("/* first */ word /* second */")
+ match[1].should == " first "
+ end
+
+ it "'s block should return the comment without the comment marks" do
+ @lexer.stubs(:line=).with(0)
+
+ @token.convert(@lexer,"/* this is a comment */")[1].should == "this is a comment"
+ end
+end
+
+describe Puppet::Pops::Parser::Lexer::TOKENS[:RETURN] do
+ before { @token = Puppet::Pops::Parser::Lexer::TOKENS[:RETURN] }
+
+ it "should match against carriage returns" do
+ @token.regex.should =~ "\n"
+ end
+
+ it "should be marked to initiate text skipping" do
+ @token.skip_text.should be_true
+ end
+end
+
+shared_examples_for "handling `-` in standard variable names for egrammar" do |prefix|
+ # Watch out - a regex might match a *prefix* on these, not just the whole
+ # word, so make sure you don't have false positive or negative results based
+ # on that.
+ legal = %w{f foo f::b foo::b f::bar foo::bar 3 foo3 3foo}
+ illegal = %w{f- f-o -f f::-o f::o- f::o-o}
+
+ ["", "::"].each do |global_scope|
+ legal.each do |name|
+ var = prefix + global_scope + name
+ it "should accept #{var.inspect} as a valid variable name" do
+ (subject.regex.match(var) || [])[0].should == var
+ end
+ end
+
+ illegal.each do |name|
+ var = prefix + global_scope + name
+ it "when `variable_with_dash` is disabled it should NOT accept #{var.inspect} as a valid variable name" do
+ Puppet[:allow_variables_with_dashes] = false
+ (subject.regex.match(var) || [])[0].should_not == var
+ end
+
+ it "when `variable_with_dash` is enabled it should NOT accept #{var.inspect} as a valid variable name" do
+ Puppet[:allow_variables_with_dashes] = true
+ (subject.regex.match(var) || [])[0].should_not == var
+ end
+ end
+ end
+end
+
+describe Puppet::Pops::Parser::Lexer::TOKENS[:DOLLAR_VAR] do
+ its(:skip_text) { should be_false }
+
+ it_should_behave_like "handling `-` in standard variable names for egrammar", '$'
+end
+
+describe Puppet::Pops::Parser::Lexer::TOKENS[:VARIABLE] do
+ its(:skip_text) { should be_false }
+
+ it_should_behave_like "handling `-` in standard variable names for egrammar", ''
+end
+
+describe "the horrible deprecation / compatibility variables with dashes" do
+ ENamesWithDashes = %w{f- f-o -f f::-o f::o- f::o-o}
+
+ { Puppet::Pops::Parser::Lexer::TOKENS[:DOLLAR_VAR_WITH_DASH] => '$',
+ Puppet::Pops::Parser::Lexer::TOKENS[:VARIABLE_WITH_DASH] => ''
+ }.each do |token, prefix|
+ describe token do
+ its(:skip_text) { should be_false }
+
+ context "when compatibly is disabled" do
+ before :each do Puppet[:allow_variables_with_dashes] = false end
+ Puppet::Pops::Parser::Lexer::TOKENS.each do |name, value|
+ it "should be unacceptable after #{name}" do
+ token.acceptable?(:after => name).should be_false
+ end
+ end
+
+ # Yes, this should still *match*, just not be acceptable.
+ ENamesWithDashes.each do |name|
+ ["", "::"].each do |global_scope|
+ var = prefix + global_scope + name
+ it "should match #{var.inspect}" do
+ subject.regex.match(var).to_a.should == [var]
+ end
+ end
+ end
+ end
+
+ context "when compatibility is enabled" do
+ before :each do Puppet[:allow_variables_with_dashes] = true end
+
+ it "should be acceptable after DQPRE" do
+ token.acceptable?(:after => :DQPRE).should be_true
+ end
+
+ ENamesWithDashes.each do |name|
+ ["", "::"].each do |global_scope|
+ var = prefix + global_scope + name
+ it "should match #{var.inspect}" do
+ subject.regex.match(var).to_a.should == [var]
+ end
+ end
+ end
+ end
+ end
+ end
+
+ context "deprecation warnings" do
+ before :each do Puppet[:allow_variables_with_dashes] = true end
+
+ it "should match a top level variable" do
+ Puppet.expects(:deprecation_warning).once
+
+ EgrammarLexerSpec.tokens_scanned_from('$foo-bar').should == [
+ [:VARIABLE, {:value=>"foo-bar", :line=>1, :pos=>1, :offset=>0, :length=>8}]
+ ]
+ end
+
+ it "does not warn about a variable without a dash" do
+ Puppet.expects(:deprecation_warning).never
+
+ EgrammarLexerSpec.tokens_scanned_from('$c').should == [
+ [:VARIABLE, {:value=>"c", :line=>1, :pos=>1, :offset=>0, :length=>2}]
+ ]
+ end
+
+ it "does not warn about referencing a class name that contains a dash" do
+ Puppet.expects(:deprecation_warning).never
+
+ EgrammarLexerSpec.tokens_scanned_from('foo-bar').should == [
+ [:NAME, {:value=>"foo-bar", :line=>1, :pos=>1, :offset=>0, :length=>7}]
+ ]
+ end
+
+ it "warns about reference to variable" do
+ Puppet.expects(:deprecation_warning).once
+
+ EgrammarLexerSpec.tokens_scanned_from('$::foo-bar::baz-quux').should == [
+ [:VARIABLE, {:value=>"::foo-bar::baz-quux", :line=>1, :pos=>1, :offset=>0, :length=>20}]
+ ]
+ end
+
+ it "warns about reference to variable interpolated in a string" do
+ Puppet.expects(:deprecation_warning).once
+
+ EgrammarLexerSpec.tokens_scanned_from('"$::foo-bar::baz-quux"').should == [
+ [:DQPRE, {:value=>"", :line=>1, :pos=>1, :offset=>0, :length=>2}], # length since preamble includes start and terminator
+ [:VARIABLE, {:value=>"::foo-bar::baz-quux", :line=>1, :pos=>3, :offset=>2, :length=>19}],
+ [:DQPOST, {:value=>"", :line=>1, :pos=>22, :offset=>21, :length=>1}],
+ ]
+ end
+
+ it "warns about reference to variable interpolated in a string as an expression" do
+ Puppet.expects(:deprecation_warning).once
+
+ EgrammarLexerSpec.tokens_scanned_from('"${::foo-bar::baz-quux}"').should == [
+ [:DQPRE, {:value=>"", :line=>1, :pos=>1, :offset=>0, :length=>3}],
+ [:VARIABLE, {:value=>"::foo-bar::baz-quux", :line=>1, :pos=>4, :offset=>3, :length=>19}],
+ [:DQPOST, {:value=>"", :line=>1, :pos=>23, :offset=>22, :length=>2}],
+ ]
+ end
+ end
+end
+
+
+describe Puppet::Pops::Parser::Lexer,"when lexing strings" do
+ {
+ %q{'single quoted string')} => [[:STRING,'single quoted string']],
+ %q{"double quoted string"} => [[:STRING,'double quoted string']],
+ %q{'single quoted string with an escaped "\\'"'} => [[:STRING,'single quoted string with an escaped "\'"']],
+ %q{'single quoted string with an escaped "\$"'} => [[:STRING,'single quoted string with an escaped "\$"']],
+ %q{'single quoted string with an escaped "\."'} => [[:STRING,'single quoted string with an escaped "\."']],
+ %q{'single quoted string with an escaped "\r\n"'} => [[:STRING,'single quoted string with an escaped "\r\n"']],
+ %q{'single quoted string with an escaped "\n"'} => [[:STRING,'single quoted string with an escaped "\n"']],
+ %q{'single quoted string with an escaped "\\\\"'} => [[:STRING,'single quoted string with an escaped "\\\\"']],
+ %q{"string with an escaped '\\"'"} => [[:STRING,"string with an escaped '\"'"]],
+ %q{"string with an escaped '\\$'"} => [[:STRING,"string with an escaped '$'"]],
+ %Q{"string with a line ending with a backslash: \\\nfoo"} => [[:STRING,"string with a line ending with a backslash: foo"]],
+ %q{"string with $v (but no braces)"} => [[:DQPRE,"string with "],[:VARIABLE,'v'],[:DQPOST,' (but no braces)']],
+ %q["string with ${v} in braces"] => [[:DQPRE,"string with "],[:VARIABLE,'v'],[:DQPOST,' in braces']],
+ %q["string with ${qualified::var} in braces"] => [[:DQPRE,"string with "],[:VARIABLE,'qualified::var'],[:DQPOST,' in braces']],
+ %q{"string with $v and $v (but no braces)"} => [[:DQPRE,"string with "],[:VARIABLE,"v"],[:DQMID," and "],[:VARIABLE,"v"],[:DQPOST," (but no braces)"]],
+ %q["string with ${v} and ${v} in braces"] => [[:DQPRE,"string with "],[:VARIABLE,"v"],[:DQMID," and "],[:VARIABLE,"v"],[:DQPOST," in braces"]],
+ %q["string with ${'a nested single quoted string'} inside it."] => [[:DQPRE,"string with "],[:STRING,'a nested single quoted string'],[:DQPOST,' inside it.']],
+ %q["string with ${['an array ',$v2]} in it."] => [[:DQPRE,"string with "],:LBRACK,[:STRING,"an array "],:COMMA,[:VARIABLE,"v2"],:RBRACK,[:DQPOST," in it."]],
+ %q{a simple "scanner" test} => [[:NAME,"a"],[:NAME,"simple"], [:STRING,"scanner"],[:NAME,"test"]],
+ %q{a simple 'single quote scanner' test} => [[:NAME,"a"],[:NAME,"simple"], [:STRING,"single quote scanner"],[:NAME,"test"]],
+ %q{a harder 'a $b \c"'} => [[:NAME,"a"],[:NAME,"harder"], [:STRING,'a $b \c"']],
+ %q{a harder "scanner test"} => [[:NAME,"a"],[:NAME,"harder"], [:STRING,"scanner test"]],
+ %q{a hardest "scanner \"test\""} => [[:NAME,"a"],[:NAME,"hardest"],[:STRING,'scanner "test"']],
+ %Q{a hardestest "scanner \\"test\\"\n"} => [[:NAME,"a"],[:NAME,"hardestest"],[:STRING,%Q{scanner "test"\n}]],
+ %q{function("call")} => [[:NAME,"function"],[:LPAREN,"("],[:STRING,'call'],[:RPAREN,")"]],
+ %q["string with ${(3+5)/4} nested math."] => [[:DQPRE,"string with "],:LPAREN,[:NAME,"3"],:PLUS,[:NAME,"5"],:RPAREN,:DIV,[:NAME,"4"],[:DQPOST," nested math."]],
+ %q["$$$$"] => [[:STRING,"$$$$"]],
+ %q["$variable"] => [[:DQPRE,""],[:VARIABLE,"variable"],[:DQPOST,""]],
+ %q["$var$other"] => [[:DQPRE,""],[:VARIABLE,"var"],[:DQMID,""],[:VARIABLE,"other"],[:DQPOST,""]],
+ %q["foo$bar$"] => [[:DQPRE,"foo"],[:VARIABLE,"bar"],[:DQPOST,"$"]],
+ %q["foo$$bar"] => [[:DQPRE,"foo$"],[:VARIABLE,"bar"],[:DQPOST,""]],
+ %q[""] => [[:STRING,""]],
+ %q["123 456 789 0"] => [[:STRING,"123 456 789 0"]],
+ %q["${123} 456 $0"] => [[:DQPRE,""],[:VARIABLE,"123"],[:DQMID," 456 "],[:VARIABLE,"0"],[:DQPOST,""]],
+ %q["$foo::::bar"] => [[:DQPRE,""],[:VARIABLE,"foo"],[:DQPOST,"::::bar"]]
+ }.each { |src,expected_result|
+ it "should handle #{src} correctly" do
+ EgrammarLexerSpec.tokens_scanned_from(src).should be_like(*expected_result)
+ end
+ }
+end
+
+describe Puppet::Pops::Parser::Lexer::TOKENS[:DOLLAR_VAR] do
+ before { @token = Puppet::Pops::Parser::Lexer::TOKENS[:DOLLAR_VAR] }
+
+ it "should match against alpha words prefixed with '$'" do
+ @token.regex.should =~ '$this_var'
+ end
+
+ it "should return the VARIABLE token and the variable name stripped of the '$'" do
+ @token.convert(stub("lexer"), "$myval").should == [Puppet::Pops::Parser::Lexer::TOKENS[:VARIABLE], "myval"]
+ end
+end
+
+describe Puppet::Pops::Parser::Lexer::TOKENS[:REGEX] do
+ before { @token = Puppet::Pops::Parser::Lexer::TOKENS[:REGEX] }
+
+ it "should match against any expression enclosed in //" do
+ @token.regex.should =~ '/this is a regex/'
+ end
+
+ it 'should not match if there is \n in the regex' do
+ @token.regex.should_not =~ "/this is \n a regex/"
+ end
+
+ describe "when scanning" do
+ it "should not consider escaped slashes to be the end of a regex" do
+ EgrammarLexerSpec.tokens_scanned_from("$x =~ /this \\/ foo/").should be_like(__,__,[:REGEX,%r{this / foo}])
+ end
+
+ it "should not lex chained division as a regex" do
+ EgrammarLexerSpec.tokens_scanned_from("$x = $a/$b/$c").collect { |name, data| name }.should_not be_include( :REGEX )
+ end
+
+ it "should accept a regular expression after NODE" do
+ EgrammarLexerSpec.tokens_scanned_from("node /www.*\.mysite\.org/").should be_like(__,[:REGEX,Regexp.new("www.*\.mysite\.org")])
+ end
+
+ it "should accept regular expressions in a CASE" do
+ s = %q{case $variable {
+ "something": {$othervar = 4096 / 2}
+ /regex/: {notice("this notably sucks")}
+ }
+ }
+ EgrammarLexerSpec.tokens_scanned_from(s).should be_like(
+ :CASE,:VARIABLE,:LBRACE,:STRING,:COLON,:LBRACE,:VARIABLE,:EQUALS,:NAME,:DIV,:NAME,:RBRACE,[:REGEX,/regex/],:COLON,:LBRACE,:NAME,:LPAREN,:STRING,:RPAREN,:RBRACE,:RBRACE
+ )
+ end
+ end
+
+ it "should return the REGEX token and a Regexp" do
+ @token.convert(stub("lexer"), "/myregex/").should == [Puppet::Pops::Parser::Lexer::TOKENS[:REGEX], Regexp.new(/myregex/)]
+ end
+end
+
+describe Puppet::Pops::Parser::Lexer, "when lexing comments" do
+ before { @lexer = Puppet::Pops::Parser::Lexer.new }
+
+ it "should skip whitespace before lexing the next token after a non-token" do
+ EgrammarLexerSpec.tokens_scanned_from("/* 1\n\n */ \ntest").should be_like([:NAME, "test"])
+ end
+end
+
+# FIXME: We need to rewrite all of these tests, but I just don't want to take the time right now.
+describe "Puppet::Pops::Parser::Lexer in the old tests" do
+ before { @lexer = Puppet::Pops::Parser::Lexer.new }
+
+ it "should do simple lexing" do
+ {
+ %q{\\} => [[:BACKSLASH,"\\"]],
+ %q{simplest scanner test} => [[:NAME,"simplest"],[:NAME,"scanner"],[:NAME,"test"]],
+ %Q{returned scanner test\n} => [[:NAME,"returned"],[:NAME,"scanner"],[:NAME,"test"]]
+ }.each { |source,expected|
+ EgrammarLexerSpec.tokens_scanned_from(source).should be_like(*expected)
+ }
+ end
+
+ it "should fail usefully" do
+ expect { EgrammarLexerSpec.tokens_scanned_from('^') }.to raise_error(RuntimeError)
+ end
+
+ it "should fail if the string is not set" do
+ expect { @lexer.fullscan }.to raise_error(Puppet::LexError)
+ end
+
+ it "should correctly identify keywords" do
+ EgrammarLexerSpec.tokens_scanned_from("case").should be_like([:CASE, "case"])
+ end
+
+ it "should correctly parse class references" do
+ %w{Many Different Words A Word}.each { |t| EgrammarLexerSpec.tokens_scanned_from(t).should be_like([:CLASSREF,t])}
+ end
+
+ # #774
+ it "should correctly parse namespaced class refernces token" do
+ %w{Foo ::Foo Foo::Bar ::Foo::Bar}.each { |t| EgrammarLexerSpec.tokens_scanned_from(t).should be_like([:CLASSREF, t]) }
+ end
+
+ it "should correctly parse names" do
+ %w{this is a bunch of names}.each { |t| EgrammarLexerSpec.tokens_scanned_from(t).should be_like([:NAME,t]) }
+ end
+
+ it "should correctly parse names with numerals" do
+ %w{1name name1 11names names11}.each { |t| EgrammarLexerSpec.tokens_scanned_from(t).should be_like([:NAME,t]) }
+ end
+
+ it "should correctly parse empty strings" do
+ expect { EgrammarLexerSpec.tokens_scanned_from('$var = ""') }.to_not raise_error
+ end
+
+ it "should correctly parse virtual resources" do
+ EgrammarLexerSpec.tokens_scanned_from("@type {").should be_like([:AT, "@"], [:NAME, "type"], [:LBRACE, "{"])
+ end
+
+ it "should correctly deal with namespaces" do
+ @lexer.string = %{class myclass}
+ @lexer.fullscan
+ @lexer.namespace.should == "myclass"
+
+ @lexer.namepop
+ @lexer.namespace.should == ""
+
+ @lexer.string = "class base { class sub { class more"
+ @lexer.fullscan
+ @lexer.namespace.should == "base::sub::more"
+
+ @lexer.namepop
+ @lexer.namespace.should == "base::sub"
+ end
+
+ it "should not put class instantiation on the namespace" do
+ @lexer.string = "class base { class sub { class { mode"
+ @lexer.fullscan
+ @lexer.namespace.should == "base::sub"
+ end
+
+ it "should correctly handle fully qualified names" do
+ @lexer.string = "class base { class sub::more {"
+ @lexer.fullscan
+ @lexer.namespace.should == "base::sub::more"
+
+ @lexer.namepop
+ @lexer.namespace.should == "base"
+ end
+
+ it "should correctly lex variables" do
+ ["$variable", "$::variable", "$qualified::variable", "$further::qualified::variable"].each do |string|
+ EgrammarLexerSpec.tokens_scanned_from(string).should be_like([:VARIABLE,string.sub(/^\$/,'')])
+ end
+ end
+
+ it "should end variables at `-`" do
+ EgrammarLexerSpec.tokens_scanned_from('$hyphenated-variable').
+ should be_like [:VARIABLE, "hyphenated"], [:MINUS, '-'], [:NAME, 'variable']
+ end
+
+ it "should not include whitespace in a variable" do
+ EgrammarLexerSpec.tokens_scanned_from("$foo bar").should_not be_like([:VARIABLE, "foo bar"])
+ end
+ it "should not include excess colons in a variable" do
+ EgrammarLexerSpec.tokens_scanned_from("$foo::::bar").should_not be_like([:VARIABLE, "foo::::bar"])
+ end
+end
+
+describe "Puppet::Pops::Parser::Lexer in the old tests when lexing example files" do
+ my_fixtures('*.pp') do |file|
+ it "should correctly lex #{file}" do
+ lexer = Puppet::Pops::Parser::Lexer.new
+ lexer.file = file
+ expect { lexer.fullscan }.to_not raise_error
+ end
+ end
+end
+
+describe "when trying to lex an non-existent file" do
+ include PuppetSpec::Files
+
+ it "should return an empty list of tokens" do
+ lexer = Puppet::Pops::Parser::Lexer.new
+ lexer.file = nofile = tmpfile('lexer')
+ File.exists?(nofile).should == false
+
+ lexer.fullscan.should == [[false,false]]
+ end
+end
+
+describe "when string quotes are not closed" do
+ it "should report with message including an \" opening quote" do
+ expect { EgrammarLexerSpec.tokens_scanned_from('$var = "') }.to raise_error(/after '"'/)
+ end
+
+ it "should report with message including an \' opening quote" do
+ expect { EgrammarLexerSpec.tokens_scanned_from('$var = \'') }.to raise_error(/after "'"/)
+ end
+
+ it "should report <eof> if immediately followed by eof" do
+ expect { EgrammarLexerSpec.tokens_scanned_from('$var = "') }.to raise_error(/followed by '<eof>'/)
+ end
+
+ it "should report max 5 chars following quote" do
+ expect { EgrammarLexerSpec.tokens_scanned_from('$var = "123456') }.to raise_error(/followed by '12345...'/)
+ end
+
+ it "should escape control chars" do
+ expect { EgrammarLexerSpec.tokens_scanned_from('$var = "12\n3456') }.to raise_error(/followed by '12\\n3...'/)
+ end
+
+ it "should resport position of opening quote" do
+ expect { EgrammarLexerSpec.tokens_scanned_from('$var = "123456') }.to raise_error(/at line 1:8/)
+ expect { EgrammarLexerSpec.tokens_scanned_from('$var = "123456') }.to raise_error(/at line 1:9/)
+ end
+end
+
+describe "when lexing number, bad input should not go unpunished" do
+ it "should slap bad octal as such" do
+ expect { EgrammarLexerSpec.tokens_scanned_from('$var = 0778') }.to raise_error(/Not a valid octal/)
+ end
+
+ it "should slap bad hex as such" do
+ expect { EgrammarLexerSpec.tokens_scanned_from('$var = 0xFG') }.to raise_error(/Not a valid hex/)
+ expect { EgrammarLexerSpec.tokens_scanned_from('$var = 0xfg') }.to raise_error(/Not a valid hex/)
+ end
+ # Note, bad decimals are probably impossible to enter, as they are not recognized as complete numbers, instead,
+ # the error will be something else, depending on what follows some initial digit.
+ #
+end
+
+describe "when lexing interpolation detailed positioning should be correct" do
+ it "should correctly position a string without interpolation" do
+ EgrammarLexerSpec.tokens_scanned_from('"not interpolated"').should be_like(
+ [:STRING, {:value=>"not interpolated", :line=>1, :offset=>0, :pos=>1, :length=>18}])
+ end
+
+ it "should correctly position a string with false start in interpolation" do
+ EgrammarLexerSpec.tokens_scanned_from('"not $$$ rpolated"').should be_like(
+ [:STRING, {:value=>"not $$$ rpolated", :line=>1, :offset=>0, :pos=>1, :length=>18}])
+ end
+
+ it "should correctly position pre-mid-end interpolation " do
+ EgrammarLexerSpec.tokens_scanned_from('"pre $x mid $y end"').should be_like(
+ [:DQPRE, {:value=>"pre ", :line=>1, :offset=>0, :pos=>1, :length=>6}],
+ [:VARIABLE, {:value=>"x", :line=>1, :offset=>6, :pos=>7, :length=>1}],
+ [:DQMID, {:value=>" mid ", :line=>1, :offset=>7, :pos=>8, :length=>6}],
+ [:VARIABLE, {:value=>"y", :line=>1, :offset=>13, :pos=>14, :length=>1}],
+ [:DQPOST, {:value=>" end", :line=>1, :offset=>14, :pos=>15, :length=>5}]
+ )
+ end
+
+ it "should correctly position pre-mid-end interpolation using ${} " do
+ EgrammarLexerSpec.tokens_scanned_from('"pre ${x} mid ${y} end"').should be_like(
+ [:DQPRE, {:value=>"pre ", :line=>1, :offset=>0, :pos=>1, :length=>7}],
+ [:VARIABLE, {:value=>"x", :line=>1, :offset=>7, :pos=>8, :length=>1}],
+ [:DQMID, {:value=>" mid ", :line=>1, :offset=>8, :pos=>9, :length=>8}],
+ [:VARIABLE, {:value=>"y", :line=>1, :offset=>16, :pos=>17, :length=>1}],
+ [:DQPOST, {:value=>" end", :line=>1, :offset=>17, :pos=>18, :length=>6}]
+ )
+ end
+
+ it "should correctly position pre-end interpolation using ${} with f call" do
+ EgrammarLexerSpec.tokens_scanned_from('"pre ${x()} end"').should be_like(
+ [:DQPRE, {:value=>"pre ", :line=>1, :offset=>0, :pos=>1, :length=>7}],
+ [:NAME, {:value=>"x", :line=>1, :offset=>7, :pos=>8, :length=>1}],
+ [:LPAREN, {:value=>"(", :line=>1, :offset=>8, :pos=>9, :length=>1}],
+ [:RPAREN, {:value=>")", :line=>1, :offset=>9, :pos=>10, :length=>1}],
+ [:DQPOST, {:value=>" end", :line=>1, :offset=>10, :pos=>11, :length=>6}]
+ )
+ end
+
+ it "should correctly position pre-end interpolation using ${} with $x" do
+ EgrammarLexerSpec.tokens_scanned_from('"pre ${$x} end"').should be_like(
+ [:DQPRE, {:value=>"pre ", :line=>1, :offset=>0, :pos=>1, :length=>7}],
+ [:VARIABLE, {:value=>"x", :line=>1, :offset=>7, :pos=>8, :length=>2}],
+ [:DQPOST, {:value=>" end", :line=>1, :offset=>9, :pos=>10, :length=>6}]
+ )
+ end
+
+ it "should correctly position pre-end interpolation across lines" do
+ EgrammarLexerSpec.tokens_scanned_from(%Q["pre ${\n$x} end"]).should be_like(
+ [:DQPRE, {:value=>"pre ", :line=>1, :offset=>0, :pos=>1, :length=>7}],
+ [:VARIABLE, {:value=>"x", :line=>2, :offset=>8, :pos=>1, :length=>2}],
+ [:DQPOST, {:value=>" end", :line=>2, :offset=>10, :pos=>3, :length=>6}]
+ )
+ end
+
+ it "should correctly position interpolation across lines when strings have embedded newlines" do
+ EgrammarLexerSpec.tokens_scanned_from(%Q["pre \n\n${$x}\n mid$y"]).should be_like(
+ [:DQPRE, {:value=>"pre \n\n", :line=>1, :offset=>0, :pos=>1, :length=>9}],
+ [:VARIABLE, {:value=>"x", :line=>3, :offset=>9, :pos=>3, :length=>2}],
+ [:DQMID, {:value=>"\n mid", :line=>3, :offset=>11, :pos=>5, :length=>7}],
+ [:VARIABLE, {:value=>"y", :line=>4, :offset=>18, :pos=>6, :length=>1}]
+ )
+ end
+end
diff --git a/spec/unit/pops/parser/parse_basic_expressions_spec.rb b/spec/unit/pops/parser/parse_basic_expressions_spec.rb
new file mode 100644
index 0000000..6560e62
--- /dev/null
+++ b/spec/unit/pops/parser/parse_basic_expressions_spec.rb
@@ -0,0 +1,248 @@
+#! /usr/bin/env ruby
+require 'spec_helper'
+require 'puppet/pops'
+
+# relative to this spec file (./) does not work as this file is loaded by rspec
+require File.join(File.dirname(__FILE__), '/parser_rspec_helper')
+
+describe "egrammar parsing basic expressions" do
+ include ParserRspecHelper
+
+ context "When the parser parses arithmetic" do
+ context "with Integers" do
+ it "$a = 2 + 2" do; dump(parse("$a = 2 + 2")).should == "(= $a (+ 2 2))" ; end
+ it "$a = 7 - 3" do; dump(parse("$a = 7 - 3")).should == "(= $a (- 7 3))" ; end
+ it "$a = 6 * 3" do; dump(parse("$a = 6 * 3")).should == "(= $a (* 6 3))" ; end
+ it "$a = 6 / 3" do; dump(parse("$a = 6 / 3")).should == "(= $a (/ 6 3))" ; end
+ it "$a = 6 % 3" do; dump(parse("$a = 6 % 3")).should == "(= $a (% 6 3))" ; end
+ it "$a = -(6/3)" do; dump(parse("$a = -(6/3)")).should == "(= $a (- (/ 6 3)))" ; end
+ it "$a = -6/3" do; dump(parse("$a = -6/3")).should == "(= $a (/ (- 6) 3))" ; end
+ it "$a = 8 >> 1 " do; dump(parse("$a = 8 >> 1")).should == "(= $a (>> 8 1))" ; end
+ it "$a = 8 << 1 " do; dump(parse("$a = 8 << 1")).should == "(= $a (<< 8 1))" ; end
+ end
+
+ context "with Floats" do
+ it "$a = 2.2 + 2.2" do; dump(parse("$a = 2.2 + 2.2")).should == "(= $a (+ 2.2 2.2))" ; end
+ it "$a = 7.7 - 3.3" do; dump(parse("$a = 7.7 - 3.3")).should == "(= $a (- 7.7 3.3))" ; end
+ it "$a = 6.1 * 3.1" do; dump(parse("$a = 6.1 - 3.1")).should == "(= $a (- 6.1 3.1))" ; end
+ it "$a = 6.6 / 3.3" do; dump(parse("$a = 6.6 / 3.3")).should == "(= $a (/ 6.6 3.3))" ; end
+ it "$a = -(6.0/3.0)" do; dump(parse("$a = -(6.0/3.0)")).should == "(= $a (- (/ 6.0 3.0)))" ; end
+ it "$a = -6.0/3.0" do; dump(parse("$a = -6.0/3.0")).should == "(= $a (/ (- 6.0) 3.0))" ; end
+ it "$a = 3.14 << 2" do; dump(parse("$a = 3.14 << 2")).should == "(= $a (<< 3.14 2))" ; end
+ it "$a = 3.14 >> 2" do; dump(parse("$a = 3.14 >> 2")).should == "(= $a (>> 3.14 2))" ; end
+ end
+
+ context "with hex and octal Integer values" do
+ it "$a = 0xAB + 0xCD" do; dump(parse("$a = 0xAB + 0xCD")).should == "(= $a (+ 0xAB 0xCD))" ; end
+ it "$a = 0777 - 0333" do; dump(parse("$a = 0777 - 0333")).should == "(= $a (- 0777 0333))" ; end
+ end
+
+ context "with strings requiring boxing to Numeric" do
+ # Test that numbers in string form does not turn into numbers
+ it "$a = '2' + '2'" do; dump(parse("$a = '2' + '2'")).should == "(= $a (+ '2' '2'))" ; end
+ it "$a = '2.2' + '0.2'" do; dump(parse("$a = '2.2' + '0.2'")).should == "(= $a (+ '2.2' '0.2'))" ; end
+ it "$a = '0xab' + '0xcd'" do; dump(parse("$a = '0xab' + '0xcd'")).should == "(= $a (+ '0xab' '0xcd'))" ; end
+ it "$a = '0777' + '0333'" do; dump(parse("$a = '0777' + '0333'")).should == "(= $a (+ '0777' '0333'))" ; end
+ end
+
+ context "precedence should be correct" do
+ it "$a = 1 + 2 * 3" do; dump(parse("$a = 1 + 2 * 3")).should == "(= $a (+ 1 (* 2 3)))"; end
+ it "$a = 1 + 2 % 3" do; dump(parse("$a = 1 + 2 % 3")).should == "(= $a (+ 1 (% 2 3)))"; end
+ it "$a = 1 + 2 / 3" do; dump(parse("$a = 1 + 2 / 3")).should == "(= $a (+ 1 (/ 2 3)))"; end
+ it "$a = 1 + 2 << 3" do; dump(parse("$a = 1 + 2 << 3")).should == "(= $a (<< (+ 1 2) 3))"; end
+ it "$a = 1 + 2 >> 3" do; dump(parse("$a = 1 + 2 >> 3")).should == "(= $a (>> (+ 1 2) 3))"; end
+ end
+
+ context "parentheses alter precedence" do
+ it "$a = (1 + 2) * 3" do; dump(parse("$a = (1 + 2) * 3")).should == "(= $a (* (+ 1 2) 3))"; end
+ it "$a = (1 + 2) / 3" do; dump(parse("$a = (1 + 2) / 3")).should == "(= $a (/ (+ 1 2) 3))"; end
+ end
+ end
+
+ context "When the evaluator performs boolean operations" do
+ context "using operators AND OR NOT" do
+ it "$a = true and true" do; dump(parse("$a = true and true")).should == "(= $a (&& true true))"; end
+ it "$a = true or true" do; dump(parse("$a = true or true")).should == "(= $a (|| true true))" ; end
+ it "$a = !true" do; dump(parse("$a = !true")).should == "(= $a (! true))" ; end
+ end
+
+ context "precedence should be correct" do
+ it "$a = false or true and true" do
+ dump(parse("$a = false or true and true")).should == "(= $a (|| false (&& true true)))"
+ end
+
+ it "$a = (false or true) and true" do
+ dump(parse("$a = (false or true) and true")).should == "(= $a (&& (|| false true) true))"
+ end
+
+ it "$a = !true or true and true" do
+ dump(parse("$a = !false or true and true")).should == "(= $a (|| (! false) (&& true true)))"
+ end
+ end
+
+ # Possibly change to check of literal expressions
+ context "on values requiring boxing to Boolean" do
+ it "'x' == true" do
+ dump(parse("! 'x'")).should == "(! 'x')"
+ end
+
+ it "'' == false" do
+ dump(parse("! ''")).should == "(! '')"
+ end
+
+ it ":undef == false" do
+ dump(parse("! undef")).should == "(! :undef)"
+ end
+ end
+ end
+
+ context "When parsing comparisons" do
+ context "of string values" do
+ it "$a = 'a' == 'a'" do; dump(parse("$a = 'a' == 'a'")).should == "(= $a (== 'a' 'a'))" ; end
+ it "$a = 'a' != 'a'" do; dump(parse("$a = 'a' != 'a'")).should == "(= $a (!= 'a' 'a'))" ; end
+ it "$a = 'a' < 'b'" do; dump(parse("$a = 'a' < 'b'")).should == "(= $a (< 'a' 'b'))" ; end
+ it "$a = 'a' > 'b'" do; dump(parse("$a = 'a' > 'b'")).should == "(= $a (> 'a' 'b'))" ; end
+ it "$a = 'a' <= 'b'" do; dump(parse("$a = 'a' <= 'b'")).should == "(= $a (<= 'a' 'b'))" ; end
+ it "$a = 'a' >= 'b'" do; dump(parse("$a = 'a' >= 'b'")).should == "(= $a (>= 'a' 'b'))" ; end
+ end
+
+ context "of integer values" do
+ it "$a = 1 == 1" do; dump(parse("$a = 1 == 1")).should == "(= $a (== 1 1))" ; end
+ it "$a = 1 != 1" do; dump(parse("$a = 1 != 1")).should == "(= $a (!= 1 1))" ; end
+ it "$a = 1 < 2" do; dump(parse("$a = 1 < 2")).should == "(= $a (< 1 2))" ; end
+ it "$a = 1 > 2" do; dump(parse("$a = 1 > 2")).should == "(= $a (> 1 2))" ; end
+ it "$a = 1 <= 2" do; dump(parse("$a = 1 <= 2")).should == "(= $a (<= 1 2))" ; end
+ it "$a = 1 >= 2" do; dump(parse("$a = 1 >= 2")).should == "(= $a (>= 1 2))" ; end
+ end
+
+ context "of regular expressions (parse errors)" do
+ # Not supported in concrete syntax
+ it "$a = /.*/ == /.*/" do
+ expect { parse("$a = /.*/ == /.*/") }.to raise_error(Puppet::ParseError)
+ end
+
+ it "$a = /.*/ != /a.*/" do
+ expect { parse("$a = /.*/ != /.*/") }.to raise_error(Puppet::ParseError)
+ end
+ end
+ end
+
+ context "When parsing Regular Expression matching" do
+ it "$a = 'a' =~ /.*/" do; dump(parse("$a = 'a' =~ /.*/")).should == "(= $a (=~ 'a' /.*/))" ; end
+ it "$a = 'a' =~ '.*'" do; dump(parse("$a = 'a' =~ '.*'")).should == "(= $a (=~ 'a' '.*'))" ; end
+ it "$a = 'a' !~ /b.*/" do; dump(parse("$a = 'a' !~ /b.*/")).should == "(= $a (!~ 'a' /b.*/))" ; end
+ it "$a = 'a' !~ 'b.*'" do; dump(parse("$a = 'a' !~ 'b.*'")).should == "(= $a (!~ 'a' 'b.*'))" ; end
+ end
+
+ context "When parsing Lists" do
+ it "$a = []" do
+ dump(parse("$a = []")).should == "(= $a ([]))"
+ end
+
+ it "$a = [1]" do
+ dump(parse("$a = [1]")).should == "(= $a ([] 1))"
+ end
+
+ it "$a = [1,2,3]" do
+ dump(parse("$a = [1,2,3]")).should == "(= $a ([] 1 2 3))"
+ end
+
+ it "[...[...[]]] should create nested arrays without trouble" do
+ dump(parse("$a = [1,[2.0, 2.1, [2.2]],[3.0, 3.1]]")).should == "(= $a ([] 1 ([] 2.0 2.1 ([] 2.2)) ([] 3.0 3.1)))"
+ end
+
+ it "$a = [2 + 2]" do
+ dump(parse("$a = [2+2]")).should == "(= $a ([] (+ 2 2)))"
+ end
+
+ it "$a [1,2,3] == [1,2,3]" do
+ dump(parse("$a = [1,2,3] == [1,2,3]")).should == "(= $a (== ([] 1 2 3) ([] 1 2 3)))"
+ end
+ end
+
+ context "When parsing indexed access" do
+ it "$a = $b[2]" do
+ dump(parse("$a = $b[2]")).should == "(= $a (slice $b 2))"
+ end
+
+ it "$a = [1, 2, 3][2]" do
+ dump(parse("$a = [1,2,3][2]")).should == "(= $a (slice ([] 1 2 3) 2))"
+ end
+
+ it "$a = {'a' => 1, 'b' => 2}['b']" do
+ dump(parse("$a = {'a'=>1,'b' =>2}[b]")).should == "(= $a (slice ({} ('a' 1) ('b' 2)) b))"
+ end
+ end
+
+ context "When parsing assignments" do
+ it "Should allow simple assignment" do
+ dump(parse("$a = 10")).should == "(= $a 10)"
+ end
+
+ it "Should allow chained assignment" do
+ dump(parse("$a = $b = 10")).should == "(= $a (= $b 10))"
+ end
+
+ it "Should allow chained assignment with expressions" do
+ dump(parse("$a = 1 + ($b = 10)")).should == "(= $a (+ 1 (= $b 10)))"
+ end
+ end
+
+ context "When parsing Hashes" do
+ it "should create a Hash when evaluating a LiteralHash" do
+ dump(parse("$a = {'a'=>1,'b'=>2}")).should == "(= $a ({} ('a' 1) ('b' 2)))"
+ end
+
+ it "$a = {...{...{}}} should create nested hashes without trouble" do
+ dump(parse("$a = {'a'=>1,'b'=>{'x'=>2.1,'y'=>2.2}}")).should == "(= $a ({} ('a' 1) ('b' ({} ('x' 2.1) ('y' 2.2)))))"
+ end
+
+ it "$a = {'a'=> 2 + 2} should evaluate values in entries" do
+ dump(parse("$a = {'a'=>2+2}")).should == "(= $a ({} ('a' (+ 2 2))))"
+ end
+
+ it "$a = {'a'=> 1, 'b'=>2} == {'a'=> 1, 'b'=>2}" do
+ dump(parse("$a = {'a'=>1,'b'=>2} == {'a'=>1,'b'=>2}")).should == "(= $a (== ({} ('a' 1) ('b' 2)) ({} ('a' 1) ('b' 2))))"
+ end
+
+ it "$a = {'a'=> 1, 'b'=>2} != {'x'=> 1, 'y'=>3}" do
+ dump(parse("$a = {'a'=>1,'b'=>2} != {'a'=>1,'b'=>2}")).should == "(= $a (!= ({} ('a' 1) ('b' 2)) ({} ('a' 1) ('b' 2))))"
+ end
+ end
+
+ context "When parsing the 'in' operator" do
+ it "with integer in a list" do
+ dump(parse("$a = 1 in [1,2,3]")).should == "(= $a (in 1 ([] 1 2 3)))"
+ end
+
+ it "with string key in a hash" do
+ dump(parse("$a = 'a' in {'x'=>1, 'a'=>2, 'y'=> 3}")).should == "(= $a (in 'a' ({} ('x' 1) ('a' 2) ('y' 3))))"
+ end
+
+ it "with substrings of a string" do
+ dump(parse("$a = 'ana' in 'bananas'")).should == "(= $a (in 'ana' 'bananas'))"
+ end
+
+ it "with sublist in a list" do
+ dump(parse("$a = [2,3] in [1,2,3]")).should == "(= $a (in ([] 2 3) ([] 1 2 3)))"
+ end
+ end
+
+ context "When parsing string interpolation" do
+ it "should interpolate a bare word as a variable name, \"${var}\"" do
+ dump(parse("$a = \"$var\"")).should == "(= $a (cat '' (str $var) ''))"
+ end
+
+ it "should interpolate a variable in a text expression, \"${$var}\"" do
+ dump(parse("$a = \"${$var}\"")).should == "(= $a (cat '' (str $var) ''))"
+ end
+
+ it "should interpolate a variable, \"yo${var}yo\"" do
+ dump(parse("$a = \"yo${var}yo\"")).should == "(= $a (cat 'yo' (str $var) 'yo'))"
+ end
+
+ it "should interpolate any expression in a text expression, \"${var*2}\"" do
+ dump(parse("$a = \"yo${var+2}yo\"")).should == "(= $a (cat 'yo' (str (+ $var 2)) 'yo'))"
+ end
+ end
+end
diff --git a/spec/unit/pops/parser/parse_calls_spec.rb b/spec/unit/pops/parser/parse_calls_spec.rb
new file mode 100644
index 0000000..62012a4
--- /dev/null
+++ b/spec/unit/pops/parser/parse_calls_spec.rb
@@ -0,0 +1,93 @@
+#! /usr/bin/env ruby
+require 'spec_helper'
+require 'puppet/pops'
+
+# relative to this spec file (./) does not work as this file is loaded by rspec
+require File.join(File.dirname(__FILE__), '/parser_rspec_helper')
+
+describe "egrammar parsing function calls" do
+ include ParserRspecHelper
+
+ context "When parsing calls as statements" do
+ context "in top level scope" do
+ it "foo()" do
+ dump(parse("foo()")).should == "(invoke foo)"
+ end
+
+ it "foo bar" do
+ dump(parse("foo bar")).should == "(invoke foo bar)"
+ end
+
+ it "foo(bar)" do
+ dump(parse("foo(bar)")).should == "(invoke foo bar)"
+ end
+
+ it "foo(bar,)" do
+ dump(parse("foo(bar,)")).should == "(invoke foo bar)"
+ end
+
+ it "foo(bar, fum,)" do
+ dump(parse("foo(bar,fum,)")).should == "(invoke foo bar fum)"
+ end
+ end
+
+ context "in nested scopes" do
+ it "if true { foo() }" do
+ dump(parse("if true {foo()}")).should == "(if true\n (then (invoke foo)))"
+ end
+
+ it "if true { foo bar}" do
+ dump(parse("if true {foo bar}")).should == "(if true\n (then (invoke foo bar)))"
+ end
+ end
+ end
+
+ context "When parsing calls as expressions" do
+ it "$a = foo()" do
+ dump(parse("$a = foo()")).should == "(= $a (call foo))"
+ end
+
+ it "$a = foo(bar)" do
+ dump(parse("$a = foo()")).should == "(= $a (call foo))"
+ end
+
+ # # For regular grammar where a bare word can not be a "statement"
+ # it "$a = foo bar # illegal, must have parentheses" do
+ # expect { dump(parse("$a = foo bar"))}.to raise_error(Puppet::ParseError)
+ # end
+
+ # For egrammar where a bare word can be a "statement"
+ it "$a = foo bar # illegal, must have parentheses" do
+ dump(parse("$a = foo bar")).should == "(block (= $a foo) bar)"
+ end
+
+ context "in nested scopes" do
+ it "if true { $a = foo() }" do
+ dump(parse("if true { $a = foo()}")).should == "(if true\n (then (= $a (call foo))))"
+ end
+
+ it "if true { $a= foo(bar)}" do
+ dump(parse("if true {$a = foo(bar)}")).should == "(if true\n (then (= $a (call foo bar))))"
+ end
+ end
+ end
+
+ context "When parsing method calls" do
+ it "$a.foo" do
+ dump(parse("$a.foo")).should == "(call-method (. $a foo))"
+ end
+
+ it "$a.foo {|| }" do
+ dump(parse("$a.foo || { }")).should == "(call-method (. $a foo) (lambda ()))"
+ end
+
+ it "$a.foo {|$x| }" do
+ dump(parse("$a.foo {|$x| }")).should == "(call-method (. $a foo) (lambda (parameters x) ()))"
+ end
+
+ it "$a.foo {|$x| }" do
+ dump(parse("$a.foo {|$x| $b = $x}")).should ==
+ "(call-method (. $a foo) (lambda (parameters x) (block (= $b $x))))"
+ end
+ end
+end
diff --git a/spec/unit/pops/parser/parse_conditionals_spec.rb b/spec/unit/pops/parser/parse_conditionals_spec.rb
new file mode 100644
index 0000000..b0cb36b
--- /dev/null
+++ b/spec/unit/pops/parser/parse_conditionals_spec.rb
@@ -0,0 +1,159 @@
+#! /usr/bin/env ruby
+require 'spec_helper'
+require 'puppet/pops'
+
+# relative to this spec file (./) does not work as this file is loaded by rspec
+require File.join(File.dirname(__FILE__), '/parser_rspec_helper')
+
+describe "egrammar parsing conditionals" do
+ include ParserRspecHelper
+
+ context "When parsing if statements" do
+ it "if true { $a = 10 }" do
+ dump(parse("if true { $a = 10 }")).should == "(if true\n (then (= $a 10)))"
+ end
+
+ it "if true { $a = 10 } else {$a = 20}" do
+ dump(parse("if true { $a = 10 } else {$a = 20}")).should ==
+ ["(if true",
+ " (then (= $a 10))",
+ " (else (= $a 20)))"].join("\n")
+ end
+
+ it "if true { $a = 10 } elsif false { $a = 15} else {$a = 20}" do
+ dump(parse("if true { $a = 10 } elsif false { $a = 15} else {$a = 20}")).should ==
+ ["(if true",
+ " (then (= $a 10))",
+ " (else (if false",
+ " (then (= $a 15))",
+ " (else (= $a 20)))))"].join("\n")
+ end
+
+ it "if true { $a = 10 $b = 10 } else {$a = 20}" do
+ dump(parse("if true { $a = 10 $b = 20} else {$a = 20}")).should ==
+ ["(if true",
+ " (then (block (= $a 10) (= $b 20)))",
+ " (else (= $a 20)))"].join("\n")
+ end
+
+ it "allows a parenthesized conditional expression" do
+ dump(parse("if (true) { 10 }")).should == "(if true\n (then 10))"
+ end
+
+ it "allows a parenthesized elsif conditional expression" do
+ dump(parse("if true { 10 } elsif (false) { 20 }")).should ==
+ ["(if true",
+ " (then 10)",
+ " (else (if false",
+ " (then 20))))"].join("\n")
+ end
+ end
+
+ context "When parsing unless statements" do
+ it "unless true { $a = 10 }" do
+ dump(parse("unless true { $a = 10 }")).should == "(unless true\n (then (= $a 10)))"
+ end
+
+ it "unless true { $a = 10 } else {$a = 20}" do
+ dump(parse("unless true { $a = 10 } else {$a = 20}")).should ==
+ ["(unless true",
+ " (then (= $a 10))",
+ " (else (= $a 20)))"].join("\n")
+ end
+
+ it "allows a parenthesized conditional expression" do
+ dump(parse("unless (true) { 10 }")).should == "(unless true\n (then 10))"
+ end
+
+ it "unless true { $a = 10 } elsif false { $a = 15} else {$a = 20} # is illegal" do
+ expect { parse("unless true { $a = 10 } elsif false { $a = 15} else {$a = 20}")}.to raise_error(Puppet::ParseError)
+ end
+ end
+
+ context "When parsing selector expressions" do
+ it "$a = $b ? banana => fruit " do
+ dump(parse("$a = $b ? banana => fruit")).should ==
+ "(= $a (? $b (banana => fruit)))"
+ end
+
+ it "$a = $b ? { banana => fruit}" do
+ dump(parse("$a = $b ? { banana => fruit }")).should ==
+ "(= $a (? $b (banana => fruit)))"
+ end
+
+ it "does not fail on a trailing blank line" do
+ dump(parse("$a = $b ? { banana => fruit }\n\n")).should ==
+ "(= $a (? $b (banana => fruit)))"
+ end
+
+ it "$a = $b ? { banana => fruit, grape => berry }" do
+ dump(parse("$a = $b ? {banana => fruit, grape => berry}")).should ==
+ "(= $a (? $b (banana => fruit) (grape => berry)))"
+ end
+
+ it "$a = $b ? { banana => fruit, grape => berry, default => wat }" do
+ dump(parse("$a = $b ? {banana => fruit, grape => berry, default => wat}")).should ==
+ "(= $a (? $b (banana => fruit) (grape => berry) (:default => wat)))"
+ end
+
+ it "$a = $b ? { default => wat, banana => fruit, grape => berry, }" do
+ dump(parse("$a = $b ? {default => wat, banana => fruit, grape => berry}")).should ==
+ "(= $a (? $b (:default => wat) (banana => fruit) (grape => berry)))"
+ end
+ end
+
+ context "When parsing case statements" do
+ it "case $a { a : {}}" do
+ dump(parse("case $a { a : {}}")).should ==
+ ["(case $a",
+ " (when (a) (then ())))"
+ ].join("\n")
+ end
+
+ it "allows a parenthesized value expression" do
+ dump(parse("case ($a) { a : {}}")).should ==
+ ["(case $a",
+ " (when (a) (then ())))"
+ ].join("\n")
+ end
+
+ it "case $a { /.*/ : {}}" do
+ dump(parse("case $a { /.*/ : {}}")).should ==
+ ["(case $a",
+ " (when (/.*/) (then ())))"
+ ].join("\n")
+ end
+
+ it "case $a { a, b : {}}" do
+ dump(parse("case $a { a, b : {}}")).should ==
+ ["(case $a",
+ " (when (a b) (then ())))"
+ ].join("\n")
+ end
+
+ it "case $a { a, b : {} default : {}}" do
+ dump(parse("case $a { a, b : {} default : {}}")).should ==
+ ["(case $a",
+ " (when (a b) (then ()))",
+ " (when (:default) (then ())))"
+ ].join("\n")
+ end
+
+ it "case $a { a : {$b = 10 $c = 20}}" do
+ dump(parse("case $a { a : {$b = 10 $c = 20}}")).should ==
+ ["(case $a",
+ " (when (a) (then (block (= $b 10) (= $c 20)))))"
+ ].join("\n")
+ end
+ end
+
+ context "When parsing imports" do
+ it "import 'foo'" do
+ dump(parse("import 'foo'")).should == "(import 'foo')"
+ end
+
+ it "import 'foo', 'bar'" do
+ dump(parse("import 'foo', 'bar'")).should == "(import 'foo' 'bar')"
+ end
+ end
+end
diff --git a/spec/unit/pops/parser/parse_containers_spec.rb b/spec/unit/pops/parser/parse_containers_spec.rb
new file mode 100644
index 0000000..a6b9f20
--- /dev/null
+++ b/spec/unit/pops/parser/parse_containers_spec.rb
@@ -0,0 +1,175 @@
+#! /usr/bin/env ruby
+require 'spec_helper'
+require 'puppet/pops'
+
+# relative to this spec file (./) does not work as this file is loaded by rspec
+require File.join(File.dirname(__FILE__), '/parser_rspec_helper')
+
+describe "egrammar parsing containers" do
+ include ParserRspecHelper
+
+ context "When parsing file scope" do
+ it "$a = 10 $b = 20" do
+ dump(parse("$a = 10 $b = 20")).should == "(block (= $a 10) (= $b 20))"
+ end
+
+ it "$a = 10" do
+ dump(parse("$a = 10")).should == "(= $a 10)"
+ end
+ end
+
+ context "When parsing class" do
+ it "class foo {}" do
+ dump(parse("class foo {}")).should == "(class foo ())"
+ end
+
+ it "class foo::bar {}" do
+ dump(parse("class foo::bar {}")).should == "(class foo::bar ())"
+ end
+
+ it "class foo inherits bar {}" do
+ dump(parse("class foo inherits bar {}")).should == "(class foo (inherits bar) ())"
+ end
+
+ it "class foo($a) {}" do
+ dump(parse("class foo($a) {}")).should == "(class foo (parameters a) ())"
+ end
+
+ it "class foo($a, $b) {}" do
+ dump(parse("class foo($a, $b) {}")).should == "(class foo (parameters a b) ())"
+ end
+
+ it "class foo($a, $b=10) {}" do
+ dump(parse("class foo($a, $b=10) {}")).should == "(class foo (parameters a (= b 10)) ())"
+ end
+
+ it "class foo($a, $b) inherits belgo::bar {}" do
+ dump(parse("class foo($a, $b) inherits belgo::bar{}")).should == "(class foo (inherits belgo::bar) (parameters a b) ())"
+ end
+
+ it "class foo {$a = 10 $b = 20}" do
+ dump(parse("class foo {$a = 10 $b = 20}")).should == "(class foo (block (= $a 10) (= $b 20)))"
+ end
+
+ context "it should handle '3x weirdness'" do
+ it "class class {} # a class named 'class'" do
+ # Not as much weird as confusing that it is possible to name a class 'class'. Can have
+ # a very confusing effect when resolving relative names, getting the global hardwired "Class"
+ # instead of some foo::class etc.
+ # This is allowed in 3.x.
+ dump(parse("class class {}")).should == "(class class ())"
+ end
+
+ it "class default {} # a class named 'default'" do
+ # The weirdness here is that a class can inherit 'default' but not declare a class called default.
+ # (It will work with relative names i.e. foo::default though). The whole idea with keywords as
+ # names is flawed to begin with - it generally just a very bad idea.
+ expect { dump(parse("class default {}")).should == "(class default ())" }.to raise_error(Puppet::ParseError)
+ end
+
+ it "class foo::default {} # a nested name 'default'" do
+ dump(parse("class foo::default {}")).should == "(class foo::default ())"
+ end
+
+ it "class class inherits default {} # inherits default", :broken => true do
+ dump(parse("class class inherits default {}")).should == "(class class (inherits default) ())"
+ end
+
+ it "class class inherits default {} # inherits default" do
+ # TODO: See previous test marked as :broken=>true, it is actually this test (result) that is wacky,
+ # this because a class is named at parse time (since class evaluation is lazy, the model must have the
+ # full class name for nested classes - only, it gets this wrong when a class is named "class" - or at least
+ # I think it is wrong.)
+ #
+ dump(parse("class class inherits default {}")).should == "(class class::class (inherits default) ())"
+ end
+
+ it "class foo inherits class" do
+ dump(parse("class foo inherits class {}")).should == "(class foo (inherits class) ())"
+ end
+ end
+ end
+
+ context "When the parser parses define" do
+ it "define foo {}" do
+ dump(parse("define foo {}")).should == "(define foo ())"
+ end
+
+ it "define foo::bar {}" do
+ dump(parse("define foo::bar {}")).should == "(define foo::bar ())"
+ end
+
+ it "define foo($a) {}" do
+ dump(parse("define foo($a) {}")).should == "(define foo (parameters a) ())"
+ end
+
+ it "define foo($a, $b) {}" do
+ dump(parse("define foo($a, $b) {}")).should == "(define foo (parameters a b) ())"
+ end
+
+ it "define foo($a, $b=10) {}" do
+ dump(parse("define foo($a, $b=10) {}")).should == "(define foo (parameters a (= b 10)) ())"
+ end
+
+ it "define foo {$a = 10 $b = 20}" do
+ dump(parse("define foo {$a = 10 $b = 20}")).should == "(define foo (block (= $a 10) (= $b 20)))"
+ end
+
+ context "it should handle '3x weirdness'" do
+ it "define class {} # a define named 'class'" do
+ # This is weird because Class already exists, and instantiating this define will probably not
+ # work
+ dump(parse("define class {}")).should == "(define class ())"
+ end
+
+ it "define default {} # a define named 'default'" do
+ # Check unwanted ability to define 'default'.
+ # The expression below is not allowed (which is good).
+ #
+ expect { dump(parse("define default {}")).should == "(define default ())"}.to raise_error(Puppet::ParseError)
+ end
+ end
+ end
+
+ context "When parsing node" do
+ it "node foo {}" do
+ dump(parse("node foo {}")).should == "(node (matches foo) ())"
+ end
+
+ it "node foo, x::bar, default {}" do
+ dump(parse("node foo, x::bar, default {}")).should == "(node (matches foo x::bar :default) ())"
+ end
+
+ it "node 'foo' {}" do
+ dump(parse("node 'foo' {}")).should == "(node (matches 'foo') ())"
+ end
+
+ it "node foo inherits x::bar {}" do
+ dump(parse("node foo inherits x::bar {}")).should == "(node (matches foo) (parent x::bar) ())"
+ end
+
+ it "node foo inherits 'bar' {}" do
+ dump(parse("node foo inherits 'bar' {}")).should == "(node (matches foo) (parent 'bar') ())"
+ end
+
+ it "node foo inherits default {}" do
+ dump(parse("node foo inherits default {}")).should == "(node (matches foo) (parent :default) ())"
+ end
+
+ it "node /web.*/ {}" do
+ dump(parse("node /web.*/ {}")).should == "(node (matches /web.*/) ())"
+ end
+
+ it "node /web.*/, /do\.wop.*/, and.so.on {}" do
+ dump(parse("node /web.*/, /do\.wop.*/, 'and.so.on' {}")).should == "(node (matches /web.*/ /do\.wop.*/ 'and.so.on') ())"
+ end
+
+ it "node wat inherits /apache.*/ {}" do
+ expect { parse("node wat inherits /apache.*/ {}")}.to raise_error(Puppet::ParseError)
+ end
+
+ it "node foo inherits bar {$a = 10 $b = 20}" do
+ dump(parse("node foo inherits bar {$a = 10 $b = 20}")).should == "(node (matches foo) (parent bar) (block (= $a 10) (= $b 20)))"
+ end
+ end
+end
diff --git a/spec/unit/pops/parser/parse_resource_spec.rb b/spec/unit/pops/parser/parse_resource_spec.rb
new file mode 100644
index 0000000..7d2b54d
--- /dev/null
+++ b/spec/unit/pops/parser/parse_resource_spec.rb
@@ -0,0 +1,228 @@
+#! /usr/bin/env ruby
+require 'spec_helper'
+require 'puppet/pops'
+
+# relative to this spec file (./) does not work as this file is loaded by rspec
+require File.join(File.dirname(__FILE__), '/parser_rspec_helper')
+
+describe "egrammar parsing resource declarations" do
+ include ParserRspecHelper
+
+ context "When parsing regular resource" do
+ it "file { 'title': }" do
+ dump(parse("file { 'title': }")).should == [
+ "(resource file",
+ " ('title'))"
+ ].join("\n")
+ end
+
+ it "file { 'title': path => '/somewhere', mode => 0777}" do
+ dump(parse("file { 'title': path => '/somewhere', mode => 0777}")).should == [
+ "(resource file",
+ " ('title'",
+ " (path => '/somewhere')",
+ " (mode => 0777)))"
+ ].join("\n")
+ end
+
+ it "file { 'title': path => '/somewhere', }" do
+ dump(parse("file { 'title': path => '/somewhere', }")).should == [
+ "(resource file",
+ " ('title'",
+ " (path => '/somewhere')))"
+ ].join("\n")
+ end
+
+ it "file { 'title': , }" do
+ dump(parse("file { 'title': , }")).should == [
+ "(resource file",
+ " ('title'))"
+ ].join("\n")
+ end
+
+ it "file { 'title': ; }" do
+ dump(parse("file { 'title': ; }")).should == [
+ "(resource file",
+ " ('title'))"
+ ].join("\n")
+ end
+
+ it "file { 'title': ; 'other_title': }" do
+ dump(parse("file { 'title': ; 'other_title': }")).should == [
+ "(resource file",
+ " ('title')",
+ " ('other_title'))"
+ ].join("\n")
+ end
+
+ it "file { 'title1': path => 'x'; 'title2': path => 'y'}" do
+ dump(parse("file { 'title1': path => 'x'; 'title2': path => 'y'}")).should == [
+ "(resource file",
+ " ('title1'",
+ " (path => 'x'))",
+ " ('title2'",
+ " (path => 'y')))",
+ ].join("\n")
+ end
+ end
+
+ context "When parsing resource defaults" do
+ it "File { }" do
+ dump(parse("File { }")).should == "(resource-defaults file)"
+ end
+
+ it "File { mode => 0777 }" do
+ dump(parse("File { mode => 0777}")).should == [
+ "(resource-defaults file",
+ " (mode => 0777))"
+ ].join("\n")
+ end
+ end
+
+ context "When parsing resource override" do
+ it "File['x'] { }" do
+ dump(parse("File['x'] { }")).should == "(override (slice file 'x'))"
+ end
+
+ it "File['x'] { x => 1 }" do
+ dump(parse("File['x'] { x => 1}")).should == "(override (slice file 'x')\n (x => 1))"
+ end
+
+ it "File['x', 'y'] { x => 1 }" do
+ dump(parse("File['x', 'y'] { x => 1}")).should == "(override (slice file ('x' 'y'))\n (x => 1))"
+ end
+
+ it "File['x'] { x => 1, y => 2 }" do
+ dump(parse("File['x'] { x => 1, y=> 2}")).should == "(override (slice file 'x')\n (x => 1)\n (y => 2))"
+ end
+
+ it "File['x'] { x +> 1 }" do
+ dump(parse("File['x'] { x +> 1}")).should == "(override (slice file 'x')\n (x +> 1))"
+ end
+ end
+
+ context "When parsing virtual and exported resources" do
+ it "@@file { 'title': }" do
+ dump(parse("@@file { 'title': }")).should == "(exported-resource file\n ('title'))"
+ end
+
+ it "@file { 'title': }" do
+ dump(parse("@file { 'title': }")).should == "(virtual-resource file\n ('title'))"
+ end
+
+ it "@file { mode => 0777 }" do
+ # Defaults are not virtualizeable
+ expect {
+ dump(parse("@file { mode => 0777 }")).should == ""
+ }.to raise_error(Puppet::ParseError, /Defaults are not virtualizable/)
+ end
+ end
+
+ context "When parsing class resource" do
+ it "class { 'cname': }" do
+ dump(parse("class { 'cname': }")).should == [
+ "(resource class",
+ " ('cname'))"
+ ].join("\n")
+ end
+
+ it "class { 'cname': x => 1, y => 2}" do
+ dump(parse("class { 'cname': x => 1, y => 2}")).should == [
+ "(resource class",
+ " ('cname'",
+ " (x => 1)",
+ " (y => 2)))"
+ ].join("\n")
+ end
+
+ it "class { 'cname1': x => 1; 'cname2': y => 2}" do
+ dump(parse("class { 'cname1': x => 1; 'cname2': y => 2}")).should == [
+ "(resource class",
+ " ('cname1'",
+ " (x => 1))",
+ " ('cname2'",
+ " (y => 2)))",
+ ].join("\n")
+ end
+ end
+
+ context "reported issues in 3.x" do
+ it "should not screw up on brackets in title of resource #19632" do
+ dump(parse('notify { "thisisa[bug]": }')).should == [
+ "(resource notify",
+ " ('thisisa[bug]'))",
+ ].join("\n")
+ end
+ end
+
+ context "When parsing Relationships" do
+ it "File[a] -> File[b]" do
+ dump(parse("File[a] -> File[b]")).should == "(-> (slice file a) (slice file b))"
+ end
+
+ it "File[a] <- File[b]" do
+ dump(parse("File[a] <- File[b]")).should == "(<- (slice file a) (slice file b))"
+ end
+
+ it "File[a] ~> File[b]" do
+ dump(parse("File[a] ~> File[b]")).should == "(~> (slice file a) (slice file b))"
+ end
+
+ it "File[a] <~ File[b]" do
+ dump(parse("File[a] <~ File[b]")).should == "(<~ (slice file a) (slice file b))"
+ end
+
+ it "Should chain relationships" do
+ dump(parse("a -> b -> c")).should ==
+ "(-> (-> a b) c)"
+ end
+
+ it "Should chain relationships" do
+ dump(parse("File[a] -> File[b] ~> File[c] <- File[d] <~ File[e]")).should ==
+ "(<~ (<- (~> (-> (slice file a) (slice file b)) (slice file c)) (slice file d)) (slice file e))"
+ end
+
+ it "should create relationships between collects" do
+ dump(parse("File <| mode == 0644 |> -> File <| mode == 0755 |>")).should ==
+ "(-> (collect file\n (<| |> (== mode 0644))) (collect file\n (<| |> (== mode 0755))))"
+ end
+ end
+
+ context "When parsing collection" do
+ context "of virtual resources" do
+ it "File <| |>" do
+ dump(parse("File <| |>")).should == "(collect file\n (<| |>))"
+ end
+ end
+
+ context "of exported resources" do
+ it "File <<| |>>" do
+ dump(parse("File <<| |>>")).should == "(collect file\n (<<| |>>))"
+ end
+ end
+
+ context "queries are parsed with correct precedence" do
+ it "File <| tag == 'foo' |>" do
+ dump(parse("File <| tag == 'foo' |>")).should == "(collect file\n (<| |> (== tag 'foo')))"
+ end
+
+ it "File <| tag == 'foo' and mode != 0777 |>" do
+ dump(parse("File <| tag == 'foo' and mode != 0777 |>")).should == "(collect file\n (<| |> (&& (== tag 'foo') (!= mode 0777))))"
+ end
+
+ it "File <| tag == 'foo' or mode != 0777 |>" do
+ dump(parse("File <| tag == 'foo' or mode != 0777 |>")).should == "(collect file\n (<| |> (|| (== tag 'foo') (!= mode 0777))))"
+ end
+
+ it "File <| tag == 'foo' or tag == 'bar' and mode != 0777 |>" do
+ dump(parse("File <| tag == 'foo' or tag == 'bar' and mode != 0777 |>")).should ==
+ "(collect file\n (<| |> (|| (== tag 'foo') (&& (== tag 'bar') (!= mode 0777)))))"
+ end
+
+ it "File <| (tag == 'foo' or tag == 'bar') and mode != 0777 |>" do
+ dump(parse("File <| (tag == 'foo' or tag == 'bar') and mode != 0777 |>")).should ==
+ "(collect file\n (<| |> (&& (|| (== tag 'foo') (== tag 'bar')) (!= mode 0777))))"
+ end
+ end
+ end
+end
diff --git a/spec/unit/pops/parser/parser_rspec_helper.rb b/spec/unit/pops/parser/parser_rspec_helper.rb
new file mode 100644
index 0000000..4b2e7a0
--- /dev/null
+++ b/spec/unit/pops/parser/parser_rspec_helper.rb
@@ -0,0 +1,11 @@
+require 'puppet/pops'
+
+require File.join(File.dirname(__FILE__), '/../factory_rspec_helper')
+
+module ParserRspecHelper
+ include FactoryRspecHelper
+ def parse(code)
+ parser = Puppet::Pops::Parser::Parser.new()
+ parser.parse_string(code)
+ end
+end
diff --git a/spec/unit/pops/parser/parser_spec.rb b/spec/unit/pops/parser/parser_spec.rb
new file mode 100644
index 0000000..86d3d6d
--- /dev/null
+++ b/spec/unit/pops/parser/parser_spec.rb
@@ -0,0 +1,15 @@
+require 'spec_helper'
+require 'puppet/pops'
+
+describe Puppet::Pops::Parser::Parser do
+ it "should instantiate a parser" do
+ parser = Puppet::Pops::Parser::Parser.new()
+ parser.class.should == Puppet::Pops::Parser::Parser
+ end
+
+ it "should parse a code string and return a model" do
+ parser = Puppet::Pops::Parser::Parser.new()
+ model = parser.parse_string("$a = 10").current
+ model.class.should == Model::AssignmentExpression
+ end
+end
diff --git a/spec/unit/pops/parser/rgen_sanitycheck_spec.rb b/spec/unit/pops/parser/rgen_sanitycheck_spec.rb
new file mode 100644
index 0000000..dc71691
--- /dev/null
+++ b/spec/unit/pops/parser/rgen_sanitycheck_spec.rb
@@ -0,0 +1,16 @@
+#! /usr/bin/env ruby
+require 'spec_helper'
+require 'puppet/pops'
+
+# relative to this spec file (./) does not work as this file is loaded by rspec
+require File.join(File.dirname(__FILE__), '/parser_rspec_helper')
+
+describe "RGen working with hashes" do
+ it "should be possible to create an empty hash after having required the files above" do
+ # If this fails, it means the rgen addition to Array is not monkey patched as it
+ # should (it will return an array instead of fail in a method_missing), and thus
+ # screw up Hash's check if it can do "to_hash' or not.
+ #
+ Hash[[]]
+ end
+end
diff --git a/spec/unit/pops/transformer/transform_basic_expressions_spec.rb b/spec/unit/pops/transformer/transform_basic_expressions_spec.rb
new file mode 100644
index 0000000..11a2c76
--- /dev/null
+++ b/spec/unit/pops/transformer/transform_basic_expressions_spec.rb
@@ -0,0 +1,243 @@
+#! /usr/bin/env ruby
+require 'spec_helper'
+require 'puppet/pops'
+
+# relative to this spec file (./) does not work as this file is loaded by rspec
+require File.join(File.dirname(__FILE__), '/transformer_rspec_helper')
+
+describe "transformation to Puppet AST for basic expressions" do
+ include TransformerRspecHelper
+
+ context "When transforming arithmetic" do
+ context "with Integers" do
+ it "$a = 2 + 2" do; astdump(parse("$a = 2 + 2")).should == "(= $a (+ 2 2))" ; end
+ it "$a = 7 - 3" do; astdump(parse("$a = 7 - 3")).should == "(= $a (- 7 3))" ; end
+ it "$a = 6 * 3" do; astdump(parse("$a = 6 * 3")).should == "(= $a (* 6 3))" ; end
+ it "$a = 6 / 3" do; astdump(parse("$a = 6 / 3")).should == "(= $a (/ 6 3))" ; end
+ it "$a = 6 % 3" do; astdump(parse("$a = 6 % 3")).should == "(= $a (% 6 3))" ; end
+ it "$a = -(6/3)" do; astdump(parse("$a = -(6/3)")).should == "(= $a (- (/ 6 3)))" ; end
+ it "$a = -6/3" do; astdump(parse("$a = -6/3")).should == "(= $a (/ (- 6) 3))" ; end
+ it "$a = 8 >> 1 " do; astdump(parse("$a = 8 >> 1")).should == "(= $a (>> 8 1))" ; end
+ it "$a = 8 << 1 " do; astdump(parse("$a = 8 << 1")).should == "(= $a (<< 8 1))" ; end
+ end
+
+ context "with Floats" do
+ it "$a = 2.2 + 2.2" do; astdump(parse("$a = 2.2 + 2.2")).should == "(= $a (+ 2.2 2.2))" ; end
+ it "$a = 7.7 - 3.3" do; astdump(parse("$a = 7.7 - 3.3")).should == "(= $a (- 7.7 3.3))" ; end
+ it "$a = 6.1 * 3.1" do; astdump(parse("$a = 6.1 - 3.1")).should == "(= $a (- 6.1 3.1))" ; end
+ it "$a = 6.6 / 3.3" do; astdump(parse("$a = 6.6 / 3.3")).should == "(= $a (/ 6.6 3.3))" ; end
+ it "$a = -(6.0/3.0)" do; astdump(parse("$a = -(6.0/3.0)")).should == "(= $a (- (/ 6.0 3.0)))" ; end
+ it "$a = -6.0/3.0" do; astdump(parse("$a = -6.0/3.0")).should == "(= $a (/ (- 6.0) 3.0))" ; end
+ it "$a = 3.14 << 2" do; astdump(parse("$a = 3.14 << 2")).should == "(= $a (<< 3.14 2))" ; end
+ it "$a = 3.14 >> 2" do; astdump(parse("$a = 3.14 >> 2")).should == "(= $a (>> 3.14 2))" ; end
+ end
+
+ context "with hex and octal Integer values" do
+ it "$a = 0xAB + 0xCD" do; astdump(parse("$a = 0xAB + 0xCD")).should == "(= $a (+ 0xAB 0xCD))" ; end
+ it "$a = 0777 - 0333" do; astdump(parse("$a = 0777 - 0333")).should == "(= $a (- 0777 0333))" ; end
+ end
+
+ context "with strings requiring boxing to Numeric" do
+ # In AST, there is no difference, the ast dumper prints all numbers without quotes - they are still
+ # strings
+ it "$a = '2' + '2'" do; astdump(parse("$a = '2' + '2'")).should == "(= $a (+ 2 2))" ; end
+ it "$a = '2.2' + '0.2'" do; astdump(parse("$a = '2.2' + '0.2'")).should == "(= $a (+ 2.2 0.2))" ; end
+ it "$a = '0xab' + '0xcd'" do; astdump(parse("$a = '0xab' + '0xcd'")).should == "(= $a (+ 0xab 0xcd))" ; end
+ it "$a = '0777' + '0333'" do; astdump(parse("$a = '0777' + '0333'")).should == "(= $a (+ 0777 0333))" ; end
+ end
+
+ context "precedence should be correct" do
+ it "$a = 1 + 2 * 3" do; astdump(parse("$a = 1 + 2 * 3")).should == "(= $a (+ 1 (* 2 3)))"; end
+ it "$a = 1 + 2 % 3" do; astdump(parse("$a = 1 + 2 % 3")).should == "(= $a (+ 1 (% 2 3)))"; end
+ it "$a = 1 + 2 / 3" do; astdump(parse("$a = 1 + 2 / 3")).should == "(= $a (+ 1 (/ 2 3)))"; end
+ it "$a = 1 + 2 << 3" do; astdump(parse("$a = 1 + 2 << 3")).should == "(= $a (<< (+ 1 2) 3))"; end
+ it "$a = 1 + 2 >> 3" do; astdump(parse("$a = 1 + 2 >> 3")).should == "(= $a (>> (+ 1 2) 3))"; end
+ end
+
+ context "parentheses alter precedence" do
+ it "$a = (1 + 2) * 3" do; astdump(parse("$a = (1 + 2) * 3")).should == "(= $a (* (+ 1 2) 3))"; end
+ it "$a = (1 + 2) / 3" do; astdump(parse("$a = (1 + 2) / 3")).should == "(= $a (/ (+ 1 2) 3))"; end
+ end
+ end
+
+ context "When transforming boolean operations" do
+ context "using operators AND OR NOT" do
+ it "$a = true and true" do; astdump(parse("$a = true and true")).should == "(= $a (&& true true))"; end
+ it "$a = true or true" do; astdump(parse("$a = true or true")).should == "(= $a (|| true true))" ; end
+ it "$a = !true" do; astdump(parse("$a = !true")).should == "(= $a (! true))" ; end
+ end
+
+ context "precedence should be correct" do
+ it "$a = false or true and true" do
+ astdump(parse("$a = false or true and true")).should == "(= $a (|| false (&& true true)))"
+ end
+
+ it "$a = (false or true) and true" do
+ astdump(parse("$a = (false or true) and true")).should == "(= $a (&& (|| false true) true))"
+ end
+
+ it "$a = !true or true and true" do
+ astdump(parse("$a = !false or true and true")).should == "(= $a (|| (! false) (&& true true)))"
+ end
+ end
+
+ # Possibly change to check of literal expressions
+ context "on values requiring boxing to Boolean" do
+ it "'x' == true" do
+ astdump(parse("! 'x'")).should == "(! 'x')"
+ end
+
+ it "'' == false" do
+ astdump(parse("! ''")).should == "(! '')"
+ end
+
+ it ":undef == false" do
+ astdump(parse("! undef")).should == "(! :undef)"
+ end
+ end
+ end
+
+ context "When transforming comparisons" do
+ context "of string values" do
+ it "$a = 'a' == 'a'" do; astdump(parse("$a = 'a' == 'a'")).should == "(= $a (== 'a' 'a'))" ; end
+ it "$a = 'a' != 'a'" do; astdump(parse("$a = 'a' != 'a'")).should == "(= $a (!= 'a' 'a'))" ; end
+ it "$a = 'a' < 'b'" do; astdump(parse("$a = 'a' < 'b'")).should == "(= $a (< 'a' 'b'))" ; end
+ it "$a = 'a' > 'b'" do; astdump(parse("$a = 'a' > 'b'")).should == "(= $a (> 'a' 'b'))" ; end
+ it "$a = 'a' <= 'b'" do; astdump(parse("$a = 'a' <= 'b'")).should == "(= $a (<= 'a' 'b'))" ; end
+ it "$a = 'a' >= 'b'" do; astdump(parse("$a = 'a' >= 'b'")).should == "(= $a (>= 'a' 'b'))" ; end
+ end
+
+ context "of integer values" do
+ it "$a = 1 == 1" do; astdump(parse("$a = 1 == 1")).should == "(= $a (== 1 1))" ; end
+ it "$a = 1 != 1" do; astdump(parse("$a = 1 != 1")).should == "(= $a (!= 1 1))" ; end
+ it "$a = 1 < 2" do; astdump(parse("$a = 1 < 2")).should == "(= $a (< 1 2))" ; end
+ it "$a = 1 > 2" do; astdump(parse("$a = 1 > 2")).should == "(= $a (> 1 2))" ; end
+ it "$a = 1 <= 2" do; astdump(parse("$a = 1 <= 2")).should == "(= $a (<= 1 2))" ; end
+ it "$a = 1 >= 2" do; astdump(parse("$a = 1 >= 2")).should == "(= $a (>= 1 2))" ; end
+ end
+
+ context "of regular expressions (parse errors)" do
+ # Not supported in concrete syntax
+ it "$a = /.*/ == /.*/" do
+ expect { parse("$a = /.*/ == /.*/") }.to raise_error(Puppet::ParseError)
+ end
+
+ it "$a = /.*/ != /a.*/" do
+ expect { parse("$a = /.*/ != /.*/") }.to raise_error(Puppet::ParseError)
+ end
+ end
+ end
+
+ context "When transforming Regular Expression matching" do
+ it "$a = 'a' =~ /.*/" do; astdump(parse("$a = 'a' =~ /.*/")).should == "(= $a (=~ 'a' /.*/))" ; end
+ it "$a = 'a' =~ '.*'" do; astdump(parse("$a = 'a' =~ '.*'")).should == "(= $a (=~ 'a' '.*'))" ; end
+ it "$a = 'a' !~ /b.*/" do; astdump(parse("$a = 'a' !~ /b.*/")).should == "(= $a (!~ 'a' /b.*/))" ; end
+ it "$a = 'a' !~ 'b.*'" do; astdump(parse("$a = 'a' !~ 'b.*'")).should == "(= $a (!~ 'a' 'b.*'))" ; end
+ end
+
+ context "When transforming Lists" do
+ it "$a = []" do
+ astdump(parse("$a = []")).should == "(= $a ([]))"
+ end
+
+ it "$a = [1]" do
+ astdump(parse("$a = [1]")).should == "(= $a ([] 1))"
+ end
+
+ it "$a = [1,2,3]" do
+ astdump(parse("$a = [1,2,3]")).should == "(= $a ([] 1 2 3))"
+ end
+
+ it "[...[...[]]] should create nested arrays without trouble" do
+ astdump(parse("$a = [1,[2.0, 2.1, [2.2]],[3.0, 3.1]]")).should == "(= $a ([] 1 ([] 2.0 2.1 ([] 2.2)) ([] 3.0 3.1)))"
+ end
+
+ it "$a = [2 + 2]" do
+ astdump(parse("$a = [2+2]")).should == "(= $a ([] (+ 2 2)))"
+ end
+
+ it "$a [1,2,3] == [1,2,3]" do
+ astdump(parse("$a = [1,2,3] == [1,2,3]")).should == "(= $a (== ([] 1 2 3) ([] 1 2 3)))"
+ end
+ end
+
+ context "When transforming indexed access" do
+ it "$a = $b[2]" do
+ astdump(parse("$a = $b[2]")).should == "(= $a (slice $b 2))"
+ end
+
+ it "$a = [1, 2, 3][2]" do
+ astdump(parse("$a = [1,2,3][2]")).should == "(= $a (slice ([] 1 2 3) 2))"
+ end
+
+ it "$a = {'a' => 1, 'b' => 2}['b']" do
+ astdump(parse("$a = {'a'=>1,'b' =>2}[b]")).should == "(= $a (slice ({} ('a' 1) ('b' 2)) b))"
+ end
+ end
+
+ context "When transforming Hashes" do
+ it "should create a Hash when evaluating a LiteralHash" do
+ astdump(parse("$a = {'a'=>1,'b'=>2}")).should == "(= $a ({} ('a' 1) ('b' 2)))"
+ end
+
+ it "$a = {...{...{}}} should create nested hashes without trouble" do
+ astdump(parse("$a = {'a'=>1,'b'=>{'x'=>2.1,'y'=>2.2}}")).should == "(= $a ({} ('a' 1) ('b' ({} ('x' 2.1) ('y' 2.2)))))"
+ end
+
+ it "$a = {'a'=> 2 + 2} should evaluate values in entries" do
+ astdump(parse("$a = {'a'=>2+2}")).should == "(= $a ({} ('a' (+ 2 2))))"
+ end
+
+ it "$a = {'a'=> 1, 'b'=>2} == {'a'=> 1, 'b'=>2}" do
+ astdump(parse("$a = {'a'=>1,'b'=>2} == {'a'=>1,'b'=>2}")).should == "(= $a (== ({} ('a' 1) ('b' 2)) ({} ('a' 1) ('b' 2))))"
+ end
+
+ it "$a = {'a'=> 1, 'b'=>2} != {'x'=> 1, 'y'=>3}" do
+ astdump(parse("$a = {'a'=>1,'b'=>2} != {'a'=>1,'b'=>2}")).should == "(= $a (!= ({} ('a' 1) ('b' 2)) ({} ('a' 1) ('b' 2))))"
+ end
+ end
+
+ context "When transforming the 'in' operator" do
+ it "with integer in a list" do
+ astdump(parse("$a = 1 in [1,2,3]")).should == "(= $a (in 1 ([] 1 2 3)))"
+ end
+
+ it "with string key in a hash" do
+ astdump(parse("$a = 'a' in {'x'=>1, 'a'=>2, 'y'=> 3}")).should == "(= $a (in 'a' ({} ('a' 2) ('x' 1) ('y' 3))))"
+ end
+
+ it "with substrings of a string" do
+ astdump(parse("$a = 'ana' in 'bananas'")).should == "(= $a (in 'ana' 'bananas'))"
+ end
+
+ it "with sublist in a list" do
+ astdump(parse("$a = [2,3] in [1,2,3]")).should == "(= $a (in ([] 2 3) ([] 1 2 3)))"
+ end
+ end
+
+ context "When transforming string interpolation" do
+ it "should interpolate a bare word as a variable name, \"${var}\"" do
+ astdump(parse("$a = \"$var\"")).should == "(= $a (cat '' (str $var) ''))"
+ end
+
+ it "should interpolate a variable in a text expression, \"${$var}\"" do
+ astdump(parse("$a = \"${$var}\"")).should == "(= $a (cat '' (str $var) ''))"
+ end
+
+ it "should interpolate two variables in a text expression" do
+ astdump(parse(%q{$a = "xxx $x and $y end"})).should == "(= $a (cat 'xxx ' (str $x) ' and ' (str $y) ' end'))"
+ end
+
+ it "should interpolate one variables followed by parentheses" do
+ astdump(parse(%q{$a = "xxx ${x} (yay)"})).should == "(= $a (cat 'xxx ' (str $x) ' (yay)'))"
+ end
+
+ it "should interpolate a variable, \"yo${var}yo\"" do
+ astdump(parse("$a = \"yo${var}yo\"")).should == "(= $a (cat 'yo' (str $var) 'yo'))"
+ end
+
+ it "should interpolate any expression in a text expression, \"${var*2}\"" do
+ astdump(parse("$a = \"yo${var+2}yo\"")).should == "(= $a (cat 'yo' (str (+ $var 2)) 'yo'))"
+ end
+ end
+end
diff --git a/spec/unit/pops/transformer/transform_calls_spec.rb b/spec/unit/pops/transformer/transform_calls_spec.rb
new file mode 100644
index 0000000..f2303db
--- /dev/null
+++ b/spec/unit/pops/transformer/transform_calls_spec.rb
@@ -0,0 +1,80 @@
+#! /usr/bin/env ruby
+require 'spec_helper'
+require 'puppet/pops'
+
+# relative to this spec file (./) does not work as this file is loaded by rspec
+require File.join(File.dirname(__FILE__), '/transformer_rspec_helper')
+
+describe "transformation to Puppet AST for function calls" do
+ include TransformerRspecHelper
+
+ context "When transforming calls as statements" do
+ context "in top level scope" do
+ it "foo()" do
+ astdump(parse("foo()")).should == "(invoke foo)"
+ end
+
+ it "foo bar" do
+ astdump(parse("foo bar")).should == "(invoke foo bar)"
+ end
+ end
+
+ context "in nested scopes" do
+ it "if true { foo() }" do
+ astdump(parse("if true {foo()}")).should == "(if true\n (then (invoke foo)))"
+ end
+
+ it "if true { foo bar}" do
+ astdump(parse("if true {foo bar}")).should == "(if true\n (then (invoke foo bar)))"
+ end
+ end
+ end
+
+ context "When transforming calls as expressions" do
+ it "$a = foo()" do
+ astdump(parse("$a = foo()")).should == "(= $a (call foo))"
+ end
+
+ it "$a = foo(bar)" do
+ astdump(parse("$a = foo()")).should == "(= $a (call foo))"
+ end
+
+ # For egrammar where a bare word can be a "statement"
+ it "$a = foo bar # assignment followed by bare word is ok in egrammar" do
+ astdump(parse("$a = foo bar")).should == "(block (= $a foo) bar)"
+ end
+
+ context "in nested scopes" do
+ it "if true { $a = foo() }" do
+ astdump(parse("if true { $a = foo()}")).should == "(if true\n (then (= $a (call foo))))"
+ end
+
+ it "if true { $a= foo(bar)}" do
+ astdump(parse("if true {$a = foo(bar)}")).should == "(if true\n (then (= $a (call foo bar))))"
+ end
+ end
+ end
+
+ context "When transforming method calls" do
+ it "$a.foo" do
+ astdump(parse("$a.foo")).should == "(call-method (. $a foo))"
+ end
+
+ it "$a.foo {|| }" do
+ astdump(parse("$a.foo || { }")).should == "(call-method (. $a foo) (lambda ()))"
+ end
+
+ it "$a.foo {|| []} # check transformation to block with empty array" do
+ astdump(parse("$a.foo || { []}")).should == "(call-method (. $a foo) (lambda (block ([]))))"
+ end
+
+ it "$a.foo {|$x| }" do
+ astdump(parse("$a.foo {|$x| }")).should == "(call-method (. $a foo) (lambda (parameters x) ()))"
+ end
+
+ it "$a.foo {|$x| $b = $x}" do
+ astdump(parse("$a.foo {|$x| $b = $x}")).should ==
+ "(call-method (. $a foo) (lambda (parameters x) (block (= $b $x))))"
+ end
+ end
+end
diff --git a/spec/unit/pops/transformer/transform_conditionals_spec.rb b/spec/unit/pops/transformer/transform_conditionals_spec.rb
new file mode 100644
index 0000000..6eefa51
--- /dev/null
+++ b/spec/unit/pops/transformer/transform_conditionals_spec.rb
@@ -0,0 +1,132 @@
+#! /usr/bin/env ruby
+require 'spec_helper'
+require 'puppet/pops'
+
+# relative to this spec file (./) does not work as this file is loaded by rspec
+require File.join(File.dirname(__FILE__), '/transformer_rspec_helper')
+
+describe "transformation to Puppet AST for conditionals" do
+ include TransformerRspecHelper
+
+ context "When transforming if statements" do
+ it "if true { $a = 10 }" do
+ astdump(parse("if true { $a = 10 }")).should == "(if true\n (then (= $a 10)))"
+ end
+
+ it "if true { $a = 10 } else {$a = 20}" do
+ astdump(parse("if true { $a = 10 } else {$a = 20}")).should ==
+ ["(if true",
+ " (then (= $a 10))",
+ " (else (= $a 20)))"].join("\n")
+ end
+
+ it "if true { $a = 10 } elsif false { $a = 15} else {$a = 20}" do
+ astdump(parse("if true { $a = 10 } elsif false { $a = 15} else {$a = 20}")).should ==
+ ["(if true",
+ " (then (= $a 10))",
+ " (else (if false",
+ " (then (= $a 15))",
+ " (else (= $a 20)))))"].join("\n")
+ end
+
+ it "if true { $a = 10 $b = 10 } else {$a = 20}" do
+ astdump(parse("if true { $a = 10 $b = 20} else {$a = 20}")).should ==
+ ["(if true",
+ " (then (block (= $a 10) (= $b 20)))",
+ " (else (= $a 20)))"].join("\n")
+ end
+ end
+
+ context "When transforming unless statements" do
+ # Note that Puppet 3.1 does not have an "unless x", it is encoded as "if !x"
+ it "unless true { $a = 10 }" do
+ astdump(parse("unless true { $a = 10 }")).should == "(if (! true)\n (then (= $a 10)))"
+ end
+
+ it "unless true { $a = 10 } else {$a = 20}" do
+ astdump(parse("unless true { $a = 10 } else {$a = 20}")).should ==
+ ["(if (! true)",
+ " (then (= $a 10))",
+ " (else (= $a 20)))"].join("\n")
+ end
+
+ it "unless true { $a = 10 } elsif false { $a = 15} else {$a = 20} # is illegal" do
+ expect { parse("unless true { $a = 10 } elsif false { $a = 15} else {$a = 20}")}.to raise_error(Puppet::ParseError)
+ end
+ end
+
+ context "When transforming selector expressions" do
+ it "$a = $b ? banana => fruit " do
+ astdump(parse("$a = $b ? banana => fruit")).should ==
+ "(= $a (? $b (banana => fruit)))"
+ end
+
+ it "$a = $b ? { banana => fruit}" do
+ astdump(parse("$a = $b ? { banana => fruit }")).should ==
+ "(= $a (? $b (banana => fruit)))"
+ end
+
+ it "$a = $b ? { banana => fruit, grape => berry }" do
+ astdump(parse("$a = $b ? {banana => fruit, grape => berry}")).should ==
+ "(= $a (? $b (banana => fruit) (grape => berry)))"
+ end
+
+ it "$a = $b ? { banana => fruit, grape => berry, default => wat }" do
+ astdump(parse("$a = $b ? {banana => fruit, grape => berry, default => wat}")).should ==
+ "(= $a (? $b (banana => fruit) (grape => berry) (:default => wat)))"
+ end
+
+ it "$a = $b ? { default => wat, banana => fruit, grape => berry, }" do
+ astdump(parse("$a = $b ? {default => wat, banana => fruit, grape => berry}")).should ==
+ "(= $a (? $b (:default => wat) (banana => fruit) (grape => berry)))"
+ end
+ end
+
+ context "When transforming case statements" do
+ it "case $a { a : {}}" do
+ astdump(parse("case $a { a : {}}")).should ==
+ ["(case $a",
+ " (when (a) (then ())))"
+ ].join("\n")
+ end
+
+ it "case $a { /.*/ : {}}" do
+ astdump(parse("case $a { /.*/ : {}}")).should ==
+ ["(case $a",
+ " (when (/.*/) (then ())))"
+ ].join("\n")
+ end
+
+ it "case $a { a, b : {}}" do
+ astdump(parse("case $a { a, b : {}}")).should ==
+ ["(case $a",
+ " (when (a b) (then ())))"
+ ].join("\n")
+ end
+
+ it "case $a { a, b : {} default : {}}" do
+ astdump(parse("case $a { a, b : {} default : {}}")).should ==
+ ["(case $a",
+ " (when (a b) (then ()))",
+ " (when (:default) (then ())))"
+ ].join("\n")
+ end
+
+ it "case $a { a : {$b = 10 $c = 20}}" do
+ astdump(parse("case $a { a : {$b = 10 $c = 20}}")).should ==
+ ["(case $a",
+ " (when (a) (then (block (= $b 10) (= $c 20)))))"
+ ].join("\n")
+ end
+ end
+
+ context "When transforming imports" do
+ it "import 'foo'" do
+ astdump(parse("import 'foo'")).should == ":nop"
+ end
+
+ it "import 'foo', 'bar'" do
+ astdump(parse("import 'foo', 'bar'")).should == ":nop"
+ end
+ end
+end
diff --git a/spec/unit/pops/transformer/transform_containers_spec.rb b/spec/unit/pops/transformer/transform_containers_spec.rb
new file mode 100644
index 0000000..682860f
--- /dev/null
+++ b/spec/unit/pops/transformer/transform_containers_spec.rb
@@ -0,0 +1,182 @@
+#! /usr/bin/env ruby
+require 'spec_helper'
+require 'puppet/pops'
+
+# relative to this spec file (./) does not work as this file is loaded by rspec
+require File.join(File.dirname(__FILE__), '/transformer_rspec_helper')
+
+describe "transformation to Puppet AST for containers" do
+ include TransformerRspecHelper
+
+ context "When transforming file scope" do
+ it "$a = 10 $b = 20" do
+ astdump(parse("$a = 10 $b = 20")).should == "(block (= $a 10) (= $b 20))"
+ end
+
+ it "$a = 10" do
+ astdump(parse("$a = 10")).should == "(= $a 10)"
+ end
+ end
+
+ context "When transforming class" do
+ it "class foo {}" do
+ astdump(parse("class foo {}")).should == "(class foo ())"
+ end
+
+ it "class foo::bar {}" do
+ astdump(parse("class foo::bar {}")).should == "(class foo::bar ())"
+ end
+
+ it "class foo inherits bar {}" do
+ astdump(parse("class foo inherits bar {}")).should == "(class foo (inherits bar) ())"
+ end
+
+ it "class foo($a) {}" do
+ astdump(parse("class foo($a) {}")).should == "(class foo (parameters a) ())"
+ end
+
+ it "class foo($a, $b) {}" do
+ astdump(parse("class foo($a, $b) {}")).should == "(class foo (parameters a b) ())"
+ end
+
+ it "class foo($a, $b=10) {}" do
+ astdump(parse("class foo($a, $b=10) {}")).should == "(class foo (parameters a (= b 10)) ())"
+ end
+
+ it "class foo($a, $b) inherits belgo::bar {}" do
+ astdump(parse("class foo($a, $b) inherits belgo::bar{}")).should == "(class foo (inherits belgo::bar) (parameters a b) ())"
+ end
+
+ it "class foo {$a = 10 $b = 20}" do
+ astdump(parse("class foo {$a = 10 $b = 20}")).should == "(class foo (block (= $a 10) (= $b 20)))"
+ end
+
+ context "it should handle '3x weirdness'" do
+ it "class class {} # a class named 'class'" do
+ # Not as much weird as confusing that it is possible to name a class 'class'. Can have
+ # a very confusing effect when resolving relative names, getting the global hardwired "Class"
+ # instead of some foo::class etc.
+ # This is allowed in 3.x.
+ astdump(parse("class class {}")).should == "(class class ())"
+ end
+
+ it "class default {} # a class named 'default'" do
+ # The weirdness here is that a class can inherit 'default' but not declare a class called default.
+ # (It will work with relative names i.e. foo::default though). The whole idea with keywords as
+ # names is flawed to begin with - it generally just a very bad idea.
+ expect { dump(parse("class default {}")).should == "(class default ())" }.to raise_error(Puppet::ParseError)
+ end
+
+ it "class foo::default {} # a nested name 'default'" do
+ astdump(parse("class foo::default {}")).should == "(class foo::default ())"
+ end
+
+ it "class class inherits default {} # inherits default", :broken => true do
+ astdump(parse("class class inherits default {}")).should == "(class class (inherits default) ())"
+ end
+
+ it "class class inherits default {} # inherits default" do
+ # TODO: See previous test marked as :broken=>true, it is actually this test (result) that is wacky,
+ # this because a class is named at parse time (since class evaluation is lazy, the model must have the
+ # full class name for nested classes - only, it gets this wrong when a class is named "class" - or at least
+ # I think it is wrong.)
+ #
+ astdump(parse("class class inherits default {}")).should == "(class class::class (inherits default) ())"
+ end
+
+ it "class foo inherits class" do
+ astdump(parse("class foo inherits class {}")).should == "(class foo (inherits class) ())"
+ end
+ end
+ end
+
+ context "When transforming define" do
+ it "define foo {}" do
+ astdump(parse("define foo {}")).should == "(define foo ())"
+ end
+
+ it "define foo::bar {}" do
+ astdump(parse("define foo::bar {}")).should == "(define foo::bar ())"
+ end
+
+ it "define foo($a) {}" do
+ astdump(parse("define foo($a) {}")).should == "(define foo (parameters a) ())"
+ end
+
+ it "define foo($a, $b) {}" do
+ astdump(parse("define foo($a, $b) {}")).should == "(define foo (parameters a b) ())"
+ end
+
+ it "define foo($a, $b=10) {}" do
+ astdump(parse("define foo($a, $b=10) {}")).should == "(define foo (parameters a (= b 10)) ())"
+ end
+
+ it "define foo {$a = 10 $b = 20}" do
+ astdump(parse("define foo {$a = 10 $b = 20}")).should == "(define foo (block (= $a 10) (= $b 20)))"
+ end
+
+ context "it should handle '3x weirdness'" do
+ it "define class {} # a define named 'class'" do
+ # This is weird because Class already exists, and instantiating this define will probably not
+ # work
+ astdump(parse("define class {}")).should == "(define class ())"
+ end
+
+ it "define default {} # a define named 'default'" do
+ # Check unwanted ability to define 'default'.
+ # The expression below is not allowed (which is good).
+ #
+ expect { dump(parse("define default {}")).should == "(define default ())"}.to raise_error(Puppet::ParseError)
+ end
+ end
+ end
+
+ context "When transforming node" do
+ it "node foo {}" do
+ # AST can not differentiate between bare word and string
+ astdump(parse("node foo {}")).should == "(node (matches 'foo') ())"
+ end
+
+ it "node foo, x.bar, default {}" do
+ # AST can not differentiate between bare word and string
+ astdump(parse("node foo, x_bar, default {}")).should == "(node (matches 'foo' 'x_bar' :default) ())"
+ end
+
+ it "node 'foo' {}" do
+ # AST can not differentiate between bare word and string
+ astdump(parse("node 'foo' {}")).should == "(node (matches 'foo') ())"
+ end
+
+ it "node foo inherits x::bar {}" do
+ # AST can not differentiate between bare word and string
+ astdump(parse("node foo inherits x_bar {}")).should == "(node (matches 'foo') (parent x_bar) ())"
+ end
+
+ it "node foo inherits 'bar' {}" do
+ # AST can not differentiate between bare word and string
+ astdump(parse("node foo inherits 'bar' {}")).should == "(node (matches 'foo') (parent 'bar') ())"
+ end
+
+ it "node foo inherits default {}" do
+ # AST can not differentiate between bare word and string
+ astdump(parse("node foo inherits default {}")).should == "(node (matches 'foo') (parent :default) ())"
+ end
+
+ it "node /web.*/ {}" do
+ astdump(parse("node /web.*/ {}")).should == "(node (matches /web.*/) ())"
+ end
+
+ it "node /web.*/, /do\.wop.*/, and.so.on {}" do
+ astdump(parse("node /web.*/, /do\.wop.*/, 'and.so.on' {}")).should == "(node (matches /web.*/ /do\.wop.*/ 'and.so.on') ())"
+ end
+
+ it "node wat inherits /apache.*/ {}" do
+ expect { parse("node wat inherits /apache.*/ {}")}.to raise_error(Puppet::ParseError)
+ end
+
+ it "node foo inherits bar {$a = 10 $b = 20}" do
+ # AST can not differentiate between bare word and string
+ astdump(parse("node foo inherits bar {$a = 10 $b = 20}")).should == "(node (matches 'foo') (parent bar) (block (= $a 10) (= $b 20)))"
+ end
+ end
+end
diff --git a/spec/unit/pops/transformer/transform_resource_spec.rb b/spec/unit/pops/transformer/transform_resource_spec.rb
new file mode 100644
index 0000000..251ef2f
--- /dev/null
+++ b/spec/unit/pops/transformer/transform_resource_spec.rb
@@ -0,0 +1,185 @@
+#! /usr/bin/env ruby
+require 'spec_helper'
+require 'puppet/pops'
+
+# relative to this spec file (./) does not work as this file is loaded by rspec
+require File.join(File.dirname(__FILE__), '/transformer_rspec_helper')
+
+describe "transformation to Puppet AST for resource declarations" do
+ include TransformerRspecHelper
+
+ context "When transforming regular resource" do
+ it "file { 'title': }" do
+ astdump(parse("file { 'title': }")).should == [
+ "(resource file",
+ " ('title'))"
+ ].join("\n")
+ end
+
+ it "file { 'title': ; 'other_title': }" do
+ astdump(parse("file { 'title': ; 'other_title': }")).should == [
+ "(resource file",
+ " ('title')",
+ " ('other_title'))"
+ ].join("\n")
+ end
+
+ it "file { 'title': path => '/somewhere', mode => 0777}" do
+ astdump(parse("file { 'title': path => '/somewhere', mode => 0777}")).should == [
+ "(resource file",
+ " ('title'",
+ " (path => '/somewhere')",
+ " (mode => 0777)))"
+ ].join("\n")
+ end
+
+ it "file { 'title1': path => 'x'; 'title2': path => 'y'}" do
+ astdump(parse("file { 'title1': path => 'x'; 'title2': path => 'y'}")).should == [
+ "(resource file",
+ " ('title1'",
+ " (path => 'x'))",
+ " ('title2'",
+ " (path => 'y')))",
+ ].join("\n")
+ end
+ end
+
+ context "When transforming resource defaults" do
+ it "File { }" do
+ astdump(parse("File { }")).should == "(resource-defaults file)"
+ end
+
+ it "File { mode => 0777 }" do
+ astdump(parse("File { mode => 0777}")).should == [
+ "(resource-defaults file",
+ " (mode => 0777))"
+ ].join("\n")
+ end
+ end
+
+ context "When transforming resource override" do
+ it "File['x'] { }" do
+ astdump(parse("File['x'] { }")).should == "(override (slice file 'x'))"
+ end
+
+ it "File['x'] { x => 1 }" do
+ astdump(parse("File['x'] { x => 1}")).should == "(override (slice file 'x')\n (x => 1))"
+ end
+
+ it "File['x', 'y'] { x => 1 }" do
+ astdump(parse("File['x', 'y'] { x => 1}")).should == "(override (slice file ('x' 'y'))\n (x => 1))"
+ end
+
+ it "File['x'] { x => 1, y => 2 }" do
+ astdump(parse("File['x'] { x => 1, y=> 2}")).should == "(override (slice file 'x')\n (x => 1)\n (y => 2))"
+ end
+
+ it "File['x'] { x +> 1 }" do
+ astdump(parse("File['x'] { x +> 1}")).should == "(override (slice file 'x')\n (x +> 1))"
+ end
+ end
+
+ context "When transforming virtual and exported resources" do
+ it "@@file { 'title': }" do
+ astdump(parse("@@file { 'title': }")).should == "(exported-resource file\n ('title'))"
+ end
+
+ it "@file { 'title': }" do
+ astdump(parse("@file { 'title': }")).should == "(virtual-resource file\n ('title'))"
+ end
+ end
+
+ context "When transforming class resource" do
+ it "class { 'cname': }" do
+ astdump(parse("class { 'cname': }")).should == [
+ "(resource class",
+ " ('cname'))"
+ ].join("\n")
+ end
+
+ it "class { 'cname': x => 1, y => 2}" do
+ astdump(parse("class { 'cname': x => 1, y => 2}")).should == [
+ "(resource class",
+ " ('cname'",
+ " (x => 1)",
+ " (y => 2)))"
+ ].join("\n")
+ end
+
+ it "class { 'cname1': x => 1; 'cname2': y => 2}" do
+ astdump(parse("class { 'cname1': x => 1; 'cname2': y => 2}")).should == [
+ "(resource class",
+ " ('cname1'",
+ " (x => 1))",
+ " ('cname2'",
+ " (y => 2)))",
+ ].join("\n")
+ end
+ end
+
+ context "When transforming Relationships" do
+ it "File[a] -> File[b]" do
+ astdump(parse("File[a] -> File[b]")).should == "(-> (slice file a) (slice file b))"
+ end
+
+ it "File[a] <- File[b]" do
+ astdump(parse("File[a] <- File[b]")).should == "(<- (slice file a) (slice file b))"
+ end
+
+ it "File[a] ~> File[b]" do
+ astdump(parse("File[a] ~> File[b]")).should == "(~> (slice file a) (slice file b))"
+ end
+
+ it "File[a] <~ File[b]" do
+ astdump(parse("File[a] <~ File[b]")).should == "(<~ (slice file a) (slice file b))"
+ end
+
+ it "Should chain relationships" do
+ astdump(parse("a -> b -> c")).should ==
+ "(-> (-> a b) c)"
+ end
+
+ it "Should chain relationships" do
+ astdump(parse("File[a] -> File[b] ~> File[c] <- File[d] <~ File[e]")).should ==
+ "(<~ (<- (~> (-> (slice file a) (slice file b)) (slice file c)) (slice file d)) (slice file e))"
+ end
+ end
+
+ context "When transforming collection" do
+ context "of virtual resources" do
+ it "File <| |>" do
+ astdump(parse("File <| |>")).should == "(collect file\n (<| |>))"
+ end
+ end
+
+ context "of exported resources" do
+ it "File <<| |>>" do
+ astdump(parse("File <<| |>>")).should == "(collect file\n (<<| |>>))"
+ end
+ end
+
+ context "queries are parsed with correct precedence" do
+ it "File <| tag == 'foo' |>" do
+ astdump(parse("File <| tag == 'foo' |>")).should == "(collect file\n (<| |> (== tag 'foo')))"
+ end
+
+ it "File <| tag == 'foo' and mode != 0777 |>" do
+ astdump(parse("File <| tag == 'foo' and mode != 0777 |>")).should == "(collect file\n (<| |> (&& (== tag 'foo') (!= mode 0777))))"
+ end
+
+ it "File <| tag == 'foo' or mode != 0777 |>" do
+ astdump(parse("File <| tag == 'foo' or mode != 0777 |>")).should == "(collect file\n (<| |> (|| (== tag 'foo') (!= mode 0777))))"
+ end
+
+ it "File <| tag == 'foo' or tag == 'bar' and mode != 0777 |>" do
+ astdump(parse("File <| tag == 'foo' or tag == 'bar' and mode != 0777 |>")).should ==
+ "(collect file\n (<| |> (|| (== tag 'foo') (&& (== tag 'bar') (!= mode 0777)))))"
+ end
+
+ it "File <| (tag == 'foo' or tag == 'bar') and mode != 0777 |>" do
+ astdump(parse("File <| (tag == 'foo' or tag == 'bar') and mode != 0777 |>")).should ==
+ "(collect file\n (<| |> (&& (|| (== tag 'foo') (== tag 'bar')) (!= mode 0777))))"
+ end
+ end
+ end
+end
diff --git a/spec/unit/pops/transformer/transformer_rspec_helper.rb b/spec/unit/pops/transformer/transformer_rspec_helper.rb
new file mode 100644
index 0000000..94c3f02
--- /dev/null
+++ b/spec/unit/pops/transformer/transformer_rspec_helper.rb
@@ -0,0 +1,27 @@
+require 'puppet/pops'
+require 'puppet/parser/ast'
+
+require File.join(File.dirname(__FILE__), '/../factory_rspec_helper')
+
+module TransformerRspecHelper
+ include FactoryRspecHelper
+ # Dumps the AST to string form
+ #
+ def astdump(ast)
+ ast = transform(ast) unless ast.kind_of?(Puppet::Parser::AST)
+ Puppet::Pops::Model::AstTreeDumper.new.dump(ast)
+ end
+
+ # Transforms the Pops model to an AST model
+ #
+ def transform(model)
+ Puppet::Pops::Model::AstTransformer.new.transform(model)
+ end
+
+ # Parses the string code to a Pops model
+ #
+ def parse(code)
+ parser = Puppet::Pops::Parser::Parser.new()
+ parser.parse_string(code)
+ end
+end
diff --git a/spec/unit/pops/visitor_spec.rb b/spec/unit/pops/visitor_spec.rb
new file mode 100644
index 0000000..c5858c2
--- /dev/null
+++ b/spec/unit/pops/visitor_spec.rb
@@ -0,0 +1,94 @@
+#! /usr/bin/env ruby
+require 'spec_helper'
+require 'puppet/pops'
+
+describe Puppet::Pops::Visitor do
+ describe "A visitor and a visitable in a configuration with min and max args set to 0" do
+ class DuckProcessor
+ def initialize
+ @friend_visitor = Puppet::Pops::Visitor.new(self, "friend", 0, 0)
+ end
+
+ def hi(o, *args)
+ @friend_visitor.visit(o, *args)
+ end
+
+ def friend_Duck(o)
+ "Hi #{o.class}"
+ end
+
+ def friend_Numeric(o)
+ "Howdy #{o.class}"
+ end
+ end
+
+ class Duck
+ include Puppet::Pops::Visitable
+ end
+
+ it "should select the expected method when there are no arguments" do
+ duck = Duck.new
+ duck_processor = DuckProcessor.new
+ duck_processor.hi(duck).should == "Hi Duck"
+ end
+
+ it "should fail if there are too many arguments" do
+ duck = Duck.new
+ duck_processor = DuckProcessor.new
+ expect { duck_processor.hi(duck, "how are you?") }.to raise_error(/^Visitor Error: Too many.*/)
+ end
+
+ it "should select method for superclass" do
+ duck_processor = DuckProcessor.new
+ duck_processor.hi(42).should == "Howdy Fixnum"
+ end
+
+ it "should select method for superclass" do
+ duck_processor = DuckProcessor.new
+ duck_processor.hi(42.0).should == "Howdy Float"
+ end
+
+ it "should fail if class not handled" do
+ duck_processor = DuckProcessor.new
+ expect { duck_processor.hi("wassup?") }.to raise_error(/Visitor Error: the configured.*/)
+ end
+ end
+
+ describe "A visitor and a visitable in a configuration with min =1, and max args set to 2" do
+ class DuckProcessor2
+ def initialize
+ @friend_visitor = Puppet::Pops::Visitor.new(self, "friend", 1, 2)
+ end
+
+ def hi(o, *args)
+ @friend_visitor.visit(o, *args)
+ end
+
+ def friend_Duck(o, drink, eat="grain")
+ "Hi #{o.class}, drink=#{drink}, eat=#{eat}"
+ end
+ end
+
+ class Duck
+ include Puppet::Pops::Visitable
+ end
+
+ it "should select the expected method when there are is one arguments" do
+ duck = Duck.new
+ duck_processor = DuckProcessor2.new
+ duck_processor.hi(duck, "water").should == "Hi Duck, drink=water, eat=grain"
+ end
+
+ it "should fail if there are too many arguments" do
+ duck = Duck.new
+ duck_processor = DuckProcessor2.new
+ expect { duck_processor.hi(duck, "scotch", "soda", "peanuts") }.to raise_error(/^Visitor Error: Too many.*/)
+ end
+
+ it "should fail if there are too few arguments" do
+ duck = Duck.new
+ duck_processor = DuckProcessor2.new
+ expect { duck_processor.hi(duck) }.to raise_error(/^Visitor Error: Too few.*/)
+ end
+ end
+end
diff --git a/spec/unit/resource/type_spec.rb b/spec/unit/resource/type_spec.rb
index e80e903..f891d5c 100755
--- a/spec/unit/resource/type_spec.rb
+++ b/spec/unit/resource/type_spec.rb
@@ -715,13 +715,13 @@ def code(str)
dest.doc.should == "foonessyayness"
end
- it "should turn its code into an ASTArray if necessary" do
+ it "should turn its code into a BlockExpression if necessary" do
dest = Puppet::Resource::Type.new(:hostclass, "bar", :code => code("foo"))
source = Puppet::Resource::Type.new(:hostclass, "foo", :code => code("bar"))
dest.merge(source)
- dest.code.should be_instance_of(Puppet::Parser::AST::ASTArray)
+ dest.code.should be_instance_of(Puppet::Parser::AST::BlockExpression)
end
it "should set the other class's code as its code if it has none" do
@@ -742,7 +742,7 @@ def code(str)
dest.merge(source)
- dest.code.children.collect { |l| l.value }.should == %w{dest source}
+ dest.code.children.collect { |l| l[0].value }.should == %w{dest source}
end
end
end
On Fri Apr 05 23:18:04 UTC 2013 pull request #1584 was closed.
(#11331) A whole new parser and some iteration goodness requested by (zaphod42)
The pull request was merged by: zaphod42