aboutsummaryrefslogtreecommitdiff
path: root/runtime/Ruby/lib/antlr3/profile.rb
diff options
context:
space:
mode:
Diffstat (limited to 'runtime/Ruby/lib/antlr3/profile.rb')
-rw-r--r--runtime/Ruby/lib/antlr3/profile.rb360
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