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 +4 -4
- data/.github/workflows/test.yaml +10 -1
- data/README.md +11 -1
- data/doc/TODO.md +5 -1
- data/lib/lrama/command.rb +3 -3
- 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 +285 -0
- data/lib/lrama/digraph.rb +2 -3
- data/lib/lrama/grammar/auxiliary.rb +7 -0
- data/lib/lrama/grammar/rule.rb +6 -0
- data/lib/lrama/grammar/symbol.rb +4 -11
- data/lib/lrama/grammar.rb +39 -6
- data/lib/lrama/lexer/token/type.rb +8 -0
- data/lib/lrama/lexer/token.rb +3 -2
- data/lib/lrama/output.rb +1 -1
- data/lib/lrama/parser/token_scanner.rb +3 -6
- data/lib/lrama/parser.rb +1 -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 -3
- data/lib/lrama/states/item.rb +38 -2
- data/lib/lrama/states.rb +21 -32
- data/lib/lrama/states_reporter.rb +28 -3
- 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
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f2e675399217ba6b1c8cc57aaa36bbf863ed8b22dc3e22777c88d0d0aaf1cb26
|
|
4
|
+
data.tar.gz: 940b0da60c6b25edb1c10ed80539b17617e033d7ccc3a5c9c6036959c356ae37
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 26eb48911cf5ba12b3087390bb7f7053165ff092a8919f7969e31b3e784a0085e876140d419cf4b463426cbfc4ae5f3625504a6c1433f9f9842ee9c32490082f
|
|
7
|
+
data.tar.gz: 8bce42121ffb5d45076dc3a1664e61209fec0f1a06a08d50d3a4279eb5158911b35165e21f8de0ac87c19e7cab1d2b7b0cac16bcaba0606ec61ea3b1c6195e0b
|
data/.github/workflows/test.yaml
CHANGED
|
@@ -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
|
-
* [
|
|
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[
|
|
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
|
|
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
|
|
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,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]
|
|
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
|