lrama 0.5.3 → 0.5.4

Sign up to get free protection for your applications and to get access to all the features.
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