# vim: syntax=ruby

# the parser

class Puppet::Parser::Parser

token STRING DQPRE DQMID DQPOST token LBRACK RBRACK LBRACE RBRACE SYMBOL FARROW COMMA TRUE token FALSE EQUALS APPENDS 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 CLASSREF token NOT OR AND UNDEF PARROW PLUS MINUS TIMES DIV LSHIFT RSHIFT UMINUS token MATCH NOMATCH REGEX IN_EDGE OUT_EDGE IN_EDGE_SUB OUT_EDGE_SUB token IN UNLESS

prechigh

right NOT
nonassoc UMINUS
left  IN MATCH NOMATCH
left  TIMES DIV
left  MINUS PLUS
left  LSHIFT RSHIFT
left  NOTEQUAL ISEQUAL
left  GREATEREQUAL GREATERTHAN LESSTHAN LESSEQUAL
left  AND
left  OR

preclow

rule program: statements_and_declarations

| nil

statements_and_declarations:   statement_or_declaration {
  result = ast AST::ASTArray, :children => (val[0] ? [val[0]] : [])
}
| statements_and_declarations statement_or_declaration {
  if val[1]
    val[0].push(val[1])
  end
  result = val[0]
}

# statements is like statements_and_declarations, but it doesn’t allow # nested definitions, classes, or nodes. statements: statements_and_declarations {

val[0].each do |stmt|
  if stmt.is_a?(AST::TopLevelConstruct)
    error "Classes, definitions, and nodes may only appear at toplevel or inside other classes",          :line => stmt.context[:line], :file => stmt.context[:file]
  end
end
result = val[0]

}

# The main list of valid statements statement_or_declaration: resource

| virtualresource
| collection
| assignment
| casestatement
| ifstatement_begin
| unlessstatement
| import
| fstatement
| definition
| hostclass
| nodedef
| resourceoverride
| append
| relationship

keyword: AND

| CASE
| CLASS
| DEFAULT
| DEFINE
| ELSE
| ELSIF
| IF
| IN
| IMPORT
| INHERITS
| NODE
| OR
| UNDEF
| UNLESS

relationship: relationship_side edge relationship_side {

result = AST::Relationship.new(val[0], val[2], val[1][:value], ast_context)

}

| relationship edge relationship_side {
  result = AST::Relationship.new(val[0], val[2], val[1][:value], ast_context)

}

relationship_side: resource

| resourceref
| collection
| variable
| quotedtext
| selector
| casestatement
| hasharrayaccesses

edge: IN_EDGE | OUT_EDGE | IN_EDGE_SUB | OUT_EDGE_SUB

fstatement: NAME LPAREN expressions RPAREN {

result = ast AST::Function,
  :name => val[0][:value],
  :line => val[0][:line],
  :arguments => val[2],
  :ftype => :statement

} | NAME LPAREN expressions COMMA RPAREN {

result = ast AST::Function,
  :name => val[0][:value],
  :line => val[0][:line],
  :arguments => val[2],
  :ftype => :statement

} | NAME LPAREN RPAREN {

result = ast AST::Function,
  :name => val[0][:value],
  :line => val[0][:line],
  :arguments => AST::ASTArray.new({}),
  :ftype => :statement

}

| NAME funcvalues {
  result = ast AST::Function,
  :name => val[0][:value],
  :line => val[0][:line],
  :arguments => val[1],
  :ftype => :statement

}

funcvalues: rvalue { result = aryfy(val) } # This rvalue could be an expression

| funcvalues COMMA rvalue {
  val[0].push(val[2])
  result = val[0]

}

expressions: expression { result = aryfy(val) }

| expressions comma expression { result = val[0].push(val[2]) }

rvalue: quotedtext

| name
| type
| boolean
| selector
| variable
| array
| hasharrayaccesses
| resourceref
| funcrvalue
| undef

