lrama 0.5.2 → 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.
Files changed (52) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/test.yaml +10 -1
  3. data/.gitignore +1 -0
  4. data/Gemfile +1 -0
  5. data/LEGAL.md +1 -16
  6. data/README.md +11 -1
  7. data/Steepfile +2 -1
  8. data/doc/TODO.md +8 -3
  9. data/exe/lrama +1 -1
  10. data/lib/lrama/command.rb +91 -72
  11. data/lib/lrama/context.rb +11 -1
  12. data/lib/lrama/counterexamples/derivation.rb +63 -0
  13. data/lib/lrama/counterexamples/example.rb +124 -0
  14. data/lib/lrama/counterexamples/path.rb +69 -0
  15. data/lib/lrama/counterexamples/state_item.rb +6 -0
  16. data/lib/lrama/counterexamples/triple.rb +21 -0
  17. data/lib/lrama/counterexamples.rb +285 -0
  18. data/lib/lrama/digraph.rb +2 -3
  19. data/lib/lrama/grammar/auxiliary.rb +7 -0
  20. data/lib/lrama/grammar/code.rb +123 -0
  21. data/lib/lrama/grammar/error_token.rb +9 -0
  22. data/lib/lrama/grammar/precedence.rb +11 -0
  23. data/lib/lrama/grammar/printer.rb +9 -0
  24. data/lib/lrama/grammar/reference.rb +22 -0
  25. data/lib/lrama/grammar/rule.rb +39 -0
  26. data/lib/lrama/grammar/symbol.rb +87 -0
  27. data/lib/lrama/grammar/union.rb +10 -0
  28. data/lib/lrama/grammar.rb +89 -282
  29. data/lib/lrama/lexer/token/type.rb +8 -0
  30. data/lib/lrama/lexer/token.rb +77 -0
  31. data/lib/lrama/lexer.rb +4 -74
  32. data/lib/lrama/output.rb +32 -4
  33. data/lib/lrama/parser/token_scanner.rb +3 -6
  34. data/lib/lrama/parser.rb +9 -1
  35. data/lib/lrama/report/duration.rb +25 -0
  36. data/lib/lrama/report/profile.rb +25 -0
  37. data/lib/lrama/report.rb +2 -47
  38. data/lib/lrama/state/reduce_reduce_conflict.rb +9 -0
  39. data/lib/lrama/state/resolved_conflict.rb +29 -0
  40. data/lib/lrama/state/shift_reduce_conflict.rb +9 -0
  41. data/lib/lrama/state.rb +13 -30
  42. data/lib/lrama/states/item.rb +79 -0
  43. data/lib/lrama/states.rb +24 -73
  44. data/lib/lrama/states_reporter.rb +28 -3
  45. data/lib/lrama/type.rb +4 -0
  46. data/lib/lrama/version.rb +1 -1
  47. data/lib/lrama.rb +2 -0
  48. data/lrama.gemspec +1 -1
  49. data/sig/lrama/{report.rbs → report/duration.rbs} +0 -4
  50. data/sig/lrama/report/profile.rbs +7 -0
  51. data/template/bison/yacc.c +371 -0
  52. metadata +30 -5
@@ -11,7 +11,7 @@ module Lrama
11
11
  end
12
12
 
13
13
  def current_type
14
- current_token && current_token.type
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
- token = current_token
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 << current_token
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
@@ -1,4 +1,4 @@
1
- require "lrama/report"
1
+ require "lrama/report/duration"
2
2
  require "lrama/parser/token_scanner"
3
3
 
4
4
  module Lrama
@@ -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
@@ -59,6 +60,13 @@ module Lrama
59
60
  code = grammar.build_code(:printer, code)
60
61
  ident_or_tags = ts.consume_multi(T::Ident, T::Tag)
61
62
  grammar.add_printer(ident_or_tags: ident_or_tags, code: code, lineno: lineno)
