lrama 0.5.3 → 0.5.4

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 32eaf486545ca4143a227a34cee2bfdb94928e002a85fcb2e098dba3dedb71c7
4
- data.tar.gz: b80a7402d9f6caf9f0e8b5396e4bd6b80d84487a35c8cd4b4d711e51ffb56601
3
+ metadata.gz: f2e675399217ba6b1c8cc57aaa36bbf863ed8b22dc3e22777c88d0d0aaf1cb26
4
+ data.tar.gz: 940b0da60c6b25edb1c10ed80539b17617e033d7ccc3a5c9c6036959c356ae37
5
5
  SHA512:
6
- metadata.gz: 1d118074af8984d85e27485a0c9fb2f8c7eab9a55b771ea3eff17e26d84fe56316404f6316ba5a9c83e61591c83ff9048c6625a904529aa686417f6f295499d9
7
- data.tar.gz: e3591cc5f70e6fe8a04af5070a7b5d6ee60e98718c4c6dc6db1fbef2b3be9f5239155da91bb51ba10611b5017925f1bf71aa1259fd5550b5af6d0ba458e2ab96
6
+ metadata.gz: 26eb48911cf5ba12b3087390bb7f7053165ff092a8919f7969e31b3e784a0085e876140d419cf4b463426cbfc4ae5f3625504a6c1433f9f9842ee9c32490082f
7
+ data.tar.gz: 8bce42121ffb5d45076dc3a1664e61209fec0f1a06a08d50d3a4279eb5158911b35165e21f8de0ac87c19e7cab1d2b7b0cac16bcaba0606ec61ea3b1c6195e0b
@@ -22,6 +22,15 @@ jobs:
22
22
  bundler-cache: true
23
23
  - run: bundle install
24
24
  - run: bundle exec rspec
25
+ check-misc:
26
+ runs-on: ubuntu-20.04
27
+ steps:
28
+ - uses: actions/checkout@v3
29
+ # Copy from https://github.com/ruby/ruby/blob/089227e94823542acfdafa68541d330eee42ffea/.github/workflows/check_misc.yml#L27
30
+ - name: Check for trailing spaces
31
+ run: |
32
+ git grep -I -n '[ ]$' -- '*.rb' '*.[chy]' '*.rs' && exit 1 || :
33
+ git grep -n '^[ ][ ]*$' -- '*.md' && exit 1 || :
25
34
  steep-check:
26
35
  runs-on: ubuntu-20.04
27
36
  strategy:
@@ -58,7 +67,7 @@ jobs:
58
67
  - run: mkdir -p tool/lrama
59
68
  working-directory: ../ruby
60
69
  - name: Copy Lrama to ruby/tool
61
- run: cp -r exe lib template ../ruby/tool/lrama
70
+ run: cp -r LEGAL.md MIT exe lib template ../ruby/tool/lrama
62
71
  working-directory:
63
72
  - run: tree tool/lrama
64
73
  working-directory: ../ruby
data/README.md CHANGED
@@ -49,10 +49,20 @@ Enter the formula:
49
49
 
50
50
  ## Versions and Branches
51
51
 
52
+ ### v0_5 (`master` branch)
53
+
54
+ This branch is for Ruby 3.3. `lrama_0_5` branch is created from this branch, once Ruby 3.3 is released.
55
+
52
56
  ### v0_4 (`lrama_0_4` branch)
53
57
 
