diff options
| author | Luke Kanies <luke@madstop.com> | 2005-08-23 16:09:14 +0000 |
|---|---|---|
| committer | Luke Kanies <luke@madstop.com> | 2005-08-23 16:09:14 +0000 |
| commit | 6029ef7812765775306ff8394005c326e359d886 (patch) | |
| tree | 32cbe5ea68e0e9fbdc0935d0b41e58fdfcba9e3d /lib/puppet/parser | |
| parent | e87eb58ce8dc40ba8c66233bf17cea61094e7647 (diff) | |
| download | puppet-6029ef7812765775306ff8394005c326e359d886.tar.gz puppet-6029ef7812765775306ff8394005c326e359d886.tar.xz puppet-6029ef7812765775306ff8394005c326e359d886.zip | |
Moving all files into a consolidated trunk. All tests pass except the known-failing certificate test, but there appear to be some errors that are incorrectly not resulting in failurs. I will track those down ASAP.
git-svn-id: https://reductivelabs.com/svn/puppet/trunk@576 980ebf18-57e1-0310-9a29-db15c13687c0
Diffstat (limited to 'lib/puppet/parser')
| -rw-r--r-- | lib/puppet/parser/ast.rb | 1238 | ||||
| -rw-r--r-- | lib/puppet/parser/grammar.ra | 672 | ||||
| -rw-r--r-- | lib/puppet/parser/interpreter.rb | 135 | ||||
| -rw-r--r-- | lib/puppet/parser/lexer.rb | 225 | ||||
| -rw-r--r-- | lib/puppet/parser/makefile | 5 | ||||
| -rw-r--r-- | lib/puppet/parser/parser.rb | 1264 | ||||
| -rw-r--r-- | lib/puppet/parser/scope.rb | 427 |
7 files changed, 3966 insertions, 0 deletions
diff --git a/lib/puppet/parser/ast.rb b/lib/puppet/parser/ast.rb new file mode 100644 index 000000000..ff237f62e --- /dev/null +++ b/lib/puppet/parser/ast.rb @@ -0,0 +1,1238 @@ +#/usr/bin/ruby + +# $Id$ +# vim: syntax=ruby + +# the AST tree + +# the parent class for all of our syntactical objects +module Puppet + module Parser + class ASTError < RuntimeError; end + #--------------------------------------------------------------- + class AST + attr_accessor :line, :file, :parent + + @@pink = "[0;31m" + @@green = "[0;32m" + @@yellow = "[0;33m" + @@slate = "[0;34m" + @@reset = "[0m" + + @@indent = " " * 4 + @@indline = @@pink + ("-" * 4) + @@reset + @@midline = @@slate + ("-" * 4) + @@reset + + @@settypes = Hash.new { |hash,key| + hash[key] = Hash.new(0) + } + + def AST.indention + return @@indent * @@indention + end + + def AST.midline + return @@midline + end + + def evaluate(scope) + #Puppet.debug("Evaluating ast %s" % @name) + value = self.collect { |obj| + obj.evaluate(scope) + }.reject { |obj| + obj.nil? + } + end + + def typewrap(string) + #return self.class.to_s.sub(/.+::/,'') + + #"(" + @@green + string.to_s + @@reset + ")" + return @@green + string.to_s + @@reset + + "(" + self.class.to_s.sub(/.+::/,'') + ")" + end + + def initialize(args) + # this has to wait until all of the objects are defined + unless defined? @@klassorder + @@klassorder = [ + AST::VarDef, AST::TypeDefaults, + AST::ObjectDef, AST::StatementArray + ] + end + + args.each { |param,value| + method = param.to_s + "=" + unless self.respond_to?(method) + error = Puppet::ParseError.new( + "Invalid parameter %s to object class %s" % + [method,self.class.to_s] + ) + error.line = self.line + error.file = self.file + error.stack = caller + raise error + end + + begin + #Puppet.debug("sending %s to %s" % [method, self.class]) + self.send(method,value) + rescue => detail + error = Puppet::DevError.new( + "Could not set parameter %s on class %s: %s" % + [method,self.class.to_s,detail] + ) + error.stack = caller + raise error + end + } + end + + #--------------------------------------------------------------- + # this differentiation is used by the interpreter + # these objects have children + class Branch < AST + include Enumerable + attr_accessor :pin, :children + + def each + @children.each { |child| + yield child + } + end + + def evaluate(scope) + #Puppet.debug("Evaluating branch") +# rets = nil +# if scope.declarative +# # if we're operating declaratively, then we want to get +# # all of our 'setting' operations done first +# rets = @children.sort { |a,b| +# [a,b].each { |i| +# unless @@klassorder.include?(i.class) +# raise "Order not defined for %s" % i.class +# end +# } +# @@klassorder.index(a.class) <=> @@klassorder.index(b.class) +# }.collect { |item| +# Puppet.debug "Decl evaluating %s" % item.class +# item.evaluate(scope) +# } +# else +# rets = @children.collect { |item| +# item.evaluate(scope) +# } +# end + self.collect { |item| + #Puppet.debug "Evaluating %s" % item.class + item.evaluate(scope) + }.reject { |obj| + obj.nil + } + end + + def initialize(arghash) + super(arghash) + + unless defined? @children + @children = [] + end + + #puts "children is '%s'" % [@children] + + self.each { |child| + if child.class == Array + error = Puppet::DevError.new( + "child for %s(%s) is array" % [self.class,self.parent] + ) + error.stack = caller + raise error + end + unless child.nil? + child.parent = self + end + } + end + + def tree(indent = 0) + return ((@@indline * indent) + + self.typewrap(self.pin)) + "\n" + self.collect { |child| + child.tree(indent + 1) + }.join("\n") + end + end + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + class ASTArray < AST::Branch + include Enumerable + + def [](index) + @children[index] + end + + def evaluate(scope) + rets = nil + if scope.declarative + test = [ + AST::VarDef, AST::TypeDefaults + ] + + # if we're operating declaratively, then we want to get + # all of our 'setting' operations done first + 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.evaluate(scope) + } + else + rets = @children.collect { |item| + item.evaluate(scope) + } + end + rets = rets.reject { |obj| obj.nil? } + end + + def initialize(hash) + super(hash) + + @children.each { |child| + unless child.is_a?(AST) + Puppet.err("child %s is not an ast" % child) + exit + end + } + return self + 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 + + def to_s + return "[" + @children.collect { |child| + child.to_s + }.join(", ") + "]" + end + + def tree(indent = 0) + #puts((AST.indent * indent) + self.pin) + self.collect { |child| + if child.class == Array + Puppet.debug "child is array for %s" % self.class + end + child.tree(indent) + }.join("\n" + (AST.midline * (indent+1)) + "\n") + end + end + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + class StatementArray < ASTArray + def evaluate(scope) + rets = nil + if scope.declarative + # if we're operating declaratively, then we want to get + # all of our 'setting' operations done first + rets = @children.sort { |a,b| + [a,b].each { |i| + unless @@klassorder.include?(i.class) + error = Puppet::DevError.new( + "Order not defined for %s" % i.class + ) + error.stack = caller + raise error + end + } + @@klassorder.index(a.class) <=> @@klassorder.index(b.class) + }.collect { |item| + Puppet.debug "Decl evaluating %s" % item.class + item.evaluate(scope) + }.reject { |obj| obj.nil? } + else + rets = @children.collect { |item| + item.evaluate(scope) + }.reject { |obj| obj.nil? } + end + + return rets + end + end + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + # and these ones don't + class Leaf < AST + attr_accessor :value, :type + + # this only works if @value has already been evaluated + # otherwise you get AST objects, which you don't likely want... + def evaluate(scope) + return @value + end + + def tree(indent = 0) + return ((@@indent * indent) + self.typewrap(self.value)) + end + + def to_s + return @value + end + end + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + class Boolean < AST::Leaf + 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 + + def evaluate(scope) + return @value + end + + def to_s + return @value + end + end + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + class String < AST::Leaf + def evaluate(scope) + return scope.strinterp(@value) + end + end + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + class Word < AST::Leaf; end + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + class Type < AST::Leaf; end + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + class Name < AST::Leaf; end + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + class Variable < Word + def evaluate(scope) + # look up the variable value in the symbol table + 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 + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + class ObjectDef < AST::Branch + attr_accessor :name, :type + attr_reader :params + + def []=(index,obj) + @params[index] = obj + end + + def [](index) + return @params[index] + end + + def each + #Puppet.debug("each called on %s" % self) + [@type,@name,@params].flatten.each { |param| + #Puppet.debug("yielding param %s" % param) + yield param + } + end + + def evaluate(scope) + hash = {} + + objtype = @type.evaluate(scope) + objnames = @name.evaluate(scope) + + # first, retrieve the defaults + defaults = scope.lookupdefaults(objtype) + defaults.each { |var,value| + Puppet.debug "Found default %s for %s" % + [var,objtype] + + hash[var] = value + } + + # then set all of the specified params + @params.each { |param| + ary = param.evaluate(scope) + hash[ary[0]] = ary[1] + } + + # it's easier to always use an array, even for only one name + unless objnames.is_a?(Array) + objnames = [objnames] + end + + 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 + + # 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 type is not defined in our scope, we assume + # that it's a type that the client will understand, so we + # just store it in our objectable + if object.nil? + begin + Puppet.debug("Setting object '%s' with arguments %s" % + [objname, 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.evaluate(scope,hash,objtype,objname) + end + }.reject { |obj| obj.nil? } + end + + def initialize(hash) + super + + Puppet.debug "%s id is %s" % [@name, object_id] + + # we don't have to evaluate because we require bare words + # for types + objtype = @type.value + + if Puppet[:typecheck] + builtin = false + begin + builtin = Puppet::Type.type(objtype) + rescue TypeError + # nothing; we've already set builtin to false + end + if builtin + # we're a builtin type + #Puppet.debug "%s is a builtin type" % objtype + if Puppet[:paramcheck] + @params.each { |param| + pname = param.param.value + next if pname == "name" # always allow these + unless builtin.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 + # FIXME this should use scoping rules to find the set type, + # not a global list + elsif @@settypes.include?(objtype) + # we've defined it locally + Puppet.debug "%s is a defined type" % objtype + hash = @@settypes[objtype] + @params.each { |param| + # FIXME we might need to do more here eventually... + if Puppet::Type.metaparam?(param.param.value.intern) + next + end + + pname = param.param.value + unless hash.include?(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 + } + else + # we don't know anything about it + error = Puppet::ParseError.new( + "Unknown type '%s'" % objtype + ) + error.line = self.line + error.file = self.file + error.stack = caller + raise error + end + end + end + + def params=(params) + if params.is_a?(AST::ASTArray) + @params = params + else + @params = AST::ASTArray.new( + :children => [params] + ) + end + end + + 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 + + def to_s + return "%s => { %s }" % [@name, + @params.collect { |param| + param.to_s + }.join("\n") + ] + end + end + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + class ObjectRef < AST::Branch + attr_accessor :name, :type + + def each + #Puppet.debug("each called on %s" % self) + [@type,@name].flatten.each { |param| + #Puppet.debug("yielding param %s" % param) + yield param + } + end + + def evaluate(scope) + objtype = @type.evaluate(scope) + objnames = @name.evaluate(scope) + + # it's easier to always use an array, even for only one name + unless objnames.is_a?(Array) + objnames = [objnames] + end + + 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?(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 + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + class ObjectParam < AST::Branch + attr_accessor :value, :param + + def each + [@param,@value].each { |child| yield child } + end + + def evaluate(scope) + return [@param.evaluate(scope),@value.evaluate(scope)] + 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 + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + class Test < AST::Branch + attr_accessor :lhs, :rhs + + # is our test true or false? + def evaluate(scope) + # retrieve our values and make them a touch easier to manage + lvalue = @lhs.evaluate(scope) + rvalue = @rhs.evaluate(scope) + + # FIXME this probably won't work except on strings right now... + retvalue = lvalue.send(@pin, rvalue) + + #Puppet.debug "test '%s' returned %s" % [self.to_s,retvalue] + return retvalue + end + + def tree(indent = 0) + return [ + @lhs.tree(indent + 1), + ((@@indline * indent) + self.typewrap(self.pin)), + @rhs.tree(indent + 1) + ].join("\n") + end + + def each + [@lhs,@rhs].each { |child| yield child } + end + + def to_s + return "%s %s %s" % [@lhs,@pin,@rhs] + end + end + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + class If < AST::Branch + attr_accessor :test, :statements, :else, :elsif + + # 'if' is a bit special, since we don't want to continue + # evaluating if a test turns up true + def evaluate(scope) + scope = scope.newscope + retvalue = nil + if @test.evaluate(scope) + Puppet.debug "%s is true" % @test + retvalue = @statements.evaluate(scope) + elsif defined? @elsif + Puppet.debug "%s is false" % @test + elsereturn = nil + @elsif.each { |elsetest| + if elsereturn = @elsif.evaluate(scope) + retvalue = elsereturn + end + } + elsif defined? @else + retvalue = @else.evaluate(scope) + else + Puppet.debug "None of the ifs are true" + end + return retvalue + end + + def tree(indent = 0) + rettree = [ + @test.tree(indent + 1), + ((@@indline * indent) + self.typewrap(self.pin)), + @statements.tree(indent + 1) + ] + if defined? @elsif + @elsif.each { |elsetest| + rettree.push(elsetest.tree(indent + 1)) + } + end + + if defined? @else + rettree.push(@else.tree(indent+1)) + end + + return rettree.flatten.join("\n") + end + + def each + list = [@test,@statements] + + if defined? @elsif + @elsif.each { |tmp| + list.push(tmp) + } + end + + if defined? @else + list.push(@else) + end + + list.each { |child| yield child } + end + end + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + class Selector < AST::Branch + attr_accessor :param, :value + + # okay, here's a decision point... + def evaluate(scope) + # retrieve our values and make them a touch easier to manage + hash = Hash[*(@value.evaluate(scope).flatten)] + + retvalue = nil + + paramvalue = @param.evaluate(scope) + + retvalue = hash.detect { |test,value| + # FIXME this will return variables named 'default'... + if paramvalue == test + break value + end + } + if retvalue.nil? + if hash.include?("default") + return hash["default"] + else + error = Puppet::ParseError.new( + "No value for selector param '%s'" % paramvalue + ) + error.line = self.line + error.file = self.file + error.stack = self.stack + raise error + end + end + + return retvalue + end + + def tree(indent = 0) + return [ + @param.tree(indent + 1), + ((@@indline * indent) + self.typewrap(self.pin)), + @value.tree(indent + 1) + ].join("\n") + end + + def each + [@param,@value].each { |child| yield child } + end + end + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + class VarDef < AST::Branch + attr_accessor :name, :value + + def evaluate(scope) + name = @name.evaluate(scope) + value = @value.evaluate(scope) + + Puppet.debug "setting %s to %s" % [name,value] + 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 + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + class TypeDefaults < AST::Branch + attr_accessor :type, :params + + def each + [@type,@params].each { |child| yield child } + end + + def evaluate(scope) + type = @type.evaluate(scope) + params = @params.evaluate(scope) + + #Puppet.info "Params are %s" % params.inspect + #Puppet.debug("evaluating '%s.%s' with values [%s]" % + # [type,name,values]) + # okay, now i need the interpreter's client object thing... + 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 + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + # these are analogous to defining new object types + class CompDef < AST::Branch + attr_accessor :name, :args, :code + + def each + [@name,@args,@code].each { |child| yield child } + end + + def evaluate(scope) + name = @name.evaluate(scope) + + args = @args.evaluate(scope) + + #Puppet.debug("defining '%s' with arguments [%s]" % + # [name,args]) + #p @args + #p args + # okay, now i need to evaluate all of the statements + # within a component and a new lexical scope... + + begin + scope.settype(name, + 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) + 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 + hash = @@settypes[@name.value] + if @args.is_a?(AST::ASTArray) + @args.each { |ary| + if ary.is_a?(AST::ASTArray) + arg = ary[0] + hash[arg.value] += 1 + else + hash[ary.value] += 1 + end + } + else + Puppet.warning "got arg %s" % @args.inspect + hash[@args.value] += 1 + end + 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 + end + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + # these are analogous to defining new object types + class ClassDef < AST::CompDef + attr_accessor :parentclass + + def each + [@name,@args,@parentclass,@code].each { |child| yield child } + end + + def evaluate(scope) + name = @name.evaluate(scope) + args = @args.evaluate(scope) + + #Puppet.debug "evaluating parent %s of type %s" % + # [@parent.name, @parent.class] + parent = @parentclass.evaluate(scope) + + Puppet.debug("defining hostclass '%s' with arguments [%s]" % + [name,args]) + + begin + scope.settype(name, + HostClass.new( + :name => name, + :args => args, + :parent => parent, + :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 tree(indent = 0) + return [ + @name.tree(indent + 1), + ((@@indline * 4 * indent) + self.typewrap("class")), + @args.tree(indent + 1), + @parentclass.tree(indent + 1), + @code.tree(indent + 1), + ].join("\n") + end + + def to_s + return "class %s(%s) inherits %s {\n%s }" % + [@name, @args, @parentclass, @code] + end + end + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + # host definitions are special, because they get called when a host + # whose name matches connects + class NodeDef < AST::Branch + attr_accessor :names, :code + + def each + [@names,@code].each { |child| yield child } + end + + def evaluate(scope) + names = @names.evaluate(scope) + + unless names.is_a?(Array) + names = [names] + end + Puppet.debug("defining hosts '%s'" % [names.join(", ")]) + + names.each { |name| + begin + scope.sethost(name, + Host.new( + :name => name, + :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 tree(indent = 0) + return [ + @names.tree(indent + 1), + ((@@indline * 4 * indent) + self.typewrap("host")), + @code.tree(indent + 1), + ].join("\n") + end + + def to_s + return "host %s {\n%s }" % [@name, @code] + end + end + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + # this is not really an AST node; it's just a placeholder + # for a bunch of AST code to evaluate later + class Component < AST::Branch + attr_accessor :name, :args, :code + + def evaluate(scope,hash,objtype,objname) + scope = scope.newscope + scope.type = objtype + scope.name = objname + + # define all of the arguments in our local scope + if self.args + Puppet.debug "args are %s" % self.args.inspect + self.args.each { |arg, default| + unless hash.include?(arg) + if default + hash[arg] = default + 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 + } + + 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 + } + end + + # now just evaluate the code with our new bindings + self.code.evaluate(scope) + end + end + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + # this is not really an AST node; it's just a placeholder + # for a bunch of AST code to evaluate later + class HostClass < AST::Component + attr_accessor :parentclass + + def evaluate(scope,hash,objtype,objname) + if @parentclass + 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 + parentobj.evaluate(scope,hash,objtype,objname) + end + + # just use the Component evaluate method, but change the type + # to our own type + super(scope,hash,@name,objname) + end + + def initialize(hash) + @parentclass = nil + super + if self.parent.is_a?(Array) + self.parent = nil + end + end + + end + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + # this is not really an AST node; it's just a placeholder + # for a bunch of AST code to evaluate later + class Host < AST::Component + attr_accessor :name, :args, :code, :parentclass + + def evaluate(scope,hash,objtype,objname) + if @parentclass + 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 + parentobj.evaluate(scope,hash,objtype,objname) + end + + # just use the Component evaluate method, but change the type + # to our own type + super(scope,hash,@name,objname) + end + end + #--------------------------------------------------------------- + end + end +end diff --git a/lib/puppet/parser/grammar.ra b/lib/puppet/parser/grammar.ra new file mode 100644 index 000000000..7386a0e15 --- /dev/null +++ b/lib/puppet/parser/grammar.ra @@ -0,0 +1,672 @@ +#/usr/bin/ruby + +# $Id$ +# vim: syntax=ruby + +# the parser + +class Puppet::Parser::Parser + +token LBRACK QTEXT RBRACK LBRACE RBRACE SYMBOL FARROW COMMA TRUE FALSE EQUALS +token QMARK LPAREN RPAREN ISEQUAL GREATEREQUAL GREATERTHAN LESSTHAN LESSEQUAL NOTEQUAL +token IF ELSE IMPORT DEFINE ELSIF VARIABLE CLASS INHERITS NODE BOOLEAN DOT COLON TYPE +token NAME SEMIC + +rule +program: statements { + if val[0].is_a?(AST::ASTArray) + result = val[0] + else + result = AST::ASTArray.new( + :line => @lexer.line, + :children => [val[0]] + ) + end + + # this is mainly so we can test the parser separately from the + # interpreter + if Puppet[:parseonly] + begin + if Puppet[:debug] + puts result.tree(0) + end + rescue NoMethodError => detail + Puppet.err detail + #exit(78) + end + #require 'puppet/parser/interpreter' + #result = Puppet::Server.new(result) + end +} + +statements: statement + | statements statement { + if val[0].is_a?(AST::ASTArray) + val[0].push(val[1]) + result = val[0] + else + result = AST::ASTArray.new( + :file => @lexer.file, + :line => @lexer.line, + :children => [val[0],val[1]] + ) + end +} + +statement: object + | assignment + | selector + | iftest + | import + | definition + | hostclass + +#object: name LBRACE objectname COLON params endcomma RBRACE { +object: name LBRACE objectinstances endsemi RBRACE { + ary = val[2] + if val[0].is_a?(AST::ASTArray) + Puppet.notice "invalid name" + raise Puppet::ParseError, "Invalid name" + end + if ary[0].is_a?(AST::Leaf) + ary = [ary] + end + result = AST::ASTArray.new( + :line => @lexer.line, + :file => @lexer.file + ) + ary.each { |instance| + Puppet.debug "Adding %s with name %s" % [val[0].value, instance[0].value] + result.push AST::ObjectDef.new( + :pin => "{}", + :line => @lexer.line, + :file => @lexer.file, + :type => val[0], + :name => instance[0], + :params => instance[1] + ) + } +} | name LBRACE params endcomma RBRACE { + if val[0].is_a?(AST::ASTArray) + Puppet.notice "invalid name" + raise Puppet::ParseError, "Invalid name" + end + # an object but without a name + # this cannot be an instance of a library type + + Puppet.debug "Adding %s" % val[0].value + # make a unique name for bookkeeping purposes + name = AST::Name.new( + :line => @lexer.line, + :file => @lexer.file, + :value => [val[0].value, "-", val[0].object_id].join('') + ) + + result = AST::ObjectDef.new( + :pin => "{}", + :line => @lexer.line, + :file => @lexer.file, + :type => val[0], + :name => name, + :params => val[2] + ) +} | type LBRACE params endcomma RBRACE { + # a template setting for a type + if val[0].is_a?(AST::ASTArray) + Puppet.notice "invalid type" + raise Puppet::ParseError, "Invalid type" + end + result = AST::TypeDefaults.new( + :pin => "{}", + :line => @lexer.line, + :file => @lexer.file, + :type => val[0], + :params => val[2] + ) +} + +objectinst: objectname COLON params { + result = AST::ASTArray.new( + :line => @lexer.line, + :file => @lexer.file, + :children => [val[0],val[2]] + ) +} + +objectinstances: objectinst + | objectinstances SEMIC objectinst { + if val[0][0].is_a?(AST::ASTArray) + val[0].push val[2] + result = val[0] + else + result = AST::ASTArray.new( + :line => @lexer.line, + :file => @lexer.file, + :children => [val[0],val[2]] + ) + end +} + +endsemi: # nothing + | SEMIC + +name: NAME { + result = AST::Name.new( + :line => @lexer.line, + :file => @lexer.file, + :value => val[0] + ) +} + +type: TYPE { + result = AST::Type.new( + :line => @lexer.line, + :file => @lexer.file, + :value => val[0] + ) +} + +objectname: quotedtext + | name + | selector + | variable + | array + +assignment: VARIABLE EQUALS rvalue { + # this is distinct from referencing a variable + variable = AST::Name.new( + :line => @lexer.line, + :file => @lexer.file, + :value => val[0].sub(/^\$/,'') + ) + result = AST::VarDef.new( + :pin => "=", + :line => @lexer.line, + :file => @lexer.file, + :name => variable, + :value => val[2] + ) +} + +params: # nothing +{ + result = AST::ASTArray.new( + :line => @lexer.line, + :file => @lexer.file, + :children => [] + ) +} + | param { result = val[0] } + | params COMMA param { + if val[0].is_a?(AST::ASTArray) + val[0].push(val[2]) + result = val[0] + else + result = AST::ASTArray.new( + :line => @lexer.line, + :file => @lexer.file, + :children => [val[0],val[2]] + ) + end +} + +param: NAME FARROW rvalue { + leaf = AST::String.new( + :line => @lexer.line, + :file => @lexer.file, + :value => val[0] + ) + result = AST::ObjectParam.new( + :pin => "=>", + :line => @lexer.line, + :file => @lexer.file, + :param => leaf, + :value => val[2] + ) +} + +rvalues: rvalue + | rvalues comma rvalue { + if val[0].is_a?(AST::ASTArray) + result = val[0].push(val[2]) + else + result = AST::ASTArray.new( + :line => @lexer.line, + :file => @lexer.file, + :children => [val[0],val[2]] + ) + end +} + +rvalue: quotedtext + | name + | type + | boolean + | selector + | object + | variable + | array + | objectref + +quotedtext: QTEXT { + result = AST::String.new( + :line => @lexer.line, + :file => @lexer.file, + :value => val[0] + ) +} + +boolean: BOOLEAN { + result = AST::Boolean.new( + :line => @lexer.line, + :file => @lexer.file, + :value => val[0] + ) +} + +objectref: name LBRACK rvalue RBRACK { + result = AST::ObjectRef.new( + :pin => '[]', + :line => @lexer.line, + :file => @lexer.file, + :type => val[0], + :name => val[2] + ) +} + +iftest: IF test LBRACE statements RBRACE { + result = AST::If.new( + :pin => "if", + :test => val[1], + :file => @lexer.file, + :line => @lexer.line, + :statements => val[3] + ) +} + | IF test LBRACE statements RBRACE elsifs ELSE LBRACE statements RBRACE { + # make sure our elsifs are an array, as it will save lots of + # effort later + unless val[5].is_a?(AST::ASTArray) + val[5] = AST::ASTArray.new( + :line => @lexer.line, + :file => @lexer.file, + :children => [val[5]] + ) + end + + result = AST::If.new( + :pin => "if", + :test => val[1], + :statements => val[3], + :file => @lexer.file, + :line => @lexer.line, + :else => val[8], + :elsif => val[5] + ) + +} + | IF test LBRACE statements RBRACE ELSE LBRACE statements RBRACE { + result = AST::If.new( + :pin => "if", + :test => val[1], + :statements => val[3], + :file => @lexer.file, + :line => @lexer.line, + :else => val[7] + ) + +} + +elsifs: ELSIF test LBRACE statements RBRACE { + result = AST::If.new( + :pin => "elseif", + :test => val[1], + :file => @lexer.file, + :statements => val[3], + :line => @lexer.line + ) +} + | elsifs ELSIF test LBRACE statements RBRACE { + second = AST::If.new( + :pin => "elsif", + :test => val[2], + :statements => val[4], + :file => @lexer.file, + :line => @lexer.line + ) + + if val[0].is_a?(AST::ASTArray) + val[0].push(second) + result = val[0] + else + result = AST::ASTArray.new( + :line => @lexer.line, + :file => @lexer.file, + :children => [val[0],second] + ) + end +} + +test: rvalue + | rvalue testop rvalue { + result = AST::Test.new( + :pin => val[1], + :line => @lexer.line, + :file => @lexer.file, + :lhs => val[0], + :rhs => val[2] + ) +} + +testop: ISEQUAL + | GREATEREQUAL + | GREATERTHAN + | LESSTHAN + | LESSEQUAL + | NOTEQUAL + +selector: variable QMARK svalues { + result = AST::Selector.new( + :pin => "?", + :line => @lexer.line, + :file => @lexer.file, + :param => val[0], + :value => val[2] + ) +} + +svalues: selectval + | LBRACE sintvalues RBRACE { result = val[1] } + +sintvalues: selectval + | sintvalues comma selectval { + if val[0].is_a?(AST::ASTArray) + val[0].push(val[2]) + result = val[0] + else + result = AST::ASTArray.new( + :line => @lexer.line, + :file => @lexer.file, + :children => [val[0],val[2]] + ) + end +} + +selectval: selectlhand FARROW rvalue { + leaf = AST::String.new( + :line => @lexer.line, + :file => @lexer.file, + :value => val[0] + ) + result = AST::ObjectParam.new( + :pin => "=>", + :line => @lexer.line, + :file => @lexer.file, + :param => leaf, + :value => val[2] + ) +} + +selectlhand: NAME + | TYPE + | QTEXT + +import: IMPORT QTEXT { + # importing files + # yuk, i hate keywords + # we'll probably have to have some kind of search path eventually + # but for now, just use a path relative to the file doing the importing + path = @lexer.file.sub(%r{[^/]+$},val[1]) + parser = Puppet::Parser::Parser.new() + parser.stack = self.stack + Puppet.debug("importing %s" % path) + noimport = false + begin + parser.file = path + rescue Puppet::ImportError + Puppet.warning("Importing %s would result in an import loop" % path) + result = AST::ASTArray.new( + :file => @lexer.file, + :line => @lexer.line + ) + noimport = true + end + unless noimport + result = parser.parse + end +} + +definition: DEFINE NAME argumentlist LBRACE statements RBRACE { + result = AST::CompDef.new( + :name => AST::Name.new(:value => val[1], :line => @lexer.line), + :args => val[2], + :file => @lexer.file, + :line => @lexer.line, + :code => val[4] + ) +} + +hostclass: CLASS NAME argumentlist parent LBRACE statements RBRACE { + result = AST::ClassDef.new( + :name => AST::Name.new(:value => val[1], :line => @lexer.line), + :args => val[2], + :parentclass => val[3], + :file => @lexer.file, + :line => @lexer.line, + :code => val[5] + ) +} + +#nodedef: NODE words LBRACE statements RBRACE { +# result = AST::NodeDef.new( +# :names => val[1], +# :code => val[3] +# ) +#} + +nothing: { + result = AST::ASTArray.new( + :line => @lexer.line, + :file => @lexer.file, + :children => [] + ) +} + +argumentlist: nothing + | LPAREN nothing RPAREN { + result = val[1] +} + | LPAREN arguments RPAREN { + if val[1].is_a?(AST::ASTArray) + result = val[1] + else + result = AST::ASTArray.new( + :line => @lexer.line, + :file => @lexer.file, + :children => [val[0]] + ) + end +} + +arguments: argument + | arguments COMMA argument { + if val[0].is_a?(AST::ASTArray) + val[0].push(val[2]) + result = val[0] + else + result = AST::ASTArray.new( + :line => @lexer.line, + :file => @lexer.file, + :children => [val[0],val[2]] + ) + end +} + +argument: name EQUALS rvalue { + result = AST::ASTArray.new( + :line => @lexer.line, + :file => @lexer.file, + :children => [val[0],val[2]] + ) +} + | name { + result = AST::ASTArray.new( + :line => @lexer.line, + :file => @lexer.file, + :children => [val[0]] + ) +} + +parent: nothing + | INHERITS NAME { + result = AST::Name.new( + :value => val[1], + :file => @lexer.file, + :line => @lexer.line + ) +} + +variable: VARIABLE { + name = val[0].sub(/^\$/,'') + result = AST::Variable.new( + :line => @lexer.line, + :file => @lexer.file, + :value => name + ) +} + +array: LBRACK rvalues RBRACK { + if val[1].is_a?(AST::ASTArray) + result = val[1] + else + result = AST::ASTArray.new + result.push val[1] + end +} + +comma: FARROW + | COMMA + +endcomma: # nothing + | COMMA { result = nil } + +end +---- header ---- +require 'puppet' +require 'puppet/parser/lexer' +require 'puppet/parser/ast' +#require 'puppet/parser/interpreter' + +module Puppet + # this exception class already has a :stack accessor + class ParseError < Puppet::Error + attr_accessor :line, :file + end + + class ImportError < Racc::ParseError; end +end + +Puppet[:typecheck] = true +Puppet[:paramcheck] = true + +---- inner ---- +attr_writer :stack +attr_reader :file + +def file=(file) + if self.stack.include?(file) + raise Puppet::ImportError.new("Import loop detected") + else + @lexer.file = file + end +end + +def initialize + @lexer = Puppet::Parser::Lexer.new() + if Puppet[:debug] + @yydebut = true + 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 = "an error was found" + + if Puppet[:debug] + puts stack.inspect + puts stack + end + if @lexer.file + error += (" in '%s'" % @lexer.file) + 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 + begin + yyparse(@lexer,:scan) + rescue Racc::ParseError => except + error = Puppet::ParseError.new(except) + error.line = @lexer.line + error.file = @lexer.file + error.stack = caller + 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 + except.stack ||= except.stack + raise except + rescue Puppet::DevError => except + except.line ||= @lexer.line + except.file ||= @lexer.file + except.stack ||= caller + if Puppet[:debug] + puts except.stack + end + raise except + rescue => except + error = Puppet::DevError.new(except.message) + error.line = @lexer.line + error.file = @lexer.file + error.stack = caller + if Puppet[:debug] + puts caller + end + raise error + end +end + +def stack + if defined? @stack and ! @stack.nil? + if @lexer.file + return [@stack,@lexer.file].flatten + else + return @stack + end + else + if @lexer.file + return [@lexer.file] + else + return [] + end + end +end + +def string=(string) + @lexer.string = string +end diff --git a/lib/puppet/parser/interpreter.rb b/lib/puppet/parser/interpreter.rb new file mode 100644 index 000000000..88ab5854e --- /dev/null +++ b/lib/puppet/parser/interpreter.rb @@ -0,0 +1,135 @@ +#!/usr/local/bin/ruby -w + +# $Id$ + +# the interpreter +# +# this builds our virtual pinball machine, into which we'll place our host-specific +# information and out of which we'll receive our host-specific configuration + +require 'puppet' +require 'puppet/parser/parser' +require 'puppet/parser/scope' + + +module Puppet + module Parser + #--------------------------------------------------------------- + class Interpreter + attr_accessor :ast, :topscope + # just shorten the constant path a bit, using what amounts to an alias + AST = Puppet::Parser::AST + + #------------------------------------------------------------ + def clear + TransObject.clear + end + #------------------------------------------------------------ + + #------------------------------------------------------------ + #def callfunc(function,*args) + # #Puppet.debug("Calling %s on %s" % [function,@client]) + # @client.callfunc(function,*args) + # #Puppet.debug("Finished %s" % function) + #end + #------------------------------------------------------------ + + #------------------------------------------------------------ + # create our interpreter + def initialize(hash) + unless hash.include?(:ast) + raise ArgumentError.new("Must pass tree and client to Interpreter") + end + @ast = hash[:ast] + #@client = hash[:client] + + @scope = Puppet::Parser::Scope.new() # no parent scope + @topscope = @scope + @scope.interp = self + + if hash.include?(:facts) + facts = hash[:facts] + unless facts.is_a?(Hash) + raise ArgumentError.new("Facts must be a hash") + end + + facts.each { |fact,value| + @scope.setvar(fact,value) + } + end + end + #------------------------------------------------------------ + + #------------------------------------------------------------ + # evaluate our whole tree + def run + # evaluate returns a value, but at the top level we only + # care about its side effects + # i think + unless @ast.is_a?(AST) or @ast.is_a?(AST::ASTArray) + Puppet.err "Received top-level non-ast '%s' of type %s" % + [@ast,@ast.class] + raise TypeError.new("Received non-ast '%s' of type %s" % + [@ast,@ast.class]) + end + + begin + @ast.evaluate(@scope) + rescue Puppet::DevError, Puppet::Error, Puppet::ParseError => except + #Puppet.err "File %s, line %s: %s" % + # [except.file, except.line, except.message] + if Puppet[:debug] + puts except.stack + end + #exit(1) + raise + rescue => except + error = Puppet::DevError.new("%s: %s" % + [except.class, except.message]) + error.stack = caller + if Puppet[:debug] + puts error.stack + end + raise error + end + + # okay, at this point we have a tree of scopes, and we want to + # unzip along that tree, building our structure of objects + # to pass to the client + # this will be heirarchical, and will (at this point) contain + # only TransObjects and TransSettings + @topscope.name = "top" + @topscope.type = "puppet" + begin + topbucket = @topscope.to_trans + rescue => detail + Puppet.warning detail + raise + end + + # add our settings to the front of the array + # at least, for now + #@topscope.typesets.each { |setting| + # topbucket.unshift setting + #} + + # guarantee that settings are at the very top + #topbucket.push settingbucket + #topbucket.push @scope.to_trans + + #retlist = TransObject.list + #Puppet.debug "retobject length is %s" % retlist.length + #TransObject.clear + return topbucket + end + #------------------------------------------------------------ + + #------------------------------------------------------------ + def scope + return @scope + end + #------------------------------------------------------------ + end + #--------------------------------------------------------------- + end +end diff --git a/lib/puppet/parser/lexer.rb b/lib/puppet/parser/lexer.rb new file mode 100644 index 000000000..d9d9d5f5c --- /dev/null +++ b/lib/puppet/parser/lexer.rb @@ -0,0 +1,225 @@ +#!/usr/local/bin/ruby -w + +# $Id$ + +# the scanner/lexer + +require 'strscan' +require 'puppet' + + +module Puppet + class LexError < RuntimeError; end + module Parser + #--------------------------------------------------------------- + class Lexer + attr_reader :line, :last, :file + + #%r{\w+} => :WORD, + @@tokens = { + %r{#.*} => :COMMENT, + %r{\[} => :LBRACK, + %r{\]} => :RBRACK, + %r{\{} => :LBRACE, + %r{\}} => :RBRACE, + %r{\(} => :LPAREN, + %r{\)} => :RPAREN, + %r{"} => :DQUOTE, + %r{\n} => :RETURN, + %r{'} => :SQUOTE, + %r{=} => :EQUALS, + %r{==} => :ISEQUAL, + %r{>=} => :GREATEREQUAL, + %r{>} => :GREATERTHAN, + %r{<} => :LESSTHAN, + %r{<=} => :LESSEQUAL, + %r{!=} => :NOTEQUAL, + %r{,} => :COMMA, + %r{\.} => :DOT, + %r{:} => :COLON, + %r{;} => :SEMIC, + %r{\?} => :QMARK, + %r{\\} => :BACKSLASH, + %r{=>} => :FARROW, + %r{[a-z]\w*} => :NAME, + %r{[A-Z]\w*} => :TYPE, + %r{[0-9]+} => :NUMBER, + %r{\$\w+} => :VARIABLE + } + + @@keywords = { + "if" => :IF, + "elsif" => :ELSIF, + "else" => :ELSE, + "import" => :IMPORT, + "class" => :CLASS, + "node" => :NODE, + "host" => :NODE, + "true" => :BOOLEAN, + "false" => :BOOLEAN, + "inherits" => :INHERITS, + "define" => :DEFINE + } + + # scan the whole file + # basically just used for testing + def fullscan + array = [] + + self.scan { |token,str| + #Puppet.debug("got token '%s' => '%s'" % [token,str]) + if token.nil? + return array + else + array.push([token,str]) + end + } + return array + end + + # this is probably pretty damned inefficient... + # it'd be nice not to have to load the whole file first... + def file=(file) + @file = file + @line = 1 + File.open(file) { |of| + str = "" + of.each { |line| str += line } + @scanner = StringScanner.new(str) + } + end + + def initialize + @line = 1 + @last = "" + @scanner = nil + @file = nil + # AAARRGGGG! okay, regexes in ruby are bloody annoying + # no one else has "\n" =~ /\s/ + @skip = %r{[ \t]+} + end + + def rest + @scanner.rest + end + + # this is the heart of the lexer + def scan + #Puppet.debug("entering scan") + if @scanner.nil? + raise TypeError.new("Invalid or empty string") + end + + @scanner.skip(@skip) + until @scanner.eos? do + yielded = false + sendbreak = false # gah, this is a nasty hack + stoken = nil + sregex = nil + value = "" + + # first find out which type of token we've got + @@tokens.each { |regex,token| + # we're just checking, which doesn't advance the scan + # pointer + tmp = @scanner.check(regex) + if tmp.nil? + #puppet.debug("did not match %s to '%s'" % + # [regex,@scanner.rest]) + next + end + + # find the longest match + if tmp.length > value.length + value = tmp + stoken = token + sregex = regex + else + # we've already got a longer match + next + end + } + + # error out if we didn't match anything at all + if stoken.nil? + nword = nil + if @scanner.rest =~ /^(\S+)/ + nword = $1 + elsif@scanner.rest =~ /^(\s+)/ + nword = $1 + else + nword = @scanner.rest + end + raise "Could not match '%s'" % nword + end + + value = @scanner.scan(sregex) + + if value == "" + raise "Didn't match regex on token %s" % stoken + end + + # token-specific operations + # if this gets much more complicated, it should + # be moved up to where the tokens themselves are defined + # which will get me about 75% of the way to a lexer generator + case stoken + when :NAME then + wtoken = stoken + # we're looking for keywords here + if @@keywords.include?(value) + wtoken = @@keywords[value] + #Puppet.debug("token '%s'" % wtoken) + end + yield [wtoken,value] + @last = value + when :NUMBER then + yield [:NAME,value] + # just throw comments away + when :COMMENT then + # just throw comments away + when :RETURN then + @line += 1 + @scanner.skip(@skip) + when :DQUOTE then + #Puppet.debug("searching '%s' after '%s'" % [self.rest,value]) + value = self.slurpstring(value) + yield [:QTEXT,value] + @last = value + #stoken = :QTEXT + #Puppet.debug("got string '%s' => '%s'" % [:QTEXT,value]) + else + yield [stoken,value] + @last = value + #Puppet.debug("got token '%s' => '%s'" % [stoken,value]) + end + @scanner.skip(@skip) + end + @scanner = nil + yield [false,false] + end + + # we've encountered an opening quote... + # slurp in the rest of the string and return it + def slurpstring(quote) + #Puppet.debug("searching '%s'" % self.rest) + str = @scanner.scan_until(/[^\\]#{quote}/) + #str = @scanner.scan_until(/"/) + if str.nil? + raise Puppet::LexError.new("Unclosed quote after '%s' in '%s'" % + [self.last,self.rest]) + else + str.sub!(/#{quote}$/,"") + str.gsub!(/\\#{quote}/,quote) + end + + return str + end + + def string=(string) + @scanner = StringScanner.new(string) + end + end + #--------------------------------------------------------------- + end +end diff --git a/lib/puppet/parser/makefile b/lib/puppet/parser/makefile new file mode 100644 index 000000000..9ec6265de --- /dev/null +++ b/lib/puppet/parser/makefile @@ -0,0 +1,5 @@ +#parser.rb: grammar.ry +# ryacc --output parser grammar + +parser.rb: grammar.ra + racc -v -o$@ grammar.ra diff --git a/lib/puppet/parser/parser.rb b/lib/puppet/parser/parser.rb new file mode 100644 index 000000000..3b42bd8d7 --- /dev/null +++ b/lib/puppet/parser/parser.rb @@ -0,0 +1,1264 @@ +# +# DO NOT MODIFY!!!! +# This file is automatically generated by racc 1.4.4 +# from racc grammer file "grammar.ra". +# + +require 'racc/parser' + + +require 'puppet' +require 'puppet/parser/lexer' +require 'puppet/parser/ast' +#require 'puppet/parser/interpreter' + +module Puppet + # this exception class already has a :stack accessor + class ParseError < Puppet::Error + attr_accessor :line, :file + end + + class ImportError < Racc::ParseError; end +end + +Puppet[:typecheck] = true +Puppet[:paramcheck] = true + + +module Puppet + + module Parser + + class Parser < Racc::Parser + +module_eval <<'..end grammar.ra modeval..ida89b5273aa', 'grammar.ra', 573 +attr_writer :stack +attr_reader :file + +def file=(file) + if self.stack.include?(file) + raise Puppet::ImportError.new("Import loop detected") + else + @lexer.file = file + end +end + +def initialize + @lexer = Puppet::Parser::Lexer.new() + if Puppet[:debug] + @yydebut = true + 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 = "an error was found" + + if Puppet[:debug] + puts stack.inspect + puts stack + end + if @lexer.file + error += (" in '%s'" % @lexer.file) + 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 + begin + yyparse(@lexer,:scan) + rescue Racc::ParseError => except + error = Puppet::ParseError.new(except) + error.line = @lexer.line + error.file = @lexer.file + error.stack = caller + 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 + except.stack ||= except.stack + raise except + rescue Puppet::DevError => except + except.line ||= @lexer.line + except.file ||= @lexer.file + except.stack ||= caller + if Puppet[:debug] + puts except.stack + end + raise except + rescue => except + error = Puppet::DevError.new(except.message) + error.line = @lexer.line + error.file = @lexer.file + error.stack = caller + if Puppet[:debug] + puts caller + end + raise error + end +end + +def stack + if defined? @stack and ! @stack.nil? + if @lexer.file + return [@stack,@lexer.file].flatten + else + return @stack + end + else + if @lexer.file + return [@lexer.file] + else + return [] + end + end +end + +def string=(string) + @lexer.string = string +end +..end grammar.ra modeval..ida89b5273aa + +##### racc 1.4.4 generates ### + +racc_reduce_table = [ + 0, 0, :racc_error, + 1, 38, :_reduce_1, + 1, 39, :_reduce_none, + 2, 39, :_reduce_3, + 1, 40, :_reduce_none, + 1, 40, :_reduce_none, + 1, 40, :_reduce_none, + 1, 40, :_reduce_none, + 1, 40, :_reduce_none, + 1, 40, :_reduce_none, + 1, 40, :_reduce_none, + 5, 41, :_reduce_11, + 5, 41, :_reduce_12, + 5, 41, :_reduce_13, + 3, 54, :_reduce_14, + 1, 49, :_reduce_none, + 3, 49, :_reduce_16, + 0, 50, :_reduce_none, + 1, 50, :_reduce_none, + 1, 48, :_reduce_19, + 1, 53, :_reduce_20, + 1, 55, :_reduce_none, + 1, 55, :_reduce_none, + 1, 55, :_reduce_none, + 1, 55, :_reduce_none, + 1, 55, :_reduce_none, + 3, 42, :_reduce_26, + 0, 51, :_reduce_27, + 1, 51, :_reduce_28, + 3, 51, :_reduce_29, + 3, 60, :_reduce_30, + 1, 61, :_reduce_none, + 3, 61, :_reduce_32, + 1, 59, :_reduce_none, + 1, 59, :_reduce_none, + 1, 59, :_reduce_none, + 1, 59, :_reduce_none, + 1, 59, :_reduce_none, + 1, 59, :_reduce_none, + 1, 59, :_reduce_none, + 1, 59, :_reduce_none, + 1, 59, :_reduce_none, + 1, 56, :_reduce_42, + 1, 63, :_reduce_43, + 4, 64, :_reduce_44, + 5, 44, :_reduce_45, + 10, 44, :_reduce_46, + 9, 44, :_reduce_47, + 5, 66, :_reduce_48, + 6, 66, :_reduce_49, + 1, 65, :_reduce_none, + 3, 65, :_reduce_51, + 1, 67, :_reduce_none, + 1, 67, :_reduce_none, + 1, 67, :_reduce_none, + 1, 67, :_reduce_none, + 1, 67, :_reduce_none, + 1, 67, :_reduce_none, + 3, 43, :_reduce_58, + 1, 68, :_reduce_none, + 3, 68, :_reduce_60, + 1, 70, :_reduce_none, + 3, 70, :_reduce_62, + 3, 69, :_reduce_63, + 1, 71, :_reduce_none, + 1, 71, :_reduce_none, + 1, 71, :_reduce_none, + 2, 45, :_reduce_67, + 6, 46, :_reduce_68, + 7, 47, :_reduce_69, + 0, 74, :_reduce_70, + 1, 72, :_reduce_none, + 3, 72, :_reduce_72, + 3, 72, :_reduce_73, + 1, 75, :_reduce_none, + 3, 75, :_reduce_75, + 3, 76, :_reduce_76, + 1, 76, :_reduce_77, + 1, 73, :_reduce_none, + 2, 73, :_reduce_79, + 1, 57, :_reduce_80, + 3, 58, :_reduce_81, + 1, 62, :_reduce_none, + 1, 62, :_reduce_none, + 0, 52, :_reduce_none, + 1, 52, :_reduce_85 ] + +racc_reduce_n = 86 + +racc_shift_n = 153 + +racc_action_table = [ + 22, 24, 22, 24, 1, 54, 5, 9, 39, 12, + 14, 22, 24, 22, 24, 82, 3, 7, 116, 84, + 85, 59, 22, 24, 115, 28, 126, 28, 96, 31, + 108, 31, 3, 7, 3, 7, 28, 59, 28, 62, + 31, 7, 31, 3, 7, 3, 7, 28, 22, 24, + 89, 31, 56, 57, 3, 7, 97, 22, 24, 22, + 24, 22, 24, 128, 22, 24, 129, 113, 56, 57, + 22, 24, 114, 28, 98, 22, 24, 31, 59, 103, + 3, 7, 28, 80, 28, 80, 28, 63, 31, 28, + 7, 3, 7, 31, 69, 28, 3, 7, 133, 31, + 28, 121, 3, 7, 31, 22, 24, 3, 7, 56, + 57, 122, 136, 80, 1, 137, 5, 9, 124, 12, + 14, 125, 111, 109, 84, 85, 3, 7, 102, 102, + 28, 78, 63, 55, 31, 44, 134, 3, 7, 1, + 140, 5, 9, 99, 12, 14, 98, 138, 37, 80, + 37, 3, 7, 141, 152, 44, 1, 42, 5, 9, + 144, 12, 14, 41, 146, 40, 39, 38, 3, 7, + 1, 151, 5, 9, 37, 12, 14, 36, 7, nil, + nil, nil, 3, 7, nil, 149, nil, 1, nil, 5, + 9, nil, 12, 14, nil, nil, nil, nil, nil, 3, + 7, 1, 147, 5, 9, nil, 12, 14, nil, nil, + nil, nil, nil, 3, 7, nil, nil, nil, 1, nil, + 5, 9, nil, 12, 14, nil, nil, nil, nil, nil, + 3, 7, 1, nil, 5, 9, nil, 12, 14, nil, + nil, nil, nil, nil, 3, 7, 1, nil, 5, 9, + nil, 12, 14, nil, nil, nil, nil, nil, 3, 7, + 1, nil, 5, 9, nil, 12, 14, nil, nil, nil, + nil, nil, 3, 7, 1, nil, 5, 9, nil, 12, + 14, nil, nil, nil, nil, nil, 3, 7, 1, nil, + 5, 9, nil, 12, 14, nil, nil, nil, nil, nil, + 3, 7, 1, nil, 5, 9, nil, 12, 14, nil, + nil, nil, nil, nil, 3, 7, 1, nil, 5, 9, + nil, 12, 14, nil, nil, nil, nil, nil, 3, 7, + 1, nil, 5, 9, nil, 12, 14, nil, nil, nil, + nil, nil, 3, 7, 48, 49, 50, 51, 52, 53 ] + +racc_action_check = [ + 1, 1, 83, 83, 96, 27, 96, 96, 27, 96, + 96, 89, 89, 54, 54, 46, 96, 96, 94, 46, + 46, 112, 47, 47, 94, 1, 106, 83, 64, 1, + 87, 83, 1, 1, 83, 83, 89, 37, 54, 37, + 89, 63, 54, 89, 89, 54, 54, 47, 98, 98, + 61, 47, 112, 112, 47, 47, 66, 99, 99, 129, + 129, 39, 39, 109, 113, 113, 109, 92, 37, 37, + 137, 137, 93, 98, 80, 22, 22, 98, 62, 79, + 98, 98, 99, 97, 129, 44, 39, 42, 129, 113, + 99, 129, 129, 113, 39, 137, 113, 113, 117, 137, + 22, 100, 137, 137, 22, 40, 40, 22, 22, 62, + 62, 101, 127, 102, 117, 127, 117, 117, 103, 117, + 117, 104, 91, 88, 91, 91, 117, 117, 81, 74, + 40, 41, 38, 34, 40, 33, 118, 40, 40, 88, + 135, 88, 88, 73, 88, 88, 69, 128, 68, 134, + 23, 88, 88, 136, 150, 18, 135, 14, 135, 135, + 139, 135, 135, 13, 142, 12, 11, 9, 135, 135, + 150, 148, 150, 150, 6, 150, 150, 5, 116, nil, + nil, nil, 150, 150, nil, 145, nil, 148, nil, 148, + 148, nil, 148, 148, nil, nil, nil, nil, nil, 148, + 148, 145, 143, 145, 145, nil, 145, 145, nil, nil, + nil, nil, nil, 145, 145, nil, nil, nil, 143, nil, + 143, 143, nil, 143, 143, nil, nil, nil, nil, nil, + 143, 143, 144, nil, 144, 144, nil, 144, 144, nil, + nil, nil, nil, nil, 144, 144, 141, nil, 141, 141, + nil, 141, 141, nil, nil, nil, nil, nil, 141, 141, + 146, nil, 146, 146, nil, 146, 146, nil, nil, nil, + nil, nil, 146, 146, 55, nil, 55, 55, nil, 55, + 55, nil, nil, nil, nil, nil, 55, 55, 138, nil, + 138, 138, nil, 138, 138, nil, nil, nil, nil, nil, + 138, 138, 15, nil, 15, 15, nil, 15, 15, nil, + nil, nil, nil, nil, 15, 15, 125, nil, 125, 125, + nil, 125, 125, nil, nil, nil, nil, nil, 125, 125, + 0, nil, 0, 0, nil, 0, 0, nil, nil, nil, + nil, nil, 0, 0, 26, 26, 26, 26, 26, 26 ] + +racc_action_pointer = [ + 308, -2, nil, nil, nil, 174, 161, nil, nil, 132, + nil, 161, 153, 163, 122, 280, nil, nil, 150, nil, + nil, nil, 73, 137, nil, nil, 328, 3, nil, nil, + nil, nil, nil, 130, 128, nil, nil, 34, 118, 59, + 103, 131, 73, nil, 50, nil, 11, 20, nil, nil, + nil, nil, nil, nil, 11, 252, nil, nil, nil, nil, + nil, 42, 75, 6, 23, nil, 23, nil, 135, 138, + nil, nil, nil, 107, 120, nil, nil, nil, nil, 50, + 66, 119, nil, 0, nil, nil, nil, 26, 117, 9, + nil, 116, 55, 57, 9, nil, -18, 48, 46, 55, + 95, 105, 78, 83, 116, nil, 20, nil, nil, 40, + nil, nil, 18, 62, nil, nil, 143, 92, 127, nil, + nil, nil, nil, nil, nil, 294, nil, 89, 142, 57, + nil, nil, nil, nil, 114, 134, 148, 68, 266, 155, + nil, 224, 159, 196, 210, 179, 238, nil, 165, nil, + 148, nil, nil ] + +racc_action_default = [ + -86, -86, -7, -20, -8, -86, -86, -19, -9, -86, + -10, -86, -80, -86, -86, -1, -2, -4, -86, -5, + -6, -33, -86, -39, -42, -40, -50, -34, -80, -38, + -36, -43, -41, -35, -86, -37, -67, -86, -70, -27, + -86, -86, -70, -3, -27, -31, -86, -86, -52, -53, + -54, -55, -56, -57, -86, -86, -65, -64, -58, -66, + -59, -86, -86, -70, -86, -71, -86, -21, -24, -19, + -25, -22, -28, -17, -84, -23, -15, -26, 153, -70, + -86, -84, -81, -86, -82, -83, -51, -86, -86, -86, + -61, -86, -77, -86, -86, -74, -86, -27, -86, -18, + -86, -86, -85, -86, -86, -78, -86, -32, -44, -45, + -63, -60, -86, -86, -72, -73, -86, -86, -14, -30, + -16, -11, -12, -29, -79, -86, -13, -86, -86, -86, + -62, -76, -75, -68, -86, -86, -86, -86, -86, -86, + -69, -86, -86, -86, -86, -86, -86, -47, -86, -46, + -86, -48, -49 ] + +racc_goto_table = [ + 15, 29, 76, 33, 60, 70, 23, 34, 67, 123, + 93, 74, 83, 95, 45, 64, 81, 58, 101, 79, + 47, 127, 29, 73, 33, 106, 105, 23, 91, 90, + 100, 104, 77, 46, 43, 94, 13, nil, nil, 86, + 29, 123, 33, nil, 68, 23, 87, 29, nil, 33, + nil, nil, 23, nil, 29, 88, 33, 112, nil, 23, + nil, nil, 120, 35, nil, 70, 132, nil, 67, 118, + nil, nil, nil, nil, nil, 107, nil, nil, nil, 130, + nil, 110, nil, 29, 35, 33, nil, nil, 23, 29, + 119, 33, nil, nil, 23, nil, 117, nil, 29, nil, + 33, 75, 35, 23, 68, 131, nil, 43, nil, 35, + nil, nil, nil, 29, nil, 33, 35, nil, 23, 27, + nil, nil, nil, nil, nil, 135, nil, nil, nil, 29, + nil, 33, nil, nil, 23, 139, 43, 29, 143, 33, + 27, 145, 23, 142, 148, 35, 150, nil, nil, nil, + nil, 35, nil, nil, 43, nil, nil, 71, 27, nil, + 35, 75, 43, nil, 43, 27, nil, 43, nil, 43, + nil, nil, 27, nil, nil, 35, nil, nil, nil, nil, + nil, 92, nil, nil, nil, nil, nil, nil, nil, nil, + nil, 35, nil, nil, nil, nil, nil, nil, nil, 35, + nil, 27, nil, nil, nil, nil, nil, 27, nil, nil, + nil, nil, nil, nil, nil, nil, 27, 71, nil, nil, + nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, + nil, 27, nil, nil, 92, nil, nil, nil, nil, nil, + nil, nil, nil, nil, nil, nil, nil, 27, nil, nil, + nil, nil, nil, nil, nil, 27 ] + +racc_goto_check = [ + 2, 4, 17, 16, 32, 21, 20, 28, 19, 23, + 37, 14, 25, 39, 22, 35, 14, 31, 15, 35, + 30, 29, 4, 12, 16, 15, 37, 20, 33, 32, + 13, 36, 22, 24, 3, 38, 1, nil, nil, 22, + 4, 23, 16, nil, 20, 20, 22, 4, nil, 16, + nil, nil, 20, nil, 4, 2, 16, 25, nil, 20, + nil, nil, 17, 6, nil, 21, 39, nil, 19, 14, + nil, nil, nil, nil, nil, 22, nil, nil, nil, 32, + nil, 22, nil, 4, 6, 16, nil, nil, 20, 4, + 22, 16, nil, nil, 20, nil, 2, nil, 4, nil, + 16, 6, 6, 20, 20, 22, nil, 3, nil, 6, + nil, nil, nil, 4, nil, 16, 6, nil, 20, 11, + nil, nil, nil, nil, nil, 2, nil, nil, nil, 4, + nil, 16, nil, nil, 20, 28, 3, 4, 2, 16, + 11, 2, 20, 28, 2, 6, 2, nil, nil, nil, + nil, 6, nil, nil, 3, nil, nil, 11, 11, nil, + 6, 6, 3, nil, 3, 11, nil, 3, nil, 3, + nil, nil, 11, nil, nil, 6, nil, nil, nil, nil, + nil, 11, nil, nil, nil, nil, nil, nil, nil, nil, + nil, 6, nil, nil, nil, nil, nil, nil, nil, 6, + nil, 11, nil, nil, nil, nil, nil, 11, nil, nil, + nil, nil, nil, nil, nil, nil, 11, 11, nil, nil, + nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, + nil, 11, nil, nil, 11, nil, nil, nil, nil, nil, + nil, nil, nil, nil, nil, nil, nil, 11, nil, nil, + nil, nil, nil, nil, nil, 11 ] + +racc_goto_pointer = [ + nil, 36, 0, 19, 0, nil, 62, nil, nil, nil, + nil, 118, -16, -43, -28, -56, 2, -37, nil, -31, + 5, -34, -8, -93, 11, -34, nil, nil, 6, -88, + -6, -20, -33, -34, nil, -23, -48, -53, -28, -50 ] + +racc_goto_default = [ + nil, nil, nil, 16, 17, 19, 20, 2, 4, 8, + 10, 11, nil, nil, nil, nil, 18, nil, 66, 21, + 6, 25, 26, 72, nil, nil, 30, 32, nil, nil, + nil, nil, nil, nil, 61, nil, nil, 65, nil, nil ] + +racc_token_table = { + false => 0, + Object.new => 1, + :LBRACK => 2, + :QTEXT => 3, + :RBRACK => 4, + :LBRACE => 5, + :RBRACE => 6, + :SYMBOL => 7, + :FARROW => 8, + :COMMA => 9, + :TRUE => 10, + :FALSE => 11, + :EQUALS => 12, + :QMARK => 13, + :LPAREN => 14, + :RPAREN => 15, + :ISEQUAL => 16, + :GREATEREQUAL => 17, + :GREATERTHAN => 18, + :LESSTHAN => 19, + :LESSEQUAL => 20, + :NOTEQUAL => 21, + :IF => 22, + :ELSE => 23, + :IMPORT => 24, + :DEFINE => 25, + :ELSIF => 26, + :VARIABLE => 27, + :CLASS => 28, + :INHERITS => 29, + :NODE => 30, + :BOOLEAN => 31, + :DOT => 32, + :COLON => 33, + :TYPE => 34, + :NAME => 35, + :SEMIC => 36 } + +racc_use_result_var = true + +racc_nt_base = 37 + +Racc_arg = [ + racc_action_table, + racc_action_check, + racc_action_default, + racc_action_pointer, + racc_goto_table, + racc_goto_check, + racc_goto_default, + racc_goto_pointer, + racc_nt_base, + racc_reduce_table, + racc_token_table, + racc_shift_n, + racc_reduce_n, + racc_use_result_var ] + +Racc_token_to_s_table = [ +'$end', +'error', +'LBRACK', +'QTEXT', +'RBRACK', +'LBRACE', +'RBRACE', +'SYMBOL', +'FARROW', +'COMMA', +'TRUE', +'FALSE', +'EQUALS', +'QMARK', +'LPAREN', +'RPAREN', +'ISEQUAL', +'GREATEREQUAL', +'GREATERTHAN', +'LESSTHAN', +'LESSEQUAL', +'NOTEQUAL', +'IF', +'ELSE', +'IMPORT', +'DEFINE', +'ELSIF', +'VARIABLE', +'CLASS', +'INHERITS', +'NODE', +'BOOLEAN', +'DOT', +'COLON', +'TYPE', +'NAME', +'SEMIC', +'$start', +'program', +'statements', +'statement', +'object', +'assignment', +'selector', +'iftest', +'import', +'definition', +'hostclass', +'name', +'objectinstances', +'endsemi', +'params', +'endcomma', +'type', +'objectinst', +'objectname', +'quotedtext', +'variable', +'array', +'rvalue', +'param', +'rvalues', +'comma', +'boolean', +'objectref', +'test', +'elsifs', +'testop', +'svalues', +'selectval', +'sintvalues', +'selectlhand', +'argumentlist', +'parent', +'nothing', +'arguments', +'argument'] + +Racc_debug_parser = false + +##### racc system variables end ##### + + # reduce 0 omitted + +module_eval <<'.,.,', 'grammar.ra', 40 + def _reduce_1( val, _values, result ) + if val[0].is_a?(AST::ASTArray) + result = val[0] + else + result = AST::ASTArray.new( + :line => @lexer.line, + :children => [val[0]] + ) + end + + # this is mainly so we can test the parser separately from the + # interpreter + if Puppet[:parseonly] + begin + if Puppet[:debug] + puts result.tree(0) + end + rescue NoMethodError => detail + Puppet.err detail + #exit(78) + end + #require 'puppet/parser/interpreter' + #result = Puppet::Server.new(result) + end + result + end +.,., + + # reduce 2 omitted + +module_eval <<'.,.,', 'grammar.ra', 54 + def _reduce_3( val, _values, result ) + if val[0].is_a?(AST::ASTArray) + val[0].push(val[1]) + result = val[0] + else + result = AST::ASTArray.new( + :file => @lexer.file, + :line => @lexer.line, + :children => [val[0],val[1]] + ) + end + result + end +.,., + + # reduce 4 omitted + + # reduce 5 omitted + + # reduce 6 omitted + + # reduce 7 omitted + + # reduce 8 omitted + + # reduce 9 omitted + + # reduce 10 omitted + +module_eval <<'.,.,', 'grammar.ra', 89 + def _reduce_11( val, _values, result ) + ary = val[2] + if val[0].is_a?(AST::ASTArray) + Puppet.notice "invalid name" + raise Puppet::ParseError, "Invalid name" + end + if ary[0].is_a?(AST::Leaf) + ary = [ary] + end + result = AST::ASTArray.new( + :line => @lexer.line, + :file => @lexer.file + ) + ary.each { |instance| + Puppet.debug "Adding %s with name %s" % [val[0].value, instance[0].value] + result.push AST::ObjectDef.new( + :pin => "{}", + :line => @lexer.line, + :file => @lexer.file, + :type => val[0], + :name => instance[0], + :params => instance[1] + ) + } + result + end +.,., + +module_eval <<'.,.,', 'grammar.ra', 113 + def _reduce_12( val, _values, result ) + if val[0].is_a?(AST::ASTArray) + Puppet.notice "invalid name" + raise Puppet::ParseError, "Invalid name" + end + # an object but without a name + # this cannot be an instance of a library type + + Puppet.debug "Adding %s" % val[0].value + # make a unique name for bookkeeping purposes + name = AST::Name.new( + :line => @lexer.line, + :file => @lexer.file, + :value => [val[0].value, "-", val[0].object_id].join('') + ) + + result = AST::ObjectDef.new( + :pin => "{}", + :line => @lexer.line, + :file => @lexer.file, + :type => val[0], + :name => name, + :params => val[2] + ) + result + end +.,., + +module_eval <<'.,.,', 'grammar.ra', 126 + def _reduce_13( val, _values, result ) + # a template setting for a type + if val[0].is_a?(AST::ASTArray) + Puppet.notice "invalid type" + raise Puppet::ParseError, "Invalid type" + end + result = AST::TypeDefaults.new( + :pin => "{}", + :line => @lexer.line, + :file => @lexer.file, + :type => val[0], + :params => val[2] + ) + result + end +.,., + +module_eval <<'.,.,', 'grammar.ra', 134 + def _reduce_14( val, _values, result ) + result = AST::ASTArray.new( + :line => @lexer.line, + :file => @lexer.file, + :children => [val[0],val[2]] + ) + result + end +.,., + + # reduce 15 omitted + +module_eval <<'.,.,', 'grammar.ra', 148 + def _reduce_16( val, _values, result ) + if val[0][0].is_a?(AST::ASTArray) + val[0].push val[2] + result = val[0] + else + result = AST::ASTArray.new( + :line => @lexer.line, + :file => @lexer.file, + :children => [val[0],val[2]] + ) + end + result + end +.,., + + # reduce 17 omitted + + # reduce 18 omitted + +module_eval <<'.,.,', 'grammar.ra', 159 + def _reduce_19( val, _values, result ) + result = AST::Name.new( + :line => @lexer.line, + :file => @lexer.file, + :value => val[0] + ) + result + end +.,., + +module_eval <<'.,.,', 'grammar.ra', 167 + def _reduce_20( val, _values, result ) + result = AST::Type.new( + :line => @lexer.line, + :file => @lexer.file, + :value => val[0] + ) + result + end +.,., + + # reduce 21 omitted + + # reduce 22 omitted + + # reduce 23 omitted + + # reduce 24 omitted + + # reduce 25 omitted + +module_eval <<'.,.,', 'grammar.ra', 189 + def _reduce_26( val, _values, result ) + # this is distinct from referencing a variable + variable = AST::Name.new( + :line => @lexer.line, + :file => @lexer.file, + :value => val[0].sub(/^\$/,'') + ) + result = AST::VarDef.new( + :pin => "=", + :line => @lexer.line, + :file => @lexer.file, + :name => variable, + :value => val[2] + ) + result + end +.,., + +module_eval <<'.,.,', 'grammar.ra', 198 + def _reduce_27( val, _values, result ) + result = AST::ASTArray.new( + :line => @lexer.line, + :file => @lexer.file, + :children => [] + ) + result + end +.,., + +module_eval <<'.,.,', 'grammar.ra', 198 + def _reduce_28( val, _values, result ) + result = val[0] + result + end +.,., + +module_eval <<'.,.,', 'grammar.ra', 211 + def _reduce_29( val, _values, result ) + if val[0].is_a?(AST::ASTArray) + val[0].push(val[2]) + result = val[0] + else + result = AST::ASTArray.new( + :line => @lexer.line, + :file => @lexer.file, + :children => [val[0],val[2]] + ) + end + result + end +.,., + +module_eval <<'.,.,', 'grammar.ra', 226 + def _reduce_30( val, _values, result ) + leaf = AST::String.new( + :line => @lexer.line, + :file => @lexer.file, + :value => val[0] + ) + result = AST::ObjectParam.new( + :pin => "=>", + :line => @lexer.line, + :file => @lexer.file, + :param => leaf, + :value => val[2] + ) + result + end +.,., + + # reduce 31 omitted + +module_eval <<'.,.,', 'grammar.ra', 239 + def _reduce_32( val, _values, result ) + if val[0].is_a?(AST::ASTArray) + result = val[0].push(val[2]) + else + result = AST::ASTArray.new( + :line => @lexer.line, + :file => @lexer.file, + :children => [val[0],val[2]] + ) + end + result + end +.,., + + # reduce 33 omitted + + # reduce 34 omitted + + # reduce 35 omitted + + # reduce 36 omitted + + # reduce 37 omitted + + # reduce 38 omitted + + # reduce 39 omitted + + # reduce 40 omitted + + # reduce 41 omitted + +module_eval <<'.,.,', 'grammar.ra', 257 + def _reduce_42( val, _values, result ) + result = AST::String.new( + :line => @lexer.line, + :file => @lexer.file, + :value => val[0] + ) + result + end +.,., + +module_eval <<'.,.,', 'grammar.ra', 265 + def _reduce_43( val, _values, result ) + result = AST::Boolean.new( + :line => @lexer.line, + :file => @lexer.file, + :value => val[0] + ) + result + end +.,., + +module_eval <<'.,.,', 'grammar.ra', 275 + def _reduce_44( val, _values, result ) + result = AST::ObjectRef.new( + :pin => '[]', + :line => @lexer.line, + :file => @lexer.file, + :type => val[0], + :name => val[2] + ) + result + end +.,., + +module_eval <<'.,.,', 'grammar.ra', 285 + def _reduce_45( val, _values, result ) + result = AST::If.new( + :pin => "if", + :test => val[1], + :file => @lexer.file, + :line => @lexer.line, + :statements => val[3] + ) + result + end +.,., + +module_eval <<'.,.,', 'grammar.ra', 307 + def _reduce_46( val, _values, result ) + # make sure our elsifs are an array, as it will save lots of + # effort later + unless val[5].is_a?(AST::ASTArray) + val[5] = AST::ASTArray.new( + :line => @lexer.line, + :file => @lexer.file, + :children => [val[5]] + ) + end + + result = AST::If.new( + :pin => "if", + :test => val[1], + :statements => val[3], + :file => @lexer.file, + :line => @lexer.line, + :else => val[8], + :elsif => val[5] + ) + result + end +.,., + +module_eval <<'.,.,', 'grammar.ra', 318 + def _reduce_47( val, _values, result ) + result = AST::If.new( + :pin => "if", + :test => val[1], + :statements => val[3], + :file => @lexer.file, + :line => @lexer.line, + :else => val[7] + ) + result + end +.,., + +module_eval <<'.,.,', 'grammar.ra', 328 + def _reduce_48( val, _values, result ) + result = AST::If.new( + :pin => "elseif", + :test => val[1], + :file => @lexer.file, + :statements => val[3], + :line => @lexer.line + ) + result + end +.,., + +module_eval <<'.,.,', 'grammar.ra', 348 + def _reduce_49( val, _values, result ) + second = AST::If.new( + :pin => "elsif", + :test => val[2], + :statements => val[4], + :file => @lexer.file, + :line => @lexer.line + ) + + if val[0].is_a?(AST::ASTArray) + val[0].push(second) + result = val[0] + else + result = AST::ASTArray.new( + :line => @lexer.line, + :file => @lexer.file, + :children => [val[0],second] + ) + end + result + end +.,., + + # reduce 50 omitted + +module_eval <<'.,.,', 'grammar.ra', 359 + def _reduce_51( val, _values, result ) + result = AST::Test.new( + :pin => val[1], + :line => @lexer.line, + :file => @lexer.file, + :lhs => val[0], + :rhs => val[2] + ) + result + end +.,., + + # reduce 52 omitted + + # reduce 53 omitted + + # reduce 54 omitted + + # reduce 55 omitted + + # reduce 56 omitted + + # reduce 57 omitted + +module_eval <<'.,.,', 'grammar.ra', 376 + def _reduce_58( val, _values, result ) + result = AST::Selector.new( + :pin => "?", + :line => @lexer.line, + :file => @lexer.file, + :param => val[0], + :value => val[2] + ) + result + end +.,., + + # reduce 59 omitted + +module_eval <<'.,.,', 'grammar.ra', 378 + def _reduce_60( val, _values, result ) + result = val[1] + result + end +.,., + + # reduce 61 omitted + +module_eval <<'.,.,', 'grammar.ra', 393 + def _reduce_62( val, _values, result ) + if val[0].is_a?(AST::ASTArray) + val[0].push(val[2]) + result = val[0] + else + result = AST::ASTArray.new( + :line => @lexer.line, + :file => @lexer.file, + :children => [val[0],val[2]] + ) + end + result + end +.,., + +module_eval <<'.,.,', 'grammar.ra', 408 + def _reduce_63( val, _values, result ) + leaf = AST::String.new( + :line => @lexer.line, + :file => @lexer.file, + :value => val[0] + ) + result = AST::ObjectParam.new( + :pin => "=>", + :line => @lexer.line, + :file => @lexer.file, + :param => leaf, + :value => val[2] + ) + result + end +.,., + + # reduce 64 omitted + + # reduce 65 omitted + + # reduce 66 omitted + +module_eval <<'.,.,', 'grammar.ra', 437 + def _reduce_67( val, _values, result ) + # importing files + # yuk, i hate keywords + # we'll probably have to have some kind of search path eventually + # but for now, just use a path relative to the file doing the importing + path = @lexer.file.sub(%r{[^/]+$},val[1]) + parser = Puppet::Parser::Parser.new() + parser.stack = self.stack + Puppet.debug("importing %s" % path) + noimport = false + begin + parser.file = path + rescue Puppet::ImportError + Puppet.warning("Importing %s would result in an import loop" % path) + result = AST::ASTArray.new( + :file => @lexer.file, + :line => @lexer.line + ) + noimport = true + end + unless noimport + result = parser.parse + end + result + end +.,., + +module_eval <<'.,.,', 'grammar.ra', 447 + def _reduce_68( val, _values, result ) + result = AST::CompDef.new( + :name => AST::Name.new(:value => val[1], :line => @lexer.line), + :args => val[2], + :file => @lexer.file, + :line => @lexer.line, + :code => val[4] + ) + result + end +.,., + +module_eval <<'.,.,', 'grammar.ra', 458 + def _reduce_69( val, _values, result ) + result = AST::ClassDef.new( + :name => AST::Name.new(:value => val[1], :line => @lexer.line), + :args => val[2], + :parentclass => val[3], + :file => @lexer.file, + :line => @lexer.line, + :code => val[5] + ) + result + end +.,., + +module_eval <<'.,.,', 'grammar.ra', 473 + def _reduce_70( val, _values, result ) + result = AST::ASTArray.new( + :line => @lexer.line, + :file => @lexer.file, + :children => [] + ) + result + end +.,., + + # reduce 71 omitted + +module_eval <<'.,.,', 'grammar.ra', 478 + def _reduce_72( val, _values, result ) + result = val[1] + result + end +.,., + +module_eval <<'.,.,', 'grammar.ra', 489 + def _reduce_73( val, _values, result ) + if val[1].is_a?(AST::ASTArray) + result = val[1] + else + result = AST::ASTArray.new( + :line => @lexer.line, + :file => @lexer.file, + :children => [val[0]] + ) + end + result + end +.,., + + # reduce 74 omitted + +module_eval <<'.,.,', 'grammar.ra', 503 + def _reduce_75( val, _values, result ) + if val[0].is_a?(AST::ASTArray) + val[0].push(val[2]) + result = val[0] + else + result = AST::ASTArray.new( + :line => @lexer.line, + :file => @lexer.file, + :children => [val[0],val[2]] + ) + end + result + end +.,., + +module_eval <<'.,.,', 'grammar.ra', 511 + def _reduce_76( val, _values, result ) + result = AST::ASTArray.new( + :line => @lexer.line, + :file => @lexer.file, + :children => [val[0],val[2]] + ) + result + end +.,., + +module_eval <<'.,.,', 'grammar.ra', 518 + def _reduce_77( val, _values, result ) + result = AST::ASTArray.new( + :line => @lexer.line, + :file => @lexer.file, + :children => [val[0]] + ) + result + end +.,., + + # reduce 78 omitted + +module_eval <<'.,.,', 'grammar.ra', 527 + def _reduce_79( val, _values, result ) + result = AST::Name.new( + :value => val[1], + :file => @lexer.file, + :line => @lexer.line + ) + result + end +.,., + +module_eval <<'.,.,', 'grammar.ra', 536 + def _reduce_80( val, _values, result ) + name = val[0].sub(/^\$/,'') + result = AST::Variable.new( + :line => @lexer.line, + :file => @lexer.file, + :value => name + ) + result + end +.,., + +module_eval <<'.,.,', 'grammar.ra', 545 + def _reduce_81( val, _values, result ) + if val[1].is_a?(AST::ASTArray) + result = val[1] + else + result = AST::ASTArray.new + result.push val[1] + end + result + end +.,., + + # reduce 82 omitted + + # reduce 83 omitted + + # reduce 84 omitted + +module_eval <<'.,.,', 'grammar.ra', 550 + def _reduce_85( val, _values, result ) + result = nil + result + end +.,., + + def _reduce_none( val, _values, result ) + result + end + + end # class Parser + + end # module Parser + +end # module Puppet diff --git a/lib/puppet/parser/scope.rb b/lib/puppet/parser/scope.rb new file mode 100644 index 000000000..dcefdbcae --- /dev/null +++ b/lib/puppet/parser/scope.rb @@ -0,0 +1,427 @@ +#!/usr/local/bin/ruby -w + +# $Id$ + +# the interpreter +# +# this builds our virtual pinball machine, into which we'll place our host-specific +# information and out of which we'll receive our host-specific configuration + +require 'puppet/transportable' + +module Puppet + module Parser + class ScopeError < RuntimeError + attr_accessor :line, :file + end + #--------------------------------------------------------------- + class Scope + + attr_accessor :symtable, :objectable, :parent, :level, :interp + attr_accessor :name, :type + + # i don't really know how to deal with a global scope yet, so + # i'm leaving it disabled + @@global = nil + + @@hosttable = {} + @@settingtable = [] + @@declarative = true + + #------------------------------------------------------------ + def Scope.declarative + return @@declarative + end + #------------------------------------------------------------ + + #------------------------------------------------------------ + def Scope.declarative=(val) + @@declarative = val + end + #------------------------------------------------------------ + + #------------------------------------------------------------ + def Scope.global + return @@global + end + #------------------------------------------------------------ + + #------------------------------------------------------------ + def child=(scope) + @children.push(scope) + end + #------------------------------------------------------------ + + #------------------------------------------------------------ + def declarative + return @@declarative + end + #------------------------------------------------------------ + + #------------------------------------------------------------ + def initialize(parent = nil, declarative = true) + @parent = parent + if @parent.nil? + @level = 1 + @@declarative = declarative + else + @parent.child = self + @level = @parent.level + 1 + @interp = @parent.interp + end + + @children = [] + + @symtable = Hash.new(nil) + @typetable = Hash.new(nil) + + # the defaultstable is a hash of hashes + @defaultstable = Hash.new { |dhash,type| + dhash[type] = Hash.new(nil) + } + + @objectable = Hash.new { |typehash,typekey| + #hash[key] = TransObject.new(key) + typehash[typekey] = Hash.new { |namehash, namekey| + #Puppet.debug("Creating iobject with name %s and type %s" % + # [namekey,typekey]) + namehash[namekey] = TransObject.new(namekey,typekey) + @children.push namehash[namekey] + + # this has to be last, because the return value of the + # block is the actual hash + namehash[namekey] + } + } + @map = { + "variable" => @symtable, + "type" => @typetable, + "object" => @objectable, + "defaults" => @defaultstable + } + end + #------------------------------------------------------------ + + #------------------------------------------------------------ + # this method just abstracts the upwards-recursive nature of + # name resolution + # because different tables are different depths (e.g., flat, or + # hash of hashes), we pass in a code snippet that gets passed + # the table. It is assumed that the code snippet already has + # the name in it + def lookup(type,sub) + table = @map[type] + if table.nil? + error = Puppet::ParseError.new( + "Could not retrieve %s table at level %s" % [type,self.level] + ) + error.stack = caller + raise error + end + + if sub.is_a?(Proc) and obj = sub.call(table) + return obj + elsif table.include?(sub) + return table[sub] + elsif ! @parent.nil? + return @parent.lookup(type,sub) + else + return :undefined + end + end + #------------------------------------------------------------ + + #------------------------------------------------------------ + def lookuphost(name) + if @@hosttable.include?(name) + return @@hosttable[name] + else + return nil + end + end + #------------------------------------------------------------ + + #------------------------------------------------------------ + # collect all of the defaults set at any higher scopes + # this is a different type of lookup because it's additive -- + # it collects all of the defaults, with defaults in closer scopes + # overriding those in later scopes + def lookupdefaults(type) + values = {} + # first collect the values from the parents + unless @parent.nil? + @parent.lookupdefaults(type).each { |var,value| + values[var] = value + } + end + + # then override them with any current values + # this should probably be done differently + if @defaultstable.include?(type) + @defaultstable[type].each { |var,value| + values[var] = value + } + end + Puppet.debug "Got defaults for %s: %s" % + [type,values.inspect] + return values + end + #------------------------------------------------------------ + + #------------------------------------------------------------ + def lookuptype(name) + Puppet.debug "Looking up type %s" % name + value = self.lookup("type",name) + if value == :undefined + return nil + else + return value + end + end + #------------------------------------------------------------ + + #------------------------------------------------------------ + # slightly different, because we're looking through a hash of hashes + def lookupobject(name,type) + Puppet.debug "Looking up object %s of type %s" % [name, type] + sub = proc { |table| + if table.include?(type) + if type[type].include?(name) + type[type][name] + end + else + nil + end + } + value = self.lookup("object",sub) + if value == :undefined + return nil + else + return value + end + end + #------------------------------------------------------------ + + #------------------------------------------------------------ + def lookupvar(name) + Puppet.debug "Looking up variable %s" % name + value = self.lookup("variable", name) + if value == :undefined + error = Puppet::ParseError.new( + "Undefined variable '%s'" % name + ) + error.stack = caller + raise error + else + #Puppet.debug "Value of '%s' is '%s'" % [name,value] + return value + end + end + #------------------------------------------------------------ + + #------------------------------------------------------------ + def newscope + Puppet.debug "Creating new scope, level %s" % [self.level + 1] + return Puppet::Parser::Scope.new(self) + end + #------------------------------------------------------------ + + #------------------------------------------------------------ + def setdefaults(type,params) + table = @defaultstable[type] + + # if we got a single param, it'll be in its own array + unless params[0].is_a?(Array) + params = [params] + end + + params.each { |ary| + Puppet.debug "Default for %s is %s => %s" % + [type,ary[0].inspect,ary[1].inspect] + if @@declarative + if table.include?(ary[0]) + error = Puppet::ParseError.new( + "Default already defined for %s { %s }" % + [type,ary[0]] + ) + error.stack = caller + raise error + end + else + if table.include?(ary[0]) + # we should maybe allow this warning to be turned off... + Puppet.warning "Replacing default for %s { %s }" % + [type,ary[0]] + end + end + table[ary[0]] = ary[1] + } + end + #------------------------------------------------------------ + + #------------------------------------------------------------ + def sethost(name,host) + @@hosttable[name] = host + end + #------------------------------------------------------------ + + #------------------------------------------------------------ + def settype(name,ltype) + @typetable[name] = ltype + end + #------------------------------------------------------------ + + #------------------------------------------------------------ + # when we have an 'eval' function, we should do that instead + # for now, we only support variables in strings + def strinterp(string) + newstring = string.dup + regex = Regexp.new('\$\{(\w+)\}|\$(\w+)') + #Puppet.debug("interpreting '%s'" % string) + while match = regex.match(newstring) do + if match[1] + newstring.sub!(regex,self.lookupvar(match[1])) + elsif match[2] + newstring.sub!(regex,self.lookupvar(match[2])) + else + raise 'fuh?' + end + end + #Puppet.debug("result is '%s'" % newstring) + return newstring + end + #------------------------------------------------------------ + + #------------------------------------------------------------ + # this is kind of quirky, because it doesn't differentiate between + # creating a new object and adding params to an existing object + # it doesn't solve the real problem, though: cases like file recursion, + # where one statement explicitly modifies an object, and another + # statement modifies it because of recursion + def setobject(type, name, params, file, line) + obj = self.lookupobject(name,type) + if obj == :undefined or obj.nil? + obj = @objectable[type][name] + + # only set these if we've created the object, which is the + # most common case + obj.file = file + obj.line = line + end + + # now add the params to whatever object we've found, whether + # it was in a higher scope or we just created it + # it will not be obvious where these parameters are from, that is, + # which file they're in or whatever + params.each { |var,value| + obj[var] = value + } + return obj + end + #------------------------------------------------------------ + + #------------------------------------------------------------ + def setvar(name,value) + Puppet.debug "Setting '%s' to '%s' at level %s" % + [name.inspect,value,self.level] + if @@declarative and @symtable.include?(name) + error = Puppet::ParseError.new( + "Cannot reassign variable %s" % name + ) + error.stack = caller + raise error + else + if @symtable.include?(name) + Puppet.warning "Reassigning %s to %s" % [name,value] + end + @symtable[name] = value + end + end + #------------------------------------------------------------ + + #------------------------------------------------------------ + # I'm pretty sure this method could be obviated, but it doesn't + # really seem worth it + def to_trans + Puppet.debug "Translating scope %s at level %s" % + [self.object_id,self.level] + + results = [] + + @children.each { |child| + if child.is_a?(Scope) + cresult = child.to_trans + Puppet.debug "Got %s from scope %s" % + [cresult.class,child.object_id] + + # get rid of the arrayness + unless cresult.is_a?(TransBucket) + cresult.each { |result| + results.push(result) + } + else + results.push(cresult) + end + elsif child.is_a?(TransObject) + results.push(child) + else + error = Puppet::DevError.new( + "Puppet::Parse::Scope cannot handle objects of type %s" % + child.class + ) + error.stack = caller + raise error + end + } + results = results.reject { |child| + # if a scope didn't result in any objects, we get some nils + # just get rid of them + 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 defined? @name + bucket = TransBucket.new + bucket.name = @name + + # it'd be nice not to have to do this... + results.each { |result| + #Puppet.debug "Result type is %s" % result.class + bucket.push(result) + } + if defined? @type + bucket.type = @type + else + error = Puppet::ParseError.new( + "No type for scope %s" % @name + ) + error.stack = caller + raise error + 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 "nameless scope; just returning a list" + return results + end + end + #------------------------------------------------------------ + end + #--------------------------------------------------------------- + end +end |
