=begin Copyright 2011 Shane Brinkman-Davis See README for licence information. http://babel-bridge.rubyforge.org/ =end require File.dirname(__FILE__) + "/nodes.rb" require File.dirname(__FILE__) + "/pattern_element.rb" class String def camelize self.split("_").collect {|a| a.capitalize}.join end def first_lines(n) lines=self.split("\n",-1) lines.length<=n ? self : lines[0..n-1].join("\n") end def last_lines(n) lines=self.split("\n",-1) lines.length<=n ? self : lines[-n..-1].join("\n") end def line_col(offset) lines=self[0..offset-1].split("\n") return lines.length, lines[-1].length end end module BabelBridge VERSION = "0.2.0" # Each Rule has one or more RuleVariant # Rules attempt to match each of their Variants in order. The first one to succeed returns true and the Rule succeeds. class RuleVariant attr_accessor :pattern,:rule,:node_class def initialize(pattern,rule,node_class=nil) self.pattern=pattern self.rule=rule self.node_class=node_class end def inspect pattern.collect{|a|a.inspect}.join(', ') end def to_s "variant_class: #{node_class}, pattern: #{inspect}" end # convert the pattern into a set of lamba functions def pattern_elements @pattern_elements||=pattern.collect { |match| PatternElement.new match, self } end # returns a Node object if it matches, nil otherwise def parse(parent_node) #return parse_nongreedy_optional(src,offset,parent_node) # nongreedy optionals break standard PEG node=node_class.new(parent_node) pattern_elements.each do |pe| match=pe.parse(node) # if parse failed if !match if pe.terminal # log failures on Terminal patterns for debug output if overall parse fails node.parser.log_parsing_failure(node.next,:pattern=>pe.match,:node=>node) end return nil end # parse succeeded, add to node and continue node.add_match(match,pe.name) end node end end # Rules define one or more patterns (RuleVariants) to match for a given non-terminal class Rule attr_accessor :name,:variants,:parser,:node_class def initialize(name,parser) self.name=name self.variants=[] self.parser=parser class_name = "#{parser.module_name}_#{name}_node".camelize self.node_class = parser.const_set(class_name,Class.new(NonTerminalNode)) end def add_variant(pattern, &block) rule_variant_class_name = "#{name}_node#{self.variants.length+1}".camelize rule_variant_class = parser.const_set(rule_variant_class_name,Class.new(node_class)) self.variants << RuleVariant.new(pattern,self,rule_variant_class) rule_variant_class.class_eval &block if block rule_variant_class end def parse(node) if cached=node.parser.cached(name,node.next) return cached==:no_match ? nil : cached # return nil if cached==:no_matched end variants.each do |v| if match=v.parse(node) node.parser.cache_match(name,match) return match end end node.parser.cache_no_match(name,node.next) nil end # inspect returns a string which approximates the syntax for generating the rule and all its variants def inspect variants.collect do |v| "rule #{name.inspect}, #{v.inspect}" end.join("\n") end # returns a more human-readable explanation of the rule def to_s "rule #{name.inspect}, node_class: #{node_class}\n\t"+ "#{variants.collect {|v|v.to_s}.join("\n\t")}" end end # primary object used by the client # Used to generate the grammer with .rule methods # Used to parse with .parse class Parser # Parser sub-class grammaer definition # These methods are used in the creation of a Parser Sub-Class to define # its grammar class <failure_index key=expecting[:pattern] @expecting_list={key=>expecting} @failure_index = index elsif index == failure_index key=expecting[:pattern] self.expecting_list[key]=expecting else # ignored end end def parse(src,offset=0,rule=nil) reset_parser_tracking @start_time=Time.now self.src=src root_node=RootNode.new(self) ret=self.class[rule||self.class.root_rule].parse(root_node) unless rule if ret if ret.next ") end def parser_failure_info return unless src bracketing_lines=5 line,col=src.line_col(failure_index) ret=<<-ENDTXT Parsing error at line #{line} column #{col} offset #{failure_index} Source: ... #{(failure_index==0 ? "" : src[0..(failure_index-1)]).last_lines(bracketing_lines)}#{src[(failure_index)..-1].first_lines(bracketing_lines)} ... ENDTXT if @parsing_did_not_match_entire_input ret+="\nParser did not match entire input." else common_root=nil expecting_list.values.each do |e| node=e[:node] pl=node.parent_list if common_root common_root.each_index do |i| if pl[i]!=common_root[i] common_root=common_root[0..i-1] break end end else common_root=node.parent_list end end ret+=<1 ? ' one of' : ''}: #{expecting_list.values.collect do |a| list=node_list_string(a[:node].parent_list,common_root) [list,"#{a[:pattern].inspect} (#{list})"] end.sort.map{|i|i[1]}.join("\n ")} ENDTXT end ret end end end