resource: classname LBRACE resourceinstances endsemi RBRACE {

@lexer.commentpop
result = ast(AST::Resource, :type => val[0], :instances => val[2])

} | classname LBRACE params endcomma RBRACE {

# This is a deprecated syntax.
error "All resource specifications require names"

} | type LBRACE params endcomma RBRACE {

# a defaults setting for a type
@lexer.commentpop
result = ast(AST::ResourceDefaults, :type => val[0].value, :parameters => val[2])

}

# Override a value set elsewhere in the configuration. resourceoverride: resourceref LBRACE anyparams endcomma RBRACE {

@lexer.commentpop
result = ast AST::ResourceOverride, :object => val[0], :parameters => 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])
  Puppet.warning addcontext("You cannot collect without storeconfigs being set")
end

error "Defaults are not virtualizable" if val[1].is_a? AST::ResourceDefaults

method = type.to_s + "="

# Just mark our resource as exported and pass it through.
val[1].send(method, true)

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: type collectrhand LBRACE anyparams endcomma RBRACE {

@lexer.commentpop
type = val[0].value.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]
  Puppet.warning addcontext("You cannot collect exported resources without storeconfigs being set; the collection will be ignored")
end
args[:override] = val[3]
result = ast AST::Collection, args

}

| type collectrhand {
type = val[0].value.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]
  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 { result=val[:value] }

| OR  { result=val[0][:value] }

collexpr: colllval ISEQUAL expression {

result = ast AST::CollExpr, :test1 => val[0], :oper => val[1][:value], :test2 => val[2]
#result = ast AST::CollExpr
#result.push *val

}

| colllval NOTEQUAL expression {
  result = ast AST::CollExpr, :test1 => val[0], :oper => val[1][:value], :test2 => val[2]
  #result = ast AST::CollExpr
  #result.push *val

}

colllval: variable

| name

resourceinst: resourcename COLON params endcomma {

result = ast AST::ResourceInstance, :title => val[0], :parameters => val[2]

}

resourceinstances: resourceinst { result = aryfy(val) }

| resourceinstances SEMIC resourceinst {
  val[0].push val[2]
  result = val[0]

}

endsemi: # nothing

| SEMIC

undef: UNDEF {

result = ast AST::Undef, :value => :undef

}

name: NAME {

result = ast AST::Name, :value => val[0][:value], :line => val[0][:line]

}

type: CLASSREF {

result = ast AST::Type, :value => val[0][:value], :line => val[0][:line]

}

resourcename: quotedtext

| name
| type
| selector
| variable
| array
| hasharrayaccesses

assignment: VARIABLE EQUALS expression {

raise Puppet::ParseError, "Cannot assign to variables in other namespaces" if val[0][:value] =~ /::/
# this is distinct from referencing a variable
variable = ast AST::Name, :value => val[0][:value], :line => val[0][:line]
result = ast AST::VarDef, :name => variable, :value => val[2], :line => val[0][:line]

}

| hasharrayaccess EQUALS expression {
  result = ast AST::VarDef, :name => val[0], :value => val[2]

}

append: VARIABLE APPENDS expression {

variable = ast AST::Name, :value => val[0][:value], :line => val[0][:line]
result = ast AST::VarDef, :name => variable, :value => val[2], :append => true, :line => val[0][:line]

}

params: # nothing {

result = ast AST::ASTArray

}

| param { result = aryfy(val[0]) }
| params COMMA param {
  val[0].push(val[2])
  result = val[0]

}

param_name: NAME

| keyword 
| BOOLEAN

param: param_name FARROW expression {

result = ast AST::ResourceParam, :param => val[0][:value], :line => val[0][:line], :value => val[2]

}

addparam: NAME PARROW expression {

result = ast AST::ResourceParam, :param => val[0][:value], :line => val[0][:line], :value => val[2],
  :add => true

}

anyparam: param

| addparam

anyparams: # nothing {

result = ast AST::ASTArray

}

| anyparam { result = aryfy(val[0]) }
| anyparams COMMA anyparam {
  val[0].push(val[2])
  result = val[0]

}

# We currently require arguments in these functions. funcrvalue: NAME LPAREN expressions RPAREN {

