Warn on unused rules

This commit is contained in:
josh committed 2026-11-03 22:07:51 -05:00
1 parent 66af46a4f6
commit e5805d2db4
6 files changed
+87 -6

No files matched your search

+8
View File
@@ -1,3 +1,11 @@
## Unreleased
### New Features
- Warn about unused rules (rules not reachable from any start rule). The
warnings are written to the log file and, with `-w`, are printed to stderr
and treated as fatal errors.
## v5.2.0
### New Features
+2 -2
View File
@@ -39,8 +39,8 @@ Propane is typically invoked from the command-line as `./propane`.
debugging a grammar.
--version Show program version and exit.
-w Treat warnings as errors. This option will treat shift/reduce
conflicts as fatal errors and will print them to stderr in
addition to the log file.
conflicts and unused rules as fatal errors and will print
them to stderr in addition to the log file.
The user must specify the path to a Propane input grammar file and a path to an
output file.
+2 -2
View File
@@ -43,8 +43,8 @@ Propane is typically invoked from the command-line as `./propane`.
debugging a grammar.
--version Show program version and exit.
-w Treat warnings as errors. This option will treat shift/reduce
conflicts as fatal errors and will print them to stderr in
addition to the log file.
conflicts and unused rules as fatal errors and will print
them to stderr in addition to the log file.
The user must specify the path to a Propane input grammar file and a path to an
output file.
+2 -2
View File
@@ -10,8 +10,8 @@ Options:
debugging a grammar.
--version Show program version and exit.
-w Treat warnings as errors. This option will treat shift/reduce
conflicts as fatal errors and will print them to stderr in
addition to the log file.
conflicts and unused rules as fatal errors and will print
them to stderr in addition to the log file.
EOF
class << self
+29
View File
@@ -50,6 +50,7 @@ class Propane
build_reduce_actions!
build_tables!
check_unused_rules!
write_log!
errormessage = ""
if @errors.size > 0
@@ -68,6 +69,34 @@ class Propane
private
# Record a warning for each user-defined rule that cannot be reached from
# any start rule.
#
# @return [void]
def check_unused_rules!
reachable = Set.new
pending = @grammar.rules[0...@grammar.start_rules.length].map(&:rule_set)
while rule_set = pending.pop
next if reachable.include?(rule_set)
reachable << rule_set
rule_set.rules.each do |rule|
rule.components.each do |component|
if component.is_a?(RuleSet) && !reachable.include?(component)
pending << component
end
end
end
end
@rule_sets.each do |name, rule_set|
# Skip rule sets generated internally (real start rules and optional
# component rules).
next if name.start_with?("$") || rule_set.optional?
unless reachable.include?(rule_set)
@warnings << "Unused rule #{name} (defined on line #{rule_set.rules.first.line_number})"
end
end
end
def build_tables!
@state_table = []
@shift_table = []
+44
View File
@@ -267,6 +267,50 @@ EOF
expect(File.binread("spec/run/testparser.log")).to match %r{Shift/Reduce conflict \(state \d+\) between token b and rule As2\? \(defined on line 4\)}
end
it "warns on unused rules" do
write_grammar <<EOF
token a;
token b;
Start -> a;
Unused -> b Other?;
Other -> a;
EOF
results = run_propane(capture: true)
expect(results.stderr).to eq ""
expect(results.status).to eq 0
log = File.binread("spec/run/testparser.log")
expect(log).to match %r{Unused rule Unused \(defined on line 4\)}
expect(log).to match %r{Unused rule Other \(defined on line 5\)}
expect(log).to_not match %r{Unused rule (Start|Other\?)}
end
it "errors on unused rules with -w" do
write_grammar <<EOF
token a;
token b;
Start -> a Used;
Used -> b;
Unused -> b;
EOF
results = run_propane(extra_args: %w[-w], capture: true)
expect(results.stderr).to match %r{Unused rule Unused \(defined on line 5\)}
expect(results.stderr).to_not match %r{Unused rule (Start|Used)\b}
expect(results.status).to_not eq 0
end
it "does not warn about rules reachable from any of multiple start rules" do
write_grammar <<EOF
token a;
token b;
start Start Other;
Start -> a;
Other -> b;
EOF
results = run_propane(extra_args: %w[-w], capture: true)
expect(results.stderr).to eq ""
expect(results.status).to eq 0
end
it "errors on duplicate field aliases in a rule" do
write_grammar <<EOF
token a;