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