result = ast AST::Function,
  :name => val[0][:value], :line => val[0][:line],
  :arguments => val[2],
  :ftype => :rvalue

} | NAME LPAREN RPAREN {

result = ast AST::Function,
  :name => val[0][:value], :line => val[0][:line],
  :arguments => AST::ASTArray.new({}),
  :ftype => :rvalue

}

quotedtext: STRING { result = ast AST::String, :value => val[:value], :line => val[:line] }

| DQPRE dqrval { result = ast AST::Concat, :value => [ast(AST::String,val[0])]+val[1], :line => val[0][:line] }

dqrval: expression dqtail { result = [val] + val }

dqtail: DQPOST { result = [ast(AST::String,val)] }

| DQMID dqrval  { result = [ast(AST::String,val[0])] + val[1] }

boolean: BOOLEAN {

result = ast AST::Boolean, :value => val[0][:value], :line => val[0][:line]

}

resourceref: NAME LBRACK expressions RBRACK {

Puppet.warning addcontext("Deprecation notice:  Resource references should now be capitalized")
result = ast AST::ResourceReference, :type => val[0][:value], :line => val[0][:line], :title => val[2]

} | type LBRACK expressions RBRACK {

result = ast AST::ResourceReference, :type => val[0].value, :title => val[2]

}

unlessstatement: UNLESS expression LBRACE statements RBRACE {

@lexer.commentpop
args = {
  :test => ast(AST::Not, :value => val[1]),
  :statements => val[3]
}

result = ast AST::IfStatement, args

}

| UNLESS expression LBRACE RBRACE {
  @lexer.commentpop
  args = {
    :test => ast(AST::Not, :value => val[1]), 
    :statements => ast(AST::Nop)
  }
  result = ast AST::IfStatement, args

}

ifstatement_begin: IF ifstatement {

result = val[1]

}

ifstatement: expression LBRACE statements RBRACE else {

@lexer.commentpop
args = {
  :test => val[0],
  :statements => val[2]
}

args[:else] = val[4] if val[4]

result = ast AST::IfStatement, args

}

| expression LBRACE RBRACE else {
  @lexer.commentpop
  args = {
    :test => val[0],
    :statements => ast(AST::Nop)
}

args[:else] = val[3] if val[3]

result = ast AST::IfStatement, args

}

else: # nothing

| ELSIF ifstatement {
  result = ast AST::Else, :statements => val[1]

}

| ELSE LBRACE statements RBRACE {
  @lexer.commentpop
  result = ast AST::Else, :statements => val[2]

}

| ELSE LBRACE RBRACE {
  @lexer.commentpop
  result = ast AST::Else, :statements => ast(AST::Nop)

}

# Unlike yacc/bison, it seems racc # gives tons of shift/reduce warnings # with the following syntax: # # expression: … # | expression arithop expressio { … } # # arithop: PLUS | MINUS | DIVIDE | TIMES … # # So I had to develop the expression by adding one rule # per operator :-(

expression: rvalue

| hash
| expression IN expression {
  result = ast AST::InOperator, :lval => val[0], :rval => val[2]

}

| expression MATCH regex {
  result = ast AST::MatchOperator, :operator => val[1][:value], :lval => val[0], :rval => val[2]

}

| expression NOMATCH regex {
  result = ast AST::MatchOperator, :operator => val[1][:value], :lval => val[0], :rval => val[2]

}

| expression PLUS expression {
  result = ast AST::ArithmeticOperator, :operator => val[1][:value], :lval => val[0], :rval => val[2]

}

| expression MINUS expression {
  result = ast AST::ArithmeticOperator, :operator => val[1][:value], :lval => val[0], :rval => val[2]

}

| expression DIV expression {
  result = ast AST::ArithmeticOperator, :operator => val[1][:value], :lval => val[0], :rval => val[2]

}

| expression TIMES expression {
  result = ast AST::ArithmeticOperator, :operator => val[1][:value], :lval => val[0], :rval => val[2]

}

| expression LSHIFT expression {
  result = ast AST::ArithmeticOperator, :operator => val[1][:value], :lval => val[0], :rval => val[2]

}

