summaryrefslogtreecommitdiffstats
path: root/lib/puppet/parser/ast
diff options
context:
space:
mode:
authorluke <luke@980ebf18-57e1-0310-9a29-db15c13687c0>2006-01-13 23:16:26 +0000
committerluke <luke@980ebf18-57e1-0310-9a29-db15c13687c0>2006-01-13 23:16:26 +0000
commit87b3bb111f2ea68cbeb875f07e826e4f75ea9eea (patch)
treec03530a415b2f90be6b4c6d5b594f0b8c78a3c0b /lib/puppet/parser/ast
parent1d4638a03df6821c16c00db3084f89889f19ac33 (diff)
downloadpuppet-87b3bb111f2ea68cbeb875f07e826e4f75ea9eea.tar.gz
puppet-87b3bb111f2ea68cbeb875f07e826e4f75ea9eea.tar.xz
puppet-87b3bb111f2ea68cbeb875f07e826e4f75ea9eea.zip
Moving ast classes into separate files
git-svn-id: https://reductivelabs.com/svn/puppet/trunk@825 980ebf18-57e1-0310-9a29-db15c13687c0
Diffstat (limited to 'lib/puppet/parser/ast')
-rw-r--r--lib/puppet/parser/ast/astarray.rb85
-rw-r--r--lib/puppet/parser/ast/branch.rb47
-rw-r--r--lib/puppet/parser/ast/caseopt.rb66
-rw-r--r--lib/puppet/parser/ast/casestatement.rb78
-rw-r--r--lib/puppet/parser/ast/classdef.rb77
-rw-r--r--lib/puppet/parser/ast/compdef.rb108
-rw-r--r--lib/puppet/parser/ast/component.rb99
-rw-r--r--lib/puppet/parser/ast/hostclass.rb82
-rw-r--r--lib/puppet/parser/ast/leaf.rb89
-rw-r--r--lib/puppet/parser/ast/node.rb102
-rw-r--r--lib/puppet/parser/ast/nodedef.rb68
-rw-r--r--lib/puppet/parser/ast/objectdef.rb331
-rw-r--r--lib/puppet/parser/ast/objectparam.rb30
-rw-r--r--lib/puppet/parser/ast/objectref.rb64
-rw-r--r--lib/puppet/parser/ast/selector.rb60
-rw-r--r--lib/puppet/parser/ast/typedefaults.rb45
-rw-r--r--lib/puppet/parser/ast/vardef.rb44
17 files changed, 1475 insertions, 0 deletions
diff --git a/lib/puppet/parser/ast/astarray.rb b/lib/puppet/parser/ast/astarray.rb
new file mode 100644
index 000000000..3a02c58ea
--- /dev/null
+++ b/lib/puppet/parser/ast/astarray.rb
@@ -0,0 +1,85 @@
+require 'puppet/parser/ast/branch'
+
+class Puppet::Parser::AST
+ # 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 < 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
+end
diff --git a/lib/puppet/parser/ast/branch.rb b/lib/puppet/parser/ast/branch.rb
new file mode 100644
index 000000000..deb8dbadd
--- /dev/null
+++ b/lib/puppet/parser/ast/branch.rb
@@ -0,0 +1,47 @@
+class Puppet::Parser::AST
+ # 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
+end
diff --git a/lib/puppet/parser/ast/caseopt.rb b/lib/puppet/parser/ast/caseopt.rb
new file mode 100644
index 000000000..258f5081a
--- /dev/null
+++ b/lib/puppet/parser/ast/caseopt.rb
@@ -0,0 +1,66 @@
+class Puppet::Parser::AST
+ # 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
+end
diff --git a/lib/puppet/parser/ast/casestatement.rb b/lib/puppet/parser/ast/casestatement.rb
new file mode 100644
index 000000000..cd87cfd08
--- /dev/null
+++ b/lib/puppet/parser/ast/casestatement.rb
@@ -0,0 +1,78 @@
+class Puppet::Parser::AST
+ # 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
+
+end
diff --git a/lib/puppet/parser/ast/classdef.rb b/lib/puppet/parser/ast/classdef.rb
new file mode 100644
index 000000000..b9f1c1c6b
--- /dev/null
+++ b/lib/puppet/parser/ast/classdef.rb
@@ -0,0 +1,77 @@
+require 'puppet/parser/ast/compdef'
+
+class Puppet::Parser::AST
+ # 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
+
+end
diff --git a/lib/puppet/parser/ast/compdef.rb b/lib/puppet/parser/ast/compdef.rb
new file mode 100644
index 000000000..ffd0dc0e0
--- /dev/null
+++ b/lib/puppet/parser/ast/compdef.rb
@@ -0,0 +1,108 @@
+class Puppet::Parser::AST
+ # 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
+
+end
diff --git a/lib/puppet/parser/ast/component.rb b/lib/puppet/parser/ast/component.rb
new file mode 100644
index 000000000..f1e6b9648
--- /dev/null
+++ b/lib/puppet/parser/ast/component.rb
@@ -0,0 +1,99 @@
+class Puppet::Parser::AST
+ # 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
+
+end
diff --git a/lib/puppet/parser/ast/hostclass.rb b/lib/puppet/parser/ast/hostclass.rb
new file mode 100644
index 000000000..a5a228716
--- /dev/null
+++ b/lib/puppet/parser/ast/hostclass.rb
@@ -0,0 +1,82 @@
+class Puppet::Parser::AST
+ # 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
+
+end
diff --git a/lib/puppet/parser/ast/leaf.rb b/lib/puppet/parser/ast/leaf.rb
new file mode 100644
index 000000000..53cefdeb3
--- /dev/null
+++ b/lib/puppet/parser/ast/leaf.rb
@@ -0,0 +1,89 @@
+class Puppet::Parser::AST
+ # 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
+
+end
diff --git a/lib/puppet/parser/ast/node.rb b/lib/puppet/parser/ast/node.rb
new file mode 100644
index 000000000..18d7f8aa7
--- /dev/null
+++ b/lib/puppet/parser/ast/node.rb
@@ -0,0 +1,102 @@
+class Puppet::Parser::AST
+ # 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
diff --git a/lib/puppet/parser/ast/nodedef.rb b/lib/puppet/parser/ast/nodedef.rb
new file mode 100644
index 000000000..1cd0f683e
--- /dev/null
+++ b/lib/puppet/parser/ast/nodedef.rb
@@ -0,0 +1,68 @@
+class Puppet::Parser::AST
+ # 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
+
+end
diff --git a/lib/puppet/parser/ast/objectdef.rb b/lib/puppet/parser/ast/objectdef.rb
new file mode 100644
index 000000000..05be941ac
--- /dev/null
+++ b/lib/puppet/parser/ast/objectdef.rb
@@ -0,0 +1,331 @@
+class Puppet::Parser::AST
+ # 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
+
+end
diff --git a/lib/puppet/parser/ast/objectparam.rb b/lib/puppet/parser/ast/objectparam.rb
new file mode 100644
index 000000000..41cd050ef
--- /dev/null
+++ b/lib/puppet/parser/ast/objectparam.rb
@@ -0,0 +1,30 @@
+class Puppet::Parser::AST
+ # 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
+
+end
diff --git a/lib/puppet/parser/ast/objectref.rb b/lib/puppet/parser/ast/objectref.rb
new file mode 100644
index 000000000..e9561d65d
--- /dev/null
+++ b/lib/puppet/parser/ast/objectref.rb
@@ -0,0 +1,64 @@
+class Puppet::Parser::AST
+ # 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
+
+end
diff --git a/lib/puppet/parser/ast/selector.rb b/lib/puppet/parser/ast/selector.rb
new file mode 100644
index 000000000..51d880ed9
--- /dev/null
+++ b/lib/puppet/parser/ast/selector.rb
@@ -0,0 +1,60 @@
+class Puppet::Parser::AST
+ # 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
+
+end
diff --git a/lib/puppet/parser/ast/typedefaults.rb b/lib/puppet/parser/ast/typedefaults.rb
new file mode 100644
index 000000000..17ef94589
--- /dev/null
+++ b/lib/puppet/parser/ast/typedefaults.rb
@@ -0,0 +1,45 @@
+class Puppet::Parser::AST
+ # 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
+
+end
diff --git a/lib/puppet/parser/ast/vardef.rb b/lib/puppet/parser/ast/vardef.rb
new file mode 100644
index 000000000..6ada75589
--- /dev/null
+++ b/lib/puppet/parser/ast/vardef.rb
@@ -0,0 +1,44 @@
+class Puppet::Parser::AST
+ # 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
+
+end