Class Puppet::Parser::Parser
In: lib/puppet/parser/parser_support.rb
lib/puppet/parser/parser.rb
Parent: Object

I pulled this into a separate file, because I got tired of rebuilding the parser.rb file all the time.

Methods

_reduce_none   addcontext   aryfy   ast   classname   clear   error   file   file=   findclass   finddefine   findnode   fqfind   import   initvars   load   namesplit   new   newclass   newdefine   newnode   on_error   parse   reparse?   string=   watch_file  

Constants

ASTSet = Struct.new(:classes, :definitions, :nodes)
AST = Puppet::Parser::AST
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', 'DQTEXT', 'SQTEXT', 'RBRACK', 'LBRACE', 'RBRACE', 'SYMBOL', 'FARROW', 'COMMA', 'TRUE', 'FALSE', 'EQUALS', 'APPENDS', 'LESSEQUAL', 'NOTEQUAL', 'DOT', 'COLON', 'LLCOLLECT', 'RRCOLLECT', 'QMARK', 'LPAREN', 'RPAREN', 'ISEQUAL', 'GREATEREQUAL', 'GREATERTHAN', 'LESSTHAN', 'IF', 'ELSE', 'IMPORT', 'DEFINE', 'ELSIF', 'VARIABLE', 'CLASS', 'INHERITS', 'NODE', 'BOOLEAN', 'NAME', 'SEMIC', 'CASE', 'DEFAULT', 'AT', 'LCOLLECT', 'RCOLLECT', 'CLASSNAME', 'CLASSREF', 'NOT', 'OR', 'AND', 'UNDEF', 'PARROW', 'PLUS', 'MINUS', 'TIMES', 'DIV', 'LSHIFT', 'RSHIFT', 'UMINUS', '$start', 'program', 'statements', 'nil', 'statement', 'resource', 'virtualresource', 'collection', 'assignment', 'casestatement', 'ifstatement', 'import', 'fstatement', 'definition', 'hostclass', 'nodedef', 'resourceoverride', 'append', 'funcvalues', 'namestring', 'resourceref', 'name', 'variable', 'type', 'boolean', 'funcrvalue', 'selector', 'quotedtext', 'classname', 'resourceinstances', 'endsemi', 'params', 'endcomma', 'classref', 'anyparams', 'at', 'collectrhand', 'collstatements', 'collstatement', 'colljoin', 'collexpr', 'colllval', 'simplervalue', 'resourceinst', 'resourcename', 'undef', 'array', 'expression', 'param', 'rvalue', 'addparam', 'anyparam', 'rvalues', 'comma', 'else', 'caseopts', 'caseopt', 'casevalues', 'selectlhand', 'svalues', 'selectval', 'sintvalues', 'qtexts', 'argumentlist', 'classparent', 'hostnames', 'nodeparent', 'hostname', 'nothing', 'arguments', 'argument', 'classnameordefault']
Racc_debug_parser = false

Attributes

environment  [R] 
files  [RW] 
lexer  [RW] 
version  [R] 

Public Class methods

[Source]

     # File lib/puppet/parser/parser_support.rb, line 212
212:     def initialize(options = {})
213:         @astset = options[:astset] || ASTSet.new({}, {}, {})
214:         @environment = options[:environment]
215:         initvars()
216:     end

Public Instance methods

[Source]

      # File lib/puppet/parser/parser.rb, line 2192
2192:  def _reduce_none( val, _values, result )
2193:   result
2194:  end

Add context to a message; useful for error messages and such.

[Source]

    # File lib/puppet/parser/parser_support.rb, line 24
24:     def addcontext(message, obj = nil)
25:         obj ||= @lexer
26: 
27:         message += " on line %s" % obj.line
28:         if file = obj.file
29:             message += " in file %s" % file
30:         end
31: 
32:         return message
33:     end

Create an AST array out of all of the args

[Source]

    # File lib/puppet/parser/parser_support.rb, line 36
36:     def aryfy(*args)
37:         if args[0].instance_of?(AST::ASTArray)
38:             result = args.shift
39:             args.each { |arg|
40:                 result.push arg
41:             }
42:         else
43:             result = ast AST::ASTArray, :children => args
44:         end
45: 
46:         return result
47:     end

Create an AST object, and automatically add the file and line information if available.

[Source]

    # File lib/puppet/parser/parser_support.rb, line 51
51:     def ast(klass, hash = {})
52:         hash[:line] = @lexer.line unless hash.include?(:line)
53: 
54:         unless hash.include?(:file)
55:             if file = @lexer.file
56:                 hash[:file] = file
57:             end
58:         end
59: 
60:         k = klass.new(hash)
61:         k.doc = lexer.getcomment if !k.nil? and k.use_docs and k.doc.empty?
62:         return k
63:     end

