Skip to content

Commit

Permalink
Merge pull request ruby#537 from ydah/refactor-grammar-validator
Browse files Browse the repository at this point in the history
Remove GrammarValidator class and refactor grammar validation to States class
  • Loading branch information
ydah authored Feb 16, 2025
2 parents 9e02320 + 85dbd9c commit 47505a3
Show file tree
Hide file tree
Showing 6 changed files with 167 additions and 185 deletions.
1 change: 0 additions & 1 deletion lib/lrama.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
require_relative "lrama/digraph"
require_relative "lrama/erb"
require_relative "lrama/grammar"
require_relative "lrama/grammar_validator"
require_relative "lrama/lexer"
require_relative "lrama/logger"
require_relative "lrama/option_parser"
Expand Down
7 changes: 1 addition & 6 deletions lib/lrama/command.rb
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ def execute_command_workflow
@tracer.trace(grammar)
render_diagram(grammar)
render_output(context, grammar)
validate_grammar(grammar, states)
states.validate!(@logger)
@warnings.warn(grammar, states)
end

Expand Down Expand Up @@ -114,10 +114,5 @@ def render_output(context, grammar)
).render
end
end

def validate_grammar(grammar, states)
validator = Lrama::GrammarValidator.new(grammar, states, @logger)
exit false unless validator.valid?
end
end
end
37 changes: 0 additions & 37 deletions lib/lrama/grammar_validator.rb

This file was deleted.

28 changes: 28 additions & 0 deletions lib/lrama/states.rb
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,10 @@ def rr_conflicts_count
@rr_conflicts_count ||= @states.flat_map(&:rr_conflicts).count
end

def validate!(logger)
validate_conflicts_within_threshold!(logger)
end

private

def create_state(accessing_symbol, kernels, states_created)
Expand Down Expand Up @@ -662,5 +666,29 @@ def compute_state(state, shift, next_state)
merge_lookaheads(s, propagating_lookaheads)
end
end

def validate_conflicts_within_threshold!(logger)
exit false unless conflicts_within_threshold?(logger)
end

def conflicts_within_threshold?(logger)
return true unless @grammar.expect

[sr_conflicts_within_threshold?(logger), rr_conflicts_within_threshold?(logger)].all?
end

def sr_conflicts_within_threshold?(logger)
return true if @grammar.expect == sr_conflicts_count

logger.error("shift/reduce conflicts: #{sr_conflicts_count} found, #{@grammar.expect} expected")
false
end

def rr_conflicts_within_threshold?(logger, expected: 0)
return true if expected == rr_conflicts_count

logger.error("reduce/reduce conflicts: #{rr_conflicts_count} found, #{expected} expected")
false
end
end
end
141 changes: 0 additions & 141 deletions spec/lrama/grammar_validator_spec.rb

This file was deleted.

138 changes: 138 additions & 0 deletions spec/lrama/states_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2317,4 +2317,142 @@ class go to state 5
STR
end
end

describe "#validate!" do
let(:y) do
<<~STR
%union {
int i;
long l;
char *str;
}
%token EOI 0 "EOI"
%token <i> '\\' "backslash"
%token <i> '\13' "escaped vertical tab"
%token <i> keyword_class
%token <i> keyword_class2
%token <l> tNUMBER
%token <str> tSTRING
%token <i> keyword_end "end"
%token tPLUS "+"
%token tMINUS "-"
%token tEQ "="
%token tEQEQ "=="
%type <i> class /* comment for class */
%nonassoc tEQEQ
%left tPLUS tMINUS '>'
%right tEQ
%%
program: class
| '+' strings_1
| '-' strings_2
;
class : keyword_class tSTRING keyword_end %prec tPLUS
{ code 1 }
| keyword_class { code 2 } tSTRING '!' keyword_end { code 3 } %prec "="
| keyword_class { code 4 } tSTRING '?' keyword_end { code 5 } %prec '>'
;
strings_1: string_1
;
strings_2: string_1
| string_2
;
string_1: string
;
string_2: string '+'
;
string: tSTRING
;
%%
STR
end

context "when expect is specified" do
context "when the number of s/r conflicts is same with expect" do
let(:header) do
<<~STR
%{
// Prologue
%}
%expect 2
STR
end

it "has errors for r/r conflicts" do
grammar = Lrama::Parser.new(header + y, "states/check_conflicts.y").parse
grammar.prepare
grammar.validate!
states = Lrama::States.new(grammar, Lrama::Tracer.new(Lrama::Logger.new))
states.compute
logger = Lrama::Logger.new
allow(logger).to receive(:error)

expect{ states.validate!(logger) }.to raise_error(SystemExit)
expect(logger).to have_received(:error).with("reduce/reduce conflicts: 1 found, 0 expected")
end
end

context "when the number of s/r conflicts is not same with expect" do
let(:header) do
<<~STR
%{
// Prologue
%}
%expect 0
STR
end

it "has errors for s/r conflicts and r/r conflicts" do
grammar = Lrama::Parser.new(header + y, "states/check_conflicts.y").parse
grammar.prepare
grammar.validate!
states = Lrama::States.new(grammar, Lrama::Tracer.new(Lrama::Logger.new))
states.compute
logger = Lrama::Logger.new
allow(logger).to receive(:error)

expect{ states.validate!(logger) }.to raise_error(SystemExit)
expect(logger).to have_received(:error).with("shift/reduce conflicts: 2 found, 0 expected")
expect(logger).to have_received(:error).with("reduce/reduce conflicts: 1 found, 0 expected")
end
end
end

describe "expect is not specified" do
let(:header) do
<<~STR
%{
// Prologue
%}
STR
end

it "has warns for s/r conflicts and r/r conflicts" do
grammar = Lrama::Parser.new(header + y, "states/check_conflicts.y").parse
grammar.prepare
grammar.validate!
states = Lrama::States.new(grammar, Lrama::Tracer.new(Lrama::Logger.new))
states.compute
logger = Lrama::Logger.new
allow(logger).to receive(:error)

states.validate!(logger)
expect(logger).not_to have_received(:error)
end
end
end
end

0 comments on commit 47505a3

Please # to comment.