summaryrefslogtreecommitdiffstats
path: root/lib/puppet/parser/ast.rb
diff options
context:
space:
mode:
Diffstat (limited to 'lib/puppet/parser/ast.rb')
-rw-r--r--lib/puppet/parser/ast.rb1463
1 files changed, 21 insertions, 1442 deletions
diff --git a/lib/puppet/parser/ast.rb b/lib/puppet/parser/ast.rb
index d586a23f3..9854b5d34 100644
--- a/lib/puppet/parser/ast.rb
+++ b/lib/puppet/parser/ast.rb
@@ -4,11 +4,13 @@ require 'puppet'
module Puppet
module Parser
-
# The base class for all of the objects that make up the parse trees.
# Handles things like file name, line #, and also does the initialization
# for all of the parameters of all of the child objects.
class AST
+ # Do this so I don't have to type the full path in all of the subclasses
+ AST = Puppet::Parser::AST
+
Puppet.setdefault(:typecheck, true)
Puppet.setdefault(:paramcheck, true)
attr_accessor :line, :file, :parent
@@ -114,1450 +116,27 @@ module Puppet
end
}
end
-
- # The parent class of all AST objects that contain other AST objects.
- # Everything but the really simple objects descend from this. It is
- # important to note that Branch objects contain other AST objects only --
- # if you want to contain values, use a descendent of the AST::Leaf class.
- class Branch < AST
- include Enumerable
- attr_accessor :pin, :children
-
- # Yield each contained AST node in turn. Used mostly by 'evaluate'.
- # This definition means that I don't have to override 'evaluate'
- # every time, but each child of Branch will likely need to override
- # this method.
- def each
- @children.each { |child|
- yield child
- }
- end
-
- # Initialize our object. Largely relies on the method from the base
- # class, but also does some verification.
- def initialize(arghash)
- super(arghash)
-
- # Create the hash, if it was not set at initialization time.
- unless defined? @children
- @children = []
- end
-
- # Verify that we only got valid AST nodes.
- @children.each { |child|
- unless child.is_a?(AST)
- raise Puppet::DevError,
- "child %s is not an ast" % child
- end
- }
- end
-
- # Pretty-print the parse tree.
- def tree(indent = 0)
- return ((@@indline * indent) +
- self.typewrap(self.pin)) + "\n" + self.collect { |child|
- child.tree(indent + 1)
- }.join("\n")
- end
- end
-
- # The basic container class. This object behaves almost identically
- # to a normal array except at initialization time. Note that its name
- # is 'AST::ASTArray', rather than plain 'AST::Array'; I had too many
- # bugs when it was just 'AST::Array', because things like
- # 'object.is_a?(Array)' never behaved as I expected.
- class ASTArray < AST::Branch
- include Enumerable
-
- # Return a child by index. Probably never used.
- def [](index)
- @children[index]
- end
-
- # Evaluate our children.
- def evaluate(scope)
- rets = nil
- # We basically always operate declaratively, and when we
- # do we need to evaluate the settor-like statements first. This
- # is basically variable and type-default declarations.
- if scope.declarative?
- test = [
- AST::VarDef, AST::TypeDefaults
- ]
-
- settors = []
- others = []
- @children.each { |child|
- if test.include?(child.class)
- settors.push child
- else
- others.push child
- end
- }
- rets = [settors,others].flatten.collect { |child|
- child.safeevaluate(scope)
- }
- else
- # If we're not declarative, just do everything in order.
- rets = @children.collect { |item|
- item.safeevaluate(scope)
- }
- end
- rets = rets.reject { |obj| obj.nil? }
- 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)
- }
-
- return self
- end
-
- # Convert to a string. Only used for printing the parse tree.
- def to_s
- return "[" + @children.collect { |child|
- child.to_s
- }.join(", ") + "]"
- end
-
- # Print the parse tree.
- def tree(indent = 0)
- #puts((AST.indent * indent) + self.pin)
- self.collect { |child|
- child.tree(indent)
- }.join("\n" + (AST.midline * (indent+1)) + "\n")
- end
- end
-
- # A simple container class, containing the parameters for an object.
- # Used for abstracting the grammar declarations. Basically unnecessary
- # except that I kept finding bugs because I had too many arrays that
- # meant completely different things.
- class ObjectInst < ASTArray; end
-
- # Another simple container class to make sure we can correctly arrayfy
- # things.
- class CompArgument < ASTArray; end
-
- # The base class for all of the leaves of the parse trees. These
- # basically just have types and values. Both of these parameters
- # are simple values, not AST objects.
- class Leaf < AST
- attr_accessor :value, :type
-
- # Return our value.
- def evaluate(scope)
- return @value
- end
-
- # Print the value in parse tree context.
- def tree(indent = 0)
- return ((@@indent * indent) + self.typewrap(self.value))
- end
-
- def to_s
- return @value
- end
- end
-
- # The boolean class. True or false. Converts the string it receives
- # to a Ruby boolean.
- class Boolean < AST::Leaf
-
- # Use the parent method, but then convert to a real boolean.
- def initialize(hash)
- super
-
- unless @value == 'true' or @value == 'false'
- error = Puppet::DevError.new(
- "'%s' is not a boolean" % @value
- )
- error.stack = caller
- raise error
- end
- if @value == 'true'
- @value = true
- else
- @value = false
- end
- end
- end
-
- # The base string class.
- class String < AST::Leaf
- # Interpolate the string looking for variables, and then return
- # the result.
- def evaluate(scope)
- return scope.strinterp(@value)
- end
- end
- #---------------------------------------------------------------
-
- # The 'default' option on case statements and selectors.
- class Default < AST::Leaf; end
-
- # Capitalized words; used mostly for type-defaults, but also
- # get returned by the lexer any other time an unquoted capitalized
- # word is found.
- class Type < AST::Leaf; end
-
- # Lower-case words.
- class Name < AST::Leaf; end
-
- # A simple variable. This object is only used during interpolation;
- # the VarDef class is used for assignment.
- class Variable < Name
- # Looks up the value of the object in the scope tree (does
- # not include syntactical constructs, like '$' and '{}').
- def evaluate(scope)
- begin
- return scope.lookupvar(@value)
- rescue Puppet::ParseError => except
- except.line = self.line
- except.file = self.file
- raise except
- rescue => detail
- error = Puppet::DevError.new(detail)
- error.line = self.line
- error.file = self.file
- error.stack = caller
- raise error
- end
- end
- end
-
- # Any normal puppet object declaration. Can result in a class or a
- # component, in addition to builtin types.
- class ObjectDef < AST::Branch
- attr_accessor :name, :type
- attr_reader :params
-
- # probably not used at all
- def []=(index,obj)
- @params[index] = obj
- end
-
- # probably not used at all
- def [](index)
- return @params[index]
- end
-
- # Auto-generate a name
- def autoname(type, object)
- case object
- when Puppet::Type:
- raise Puppet::Error,
- "Built-in types must be provided with a name"
- when HostClass:
- return type
- else
- Puppet.info "Autogenerating name for object of type %s" %
- type
- return [type, "-", self.object_id].join("")
- end
- end
-
- # Iterate across all of our children.
- def each
- [@type,@name,@params].flatten.each { |param|
- #Puppet.debug("yielding param %s" % param)
- yield param
- }
- end
-
- # Does not actually return an object; instead sets an object
- # in the current scope.
- def evaluate(scope)
- hash = {}
-
- # Get our type and name.
- objtype = @type.safeevaluate(scope)
-
- # If the type was a variable, we wouldn't have typechecked yet.
- # Do it now, if so.
- unless @checked
- self.typecheck(objtype)
- end
-
- # See if our object was defined
- begin
- object = scope.lookuptype(objtype)
- rescue Puppet::ParseError => except
- except.line = self.line
- except.file = self.file
- raise except
- rescue => detail
- error = Puppet::ParseError.new(detail)
- error.line = self.line
- error.file = self.file
- error.stack = caller
- raise error
- end
-
- unless object
- # If not, verify that it's a builtin type
- begin
- object = Puppet::Type.type(objtype)
- rescue TypeError
- # otherwise, the user specified an invalid type
- error = Puppet::ParseError.new(
- "Invalid type %s" % objtype
- )
- error.line = @line
- error.file = @file
- raise error
- end
- end
-
- # Autogenerate the name if one was not passed.
- if defined? @name
- objnames = @name.safeevaluate(scope)
- else
- objnames = self.autoname(objtype, object)
- end
-
- # it's easier to always use an array, even for only one name
- unless objnames.is_a?(Array)
- objnames = [objnames]
- end
-
- # Retrieve the defaults for our type
- hash = getdefaults(objtype, scope)
-
- # then set all of the specified params
- @params.each { |param|
- ary = param.safeevaluate(scope)
- hash[ary[0]] = ary[1]
- }
-
- # this is where our implicit iteration takes place;
- # if someone passed an array as the name, then we act
- # just like the called us many times
- objnames.collect { |objname|
- # If the object is a class, that means it's a builtin type
- if object.is_a?(Class)
- begin
- Puppet.debug(
- ("Setting object '%s' " +
- "in scope %s " +
- "with arguments %s") %
- [objname, scope.object_id, hash.inspect]
- )
- obj = scope.setobject(
- objtype,
- objname,
- hash,
- @file,
- @line
- )
- rescue Puppet::ParseError => except
- except.line = self.line
- except.file = self.file
- raise except
- rescue => detail
- error = Puppet::ParseError.new(detail)
- error.line = self.line
- error.file = self.file
- error.stack = caller
- raise error
- end
- else
- # but things like components create a new type; if we find
- # one of those, evaluate that with our arguments
- Puppet.debug("Calling object '%s' with arguments %s" %
- [object.name, hash.inspect])
- object.safeevaluate(scope,hash,objtype,objname)
- end
- }.reject { |obj| obj.nil? }
- end
-
- # Retrieve the defaults for our type
- def getdefaults(objtype, scope)
- # first, retrieve the defaults
- begin
- defaults = scope.lookupdefaults(objtype)
- if defaults.length > 0
- Puppet.debug "Got defaults for %s: %s" %
- [objtype,defaults.inspect]
- end
- rescue => detail
- raise Puppet::DevError,
- "Could not lookup defaults for %s: %s" %
- [objtype, detail.to_s]
- end
-
- hash = {}
- # Add any found defaults to our argument list
- defaults.each { |var,value|
- Puppet.debug "Found default %s for %s" %
- [var,objtype]
-
- hash[var] = value
- }
-
- return hash
- end
-
- # Create our ObjectDef. Handles type checking for us.
- def initialize(hash)
- @checked = false
- super
-
- if @type.is_a?(Variable)
- Puppet.debug "Delaying typecheck"
- return
- else
- self.typecheck(@type.value)
-
- objtype = @type.value
- end
-
- end
-
- # Verify that all passed parameters are valid
- def paramcheck(builtin, objtype)
- # This defaults to true
- unless Puppet[:paramcheck]
- return
- end
-
- @params.each { |param|
- if builtin
- self.parambuiltincheck(builtin, param)
- else
- self.paramdefinedcheck(objtype, param)
- end
- }
- end
-
- def parambuiltincheck(type, param)
- unless param.is_a?(AST::ObjectParam)
- raise Puppet::DevError,
- "Got something other than param"
- end
- begin
- pname = param.param.value
- rescue => detail
- raise Puppet::DevError, detail.to_s
- end
- next if pname == "name" # always allow these
- unless type.validattr?(pname)
- error = Puppet::ParseError.new(
- "Invalid parameter '%s' for type '%s'" %
- [pname,type.name]
- )
- error.stack = caller
- error.line = self.line
- error.file = self.file
- raise error
- end
- end
-
- def paramdefinedcheck(objtype, param)
- # FIXME we might need to do more here eventually...
- if Puppet::Type.metaparam?(param.param.value.intern)
- next
- end
-
- begin
- pname = param.param.value
- rescue => detail
- raise Puppet::DevError, detail.to_s
- end
-
- unless @@settypes[objtype].validarg?(pname)
- error = Puppet::ParseError.new(
- "Invalid parameter '%s' for type '%s'" %
- [pname,objtype]
- )
- error.stack = caller
- error.line = self.line
- error.file = self.file
- raise error
- end
- end
-
- # Set the parameters for our object.
- def params=(params)
- if params.is_a?(AST::ASTArray)
- @params = params
- else
- @params = AST::ASTArray.new(
- :line => params.line,
- :file => params.file,
- :children => [params]
- )
- end
- end
-
- # Print this object out.
- def tree(indent = 0)
- return [
- @type.tree(indent + 1),
- @name.tree(indent + 1),
- ((@@indline * indent) + self.typewrap(self.pin)),
- @params.collect { |param|
- begin
- param.tree(indent + 1)
- rescue NoMethodError => detail
- Puppet.err @params.inspect
- error = Puppet::DevError.new(
- "failed to tree a %s" % self.class
- )
- error.stack = caller
- raise error
- end
- }.join("\n")
- ].join("\n")
- end
-
- # Verify that the type is valid. This throws an error if there's
- # a problem, so the return value doesn't matter
- def typecheck(objtype)
- # This will basically always be on, but I wanted to make it at
- # least simple to turn off if it came to that
- unless Puppet[:typecheck]
- return
- end
-
- builtin = false
- begin
- builtin = Puppet::Type.type(objtype)
- rescue TypeError
- # nothing; we've already set builtin to false
- end
-
- unless builtin or @@settypes.include?(objtype)
- error = Puppet::ParseError.new(
- "Unknown type '%s'" % objtype
- )
- error.line = self.line
- error.file = self.file
- error.stack = caller
- raise error
- end
-
- unless builtin
- Puppet.debug "%s is a defined type" % objtype
- end
-
- self.paramcheck(builtin, objtype)
-
- @checked = true
- end
-
- def to_s
- return "%s => { %s }" % [@name,
- @params.collect { |param|
- param.to_s
- }.join("\n")
- ]
- end
- end
-
- # A reference to an object. Only valid as an rvalue.
- class ObjectRef < AST::Branch
- attr_accessor :name, :type
-
- def each
- [@type,@name].flatten.each { |param|
- #Puppet.debug("yielding param %s" % param)
- yield param
- }
- end
-
- # Evaluate our object, but just return a simple array of the type
- # and name.
- def evaluate(scope)
- objtype = @type.safeevaluate(scope)
- objnames = @name.safeevaluate(scope)
-
- # it's easier to always use an array, even for only one name
- unless objnames.is_a?(Array)
- objnames = [objnames]
- end
-
- # Verify we can find the object.
- begin
- object = scope.lookuptype(objtype)
- rescue Puppet::ParseError => except
- except.line = self.line
- except.file = self.file
- raise except
- rescue => detail
- error = Puppet::ParseError.new(detail)
- error.line = self.line
- error.file = self.file
- error.stack = caller
- raise error
- end
- Puppet.debug "ObjectRef returned type %s" % object
-
- # should we implicitly iterate here?
- # yes, i believe that we essentially have to...
- objnames.collect { |objname|
- if object.is_a?(AST::Component)
- objname = "%s[%s]" % [objtype,objname]
- objtype = "component"
- end
- [objtype,objname]
- }.reject { |obj| obj.nil? }
- end
-
- def tree(indent = 0)
- return [
- @type.tree(indent + 1),
- @name.tree(indent + 1),
- ((@@indline * indent) + self.typewrap(self.pin))
- ].join("\n")
- end
-
- def to_s
- return "%s[%s]" % [@name,@type]
- end
- end
-
- # The AST object for the parameters inside ObjectDefs and Selectors.
- class ObjectParam < AST::Branch
- attr_accessor :value, :param
-
- def each
- [@param,@value].each { |child| yield child }
- end
-
- # Return the parameter and the value.
- def evaluate(scope)
- param = @param.safeevaluate(scope)
- value = @value.safeevaluate(scope)
- return [param, value]
- end
-
- def tree(indent = 0)
- return [
- @param.tree(indent + 1),
- ((@@indline * indent) + self.typewrap(self.pin)),
- @value.tree(indent + 1)
- ].join("\n")
- end
-
- def to_s
- return "%s => %s" % [@param,@value]
- end
- end
-
- # The basic logical structure in Puppet. Supports a list of
- # tests and statement arrays.
- class CaseStatement < AST::Branch
- attr_accessor :test, :options, :default
-
- # Short-curcuit evaluation. Return the value of the statements for
- # the first option that matches.
- def evaluate(scope)
- value = @test.safeevaluate(scope)
-
- retvalue = nil
- found = false
-
- # Iterate across the options looking for a match.
- @options.each { |option|
- if option.eachvalue { |opval| break true if opval == value }
- # we found a matching option
- retvalue = option.safeevaluate(scope)
- found = true
- break
- end
- }
-
- # Unless we found something, look for the default.
- unless found
- if defined? @default
- retvalue = @default.safeevaluate(scope)
- else
- Puppet.debug "No true answers and no default"
- end
- end
- return retvalue
- end
-
- # Do some input validation on our options.
- def initialize(hash)
- values = {}
-
- super
-
- # This won't work if we move away from only allowing
- # constants here, but for now, it's fine and useful.
- @options.each { |option|
- unless option.is_a?(CaseOpt)
- raise Puppet::DevError, "Option is not a CaseOpt"
- end
- if option.default?
- @default = option
- end
- option.eachvalue { |val|
- if values.include?(val)
- raise Puppet::ParseError,
- "Value %s appears twice in case statement" %
- val
- else
- values[val] = true
- end
- }
- }
- end
-
- def tree(indent = 0)
- rettree = [
- @test.tree(indent + 1),
- ((@@indline * indent) + self.typewrap(self.pin)),
- @options.tree(indent + 1)
- ]
-
- return rettree.flatten.join("\n")
- end
-
- def each
- [@test,@options].each { |child| yield child }
- end
- end
-
- # Each individual option in a case statement.
- class CaseOpt < AST::Branch
- attr_accessor :value, :statements
-
- # CaseOpt is a bit special -- we just want the value first,
- # so that CaseStatement can compare, and then it will selectively
- # decide whether to fully evaluate this option
-
- def each
- [@value,@statements].each { |child| yield child }
- end
-
- # Are we the default option?
- def default?
- if defined? @default
- return @default
- end
-
- if @value.is_a?(AST::ASTArray)
- @value.each { |subval|
- if subval.is_a?(AST::Default)
- @default = true
- break
- end
- }
- else
- if @value.is_a?(AST::Default)
- @default = true
- end
- end
-
- unless defined? @default
- @default = false
- end
-
- return @default
- end
-
- # You can specify a list of values; return each in turn.
- def eachvalue
- if @value.is_a?(AST::ASTArray)
- @value.each { |subval|
- yield subval.value
- }
- else
- yield @value.value
- end
- end
-
- # Evaluate the actual statements; this only gets called if
- # our option matched.
- def evaluate(scope)
- return @statements.safeevaluate(scope.newscope)
- end
-
- def tree(indent = 0)
- rettree = [
- @value.tree(indent + 1),
- ((@@indline * indent) + self.typewrap(self.pin)),
- @statements.tree(indent + 1)
- ]
- return rettree.flatten.join("\n")
- end
- end
-
- # The inline conditional operator. Unlike CaseStatement, which executes
- # code, we just return a value.
- class Selector < AST::Branch
- attr_accessor :param, :values
-
- def each
- [@param,@values].each { |child| yield child }
- end
-
- # Find the value that corresponds with the test.
- def evaluate(scope)
- retvalue = nil
- found = nil
-
- # Get our parameter.
- paramvalue = @param.safeevaluate(scope)
-
- default = nil
-
- # Then look for a match in the options.
- @values.each { |obj|
- param = obj.param.safeevaluate(scope)
- if param == paramvalue
- # we found a matching option
- retvalue = obj.value.safeevaluate(scope)
- found = true
- break
- elsif obj.param.is_a?(Default)
- default = obj
- end
- }
-
- # Unless we found something, look for the default.
- unless found
- if default
- retvalue = default.value.safeevaluate(scope)
- else
- error = Puppet::ParseError.new(
- "No value for selector param '%s'" % paramvalue
- )
- error.line = self.line
- error.file = self.file
- raise error
- end
- end
-
- return retvalue
- end
-
- def tree(indent = 0)
- return [
- @param.tree(indent + 1),
- ((@@indline * indent) + self.typewrap(self.pin)),
- @values.tree(indent + 1)
- ].join("\n")
- end
- end
-
- # Define a variable. Stores the value in the current scope.
- class VarDef < AST::Branch
- attr_accessor :name, :value
-
- # Look up our name and value, and store them appropriately. The
- # lexer strips off the syntax stuff like '$'.
- def evaluate(scope)
- name = @name.safeevaluate(scope)
- value = @value.safeevaluate(scope)
-
- begin
- scope.setvar(name,value)
- rescue Puppet::ParseError => except
- except.line = self.line
- except.file = self.file
- raise except
- rescue => detail
- error = Puppet::ParseError.new(detail)
- error.line = self.line
- error.file = self.file
- error.stack = caller
- raise error
- end
- end
-
- def each
- [@name,@value].each { |child| yield child }
- end
-
- def tree(indent = 0)
- return [
- @name.tree(indent + 1),
- ((@@indline * 4 * indent) + self.typewrap(self.pin)),
- @value.tree(indent + 1)
- ].join("\n")
- end
-
- def to_s
- return "%s => %s" % [@name,@value]
- end
- end
-
- # A statement syntactically similar to an ObjectDef, but uses a
- # capitalized object type and cannot have a name.
- class TypeDefaults < AST::Branch
- attr_accessor :type, :params
-
- def each
- [@type,@params].each { |child| yield child }
- end
-
- # As opposed to ObjectDef, this stores each default for the given
- # object type.
- def evaluate(scope)
- type = @type.safeevaluate(scope)
- params = @params.safeevaluate(scope)
-
- begin
- scope.setdefaults(type.downcase,params)
- rescue Puppet::ParseError => except
- except.line = self.line
- except.file = self.file
- raise except
- rescue => detail
- error = Puppet::ParseError.new(detail)
- error.line = self.line
- error.file = self.file
- error.stack = caller
- raise error
- end
- end
-
- def tree(indent = 0)
- return [
- @type.tree(indent + 1),
- ((@@indline * 4 * indent) + self.typewrap(self.pin)),
- @params.tree(indent + 1)
- ].join("\n")
- end
-
- def to_s
- return "%s { %s }" % [@type,@params]
- end
- end
-
- # Define a new component. This basically just stores the
- # associated parse tree by name in our current scope. Note that
- # there is currently a mismatch in how we look up components -- it
- # usually uses scopes, but sometimes uses '@@settypes'.
- # FIXME This class should verify that each of its direct children
- # has an abstractable name -- i.e., if a file does not include a
- # variable in its name, then the user is essentially guaranteed to
- # encounter an error if the component is instantiated more than
- # once.
- class CompDef < AST::Branch
- attr_accessor :name, :args, :code
-
- def each
- [@name,@args,@code].each { |child| yield child }
- end
-
- # Store the parse tree.
- def evaluate(scope)
- name = @name.safeevaluate(scope)
- args = @args.safeevaluate(scope)
-
- begin
- scope.settype(name,
- AST::Component.new(
- :name => name,
- :args => args,
- :code => @code
- )
- )
- rescue Puppet::ParseError => except
- except.line = self.line
- except.file = self.file
- raise except
- rescue => detail
- error = Puppet::ParseError.new(detail)
- error.line = self.line
- error.file = self.file
- error.stack = caller
- raise error
- end
- end
-
- def initialize(hash)
- @parentclass = nil
- super
-
- Puppet.debug "Defining type %s" % @name.value
-
- # we need to both mark that a given argument is valid,
- # and we need to also store any provided default arguments
- # FIXME This creates a global list of types and their
- # acceptable arguments. This should really be scoped
- # instead.
- @@settypes[@name.value] = self
- end
-
- def tree(indent = 0)
- return [
- @name.tree(indent + 1),
- ((@@indline * 4 * indent) + self.typewrap("define")),
- @args.tree(indent + 1),
- @code.tree(indent + 1),
- ].join("\n")
- end
-
- def to_s
- return "define %s(%s) {\n%s }" % [@name, @args, @code]
- end
-
- # Check whether a given argument is valid. Searches up through
- # any parent classes that might exist.
- def validarg?(param)
- found = false
- if @args.is_a?(AST::ASTArray)
- found = @args.detect { |arg|
- if arg.is_a?(AST::ASTArray)
- arg[0].value == param
- else
- arg.value == param
- end
- }
- else
- found = @args.value == param
- #Puppet.warning "got arg %s" % @args.inspect
- #hash[@args.value] += 1
- end
-
- if found
- return true
- # a nil parentclass is an empty astarray
- # stupid but true
- elsif @parentclass
- parent = @@settypes[@parentclass.value]
- if parent and parent != []
- return parent.validarg?(param)
- else
- raise Puppet::Error, "Could not find parent class %s" %
- @parentclass.value
- end
- else
- return false
- end
-
- end
- end
-
- # Define a new class. Syntactically similar to component definitions,
- # but classes are always singletons -- only one can exist on a given
- # host.
- class ClassDef < AST::CompDef
- attr_accessor :parentclass
-
- def each
- if @parentclass
- #[@name,@args,@parentclass,@code].each { |child| yield child }
- [@name,@parentclass,@code].each { |child| yield child }
- else
- #[@name,@args,@code].each { |child| yield child }
- [@name,@code].each { |child| yield child }
- end
- end
-
- # Store our parse tree according to name.
- def evaluate(scope)
- name = @name.safeevaluate(scope)
- #args = @args.safeevaluate(scope)
-
- #:args => args,
- arghash = {
- :name => name,
- :code => @code
- }
-
- if @parentclass
- arghash[:parentclass] = @parentclass.safeevaluate(scope)
- end
-
- #Puppet.debug("defining hostclass '%s' with arguments [%s]" %
- # [name,args])
-
- begin
- scope.settype(name,
- HostClass.new(arghash)
- )
- rescue Puppet::ParseError => except
- except.line = self.line
- except.file = self.file
- raise except
- rescue => detail
- error = Puppet::ParseError.new(detail)
- error.line = self.line
- error.file = self.file
- error.stack = caller
- raise error
- end
- end
-
- def initialize(hash)
- @parentclass = nil
- super
- end
-
- def tree(indent = 0)
- #@args.tree(indent + 1),
- return [
- @name.tree(indent + 1),
- ((@@indline * 4 * indent) + self.typewrap("class")),
- @parentclass ? @parentclass.tree(indent + 1) : "",
- @code.tree(indent + 1),
- ].join("\n")
- end
-
- def to_s
- return "class %s(%s) inherits %s {\n%s }" %
- [@name, @parentclass, @code]
- #[@name, @args, @parentclass, @code]
- end
- end
-
- # Define a node. The node definition stores a parse tree for each
- # specified node, and this parse tree is only ever looked up when
- # a client connects.
- class NodeDef < AST::Branch
- attr_accessor :names, :code, :parentclass
-
- def each
- [@names,@code].each { |child| yield child }
- end
-
- # Do implicit iteration over each of the names passed.
- def evaluate(scope)
- names = @names.safeevaluate(scope)
-
- unless names.is_a?(Array)
- names = [names]
- end
-
- names.each { |name|
- Puppet.debug("defining host '%s' in scope %s" %
- [name, scope.object_id])
- arghash = {
- :name => name,
- :code => @code
- }
-
- if @parentclass
- arghash[:parentclass] = @parentclass.safeevaluate(scope)
- end
-
- begin
- scope.setnode(name,
- Node.new(arghash)
- )
- rescue Puppet::ParseError => except
- except.line = self.line
- except.file = self.file
- raise except
- rescue => detail
- error = Puppet::ParseError.new(detail)
- error.line = self.line
- error.file = self.file
- error.stack = caller
- raise error
- end
- }
- end
-
- def initialize(hash)
- @parentclass = nil
- super
- end
-
- def tree(indent = 0)
- return [
- @names.tree(indent + 1),
- ((@@indline * 4 * indent) + self.typewrap("node")),
- @code.tree(indent + 1),
- ].join("\n")
- end
-
- def to_s
- return "node %s {\n%s }" % [@name, @code]
- end
- end
-
- # Evaluate the stored parse tree for a given component. This will
- # receive the arguments passed to the component and also the type and
- # name of the component.
- class Component < AST::Branch
- class << self
- attr_accessor :name
- end
-
- # The class name
- @name = :component
-
- attr_accessor :name, :args, :code, :scope
-
- def evaluate(scope,hash,objtype,objname)
-
- scope = scope.newscope
-
- # The type is the component or class name
- scope.type = objtype
-
- # The name is the name the user has chosen or that has
- # been dynamically generated. This is almost never used
- scope.name = objname
-
- #if self.is_a?(Node)
- # scope.isnodescope
- #end
-
- # Additionally, add a tag for whatever kind of class
- # we are
- scope.tag(objtype)
-
- unless objname =~ /-\d+/ # it was generated
- scope.tag(objname)
- end
- #scope.base = self.class.name
-
-
- # define all of the arguments in our local scope
- if self.args
-
- # Verify that all required arguments are either present or
- # have been provided with defaults.
- # FIXME This should probably also require each parent
- # class's arguments...
- self.args.each { |arg, default|
- unless hash.include?(arg)
- if defined? default and ! default.nil?
- hash[arg] = default
- Puppet.debug "Got default %s for %s in %s" %
- [default.inspect, arg.inspect, objname.inspect]
- else
- error = Puppet::ParseError.new(
- "Must pass %s to %s of type %s" %
- [arg.inspect,name,objtype]
- )
- error.line = self.line
- error.file = self.file
- error.stack = caller
- raise error
- end
- end
- }
- end
-
- # Set each of the provided arguments as variables in the
- # component's scope.
- hash["name"] = objname
- hash.each { |arg,value|
- begin
- scope.setvar(arg,hash[arg])
- rescue Puppet::ParseError => except
- except.line = self.line
- except.file = self.file
- raise except
- rescue Puppet::ParseError => except
- except.line = self.line
- except.file = self.file
- raise except
- rescue => except
- error = Puppet::ParseError.new(except.message)
- error.line = self.line
- error.file = self.file
- error.stack = caller
- raise error
- end
- }
-
- # Now just evaluate the code with our new bindings.
- self.code.safeevaluate(scope)
-
- # We return the scope, so that our children can make their scopes
- # under ours. This allows them to find our definitions.
- return scope
- end
- end
-
- # The code associated with a class. This is different from components
- # in that each class is a singleton -- only one will exist for a given
- # node.
- class HostClass < AST::Component
- @name = :class
- attr_accessor :parentclass
-
- def evaluate(scope,hash,objtype,objname)
- if scope.lookupclass(@name)
- Puppet.debug "%s class already evaluated" % @name
- return nil
- end
-
- if tmp = self.evalparent(scope, hash, objname)
- # Override our scope binding with the parent scope
- # binding. This is quite hackish, but I can't think
- # of another way to make sure our scopes end up under
- # our parent scopes.
- scope = tmp
- end
-
- # just use the Component evaluate method, but change the type
- # to our own type
- retval = super(scope,hash,@name,objname)
-
- # Set the mark after we evaluate, so we don't record it but
- # then encounter an error
- scope.setclass(@name)
- return retval
- end
-
- # Evaluate our parent class. Parent classes are evaluated in the
- # exact same scope as the children. This is maybe not a good idea
- # but, eh.
- def evalparent(scope, args, name)
- if @parentclass
- parentobj = nil
-
- begin
- parentobj = scope.lookuptype(@parentclass)
- rescue Puppet::ParseError => except
- except.line = self.line
- except.file = self.file
- raise except
- rescue => detail
- error = Puppet::ParseError.new(detail)
- error.line = self.line
- error.file = self.file
- raise error
- end
- unless parentobj
- error = Puppet::ParseError.new(
- "Could not find parent '%s' of '%s'" %
- [@parentclass,@name])
- error.line = self.line
- error.file = self.file
- raise error
- end
-
- # Verify that the parent and child are of the same type
- unless parentobj.class == self.class
- error = Puppet::ParseError.new(
- "Class %s has incompatible parent type, %s vs %s" %
- [@name, parentobj.class, self.class]
- )
- error.file = self.file
- error.line = self.line
- raise error
- end
- return parentobj.safeevaluate(scope,args,@parentclass,name)
- end
- end
-
- def initialize(hash)
- @parentclass = nil
- super
- end
-
- end
-
- # The specific code associated with a host. Nodes are annoyingly unlike
- # other objects. That's just the way it is, at least for now.
- class Node < AST::HostClass
- @name = :node
- attr_accessor :name, :args, :code, :parentclass
-
- def evaluate(scope, facts = {})
- scope = scope.newscope
-
- # nodes are never instantiated like a normal object,
- # but we need the type to be the name users would use for
- # instantiation, otherwise tags don't work out
-
- # The name has already been evaluated, so it's a normal
- # string.
- scope.type = @name
- scope.name = @name
-
- # Mark this scope as a nodescope, so that classes will be
- # singletons within it
- scope.isnodescope
-
- # Now set all of the facts inside this scope
- facts.each { |var, value|
- scope.setvar(var, value)
- }
-
- if tmp = self.evalparent(scope)
- # Again, override our scope with the parent scope, if
- # there is one.
- scope = tmp
- end
-
- #scope.tag(@name)
-
- # We never pass the facts to the parent class, because they've
- # already been defined at this top-level scope.
- #super(scope, facts, @name, @name)
-
- # And then evaluate our code.
- @code.safeevaluate(scope)
-
- return scope
- end
-
- # Evaluate our parent class.
- def evalparent(scope)
- if @parentclass
- # This is pretty messed up. I don't know if this will
- # work in the long term, but we need to evaluate the node
- # in our own scope, even though our parent node has
- # a scope associated with it, because otherwise we 1) won't
- # get our facts defined, and 2) we won't actually get the
- # objects returned, based on how nodes work.
-
- # We also can't just evaluate the node itself, because
- # it would create a node scope within this scope,
- # and that would cause mass havoc.
- node = nil
-
- # The 'node' method just returns a hash of the node
- # code and name. It's used here, and in 'evalnode'.
- unless hash = scope.node(@parentclass)
- raise Puppet::ParseError,
- "Could not find parent node %s" %
- @parentclass
- end
-
- node = hash[:node]
- # Tag the scope with the parent's name/type.
- name = node.name
- #Puppet.info "Tagging with parent node %s" % name
- scope.tag(name)
-
- begin
- code = node.code
- code.safeevaluate(scope)
- rescue Puppet::ParseError => except
- except.line = self.line
- except.file = self.file
- raise except
- rescue => detail
- error = Puppet::ParseError.new(detail)
- error.line = self.line
- error.file = self.file
- raise error
- end
-
- if node.parentclass
- node.evalparent(scope)
- end
- end
- end
-
- def initialize(hash)
- @parentclass = nil
- super
-
- end
- end
#---------------------------------------------------------------
end
end
end
+require 'puppet/parser/ast/astarray'
+require 'puppet/parser/ast/branch'
+require 'puppet/parser/ast/caseopt'
+require 'puppet/parser/ast/casestatement'
+require 'puppet/parser/ast/classdef'
+require 'puppet/parser/ast/compdef'
+require 'puppet/parser/ast/component'
+require 'puppet/parser/ast/hostclass'
+require 'puppet/parser/ast/leaf'
+require 'puppet/parser/ast/node'
+require 'puppet/parser/ast/nodedef'
+require 'puppet/parser/ast/objectdef'
+require 'puppet/parser/ast/objectparam'
+require 'puppet/parser/ast/objectref'
+require 'puppet/parser/ast/selector'
+require 'puppet/parser/ast/typedefaults'
+require 'puppet/parser/ast/vardef'
+
# $Id$