lrama 0.5.2 → 0.5.4

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