Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion lib/lrama/command.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ def initialize(argv)
@options = OptionParser.parse(argv)
@tracer = Tracer.new(STDERR, **@options.trace_opts)
@reporter = Reporter.new(**@options.report_opts)
@warnings = Warnings.new(@logger, @options.warnings)
@warnings = Warnings.new(@logger, @options.warnings, @options.warning_opts || {})
rescue => e
abort format_error_message(e.message)
end
Expand Down
52 changes: 38 additions & 14 deletions lib/lrama/counterexamples.rb
Original file line number Diff line number Diff line change
Expand Up @@ -59,14 +59,7 @@ def compute(conflict_state)
# to avoid one of example's path to be nil.
next if @exceed_cumulative_time_limit

case conflict.type
when :shift_reduce
# @type var conflict: State::ShiftReduceConflict
shift_reduce_example(conflict_state, conflict)
when :reduce_reduce
# @type var conflict: State::ReduceReduceConflict
reduce_reduce_examples(conflict_state, conflict)
end
conflict_examples(conflict_state, conflict)
rescue Timeout::Error => e
STDERR.puts "Counterexamples calculation for state #{conflict_state.id} #{e.message} with #{@iterate_count} iteration"
increment_total_duration(PathSearchTimeLimit)
Expand All @@ -76,6 +69,39 @@ def compute(conflict_state)

private

# @rbs (State conflict_state, State::conflict conflict) -> Array[Example]
def conflict_examples(conflict_state, conflict)
examples = conflict.symbols.map do |conflict_symbol|
case conflict.type
when :shift_reduce
# @type var conflict: State::ShiftReduceConflict
shift_reduce_example(conflict_state, conflict, conflict_symbol)
when :reduce_reduce
# @type var conflict: State::ReduceReduceConflict
reduce_reduce_example(conflict_state, conflict, conflict_symbol)
end
end

merge_examples(examples)
end

# @rbs (Array[Example]) -> Array[Example]
def merge_examples(examples)
merged = {} #: Hash[Array[untyped], Example]

examples.each do |example|
key = example.merge_key

if merged[key]
merged[key].merge_conflict_symbols!(example.conflict_symbols)
else
merged[key] = example
end
end

merged.values
end

# @rbs (State state, State::Item item) -> StateItem
def get_state_item(state, item)
@state_items[[state, item]]
Expand Down Expand Up @@ -176,9 +202,8 @@ def get_triple(state_item, precise_lookahead_set)
@triples[key] ||= Triple.new(state_item, precise_lookahead_set)
end

# @rbs (State conflict_state, State::ShiftReduceConflict conflict) -> Example
def shift_reduce_example(conflict_state, conflict)
conflict_symbol = conflict.symbols.first
# @rbs (State conflict_state, State::ShiftReduceConflict conflict, Grammar::Symbol conflict_symbol) -> Example
def shift_reduce_example(conflict_state, conflict, conflict_symbol)
# @type var shift_conflict_item: ::Lrama::State::Item
shift_conflict_item = conflict_state.items.find { |item| item.next_sym == conflict_symbol }
path2 = with_timeout("#shortest_path:") do
Expand All @@ -191,9 +216,8 @@ def shift_reduce_example(conflict_state, conflict)
Example.new(path1, path2, conflict, conflict_symbol, self)
end

# @rbs (State conflict_state, State::ReduceReduceConflict conflict) -> Example
def reduce_reduce_examples(conflict_state, conflict)
conflict_symbol = conflict.symbols.first
# @rbs (State conflict_state, State::ReduceReduceConflict conflict, Grammar::Symbol conflict_symbol) -> Example
def reduce_reduce_example(conflict_state, conflict, conflict_symbol)
path1 = with_timeout("#shortest_path:") do
shortest_path(conflict_state, conflict.reduce1.item, conflict_symbol)
end
Expand Down
35 changes: 35 additions & 0 deletions lib/lrama/counterexamples/derivation.rb
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,11 @@ def render_for_report
render_strings_for_report.join("\n")
end

# @rbs (?Derivation derivation) -> Array[String]
def render_symbols_for_example(derivation = self)
_render_symbols_for_example(derivation)
end

private

# @rbs (Derivation derivation, Integer offset, Array[String] strings, Integer index) -> Integer
Expand Down Expand Up @@ -71,6 +76,36 @@ def _render_for_report(derivation, offset, strings, index)

return str.length
end

# @rbs (Derivation derivation) -> Array[String]
def _render_symbols_for_example(derivation)
item = derivation.item
result = item.symbols_before_dot.map do |symbol|
normalize_symbol_for_example(symbol.display_name)
end

if derivation.left
result.concat(_render_symbols_for_example(derivation.left))
else
result << "•"
result.concat(item.symbols_after_dot.map { |symbol| normalize_symbol_for_example(symbol.display_name) })
return result
end

if (right = derivation.right&.left)
result.concat(_render_symbols_for_example(right))
tail = item.symbols_after_dot.drop(2)
else
tail = item.symbols_after_dot.drop(1)
end

result.concat(tail.map { |symbol| normalize_symbol_for_example(symbol.display_name) })
end

