# vim: syntax=ruby # the parser class Puppet::Parser::Parser token LBRACK DQTEXT SQTEXT RBRACK LBRACE RBRACE SYMBOL FARROW COMMA TRUE token FALSE EQUALS LESSEQUAL NOTEQUAL DOT COLON LLCOLLECT RRCOLLECT token QMARK LPAREN RPAREN ISEQUAL GREATEREQUAL GREATERTHAN LESSTHAN token IF ELSE IMPORT DEFINE ELSIF VARIABLE CLASS INHERITS NODE BOOLEAN token NAME SEMIC CASE DEFAULT AT LCOLLECT RCOLLECT CLASSNAME CLASSREF token NOT OR AND UNDEF PARROW rule program: statements { if val[0] # Make sure we always return an array. if val[0].is_a?(AST::ASTArray) if val[0].children.empty? result = nil else result = val[0] end else result = aryfy(val[0]) end else result = nil end } | nil statements: statement | statements statement { if val[0] and val[1] if val[0].instance_of?(AST::ASTArray) val[0].push(val[1]) result = val[0] else result = ast AST::ASTArray, :children => [val[0],val[1]] end elsif obj = (val[0] || val[1]) result = obj else result = nil end } # The main list of valid statements statement: resource | virtualresource | collection | assignment | casestatement | ifstatement | import | fstatement | definition | hostclass | nodedef | resourceoverride fstatement: NAME LPAREN funcvalues RPAREN { args = aryfy(val[2]) result = ast AST::Function, :name => val[0], :arguments => args, :ftype => :statement } | NAME LPAREN RPAREN { result = ast AST::Function, :name => val[0], :arguments => AST::ASTArray.new({}), :ftype => :statement } | NAME funcvalues { args = aryfy(val[1]) result = ast AST::Function, :name => val[0], :arguments => args, :ftype => :statement } funcvalues: namestrings | resourcerefs namestrings: namestring | namestrings COMMA namestring { result = aryfy(val[0], val[2]) result.line = @lexer.line result.file = @lexer.file } # This is *almost* an rvalue, but I couldn't get a full # rvalue to work without scads of shift/reduce conflicts. namestring: name | variable | type | boolean | funcrvalue | selector | quotedtext | CLASSNAME { result = ast AST::Name, :value => val[0] } resourcerefs: resourceref | resourcerefs COMMA resourceref { unless val[0].is_a?(AST::ASTArray) val[0] = aryfy(val[0]) end val[0].push(val[2]) result = val[0] } resource: classname LBRACE resourceinstances endsemi RBRACE { array = val[2] if array.instance_of?(AST::ResourceInstance) array = [array] end result = ast AST::ASTArray # this iterates across each specified resourceinstance array.each { |instance| unless instance.instance_of?(AST::ResourceInstance) raise Puppet::Dev, "Got something that isn't an instance" end # now, i need to somehow differentiate between those things with # arrays in their names, and normal things result.push ast(AST::Resource, :type => val[0], :title => instance[0], :params => instance[1]) } } | classname LBRACE params endcomma RBRACE { # This is a deprecated syntax. error "All resource specifications require names" } | classref LBRACE params endcomma RBRACE { # a defaults setting for a type result = ast(AST::ResourceDefaults, :type => val[0], :params => val[2]) } # Override a value set elsewhere in the configuration. resourceoverride: resourceref LBRACE anyparams endcomma RBRACE { result = ast AST::ResourceOverride, :object => val[0], :params => val[2] } # Exported and virtual resources; these don't get sent to the client # unless they get collected elsewhere in the db. virtualresource: at resource { type = val[0] if (type == :exported and ! Puppet[:storeconfigs]) and ! Puppet[:parseonly] Puppet.warning addcontext("You cannot collect without storeconfigs being set") end if val[1].is_a? AST::ResourceDefaults error "Defaults are not virtualizable" end method = type.to_s + "=" # Just mark our resources as exported and pass them through. if val[1].instance_of?(AST::ASTArray) val[1].each do |obj| obj.send(method, true) end else val[1].send(method, true) end result = val[1] } at: AT { result = :virtual } | AT AT { result = :exported } # A collection statement. Currently supports no arguments at all, but eventually # will, I assume. collection: classref collectrhand { if val[0] =~ /^[a-z]/ Puppet.warning addcontext("Collection names must now be capitalized") end type = val[0].downcase args = {:type => type} if val[1].is_a?(AST::CollExpr) args[:query] = val[1] args[:query].type = type args[:form] = args[:query].form else args[:form] = val[1] end if args[:form] == :exported and ! Puppet[:storeconfigs] and ! Puppet[:parseonly] Puppet.warning addcontext("You cannot collect exported resources without storeconfigs being set; the collection will be ignored") end result = ast AST::Collection, args } collectrhand: LCOLLECT collstatements RCOLLECT { if val[1] result = val[1] result.form = :virtual else result = :virtual end } | LLCOLLECT collstatements RRCOLLECT { if val[1] result = val[1] result.form = :exported else result = :exported end } # A mini-language for handling collection comparisons. This is organized # to avoid the need for precedence indications. collstatements: nil | collstatement | collstatements colljoin collstatement { result = ast AST::CollExpr, :test1 => val[0], :oper => val[1], :test2 => val[2] } collstatement: collexpr | LPAREN collstatements RPAREN { result = val[1] result.parens = true } colljoin: AND | OR collexpr: colllval ISEQUAL simplervalue { result = ast AST::CollExpr, :test1 => val[0], :oper => val[1], :test2 => val[2] #result = ast AST::CollExpr #result.push *val } | colllval NOTEQUAL simplervalue { result = ast AST::CollExpr, :test1 => val[0], :oper => val[1], :test2 => val[2] #result = ast AST::CollExpr #result.push *val } colllval: variable | name resourceinst: resourcename COLON params endcomma { result = ast AST::ResourceInstance, :children => [val[0],val[2]] } resourceinstances: resourceinst | resourceinstances SEMIC resourceinst { if val[0].instance_of?(AST::ResourceInstance) result = ast AST::ASTArray, :children => [val[0],val[2]] else val[0].push val[2] result = val[0] end } endsemi: # nothing | SEMIC undef: UNDEF { result = ast AST::Undef, :value => :undef } name: NAME { result = ast AST::Name, :value => val[0] } type: CLASSREF { result = ast AST::Type, :value => val[0] } resourcename: quotedtext | name | type | selector | variable | array assignment: VARIABLE EQUALS rvalue { if val[0] =~ /::/ raise Puppet::ParseError, "Cannot assign to variables in other namespaces" end # this is distinct from referencing a variable variable = ast AST::Name, :value => val[0] result = ast AST::VarDef, :name => variable, :value => val[2] } params: # nothing { result = ast AST::ASTArray } | param { result = val[0] } | params COMMA param { if val[0].instance_of?(AST::ASTArray) val[0].push(val[2]) result = val[0] else result = ast AST::ASTArray, :children => [val[0],val[2]] end } param: NAME FARROW rvalue { result = ast AST::ResourceParam, :param => val[0], :value => val[2] } addparam: NAME PARROW rvalue { result = ast AST::ResourceParam, :param => val[0], :value => val[2], :add => true } anyparam: param | addparam anyparams: # nothing { result = ast AST::ASTArray } | anyparam { result = val[0] } | anyparams COMMA anyparam { if val[0].instance_of?(AST::ASTArray) val[0].push(val[2]) result = val[0] else result = ast AST::ASTArray, :children => [val[0],val[2]] end } rvalues: rvalue | rvalues comma rvalue { if val[0].instance_of?(AST::ASTArray) result = val[0].push(val[2]) else result = ast AST::ASTArray, :children => [val[0],val[2]] end } simplervalue: quotedtext | name | type | boolean | selector | variable rvalue: quotedtext | name | type | boolean | selector | variable | array | resourceref | funcrvalue | undef # We currently require arguments in these functions. funcrvalue: NAME LPAREN funcvalues RPAREN { args = aryfy(val[2]) result = ast AST::Function, :name => val[0], :arguments => args, :ftype => :rvalue } | NAME LPAREN RPAREN { result = ast AST::Function, :name => val[0], :arguments => AST::ASTArray.new({}), :ftype => :rvalue } quotedtext: DQTEXT { result = ast AST::String, :value => val[0] } | SQTEXT { result = ast AST::FlatString, :value => val[0] } boolean: BOOLEAN { result = ast AST::Boolean, :value => val[0] } resourceref: NAME LBRACK rvalue RBRACK { Puppet.warning addcontext("Deprecation notice: Resource references should now be capitalized") result = ast AST::ResourceReference, :type => val[0], :title => val[2] } | classref LBRACK rvalue RBRACK { result = ast AST::ResourceReference, :type => val[0], :title => val[2] } ifstatement: IF iftest LBRACE statements RBRACE else { args = { :test => val[1], :statements => val[3] } if val[5] args[:else] = val[5] end result = ast AST::IfStatement, args } else: # nothing | ELSE LBRACE statements RBRACE { result = ast AST::Else, :statements => val[2] } # Currently we only support a single value, but eventually one assumes # we'll support operators and such. iftest: rvalue casestatement: CASE rvalue LBRACE caseopts RBRACE { options = val[3] unless options.instance_of?(AST::ASTArray) options = ast AST::ASTArray, :children => [val[3]] end result = ast AST::CaseStatement, :test => val[1], :options => options } caseopts: caseopt | caseopts caseopt { if val[0].instance_of?(AST::ASTArray) val[0].push val[1] result = val[0] else result = ast AST::ASTArray, :children => [val[0], val[1]] end } caseopt: casevalues COLON LBRACE statements RBRACE { result = ast AST::CaseOpt, :value => val[0], :statements => val[3] } | casevalues COLON LBRACE RBRACE { result = ast(AST::CaseOpt, :value => val[0], :statements => ast(AST::ASTArray) ) } casevalues: selectlhand | casevalues COMMA selectlhand { if val[0].instance_of?(AST::ASTArray) val[0].push(val[2]) result = val[0] else result = ast AST::ASTArray, :children => [val[0],val[2]] end } selector: selectlhand QMARK svalues { result = ast AST::Selector, :param => val[0], :values => val[2] } svalues: selectval | LBRACE sintvalues endcomma RBRACE { result = val[1] } sintvalues: selectval | sintvalues comma selectval { if val[0].instance_of?(AST::ASTArray) val[0].push(val[2]) result = val[0] else result = ast AST::ASTArray, :children => [val[0],val[2]] end } selectval: selectlhand FARROW rvalue { result = ast AST::ResourceParam, :param => val[0], :value => val[2] } selectlhand: name | type | quotedtext | variable | funcrvalue | boolean | undef | DEFAULT { result = ast AST::Default, :value => val[0] } # These are only used for importing, and we don't interpolate there. qtexts: quotedtext { result = [val[0].value] } | qtexts COMMA quotedtext { results = val[0] << val[2].value } import: IMPORT qtexts { val[1].each do |file| import(file) end result = AST::ASTArray.new(:children => []) } # Disable definition inheritance for now. 8/27/06, luke #definition: DEFINE NAME argumentlist parent LBRACE statements RBRACE { definition: DEFINE classname argumentlist LBRACE statements RBRACE { newdefine classname(val[1]), :arguments => val[2], :code => val[4] @lexer.indefine = false result = nil #} | DEFINE NAME argumentlist parent LBRACE RBRACE { } | DEFINE classname argumentlist LBRACE RBRACE { newdefine classname(val[1]), :arguments => val[2] @lexer.indefine = false result = nil } #hostclass: CLASS NAME argumentlist parent LBRACE statements RBRACE { hostclass: CLASS classname classparent LBRACE statements RBRACE { # Our class gets defined in the parent namespace, not our own. @lexer.namepop newclass classname(val[1]), :code => val[4], :parent => val[2] result = nil } | CLASS classname classparent LBRACE RBRACE { # Our class gets defined in the parent namespace, not our own. @lexer.namepop newclass classname(val[1]), :parent => val[2] result = nil } nodedef: NODE hostnames nodeparent LBRACE statements RBRACE { newnode val[1], :parent => val[2], :code => val[4] result = nil } | NODE hostnames nodeparent LBRACE RBRACE { newnode val[1], :parent => val[2] result = nil } classref: CLASSREF classname: NAME | CLASSNAME # Multiple hostnames, as used for node names. These are all literal # strings, not AST objects. hostnames: hostname | hostnames COMMA hostname { result = val[0] result = [result] unless result.is_a?(Array) result << val[2] } hostname: NAME | SQTEXT | DQTEXT | DEFAULT nil: { result = nil } nothing: { result = ast AST::ASTArray, :children => [] } argumentlist: nil | LPAREN nothing RPAREN { result = nil } | LPAREN arguments RPAREN { result = val[1] result = [result] unless result[0].is_a?(Array) } arguments: argument | arguments COMMA argument { result = val[0] result = [result] unless result[0].is_a?(Array) result << val[2] } argument: NAME EQUALS rvalue { Puppet.warning addcontext("Deprecation notice: must now include '$' in prototype") result = [val[0], val[2]] } | NAME { Puppet.warning addcontext("Deprecation notice: must now include '$' in prototype") result = [val[0]] } | VARIABLE EQUALS rvalue { result = [val[0], val[2]] } | VARIABLE { result = [val[0]] } nodeparent: nil | INHERITS hostname { result = val[1] } classparent: nil | INHERITS classnameordefault { result = val[1] } classnameordefault: classname | DEFAULT variable: VARIABLE { result = ast AST::Variable, :value => val[0] } array: LBRACK rvalues RBRACK { if val[1].instance_of?(AST::ASTArray) result = val[1] else result = ast AST::ASTArray, :children => [val[1]] end } | LBRACK RBRACK { result = ast AST::ASTArray } comma: FARROW | COMMA endcomma: # nothing | COMMA { result = nil } end ---- header ---- require 'puppet' require 'puppet/util/loadedfile' require 'puppet/parser/lexer' require 'puppet/parser/ast' #require 'puppet/parser/interpreter' module Puppet class ParseError < Puppet::Error; end class ImportError < Racc::ParseError; end class AlreadyImportedError < ImportError; end end Puppet[:typecheck] = true Puppet[:paramcheck] = true ---- inner ---- # It got too annoying having code in a file that needs to be compiled. require 'puppet/parser/parser_support' # Make emacs happy # Local Variables: # mode: ruby # End: # $Id$