63
+ when T::P_error_token
64
+ lineno = ts.current_token.line
65
+ ts.next
66
+ code = ts.consume!(T::User_code)
67
+ code = grammar.build_code(:printer, code)
68
+ ident_or_tags = ts.consume_multi(T::Ident, T::Tag)
69
+ grammar.add_error_token(ident_or_tags: ident_or_tags, code: code, lineno: lineno)
62
70
  when T::P_lex_param
63
71
  ts.next
64
72
  code = ts.consume!(T::User_code)
@@ -0,0 +1,25 @@
1
+ module Lrama
2
+ class Report
3
+ module Duration
4
+ def self.enable
5
+ @_report_duration_enabled = true
6
+ end
7
+
8
+ def self.enabled?
9
+ !!@_report_duration_enabled
10
+ end
11
+
12
+ def report_duration(method_name)
13
+ time1 = Time.now.to_f
14
+ result = yield
15
+ time2 = Time.now.to_f
16
+
17
+ if Duration.enabled?
18
+ puts sprintf("%s %10.5f s", method_name, time2 - time1)
19
+ end
20
+
21
+ return result
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,25 @@
1
+ module Lrama
2
+ class Report
3
+ module Profile
4
+ # 1. Wrap target method with Profile.report_profile like below:
5
+ #
6
+ # Lrama::Report::Profile.report_profile { method }
7
+ #
8
+ # 2. Run lrama command, for example
9
+ #
10
+ # $ ./exe/lrama --trace=time spec/fixtures/integration/ruby_3_2_0/parse.tmp.y
11
+ #
12
+ # 3. Generate html file
13
+ #
14
+ # $ stackprof --d3-flamegraph tmp/stackprof-cpu-myapp.dump > tmp/flamegraph.html
15
+ #
16
+ def self.report_profile
17
+ require "stackprof"
18
+
19
+ StackProf.run(mode: :cpu, raw: true, out: 'tmp/stackprof-cpu-myapp.dump') do
20
+ yield
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
data/lib/lrama/report.rb CHANGED
@@ -1,47 +1,2 @@
1
- module Lrama
2
- class Report
3
- module Profile
4
- # 1. Wrap target method with Profile.report_profile like below:
5
- #
6
- # Lrama::Report::Profile.report_profile { method }
7
- #
8
- # 2. Run lrama command, for example
9
- #
10
- # $ ./exe/lrama --trace=time spec/fixtures/integration/ruby_3_2_0/parse.tmp.y
11
- #
12
- # 3. Generate html file
13
- #
14
- # $ stackprof --d3-flamegraph tmp/stackprof-cpu-myapp.dump > tmp/flamegraph.html
15
- #
16
- def self.report_profile
17
- require "stackprof"
18
-
19
- StackProf.run(mode: :cpu, raw: true, out: 'tmp/stackprof-cpu-myapp.dump') do
20
- yield
21
- end
22
- end
23
- end
24
-
25
- module Duration
26
- def self.enable
27
- @_report_duration_enabled = true
28
- end
29
-
30
- def self.enabled?
31
- !!@_report_duration_enabled
32
- end
33
-
34
- def report_duration(method_name)
35
- time1 = Time.now.to_f
36
- result = yield
37
- time2 = Time.now.to_f
38
-
39
- if Duration.enabled?
40
- puts sprintf("%s %10.5f s", method_name, time2 - time1)
41
- end
42
-
43
- return result
44
- end
45
- end
46
- end
47
- end
1
+ require 'lrama/report/duration'
2
+ require 'lrama/report/profile'
@@ -0,0 +1,9 @@
1
+ module Lrama
2
+ class State
3
+ class ReduceReduceConflict < Struct.new(:symbols, :reduce1, :reduce2, keyword_init: true)
4
+ def type
5
+ :reduce_reduce
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,29 @@
1
+ module Lrama
2
+ class State
3
+ # * symbol: A symbol under discussion
4
+ # * reduce: A reduce under discussion
5
+ # * which: For which a conflict is resolved. :shift, :reduce or :error (for nonassociative)
6
+ class ResolvedConflict < Struct.new(:symbol, :reduce, :which, :same_prec, keyword_init: true)
7
+ def report_message
8
+ s = symbol.display_name
9
+ r = reduce.rule.precedence_sym.display_name
10
+ case
11
+ when which == :shift && same_prec
12
+ msg = "resolved as #{which} (%right #{s})"
13
+ when which == :shift
14
+ msg = "resolved as #{which} (#{r} < #{s})"
15
+ when which == :reduce && same_prec
16
+ msg = "resolved as #{which} (%left #{s})"
17
+ when which == :reduce
18
+ msg = "resolved as #{which} (#{s} < #{r})"
19
+ when which == :error
20
+ msg = "resolved as an #{which} (%nonassoc #{s})"
21
+ else
22
+ raise "Unknown direction. #{self}"
23
+ end
24
+
25
+ "Conflict between rule #{reduce.rule.id} and token #{s} #{msg}."
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,9 @@
1
+ module Lrama
2
+ class State
3
+ class ShiftReduceConflict < Struct.new(:symbols, :shift, :reduce, keyword_init: true)
4
+ def type
5
+ :shift_reduce
6
+ end
7
+ end
8
+ end
9
+ end
data/lib/lrama/state.rb CHANGED
@@ -1,36 +1,11 @@
1
1
  require "lrama/state/reduce"