# @rbs (String name) -> String
def normalize_symbol_for_example(name)
name == '"end of file"' ? "$end" : name
end
end
end
end
122 changes: 120 additions & 2 deletions lib/lrama/counterexamples/example.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ class Example
# @path1: ::Array[StateItem]
# @path2: ::Array[StateItem]
# @conflict: State::conflict
# @conflict_symbols: ::Array[Grammar::Symbol]
# @conflict_symbol: Grammar::Symbol
# @counterexamples: Counterexamples
# @derivations1: Derivation
Expand All @@ -20,16 +21,18 @@ class Example
attr_reader :path1 #: ::Array[StateItem]
attr_reader :path2 #: ::Array[StateItem]
attr_reader :conflict #: State::conflict
attr_reader :conflict_symbols #: ::Array[Grammar::Symbol]
attr_reader :conflict_symbol #: Grammar::Symbol

# path1 is shift conflict when S/R conflict
# path2 is always reduce conflict
#
# @rbs (Array[StateItem]? path1, Array[StateItem]? path2, State::conflict conflict, Grammar::Symbol conflict_symbol, Counterexamples counterexamples) -> void
def initialize(path1, path2, conflict, conflict_symbol, counterexamples)
# @rbs (Array[StateItem]? path1, Array[StateItem]? path2, State::conflict conflict, Grammar::Symbol conflict_symbol, Counterexamples counterexamples, ?conflict_symbols: Array[Grammar::Symbol]) -> void
def initialize(path1, path2, conflict, conflict_symbol, counterexamples, conflict_symbols: [conflict_symbol])
@path1 = path1
@path2 = path2
@conflict = conflict
@conflict_symbols = conflict_symbols
@conflict_symbol = conflict_symbol
@counterexamples = counterexamples
end
Expand Down Expand Up @@ -59,6 +62,70 @@ def derivations2
@derivations2 ||= _derivations(path2)
end

# @rbs () -> String
def example1
(shared_example_symbols || full_example_symbols1).join(" ")
end

# @rbs () -> String
def example2
(shared_example_symbols || full_example_symbols2).join(" ")
end

# @rbs () -> bool
def same_example?
example1 == example2
end

# @rbs () -> String
def example1_label
same_example? ? "Example" : "First example"
end

# @rbs () -> String
def example2_label
same_example? ? "Example" : "Second example"
end

# @rbs () -> String
def derivation_label1
type == :shift_reduce ? "Shift derivation" : "First Reduce derivation"
end

# @rbs () -> String
def derivation_label2
type == :shift_reduce ? "Reduce derivation" : "Second Reduce derivation"
end

# @rbs () -> String
def conflict_label
labels = conflict_symbols.map { |symbol| normalize_symbol_for_example(symbol.display_name) }
prefix = labels.size == 1 ? "token" : "tokens"

"#{prefix} #{labels.join(", ")}"
end

# @rbs (Array[Grammar::Symbol]) -> Example
def merge_conflict_symbols!(symbols)
@conflict_symbols |= symbols
self
end

# @rbs () -> Array[untyped]
def merge_key
[
type,
path1.map(&:id),
path2.map(&:id),
example1_label,
example1,
derivations1.render_for_report,
example2_label,
example2,
derivations2.render_for_report
]
end

private

# @rbs (Array[StateItem] state_items) -> Derivation
Expand Down Expand Up @@ -149,6 +216,57 @@ def find_derivation_for_symbol(state_item, sym)
end
end
end

# @rbs (String name) -> String
def normalize_symbol_for_example(name)
name == '"end of file"' ? "$end" : name
end

# @rbs () -> Array[String]
def full_example_symbols1
derivations1.render_symbols_for_example
end

# @rbs () -> Array[String]
def full_example_symbols2
derivations2.render_symbols_for_example
end

# @rbs () -> Array[String]?
def shared_example_symbols
return @shared_example_symbols if instance_variable_defined?(:@shared_example_symbols)

@shared_example_symbols = build_shared_example_symbols
end

# @rbs () -> Array[String]?
def build_shared_example_symbols
return full_example_symbols1 if full_example_symbols1 == full_example_symbols2
return nil unless type == :shift_reduce

common = common_prefix(full_example_symbols1, full_example_symbols2)
dot_index = common.index("•")
return nil unless dot_index

shared_after_dot_length = common.length - dot_index - 1
return nil if shared_after_dot_length < path1_item.symbols_after_dot.length

common
end

# @rbs (Array[String] a, Array[String] b) -> Array[String]
def common_prefix(a, b)
prefix = [] #: Array[String]

a.zip(b) do |left, right|
break unless left && right
break unless left == right

prefix << left
end

prefix
end
end
end
end
5 changes: 5 additions & 0 deletions lib/lrama/logger.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,11 @@ def warn(message)
@out << 'warning: ' << message << "\n"
end

# @rbs (String message) -> void
def note(message)
@out << 'note: ' << message << "\n"
end

# @rbs (String message) -> void
def error(message)
@out << 'error: ' << message << "\n"
Expand Down
Loading