The fully qualifed name, with the full namespace.

[Source]

    # File lib/puppet/parser/parser_support.rb, line 66
66:     def classname(name)
67:         [@lexer.namespace, name].join("::").sub(/^::/, '')
68:     end

[Source]

    # File lib/puppet/parser/parser_support.rb, line 70
70:     def clear
71:         initvars
72:     end

Raise a Parse error.

[Source]

    # File lib/puppet/parser/parser_support.rb, line 75
75:     def error(message)
76:         if brace = @lexer.expected
77:             message += "; expected '%s'"
78:         end
79:         except = Puppet::ParseError.new(message)
80:         except.line = @lexer.line
81:         if @lexer.file
82:             except.file = @lexer.file
83:         end
84: 
85:         raise except
86:     end

[Source]

    # File lib/puppet/parser/parser_support.rb, line 88
88:     def file
89:         @lexer.file
90:     end

[Source]

     # File lib/puppet/parser/parser_support.rb, line 92
 92:     def file=(file)
 93:         unless FileTest.exists?(file)
 94:             unless file =~ /\.pp$/
 95:                 file = file + ".pp"
 96:             end
 97:             unless FileTest.exists?(file)
 98:                 raise Puppet::Error, "Could not find file %s" % file
 99:             end
100:         end
101:         if check_and_add_to_watched_files(file)
102:             @lexer.file = file
103:         else
104:             raise Puppet::AlreadyImportedError.new("Import loop detected")
105:         end
106:     end

Find a class definition, relative to the current namespace.

[Source]

     # File lib/puppet/parser/parser_support.rb, line 109
109:     def findclass(namespace, name)
110:         fqfind namespace, name, classes
111:     end

Find a component definition, relative to the current namespace.

[Source]

     # File lib/puppet/parser/parser_support.rb, line 114
114:     def finddefine(namespace, name)
115:         fqfind namespace, name, definitions
116:     end

This is only used when nodes are looking up the code for their parent nodes.

[Source]

     # File lib/puppet/parser/parser_support.rb, line 120
120:     def findnode(name)
121:         fqfind "", name, nodes
122:     end

The recursive method used to actually look these objects up.

[Source]

     # File lib/puppet/parser/parser_support.rb, line 125
125:     def fqfind(namespace, name, table)
126:         namespace = namespace.downcase
127:         name = name.to_s.downcase
128: 
129:         # If our classname is fully qualified or we have no namespace,
130:         # just try directly for the class, and return either way.
131:         if name =~ /^::/ or namespace == ""
132:             classname = name.sub(/^::/, '')
133:             self.load(classname) unless table[classname]
134:             return table[classname]
135:         end
136: 
137:         # Else, build our namespace up piece by piece, checking
138:         # for the class in each namespace.
139:         ary = namespace.split("::")
140: 
141:         while ary.length > 0
142:             newname = (ary + [name]).join("::").sub(/^::/, '')
143:             if obj = table[newname] or (self.load(newname) and obj = table[newname])
144:                 return obj
145:             end
146: 
147:             # Delete the second to last object, which reduces our namespace by one.
148:             ary.pop
149:         end
150: 
151:         # If we've gotten to this point without finding it, see if the name
152:         # exists at the top namespace
153:         if obj = table[name] or (self.load(name) and obj = table[name])
154:             return obj
155:         end
156: 
157:         return nil
158:     end

Import our files.

[Source]

     # File lib/puppet/parser/parser_support.rb, line 161
161:     def import(file)
162:         if Puppet[:ignoreimport]
163:             return AST::ASTArray.new(:children => [])
164:         end
165:         # use a path relative to the file doing the importing
166:         if @lexer.file
167:             dir = @lexer.file.sub(%r{[^/]+$},'').sub(/\/$/, '')
168:         else
169:             dir = "."
170:         end
171:         if dir == ""
172:             dir = "."
173:         end
174:         result = ast AST::ASTArray
175: 
176:         # We can't interpolate at this point since we don't have any
177:         # scopes set up. Warn the user if they use a variable reference
178:         pat = file
179:         if pat.index("$")
180:             Puppet.warning(
181:                "The import of #{pat} contains a variable reference;" +
182:                " variables are not interpolated for imports " +
183:                "in file #{@lexer.file} at line #{@lexer.line}"
184:             )
185:         end
186:         files = Puppet::Module::find_manifests(pat, :cwd => dir, :environment => @environment)
187:         if files.size == 0
188:             raise Puppet::ImportError.new("No file(s) found for import " +
189:                                           "of '#{pat}'")
190:         end
191: 
192:         files.collect { |file|
193:             parser = Puppet::Parser::Parser.new(:astset => @astset, :environment => @environment)
194:             parser.files = self.files
195:             Puppet.debug("importing '%s'" % file)
196: 
197:             unless file =~ /^#{File::SEPARATOR}/
198:                 file = File.join(dir, file)
199:             end
200:             begin
201:                 parser.file = file
202:             rescue Puppet::AlreadyImportedError
203:                 # This file has already been imported to just move on
204:                 next
205:             end
206: 
207:             # This will normally add code to the 'main' class.
208:             parser.parse
209:         }
210:     end