2
+ require "lrama/state/reduce_reduce_conflict"
3
+ require "lrama/state/resolved_conflict"
2
4
  require "lrama/state/shift"
5
+ require "lrama/state/shift_reduce_conflict"
3
6
 
4
7
  module Lrama
5
8
  class State
6
- # * symbol: A symbol under discussion
7
- # * reduce: A reduce under discussion
8
- # * which: For which a conflict is resolved. :shift, :reduce or :error (for nonassociative)
9
- ResolvedConflict = Struct.new(:symbol, :reduce, :which, :same_prec, keyword_init: true) do
10
- def report_message
11
- s = symbol.display_name
12
- r = reduce.rule.precedence_sym.display_name
13
- case
14
- when which == :shift && same_prec
15
- msg = "resolved as #{which} (%right #{s})"
16
- when which == :shift
17
- msg = "resolved as #{which} (#{r} < #{s})"
18
- when which == :reduce && same_prec
19
- msg = "resolved as #{which} (%left #{s})"
20
- when which == :reduce
21
- msg = "resolved as #{which} (#{s} < #{r})"
22
- when which == :error
23
- msg = "resolved as an #{which} (%nonassoc #{s})"
24
- else
25
- raise "Unknown direction. #{self}"
26
- end
27
-
28
- "Conflict between rule #{reduce.rule.id} and token #{s} #{msg}."
29
- end
30
- end
31
-
32
- Conflict = Struct.new(:symbols, :reduce, :type, keyword_init: true)
33
-
34
9
  attr_reader :id, :accessing_symbol, :kernels, :conflicts, :resolved_conflicts,
35
10
  :default_reduction_rule, :closure, :items
36
11
  attr_accessor :shifts, :reduces
@@ -96,7 +71,7 @@ module Lrama
96
71
  reduce.look_ahead = look_ahead
97
72
  end
98
73
 
99
- # Returns array of [nterm, next_state]
74
+ # Returns array of [Shift, next_state]
100
75
  def nterm_transitions
101
76
  return @nterm_transitions if @nterm_transitions
102
77
 
@@ -111,7 +86,7 @@ module Lrama
111
86
  @nterm_transitions
112
87
  end
113
88
 
114
- # Returns array of [term, next_state]
89
+ # Returns array of [Shift, next_state]
115
90
  def term_transitions
116
91
  return @term_transitions if @term_transitions
117
92
 
@@ -126,6 +101,10 @@ module Lrama
126
101
  @term_transitions
127
102
  end
128
103
 
104
+ def transitions
105
+ term_transitions + nterm_transitions
106
+ end
107
+
129
108
  def selected_term_transitions
130
109
  term_transitions.select do |shift, next_state|
