# coding: utf-8 require 'treetop' # see http://thingsaaronmade.com/blog/a-quick-intro-to-writing-a-parser-using-treetop.html module TlaParserS require_relative "parser_nodes" class Parser # ------------------------------------------------------------------ # attributes # Load the Treetop grammar from the 'sexp_parser' file, and # create a new instance of that parser as a class variable # so we don't have to re-create it every time we need to # parse a string @@parser = nil # ------------------------------------------------------------------ # Logger PROGNAME = "parser" # progname for logger include TlaParserS::Utils::MyLogger # mix logger # ------------------------------------------------------------------ # version # @return version [String] string from file 'VERSION' def self.version TlaParserS::version end # ------------------------------------------------------------------ # constructore def initialize( options = {} ) @logger = getLogger( PROGNAME, options ) @logger.info( "#{__method__} initialized" ) self.loadParsers end # @return [SexpParser] cached parser from 'parser_sexp.treetop' def loadParsers # cached return @@parser unless @@parser.nil? base_path = File.expand_path(File.dirname(__FILE__)) parser_path = File.join( base_path, 'parser_sexp.treetop') @logger.info( "#{__method__} loading parser from parser path #{parser_path}" ) Treetop.load( parser_path) @@parser = SexpParser.new @@parser end # ------------------------------------------------------------------ # parse # @param start [symbol] treep grammar non-terminal to start iwth def parse( data, start = nil ) return nil if data.nil? # to pass a file if data.respond_to? :read data = data.read # elsif ! data.respond_to? :lines # data.lines = data.to_a end # Pass the data over to the parser instance parser = getTheParser tree = parser.parse(data, :root=>start ) # If the AST is nil then there was an error during parsing # we need to report a simple error message to help the user if(tree.nil?) # adopted from http://whitequark.org/blog/2011/09/08/treetop-typical-errors/ parser.failure_reason =~ /(Expected .*) after/m expected = $1.nil? ? parser.failure_reason : $1 msg= <<-EOS Parse error: Line : #{parser.failure_line}, offset : #{parser.index} #{expected.gsub( "\n", '$NEWLINE')} #{data.lines.to_a[ parser.failure_line()-1 ]} #{'~' * (parser.failure_column-1)}^ EOS # puts msg @logger.error( "#{__method__} #{msg}, for data #{data}" ) raise ParseException.new msg end self.class.clean_tree( tree ) return tree end # parse # @return [SexpParser] static parser to use def getTheParser @@parser end # ------------------------------------------------------------------ # priv private # we have created a tree from our input, but it has a lot of # extraneous nodes that we don’t need or care about. Let’s put in # a system for cleaning up the tree: def self.clean_tree(root_node) return if(root_node.elements.nil?) # comment the following line if parse loosees nodes root_node.elements.delete_if{|node| node.class.name == "Treetop::Runtime::SyntaxNode" } root_node.elements.each {|node| self.clean_tree(node) } end end # class end # module