# vim: syntax=ruby class Nagios::Parser token DEFINE NAME PARAM LCURLY RCURLY VALUE RETURN rule decls: decl { return val[0] if val[0] } | decls decl { if val[1].nil? result = val[0] else if val[0].nil? result = val[1] else result = [ val[0], val[1] ].flatten end end } ; decl: object { result = [val[0]] } | RETURN { result = nil } ; object: DEFINE NAME LCURLY returns vars RCURLY { result = Nagios::Base.create(val[1],val[4]) } ; vars: var | vars var { val[1].each {|p,v| val[0][p] = v } result = val[0] } ; var: PARAM VALUE returns { result = {val[0] => val[1]} } | PARAM returns { result = {val[0] => "" } } ; returns: RETURN | RETURN returns ; end ----inner require 'strscan' class ::Nagios::Parser::SyntaxError < RuntimeError; end def parse(src) if (RUBY_VERSION < '2.1.0') && src.respond_to?("force_encoding") then src.force_encoding("ASCII-8BIT") end @ss = StringScanner.new(src) # state variables @in_parameter_value = false @in_object_definition = false @done = false @line = 1 @yydebug = true do_parse end # This tokenizes the outside of object definitions, # and detects when we start defining an object. # We ignore whitespaces, comments and inline comments. # We yield when finding newlines, the "define" keyword, # the object name and the opening curly bracket. def tokenize_outside_definitions case when (chars = @ss.skip(/[ \t]+/)) # ignore whitespace /\s+/ ; when (text = @ss.scan(/\#.*$/)) # ignore comments ; when (text = @ss.scan(/;.*$/)) # ignore inline comments ; when (text = @ss.scan(/\n/)) # newline [:RETURN, text] when (text = @ss.scan(/\b(define)\b/)) # the "define" keyword [:DEFINE, text] when (text = @ss.scan(/[^{ \t\n]+/)) # the name of the object being defined (everything not an opening curly bracket or a separator) [:NAME, text] when (text = @ss.scan(/\{/)) # the opening curly bracket - we enter object definition @in_object_definition = true [:LCURLY, text] else text = @ss.string[@ss.pos .. -1] raise ScanError, "can not match: '#{text}'" end # case end # This tokenizes until we find the parameter name. def tokenize_parameter_name case when (chars = @ss.skip(/[ \t]+/)) # ignore whitespace /\s+/ ; when (text = @ss.scan(/\#.*$/)) # ignore comments ; when (text = @ss.scan(/;.*$/)) # ignore inline comments ; when (text = @ss.scan(/\n/)) # newline [:RETURN, text] when (text = @ss.scan(/\}/)) # closing curly bracket : end of definition @in_object_definition = false [:RCURLY, text] when (not @in_parameter_value and (text = @ss.scan(/\S+/))) # This is the name of the parameter @in_parameter_value = true [:PARAM, text] else text = @ss.string[@ss.pos .. -1] raise ScanError, "can not match: '#{text}'" end # case end # This tokenizes the parameter value. # There is a special handling for lines containing semicolons : # - unescaped semicolons are line comments (and should stop parsing of the line) # - escaped (with backslash \) semicolons should be kept in the parameter value (without the backslash) def tokenize_parameter_value case when (chars = @ss.skip(/[ \t]+/)) # ignore whitespace /\s+/ ; when (text = @ss.scan(/\#.*$/)) # ignore comments ; when (text = @ss.scan(/\n/)) # newline @in_parameter_value = false [:RETURN, text] when (text = @ss.scan(/.+$/)) # Value of parameter @in_parameter_value = false # Special handling of inline comments (;) and escaped semicolons (\;) # We split the string on escaped semicolons (\;), # Then we rebuild it as long as there are no inline comments (;) # We join the rebuilt string with unescaped semicolons (on purpose) array = text.split('\;', 0) text = "" array.each do |elt| # Now we split at inline comments. If we have more than 1 element in the array # it means we have an inline comment, so we are able to stop parsing # However we still want to reconstruct the string with its first part (before the comment) linearray = elt.split(';', 0) # Let's reconstruct the string with a (unescaped) semicolon if text != "" then text += ';' end text += linearray[0] # Now we can stop if linearray.length > 1 then break end end # We strip the text to remove spaces between end of string and beginning of inline comment [:VALUE, text.strip] else text = @ss.string[@ss.pos .. -1] raise ScanError, "can not match: '#{text}'" end # case end # This tokenizes inside an object definition. # Two cases : parameter name and parameter value def tokenize_inside_definitions if @in_parameter_value tokenize_parameter_value else tokenize_parameter_name end end # The lexer. Very simple. def token text = @ss.peek(1) @line += 1 if text == "\n" token = if @in_object_definition tokenize_inside_definitions else tokenize_outside_definitions end token end def next_token return if @ss.eos? # skips empty actions until _next_token = token or @ss.eos?; end _next_token end def yydebug 1 end def yywrap 0 end def on_error(token, value, vstack ) # text = @ss.string[@ss.pos .. -1] text = @ss.peek(20) msg = "" unless value.nil? msg = "line #{@line}: syntax error at value '#{value}' : #{text}" else msg = "line #{@line}: syntax error at token '#{token}' : #{text}" end if @ss.eos? msg = "line #{@line}: Unexpected end of file" end if token == '$end'.intern puts "okay, this is silly" else raise ::Nagios::Parser::SyntaxError, msg end end