# frozen_string_literal: true require 'ostruct' module Rley # This module is used as a namespace module Parser # This module is used as a namespace # Utility class used to trace the parsing of a token sequence. class ParseTracer # The stream where the trace output is sent attr_reader(:ostream) # The trace level attr_reader(:level) attr_reader(:lexemes) attr_reader(:col_width) def initialize(aTraceLevel, anIO, aTokenSequence) @level = aTraceLevel <= 0 ? 0 : [aTraceLevel, 2].min @ostream = anIO @lexemes = aTokenSequence.map(&:lexeme) emit_tokens emit_heading end # Emit the trace text to the output IO # if the given trace level is equal or greater to the # trace level of the tracer instance. def print_if(aLevel, text) ostream.print(text) if level >= aLevel end # Emit the trace of a scanning step. def trace_scanning(aStatesetIndex, aParseState) return unless level scan_picture = '[' + '-' * (col_width - 1) + ']' org = OpenStruct.new(origin: aStatesetIndex - 1, dotted_rule: aParseState.dotted_rule) trace_diagram(aStatesetIndex, org, scan_picture) end def trace_prediction(aStatesetIndex, aParseState) return unless level trace_diagram(aStatesetIndex, aParseState, '>') end def trace_completion(aStatesetIndex, aParseState) return unless level if aStatesetIndex == lexemes.size && aParseState.origin.zero? && aParseState.complete? picture = '=' * (col_width * lexemes.size - 1) else count = col_width * (aStatesetIndex - aParseState.origin) - 1 picture = '-' * count end completion_picture = '[' + picture + (aParseState.complete? ? ']' : '>') trace_diagram(aStatesetIndex, aParseState, completion_picture) end private def emit_tokens() literals = lexemes.map { |lx| "'#{lx}'" } print_if 1, '[' + literals.join(', ') + "]\n" end def emit_heading() longest = lexemes.map(&:length).max @col_width = longest + 3 headers = lexemes.map { |l| l.center(col_width - 1, ' ').to_s } print_if 1, '|.' + headers.join('.') + ".|\n" end def padding(aStatesetIndex, aParseState, aPicture) l_pad_pattern = '.' + ' ' * (col_width - 1) left_padding = l_pad_pattern * [0, aParseState.origin].max r_pad_pattern = ' ' * (col_width - 1) + '.' right_padding = r_pad_pattern * (lexemes.size - aStatesetIndex) return left_padding + aPicture + right_padding end def parse_state_str(aStatesetIndex, aParseState) "[#{aParseState.origin}:#{aStatesetIndex}] #{aParseState.dotted_rule}" end def trace_diagram(aStatesetIndex, aParseState, aPicture) diagram = padding(aStatesetIndex, aParseState, aPicture) prefix = '|' suffix = '| ' + parse_state_str(aStatesetIndex, aParseState) trace = prefix + diagram + suffix print_if 1, trace + "\n" end end # class end # module end # module # End of file