Initialize or reset all of our variables.

[Source]

     # File lib/puppet/parser/parser_support.rb, line 219
219:     def initvars
220:         @lexer = Puppet::Parser::Lexer.new()
221:         @files = {}
222:         @loaded = []
223:     end

Try to load a class, since we could not find it.

[Source]

     # File lib/puppet/parser/parser_support.rb, line 226
226:     def load(classname)
227:         return false if classname == ""
228:         filename = classname.gsub("::", File::SEPARATOR)
229: 
230:         # First try to load the top-level module
231:         mod = filename.scan(/^[\w-]+/).shift
232:         unless @loaded.include?(mod)
233:             @loaded << mod
234:             begin
235:                 import(mod)
236:                 Puppet.info "Autoloaded module %s" % mod
237:             rescue Puppet::ImportError => detail
238:                 # We couldn't load the module
239:             end
240:         end
241: 
242:         # We don't know whether we're looking for a class or definition, so we have
243:         # to test for both.
244:         return true if classes.include?(classname) || definitions.include?(classname)
245: 
246:         unless @loaded.include?(filename)
247:             @loaded << filename
248:             # Then the individual file
249:             begin
250:                 import(filename)
251:                 Puppet.info "Autoloaded file %s from module %s" % [filename, mod]
252:             rescue Puppet::ImportError => detail
253:                 # We couldn't load the file
254:             end
255:         end
256:         # We don't know whether we're looking for a class or definition, so we have
257:         # to test for both.
258:         return classes.include?(classname) || definitions.include?(classname)
259:     end

Split an fq name into a namespace and name

[Source]

     # File lib/puppet/parser/parser_support.rb, line 262
262:     def namesplit(fullname)
263:         ary = fullname.split("::")
264:         n = ary.pop || ""
265:         ns = ary.join("::")
266:         return ns, n
267:     end

Create a new class, or merge with an existing class.

[Source]

     # File lib/puppet/parser/parser_support.rb, line 270
270:     def newclass(name, options = {})
271:         name = name.downcase
272: 
273:         if definitions.include?(name)
274:             raise Puppet::ParseError, "Cannot redefine class %s as a definition" % name
275:         end
276:         code = options[:code]
277:         parent = options[:parent]
278:         doc = options[:doc]
279: 
280:         # If the class is already defined, then add code to it.
281:         if other = @astset.classes[name]
282:             # Make sure the parents match
283:             if parent and other.parentclass and (parent != other.parentclass)
284:                 error("Class %s is already defined at %s:%s; cannot redefine" % [name, other.file, other.line])
285:             end
286: 
287:             # This might be dangerous...
288:             if parent and ! other.parentclass
289:                 other.parentclass = parent
290:             end
291: 
292:             # This might just be an empty, stub class.
293:             if code
294:                 tmp = name
295:                 if tmp == ""
296:                     tmp = "main"
297:                 end
298: 
299:                 Puppet.debug addcontext("Adding code to %s" % tmp)
300:                 # Else, add our code to it.
301:                 if other.code and code
302:                     # promote if neededcodes to ASTArray so that we can append code
303:                     # ASTArray knows how to evaluate its members.
304:                     other.code = ast AST::ASTArray, :children => [other.code] unless other.code.is_a?(AST::ASTArray)
305:                     code = ast AST::ASTArray, :children => [code] unless code.is_a?(AST::ASTArray)
306:                     other.code.children += code.children
307:                 else
308:                     other.code ||= code
309:                 end
310:             end
311: 
312:             if other.doc and doc
313:                 other.doc += doc
314:             else
315:                 other.doc ||= doc
316:             end
317:         else
318:             # Define it anew.
319:             # Note we're doing something somewhat weird here -- we're setting
320:             # the class's namespace to its fully qualified name.  This means
321:             # anything inside that class starts looking in that namespace first.
322:             args = {:namespace => name, :classname => name, :parser => self}
323:             args[:code] = code if code
324:             args[:parentclass] = parent if parent
325:             args[:doc] = doc
326: 
327:             @astset.classes[name] = ast AST::HostClass, args
328:         end
329: 
330:         return @astset.classes[name]
331:     end

Create a new definition.

[Source]

     # File lib/puppet/parser/parser_support.rb, line 334