131
110
  !shift.not_selected
@@ -169,6 +148,10 @@ module Lrama
169
148
  end
170
149
  end
171
150
 
151
+ def has_conflicts?
152
+ !@conflicts.empty?
153
+ end
154
+
172
155
  def sr_conflicts
173
156
  @conflicts.select do |conflict|
174
157
  conflict.type == :shift_reduce
@@ -0,0 +1,79 @@
1
+ # TODO: Validate position is not over rule rhs
2
+
3
+ module Lrama
4
+ class States
5
+ class Item < Struct.new(:rule, :position, keyword_init: true)
6
+ # Optimization for States#setup_state
7
+ def hash
8
+ [rule.id, position].hash
9
+ end
10
+
11
+ def rule_id
12
+ rule.id
13
+ end
14
+
15
+ def empty_rule?
16
+ rule.empty_rule?
17
+ end
18
+
19
+ def number_of_rest_symbols
20
+ rule.rhs.count - position
21
+ end
22
+
23
+ def lhs
24
+ rule.lhs
25
+ end
26
+
27
+ def next_sym
28
+ rule.rhs[position]
29
+ end
30
+
31
+ def next_next_sym
32
+ rule.rhs[position + 1]
33
+ end
34
+
35
+ def previous_sym
36
+ rule.rhs[position - 1]
37
+ end
38
+
39
+ def end_of_rule?
40
+ rule.rhs.count == position
41
+ end
42
+
43
+ def beginning_of_rule?
44
+ position == 0
45
+ end
46
+
47
+ def start_item?
48
+ rule.id == 0 && position == 0
49
+ end
50
+
51
+ def new_by_next_position
52
+ Item.new(rule: rule, position: position + 1)
53
+ end
54
+
55
+ def symbols_before_dot
56
+ rule.rhs[0...position]
57
+ end
58
+
59
+ def symbols_after_dot
60
+ rule.rhs[position..-1]
61
+ end
62
+
63
+ def to_s
64
+ "#{lhs.id.s_value}: #{display_name}"
65
+ end
66
+
67
+ def display_name
68
+ r = rule.rhs.map(&:display_name).insert(position, "•").join(" ")
69
+ "#{r} (rule #{rule.id})"
70
+ end
71
+
72
+ # Right after position
73
+ def display_rest
74
+ r = rule.rhs[position..-1].map(&:display_name).join(" ")
75
+ ". #{r} (rule #{rule.id})"
76
+ end
77
+ end
78
+ end
79
+ end
data/lib/lrama/states.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  require "forwardable"
2
- require "lrama/report"
2
+ require "lrama/report/duration"
3
+ require "lrama/states/item"
3
4
 
4
5
  module Lrama
5
6
  # States is passed to a template file
@@ -11,46 +12,7 @@ module Lrama
11
12
  include Lrama::Report::Duration
12
13
 
13
14
  def_delegators "@grammar", :symbols, :terms, :nterms, :rules,
14
- :accept_symbol, :eof_symbol, :find_symbol_by_s_value!
15
-
16
- # TODO: Validate position is not over rule rhs
17
- Item = Struct.new(:rule, :position, keyword_init: true) do
18
- # Optimization for States#setup_state
19
- def hash
20
- [rule.id, position].hash
21
- end
22
-
23
- def rule_id
24
- rule.id
25
- end
26
-
27
- def next_sym
28
- rule.rhs[position]
29
- end
30
-
31
- def end_of_rule?
32
- rule.rhs.count == position
33
- end
34
-
35
- def new_by_next_position
36
- Item.new(rule: rule, position: position + 1)
37
- end
38
-
39
- def previous_sym
40
- rule.rhs[position - 1]
41
- end
42
-
43
- def display_name
44
- r = rule.rhs.map(&:display_name).insert(position, "•").join(" ")
45
- "#{r} (rule #{rule.id})"
46
- end
47
-
48
- # Right after position
49
- def display_rest
50
- r = rule.rhs[position..-1].map(&:display_name).join(" ")
51
- ". #{r} (rule #{rule.id})"
52
- end
53
- end
15
+ :accept_symbol, :eof_symbol, :undef_symbol, :find_symbol_by_s_value!
54
16
 
