Show grammar line numbers for regex errors
This commit is contained in:
parent
25d6e3bc34
commit
5b243507cf
@ -55,7 +55,7 @@ class Propane
|
||||
@line_number = options[:line_number]
|
||||
@modes = options[:modes]
|
||||
@ptypename = options[:ptypename]
|
||||
regex = Regex.new(@pattern)
|
||||
regex = Regex.new(@pattern, @line_number)
|
||||
regex.nfa.end_state.accepts = self
|
||||
@nfa = regex.nfa
|
||||
end
|
||||
|
||||
@ -4,12 +4,13 @@ class Propane
|
||||
attr_reader :unit
|
||||
attr_reader :nfa
|
||||
|
||||
def initialize(pattern)
|
||||
def initialize(pattern, line_number)
|
||||
@pattern = pattern.dup
|
||||
@line_number = line_number
|
||||
@unit = parse_alternates
|
||||
@nfa = @unit.to_nfa
|
||||
if @pattern != ""
|
||||
raise Error.new(%[Unexpected "#{@pattern}" in pattern])
|
||||
raise Error.new(%[Line #{@line_number}: unexpected "#{@pattern}" in pattern])
|
||||
end
|
||||
end
|
||||
|
||||
@ -41,7 +42,7 @@ class Propane
|
||||
mu = MultiplicityUnit.new(last_unit, min_count, max_count)
|
||||
au.replace_last!(mu)
|
||||
else
|
||||
raise Error.new("#{c} follows nothing")
|
||||
raise Error.new("Line #{@line_number}: #{c} follows nothing")
|
||||
end
|
||||
when "|"
|
||||
au.new_alternate!
|
||||
@ -59,7 +60,7 @@ class Propane
|
||||
def parse_group
|
||||
au = parse_alternates
|
||||
if @pattern[0] != ")"
|
||||
raise Error.new("Unterminated group in pattern")
|
||||
raise Error.new("Line #{@line_number}: unterminated group in pattern")
|
||||
end
|
||||
@pattern.slice!(0)
|
||||
au
|
||||
@ -70,7 +71,7 @@ class Propane
|
||||
index = 0
|
||||
loop do
|
||||
if @pattern == ""
|
||||
raise Error.new("Unterminated character class")
|
||||
raise Error.new("Line #{@line_number}: unterminated character class")
|
||||
end
|
||||
c = @pattern.slice!(0)
|
||||
if c == "]"
|
||||
@ -84,13 +85,13 @@ class Propane
|
||||
elsif c == "-" && @pattern[0] != "]"
|
||||
begin_cu = ccu.last_unit
|
||||
unless begin_cu.is_a?(CharacterRangeUnit) && begin_cu.code_point_range.size == 1
|
||||
raise Error.new("Character range must be between single characters")
|
||||
raise Error.new("Line #{@line_number}: character range must be between single characters")
|
||||
end
|
||||
if @pattern[0] == "\\"
|
||||
@pattern.slice!(0)
|
||||
end_cu = parse_backslash
|
||||
unless end_cu.is_a?(CharacterRangeUnit) && end_cu.code_point_range.size == 1
|
||||
raise Error.new("Character range must be between single characters")
|
||||
raise Error.new("Line #{@line_number}: character range must be between single characters")
|
||||
end
|
||||
max_code_point = end_cu.code_point
|
||||
else
|
||||
@ -116,7 +117,7 @@ class Propane
|
||||
elsif max_count.to_s != ""
|
||||
max_count = max_count.to_i
|
||||
if max_count < min_count
|
||||
raise Error.new("Maximum repetition count cannot be less than minimum repetition count")
|
||||
raise Error.new("Line #{@line_number}: maximum repetition count cannot be less than minimum repetition count")
|
||||
end
|
||||
else
|
||||
max_count = nil
|
||||
@ -124,13 +125,13 @@ class Propane
|
||||
@pattern = pattern
|
||||
[min_count, max_count]
|
||||
else
|
||||
raise Error.new("Unexpected match count at #{@pattern}")
|
||||
raise Error.new("Line #{@line_number}: unexpected match count following {")
|
||||
end
|
||||
end
|
||||
|
||||
def parse_backslash
|
||||
if @pattern == ""
|
||||
raise Error.new("Error: unfollowed \\")
|
||||
raise Error.new("Line #{@line_number}: error: unfollowed \\")
|
||||
else
|
||||
c = @pattern.slice!(0)
|
||||
case c
|
||||
|
||||
@ -2,14 +2,14 @@ class Propane
|
||||
RSpec.describe Regex do
|
||||
|
||||
it "parses an empty expression" do
|
||||
regex = Regex.new("")
|
||||
regex = Regex.new("", 1)
|
||||
expect(regex.unit).to be_a Regex::AlternatesUnit
|
||||
expect(regex.unit.alternates.size).to eq 1
|
||||
expect(regex.unit.alternates[0].size).to eq 0
|
||||
end
|
||||
|
||||
it "parses a single character unit expression" do
|
||||
regex = Regex.new("a")
|
||||
regex = Regex.new("a", 1)
|
||||
expect(regex.unit).to be_a Regex::AlternatesUnit
|
||||
expect(regex.unit.alternates.size).to eq 1
|
||||
expect(regex.unit.alternates[0]).to be_a Regex::SequenceUnit
|
||||
@ -19,7 +19,7 @@ class Propane
|
||||
end
|
||||
|
||||
it "parses a group with a single character unit expression" do
|
||||
regex = Regex.new("(a)")
|
||||
regex = Regex.new("(a)", 1)
|
||||
expect(regex.unit).to be_a Regex::AlternatesUnit
|
||||
expect(regex.unit.alternates.size).to eq 1
|
||||
expect(regex.unit.alternates[0]).to be_a Regex::SequenceUnit
|
||||
@ -33,7 +33,7 @@ class Propane
|
||||
end
|
||||
|
||||
it "parses a *" do
|
||||
regex = Regex.new("a*")
|
||||
regex = Regex.new("a*", 1)
|
||||
expect(regex.unit).to be_a Regex::AlternatesUnit
|
||||
expect(regex.unit.alternates.size).to eq 1
|
||||
expect(regex.unit.alternates[0]).to be_a Regex::SequenceUnit
|
||||
@ -47,7 +47,7 @@ class Propane
|
||||
end
|
||||
|
||||
it "parses a +" do
|
||||
regex = Regex.new("a+")
|
||||
regex = Regex.new("a+", 1)
|
||||
expect(regex.unit).to be_a Regex::AlternatesUnit
|
||||
expect(regex.unit.alternates.size).to eq 1
|
||||
expect(regex.unit.alternates[0]).to be_a Regex::SequenceUnit
|
||||
@ -61,7 +61,7 @@ class Propane
|
||||
end
|
||||
|
||||
it "parses a ?" do
|
||||
regex = Regex.new("a?")
|
||||
regex = Regex.new("a?", 1)
|
||||
expect(regex.unit).to be_a Regex::AlternatesUnit
|
||||
expect(regex.unit.alternates.size).to eq 1
|
||||
expect(regex.unit.alternates[0]).to be_a Regex::SequenceUnit
|
||||
@ -75,7 +75,7 @@ class Propane
|
||||
end
|
||||
|
||||
it "parses a multiplicity count" do
|
||||
regex = Regex.new("a{5}")
|
||||
regex = Regex.new("a{5}", 1)
|
||||
expect(regex.unit).to be_a Regex::AlternatesUnit
|
||||
expect(regex.unit.alternates.size).to eq 1
|
||||
expect(regex.unit.alternates[0]).to be_a Regex::SequenceUnit
|
||||
@ -89,7 +89,7 @@ class Propane
|
||||
end
|
||||
|
||||
it "parses a minimum-only multiplicity count" do
|
||||
regex = Regex.new("a{5,}")
|
||||
regex = Regex.new("a{5,}", 1)
|
||||
expect(regex.unit).to be_a Regex::AlternatesUnit
|
||||
expect(regex.unit.alternates.size).to eq 1
|
||||
expect(regex.unit.alternates[0]).to be_a Regex::SequenceUnit
|
||||
@ -103,7 +103,7 @@ class Propane
|
||||
end
|
||||
|
||||
it "parses a minimum and maximum multiplicity count" do
|
||||
regex = Regex.new("a{5,8}")
|
||||
regex = Regex.new("a{5,8}", 1)
|
||||
expect(regex.unit).to be_a Regex::AlternatesUnit
|
||||
expect(regex.unit.alternates.size).to eq 1
|
||||
expect(regex.unit.alternates[0]).to be_a Regex::SequenceUnit
|
||||
@ -118,7 +118,7 @@ class Propane
|
||||
end
|
||||
|
||||
it "parses an escaped *" do
|
||||
regex = Regex.new("a\\*")
|
||||
regex = Regex.new("a\\*", 1)
|
||||
expect(regex.unit).to be_a Regex::AlternatesUnit
|
||||
expect(regex.unit.alternates.size).to eq 1
|
||||
expect(regex.unit.alternates[0]).to be_a Regex::SequenceUnit
|
||||
@ -131,7 +131,7 @@ class Propane
|
||||
end
|
||||
|
||||
it "parses an escaped +" do
|
||||
regex = Regex.new("a\\+")
|
||||
regex = Regex.new("a\\+", 1)
|
||||
expect(regex.unit).to be_a Regex::AlternatesUnit
|
||||
expect(regex.unit.alternates.size).to eq 1
|
||||
expect(regex.unit.alternates[0]).to be_a Regex::SequenceUnit
|
||||
@ -144,7 +144,7 @@ class Propane
|
||||
end
|
||||
|
||||
it "parses an escaped \\" do
|
||||
regex = Regex.new("\\\\d")
|
||||
regex = Regex.new("\\\\d", 1)
|
||||
expect(regex.unit).to be_a Regex::AlternatesUnit
|
||||
expect(regex.unit.alternates.size).to eq 1
|
||||
expect(regex.unit.alternates[0]).to be_a Regex::SequenceUnit
|
||||
@ -157,7 +157,7 @@ class Propane
|
||||
end
|
||||
|
||||
it "parses a character class" do
|
||||
regex = Regex.new("[a-z_]")
|
||||
regex = Regex.new("[a-z_]", 1)
|
||||
expect(regex.unit).to be_a Regex::AlternatesUnit
|
||||
expect(regex.unit.alternates.size).to eq 1
|
||||
expect(regex.unit.alternates[0]).to be_a Regex::SequenceUnit
|
||||
@ -175,7 +175,7 @@ class Propane
|
||||
end
|
||||
|
||||
it "parses a negated character class" do
|
||||
regex = Regex.new("[^xyz]")
|
||||
regex = Regex.new("[^xyz]", 1)
|
||||
expect(regex.unit).to be_a Regex::AlternatesUnit
|
||||
expect(regex.unit.alternates.size).to eq 1
|
||||
expect(regex.unit.alternates[0]).to be_a Regex::SequenceUnit
|
||||
@ -207,7 +207,7 @@ class Propane
|
||||
end
|
||||
|
||||
it "parses - as a plain character at beginning of a character class" do
|
||||
regex = Regex.new("[-9]")
|
||||
regex = Regex.new("[-9]", 1)
|
||||
expect(regex.unit).to be_a Regex::AlternatesUnit
|
||||
expect(regex.unit.alternates.size).to eq 1
|
||||
expect(regex.unit.alternates[0]).to be_a Regex::SequenceUnit
|
||||
@ -221,7 +221,7 @@ class Propane
|
||||
end
|
||||
|
||||
it "parses - as a plain character at end of a character class" do
|
||||
regex = Regex.new("[0-]")
|
||||
regex = Regex.new("[0-]", 1)
|
||||
expect(regex.unit).to be_a Regex::AlternatesUnit
|
||||
expect(regex.unit.alternates.size).to eq 1
|
||||
expect(regex.unit.alternates[0]).to be_a Regex::SequenceUnit
|
||||
@ -237,7 +237,7 @@ class Propane
|
||||
end
|
||||
|
||||
it "parses - as a plain character at beginning of a negated character class" do
|
||||
regex = Regex.new("[^-9]")
|
||||
regex = Regex.new("[^-9]", 1)
|
||||
expect(regex.unit).to be_a Regex::AlternatesUnit
|
||||
expect(regex.unit.alternates.size).to eq 1
|
||||
expect(regex.unit.alternates[0]).to be_a Regex::SequenceUnit
|
||||
@ -252,7 +252,7 @@ class Propane
|
||||
end
|
||||
|
||||
it "parses . as a plain character in a character class" do
|
||||
regex = Regex.new("[.]")
|
||||
regex = Regex.new("[.]", 1)
|
||||
expect(regex.unit).to be_a Regex::AlternatesUnit
|
||||
expect(regex.unit.alternates.size).to eq 1
|
||||
expect(regex.unit.alternates[0]).to be_a Regex::SequenceUnit
|
||||
@ -267,7 +267,7 @@ class Propane
|
||||
end
|
||||
|
||||
it "parses - as a plain character when escaped in middle of character class" do
|
||||
regex = Regex.new("[0\\-9]")
|
||||
regex = Regex.new("[0\\-9]", 1)
|
||||
expect(regex.unit).to be_a Regex::AlternatesUnit
|
||||
expect(regex.unit.alternates.size).to eq 1
|
||||
expect(regex.unit.alternates[0]).to be_a Regex::SequenceUnit
|
||||
@ -286,7 +286,7 @@ class Propane
|
||||
end
|
||||
|
||||
it "parses alternates" do
|
||||
regex = Regex.new("ab|c")
|
||||
regex = Regex.new("ab|c", 1)
|
||||
expect(regex.unit).to be_a Regex::AlternatesUnit
|
||||
expect(regex.unit.alternates.size).to eq 2
|
||||
expect(regex.unit.alternates[0]).to be_a Regex::SequenceUnit
|
||||
@ -296,7 +296,7 @@ class Propane
|
||||
end
|
||||
|
||||
it "parses a ." do
|
||||
regex = Regex.new("a.b")
|
||||
regex = Regex.new("a.b", 1)
|
||||
expect(regex.unit).to be_a Regex::AlternatesUnit
|
||||
expect(regex.unit.alternates.size).to eq 1
|
||||
expect(regex.unit.alternates[0]).to be_a Regex::SequenceUnit
|
||||
@ -307,7 +307,7 @@ class Propane
|
||||
end
|
||||
|
||||
it "parses something complex" do
|
||||
regex = Regex.new("(a|)*|[^^]|\\|v|[x-y]+")
|
||||
regex = Regex.new("(a|)*|[^^]|\\|v|[x-y]+", 1)
|
||||
expect(regex.unit).to be_a Regex::AlternatesUnit
|
||||
expect(regex.unit.alternates.size).to eq 4
|
||||
expect(regex.unit.alternates[0]).to be_a Regex::SequenceUnit
|
||||
|
||||
@ -249,6 +249,18 @@ EOF
|
||||
expect(results.status).to eq 0
|
||||
end
|
||||
|
||||
it "shows error line number for unmatched left curly brace" do
|
||||
write_grammar <<EOF
|
||||
# Line 1
|
||||
# Line 2
|
||||
token a /a{/;
|
||||
Start -> a;
|
||||
EOF
|
||||
results = run_propane(capture: true)
|
||||
expect(results.stderr).to match /Line 3: unexpected match count following \{/
|
||||
expect(results.status).to_not eq 0
|
||||
end
|
||||
|
||||
%w[d c].each do |language|
|
||||
|
||||
context "#{language.upcase} language" do
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user