54
58
  This branch generates "parse.c" compatible with Bison 3.8.2 for ruby 3.0, 3.1, 3.2. The first version migrated to ruby is ["0.4.0"](https://github.com/ruby/ruby/pull/7798) therefore keep this branch for Bison compatible branch.
55
59
 
60
+ ## Supported Ruby version
61
+
62
+ Lrama is executed with BASERUBY when building ruby from source code. Therefore Lrama needs to support BASERUBY, currently 2.5, or later version.
63
+
64
+ This also requires Lrama to be able to run with only default gems and bundled gems.
65
+
56
66
  ## Build Ruby
57
67
 
58
68
  1. Install Lrama
@@ -62,7 +72,7 @@ This branch generates "parse.c" compatible with Bison 3.8.2 for ruby 3.0, 3.1, 3
62
72
 
63
73
  1. Update `Lrama::VERSION`
64
74
  2. Release as a gem by `rake release`
65
- 3. Update Lrama in ruby/ruby by `cp -r LEGAL.md MIT exe lib ruby/tool/lrama`
75
+ 3. Update Lrama in ruby/ruby by `cp -r LEGAL.md MIT exe lib template ruby/tool/lrama`
66
76
  4. Create new release on [GitHub](https://github.com/ruby/lrama/releases)
67
77
 
68
78
  ## License
data/doc/TODO.md CHANGED
@@ -44,10 +44,14 @@
44
44
  * Reporting
45
45
  * [ ] Bison style
46
46
  * [ ] Wrap not selected reduce with "[]". See basic.output file generated by Bison.
47
+ * Counterexamples
48
+ * [x] Nonunifying Counterexamples
49
+ * [ ] Unifying Counterexamples
50
+ * [ ] Performance improvement using reverse_transitions and reverse_productions
47
51
  * Error Tolerance
48
52
  * [x] Corchuelo et al. algorithm with N = 1 (this means the next token when error is raised)
49
53
  * [x] Add new decl for error token semantic value initialization (%error-token)
50
- * [ ] Use YYMALLOC & YYFREE
54
+ * [x] Use YYMALLOC & YYFREE
51
55
  * Lex state
52
56
  * CI
53
57
  * [x] Setup CI
data/lib/lrama/command.rb CHANGED
@@ -67,7 +67,7 @@ module Lrama
67
67
  bison_list = %w[states itemsets lookaheads solved counterexamples cex all none]
68
68
  others = %w[verbose]
69
69
  list = bison_list + others
70
- not_supported = %w[counterexamples cex none]
70
+ not_supported = %w[cex none]
71
71
  h = { grammar: true }
72
72
 
73
73
  report.each do |r|
@@ -121,13 +121,13 @@ module Lrama
121
121
  # Output Files:
122
122
  opt.on('-h', '--header=[FILE]') {|v| @header = true; @header_file = v }
123
123
  opt.on('-d') { @header = true }
124
- opt.on('-r', '--report=THINGS') {|v| @report = v.split(',') }
124
+ opt.on('-r', '--report=THINGS', Array) {|v| @report = v }
125
125
  opt.on('--report-file=FILE') {|v| @report_file = v }
126
126
  opt.on('-v') { } # Do nothing
127
127
  opt.on('-o', '--output=FILE') {|v| @outfile = v }
128
128
 
129
129
  # Hidden
130
- opt.on('--trace=THINGS') {|v| @trace = v.split(',') }
130
+ opt.on('--trace=THINGS', Array) {|v| @trace = v }
131
131
 
132
132
  # Error Recovery
133
133
  opt.on('-e') {|v| @error_recovery = true }
@@ -0,0 +1,63 @@
1
+ module Lrama
2
+ class Counterexamples
3
+ class Derivation
4
+ attr_reader :item, :left, :right
5
+ attr_writer :right
6
+
7
+ def initialize(item, left, right = nil)
8
+ @item = item
9
+ @left = left
10
+ @right = right
11
+ end
12
+
13
+ def to_s
14
+ "#<Derivation(#{item.display_name})>"
15
+ end
16
+ alias :inspect :to_s
17
+
18
+ def render_strings_for_report
19
+ result = []
20
+ _render_for_report(self, 0, result, 0)
21
+ result.map(&:rstrip)
22
+ end
23
+
24
+ def render_for_report
25
+ render_strings_for_report.join("\n")
26
+ end
27
+
28
+ private
29
+
30
+ def _render_for_report(derivation, offset, strings, index)
31
+ item = derivation.item
32
+ if strings[index]
33
+ strings[index] << " " * (offset - strings[index].length)
34
+ else
35
+ strings[index] = " " * offset
36
+ end
37
+ str = strings[index]
38
+ str << "#{item.rule_id}: #{item.symbols_before_dot.map(&:display_name).join(" ")} "
39
+
40
+ if derivation.left
41
+ len = str.length
42
+ str << "#{item.next_sym.display_name}"
43
+ length = _render_for_report(derivation.left, len, strings, index + 1)
44
+ # I want String#ljust!
45
+ str << " " * (length - str.length)
46
+ else
47
+ str << " • #{item.symbols_after_dot.map(&:display_name).join(" ")} "
48
+ return str.length
49
+ end
50
+
51
+ if derivation.right&.left
52
+ length = _render_for_report(derivation.right.left, str.length, strings, index + 1)
53
+ str << "#{item.symbols_after_dot[1..-1].map(&:display_name).join(" ")} "
54
+ str << " " * (length - str.length) if length > str.length
55
+ elsif item.next_next_sym
56
+ str << "#{item.symbols_after_dot[1..-1].map(&:display_name).join(" ")} "
57
+ end
58
+
59
+ return str.length
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,124 @@
1
+ module Lrama
2
+ class Counterexamples
3
+ class Example
4
+ attr_reader :path1, :path2, :conflict, :conflict_symbol
5
+
6
+ # path1 is shift conflict when S/R conflict
7
+ # path2 is always reduce conflict
8
+ def initialize(path1, path2, conflict, conflict_symbol, counterexamples)
9
+ @path1 = path1
10
+ @path2 = path2
11
+ @conflict = conflict
12
+ @conflict_symbol = conflict_symbol
13
+ @counterexamples = counterexamples
14
+ end
15
+
16
+ def type
17
+ @conflict.type
18
+ end
19
+
20
+ def path1_item
21
+ @path1.last.to.item
22
+ end
23
+
24
+ def path2_item
25
+ @path2.last.to.item
26
+ end
27
+
28
+ def derivations1
29
+ @derivations1 ||= _derivations(path1)
30
+ end
31
+
32
+ def derivations2
33
+ @derivations2 ||= _derivations(path2)
34
+ end
35
+
36
+ private
37
+
38
+ def _derivations(paths)
39
+ derivation = nil
40
+ current = :production
41
+ lookahead_sym = paths.last.to.item.end_of_rule? ? @conflict_symbol : nil
42
+
43
+ paths.reverse.each do |path|
44
+ item = path.to.item
45
+
46
+ case current
47
+ when :production
48
+ case path
49
+ when StartPath
50
+ derivation = Derivation.new(item, derivation)
51
+ current = :start
52
+ when TransitionPath
53
+ derivation = Derivation.new(item, derivation)
54
+ current = :transition
55
+ when ProductionPath
56
+ derivation = Derivation.new(item, derivation)
57
+ current = :production
58
+ end
59
+
60
+ if lookahead_sym && item.next_next_sym && item.next_next_sym.first_set.include?(lookahead_sym)
61
+ state_item = @counterexamples.transitions[[path.to, item.next_sym]]
62
+ derivation2 = find_derivation_for_symbol(state_item, lookahead_sym)
63
+ derivation.right = derivation2
64
+ lookahead_sym = nil
65
+ end
66
+
67
+ when :transition
68
+ case path
69
+ when StartPath
70
+ derivation = Derivation.new(item, derivation)
71
+ current = :start
72
+ when TransitionPath
73
+ # ignore
74
+ current = :transition
75
+ when ProductionPath
76
+ # ignore
77
+ current = :production
78
+ end
79
+ else
80
+ raise "BUG: Unknown #{current}"
81
+ end
82
+
83
+ break if current == :start
84
+ end
85
+
86
+ derivation
87
+ end
88
+
89
+ def find_derivation_for_symbol(state_item, sym)
90
+ queue = []
91
+ queue << [state_item]
92
+
93
+ while (sis = queue.shift)
94
+ si = sis.last
95
+ next_sym = si.item.next_sym
96
+
97
+ if next_sym == sym
98
+ derivation = nil
99
+
100
+ sis.reverse.each do |si|
101
+ derivation = Derivation.new(si.item, derivation)
102
+ end
103
+
104
+ return derivation
105
+ end
106
+
107
+ if next_sym.nterm? && next_sym.first_set.include?(sym)
108
+ @counterexamples.productions[si].each do |next_item|
109
+ next if next_item.empty_rule?
110
+ next_si = StateItem.new(si.state, next_item)
111
+ next if sis.include?(next_si)
112
+ queue << (sis + [next_si])
113
+ end
114
+
115
+ if next_sym.nullable
116
+ next_si = @counterexamples.transitions[[si, next_sym]]
117
+ queue << (sis + [next_si])
118
+ end
119
+ end
120
+ end
121
+ end
122
+ end
123
+ end
124
+ end
@@ -0,0 +1,69 @@
1
+ module Lrama
2
+ class Counterexamples
3
+ class Path
4
+ def initialize(from_state_item, to_state_item)
5
+ @from_state_item = from_state_item
6
+ @to_state_item = to_state_item
7
+ end
8
+
9
+ def from
10
+ @from_state_item
11
+ end
12
+
13
+ def to
14
+ @to_state_item
15
+ end
16
+
17
+ def to_s
18
+ "#<Path(#{type})>"
19
+ end
20
+ alias :inspect :to_s
21
+ end
22
+
23
+ class StartPath < Path
24
+ def initialize(to_state_item)
25
+ super nil, to_state_item
26
+ end
27
+
28
+ def type
29
+ :start
30
+ end
31
+
32
+ def transition?
33
+ false
34
+ end
35
+
36
+ def production?
37
+ false
38
+ end
39
+ end
40
+
41
+ class TransitionPath < Path
42
+ def type
43
+ :transition
44
+ end
45
+
46
+ def transition?
47
+ true
48
+ end
49
+
50
+ def production?
51
+ false
52
+ end
53
+ end
54
+
55
+ class ProductionPath < Path
56
+ def type
57
+ :production
58
+ end
59
+
60
+ def transition?
61
+ false
62
+ end
63
+
64
+ def production?
65
+ true
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,6 @@
1
+ module Lrama
2
+ class Counterexamples
3
+ class StateItem < Struct.new(:state, :item)
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,21 @@
1
+ module Lrama
2
+ class Counterexamples
3
+ # s: state
4
+ # itm: item within s
5
+ # l: precise lookahead set
6
+ class Triple < Struct.new(:s, :itm, :l)
7
+ alias :state :s
8
+ alias :item :itm
9
+ alias :precise_lookahead_set :l
10
+
11
+ def state_item
12
+ StateItem.new(state, item)
13
+ end
14
+
15
+ def inspect
16
+ "#{state.inspect}. #{item.display_name}. #{l.map(&:id).map(&:s_value)}"
17
+ end
18
+ alias :to_s :inspect
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,285 @@
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
+ paths = 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
+
219
+ paths
220
+ end
221
+
222
+ def shortest_path(conflict_state, conflict_reduce_item, conflict_term)
223
+ # queue: is an array of [Triple, [Path]]
224
+ queue = []
225
+ visited = {}
226
+ start_state = @states.states.first
227
+ raise "BUG: Start state should be just one kernel." if start_state.kernels.count != 1
228
+
229
+ start = Triple.new(start_state, start_state.kernels.first, Set.new([@states.eof_symbol]))
230
+
231
+ queue << [start, [StartPath.new(start.state_item)]]
232
+
233
+ while true
234
+ triple, paths = queue.shift
235
+
236
+ next if visited[triple]
237
+ visited[triple] = true
238
+
239
+ # Found
240
+ if triple.state == conflict_state && triple.item == conflict_reduce_item && triple.l.include?(conflict_term)
241
+ return paths
242
+ end
243
+
244
+ # transition
245
+ triple.state.transitions.each do |shift, next_state|
246
+ next unless triple.item.next_sym && triple.item.next_sym == shift.next_sym
247
+ next_state.kernels.each do |kernel|
248
+ next if kernel.rule != triple.item.rule
249
+ t = Triple.new(next_state, kernel, triple.l)
250
+ queue << [t, paths + [TransitionPath.new(triple.state_item, t.state_item)]]
251
+ end
252
+ end
253
+
254
+ # production step
255
+ triple.state.closure.each do |item|
256
+ next unless triple.item.next_sym && triple.item.next_sym == item.lhs
257
+ l = follow_l(triple.item, triple.l)
258
+ t = Triple.new(triple.state, item, l)
259
+ queue << [t, paths + [ProductionPath.new(triple.state_item, t.state_item)]]
260
+ end
261
+
262
+ break if queue.empty?
263
+ end
264
+
265
+ return nil
266
+ end
267
+
268
+ def follow_l(item, current_l)
269
+ # 1. follow_L (A -> X1 ... Xn-1 • Xn) = L
270
+ # 2. follow_L (A -> X1 ... Xk • Xk+1 Xk+2 ... Xn) = {Xk+2} if Xk+2 is a terminal
271
+ # 3. follow_L (A -> X1 ... Xk • Xk+1 Xk+2 ... Xn) = FIRST(Xk+2) if Xk+2 is a nonnullable nonterminal
272
+ # 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
273
+ case
274
+ when item.number_of_rest_symbols == 1
275
+ current_l
276
+ when item.next_next_sym.term?
277
+ Set.new([item.next_next_sym])
278
+ when !item.next_next_sym.nullable
279
+ item.next_next_sym.first_set
280
+ else
281
+ item.next_next_sym.first_set + follow_l(item.new_by_next_position, current_l)
282
+ end
283
+ end
284
+ end
285
+ 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] && @relation[x].each do |y|
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