use std::borrow::Cow;
use std::fmt;
use std::sync::Arc;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Span {
pub start: usize,
pub end: usize,
}
impl Span {
pub fn new(start: usize, end: usize) -> Self {
Self { start, end }
}
pub fn line_col(&self, source: &str) -> (usize, usize) {
let mut line = 1;
let mut col = 1;
for (i, ch) in source.char_indices() {
if i >= self.start {
break;
}
if ch == '\n' {
line += 1;
col = 1;
} else {
col += 1;
}
}
(line, col)
}
pub fn snippet_line(&self, source: &str) -> (String, usize, usize) {
let line_start = source[..self.start].rfind('\n').map(|i| i + 1).unwrap_or(0);
let line_end = source[self.end..]
.find('\n')
.map(|i| self.end + i)
.unwrap_or(source.len());
let text = source[line_start..line_end].to_string();
let col_start = self.start - line_start;
let col_end = self.end - line_start;
(text, col_start, col_end)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Spanned<T> {
pub value: T,
pub span: Span,
}
impl<T> Spanned<T> {
pub fn new(value: T, span: Span) -> Self {
Self { value, span }
}
pub fn map<U>(self, f: impl FnOnce(T) -> U) -> Spanned<U> {
Spanned {
value: f(self.value),
span: self.span,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub enum Expected {
Keyword(Cow<'static, str>),
Symbol(Cow<'static, str>),
Category(Cow<'static, str>),
}
impl fmt::Display for Expected {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Expected::Keyword(kw) => write!(f, "`{}`", kw),
Expected::Symbol(s) => write!(f, "`{}`", s),
Expected::Category(c) => write!(f, "{}", c),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum NumberReason {
Overflow,
InvalidDigit,
Empty,
Other,
}
impl fmt::Display for NumberReason {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
NumberReason::Overflow => write!(f, "number too large"),
NumberReason::InvalidDigit => write!(f, "invalid digit"),
NumberReason::Empty => write!(f, "empty number"),
NumberReason::Other => write!(f, "invalid number"),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum ErrorKind {
UnexpectedToken {
expected: Vec<Expected>,
found: String,
},
UnexpectedEof { expected: Vec<Expected> },
UnterminatedString,
UnterminatedComment,
InvalidEscape { sequence: String },
InvalidNumber { raw: String, reason: NumberReason },
EmptyInput,
MissingClause {
clause: &'static str,
after: &'static str,
},
DuplicateClause { clause: &'static str },
InvalidAssignment { reason: &'static str },
Unsupported { production: Cow<'static, str> },
Internal { message: String },
UnresolvedVariable { name: String },
RedeclaredVariable { name: String, first_span: Span },
AggregationMix { non_grouping: Vec<String> },
DistinctNotAllowed,
InvalidReference { name: String, reason: &'static str },
}
impl fmt::Display for ErrorKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ErrorKind::UnexpectedToken { expected, found } => {
write!(f, "unexpected token `{}`, expected ", found)?;
fmt_expected_list(expected, f)
}
ErrorKind::UnexpectedEof { expected } => {
write!(f, "unexpected end of input, expected ")?;
fmt_expected_list(expected, f)
}
ErrorKind::UnterminatedString => write!(f, "unterminated string literal"),
ErrorKind::UnterminatedComment => write!(f, "unterminated comment"),
ErrorKind::InvalidEscape { sequence } => {
write!(f, "invalid escape sequence: `{}`", sequence)
}
ErrorKind::InvalidNumber { raw, reason } => {
write!(f, "invalid number `{}`: {}", raw, reason)
}
ErrorKind::EmptyInput => write!(f, "empty input"),
ErrorKind::MissingClause { clause, after } => {
write!(f, "expected {} after `{}`", clause, after)
}
ErrorKind::DuplicateClause { clause } => {
write!(f, "duplicate `{}` clause", clause)
}
ErrorKind::InvalidAssignment { reason } => {
write!(f, "invalid assignment: {}", reason)
}
ErrorKind::Unsupported { production } => {
write!(f, "unsupported grammar production: {}", production)
}
ErrorKind::Internal { message } => {
write!(f, "internal error: {}", message)
}
ErrorKind::UnresolvedVariable { name } => {
write!(f, "unresolved variable `{}`", name)
}
ErrorKind::RedeclaredVariable { name, first_span } => {
write!(
f,
"variable `{}` redeclared (first declared at {:?})",
name, first_span
)
}
ErrorKind::AggregationMix { non_grouping } => {
write!(
f,
"mixing aggregate and non-aggregate expressions: non-grouping keys: {:?}",
non_grouping
)
}
ErrorKind::DistinctNotAllowed => {
write!(f, "DISTINCT is not allowed in this context")
}
ErrorKind::InvalidReference { name, reason } => {
write!(f, "invalid reference to `{}`: {}", name, reason)
}
}
}
}
fn fmt_expected_list(expected: &[Expected], f: &mut fmt::Formatter<'_>) -> fmt::Result {
if expected.is_empty() {
write!(f, "nothing in particular")
} else if expected.len() == 1 {
write!(f, "{}", expected[0])
} else {
for (i, e) in expected.iter().enumerate() {
if i > 0 {
write!(f, ", ")?;
}
write!(f, "{}", e)?;
}
Ok(())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum NoteLevel {
Info,
Warning,
Help,
}
impl fmt::Display for NoteLevel {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
NoteLevel::Info => write!(f, "info"),
NoteLevel::Warning => write!(f, "warning"),
NoteLevel::Help => write!(f, "help"),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Note {
pub span: Span,
pub message: Cow<'static, str>,
pub level: NoteLevel,
}
#[derive(Debug, Clone)]
pub struct CypherError {
pub kind: ErrorKind,
pub span: Span,
pub source_label: Option<Arc<str>>,
pub notes: Vec<Note>,
pub(crate) source: Option<Arc<str>>,
}
impl CypherError {
pub fn kind(&self) -> &ErrorKind {
&self.kind
}
pub fn span(&self) -> Span {
self.span
}
pub fn notes(&self) -> &[Note] {
&self.notes
}
pub fn source_label(&self) -> Option<&str> {
self.source_label.as_deref()
}
pub fn render(&self, source: &str) -> String {
render_diagnostic(self, source)
}
}
impl fmt::Display for CypherError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if let Some(ref src) = self.source {
write!(f, "{}", render_diagnostic(self, src))
} else {
write!(f, "error: {}", self.kind)
}
}
}
impl std::error::Error for CypherError {}
impl From<std::convert::Infallible> for CypherError {
fn from(e: std::convert::Infallible) -> Self {
match e {}
}
}
#[derive(Debug, Clone)]
pub struct Diagnostics {
pub errors: Vec<CypherError>,
}
impl Diagnostics {
pub fn is_empty(&self) -> bool {
self.errors.is_empty()
}
pub fn len(&self) -> usize {
self.errors.len()
}
pub fn iter(&self) -> impl Iterator<Item = &CypherError> {
self.errors.iter()
}
}
impl IntoIterator for Diagnostics {
type Item = CypherError;
type IntoIter = std::vec::IntoIter<CypherError>;
fn into_iter(self) -> Self::IntoIter {
self.errors.into_iter()
}
}
impl<'a> IntoIterator for &'a Diagnostics {
type Item = &'a CypherError;
type IntoIter = std::slice::Iter<'a, CypherError>;
fn into_iter(self) -> Self::IntoIter {
self.errors.iter()
}
}
impl fmt::Display for Diagnostics {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for (i, err) in self.errors.iter().enumerate() {
if i > 0 {
writeln!(f)?;
}
if let Some(ref src) = err.source {
write!(f, "{}", render_diagnostic(err, src))?;
} else {
write!(f, "error: {}", err.kind)?;
}
}
Ok(())
}
}
impl fmt::Display for Span {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}..{}", self.start, self.end)
}
}
pub type Result<T> = std::result::Result<T, CypherError>;
#[cfg(feature = "miette")]
mod miette_impl;
mod render;
pub use render::render_diagnostic;