lib/babel_bridge.rb in babel_bridge-0.2.0 vs lib/babel_bridge.rb in babel_bridge-0.3.0

- old
+ new

@@ -2,349 +2,18 @@ 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 <<self - attr_accessor :rules,:module_name,:root_rule - - def rules - @rules||={} - end - # rules can be specified as: - # parser.rule :name, to_match1, to_match2, etc... - #or - # parser.rule :name, [to_match1, to_match2, etc...] - def rule(name,*pattern,&block) - pattern=pattern[0] if pattern[0].kind_of?(Array) - rule=self.rules[name]||=Rule.new(name,self) - self.root_rule||=name - rule.add_variant(pattern,&block) - end - - def node_class(name,&block) - klass=self.rules[name].node_class - return klass unless block - klass.class_eval &block - end - - def [](i) - rules[i] - end - - # rule can be symbol-name of one of the rules in rules_array or one of the actual Rule objects in that array - def root_rule=(rule) - raise "Symbol required" unless rule.kind_of?(Symbol) - raise "rule #{rule.inspect} not found" unless rules[rule] - @root_rule=rule - end - end - - #********************************************* - # pattern construction tools - # - # Ex: - # # match 'keyword' - # # (succeeds if keyword is matched; advances the read pointer) - # rule :sample_rule, "keyword" - # rule :sample_rule, match("keyword") - # - # # don't match 'keyword' - # # (succeeds only if keyword is NOT matched; does not advance the read pointer) - # rule :sample_rule, match!("keyword") - # rule :sample_rule, dont.match("keyword") - # - # # optionally match 'keyword' - # # (always succeeds; advances the read pointer if keyword is matched) - # rule :sample_rule, match?("keyword") - # rule :sample_rule, optionally.match("keyword") - # - # # ensure we could match 'keyword' - # # (succeeds only if keyword is matched, but does not advance the read pointer) - # rule :sample_rule, could.match("keyword") - # - - # dont.match("keyword") # - #********************************************* - class <<self - def many(m,delimiter=nil,post_delimiter=nil) PatternElementHash.new.match.many(m).delimiter(delimiter).post_delimiter(post_delimiter) end - def many?(m,delimiter=nil,post_delimiter=nil) PatternElementHash.new.optionally.match.many(m).delimiter(delimiter).post_delimiter(post_delimiter) end - def many!(m,delimiter=nil,post_delimiter=nil) PatternElementHash.new.dont.match.many(m).delimiter(delimiter).post_delimiter(post_delimiter) end - - def match?(*args) PatternElementHash.new.optionally.match(*args) end - def match(*args) PatternElementHash.new.match(*args) end - def match!(*args) PatternElementHash.new.dont.match(*args) end - - def dont; PatternElementHash.new.dont end - def optionally; PatternElementHash.new.optionally end - def could; PatternElementHash.new.could end - end - - - #********************************************* - #********************************************* - # parser instance implementation - # this methods are used for each actual parse run - # they are tied to an instnace of the Parser Sub-class to you can have more than one - # parser active at a time - attr_accessor :failure_index - attr_accessor :expecting_list - attr_accessor :src - attr_accessor :parse_cache - - def initialize - reset_parser_tracking - end - - def reset_parser_tracking - self.src=nil - self.failure_index=0 - self.expecting_list={} - self.parse_cache={} - end - - def cached(rule_class,offset) - (parse_cache[rule_class]||={})[offset] - end - - def cache_match(rule_class,match) - (parse_cache[rule_class]||={})[match.offset]=match - end - - def cache_no_match(rule_class,offset) - (parse_cache[rule_class]||={})[offset]=:no_match - end - - def log_parsing_failure(index,expecting) - if index>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<src.length # parse only succeeds if the whole input is matched - @parsing_did_not_match_entire_input=true - @failure_index=ret.next - ret=nil - else - reset_parser_tracking - end - end - end - @end_time=Time.now - ret - end - - def parse_time - @end_time-@start_time - end - - def parse_and_puts_errors(src,out=$stdout) - ret=parse(src) - unless ret - out.puts parser_failure_info - end - ret - end - - def node_list_string(node_list,common_root=[]) - node_list && node_list[common_root.length..-1].map{|p|"#{p.class}(#{p.offset})"}.join(" > ") - 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)}<HERE>#{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+=<<ENDTXT - -Successfully matched rules up to failure: - #{node_list_string(common_root)} - -Expecting#{expecting_list.length>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 - +%w{ + tools + string + version + nodes + pattern_element + shell + rule_variant + rule + parser +}.each do |file| + require File.join(File.dirname(__FILE__),file) +end \ No newline at end of file