diff options
author | luke <luke@980ebf18-57e1-0310-9a29-db15c13687c0> | 2007-08-04 20:00:19 +0000 |
---|---|---|
committer | luke <luke@980ebf18-57e1-0310-9a29-db15c13687c0> | 2007-08-04 20:00:19 +0000 |
commit | a8bf96af7649eedb294b0525c877cd8ba565d6e2 (patch) | |
tree | 81b8d5c1e963c3469807e0af9a52830cdbf6c6e3 /lib | |
parent | 40e4d6fa02e801a26c2880840befa32718e55452 (diff) | |
download | puppet-a8bf96af7649eedb294b0525c877cd8ba565d6e2.tar.gz puppet-a8bf96af7649eedb294b0525c877cd8ba565d6e2.tar.xz puppet-a8bf96af7649eedb294b0525c877cd8ba565d6e2.zip |
Adding a file that should have been in a commit from yesterda
git-svn-id: https://reductivelabs.com/svn/puppet/trunk@2744 980ebf18-57e1-0310-9a29-db15c13687c0
Diffstat (limited to 'lib')
-rw-r--r-- | lib/puppet/parser/parser_support.rb | 447 |
1 files changed, 447 insertions, 0 deletions
diff --git a/lib/puppet/parser/parser_support.rb b/lib/puppet/parser/parser_support.rb new file mode 100644 index 000000000..728f75a69 --- /dev/null +++ b/lib/puppet/parser/parser_support.rb @@ -0,0 +1,447 @@ +class Puppet::Parser::Parser + require 'puppet/parser/functions' + + ASTSet = Struct.new(:classes, :definitions, :nodes) + + # Define an accessor method for each table. We hide the existence of + # the struct. + [:classes, :definitions, :nodes].each do |name| + define_method(name) do + @astset.send(name) + end + end + + AST = Puppet::Parser::AST + + attr_reader :file, :interp + attr_accessor :files + + + # Add context to a message; useful for error messages and such. + def addcontext(message, obj = nil) + obj ||= @lexer + + message += " on line %s" % obj.line + if file = obj.file + message += " in file %s" % file + end + + return message + end + + # Create an AST array out of all of the args + def aryfy(*args) + if args[0].instance_of?(AST::ASTArray) + result = args.shift + args.each { |arg| + result.push arg + } + else + result = ast AST::ASTArray, :children => args + end + + return result + end + + # Create an AST object, and automatically add the file and line information if + # available. + def ast(klass, hash = nil) + hash ||= {} + unless hash.include?(:line) + hash[:line] = @lexer.line + end + + unless hash.include?(:file) + if file = @lexer.file + hash[:file] = file + end + end + + return klass.new(hash) + end + + # The fully qualifed name, with the full namespace. + def classname(name) + [@lexer.namespace, name].join("::").sub(/^::/, '') + end + + def clear + initvars + end + + # Raise a Parse error. + def error(message) + if brace = @lexer.expected + message += "; expected '%s'" + end + except = Puppet::ParseError.new(message) + except.line = @lexer.line + if @lexer.file + except.file = @lexer.file + end + + raise except + end + + def file=(file) + unless FileTest.exists?(file) + unless file =~ /\.pp$/ + file = file + ".pp" + end + unless FileTest.exists?(file) + raise Puppet::Error, "Could not find file %s" % file + end + end + if @files.detect { |f| f.file == file } + raise Puppet::AlreadyImportedError.new("Import loop detected") + else + @files << Puppet::Util::LoadedFile.new(file) + @lexer.file = file + end + end + + # Find a class definition, relative to the current namespace. + def findclass(namespace, name) + fqfind namespace, name, classes + end + + # Find a component definition, relative to the current namespace. + def finddefine(namespace, name) + fqfind namespace, name, definitions + end + + # This is only used when nodes are looking up the code for their + # parent nodes. + def findnode(name) + fqfind "", name, nodes + end + + # The recursive method used to actually look these objects up. + def fqfind(namespace, name, table) + namespace = namespace.downcase + name = name.downcase + if name =~ /^::/ or namespace == "" + classname = name.sub(/^::/, '') + unless table[classname] + self.load(classname) + end + return table[classname] + end + ary = namespace.split("::") + + while ary.length > 0 + newname = (ary + [name]).join("::").sub(/^::/, '') + if obj = table[newname] or (self.load(newname) and obj = table[newname]) + return obj + end + + # Delete the second to last object, which reduces our namespace by one. + ary.pop + end + + # If we've gotten to this point without finding it, see if the name + # exists at the top namespace + if obj = table[name] or (self.load(name) and obj = table[name]) + return obj + end + + return nil + end + + # Import our files. + def import(file) + if Puppet[:ignoreimport] + return AST::ASTArray.new(:children => []) + end + # use a path relative to the file doing the importing + if @lexer.file + dir = @lexer.file.sub(%r{[^/]+$},'').sub(/\/$/, '') + else + dir = "." + end + if dir == "" + dir = "." + end + result = ast AST::ASTArray + + # We can't interpolate at this point since we don't have any + # scopes set up. Warn the user if they use a variable reference + pat = file + if pat.index("$") + Puppet.warning( + "The import of #{pat} contains a variable reference;" + + " variables are not interpolated for imports " + + "in file #{@lexer.file} at line #{@lexer.line}" + ) + end + files = Puppet::Module::find_manifests(pat, dir) + if files.size == 0 + raise Puppet::ImportError.new("No file(s) found for import " + + "of '#{pat}'") + end + + files.collect { |file| + parser = Puppet::Parser::Parser.new(@astset) + parser.files = self.files + Puppet.debug("importing '%s'" % file) + + unless file =~ /^#{File::SEPARATOR}/ + file = File.join(dir, file) + end + begin + parser.file = file + rescue Puppet::AlreadyImportedError + # This file has already been imported to just move on + next + end + + # This will normally add code to the 'main' class. + parser.parse + } + end + + def initialize(astset = nil) + initvars() + if astset + @astset = astset + end + end + + # Initialize or reset all of our variables. + def initvars + @lexer = Puppet::Parser::Lexer.new() + @files = [] + @loaded = [] + + # This is where we store our classes and definitions and nodes. + # Clear each hash, just to help the GC a bit. + if defined?(@astset) + [:classes, :definitions, :nodes].each do |name| + @astset.send(name).clear + end + end + @astset = ASTSet.new({}, {}, {}) + end + + # Try to load a class, since we could not find it. + def load(classname) + return false if classname == "" + filename = classname.gsub("::", File::SEPARATOR) + + loaded = false + # First try to load the top-level module + mod = filename.scan(/^[\w-]+/).shift + unless @loaded.include?(mod) + @loaded << mod + begin + import(mod) + Puppet.info "Autoloaded module %s" % mod + loaded = true + rescue Puppet::ImportError => detail + # We couldn't load the module + end + end + + unless filename == mod and ! @loaded.include?(mod) + @loaded << mod + # Then the individual file + begin + import(filename) + Puppet.info "Autoloaded file %s from module %s" % [filename, mod] + loaded = true + rescue Puppet::ImportError => detail + # We couldn't load the file + end + end + return loaded + end + + # Split an fq name into a namespace and name + def namesplit(fullname) + ary = fullname.split("::") + n = ary.pop || "" + ns = ary.join("::") + return ns, n + end + + # Create a new class, or merge with an existing class. + def newclass(name, options = {}) + name = name.downcase + + if definitions.include?(name) + raise Puppet::ParseError, "Cannot redefine class %s as a definition" % name + end + code = options[:code] + parent = options[:parent] + + # If the class is already defined, then add code to it. + if other = @astset.classes[name] + # Make sure the parents match + if parent and other.parentclass and (parent != other.parentclass) + error("Class %s is already defined at %s:%s; cannot redefine" % [name, other.file, other.line]) + end + + # This might be dangerous... + if parent and ! other.parentclass + other.parentclass = parent + end + + # This might just be an empty, stub class. + if code + tmp = name + if tmp == "" + tmp = "main" + end + + Puppet.debug addcontext("Adding code to %s" % tmp) + # Else, add our code to it. + if other.code and code + other.code.children += code.children + else + other.code ||= code + end + end + else + # Define it anew. + # Note we're doing something somewhat weird here -- we're setting + # the class's namespace to its fully qualified name. This means + # anything inside that class starts looking in that namespace first. + args = {:namespace => name, :classname => name, :parser => self} + args[:code] = code if code + args[:parentclass] = parent if parent + @astset.classes[name] = ast AST::HostClass, args + end + + return @astset.classes[name] + end + + # Create a new definition. + def newdefine(name, options = {}) + name = name.downcase + if @astset.classes.include?(name) + raise Puppet::ParseError, "Cannot redefine class %s as a definition" % + name + end + # Make sure our definition doesn't already exist + if other = @astset.definitions[name] + error("%s is already defined at %s:%s; cannot redefine" % [name, other.file, other.line]) + end + + ns, whatever = namesplit(name) + args = { + :namespace => ns, + :arguments => options[:arguments], + :code => options[:code], + :parser => self, + :classname => name + } + + [:code, :arguments].each do |param| + args[param] = options[param] if options[param] + end + + @astset.definitions[name] = ast AST::Component, args + end + + # Create a new node. Nodes are special, because they're stored in a global + # table, not according to namespaces. + def newnode(names, options = {}) + names = [names] unless names.instance_of?(Array) + names.collect do |name| + name = name.to_s.downcase + if other = @astset.nodes[name] + error("Node %s is already defined at %s:%s; cannot redefine" % [other.name, other.file, other.line]) + end + name = name.to_s if name.is_a?(Symbol) + args = { + :name => name, + :parser => self + } + if options[:code] + args[:code] = options[:code] + end + if options[:parent] + args[:parentclass] = options[:parent] + end + @astset.nodes[name] = ast(AST::Node, args) + @astset.nodes[name].classname = name + @astset.nodes[name] + end + end + + def on_error(token,value,stack) + #on '%s' at '%s' in\n'%s'" % [token,value,stack] + #error = "line %s: parse error after '%s'" % + # [@lexer.line,@lexer.last] + error = "Syntax error at '%s'" % [value] + + if brace = @lexer.expected + error += "; expected '%s'" % brace + end + + except = Puppet::ParseError.new(error) + except.line = @lexer.line + if @lexer.file + except.file = @lexer.file + end + + raise except + end + + # how should I do error handling here? + def parse(string = nil) + if string + self.string = string + end + begin + main = yyparse(@lexer,:scan) + rescue Racc::ParseError => except + error = Puppet::ParseError.new(except) + error.line = @lexer.line + error.file = @lexer.file + error.set_backtrace except.backtrace + raise error + rescue Puppet::ParseError => except + except.line ||= @lexer.line + except.file ||= @lexer.file + raise except + rescue Puppet::Error => except + # and this is a framework error + except.line ||= @lexer.line + except.file ||= @lexer.file + raise except + rescue Puppet::DevError => except + except.line ||= @lexer.line + except.file ||= @lexer.file + raise except + rescue => except + error = Puppet::DevError.new(except.message) + error.line = @lexer.line + error.file = @lexer.file + error.set_backtrace except.backtrace + raise error + end + if main + # Store the results as the top-level class. + newclass("", :code => main) + end + return @astset + ensure + @lexer.clear + end + + # See if any of the files have changed. + def reparse? + if file = @files.detect { |file| file.changed? } + return file.stamp + else + return false + end + end + + def string=(string) + @lexer.string = string + end +end + +# $Id$ |