| expression RSHIFT expression {
  result = ast AST::ArithmeticOperator, :operator => val[1][:value], :lval => val[0], :rval => val[2]

}

| MINUS expression =UMINUS {
  result = ast AST::Minus, :value => val[1]

}

| expression NOTEQUAL expression {
  result = ast AST::ComparisonOperator, :operator => val[1][:value], :lval => val[0], :rval => val[2]

}

| expression ISEQUAL expression {
  result = ast AST::ComparisonOperator, :operator => val[1][:value], :lval => val[0], :rval => val[2]

}

| expression GREATERTHAN expression {
  result = ast AST::ComparisonOperator, :operator => val[1][:value], :lval => val[0], :rval => val[2]

}

| expression GREATEREQUAL expression {
  result = ast AST::ComparisonOperator, :operator => val[1][:value], :lval => val[0], :rval => val[2]

}

| expression LESSTHAN expression {
  result = ast AST::ComparisonOperator, :operator => val[1][:value], :lval => val[0], :rval => val[2]

}

| expression LESSEQUAL expression {
  result = ast AST::ComparisonOperator, :operator => val[1][:value], :lval => val[0], :rval => val[2]

}

| NOT expression {
  result = ast AST::Not, :value => val[1]

}

| expression AND expression {
  result = ast AST::BooleanOperator, :operator => val[1][:value], :lval => val[0], :rval => val[2]

}

| expression OR expression {
  result = ast AST::BooleanOperator, :operator => val[1][:value], :lval => val[0], :rval => val[2]

}

| LPAREN expression RPAREN {
  result = val[1]

}

casestatement: CASE expression LBRACE caseopts RBRACE {

@lexer.commentpop
result = ast AST::CaseStatement, :test => val[1], :options => val[3]

}

caseopts: caseopt { result = aryfy(val) }

| caseopts caseopt {
  val[0].push val[1]
  result = val[0]

}

caseopt: casevalues COLON LBRACE statements RBRACE {

@lexer.commentpop
result = ast AST::CaseOpt, :value => val[0], :statements => val[3]

} | casevalues COLON LBRACE RBRACE {

@lexer.commentpop

  result = ast(
  AST::CaseOpt,
  :value => val[0],

  :statements => ast(AST::ASTArray)
)

}

casevalues: selectlhand { result = aryfy(val) }

| casevalues COMMA selectlhand {
  val[0].push(val[2])
  result = val[0]

}

selector: selectlhand QMARK svalues {

result = ast AST::Selector, :param => val[0], :values => val[2]

}

svalues: selectval

| LBRACE sintvalues endcomma RBRACE {
  @lexer.commentpop
  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
| hasharrayaccess
| DEFAULT {
  result = ast AST::Default, :value => val[0][:value], :line => val[0][:line]

}

| regex

# These are only used for importing, and we don’t interpolate there. string: STRING { result = [val[:value]] } strings: string

|  strings COMMA string { result = val[0] += val[2] }

import: IMPORT strings {

val[1].each do |file|
  import(file)
end

result = nil

}

# Disable definition inheritance for now. 8/27/06, luke definition: DEFINE NAME argumentlist parent LBRACE statements RBRACE { definition: DEFINE classname argumentlist LBRACE statements RBRACE {

@lexer.commentpop
result = Puppet::Parser::AST::Definition.new(classname(val[1]),
                                             ast_context(true).merge(:arguments => val[2], :code => val[4],
                                                                     :line => val[0][:line]))
@lexer.indefine = false

#} | DEFINE NAME argumentlist parent LBRACE RBRACE { } | DEFINE classname argumentlist LBRACE RBRACE {

@lexer.commentpop
result = Puppet::Parser::AST::Definition.new(classname(val[1]),
                                             ast_context(true).merge(:arguments => val[2], :line => val[0][:line]))
@lexer.indefine = false

}

