diff options
Diffstat (limited to 'runtime/Ruby/lib/antlr3/profile.rb')
-rw-r--r-- | runtime/Ruby/lib/antlr3/profile.rb | 360 |
1 files changed, 360 insertions, 0 deletions
diff --git a/runtime/Ruby/lib/antlr3/profile.rb b/runtime/Ruby/lib/antlr3/profile.rb new file mode 100644 index 0000000..808b176 --- /dev/null +++ b/runtime/Ruby/lib/antlr3/profile.rb @@ -0,0 +1,360 @@ +#!/usr/bin/ruby +# encoding: utf-8 + +=begin LICENSE + +[The "BSD licence"] +Copyright (c) 2009-2010 Kyle Yetter +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + 3. The name of the author may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +=end + +module ANTLR3 +module Profile +=begin rdoc ANTLR3::Profile::ParserEvents + +ANTLR3::Profile::ParserEvents expands basic debugging events for use by +recognition code generated by ANTLR when called with the <tt>-profile</tt> +switch. + +=end +module ParserEvents + include ANTLR3::Debug::ParserEvents + + def self.included( klass ) + super + if klass.is_a?( ::Class ) + def klass.profile? + true + end + end + end + + def initialize( stream, options = {} ) + options[ :debug_listener ] ||= Profiler.new( self ) + super( stream, options ) + end + + def already_parsed_rule?( rule ) + @debug_listener.examine_rule_memoization( rule ) + super + end + + def profile + @debug_listener.profile + end + + def memoize( rule, start_index, success ) + @debug_listener.memoize( rule, rule_start_index, sucess ) + super + end +end + +class DataSet < ::Array + include ::Math + def total + inject( :+ ) + end + def average + length > 0 ? ( total.to_f / length ) : 0 + end + def variance + length.zero? and return( 0.0 ) + mean = average + inject( 0.0 ) { |t, i| t + ( i - mean )**2 } / ( length - 1 ) + end + def standard_deviation + sqrt( variance ) + end +end + + +unless const_defined?( :Profile ) + Profile = Struct.new( + :grammar_file, :parser_class, :top_rule, + :rule_invocations, :guessing_rule_invocations, :rule_invocation_depth, + :fixed_looks, :cyclic_looks, :syntactic_predicate_looks, + :memoization_cache_entries, :memoization_cache_hits, + :memoization_cache_misses, :tokens, :hidden_tokens, + :characters_matched, :hidden_characters_matched, :semantic_predicates, + :syntactic_predicates, :reported_errors + ) +end + +class Profile + def initialize + init_values = Array.new( self.class.members.length, 0 ) + super( *init_values ) + self.top_rule = self.parser_class = self.grammar_file = nil + self.fixed_looks = DataSet.new + self.cyclic_looks = DataSet.new + self.syntactic_predicate_looks = DataSet.new + end + + def fixed_decisions + fixed_looks.length + end + + def cyclic_decisions + cyclic_looks.length + end + + def backtracking_decisions + syntactic_predicate_looks.length + end + + def generate_report + report = '+' << '-' * 78 << "+\n" + report << '| ' << "ANTLR Rule Profile".center( 76 ) << " |\n" + report << '+' << '-' * 78 << "+\n" + report << "| Generated at #{ Time.now }".ljust( 78 ) << " |\n" + report << "| Profiled #{ parser_class.name }##{ top_rule }".ljust( 78 ) << " |\n" + report << "| Rule source generated from grammar file #{ grammar_file }".ljust( 78 ) << " |\n" + report << '+' << '-' * 78 << "+\n" + + report << '| ' << "Rule Invocations".center( 76 ) << " |\n" + report << '+' << '-' * 68 << '+' << '-' * 9 << "+\n" + report << "| %-66s | %7i |\n" % [ "Total Invocations", rule_invocations ] + report << "| %-66s | %7i |\n" % [ "``Guessing'' Invocations", guessing_rule_invocations ] + report << "| %-66s | %7i |\n" % [ "Deepest Level of Invocation", rule_invocation_depth ] + report << '+' << '-' * 68 << '+' << '-' * 9 << "+\n" + + report << '| ' << "Execution Events".center( 76 ) << " |\n" + report << '+' << '-' * 68 << '+' << '-' * 9 << "+\n" + report << "| %-66s | %7i |\n" % [ "Semantic Predicates Evaluated", semantic_predicates ] + report << "| %-66s | %7i |\n" % [ "Syntactic Predicates Evaluated", syntactic_predicates ] + report << "| %-66s | %7i |\n" % [ "Errors Reported", reported_errors ] + report << '+' << '-' * 68 << '+' << '-' * 9 << "+\n" + + report << '| ' << "Token and Character Data".center( 76 ) << " |\n" + report << '+' << '-' * 68 << '+' << '-' * 9 << "+\n" + report << "| %-66s | %7i |\n" % [ "Tokens Consumed", tokens ] + report << "| %-66s | %7i |\n" % [ "Hidden Tokens Consumed", hidden_tokens ] + report << "| %-66s | %7i |\n" % [ "Characters Matched", characters_matched ] + report << "| %-66s | %7i |\n" % [ "Hidden Characters Matched", hidden_characters_matched ] + report << '+' << '-' * 68 << '+' << '-' * 9 << "+\n" + + report << '| ' << "Memoization".center( 76 ) << " |\n" + report << '+' << '-' * 68 << '+' << '-' * 9 << "+\n" + report << "| %-66s | %7i |\n" % [ "Cache Entries", memoization_cache_entries ] + report << "| %-66s | %7i |\n" % [ "Cache Hits", memoization_cache_hits ] + report << "| %-66s | %7i |\n" % [ "Cache Misses", memoization_cache_misses ] + report << '+' << '-' * 68 << '+' << '-' * 9 << "+\n" + + [ + [ 'Fixed Lookahead (k)', fixed_looks ], + [ 'Arbitrary Lookahead (k)', cyclic_looks ], + [ 'Backtracking (Syntactic Predicate)', syntactic_predicate_looks ] + ].each do |name, set| + mean, stdev = '%4.2f' % set.average, '%4.2f' % set.standard_deviation + report << '| ' << "#{ name } Decisions".center( 76 ) << " |\n" + report << '+' << '-' * 68 << '+' << '-' * 9 << "+\n" + report << "| %-66s | %7i |\n" % [ "Count", set.length ] + report << "| %-66s | %7i |\n" % [ "Minimum k", set.min ] + report << "| %-66s | %7i |\n" % [ "Maximum k", set.max ] + report << "| %-66s | %7s |\n" % [ "Average k", mean ] + report << "| %-66s | %7s |\n" % [ "Standard Deviation of k", stdev ] + report << '+' << '-' * 68 << '+' << '-' * 9 << "+\n" + end + return( report ) + end +end + +=begin rdoc ANTLR3::Profile::Profiler + +When ANTLR is run with the <tt>-profile</tt> switch, it generates recognition +code that performs accounting about the decision logic performed while parsing +any given input. This information can be used to help refactor a slow grammar. +Profiler is an event-listener that performs all of the profiling accounting and +builds a simple report to present the various statistics. + +=end +class Profiler + include Debug::EventListener + include Constants + + PROTOCOL_VERSION = 2 + + attr_accessor :parser + attr_reader :rule_level + attr_reader :decision_level + + # tracks the maximum look value for the current decision + # (maxLookaheadInCurrentDecision in java Profiler) + attr_reader :decision_look + + # the last token consumed + # (lastTokenConsumed in java Profiler) + attr_reader :last_token + attr_reader :look_stack + attr_reader :profile + + attr_accessor :output + + def initialize( parser = nil, output = nil ) + @parser = parser + @profile = nil + @rule_level = 0 + @decision_level = 0 + @decision_look = 0 + @last_token = nil + @look_stack = [] + @output = output + end + + def commence + @profile = Profile.new + @rule_level = 0 + @decision_level = 0 + @decision_look = 0 + @last_token = nil + @look_stack = [] + end + + def enter_rule( grammar_file_name, rule_name ) + if @rule_level.zero? + commence + @profile.grammar_file = grammar_file_name + @profile.parser_class = @parser.class + @profile.top_rule = rule_name + end + @rule_level += 1 + @profile.rule_invocations += 1 + @profile.rule_invocation_depth < @rule_level and + @profile.rule_invocation_depth = @rule_level + end + + def exit_rule( grammar_file_name, rule_name ) + @rule_level -= 1 + end + + def examine_rule_memoization( rule ) + stop_index = parser.rule_memoization( rule, @parser.input.index ) + if stop_index == MEMO_RULE_UNKNOWN + @profile.memoization_cache_misses += 1 + @profile.guessing_rule_invocations += 1 + else + @profile.memoization_cache_hits += 1 + end + end + + def memoize( rule, start_index, success ) + @profile.memoization_cache_entries += 1 + end + + + def enter_decision( decision_number ) + @decision_level += 1 + starting_look_index = @parser.input.index + @look_stack << starting_look_index + end + + def exit_decision( decision_number ) + @look_stack.pop + @decision_level -= 1 + if @parser.cyclic_decision? then + @profile.cyclic_looks << @decision_look + else @profile.fixed_looks << @decision_look + end + + @parser.cyclic_decision = false + @decision_look = 0 + end + + def consume_token( token ) + @last_token = token + end + + def in_decision? + return( @decision_level > 0 ) + end + + def consume_hidden_token( token ) + @last_token = token + end + + def look( i, token ) + in_decision? or return + starting_index = look_stack.last + input = @parser.input + this_ref_index = input.index + num_hidden = input.tokens( starting_index, this_ref_index ).count { |t| t.hidden? } + depth = i + this_ref_index - starting_index - num_hidden + if depth > @decision_look + @decision_look = depth + end + end + + def end_backtrack( level, successful ) + @profile.syntactic_predicate_looks << @decision_look + end + + def recognition_exception( error ) + @profile.reported_errors += 1 + end + + def semantic_predicate( result, predicate ) + in_decision? and @profile.semantic_predicates += 1 + end + + def terminate + input = @parser.input + hidden_tokens = input.select { |token| token.hidden? } + @profile.hidden_tokens = hidden_tokens.length + @profile.tokens = input.tokens.length + @profile.hidden_characters_matched = hidden_tokens.inject( 0 ) do |count, token| + count + token.text.length rescue count + end + @profile.characters_matched = ( @last_token || input.tokens.last ).stop + 1 rescue 0 + write_report + end + + + def write_report + @output << @profile.generate_report unless @output.nil? + rescue NoMethodError => error + if error.name.to_s == '<<' + warn( <<-END.strip! % [ __FILE__, __LINE__, @output ] ) + [%s @ %s]: failed to write report to %p as it does not respond to :<< + END + else raise + end + rescue IOError => error + $stderr.puts( Util.tidy( <<-END ) % [ __FILE__, __LINE__, @output, error.class, error.message ] ) + | [%s @ %s]: failed to write profile report to %p due to an IO Error: + | %s: %s + END + $stderr.puts( error.backtrace.map { |call| " - #{ call }" }.join( "\n" ) ) + end + + def report + @profile.generate_report + end + + alias to_s report +end +end +end |