diff options
author | luke <luke@980ebf18-57e1-0310-9a29-db15c13687c0> | 2006-05-09 05:50:34 +0000 |
---|---|---|
committer | luke <luke@980ebf18-57e1-0310-9a29-db15c13687c0> | 2006-05-09 05:50:34 +0000 |
commit | 513b87a86e9b77bb9f1b011aa55222ce9cfb3a8d (patch) | |
tree | eee376e11f0d3ec84972548af2ab804c7c062bfe /lib/puppet/parser | |
parent | fe16f83a1b56f5d8644ee08585cc3086d4acc2a0 (diff) | |
download | puppet-513b87a86e9b77bb9f1b011aa55222ce9cfb3a8d.tar.gz puppet-513b87a86e9b77bb9f1b011aa55222ce9cfb3a8d.tar.xz puppet-513b87a86e9b77bb9f1b011aa55222ce9cfb3a8d.zip |
Preliminary commit of the first phase of the parser redesign. The biggest difference is that overrides should now work for definitions (although i do not yet have a test case -- i will add one on the next commit). The way this is implemented is by having scopes translate themselves at eval time, but in two phases -- the first phase does the overrides, and the second phase does the evaluation of definitions and classes.
git-svn-id: https://reductivelabs.com/svn/puppet/trunk@1180 980ebf18-57e1-0310-9a29-db15c13687c0
Diffstat (limited to 'lib/puppet/parser')
-rw-r--r-- | lib/puppet/parser/ast/component.rb | 15 | ||||
-rw-r--r-- | lib/puppet/parser/ast/hostclass.rb | 47 | ||||
-rw-r--r-- | lib/puppet/parser/functions.rb | 8 | ||||
-rw-r--r-- | lib/puppet/parser/scope.rb | 287 |
4 files changed, 217 insertions, 140 deletions
diff --git a/lib/puppet/parser/ast/component.rb b/lib/puppet/parser/ast/component.rb index 00677e1ea..50e8df5a8 100644 --- a/lib/puppet/parser/ast/component.rb +++ b/lib/puppet/parser/ast/component.rb @@ -14,12 +14,12 @@ class Puppet::Parser::AST #def evaluate(scope,hash,objtype,objname) def evaluate(hash) - scope = hash[:scope] + origscope = hash[:scope] objtype = hash[:type] objname = hash[:name] arguments = hash[:arguments] || {} - scope = scope.newscope( + scope = origscope.newscope( :type => @type, :name => objname, :keyword => self.keyword @@ -95,9 +95,14 @@ class Puppet::Parser::AST # Now just evaluate the code with our new bindings. self.code.safeevaluate(:scope => scope) - # We return the scope, so that our children can make their scopes - # under ours. This allows them to find our definitions. - return scope + # If we're being evaluated as a parent class, we want to return the + # scope, so it can be overridden and such, but if not, we want to + # return a TransBucket of our objects. + if hash.include?(:asparent) + return scope + else + return scope.to_trans + end end # Check whether a given argument is valid. Searches up through diff --git a/lib/puppet/parser/ast/hostclass.rb b/lib/puppet/parser/ast/hostclass.rb index 8290f4ef0..a69d185e0 100644 --- a/lib/puppet/parser/ast/hostclass.rb +++ b/lib/puppet/parser/ast/hostclass.rb @@ -11,7 +11,7 @@ class Puppet::Parser::AST scope = hash[:scope] objtype = hash[:type] objname = hash[:name] - hash = hash[:arguments] + args = hash[:arguments] # Verify that we haven't already been evaluated # FIXME The second subclass won't evaluate the parent class # code at all, and any overrides will throw an error. @@ -26,30 +26,51 @@ class Puppet::Parser::AST # Default to creating a new context newcontext = true + transscope = nil if parentscope = self.evalparent( - :scope => scope, :arguments => hash, :name => objname + :scope => scope, :arguments => args, :name => 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. + if parentscope.is_a? Puppet::TransBucket + raise Puppet::DevError, "Got a bucket instead of a scope" + end + scope = parentscope + transscope = parentscope # But don't create a new context if our parent created one newcontext = false end - # just use the Component evaluate method, but change the type - # to our own type - #retval = super(scope,hash,@name,objname) - retval = super( + # Just use the Component evaluate method, but change the type + # to our own type. + result = super( :scope => scope, - :arguments => hash, + :arguments => args, :type => @type, - :name => objname, - :newcontext => newcontext + :name => objname, # might be nil + :newcontext => newcontext, + :asparent => hash[:asparent] # might be nil ) - return retval + + # This is important but painfully difficult. If we're the top-level + # class, that is, we have no parent classes, then the transscope + # is our own scope, but if there are parent classes, then the topmost + # parent's scope is the transscope, since it contains its code and + # all of the subclass's code. + transscope ||= result + + if hash[:asparent] + # If we're a parent class, then return the scope object itself. + return result + else + # But if we're the final subclass, translate the whole scope tree + # into TransObjects and TransBuckets. + return transscope.to_trans + end end # Evaluate our parent class. Parent classes are evaluated in the @@ -97,11 +118,13 @@ class Puppet::Parser::AST raise error end # We don't need to pass the type, because the parent will just - # use its own type + # use its own type. Specify that it's being evaluated as a parent, + # so that it returns the scope, not a transbucket. return parentobj.safeevaluate( :scope => scope, :arguments => args, - :name => name + :name => name, + :asparent => true ) else return false diff --git a/lib/puppet/parser/functions.rb b/lib/puppet/parser/functions.rb index 63201924b..b60d94b0a 100644 --- a/lib/puppet/parser/functions.rb +++ b/lib/puppet/parser/functions.rb @@ -27,7 +27,6 @@ module Functions fname = "function_" + name.to_s Puppet::Parser::Scope.send(:define_method, fname, &block) - #FCollection.send(:module_function,name) # Someday we'll support specifying an arity, but for now, nope #@functions[name] = {:arity => arity, :type => ftype} @@ -63,10 +62,11 @@ module Functions newfunction(:include) do |vals| vals.each do |val| if objecttype = lookuptype(val) - # It's a defined type - objecttype.safeevaluate( + # It's a defined type, so set it into the scope so it can + # be evaluated. + setobject( :type => val, - :scope => self + :arguments => {} ) else raise Puppet::ParseError, "Unknown class %s" % val diff --git a/lib/puppet/parser/scope.rb b/lib/puppet/parser/scope.rb index 394c46033..0fb3510b0 100644 --- a/lib/puppet/parser/scope.rb +++ b/lib/puppet/parser/scope.rb @@ -179,7 +179,7 @@ module Puppet::Parser # Yield each child scope in turn def each - @children.reject { |child| + @children.each { |child| yield child } end @@ -189,11 +189,9 @@ module Puppet::Parser return unless classes classes.each do |klass| if code = lookuptype(klass) - code.safeevaluate( - :scope => self, - :facts => {}, - :type => klass - ) + # Just reuse the 'include' function, since that's the equivalent + # of what we're doing here. + function_include(klass) end end end @@ -201,7 +199,6 @@ module Puppet::Parser # Evaluate a specific node's code. This method will normally be called # on the top-level scope, but it actually evaluates the node at the # appropriate scope. - #def evalnode(names, facts, classes = nil, parent = nil) def evalnode(hash) objects = hash[:ast] names = hash[:names] or @@ -240,17 +237,19 @@ module Puppet::Parser scope.evalclasses(classes) end - # Evaluate normally, with no node definitions. This is a bit of a - # silly method, in that it just calls evaluate on the passed-in - # objects, and then calls to_trans on itself. It just conceals - # a paltry amount of info from whomever's using the scope object. + # The top-level evaluate, used to evaluate a whole AST tree. This is + # a strange method, in that it turns around and calls evaluate() on its + # :ast argument. def evaluate(hash) objects = hash[:ast] facts = hash[:facts] || {} + @@done = [] + unless objects raise Puppet::DevError, "Evaluation requires an AST tree" end + # Set all of our facts in the top-level scope. facts.each { |var, value| self.setvar(var, value) @@ -258,9 +257,10 @@ module Puppet::Parser # Evaluate all of our configuration. This does not evaluate any # node definitions. - objects.safeevaluate(:scope => self) + result = objects.safeevaluate(:scope => self) - # If they've provided a name or a parent, we assume they're looking for nodes. + # If they've provided a name or a parent, we assume they're looking + # for nodes. if hash.include? :parentnode # Specifying a parent node takes precedence, because it is assumed # that this node was found in a remote repository like ldap. @@ -277,15 +277,14 @@ module Puppet::Parser # a cfengine module end - objects = self.to_trans - objects.top = true + bucket = self.to_trans # Add our class list unless self.classlist.empty? - objects.classes = self.classlist + bucket.classes = self.classlist end - return objects + return bucket end # Pull in all of the appropriate classes and evaluate them. It'd @@ -306,8 +305,8 @@ module Puppet::Parser #Puppet.notice "hash is %s" % # hash.inspect - Puppet.notice "Classes are %s, parent is %s" % - [classes.inspect, parent.inspect] + #Puppet.notice "Classes are %s, parent is %s" % + # [classes.inspect, parent.inspect] if parent arghash[:parentclass] = parent @@ -325,52 +324,6 @@ module Puppet::Parser scope.evalclasses(classes) end - # Take all of our objects and evaluate them. -# def finish -# self.info "finishing" -# @objectlist.each { |object| -# if object.is_a? ScopeObj -# self.info "finishing %s" % object.name -# if obj = finishobject(object) -# @children << obj -# end -# end -# } -# -# @finished = true -# -# self.info "finished" -# end -# -# # If the object is defined in an upper scope, then add our -# # params to that upper scope; else, create a transobject -# # or evaluate the definition. -# def finishobject(object) -# type = object.type -# name = object.name -# -# # It should be a defined type. -# definedtype = lookuptype(type) -# -# unless definedtype -# error = Puppet::ParseError.new("No such type %s" % type) -# error.line = object.line -# error.file = object.file -# raise error -# end -# -# return definedtype.safeevaluate( -# :scope => self, -# :arguments => object, -# :type => type, -# :name => name -# ) -# end -# -# def finished? -# @finished -# end - # Initialize our new scope. Defaults to having no parent and to # being declarative. def initialize(hash = {}) @@ -395,7 +348,10 @@ module Puppet::Parser end self.istop(hash[:declarative]) else + # This is here, rather than in newchild(), so that all + # of the later variable initialization works. @parent.child = self + @level = @parent.level + 1 @interp = @parent.interp @topscope = @parent.topscope @@ -425,9 +381,6 @@ module Puppet::Parser typehash[typekey] = Hash.new(nil) } - # The list of simpler hash objects. - @objectlist = [] - # This is just for collecting statements locally, so we can # verify that there is no overlap within this specific scope @localobjectable = Hash.new { |typehash,typekey| @@ -591,25 +544,14 @@ module Puppet::Parser obj = nil - # If it's a builtin type, then use a transobject, else use - # a ScopeObj, which will get replaced later. - if self.builtintype?(hash[:type]) - obj = Puppet::TransObject.new(hash[:name], hash[:type]) + obj = Puppet::TransObject.new(hash[:name], hash[:type]) - @children << obj - else - obj = ScopeObj.new(nil) - obj.name = hash[:name] - obj.type = hash[:type] - end + @children << obj @objectable[hash[:type]][hash[:name]] = obj @definedtable[hash[:type]][hash[:name]] = obj - # Keep them in order, just for kicks - @objectlist << obj - return obj end @@ -737,48 +679,43 @@ module Puppet::Parser } end - if objecttype = lookuptype(type) - # It's a defined type - objecttype.safeevaluate( - :name => name, - :type => type, - :arguments => params, - :scope => self - ) - else - # First look for it in a parent scope - obj = lookupobject(:name => name, :type => type) - - unless obj and obj != :undefined - unless obj = @objectable[type][name] - obj = self.newobject( - :type => type, - :name => name, - :line => line, - :file => file - ) + # First look for it in a parent scope + obj = lookupobject(:name => name, :type => type) - # only set these if we've created the object, - # which is the most common case - # FIXME we eventually need to store the file - # and line with each param, not the object - # itself. - obj.file = file - obj.line = line - end - end + unless obj and obj != :undefined + unless obj = @objectable[type][name] + obj = self.newobject( + :type => type, + :name => name, + :line => line, + :file => file + ) - # Now add our parameters. This has the function of overriding - # existing values, which might have been defined in a higher - # scope. - params.each { |var,value| - # Add it to our found object - obj[var] = value - } + # only set these if we've created the object, + # which is the most common case + # FIXME we eventually need to store the file + # and line with each param, not the object + # itself. + obj.file = file + obj.line = line + end end - @localobjectable[type][name] ||= {} + # Now add our parameters. This has the function of overriding + # existing values, which might have been defined in a higher + # scope. + params.each { |var,value| + # Add it to our found object + obj[var] = value + } + # This is only used for override verification -- the local object + # table does not have transobjects or whatever in it, it just has + # simple hashes. This is necessary because setobject can modify + # our object table or a parent class's object table, and we + # still need to make sure param settings cannot be duplicated + # within our scope. + @localobjectable[type][name] ||= {} params.each { |var,value| # And add it to the local table; mmm, hack @localobjectable[type][name][var] = value @@ -867,9 +804,121 @@ module Puppet::Parser end end - # Convert our scope to a list of Transportable objects. + # Convert our scope to a TransBucket. Everything in our @localobjecttable + # gets converted to either an evaluated definition, or a TransObject def to_trans + results = [] + + @children.dup.each do |child| + + if @@done.include?(child) + raise Puppet::DevError, "Already translated %s" % child.object_id + else + info "Translating %s" % child.object_id + @@done << child + end + #warning "Working on %s of type %s with id %s" % + # [child.type, child.class, child.object_id] + + # If it's a scope, then it can only be a subclass's scope, so + # convert it to a transbucket and store it in our results list + result = nil + case child + when Scope + #raise Puppet::DevError, "got a child scope" + result = child.to_trans + when Puppet::TransObject + # These objects can map to defined types or builtin types. + # Builtin types should be passed out as they are, but defined + # types need to be evaluated. We have to wait until this + # point so that subclass overrides can happen. + # Wait until the last minute to set tags, although this + # probably should not matter + child.tags = self.tags + + # Add any defaults. + self.adddefaults(child) + + # Then make sure this child's tags are stored in the + # central table. This should maybe be in the evaluate + # methods, but, eh. + @topscope.addtags(child) + + # Now that all that is done, check to see what kind of object + # it is. + if objecttype = lookuptype(child.type) + # It's a defined type, so evaluate it + result = objecttype.safeevaluate( + :name => child.name, + :type => child.type, + :arguments => child.to_hash, + :scope => self + ) + else + # It's a builtin type, so just chunk it on in + result = child + end + else + raise Puppet::DevError, + "Puppet::Parse::Scope cannot handle objects of type %s" % + child.class + end + + # Skip empty builtin types or defined types + if result and ! result.empty? + results << result + end + end + # Get rid of any nil objects. + results.reject! { |child| + child.nil? + } + + # If we have a name and type, then make a TransBucket, which + # becomes a component. + # Else, just stack all of the objects into the current bucket. + if @type + bucket = Puppet::TransBucket.new + + if defined? @name and @name + bucket.name = @name + end + # it'd be nice not to have to do this... + results.each { |result| + #Puppet.warning "Result type is %s" % result.class + bucket.push(result) + } + bucket.type = @type + + if defined? @keyword + bucket.keyword = @keyword + end + #Puppet.debug( + # "TransBucket with name %s and type %s in scope %s" % + # [@name,@type,self.object_id] + #) + + # now find metaparams + @symtable.each { |var,value| + if Puppet::Type.metaparam?(var.intern) + #Puppet.debug("Adding metaparam %s" % var) + bucket.param(var,value) + else + #Puppet.debug("%s is not a metaparam" % var) + end + } + #Puppet.debug "Returning bucket %s from scope %s" % + # [bucket.name,self.object_id] + return bucket + else + Puppet.debug "typeless scope; just returning a list" + return results + end + end + + # Convert our scope to a list of Transportable objects. + def oldto_trans results = [] # Iterate across our child scopes and call to_trans on them |