use std::fmt;
const KNOWN_FUNCTIONS: &[&str] = &[
"sin", "cos", "tan", "csc", "sec", "cot", "arcsin", "arccos", "arctan", "sinh", "cosh", "tanh",
"log", "ln", "exp", "sqrt", "abs", "floor", "ceil", "round", "sign", "min", "max", "gcd",
"lcm",
];
pub fn levenshtein(a: &str, b: &str) -> usize {
let a_chars: Vec<char> = a.chars().collect();
let b_chars: Vec<char> = b.chars().collect();
let a_len = a_chars.len();
let b_len = b_chars.len();
if a_len == 0 {
return b_len;
}
if b_len == 0 {
return a_len;
}
let mut prev_row: Vec<usize> = (0..=b_len).collect();
let mut curr_row: Vec<usize> = vec![0; b_len + 1];
for (i, a_char) in a_chars.iter().enumerate() {
curr_row[0] = i + 1;
for (j, b_char) in b_chars.iter().enumerate() {
let cost = if a_char == b_char { 0 } else { 1 };
curr_row[j + 1] = std::cmp::min(
std::cmp::min(curr_row[j] + 1, prev_row[j + 1] + 1),
prev_row[j] + cost,
);
}
std::mem::swap(&mut prev_row, &mut curr_row);
}
prev_row[b_len]
}
pub fn suggest_function(unknown: &str) -> Option<String> {
KNOWN_FUNCTIONS
.iter()
.filter(|&&f| levenshtein(unknown, f) <= 2)
.min_by_key(|&&f| levenshtein(unknown, f))
.map(|&f| format!("Did you mean '{}'?", f))
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Position {
pub line: usize,
pub column: usize,
pub offset: usize,
}
impl Position {
pub fn new(line: usize, column: usize, offset: usize) -> Self {
Self {
line,
column,
offset,
}
}
pub fn start() -> Self {
Self::new(1, 1, 0)
}
}
impl fmt::Display for Position {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}:{}", self.line, self.column)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Span {
pub start: Position,
pub end: Position,
}
impl Span {
pub fn new(start: Position, end: Position) -> Self {
Self { start, end }
}
pub fn at(pos: Position) -> Self {
Self::new(pos, pos)
}
pub fn start() -> Self {
Self::at(Position::start())
}
}
impl fmt::Display for Span {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.start == self.end {
write!(f, "{}", self.start)
} else {
write!(f, "{}-{}", self.start, self.end)
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum ParseErrorKind {
UnexpectedToken {
expected: Vec<String>,
found: String,
},
UnexpectedEof {
expected: Vec<String>,
},
UnmatchedDelimiter {
opening: char,
position: Position,
},
InvalidNumber {
value: String,
reason: String,
},
InvalidLatexCommand {
command: String,
},
UnknownFunction {
name: String,
},
InvalidSubscript {
reason: String,
},
InvalidSuperscript {
reason: String,
},
MalformedMatrix {
reason: String,
},
EmptyExpression,
Custom(String),
}
impl fmt::Display for ParseErrorKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ParseErrorKind::UnexpectedToken { expected, found } => {
if expected.is_empty() {
write!(f, "unexpected token '{}'", found)
} else if expected.len() == 1 {
write!(f, "unexpected token '{}', expected {}", found, expected[0])
} else {
write!(
f,
"unexpected token '{}', expected one of: {}",
found,
expected.join(", ")
)
}
}
ParseErrorKind::UnexpectedEof { expected } => {
if expected.is_empty() {
write!(f, "unexpected end of input")
} else if expected.len() == 1 {
write!(f, "unexpected end of input, expected {}", expected[0])
} else {
write!(
f,
"unexpected end of input, expected one of: {}",
expected.join(", ")
)
}
}
ParseErrorKind::UnmatchedDelimiter { opening, position } => {
write!(
f,
"unmatched opening delimiter '{}' at {}",
opening, position
)
}
ParseErrorKind::InvalidNumber { value, reason } => {
write!(f, "invalid number '{}': {}", value, reason)
}
ParseErrorKind::InvalidLatexCommand { command } => {
write!(f, "invalid LaTeX command '{}'", command)
}
ParseErrorKind::UnknownFunction { name } => {
write!(f, "unknown function '{}'", name)
}
ParseErrorKind::InvalidSubscript { reason } => {
write!(f, "invalid subscript: {}", reason)
}
ParseErrorKind::InvalidSuperscript { reason } => {
write!(f, "invalid superscript: {}", reason)
}
ParseErrorKind::MalformedMatrix { reason } => {
write!(f, "malformed matrix: {}", reason)
}
ParseErrorKind::EmptyExpression => {
write!(f, "empty expression")
}
ParseErrorKind::Custom(msg) => {
write!(f, "{}", msg)
}
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct ParseError {
pub kind: ParseErrorKind,
pub span: Option<Span>,
pub context: Option<String>,
pub suggestion: Option<String>,
}
impl ParseError {
pub fn new(kind: ParseErrorKind, span: Option<Span>) -> Self {
Self {
kind,
span,
context: None,
suggestion: None,
}
}
pub fn with_context<S: Into<String>>(mut self, context: S) -> Self {
self.context = Some(context.into());
self
}
pub fn with_suggestion<S: Into<String>>(mut self, suggestion: S) -> Self {
self.suggestion = Some(suggestion.into());
self
}
pub fn unexpected_token<S1, S2>(expected: Vec<S1>, found: S2, span: Option<Span>) -> Self
where
S1: Into<String>,
S2: Into<String>,
{
Self::new(
ParseErrorKind::UnexpectedToken {
expected: expected.into_iter().map(|s| s.into()).collect(),
found: found.into(),
},
span,
)
}
pub fn unexpected_eof<S>(expected: Vec<S>, span: Option<Span>) -> Self
where
S: Into<String>,
{
Self::new(
ParseErrorKind::UnexpectedEof {
expected: expected.into_iter().map(|s| s.into()).collect(),
},
span,
)
}
pub fn unmatched_delimiter(opening: char, position: Position, span: Option<Span>) -> Self {
Self::new(
ParseErrorKind::UnmatchedDelimiter { opening, position },
span,
)
}
pub fn invalid_number<S1, S2>(value: S1, reason: S2, span: Option<Span>) -> Self
where
S1: Into<String>,
S2: Into<String>,
{
Self::new(
ParseErrorKind::InvalidNumber {
value: value.into(),
reason: reason.into(),
},
span,
)
}
pub fn invalid_latex_command<S>(command: S, span: Option<Span>) -> Self
where
S: Into<String>,
{
Self::new(
ParseErrorKind::InvalidLatexCommand {
command: command.into(),
},
span,
)
}
pub fn unknown_function<S>(name: S, span: Option<Span>) -> Self
where
S: Into<String>,
{
let name_str = name.into();
let suggestion = suggest_function(&name_str);
let mut error = Self::new(ParseErrorKind::UnknownFunction { name: name_str }, span);
error.suggestion = suggestion;
error
}
pub fn invalid_subscript<S>(reason: S, span: Option<Span>) -> Self
where
S: Into<String>,
{
Self::new(
ParseErrorKind::InvalidSubscript {
reason: reason.into(),
},
span,
)
}
pub fn invalid_superscript<S>(reason: S, span: Option<Span>) -> Self
where
S: Into<String>,
{
Self::new(
ParseErrorKind::InvalidSuperscript {
reason: reason.into(),
},
span,
)
}
pub fn malformed_matrix<S>(reason: S, span: Option<Span>) -> Self
where
S: Into<String>,
{
Self::new(
ParseErrorKind::MalformedMatrix {
reason: reason.into(),
},
span,
)
}
pub fn empty_expression(span: Option<Span>) -> Self {
Self::new(ParseErrorKind::EmptyExpression, span)
}
pub fn custom<S>(message: S, span: Option<Span>) -> Self
where
S: Into<String>,
{
Self::new(ParseErrorKind::Custom(message.into()), span)
}
}
impl fmt::Display for ParseError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match &self.span {
Some(span) => write!(f, "{} at {}", self.kind, span)?,
None => write!(f, "{}", self.kind)?,
}
if let Some(ctx) = &self.context {
write!(f, " ({})", ctx)?;
}
if let Some(suggestion) = &self.suggestion {
write!(f, " {}", suggestion)?;
}
Ok(())
}
}
impl std::error::Error for ParseError {}
pub type ParseResult<T> = Result<T, ParseError>;
#[derive(Debug)]
pub struct ErrorBuilder {
kind: ParseErrorKind,
span: Option<Span>,
context: Option<String>,
suggestion: Option<String>,
}
impl ErrorBuilder {
pub fn new(kind: ParseErrorKind) -> Self {
Self {
kind,
span: None,
context: None,
suggestion: None,
}
}
pub fn at_span(mut self, span: Span) -> Self {
self.span = Some(span);
self
}
pub fn at_position(mut self, position: Position) -> Self {
self.span = Some(Span::at(position));
self
}
pub fn with_context<S: Into<String>>(mut self, context: S) -> Self {
self.context = Some(context.into());
self
}
pub fn with_suggestion<S: Into<String>>(mut self, suggestion: S) -> Self {
self.suggestion = Some(suggestion.into());
self
}
pub fn build(self) -> ParseError {
ParseError {
kind: self.kind,
span: self.span,
context: self.context,
suggestion: self.suggestion,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_position_new() {
let pos = Position::new(5, 10, 42);
assert_eq!(pos.line, 5);
assert_eq!(pos.column, 10);
assert_eq!(pos.offset, 42);
}
#[test]
fn test_position_start() {
let pos = Position::start();
assert_eq!(pos.line, 1);
assert_eq!(pos.column, 1);
assert_eq!(pos.offset, 0);
}
#[test]
fn test_position_display() {
let pos = Position::new(5, 10, 42);
assert_eq!(pos.to_string(), "5:10");
}
#[test]
fn test_position_equality() {
let pos1 = Position::new(1, 1, 0);
let pos2 = Position::new(1, 1, 0);
let pos3 = Position::new(1, 2, 1);
assert_eq!(pos1, pos2);
assert_ne!(pos1, pos3);
}
#[test]
fn test_span_new() {
let start = Position::new(1, 1, 0);
let end = Position::new(1, 5, 4);
let span = Span::new(start, end);
assert_eq!(span.start, start);
assert_eq!(span.end, end);
}
#[test]
fn test_span_at() {
let pos = Position::new(1, 5, 4);
let span = Span::at(pos);
assert_eq!(span.start, pos);
assert_eq!(span.end, pos);
}
#[test]
fn test_span_start() {
let span = Span::start();
assert_eq!(span.start, Position::start());
assert_eq!(span.end, Position::start());
}
#[test]
fn test_span_display_single_position() {
let pos = Position::new(1, 5, 4);
let span = Span::at(pos);
assert_eq!(span.to_string(), "1:5");
}
#[test]
fn test_span_display_range() {
let start = Position::new(1, 1, 0);
let end = Position::new(1, 5, 4);
let span = Span::new(start, end);
assert_eq!(span.to_string(), "1:1-1:5");
}
#[test]
fn test_parse_error_kind_unexpected_token() {
let kind = ParseErrorKind::UnexpectedToken {
expected: vec!["number".to_string()],
found: "+".to_string(),
};
assert_eq!(kind.to_string(), "unexpected token '+', expected number");
}
#[test]
fn test_parse_error_kind_unexpected_token_multiple_expected() {
let kind = ParseErrorKind::UnexpectedToken {
expected: vec!["number".to_string(), "variable".to_string()],
found: "+".to_string(),
};
assert_eq!(
kind.to_string(),
"unexpected token '+', expected one of: number, variable"
);
}
#[test]
fn test_parse_error_kind_unexpected_token_no_expected() {
let kind = ParseErrorKind::UnexpectedToken {
expected: vec![],
found: "+".to_string(),
};
assert_eq!(kind.to_string(), "unexpected token '+'");
}
#[test]
fn test_parse_error_kind_unexpected_eof() {
let kind = ParseErrorKind::UnexpectedEof {
expected: vec!["number".to_string()],
};
assert_eq!(kind.to_string(), "unexpected end of input, expected number");
}
#[test]
fn test_parse_error_kind_unexpected_eof_multiple_expected() {
let kind = ParseErrorKind::UnexpectedEof {
expected: vec!["number".to_string(), "variable".to_string()],
};
assert_eq!(
kind.to_string(),
"unexpected end of input, expected one of: number, variable"
);
}
#[test]
fn test_parse_error_kind_unmatched_delimiter() {
let pos = Position::new(1, 5, 4);
let kind = ParseErrorKind::UnmatchedDelimiter {
opening: '(',
position: pos,
};
assert_eq!(kind.to_string(), "unmatched opening delimiter '(' at 1:5");
}
#[test]
fn test_parse_error_kind_invalid_number() {
let kind = ParseErrorKind::InvalidNumber {
value: "123.45.67".to_string(),
reason: "multiple decimal points".to_string(),
};
assert_eq!(
kind.to_string(),
"invalid number '123.45.67': multiple decimal points"
);
}
#[test]
fn test_parse_error_kind_invalid_latex_command() {
let kind = ParseErrorKind::InvalidLatexCommand {
command: r"\unknowncommand".to_string(),
};
assert_eq!(kind.to_string(), r"invalid LaTeX command '\unknowncommand'");
}
#[test]
fn test_parse_error_kind_unknown_function() {
let kind = ParseErrorKind::UnknownFunction {
name: "unknownfunc".to_string(),
};
assert_eq!(kind.to_string(), "unknown function 'unknownfunc'");
}
#[test]
fn test_parse_error_kind_invalid_subscript() {
let kind = ParseErrorKind::InvalidSubscript {
reason: "missing expression".to_string(),
};
assert_eq!(kind.to_string(), "invalid subscript: missing expression");
}
#[test]
fn test_parse_error_kind_invalid_superscript() {
let kind = ParseErrorKind::InvalidSuperscript {
reason: "missing expression".to_string(),
};
assert_eq!(kind.to_string(), "invalid superscript: missing expression");
}
#[test]
fn test_parse_error_kind_malformed_matrix() {
let kind = ParseErrorKind::MalformedMatrix {
reason: "inconsistent row lengths".to_string(),
};
assert_eq!(
kind.to_string(),
"malformed matrix: inconsistent row lengths"
);
}
#[test]
fn test_parse_error_kind_empty_expression() {
let kind = ParseErrorKind::EmptyExpression;
assert_eq!(kind.to_string(), "empty expression");
}
#[test]
fn test_parse_error_kind_custom() {
let kind = ParseErrorKind::Custom("custom error message".to_string());
assert_eq!(kind.to_string(), "custom error message");
}
#[test]
fn test_parse_error_new() {
let error = ParseError::new(ParseErrorKind::EmptyExpression, None);
assert_eq!(error.kind, ParseErrorKind::EmptyExpression);
assert_eq!(error.span, None);
assert_eq!(error.context, None);
}
#[test]
fn test_parse_error_with_context() {
let error = ParseError::new(ParseErrorKind::EmptyExpression, None)
.with_context("while parsing function arguments");
assert_eq!(
error.context,
Some("while parsing function arguments".to_string())
);
}
#[test]
fn test_parse_error_display_no_span() {
let error = ParseError::new(ParseErrorKind::EmptyExpression, None);
assert_eq!(error.to_string(), "empty expression");
}
#[test]
fn test_parse_error_display_with_span() {
let pos = Position::new(1, 5, 4);
let span = Span::at(pos);
let error = ParseError::new(ParseErrorKind::EmptyExpression, Some(span));
assert_eq!(error.to_string(), "empty expression at 1:5");
}
#[test]
fn test_parse_error_display_with_context() {
let error = ParseError::new(ParseErrorKind::EmptyExpression, None)
.with_context("while parsing function arguments");
assert_eq!(
error.to_string(),
"empty expression (while parsing function arguments)"
);
}
#[test]
fn test_parse_error_display_with_span_and_context() {
let pos = Position::new(1, 5, 4);
let span = Span::at(pos);
let error = ParseError::new(ParseErrorKind::EmptyExpression, Some(span))
.with_context("while parsing function arguments");
assert_eq!(
error.to_string(),
"empty expression at 1:5 (while parsing function arguments)"
);
}
#[test]
fn test_parse_error_unexpected_token() {
let error = ParseError::unexpected_token(vec!["number"], "+", None);
assert_eq!(
error.kind,
ParseErrorKind::UnexpectedToken {
expected: vec!["number".to_string()],
found: "+".to_string(),
}
);
}
#[test]
fn test_parse_error_unexpected_eof() {
let error = ParseError::unexpected_eof(vec!["closing parenthesis"], None);
assert_eq!(
error.kind,
ParseErrorKind::UnexpectedEof {
expected: vec!["closing parenthesis".to_string()],
}
);
}
#[test]
fn test_parse_error_unmatched_delimiter() {
let pos = Position::new(1, 1, 0);
let error = ParseError::unmatched_delimiter('(', pos, None);
assert_eq!(
error.kind,
ParseErrorKind::UnmatchedDelimiter {
opening: '(',
position: pos,
}
);
}
#[test]
fn test_parse_error_invalid_number() {
let error = ParseError::invalid_number("123.45.67", "multiple decimal points", None);
assert_eq!(
error.kind,
ParseErrorKind::InvalidNumber {
value: "123.45.67".to_string(),
reason: "multiple decimal points".to_string(),
}
);
}
#[test]
fn test_parse_error_invalid_latex_command() {
let error = ParseError::invalid_latex_command(r"\unknowncommand", None);
assert_eq!(
error.kind,
ParseErrorKind::InvalidLatexCommand {
command: r"\unknowncommand".to_string(),
}
);
}
#[test]
fn test_parse_error_unknown_function() {
let error = ParseError::unknown_function("unknownfunc", None);
assert_eq!(
error.kind,
ParseErrorKind::UnknownFunction {
name: "unknownfunc".to_string(),
}
);
}
#[test]
fn test_parse_error_invalid_subscript() {
let error = ParseError::invalid_subscript("missing expression", None);
assert_eq!(
error.kind,
ParseErrorKind::InvalidSubscript {
reason: "missing expression".to_string(),
}
);
}
#[test]
fn test_parse_error_invalid_superscript() {
let error = ParseError::invalid_superscript("missing expression", None);
assert_eq!(
error.kind,
ParseErrorKind::InvalidSuperscript {
reason: "missing expression".to_string(),
}
);
}
#[test]
fn test_parse_error_malformed_matrix() {
let error = ParseError::malformed_matrix("inconsistent row lengths", None);
assert_eq!(
error.kind,
ParseErrorKind::MalformedMatrix {
reason: "inconsistent row lengths".to_string(),
}
);
}
#[test]
fn test_parse_error_empty_expression() {
let error = ParseError::empty_expression(None);
assert_eq!(error.kind, ParseErrorKind::EmptyExpression);
}
#[test]
fn test_parse_error_custom() {
let error = ParseError::custom("custom error message", None);
assert_eq!(
error.kind,
ParseErrorKind::Custom("custom error message".to_string())
);
}
#[test]
fn test_error_builder_basic() {
let error = ErrorBuilder::new(ParseErrorKind::EmptyExpression).build();
assert_eq!(error.kind, ParseErrorKind::EmptyExpression);
assert_eq!(error.span, None);
assert_eq!(error.context, None);
}
#[test]
fn test_error_builder_with_span() {
let span = Span::at(Position::new(1, 5, 4));
let error = ErrorBuilder::new(ParseErrorKind::EmptyExpression)
.at_span(span)
.build();
assert_eq!(error.span, Some(span));
}
#[test]
fn test_error_builder_with_position() {
let pos = Position::new(1, 5, 4);
let error = ErrorBuilder::new(ParseErrorKind::EmptyExpression)
.at_position(pos)
.build();
assert_eq!(error.span, Some(Span::at(pos)));
}
#[test]
fn test_error_builder_with_context() {
let error = ErrorBuilder::new(ParseErrorKind::EmptyExpression)
.with_context("in function body")
.build();
assert_eq!(error.context, Some("in function body".to_string()));
}
#[test]
fn test_error_builder_complete() {
let pos = Position::new(1, 5, 4);
let error = ErrorBuilder::new(ParseErrorKind::EmptyExpression)
.at_position(pos)
.with_context("in function body")
.build();
assert_eq!(error.kind, ParseErrorKind::EmptyExpression);
assert_eq!(error.span, Some(Span::at(pos)));
assert_eq!(error.context, Some("in function body".to_string()));
}
#[test]
fn test_parse_result_ok() {
let result: ParseResult<i32> = Ok(42);
assert_eq!(result, Ok(42));
}
#[test]
fn test_parse_result_err() {
let result: ParseResult<i32> = Err(ParseError::empty_expression(None));
assert!(result.is_err());
}
#[test]
fn test_levenshtein_identical() {
assert_eq!(levenshtein("sin", "sin"), 0);
}
#[test]
fn test_levenshtein_substitution() {
assert_eq!(levenshtein("sin", "sen"), 1);
}
#[test]
fn test_levenshtein_insertion() {
assert_eq!(levenshtein("sin", "sign"), 1);
}
#[test]
fn test_levenshtein_deletion() {
assert_eq!(levenshtein("sign", "sin"), 1);
}
#[test]
fn test_levenshtein_multiple_edits() {
assert_eq!(levenshtein("cos", "xyz"), 3);
}
#[test]
fn test_levenshtein_empty_strings() {
assert_eq!(levenshtein("", "sin"), 3);
assert_eq!(levenshtein("cos", ""), 3);
assert_eq!(levenshtein("", ""), 0);
}
#[test]
fn test_suggest_function_close_match() {
assert_eq!(
suggest_function("sen"),
Some("Did you mean 'sin'?".to_string())
);
assert_eq!(
suggest_function("coz"),
Some("Did you mean 'cos'?".to_string())
);
assert_eq!(
suggest_function("sqr"),
Some("Did you mean 'sqrt'?".to_string())
);
}
#[test]
fn test_suggest_function_exact_match() {
assert_eq!(
suggest_function("sin"),
Some("Did you mean 'sin'?".to_string())
);
}
#[test]
fn test_suggest_function_no_match() {
assert_eq!(suggest_function("xyz"), None);
assert_eq!(suggest_function("foobar"), None);
}
#[test]
fn test_suggest_function_distance_2() {
assert_eq!(
suggest_function("sinn"),
Some("Did you mean 'sin'?".to_string())
);
}
#[test]
fn test_suggest_function_distance_3() {
assert_eq!(suggest_function("zzz"), None);
}
#[test]
fn test_unknown_function_with_suggestion() {
let error = ParseError::unknown_function("sen", None);
assert_eq!(
error.kind,
ParseErrorKind::UnknownFunction {
name: "sen".to_string(),
}
);
assert_eq!(error.suggestion, Some("Did you mean 'sin'?".to_string()));
}
#[test]
fn test_unknown_function_no_suggestion() {
let error = ParseError::unknown_function("xyz", None);
assert_eq!(
error.kind,
ParseErrorKind::UnknownFunction {
name: "xyz".to_string(),
}
);
assert_eq!(error.suggestion, None);
}
#[test]
fn test_error_display_with_suggestion() {
let error = ParseError::new(ParseErrorKind::EmptyExpression, None)
.with_suggestion("Did you mean 'sin'?");
assert_eq!(error.to_string(), "empty expression Did you mean 'sin'?");
}
#[test]
fn test_error_display_with_span_and_suggestion() {
let pos = Position::new(1, 5, 4);
let span = Span::at(pos);
let error = ParseError::new(ParseErrorKind::EmptyExpression, Some(span))
.with_suggestion("Did you mean 'sin'?");
assert_eq!(
error.to_string(),
"empty expression at 1:5 Did you mean 'sin'?"
);
}
#[test]
fn test_error_display_with_context_and_suggestion() {
let error = ParseError::new(ParseErrorKind::EmptyExpression, None)
.with_context("while parsing function arguments")
.with_suggestion("Did you mean 'sin'?");
assert_eq!(
error.to_string(),
"empty expression (while parsing function arguments) Did you mean 'sin'?"
);
}
#[test]
fn test_error_builder_with_suggestion() {
let error = ErrorBuilder::new(ParseErrorKind::EmptyExpression)
.with_suggestion("Did you mean 'sin'?")
.build();
assert_eq!(error.suggestion, Some("Did you mean 'sin'?".to_string()));
}
#[test]
fn test_with_suggestion_method() {
let error = ParseError::new(ParseErrorKind::EmptyExpression, None)
.with_suggestion("Try using 'sin' instead");
assert_eq!(
error.suggestion,
Some("Try using 'sin' instead".to_string())
);
}
}