Module: LexerKit::Debug::Visualizer

Defined in:
lib/lexer_kit/debug/visualizer.rb

Overview

Visualizer for DFA tables and Jump tables

Class Method Summary collapse

Class Method Details

.dfa_to_dot(dfa, program: nil) ⇒ String

Format a DFA table as Graphviz DOT format

Parameters:

Returns:

  • (String)


68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
# File 'lib/lexer_kit/debug/visualizer.rb', line 68

def self.dfa_to_dot(dfa, program: nil)
  output = StringIO.new

  output.puts "digraph DFA {"
  output.puts "  rankdir=LR;"
  output.puts "  node [shape=circle];"
  output.puts

  # Mark accept states with double circle
  (1...dfa.state_count).each do |state|
    token = dfa.accept(state)
    if token
      label = format_token(token, program)
      output.puts "  s#{state} [shape=doublecircle, label=\"#{state}\\n#{label}\"];"
    end
  end
  output.puts

  # Build class to byte mapping for edge labels
  bytes_by_class = Hash.new { |h, k| h[k] = [] }
  dfa.byte_class.each_with_index do |cls, byte|
    bytes_by_class[cls] << byte
  end

  # Transitions
  (1...dfa.state_count).each do |state|
    (0...dfa.class_count).each do |cls|
      next_state = dfa.transitions[(state * dfa.class_count) + cls]
      next if next_state.nil? || next_state == 0 # Skip dead state

      bytes = bytes_by_class[cls]
      label = compress_ranges(bytes)
      output.puts "  s#{state} -> s#{next_state} [label=\"#{escape_dot(label)}\"];"
    end
  end

  output.puts "}"
  output.string
end

.format_dfa(dfa, program: nil) ⇒ String

Format a DFA table as a readable string

Parameters:

Returns:

  • (String)


13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
# File 'lib/lexer_kit/debug/visualizer.rb', line 13

def self.format_dfa(dfa, program: nil)
  output = StringIO.new

  output.puts "DFA Table"
  output.puts "  States: #{dfa.state_count}"
  output.puts "  Classes: #{dfa.class_count}"
  output.puts

  # Byte class mapping (show non-zero classes)
  output.puts "  Byte Classes:"
  classes_by_value = Hash.new { |h, k| h[k] = [] }
  dfa.byte_class.each_with_index do |cls, byte|
    classes_by_value[cls] << byte if cls > 0
  end
  classes_by_value.sort.each do |cls, bytes|
    byte_ranges = compress_ranges(bytes)
    output.puts "    Class #{cls}: #{byte_ranges}"
  end
  output.puts

  # Transitions (non-dead state transitions)
  output.puts "  Transitions:"
  (1...dfa.state_count).each do |state|
    transitions = []
    (0...dfa.class_count).each do |cls|
      # Access transitions array directly: transitions[state * class_count + cls]
      next_state = dfa.transitions[(state * dfa.class_count) + cls]
      if next_state && next_state != 0 # Not dead state
        transitions << "c#{cls}->s#{next_state}"
      end
    end
    next unless transitions.any?

    accept = dfa.accept(state)
    accept_str = accept ? " [accept: #{format_token(accept, program)}]" : ""
    output.puts "    State #{state}: #{transitions.join(', ')}#{accept_str}"
  end
  output.puts

  # Accept states
  output.puts "  Accept States:"
  (1...dfa.state_count).each do |state|
    token = dfa.accept(state)
    if token
      output.puts "    State #{state}: #{format_token(token, program)}"
    end
  end

  output.string
end

.format_jump_table(table) ⇒ String

Format a JumpTable as a readable string

Parameters:

Returns:

  • (String)


111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
# File 'lib/lexer_kit/debug/visualizer.rb', line 111

def self.format_jump_table(table)
  output = StringIO.new

  output.puts "Jump Table"
  output.puts "  Default: -> #{format('%04d', table.default_offset)}"
  output.puts "  Entries:"

  # Group entries by target offset
  by_offset = Hash.new { |h, k| h[k] = [] }
  table.entries.each do |byte, offset|
    by_offset[offset] << byte
  end

  by_offset.sort.each do |offset, bytes|
    byte_str = compress_ranges(bytes)
    output.puts "    #{byte_str} -> #{format('%04d', offset)}"
  end

  output.string
end

.format_keyword_table(table, program: nil) ⇒ String

Format a KeywordTable as a readable string

Parameters:

Returns:

  • (String)


136
137
138
139
140
141
142
143
144
145
146
147
148
# File 'lib/lexer_kit/debug/visualizer.rb', line 136

def self.format_keyword_table(table, program: nil)
  output = StringIO.new

  output.puts "Keyword Table"
  output.puts "  Base Token: #{format_token(table.base_token_id, program)}"
  output.puts "  Keywords:"

  table.keywords.each do |keyword, token_id|
    output.puts "    #{keyword.inspect} -> #{format_token(token_id, program)}"
  end

  output.string
end