use std::{borrow::Cow, fmt::Display};
use super::Location;
use crate::scanner::{self, Comment, CommentStyle, NationalStyle, Token, TokenType};
pub type Result<T> = std::result::Result<T, Error>;
#[derive(Debug)]
pub enum Error {
Scanner(scanner::Error),
Unexpected {
unexpected: Cow<'static, str>,
expected: &'static str,
loc: Location,
},
Unbalanced {
loc: Location,
},
FullPartitionedOuterJoin {
loc: Location,
},
AggregateFunctionNotAllowed {
loc: Location,
},
AnalyticFunctionNotAllowed {
loc: Location,
},
InvalidNumArgs {
loc: Location,
},
InvalidArgName {
loc: Location,
},
}
impl Error {
pub(super) fn unexpected_token<'s, T: AsRef<Token<'s>>>(t: T, expected: &'static str) -> Self {
let t = t.as_ref();
Self::Unexpected {
unexpected: unexpected_token_display(t),
expected,
loc: t.loc,
}
}
}
fn unexpected_token_display(t: &Token<'_>) -> Cow<'static, str> {
fn push_truncated(out: &mut String, mut max: usize, s: &str) {
if !s.is_empty() {
let starts_with_whitespace =
s.chars().next().map(|c| c.is_whitespace()).unwrap_or(false);
let mut splits = s.split_whitespace().enumerate();
while max > 0
&& let Some((i, split)) = splits.next()
{
if i == 0 {
if starts_with_whitespace {
out.push(' ');
max -= 1;
}
} else {
out.push(' ');
max -= 1;
}
if split.len() > max {
out.push_str(&split[..max]);
out.push('…');
return;
} else {
out.push_str(split);
max -= split.len();
}
}
if splits.next().is_some() {
out.push('…');
}
}
}
match &t.ttype {
TokenType::LeftParen => "`(`".into(),
TokenType::RightParen => "`)`".into(),
TokenType::Dot => "`.`".into(),
TokenType::Comma => "`,`".into(),
TokenType::Semicolon => "`;`".into(),
TokenType::Plus => "`+`".into(),
TokenType::Minus => "`-`".into(),
TokenType::Star => "`*`".into(),
TokenType::Slash => "`/`".into(),
TokenType::At => "`@`".into(),
TokenType::PipePipe => "`||`".into(),
TokenType::Equal => "`=`".into(),
TokenType::EqualGreater => "`=>`".into(),
TokenType::CaretEqual => "`^=`".into(),
TokenType::Less => "`<`".into(),
TokenType::LessEqual => "`<=`".into(),
TokenType::LessGreater => "`<>`".into(),
TokenType::Greater => "`>`".into(),
TokenType::GreaterEqual => "`>=`".into(),
TokenType::BangEqual => "`!=`".into(),
TokenType::QuestionMark => "`?`".into(),
TokenType::Comment(Comment(text, style)) => {
let (prefix, suffix) = match style {
CommentStyle::Line => ("`--", "`"),
CommentStyle::Block => ("`/*", "*/`"),
};
const COMMENT_MAX_LEN: usize = 20;
let mut out = String::with_capacity(COMMENT_MAX_LEN + 1 + prefix.len() + suffix.len());
out.push_str(prefix);
push_truncated(&mut out, COMMENT_MAX_LEN, text);
out.push_str(suffix);
out.into()
}
TokenType::Integer(lexem) | TokenType::Float(lexem) => format!("`{lexem}`").into(),
TokenType::Text(text, style) => {
let (prefix, s, suffix): (&str, &str, &str) = match (text, style) {
(scanner::Text::Regular(s), NationalStyle::None) => ("`'", s, "'`"),
(scanner::Text::Regular(s), NationalStyle::National) => ("`N'", s, "'`"),
(scanner::Text::Quoted(s), NationalStyle::None) => {
("`Q'", s.as_str_with_delimiters(), "'`")
}
(scanner::Text::Quoted(s), NationalStyle::National) => {
("`NQ'", s.as_str_with_delimiters(), "'`")
}
};
const TEXT_MAX_LEN: usize = 20;
let mut out = String::with_capacity(TEXT_MAX_LEN + 1 + prefix.len() + suffix.len());
out.push_str(prefix);
push_truncated(&mut out, TEXT_MAX_LEN, s);
out.push_str(suffix);
out.into()
}
TokenType::Placeholder(ident) => format!("':{ident}'").into(),
TokenType::Identifier(ident, _) => format!("'{ident}'").into(),
TokenType::Keyword(kw) => kw.as_str().into(),
}
}
impl From<scanner::Error> for Error {
fn from(value: scanner::Error) -> Self {
Self::Scanner(value)
}
}
impl From<&scanner::Error> for Error {
fn from(value: &scanner::Error) -> Self {
value.clone().into()
}
}
impl Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Error::Scanner(e) => e.fmt(f),
Error::Unexpected {
unexpected,
expected,
loc,
} => {
write!(f, "Unexpected {unexpected}; expected {expected} {loc}")
}
Error::Unbalanced { loc } => {
write!(f, "Unbalanced parentheses {loc}",)
}
Error::FullPartitionedOuterJoin { loc } => {
write!(
f,
"FULL PARTITIONED OUTER JOIN is not supported (ORA-39754) {loc}"
)
}
Error::AggregateFunctionNotAllowed { loc } => {
write!(f, "Aggregate functions not allowed here {loc}")
}
Error::AnalyticFunctionNotAllowed { loc } => {
write!(f, "Analytic functions not allowed here {loc}")
}
Error::InvalidNumArgs { loc } => write!(f, "Invalid number of arguments {loc}"),
Error::InvalidArgName { loc } => write!(
f,
"Invalid argument name, only simple identifiers allowed {loc}"
),
}
}
}
impl std::error::Error for Error {}