55
17
  attr_reader :states, :reads_relation, :includes_relation, :lookback_relation
56
18
 
@@ -140,43 +102,27 @@ module Lrama
140
102
  end
141
103
 
142
104
  def direct_read_sets
143
- h = {}
144
-
145
- @direct_read_sets.each do |k, v|
146
- h[k] = bitmap_to_terms(v)
105
+ @direct_read_sets.transform_values do |v|
106
+ bitmap_to_terms(v)
147
107
  end
148
-
149
- return h
150
108
  end
151
109
 
152
110
  def read_sets
153
- h = {}
154
-
155
- @read_sets.each do |k, v|
156
- h[k] = bitmap_to_terms(v)
111
+ @read_sets.transform_values do |v|
112
+ bitmap_to_terms(v)
157
113
  end
158
-
159
- return h
160
114
  end
161
115
 
162
116
  def follow_sets
163
- h = {}
164
-
165
- @follow_sets.each do |k, v|
166
- h[k] = bitmap_to_terms(v)
117
+ @follow_sets.transform_values do |v|
118
+ bitmap_to_terms(v)
167
119
  end
168
-
169
- return h
170
120
  end
171
121
 
172
122
  def la
173
- h = {}
174
-
175
- @la.each do |k, v|
176
- h[k] = bitmap_to_terms(v)
123
+ @la.transform_values do |v|
124
+ bitmap_to_terms(v)
177
125
  end
178
-
179
- return h
180
126
  end
181
127
 
182
128
  private
@@ -490,7 +436,7 @@ module Lrama
490
436
 
491
437
  # Can resolve only when both have prec
492
438
  unless shift_prec && reduce_prec
493
- state.conflicts << State::Conflict.new(symbols: [sym], reduce: reduce, type: :shift_reduce)
439
+ state.conflicts << State::ShiftReduceConflict.new(symbols: [sym], shift: shift, reduce: reduce)
494
440
  next
495
441
  end
496
442
 
@@ -539,16 +485,21 @@ module Lrama
539
485
 
540
486
  def compute_reduce_reduce_conflicts
541
487
  states.each do |state|
542
- a = []
488
+ count = state.reduces.count
489
+
490
+ for i in 0...count do
491
+ reduce1 = state.reduces[i]
492
+ next if reduce1.look_ahead.nil?
543
493
 
544
- state.reduces.each do |reduce|
545
- next if reduce.look_ahead.nil?
494
+ for j in (i+1)...count do
495
+ reduce2 = state.reduces[j]
496
+ next if reduce2.look_ahead.nil?
546
497
 
547
- intersection = a & reduce.look_ahead
548
- a += reduce.look_ahead
498
+ intersection = reduce1.look_ahead & reduce2.look_ahead
549
499
 
550
- if !intersection.empty?
551
- state.conflicts << State::Conflict.new(symbols: intersection.dup, reduce: reduce, type: :reduce_reduce)
500
+ if !intersection.empty?
501
+ state.conflicts << State::ReduceReduceConflict.new(symbols: intersection, reduce1: reduce1, reduce2: reduce2)
502
+ end
552
503
  end
553
504
  end
554
505
  end
@@ -14,13 +14,13 @@ module Lrama
14
14
 
15
15
  private
16
16
 
17
- def _report(io, grammar: false, states: false, itemsets: false, lookaheads: false, solved: false, verbose: false)
17
+ def _report(io, grammar: false, states: false, itemsets: false, lookaheads: false, solved: false, counterexamples: false, verbose: false)
18
18
  # TODO: Unused terms
19
19
  # TODO: Unused rules
20
20
 
21
21
  report_conflicts(io)
22
22
  report_grammar(io) if grammar
23
- report_states(io, itemsets, lookaheads, solved, verbose)
23
+ report_states(io, itemsets, lookaheads, solved, counterexamples, verbose)
24
24
  end
