diff options
| author | luke <luke@980ebf18-57e1-0310-9a29-db15c13687c0> | 2006-01-13 23:16:26 +0000 |
|---|---|---|
| committer | luke <luke@980ebf18-57e1-0310-9a29-db15c13687c0> | 2006-01-13 23:16:26 +0000 |
| commit | 87b3bb111f2ea68cbeb875f07e826e4f75ea9eea (patch) | |
| tree | c03530a415b2f90be6b4c6d5b594f0b8c78a3c0b /lib/puppet/parser | |
| parent | 1d4638a03df6821c16c00db3084f89889f19ac33 (diff) | |
| download | puppet-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')
| -rw-r--r-- | lib/puppet/parser/ast.rb | 1463 | ||||
| -rw-r--r-- | lib/puppet/parser/ast/astarray.rb | 85 | ||||
| -rw-r--r-- | lib/puppet/parser/ast/branch.rb | 47 | ||||
| -rw-r--r-- | lib/puppet/parser/ast/caseopt.rb | 66 | ||||
| -rw-r--r-- | lib/puppet/parser/ast/casestatement.rb | 78 | ||||
| -rw-r--r-- | lib/puppet/parser/ast/classdef.rb | 77 | ||||
| -rw-r--r-- | lib/puppet/parser/ast/compdef.rb | 108 | ||||
| -rw-r--r-- | lib/puppet/parser/ast/component.rb | 99 | ||||
| -rw-r--r-- | lib/puppet/parser/ast/hostclass.rb | 82 | ||||
| -rw-r--r-- | lib/puppet/parser/ast/leaf.rb | 89 | ||||
| -rw-r--r-- | lib/puppet/parser/ast/node.rb | 102 | ||||
| -rw-r--r-- | lib/puppet/parser/ast/nodedef.rb | 68 | ||||
| -rw-r--r-- | lib/puppet/parser/ast/objectdef.rb | 331 | ||||
| -rw-r--r-- | lib/puppet/parser/ast/objectparam.rb | 30 | ||||
| -rw-r--r-- | lib/puppet/parser/ast/objectref.rb | 64 | ||||
| -rw-r--r-- | lib/puppet/parser/ast/selector.rb | 60 | ||||
| -rw-r--r-- | lib/puppet/parser/ast/typedefaults.rb | 45 | ||||
| -rw-r--r-- | lib/puppet/parser/ast/vardef.rb | 44 |
18 files changed, 1496 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$ 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 |
