[Bug] Mark a error as non-recoverable?
I need to mark a error as non-recoverable. In order to make the error message clearer.
use std::{num::{ParseFloatError, ParseIntError}, ops::Range};
use untwine::{parser, parser_repl, prelude::Recoverable};
#[derive(Debug, Default)]
pub enum UnitOfMeasurement {
#[default]
INCHES,
CENTIMETERS,
DOTS,
XDOTS
}
#[derive(Debug, Default)]
pub struct ValueWithUnit(f32, UnitOfMeasurement);
#[derive(Debug)]
pub enum GridUnit {
FormatId(String),
Value(ValueWithUnit)
}
/// (y, x)
pub type GridOrigin = (ValueWithUnit, ValueWithUnit);
#[derive(Debug, Recoverable)]
pub enum Command {
Grid(GridUnit, GridOrigin),
End,
#[recover]
Error(Range<usize>),
}
parser! {
[error = ParseFormError, recover = true]
space = #{char::is_ascii_whitespace};
semicolon = space* ';';
digit = '0'-'9' -> char;
float: num=<"-"? digit+ ("." digit+)?> -> f32 { num.parse()? }
ident_char = ["0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxy"] -> char;
ident: chars=<#[repeat(1..=6)]ident_char> -> String { chars.to_string() }
unit_inches: ("INCHES" | "INCH" | "IN") -> UnitOfMeasurement { UnitOfMeasurement::INCHES }
unit_centimeters: ("CM" | "CENTIMETERS") -> UnitOfMeasurement { UnitOfMeasurement::CENTIMETERS }
unit_dots: "DOTS" -> UnitOfMeasurement { UnitOfMeasurement::DOTS }
unit_xdots: "XDOTS" -> UnitOfMeasurement { UnitOfMeasurement::XDOTS }
unit = (unit_inches | unit_centimeters | unit_dots | unit_xdots) -> UnitOfMeasurement;
value_with_opt_unit: value=float space+ unit=(space+ unit)? -> ValueWithUnit { ValueWithUnit(value, unit.unwrap_or_default()) }
omitted_value_with_dots: unit_dots -> ValueWithUnit { ValueWithUnit (1.0f32, UnitOfMeasurement::DOTS)}
opt_value_with_opt_unit: value=float? unit=(space+ unit)? -> ValueWithUnit {
let unit = unit.unwrap_or_default();
let value = value.unwrap_or_default();
if value <= 0.0f32 {
return Err(ParseFormError::InvalidGridUnit(value, unit));
}
// match unit {
// UnitOfMeasurement::DOTS | UnitOfMeasurement::XDOTS => {
// if value != value.floor() {
// return Err(ParseFormError::InvalidGridUnit(value, unit));
// }
// }
// _ => {}
// }
ValueWithUnit(value, unit)
}
grid_value_unit: value=(omitted_value_with_dots | opt_value_with_opt_unit) -> GridUnit { GridUnit::Value(value) }
grid_value_format: value=ident -> GridUnit { GridUnit::FormatId(value) }
grid_value = (grid_value_unit | grid_value_format) -> GridUnit;
grid: space* "GRID" grid_value=((space+ "UNIT")? (space+ "IS")? space+ grid_value)? origin=(space+ "ORIGIN" (space+ opt_value_with_opt_unit)? (space+ opt_value_with_opt_unit)?)? semicolon -> Command {
let grid_value = grid_value.unwrap_or_else(|| GridUnit::FormatId("FMT1".into()));
let origin = match origin {
None => Default::default(),
Some((Some(y), Some(x))) => (y, x),
Some((Some(y), None)) => (y, Default::default()),
Some((None, Some(x))) => (Default::default(), x),
Some((None, None)) => (Default::default(), Default::default()),
};
Command::Grid(grid_value, origin)
}
end: space* "END" semicolon -> Command { Command::End }
command = (grid | end) -> Command;
pub test_form: commands=command+ -> Vec<Command> { commands }
}
The problem is that I cannot accept decimal places when the unit are DOTS. So I had to add this verification.
match unit {
UnitOfMeasurement::DOTS | UnitOfMeasurement::XDOTS => {
if value != value.floor() {
return Err(ParseFormError::InvalidGridUnit(value, unit));
}
}
_ => {}
}
But once I do that, I get the error message:
> GRID DOTS ORIGIN 0.5 DOTS;
1 | GRID DOTS ORIGIN 0.5 DOTS;
^
[1:21] Expected space
I think this is caused by trying to recover from the optional part of the rule:
opt_value_with_opt_unit: value=float? unit=(space+ unit)? -> ValueWithUnit {
^
I know that its trying to recover from a error, because if I put a debug print before the return err
match unit {
UnitOfMeasurement::DOTS | UnitOfMeasurement::XDOTS => {
if value != value.floor() {
println!("error")
return Err(ParseFormError::InvalidGridUnit(value, unit));
}
}
_ => {}
}
You can see that it print the debug text.
Any help would be greatly appreciated. Thanks.
This is not related to error recovery; you have no patterns here which can be recovered to. Only full rules wrapped in literals, and delimited lists, can be recovered from.
You're right that this most likely is a bug with the error prioritization, which I'll have to look into. However, I think you should try to encode this logic into the patterns rather than returning an error after the value has already been parsed. I would recommend using a match which can parse a value followed by a unit which is valid for that value.
I probably won't be able to look into this until next week, so I'll leave the issue open for now. It would be very helpful to me if you can minimize the complexity of the parser required to produce this bug, and show me that.
New test case
use std::{default, num::{ParseFloatError}, ops::Range};
use untwine::{parser, parser_repl, prelude::Recoverable};
#[derive(Debug, Default)]
pub enum UnitOfMeasurement {
DOTS,
#[default]
XDOTS
}
#[derive(Debug, Default)]
pub struct ValueWithUnit(f32, UnitOfMeasurement);
#[derive(Debug, thiserror::Error)]
pub enum ParseFormError {
#[error(transparent)]
Untwine(#[from] untwine::ParserError),
#[error("Failed to parse number: {0}")]
ParseFloat(#[from] ParseFloatError),
#[error("Invalid Grid Unit: {0} {1:?}")]
InvalidGridUnit(f32, UnitOfMeasurement)
}
parser! {
[error = ParseFormError, recover = true]
space = #{char::is_ascii_whitespace};
semicolon = space* ';';
digit = '0'-'9' -> char;
float: num=<"-"? digit+ ("." digit+)?> -> f32 { num.parse()? }
unit_dots: "DOTS" -> UnitOfMeasurement { UnitOfMeasurement::DOTS }
unit_xdots: "XDOTS" -> UnitOfMeasurement { UnitOfMeasurement::XDOTS }
unit = (unit_dots | unit_xdots) -> UnitOfMeasurement;
pub opt_value_with_opt_unit: value=float? unit=(space+ unit)? -> ValueWithUnit {
let unit = unit.unwrap_or_default();
let value = value.unwrap_or_default();
if value <= 0.0f32 {
return Err(ParseFormError::InvalidGridUnit(value, unit));
}
match unit {
UnitOfMeasurement::DOTS | UnitOfMeasurement::XDOTS => {
if value != value.floor() {
return Err(ParseFormError::InvalidGridUnit(value, unit));
}
}
_ => {}
}
ValueWithUnit(value, unit)
}
}
fn main() {
parser_repl(opt_value_with_opt_unit);
}
Prompt
1.2 DOTS
Error
1 | 1.2 DOTS
^
[1:4] Expected space
Thank you very much for providing a minimal example to reproduce the error!