25
25
 
26
26
  def report_conflicts(io)
@@ -71,7 +71,11 @@ module Lrama
71
71
  io << "\n\n"
72
72
  end
73
73
 
74
- def report_states(io, itemsets, lookaheads, solved, verbose)
74
+ def report_states(io, itemsets, lookaheads, solved, counterexamples, verbose)
75
+ if counterexamples
76
+ cex = Counterexamples.new(@states)
77
+ end
78
+
75
79
  @states.states.each do |state|
76
80
  # Report State
77
81
  io << "State #{state.id}\n\n"
@@ -194,6 +198,27 @@ module Lrama
194
198
  io << "\n" if !state.resolved_conflicts.empty?
195
199
  end
196
200
 
201
+ if counterexamples && state.has_conflicts?
202
+ # Report counterexamples
203
+ examples = cex.compute(state)
204
+ examples.each do |example|
205
+ label0 = example.type == :shift_reduce ? "shift/reduce" : "reduce/reduce"
206
+ label1 = example.type == :shift_reduce ? "Shift derivation" : "First Reduce derivation"
207
+ label2 = example.type == :shift_reduce ? "Reduce derivation" : "Second Reduce derivation"
208
+
209
+ io << " #{label0} conflict on token #{example.conflict_symbol.id.s_value}:\n"
210
+ io << " #{example.path1_item.to_s}\n"
211
+ io << " #{example.path2_item.to_s}\n"
212
+ io << " #{label1}\n"
213
+ example.derivations1.render_strings_for_report.each do |str|
214
+ io << " #{str}\n"
215
+ end
216
+ io << " #{label2}\n"
217
+ example.derivations2.render_strings_for_report.each do |str|
218
+ io << " #{str}\n"
219
+ end
220
+ end
221
+ end
197
222
 
198
223
  if verbose
199
224
  # Report direct_read_sets
data/lib/lrama/type.rb ADDED
@@ -0,0 +1,4 @@
1
+ module Lrama
2
+ class Type < Struct.new(:id, :tag, keyword_init: true)
3
+ end
4
+ end
data/lib/lrama/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Lrama
2
- VERSION = "0.5.2".freeze
2
+ VERSION = "0.5.4".freeze
3
3
  end
data/lib/lrama.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  require "lrama/bitmap"
2
2
  require "lrama/command"
3
3
  require "lrama/context"
4
+ require "lrama/counterexamples"
4
5
  require "lrama/digraph"
5
6
  require "lrama/grammar"
6
7
  require "lrama/lexer"
@@ -10,5 +11,6 @@ require "lrama/report"
10
11
  require "lrama/state"
11
12
  require "lrama/states"
12
13
  require "lrama/states_reporter"
14
+ require "lrama/type"
13
15
  require "lrama/version"
14
16
  require "lrama/warning"
data/lrama.gemspec CHANGED
@@ -10,7 +10,7 @@ Gem::Specification.new do |spec|
10
10
  spec.description = "LALR (1) parser generator written by Ruby"
11
11
  spec.homepage = "https://github.com/ruby/lrama"
12
12
  # See LEGAL.md file for detail
13
- spec.license = "GNU GPLv3"
13
+ spec.license = "GPL-3.0-or-later"
14
14
  spec.required_ruby_version = Gem::Requirement.new(">= 2.5.0")
15
15
 
16
16
  spec.files = Dir.chdir(File.expand_path(__dir__)) do
@@ -1,9 +1,5 @@
1
1
  module Lrama
2
2
  class Report
3
- module Profile
4
- def self.report_profile: { -> void } -> StackProf::result
5
- end
6
-
7
3
  module Duration
8
4
  self.@_report_duration_enabled: bool | nil
9
5
 
@@ -0,0 +1,7 @@
1
+ module Lrama
2
+ class Report
3
+ module Profile
4
+ def self.report_profile: { -> void } -> StackProf::result
5
+ end
6
+ end
7
+ end