hostclass: CLASS NAME argumentlist parent LBRACE statements RBRACE { hostclass: CLASS classname argumentlist classparent LBRACE statements_and_declarations RBRACE {

@lexer.commentpop
# Our class gets defined in the parent namespace, not our own.
@lexer.namepop
result = Puppet::Parser::AST::Hostclass.new(classname(val[1]),
                                            ast_context(true).merge(:arguments => val[2], :parent => val[3],
                                                                    :code => val[5], :line => val[0][:line]))

} | CLASS classname argumentlist classparent LBRACE RBRACE {

@lexer.commentpop
# Our class gets defined in the parent namespace, not our own.
@lexer.namepop
result = Puppet::Parser::AST::Hostclass.new(classname(val[1]),
                                            ast_context(true).merge(:arguments => val[2], :parent => val[3],
                                                                    :line => val[0][:line]))

}

nodedef: NODE hostnames nodeparent LBRACE statements RBRACE {

@lexer.commentpop
result = Puppet::Parser::AST::Node.new(val[1],
                                       ast_context(true).merge(:parent => val[2], :code => val[4],
                                                               :line => val[0][:line]))

} | NODE hostnames nodeparent LBRACE RBRACE {

@lexer.commentpop
result = Puppet::Parser::AST::Node.new(val[1], ast_context(true).merge(:parent => val[2], :line => val[0][:line]))

}

classname: NAME { result = val[:value] }

| CLASS { result = "class" }

# Multiple hostnames, as used for node names. These are all literal # strings, not AST objects. hostnames: nodename {

result = [result]

}

| hostnames COMMA nodename {
  result = val[0]
  result << val[2]

}

nodename: hostname {

result = ast AST::HostName, :value => val[0]

}

hostname: NAME { result = val[:value] }

| STRING { result = val[0][:value] }
| DEFAULT { result = val[0][:value] }
| regex

nil: {

result = nil

}

nothing: {

result = ast AST::ASTArray, :children => []

}

argumentlist: nil

| LPAREN nothing RPAREN {
  result = nil

}

| LPAREN arguments endcomma 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:

VARIABLE EQUALS expression { result = [val[0][:value], val[2]] }

| VARIABLE { result = [val[:value]] }

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][:value], :line => val[0][:line]

}

array: LBRACK expressions RBRACK { result = val }

| LBRACK expressions COMMA RBRACK { result = val[1] }
| LBRACK               RBRACK { result = ast AST::ASTArray }

comma: FARROW

| COMMA

endcomma: # nothing

| COMMA { result = nil }

regex: REGEX {

result = ast AST::Regex, :value => val[0][:value]

}

hash: LBRACE hashpairs RBRACE {

if val[1].instance_of?(AST::ASTHash)
  result = val[1]
else
  result = ast AST::ASTHash, { :value => val[1] }
end

}

| LBRACE hashpairs COMMA RBRACE {
  if val[1].instance_of?(AST::ASTHash)
  result = val[1]
else
  result = ast AST::ASTHash, { :value => val[1] }
end

} | LBRACE RBRACE {

result = ast AST::ASTHash

}

hashpairs: hashpair

| hashpairs COMMA hashpair {
  if val[0].instance_of?(AST::ASTHash)
  result = val[0].merge(val[2])
else
  result = ast AST::ASTHash, :value => val[0]
  result.merge(val[2])
end

}

hashpair: key FARROW expression {

result = ast AST::ASTHash, { :value => { val[0] => val[2] } }

}

key: NAME { result = val[:value] }

| quotedtext { result = val[0] }

hasharrayaccess: VARIABLE LBRACK expression RBRACK {

result = ast AST::HashOrArrayAccess, :variable => val[0][:value], :key => val[2]

}

hasharrayaccesses: hasharrayaccess

| hasharrayaccesses LBRACK expression RBRACK {
  result = ast AST::HashOrArrayAccess, :variable => val[0], :key => val[2]

}

end —- header —- require ‘puppet’ require ‘puppet/util/loadedfile’ require ‘puppet/parser/lexer’ require ‘puppet/parser/ast’

module Puppet

class ParseError < Puppet::Error; end
class ImportError < Racc::ParseError; end
class AlreadyImportedError < ImportError; end

end

—- 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: