lrama 0.5.3 → 0.5.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/test.yaml +24 -1
- data/Gemfile +3 -2
- data/README.md +11 -1
- data/doc/TODO.md +5 -1
- data/exe/lrama +0 -1
- data/lib/lrama/command.rb +5 -10
- data/lib/lrama/context.rb +0 -2
- data/lib/lrama/counterexamples/derivation.rb +63 -0
- data/lib/lrama/counterexamples/example.rb +124 -0
- data/lib/lrama/counterexamples/path.rb +69 -0
- data/lib/lrama/counterexamples/state_item.rb +6 -0
- data/lib/lrama/counterexamples/triple.rb +21 -0
- data/lib/lrama/counterexamples.rb +283 -0
- data/lib/lrama/digraph.rb +2 -3
- data/lib/lrama/grammar/auxiliary.rb +7 -0
- data/lib/lrama/grammar/code.rb +0 -1
- data/lib/lrama/grammar/rule.rb +6 -0
- data/lib/lrama/grammar/symbol.rb +4 -11
- data/lib/lrama/grammar.rb +44 -8
- data/lib/lrama/lexer/token/type.rb +8 -0
- data/lib/lrama/lexer/token.rb +4 -2
- data/lib/lrama/lexer.rb +3 -4
- data/lib/lrama/output.rb +1 -1
- data/lib/lrama/parser/token_scanner.rb +3 -6
- data/lib/lrama/parser.rb +9 -0
- data/lib/lrama/state/reduce_reduce_conflict.rb +9 -0
- data/lib/lrama/state/shift_reduce_conflict.rb +9 -0
- data/lib/lrama/state.rb +11 -4
- data/lib/lrama/states/item.rb +38 -2
- data/lib/lrama/states.rb +28 -34
- data/lib/lrama/states_reporter.rb +29 -16
- data/lib/lrama/type.rb +4 -0
- data/lib/lrama/version.rb +1 -1
- data/lib/lrama.rb +2 -0
- data/template/bison/yacc.c +103 -95
- metadata +13 -2
| @@ -0,0 +1,283 @@ | |
| 1 | 
            +
            require "set"
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require "lrama/counterexamples/derivation"
         | 
| 4 | 
            +
            require "lrama/counterexamples/example"
         | 
| 5 | 
            +
            require "lrama/counterexamples/path"
         | 
| 6 | 
            +
            require "lrama/counterexamples/state_item"
         | 
| 7 | 
            +
            require "lrama/counterexamples/triple"
         | 
| 8 | 
            +
             | 
| 9 | 
            +
            module Lrama
         | 
| 10 | 
            +
              # See: https://www.cs.cornell.edu/andru/papers/cupex/cupex.pdf
         | 
| 11 | 
            +
              #      4. Constructing Nonunifying Counterexamples
         | 
| 12 | 
            +
              class Counterexamples
         | 
| 13 | 
            +
                attr_reader :transitions, :productions
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                def initialize(states)
         | 
| 16 | 
            +
                  @states = states
         | 
| 17 | 
            +
                  setup_transitions
         | 
| 18 | 
            +
                  setup_productions
         | 
| 19 | 
            +
                end
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                def to_s
         | 
| 22 | 
            +
                  "#<Counterexamples>"
         | 
| 23 | 
            +
                end
         | 
| 24 | 
            +
                alias :inspect :to_s
         | 
| 25 | 
            +
             | 
| 26 | 
            +
                def compute(conflict_state)
         | 
| 27 | 
            +
                  conflict_state.conflicts.flat_map do |conflict|
         | 
| 28 | 
            +
                    case conflict.type
         | 
| 29 | 
            +
                    when :shift_reduce
         | 
| 30 | 
            +
                      shift_reduce_example(conflict_state, conflict)
         | 
| 31 | 
            +
                    when :reduce_reduce
         | 
| 32 | 
            +
                      reduce_reduce_examples(conflict_state, conflict)
         | 
| 33 | 
            +
                    end
         | 
| 34 | 
            +
                  end.compact
         | 
| 35 | 
            +
                end
         | 
| 36 | 
            +
             | 
| 37 | 
            +
                private
         | 
| 38 | 
            +
             | 
| 39 | 
            +
                def setup_transitions
         | 
| 40 | 
            +
                  # Hash [StateItem, Symbol] => StateItem
         | 
| 41 | 
            +
                  @transitions = {}
         | 
| 42 | 
            +
                  # Hash [StateItem, Symbol] => Set(StateItem)
         | 
| 43 | 
            +
                  @reverse_transitions = {}
         | 
| 44 | 
            +
             | 
| 45 | 
            +
                  @states.states.each do |src_state|
         | 
| 46 | 
            +
                    trans = {}
         | 
| 47 | 
            +
             | 
| 48 | 
            +
                    src_state.transitions.each do |shift, next_state|
         | 
| 49 | 
            +
                      trans[shift.next_sym] = next_state
         | 
| 50 | 
            +
                    end
         | 
| 51 | 
            +
             | 
| 52 | 
            +
                    src_state.items.each do |src_item|
         | 
| 53 | 
            +
                      next if src_item.end_of_rule?
         | 
| 54 | 
            +
                      sym = src_item.next_sym
         | 
| 55 | 
            +
                      dest_state = trans[sym]
         | 
| 56 | 
            +
             | 
| 57 | 
            +
                      dest_state.kernels.each do |dest_item|
         | 
| 58 | 
            +
                        next unless (src_item.rule == dest_item.rule) && (src_item.position + 1 == dest_item.position)
         | 
| 59 | 
            +
                        src_state_item = StateItem.new(src_state, src_item)
         | 
| 60 | 
            +
                        dest_state_item = StateItem.new(dest_state, dest_item)
         | 
| 61 | 
            +
             | 
| 62 | 
            +
                        @transitions[[src_state_item, sym]] = dest_state_item
         | 
| 63 | 
            +
             | 
| 64 | 
            +
                        key = [dest_state_item, sym]
         | 
| 65 | 
            +
                        @reverse_transitions[key] ||= Set.new
         | 
| 66 | 
            +
                        @reverse_transitions[key] << src_state_item
         | 
| 67 | 
            +
                      end
         | 
| 68 | 
            +
                    end
         | 
| 69 | 
            +
                  end
         | 
| 70 | 
            +
                end
         | 
| 71 | 
            +
             | 
| 72 | 
            +
                def setup_productions
         | 
| 73 | 
            +
                  # Hash [StateItem] => Set(Item)
         | 
| 74 | 
            +
                  @productions = {}
         | 
| 75 | 
            +
                  # Hash [State, Symbol] => Set(Item). Symbol is nterm
         | 
| 76 | 
            +
                  @reverse_productions = {}
         | 
| 77 | 
            +
             | 
| 78 | 
            +
                  @states.states.each do |state|
         | 
| 79 | 
            +
                    # LHS => Set(Item)
         | 
| 80 | 
            +
                    h = {}
         | 
| 81 | 
            +
             | 
| 82 | 
            +
                    state.closure.each do |item|
         | 
| 83 | 
            +
                      sym = item.lhs
         | 
| 84 | 
            +
             | 
| 85 | 
            +
                      h[sym] ||= Set.new
         | 
| 86 | 
            +
                      h[sym] << item
         | 
| 87 | 
            +
                    end
         | 
| 88 | 
            +
             | 
| 89 | 
            +
                    state.items.each do |item|
         | 
| 90 | 
            +
                      next if item.end_of_rule?
         | 
| 91 | 
            +
                      next if item.next_sym.term?
         | 
| 92 | 
            +
             | 
| 93 | 
            +
                      sym = item.next_sym
         | 
| 94 | 
            +
                      state_item = StateItem.new(state, item)
         | 
| 95 | 
            +
                      key = [state, sym]
         | 
| 96 | 
            +
             | 
| 97 | 
            +
                      @productions[state_item] = h[sym]
         | 
| 98 | 
            +
             | 
| 99 | 
            +
                      @reverse_productions[key] ||= Set.new
         | 
| 100 | 
            +
                      @reverse_productions[key] << item
         | 
| 101 | 
            +
                    end
         | 
| 102 | 
            +
                  end
         | 
| 103 | 
            +
                end
         | 
| 104 | 
            +
             | 
| 105 | 
            +
                def shift_reduce_example(conflict_state, conflict)
         | 
| 106 | 
            +
                  conflict_symbol = conflict.symbols.first
         | 
| 107 | 
            +
                  shift_conflict_item = conflict_state.items.find { |item| item.next_sym == conflict_symbol }
         | 
| 108 | 
            +
                  path2 = shortest_path(conflict_state, conflict.reduce.item, conflict_symbol)
         | 
| 109 | 
            +
                  path1 = find_shift_conflict_shortest_path(path2, conflict_state, shift_conflict_item)
         | 
| 110 | 
            +
             | 
| 111 | 
            +
                  Example.new(path1, path2, conflict, conflict_symbol, self)
         | 
| 112 | 
            +
                end
         | 
| 113 | 
            +
             | 
| 114 | 
            +
                def reduce_reduce_examples(conflict_state, conflict)
         | 
| 115 | 
            +
                  conflict_symbol = conflict.symbols.first
         | 
| 116 | 
            +
                  path1 = shortest_path(conflict_state, conflict.reduce1.item, conflict_symbol)
         | 
| 117 | 
            +
                  path2 = shortest_path(conflict_state, conflict.reduce2.item, conflict_symbol)
         | 
| 118 | 
            +
             | 
| 119 | 
            +
                  Example.new(path1, path2, conflict, conflict_symbol, self)
         | 
| 120 | 
            +
                end
         | 
| 121 | 
            +
             | 
| 122 | 
            +
                def find_shift_conflict_shortest_path(reduce_path, conflict_state, conflict_item)
         | 
| 123 | 
            +
                  state_items = find_shift_conflict_shortest_state_items(reduce_path, conflict_state, conflict_item)
         | 
| 124 | 
            +
                  build_paths_from_state_items(state_items)
         | 
| 125 | 
            +
                end
         | 
| 126 | 
            +
             | 
| 127 | 
            +
                def find_shift_conflict_shortest_state_items(reduce_path, conflict_state, conflict_item)
         | 
| 128 | 
            +
                  target_state_item = StateItem.new(conflict_state, conflict_item)
         | 
| 129 | 
            +
                  result = [target_state_item]
         | 
| 130 | 
            +
                  reversed_reduce_path = reduce_path.to_a.reverse
         | 
| 131 | 
            +
                  # Index for state_item
         | 
| 132 | 
            +
                  i = 0
         | 
| 133 | 
            +
             | 
| 134 | 
            +
                  while (path = reversed_reduce_path[i])
         | 
| 135 | 
            +
                    # Index for prev_state_item
         | 
| 136 | 
            +
                    j = i + 1
         | 
| 137 | 
            +
                    _j = j
         | 
| 138 | 
            +
             | 
| 139 | 
            +
                    while (prev_path = reversed_reduce_path[j])
         | 
| 140 | 
            +
                      if prev_path.production?
         | 
| 141 | 
            +
                        j += 1
         | 
| 142 | 
            +
                      else
         | 
| 143 | 
            +
                        break
         | 
| 144 | 
            +
                      end
         | 
| 145 | 
            +
                    end
         | 
| 146 | 
            +
             | 
| 147 | 
            +
                    state_item = path.to
         | 
| 148 | 
            +
                    prev_state_item = prev_path&.to
         | 
| 149 | 
            +
             | 
| 150 | 
            +
                    if target_state_item == state_item || target_state_item.item.start_item?
         | 
| 151 | 
            +
                      result.concat(reversed_reduce_path[_j..-1].map(&:to))
         | 
| 152 | 
            +
                      break
         | 
| 153 | 
            +
                    end
         | 
| 154 | 
            +
             | 
| 155 | 
            +
                    if target_state_item.item.beginning_of_rule?
         | 
| 156 | 
            +
                      queue = []
         | 
| 157 | 
            +
                      queue << [target_state_item]
         | 
| 158 | 
            +
             | 
| 159 | 
            +
                      # Find reverse production
         | 
| 160 | 
            +
                      while (sis = queue.shift)
         | 
| 161 | 
            +
                        si = sis.last
         | 
| 162 | 
            +
             | 
| 163 | 
            +
                        # Reach to start state
         | 
| 164 | 
            +
                        if si.item.start_item?
         | 
| 165 | 
            +
                          sis.shift
         | 
| 166 | 
            +
                          result.concat(sis)
         | 
| 167 | 
            +
                          target_state_item = si
         | 
| 168 | 
            +
                          break
         | 
| 169 | 
            +
                        end
         | 
| 170 | 
            +
             | 
| 171 | 
            +
                        if !si.item.beginning_of_rule?
         | 
| 172 | 
            +
                          key = [si, si.item.previous_sym]
         | 
| 173 | 
            +
                          @reverse_transitions[key].each do |prev_target_state_item|
         | 
| 174 | 
            +
                            next if prev_target_state_item.state != prev_state_item.state
         | 
| 175 | 
            +
                            sis.shift
         | 
| 176 | 
            +
                            result.concat(sis)
         | 
| 177 | 
            +
                            result << prev_target_state_item
         | 
| 178 | 
            +
                            target_state_item = prev_target_state_item
         | 
| 179 | 
            +
                            i = j
         | 
| 180 | 
            +
                            queue.clear
         | 
| 181 | 
            +
                            break
         | 
| 182 | 
            +
                          end
         | 
| 183 | 
            +
                        else
         | 
| 184 | 
            +
                          key = [si.state, si.item.lhs]
         | 
| 185 | 
            +
                          @reverse_productions[key].each do |item|
         | 
| 186 | 
            +
                            state_item = StateItem.new(si.state, item)
         | 
| 187 | 
            +
                            queue << (sis + [state_item])
         | 
| 188 | 
            +
                          end
         | 
| 189 | 
            +
                        end
         | 
| 190 | 
            +
                      end
         | 
| 191 | 
            +
                    else
         | 
| 192 | 
            +
                      # Find reverse transition
         | 
| 193 | 
            +
                      key = [target_state_item, target_state_item.item.previous_sym]
         | 
| 194 | 
            +
                      @reverse_transitions[key].each do |prev_target_state_item|
         | 
| 195 | 
            +
                        next if prev_target_state_item.state != prev_state_item.state
         | 
| 196 | 
            +
                        result << prev_target_state_item
         | 
| 197 | 
            +
                        target_state_item = prev_target_state_item
         | 
| 198 | 
            +
                        i = j
         | 
| 199 | 
            +
                        break
         | 
| 200 | 
            +
                      end
         | 
| 201 | 
            +
                    end
         | 
| 202 | 
            +
                  end
         | 
| 203 | 
            +
             | 
| 204 | 
            +
                  result.reverse
         | 
| 205 | 
            +
                end
         | 
| 206 | 
            +
             | 
| 207 | 
            +
                def build_paths_from_state_items(state_items)
         | 
| 208 | 
            +
                  state_items.zip([nil] + state_items).map do |si, prev_si|
         | 
| 209 | 
            +
                    case
         | 
| 210 | 
            +
                    when prev_si.nil?
         | 
| 211 | 
            +
                      StartPath.new(si)
         | 
| 212 | 
            +
                    when si.item.beginning_of_rule?
         | 
| 213 | 
            +
                      ProductionPath.new(prev_si, si)
         | 
| 214 | 
            +
                    else
         | 
| 215 | 
            +
                      TransitionPath.new(prev_si, si)
         | 
| 216 | 
            +
                    end
         | 
| 217 | 
            +
                  end
         | 
| 218 | 
            +
                end
         | 
| 219 | 
            +
             | 
| 220 | 
            +
                def shortest_path(conflict_state, conflict_reduce_item, conflict_term)
         | 
| 221 | 
            +
                  # queue: is an array of [Triple, [Path]]
         | 
| 222 | 
            +
                  queue = []
         | 
| 223 | 
            +
                  visited = {}
         | 
| 224 | 
            +
                  start_state = @states.states.first
         | 
| 225 | 
            +
                  raise "BUG: Start state should be just one kernel." if start_state.kernels.count != 1
         | 
| 226 | 
            +
             | 
| 227 | 
            +
                  start = Triple.new(start_state, start_state.kernels.first, Set.new([@states.eof_symbol]))
         | 
| 228 | 
            +
             | 
| 229 | 
            +
                  queue << [start, [StartPath.new(start.state_item)]]
         | 
| 230 | 
            +
             | 
| 231 | 
            +
                  while true
         | 
| 232 | 
            +
                    triple, paths = queue.shift
         | 
| 233 | 
            +
             | 
| 234 | 
            +
                    next if visited[triple]
         | 
| 235 | 
            +
                    visited[triple] = true
         | 
| 236 | 
            +
             | 
| 237 | 
            +
                    # Found
         | 
| 238 | 
            +
                    if triple.state == conflict_state && triple.item == conflict_reduce_item && triple.l.include?(conflict_term)
         | 
| 239 | 
            +
                      return paths
         | 
| 240 | 
            +
                    end
         | 
| 241 | 
            +
             | 
| 242 | 
            +
                    # transition
         | 
| 243 | 
            +
                    triple.state.transitions.each do |shift, next_state|
         | 
| 244 | 
            +
                      next unless triple.item.next_sym && triple.item.next_sym == shift.next_sym
         | 
| 245 | 
            +
                      next_state.kernels.each do |kernel|
         | 
| 246 | 
            +
                        next if kernel.rule != triple.item.rule
         | 
| 247 | 
            +
                        t = Triple.new(next_state, kernel, triple.l)
         | 
| 248 | 
            +
                        queue << [t, paths + [TransitionPath.new(triple.state_item, t.state_item)]]
         | 
| 249 | 
            +
                      end
         | 
| 250 | 
            +
                    end
         | 
| 251 | 
            +
             | 
| 252 | 
            +
                    # production step
         | 
| 253 | 
            +
                    triple.state.closure.each do |item|
         | 
| 254 | 
            +
                      next unless triple.item.next_sym && triple.item.next_sym == item.lhs
         | 
| 255 | 
            +
                      l = follow_l(triple.item, triple.l)
         | 
| 256 | 
            +
                      t = Triple.new(triple.state, item, l)
         | 
| 257 | 
            +
                      queue << [t, paths + [ProductionPath.new(triple.state_item, t.state_item)]]
         | 
| 258 | 
            +
                    end
         | 
| 259 | 
            +
             | 
| 260 | 
            +
                    break if queue.empty?
         | 
| 261 | 
            +
                  end
         | 
| 262 | 
            +
             | 
| 263 | 
            +
                  return nil
         | 
| 264 | 
            +
                end
         | 
| 265 | 
            +
             | 
| 266 | 
            +
                def follow_l(item, current_l)
         | 
| 267 | 
            +
                  # 1. follow_L (A -> X1 ... Xn-1 • Xn) = L
         | 
| 268 | 
            +
                  # 2. follow_L (A -> X1 ... Xk • Xk+1 Xk+2 ... Xn) = {Xk+2} if Xk+2 is a terminal
         | 
| 269 | 
            +
                  # 3. follow_L (A -> X1 ... Xk • Xk+1 Xk+2 ... Xn) = FIRST(Xk+2) if Xk+2 is a nonnullable nonterminal
         | 
| 270 | 
            +
                  # 4. follow_L (A -> X1 ... Xk • Xk+1 Xk+2 ... Xn) = FIRST(Xk+2) + follow_L (A -> X1 ... Xk+1 • Xk+2 ... Xn) if Xk+2 is a nullable nonterminal
         | 
| 271 | 
            +
                  case
         | 
| 272 | 
            +
                  when item.number_of_rest_symbols == 1
         | 
| 273 | 
            +
                    current_l
         | 
| 274 | 
            +
                  when item.next_next_sym.term?
         | 
| 275 | 
            +
                    Set.new([item.next_next_sym])
         | 
| 276 | 
            +
                  when !item.next_next_sym.nullable
         | 
| 277 | 
            +
                    item.next_next_sym.first_set
         | 
| 278 | 
            +
                  else
         | 
| 279 | 
            +
                    item.next_next_sym.first_set + follow_l(item.new_by_next_position, current_l)
         | 
| 280 | 
            +
                  end
         | 
| 281 | 
            +
                end
         | 
| 282 | 
            +
              end
         | 
| 283 | 
            +
            end
         | 
    
        data/lib/lrama/digraph.rb
    CHANGED
    
    | @@ -33,7 +33,7 @@ module Lrama | |
| 33 33 | 
             
                  @h[x] = d
         | 
| 34 34 | 
             
                  @result[x] = @base_function[x] # F x = F' x
         | 
| 35 35 |  | 
| 36 | 
            -
                  @relation[x] | 
| 36 | 
            +
                  @relation[x]&.each do |y|
         | 
| 37 37 | 
             
                    traverse(y) if @h[y] == 0
         | 
| 38 38 | 
             
                    @h[x] = [@h[x], @h[y]].min
         | 
| 39 39 | 
             
                    @result[x] |= @result[y] # F x = F x + F y
         | 
| @@ -43,9 +43,8 @@ module Lrama | |
| 43 43 | 
             
                    while true do
         | 
| 44 44 | 
             
                      z = @stack.pop
         | 
| 45 45 | 
             
                      @h[z] = Float::INFINITY
         | 
| 46 | 
            -
                      @result[z] = @result[x] # F (Top of S) = F x
         | 
| 47 | 
            -
             | 
| 48 46 | 
             
                      break if z == x
         | 
| 47 | 
            +
                      @result[z] = @result[x] # F (Top of S) = F x
         | 
| 49 48 | 
             
                    end
         | 
| 50 49 | 
             
                  end
         | 
| 51 50 | 
             
                end
         | 
    
        data/lib/lrama/grammar/code.rb
    CHANGED
    
    
    
        data/lib/lrama/grammar/rule.rb
    CHANGED
    
    
    
        data/lib/lrama/grammar/symbol.rb
    CHANGED
    
    | @@ -7,6 +7,7 @@ | |
| 7 7 | 
             
            module Lrama
         | 
| 8 8 | 
             
              class Grammar
         | 
| 9 9 | 
             
                class Symbol < Struct.new(:id, :alias_name, :number, :tag, :term, :token_id, :nullable, :precedence, :printer, :error_token, keyword_init: true)
         | 
| 10 | 
            +
                  attr_accessor :first_set, :first_set_bitmap
         | 
| 10 11 | 
             
                  attr_writer :eof_symbol, :error_symbol, :undef_symbol, :accept_symbol
         | 
| 11 12 |  | 
| 12 13 | 
             
                  def term?
         | 
| @@ -34,11 +35,7 @@ module Lrama | |
| 34 35 | 
             
                  end
         | 
| 35 36 |  | 
| 36 37 | 
             
                  def display_name
         | 
| 37 | 
            -
                     | 
| 38 | 
            -
                      alias_name
         | 
| 39 | 
            -
                    else
         | 
| 40 | 
            -
                      id.s_value
         | 
| 41 | 
            -
                    end
         | 
| 38 | 
            +
                    alias_name || id.s_value
         | 
| 42 39 | 
             
                  end
         | 
| 43 40 |  | 
| 44 41 | 
             
                  # name for yysymbol_kind_t
         | 
| @@ -51,11 +48,7 @@ module Lrama | |
| 51 48 | 
             
                    when eof_symbol?
         | 
| 52 49 | 
             
                      name = "YYEOF"
         | 
| 53 50 | 
             
                    when term? && id.type == Token::Char
         | 
| 54 | 
            -
                       | 
| 55 | 
            -
                        name = number.to_s + alias_name
         | 
| 56 | 
            -
                      else
         | 
| 57 | 
            -
                        name = number.to_s + id.s_value
         | 
| 58 | 
            -
                      end
         | 
| 51 | 
            +
                      name = number.to_s + display_name
         | 
| 59 52 | 
             
                    when term? && id.type == Token::Ident
         | 
| 60 53 | 
             
                      name = id.s_value
         | 
| 61 54 | 
             
                    when nterm? && (id.s_value.include?("$") || id.s_value.include?("@"))
         | 
| @@ -66,7 +59,7 @@ module Lrama | |
| 66 59 | 
             
                      raise "Unexpected #{self}"
         | 
| 67 60 | 
             
                    end
         | 
| 68 61 |  | 
| 69 | 
            -
                    "YYSYMBOL_" + name.gsub( | 
| 62 | 
            +
                    "YYSYMBOL_" + name.gsub(/\W+/, "_")
         | 
| 70 63 | 
             
                  end
         | 
| 71 64 |  | 
| 72 65 | 
             
                  # comment for yysymbol_kind_t
         | 
    
        data/lib/lrama/grammar.rb
    CHANGED
    
    | @@ -1,3 +1,4 @@ | |
| 1 | 
            +
            require "lrama/grammar/auxiliary"
         | 
| 1 2 | 
             
            require "lrama/grammar/code"
         | 
| 2 3 | 
             
            require "lrama/grammar/error_token"
         | 
| 3 4 | 
             
            require "lrama/grammar/precedence"
         | 
| @@ -7,16 +8,13 @@ require "lrama/grammar/rule" | |
| 7 8 | 
             
            require "lrama/grammar/symbol"
         | 
| 8 9 | 
             
            require "lrama/grammar/union"
         | 
| 9 10 | 
             
            require "lrama/lexer"
         | 
| 11 | 
            +
            require "lrama/type"
         | 
| 10 12 |  | 
| 11 13 | 
             
            module Lrama
         | 
| 12 | 
            -
              Type = Struct.new(:id, :tag, keyword_init: true)
         | 
| 13 14 | 
             
              Token = Lrama::Lexer::Token
         | 
| 14 15 |  | 
| 15 16 | 
             
              # Grammar is the result of parsing an input grammar file
         | 
| 16 17 | 
             
              class Grammar
         | 
| 17 | 
            -
                # Grammar file information not used by States but by Output
         | 
| 18 | 
            -
                Aux = Struct.new(:prologue_first_lineno, :prologue, :epilogue_first_lineno, :epilogue, keyword_init: true)
         | 
| 19 | 
            -
             | 
| 20 18 | 
             
                attr_reader :eof_symbol, :error_symbol, :undef_symbol, :accept_symbol, :aux
         | 
| 21 19 | 
             
                attr_accessor :union, :expect,
         | 
| 22 20 | 
             
                              :printers, :error_tokens,
         | 
| @@ -38,7 +36,7 @@ module Lrama | |
| 38 36 | 
             
                  @error_symbol = nil
         | 
| 39 37 | 
             
                  @undef_symbol = nil
         | 
| 40 38 | 
             
                  @accept_symbol = nil
         | 
| 41 | 
            -
                  @aux =  | 
| 39 | 
            +
                  @aux = Auxiliary.new
         | 
| 42 40 |  | 
| 43 41 | 
             
                  append_special_symbols
         | 
| 44 42 | 
             
                end
         | 
| @@ -48,7 +46,7 @@ module Lrama | |
| 48 46 | 
             
                end
         | 
| 49 47 |  | 
| 50 48 | 
             
                def add_error_token(ident_or_tags:, code:, lineno:)
         | 
| 51 | 
            -
                  @error_tokens << ErrorToken.new(ident_or_tags, code, lineno)
         | 
| 49 | 
            +
                  @error_tokens << ErrorToken.new(ident_or_tags: ident_or_tags, code: code, lineno: lineno)
         | 
| 52 50 | 
             
                end
         | 
| 53 51 |  | 
| 54 52 | 
             
                def add_term(id:, alias_name: nil, tag: nil, token_id: nil, replace: false)
         | 
| @@ -105,6 +103,10 @@ module Lrama | |
| 105 103 | 
             
                  set_precedence(sym, Precedence.new(type: :right, precedence: precedence))
         | 
| 106 104 | 
             
                end
         | 
| 107 105 |  | 
| 106 | 
            +
                def add_precedence(sym, precedence)
         | 
| 107 | 
            +
                  set_precedence(sym, Precedence.new(type: :precedence, precedence: precedence))
         | 
| 108 | 
            +
                end
         | 
| 109 | 
            +
             | 
| 108 110 | 
             
                def set_precedence(sym, precedence)
         | 
| 109 111 | 
             
                  raise "" if sym.nterm?
         | 
| 110 112 | 
             
                  sym.precedence = precedence
         | 
| @@ -215,6 +217,41 @@ module Lrama | |
| 215 217 | 
             
                  end
         | 
| 216 218 | 
             
                end
         | 
| 217 219 |  | 
| 220 | 
            +
                def compute_first_set
         | 
| 221 | 
            +
                  terms.each do |term|
         | 
| 222 | 
            +
                    term.first_set = Set.new([term]).freeze
         | 
| 223 | 
            +
                    term.first_set_bitmap = Lrama::Bitmap.from_array([term.number])
         | 
| 224 | 
            +
                  end
         | 
| 225 | 
            +
             | 
| 226 | 
            +
                  nterms.each do |nterm|
         | 
| 227 | 
            +
                    nterm.first_set = Set.new([]).freeze
         | 
| 228 | 
            +
                    nterm.first_set_bitmap = Lrama::Bitmap.from_array([])
         | 
| 229 | 
            +
                  end
         | 
| 230 | 
            +
             | 
| 231 | 
            +
                  while true do
         | 
| 232 | 
            +
                    changed = false
         | 
| 233 | 
            +
             | 
| 234 | 
            +
                    @rules.each do |rule|
         | 
| 235 | 
            +
                      rule.rhs.each do |r|
         | 
| 236 | 
            +
                        if rule.lhs.first_set_bitmap | r.first_set_bitmap != rule.lhs.first_set_bitmap
         | 
| 237 | 
            +
                          changed = true
         | 
| 238 | 
            +
                          rule.lhs.first_set_bitmap = rule.lhs.first_set_bitmap | r.first_set_bitmap
         | 
| 239 | 
            +
                        end
         | 
| 240 | 
            +
             | 
| 241 | 
            +
                        break unless r.nullable
         | 
| 242 | 
            +
                      end
         | 
| 243 | 
            +
                    end
         | 
| 244 | 
            +
             | 
| 245 | 
            +
                    break unless changed
         | 
| 246 | 
            +
                  end
         | 
| 247 | 
            +
             | 
| 248 | 
            +
                  nterms.each do |nterm|
         | 
| 249 | 
            +
                    nterm.first_set = Lrama::Bitmap.to_array(nterm.first_set_bitmap).map do |number|
         | 
| 250 | 
            +
                      find_symbol_by_number!(number)
         | 
| 251 | 
            +
                    end.to_set
         | 
| 252 | 
            +
                  end
         | 
| 253 | 
            +
                end
         | 
| 254 | 
            +
             | 
| 218 255 | 
             
                def find_symbol_by_s_value(s_value)
         | 
| 219 256 | 
             
                  @symbols.find do |sym|
         | 
| 220 257 | 
             
                    sym.id.s_value == s_value
         | 
| @@ -277,7 +314,6 @@ module Lrama | |
| 277 314 | 
             
                  end || (raise "Nterm not found: #{id}")
         | 
| 278 315 | 
             
                end
         | 
| 279 316 |  | 
| 280 | 
            -
             | 
| 281 317 | 
             
                def append_special_symbols
         | 
| 282 318 | 
             
                  # YYEMPTY (token_id: -2, number: -2) is added when a template is evaluated
         | 
| 283 319 | 
             
                  # term = add_term(id: Token.new(Token::Ident, "YYEMPTY"), token_id: -2)
         | 
| @@ -479,7 +515,7 @@ module Lrama | |
| 479 515 | 
             
                          sym.token_id = 11
         | 
| 480 516 | 
             
                        when "\""
         | 
| 481 517 | 
             
                          sym.token_id = 34
         | 
| 482 | 
            -
                        when " | 
| 518 | 
            +
                        when "'"
         | 
| 483 519 | 
             
                          sym.token_id = 39
         | 
| 484 520 | 
             
                        when "\\\\"
         | 
| 485 521 | 
             
                          sym.token_id = 92
         | 
    
        data/lib/lrama/lexer/token.rb
    CHANGED
    
    | @@ -1,7 +1,8 @@ | |
| 1 | 
            +
            require 'lrama/lexer/token/type'
         | 
| 2 | 
            +
             | 
| 1 3 | 
             
            module Lrama
         | 
| 2 4 | 
             
              class Lexer
         | 
| 3 | 
            -
                class Token | 
| 4 | 
            -
                  Type = Struct.new(:id, :name, keyword_init: true)
         | 
| 5 | 
            +
                class Token
         | 
| 5 6 |  | 
| 6 7 | 
             
                  attr_accessor :line, :column, :referred
         | 
| 7 8 | 
             
                  # For User_code
         | 
| @@ -60,6 +61,7 @@ module Lrama | |
| 60 61 | 
             
                  define_type(:P_nonassoc)       # %nonassoc
         | 
| 61 62 | 
             
                  define_type(:P_left)           # %left
         | 
| 62 63 | 
             
                  define_type(:P_right)          # %right
         | 
| 64 | 
            +
                  define_type(:P_precedence)     # %precedence
         | 
| 63 65 | 
             
                  define_type(:P_prec)           # %prec
         | 
| 64 66 | 
             
                  define_type(:User_code)        # { ... }
         | 
| 65 67 | 
             
                  define_type(:Tag)              # <int>
         | 
    
        data/lib/lrama/lexer.rb
    CHANGED
    
    | @@ -30,7 +30,6 @@ module Lrama | |
| 30 30 | 
             
                  @grammar_rules = []
         | 
| 31 31 | 
             
                  @epilogue = []
         | 
| 32 32 |  | 
| 33 | 
            -
                  #
         | 
| 34 33 | 
             
                  @bison_declarations_tokens = []
         | 
| 35 34 | 
             
                  @grammar_rules_tokens = []
         | 
| 36 35 |  | 
| @@ -155,6 +154,8 @@ module Lrama | |
| 155 154 | 
             
                      tokens << create_token(Token::P_left, ss[0], line, ss.pos - column)
         | 
| 156 155 | 
             
                    when ss.scan(/%right/)
         | 
| 157 156 | 
             
                      tokens << create_token(Token::P_right, ss[0], line, ss.pos - column)
         | 
| 157 | 
            +
                    when ss.scan(/%precedence/)
         | 
| 158 | 
            +
                      tokens << create_token(Token::P_precedence, ss[0], line, ss.pos - column)
         | 
| 158 159 | 
             
                    when ss.scan(/%prec/)
         | 
| 159 160 | 
             
                      tokens << create_token(Token::P_prec, ss[0], line, ss.pos - column)
         | 
| 160 161 | 
             
                    when ss.scan(/{/)
         | 
| @@ -223,7 +224,7 @@ module Lrama | |
| 223 224 | 
             
                      references << [:dollar, ss[2], tag, str.length, str.length + ss[0].length - 1]
         | 
| 224 225 | 
             
                    when ss.scan(/@\$/) # @$
         | 
| 225 226 | 
             
                      references << [:at, "$", nil, str.length, str.length + ss[0].length - 1]
         | 
| 226 | 
            -
                    when ss.scan(/@(\d) | 
| 227 | 
            +
                    when ss.scan(/@(\d+)/) # @1
         | 
| 227 228 | 
             
                      references << [:at, Integer(ss[1]), nil, str.length, str.length + ss[0].length - 1]
         | 
| 228 229 | 
             
                    when ss.scan(/{/)
         | 
| 229 230 | 
             
                      brace_count += 1
         | 
| @@ -314,8 +315,6 @@ module Lrama | |
| 314 315 | 
             
                      str << ss.getch
         | 
| 315 316 | 
             
                      next
         | 
| 316 317 | 
             
                    end
         | 
| 317 | 
            -
             | 
| 318 | 
            -
                    str << ss[0]
         | 
| 319 318 | 
             
                  end
         | 
| 320 319 |  | 
| 321 320 | 
             
                  line # Reach to end of input
         | 
    
        data/lib/lrama/output.rb
    CHANGED
    
    
| @@ -11,7 +11,7 @@ module Lrama | |
| 11 11 | 
             
                  end
         | 
| 12 12 |  | 
| 13 13 | 
             
                  def current_type
         | 
| 14 | 
            -
                    current_token | 
| 14 | 
            +
                    current_token&.type
         | 
| 15 15 | 
             
                  end
         | 
| 16 16 |  | 
| 17 17 | 
             
                  def previous_token
         | 
| @@ -26,9 +26,7 @@ module Lrama | |
| 26 26 |  | 
| 27 27 | 
             
                  def consume(*token_types)
         | 
| 28 28 | 
             
                    if token_types.include?(current_type)
         | 
| 29 | 
            -
                       | 
| 30 | 
            -
                      self.next
         | 
| 31 | 
            -
                      return token
         | 
| 29 | 
            +
                      return self.next
         | 
| 32 30 | 
             
                    end
         | 
| 33 31 |  | 
| 34 32 | 
             
                    return nil
         | 
| @@ -42,8 +40,7 @@ module Lrama | |
| 42 40 | 
             
                    a = []
         | 
| 43 41 |  | 
| 44 42 | 
             
                    while token_types.include?(current_type)
         | 
| 45 | 
            -
                      a <<  | 
| 46 | 
            -
                      self.next
         | 
| 43 | 
            +
                      a << self.next
         | 
| 47 44 | 
             
                    end
         | 
| 48 45 |  | 
| 49 46 | 
             
                    raise "No token is consumed. #{token_types}" if a.empty?
         | 
    
        data/lib/lrama/parser.rb
    CHANGED
    
    | @@ -22,6 +22,7 @@ module Lrama | |
| 22 22 | 
             
                    process_epilogue(grammar, lexer)
         | 
| 23 23 | 
             
                    grammar.prepare
         | 
| 24 24 | 
             
                    grammar.compute_nullable
         | 
| 25 | 
            +
                    grammar.compute_first_set
         | 
| 25 26 | 
             
                    grammar.validate!
         | 
| 26 27 |  | 
| 27 28 | 
             
                    grammar
         | 
| @@ -158,6 +159,14 @@ module Lrama | |
| 158 159 | 
             
                        grammar.add_right(sym, precedence_number)
         | 
| 159 160 | 
             
                      end
         | 
| 160 161 | 
             
                      precedence_number += 1
         | 
| 162 | 
            +
                    when T::P_precedence
         | 
| 163 | 
            +
                      # %precedence (ident|char|string)+
         | 
| 164 | 
            +
                      ts.next
         | 
| 165 | 
            +
                      while (id = ts.consume(T::Ident, T::Char, T::String)) do
         | 
| 166 | 
            +
                        sym = grammar.add_term(id: id)
         | 
| 167 | 
            +
                        grammar.add_precedence(sym, precedence_number)
         | 
| 168 | 
            +
                      end
         | 
| 169 | 
            +
                      precedence_number += 1
         | 
| 161 170 | 
             
                    when nil
         | 
| 162 171 | 
             
                      # end of input
         | 
| 163 172 | 
             
                      raise "Reach to end of input within declarations"
         | 
    
        data/lib/lrama/state.rb
    CHANGED
    
    | @@ -1,11 +1,11 @@ | |
| 1 1 | 
             
            require "lrama/state/reduce"
         | 
| 2 | 
            -
            require "lrama/state/ | 
| 2 | 
            +
            require "lrama/state/reduce_reduce_conflict"
         | 
| 3 3 | 
             
            require "lrama/state/resolved_conflict"
         | 
| 4 | 
            +
            require "lrama/state/shift"
         | 
| 5 | 
            +
            require "lrama/state/shift_reduce_conflict"
         | 
| 4 6 |  | 
| 5 7 | 
             
            module Lrama
         | 
| 6 8 | 
             
              class State
         | 
| 7 | 
            -
                Conflict = Struct.new(:symbols, :reduce, :type, keyword_init: true)
         | 
| 8 | 
            -
             | 
| 9 9 | 
             
                attr_reader :id, :accessing_symbol, :kernels, :conflicts, :resolved_conflicts,
         | 
| 10 10 | 
             
                            :default_reduction_rule, :closure, :items
         | 
| 11 11 | 
             
                attr_accessor :shifts, :reduces
         | 
| @@ -62,7 +62,6 @@ module Lrama | |
| 62 62 | 
             
                  @items_to_state[items] = next_state
         | 
| 63 63 | 
             
                end
         | 
| 64 64 |  | 
| 65 | 
            -
                #
         | 
| 66 65 | 
             
                def set_look_ahead(rule, look_ahead)
         | 
| 67 66 | 
             
                  reduce = reduces.find do |r|
         | 
| 68 67 | 
             
                    r.rule == rule
         | 
| @@ -101,6 +100,10 @@ module Lrama | |
| 101 100 | 
             
                  @term_transitions
         | 
| 102 101 | 
             
                end
         | 
| 103 102 |  | 
| 103 | 
            +
                def transitions
         | 
| 104 | 
            +
                  term_transitions + nterm_transitions
         | 
| 105 | 
            +
                end
         | 
| 106 | 
            +
             | 
| 104 107 | 
             
                def selected_term_transitions
         | 
| 105 108 | 
             
                  term_transitions.select do |shift, next_state|
         | 
| 106 109 | 
             
                    !shift.not_selected
         | 
| @@ -144,6 +147,10 @@ module Lrama | |
| 144 147 | 
             
                  end
         | 
| 145 148 | 
             
                end
         | 
| 146 149 |  | 
| 150 | 
            +
                def has_conflicts?
         | 
| 151 | 
            +
                  !@conflicts.empty?
         | 
| 152 | 
            +
                end
         | 
| 153 | 
            +
             | 
| 147 154 | 
             
                def sr_conflicts
         | 
| 148 155 | 
             
                  @conflicts.select do |conflict|
         | 
| 149 156 | 
             
                    conflict.type == :shift_reduce
         |