334:     def newdefine(name, options = {})
335:         name = name.downcase
336:         if @astset.classes.include?(name)
337:             raise Puppet::ParseError, "Cannot redefine class %s as a definition" %
338:                 name
339:         end
340:         # Make sure our definition doesn't already exist
341:         if other = @astset.definitions[name]
342:             error("%s is already defined at %s:%s; cannot redefine" % [name, other.file, other.line])
343:         end
344: 
345:         ns, whatever = namesplit(name)
346:         args = {
347:             :namespace => ns,
348:             :arguments => options[:arguments],
349:             :code => options[:code],
350:             :parser => self,
351:             :classname => name,
352:             :doc => options[:doc]
353:         }
354: 
355:         [:code, :arguments].each do |param|
356:             args[param] = options[param] if options[param]
357:         end
358: 
359:         @astset.definitions[name] = ast AST::Definition, args
360:     end

Create a new node. Nodes are special, because they‘re stored in a global table, not according to namespaces.

[Source]

     # File lib/puppet/parser/parser_support.rb, line 364
364:     def newnode(names, options = {})
365:         names = [names] unless names.instance_of?(Array)
366:         doc = lexer.getcomment
367:         names.collect do |name|
368:             name = name.to_s.downcase
369:             if other = @astset.nodes[name]
370:                 error("Node %s is already defined at %s:%s; cannot redefine" % [other.name, other.file, other.line])
371:             end
372:             name = name.to_s if name.is_a?(Symbol)
373:             args = {
374:                 :name => name,
375:                 :parser => self,
376:                 :doc => doc
377:             }
378:             if options[:code]
379:                 args[:code] = options[:code]
380:             end
381:             if options[:parent]
382:                 args[:parentclass] = options[:parent]
383:             end
384:             @astset.nodes[name] = ast(AST::Node, args)
385:             @astset.nodes[name].classname = name
386:             @astset.nodes[name]
387:         end
388:     end

[Source]

     # File lib/puppet/parser/parser_support.rb, line 390
390:     def on_error(token,value,stack)
391:         if token == 0 # denotes end of file
392:             value = 'end of file'
393:         else
394:             value = "'%s'" % value
395:         end
396:         error = "Syntax error at %s" % [value]
397: 
398:         if brace = @lexer.expected
399:             error += "; expected '%s'" % brace
400:         end
401: 
402:         except = Puppet::ParseError.new(error)
403:         except.line = @lexer.line
404:         if @lexer.file
405:             except.file = @lexer.file
406:         end
407: 
408:         raise except
409:     end

how should I do error handling here?

[Source]

     # File lib/puppet/parser/parser_support.rb, line 412
412:     def parse(string = nil)
413:         if string
414:             self.string = string
415:         end
416:         begin
417:             @yydebug = false
418:             main = yyparse(@lexer,:scan)
419:         rescue Racc::ParseError => except
420:             error = Puppet::ParseError.new(except)
421:             error.line = @lexer.line
422:             error.file = @lexer.file
423:             error.set_backtrace except.backtrace
424:             raise error
425:         rescue Puppet::ParseError => except
426:             except.line ||= @lexer.line
427:             except.file ||= @lexer.file
428:             raise except
429:         rescue Puppet::Error => except
430:             # and this is a framework error
431:             except.line ||= @lexer.line
432:             except.file ||= @lexer.file
433:             raise except
434:         rescue Puppet::DevError => except
435:             except.line ||= @lexer.line
436:             except.file ||= @lexer.file
437:             raise except
438:         rescue => except
439:             error = Puppet::DevError.new(except.message)
440:             error.line = @lexer.line
441:             error.file = @lexer.file
442:             error.set_backtrace except.backtrace
443:             raise error
444:         end
445:         if main
446:             # Store the results as the top-level class.
447:             newclass("", :code => main)
448:         end
449:         @version = Time.now.to_i
450:         return @astset
451:     ensure
452:         @lexer.clear
453:     end

See if any of the files have changed.

[Source]

     # File lib/puppet/parser/parser_support.rb, line 456
456:     def reparse?
457:         if file = @files.detect { |name, file| file.changed?  }
458:             return file[1].stamp
459:         else
460:             return false
461:         end
462:     end

[Source]

     # File lib/puppet/parser/parser_support.rb, line 464
464:     def string=(string)
465:         @lexer.string = string
466:     end

Add a new file to be checked when we‘re checking to see if we should be reparsed. This is basically only used by the TemplateWrapper to let the parser know about templates that should be parsed.

[Source]

     # File lib/puppet/parser/parser_support.rb, line 471
471:     def watch_file(filename)
472:             check_and_add_to_watched_files(filename)
473:     end

[Validate]