pub mod source_file;
use std::{
collections::HashMap,
fmt::{Debug, Display},
num::{ParseFloatError, ParseIntError},
ops::Range,
};
use ariadne::{Config, IndexType, Label, Report, ReportBuilder, ReportKind};
use chumsky::{
extra,
input::{Input, MappedSpan},
primitive::{any, choice, just, map_ctx},
text::{newline, TextExpected},
util::Maybe,
DefaultExpected, Parser,
};
use crate::{
ast::{CurlyKind, EscapeSequence},
AriadneCache, Span,
};
#[derive(PartialEq, Clone)]
pub enum Error<'s> {
Multiple(Vec<Error<'s>>),
ExpectedEndOfInput(Span<'s>),
UnexpectedEndOfInput(Span<'s>),
ExpectedOneOfMultipleChars {
at: Span<'s>,
got: Option<char>,
expected: Vec<char>,
},
ExpectedChar {
at: Span<'s>,
got: Option<char>,
expected: char,
},
UnexpectedChar(Span<'s>, Option<char>),
ExpectedDigit {
at: Span<'s>,
got: Option<char>,
expected: Range<u32>,
},
ExpectedIdentifier {
at: Span<'s>,
got: Option<char>,
expected: &'s str,
},
ExpectedIdentifierPart {
at: Span<'s>,
got: Option<char>,
},
ExpectedInlineWhitespace {
at: Span<'s>,
got: Option<char>,
},
ExpectedWhitespace {
at: Span<'s>,
got: Option<char>,
},
ExpectedNewline {
at: Span<'s>,
got: Option<char>,
},
UnclosedDelimiter {
start: Span<'s>,
kind: DelimiterType,
},
UnknownEscapeSequence(Span<'s>),
MissingTemplateBodyStartNewline {
template_start: Span<'s>,
expected_newline_span: Span<'s>,
},
InvalidIntLiteral(ParseIntError, Span<'s>),
InvalidFloatLiteral(ParseFloatError, Span<'s>),
#[allow(clippy::doc_markdown)]
InvalidNumericMemberAccess(ParseIntError, Span<'s>),
WrongCurlyCountTemplateCurlyEscapeSequence {
curly_kind: CurlyKind,
template_curly_span: Span<'s>,
template_curly_count: usize,
escape_sequence_span: Span<'s>,
escape_sequence_curly_count: usize,
},
}
impl<'s> Error<'s> {
#[allow(clippy::too_many_lines)]
fn write_to_buf(&self, cache: &mut AriadneCache<'s>, buf: &mut Vec<u8>, with_color: bool) {
fn builder(at: Span<'_>, with_color: bool) -> ReportBuilder<'_, Span<'_>> {
Report::build(ReportKind::Error, at).with_config(
Config::new()
.with_color(with_color)
.with_index_type(IndexType::Byte),
)
}
fn write<'s>(
builder: ReportBuilder<'s, Span<'s>>,
cache: &mut AriadneCache<'s>,
buf: &mut Vec<u8>,
) {
builder.finish().write(cache, buf).unwrap();
}
match self {
Self::Multiple(inner) => {
for err in inner {
err.write_to_buf(cache, buf, with_color);
}
}
Self::ExpectedEndOfInput(at) => write(
builder(*at, with_color).with_message("Expected end of input").with_label(Label::new(*at).with_message("Expected the end of input here")),
cache,
buf
),
Self::UnexpectedEndOfInput(at) => write(
builder(*at, with_color).with_message("Unexpected end of input"),
cache,
buf
),
Self::ExpectedChar { at, expected, got: _ } => write(
builder(*at, with_color).with_message("Expected character").with_label(Label::new(*at).with_message(format!("Expected {expected:?}"))),
cache,
buf
),
Self::ExpectedOneOfMultipleChars { at, expected, got: _ } => write(
builder(*at, with_color).with_message("Expected a different character").with_label(Label::new(*at).with_message(format!("Expected one of the following characters: {expected:?}"))),
cache,
buf
),
Self::UnexpectedChar(at, _got) => write(
builder(*at, with_color).with_message("Unexpected character").with_label(Label::new(*at).with_message("Unexpected character")),
cache,
buf
),
Self::ExpectedDigit { at, expected, got: _ } => write(
builder(*at, with_color).with_message("Expected a digit").with_label(Label::new(*at).with_message(format!("Expected a digit in the range of {expected:?}"))),
cache,
buf
),
Self::ExpectedIdentifier { at, expected, got: _ } => write(
builder(*at, with_color).with_message("Expected identifier").with_label(Label::new(*at).with_message(format!("Expected identifier {expected:?}"))),
cache,
buf
),
Self::ExpectedIdentifierPart { at, got: _ } => write(
builder(*at, with_color).with_message("Expected identifier").with_label(Label::new(*at).with_message("Expected an indentifier")),
cache,
buf
),
Self::ExpectedInlineWhitespace { at, got: _ } => write(
builder(*at, with_color).with_message("Expected inline whitespace").with_label(Label::new(*at).with_message("Expected inline whitespace here")),
cache,
buf
),
Self::ExpectedWhitespace { at, got: _ } => write(
builder(*at, with_color).with_message("Expected whitespace").with_label(Label::new(*at).with_message("Expected whitespace here")),
cache,
buf
),
Self::ExpectedNewline { at, got: _ } => write(
builder(*at, with_color).with_message("Expected a newline").with_label(Label::new(*at).with_message("Expected a newline here")),
cache,
buf
),
Self::InvalidFloatLiteral(err, at) => write(
builder(*at, with_color).with_message(format!("Invalid float literal: {err}")).with_label(Label::new(*at).with_message("This float literal is invalid")),
cache,
buf,
),
Self::InvalidIntLiteral(err, at) => write(
builder(*at, with_color).with_message(format!("Invalid int literal: {err}")).with_label(Label::new(*at).with_message("This int literal is invalid")),
cache,
buf,
),
Self::InvalidNumericMemberAccess(err, at) => write(
builder(*at, with_color).with_message(format!("Invalid tuple access: {err}")).with_label(Label::new(*at).with_message("This tuple access is invalid")),
cache,
buf,
),
Self::MissingTemplateBodyStartNewline {
template_start,
expected_newline_span,
} => write(
builder(*expected_newline_span, with_color)
.with_message("Missing Template body newline")
.with_note("Template bodies need to start on a newline")
.with_label(
Label::new(*expected_newline_span)
.with_message("Expected a newline here")
.with_priority(1),
)
.with_label(
Label::new(*template_start)
.with_message("Template body starts here")
.with_priority(0),
),
cache,
buf,
),
Self::UnclosedDelimiter { start, kind } => write(
builder(*start, with_color)
.with_message("Unclosed delimiter")
.with_label(
Label::new(*start).with_message(format!("This {kind} was never closed")),
),
cache,
buf,
),
Self::UnknownEscapeSequence(at) => write(
builder(*at, with_color).with_message("Unknown escape sequence").with_label(Label::new(*at).with_message("This escape sequence is unknown")),
cache,
buf,
),
Self::WrongCurlyCountTemplateCurlyEscapeSequence {
curly_kind,
template_curly_span,
template_curly_count,
escape_sequence_span,
escape_sequence_curly_count,
} => write(
builder(*escape_sequence_span, with_color)
.with_message("Too few curly braces in escape sequence")
.with_label(Label::new(*template_curly_span).with_message(format!(
"Template body starts here with {template_curly_count} curly braces"
)))
.with_label(Label::new(*escape_sequence_span).with_message(format!("The template body uses {template_curly_count} curly braces but this {curly_kind} curly brace escape sequence escapes just {escape_sequence_curly_count}")))
.with_note(format!("Up to {template_curly_count} consecutive curly braces can be used without escaping them")),
cache,
buf
),
}
}
}
impl Debug for Error<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str("\n")?;
<Self as Display>::fmt(self, f)
}
}
impl Display for Error<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut buf = Vec::new();
let mut cache = AriadneCache(HashMap::new());
self.write_to_buf(&mut cache, &mut buf, !f.alternate());
f.write_str(std::str::from_utf8(&buf).unwrap())
}
}
impl std::error::Error for Error<'_> {}
impl<'s, F: Fn(chumsky::span::SimpleSpan) -> Span<'s> + 's>
chumsky::error::Error<'s, MappedSpan<Span<'s>, &'s str, F>> for Error<'s>
{
fn merge(self, other: Self) -> Self {
match (self, other) {
(Error::Multiple(mut previous), Error::Multiple(next)) => {
previous.extend(next);
Error::Multiple(previous)
}
(Error::Multiple(mut previous), other) => {
previous.push(other);
Error::Multiple(previous)
}
(first, second) => Error::Multiple(vec![first, second]),
}
}
}
impl<'s, F: Fn(chumsky::span::SimpleSpan) -> Span<'s> + 's>
chumsky::error::LabelError<'s, MappedSpan<Span<'s>, &'s str, F>, DefaultExpected<'s, char>>
for Error<'s>
{
fn expected_found<E: IntoIterator<Item = DefaultExpected<'s, char>>>(
expected: E,
found: Option<
chumsky::util::MaybeRef<'s, <MappedSpan<Span<'s>, &'s str, F> as Input<'s>>::Token>,
>,
span: <MappedSpan<Span<'s>, &'s str, F> as Input<'s>>::Span,
) -> Self {
Self::Multiple(
expected
.into_iter()
.map(|expected| match expected {
DefaultExpected::Any => Self::UnexpectedEndOfInput(span),
DefaultExpected::EndOfInput => Self::ExpectedEndOfInput(span),
DefaultExpected::SomethingElse => {
Self::UnexpectedChar(span, found.map(Maybe::into_inner))
}
DefaultExpected::Token(t) => Self::ExpectedChar {
at: span,
expected: t.into_inner(),
got: found.map(Maybe::into_inner),
},
_ => Self::UnexpectedChar(span, found.map(Maybe::into_inner)),
})
.collect(),
)
}
}
impl<'s, F: Fn(chumsky::span::SimpleSpan) -> Span<'s> + 's>
chumsky::error::LabelError<
's,
MappedSpan<Span<'s>, &'s str, F>,
TextExpected<'s, MappedSpan<Span<'s>, &'s str, F>>,
> for Error<'s>
{
fn expected_found<
E: IntoIterator<Item = TextExpected<'s, MappedSpan<Span<'s>, &'s str, F>>>,
>(
expected: E,
found: Option<
chumsky::util::MaybeRef<'s, <MappedSpan<Span<'s>, &'s str, F> as Input<'s>>::Token>,
>,
span: <MappedSpan<Span<'s>, &'s str, F> as Input<'s>>::Span,
) -> Self {
Self::Multiple(
expected
.into_iter()
.map(|expected| match expected {
TextExpected::Digit(range) => Error::ExpectedDigit {
at: span,
expected: range,
got: found.map(Maybe::into_inner),
},
TextExpected::Identifier(ident) => Error::ExpectedIdentifier {
at: span,
expected: ident,
got: found.map(Maybe::into_inner),
},
TextExpected::IdentifierPart => Error::ExpectedIdentifierPart {
at: span,
got: found.map(Maybe::into_inner),
},
TextExpected::InlineWhitespace => Error::ExpectedInlineWhitespace {
at: span,
got: found.map(Maybe::into_inner),
},
TextExpected::Newline => Error::ExpectedNewline {
at: span,
got: found.map(Maybe::into_inner),
},
TextExpected::Whitespace => Error::ExpectedWhitespace {
at: span,
got: found.map(Maybe::into_inner),
},
_ => Error::UnexpectedChar(span, found.map(Maybe::into_inner)),
})
.collect(),
)
}
}
impl<'s, F: Fn(chumsky::span::SimpleSpan) -> Span<'s> + 's>
chumsky::error::LabelError<'s, MappedSpan<Span<'s>, &'s str, F>, Maybe<char, &'s char>>
for Error<'s>
{
fn expected_found<E: IntoIterator<Item = Maybe<char, &'s char>>>(
expected: E,
found: Option<
chumsky::util::MaybeRef<'s, <MappedSpan<Span<'s>, &'s str, F> as Input<'s>>::Token>,
>,
span: <MappedSpan<Span<'s>, &'s str, F> as Input<'s>>::Span,
) -> Self {
Self::ExpectedOneOfMultipleChars {
at: span,
got: found.map(Maybe::into_inner),
expected: expected.into_iter().map(Maybe::into_inner).collect(),
}
}
}
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum DelimiterType {
MultiLineComment,
ParameterList,
ArrayType,
ArrayLiteral,
TupleType,
TupleLiteral,
StructType,
BracketedType,
StructLiteral,
StringLiteral,
BracketedExpression,
TemplateBody { curly_count: usize },
SelectArms,
Command { curly_count: usize },
}
impl Display for DelimiterType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::MultiLineComment => write!(f, "multiline comment"),
Self::ParameterList => write!(f, "parameter list"),
Self::ArrayType => write!(f, "array type"),
Self::ArrayLiteral => write!(f, "array literal"),
Self::TupleType => write!(f, "tuple type"),
Self::TupleLiteral => write!(f, "tuple literal"),
Self::StructType => write!(f, "struct type"),
Self::BracketedType => write!(f, "bracketed type"),
Self::StructLiteral => write!(f, "struct literal"),
Self::StringLiteral => write!(f, "string literal"),
Self::BracketedExpression => write!(f, "bracketed expression"),
Self::TemplateBody { curly_count } if *curly_count == 1 => write!(f, "template body"),
Self::TemplateBody { curly_count } => {
write!(f, "template body ({curly_count} curly braces)")
}
Self::SelectArms => write!(f, "select arms"),
Self::Command { curly_count } if *curly_count == 1 => write!(f, "command"),
Self::Command { curly_count } => write!(f, "command ({curly_count} curly braces)"),
}
}
}
fn with_span<'a, I: Input<'a>, T, E: extra::ParserExtra<'a, I>>(
original: impl Parser<'a, I, T, E> + Clone,
) -> impl Parser<'a, I, (T, I::Span), E> + Clone {
original.map_with(|output, extra| (output, extra.span()))
}
fn map_err_missing_delimiter<
's,
I: Input<'s, Span = Span<'s>>,
S,
T,
E,
Extra: extra::ParserExtra<'s, I, Error = Error<'s>>,
>(
start: impl Parser<'s, I, S, Extra> + Clone,
output: impl Parser<'s, I, T, Extra> + Clone,
end: impl Parser<'s, I, E, Extra> + Clone,
delimiter_type: DelimiterType,
) -> impl Parser<'s, I, T, Extra> + Clone {
start
.to_span()
.then(output)
.then(end.or_not())
.try_map(move |((start, output), end), _| match end {
Some(_) => Ok(output),
None => Err(Error::UnclosedDelimiter {
start,
kind: delimiter_type,
}),
})
}
fn ignore_ctx<'a, I: Input<'a>, T, E: extra::ParserExtra<'a, I>>(
original: impl Parser<'a, I, T, extra::Full<E::Error, E::State, ()>> + Clone,
) -> impl Parser<'a, I, T, E> + Clone {
map_ctx(|_| (), original)
}
#[macro_export]
#[allow(non_snake_case)]
macro_rules! __internal__parse_with_path {
($p:expr, $path: expr, $source:expr) => {{
use ::chumsky::{input::Input, Parser};
$p.parse(
$source.map_span(|span: ::chumsky::span::SimpleSpan| $crate::Span {
start: span.start,
end: span.end,
path_and_source: ($path, $source),
}),
)
}};
}
#[macro_export]
#[allow(non_snake_case)]
macro_rules! __internal__parse_include_str {
($p:expr, $path:literal) => {{
use ::chumsky::input::Input;
$p.parse(
include_str!($path).map_span(|span: ::chumsky::span::SimpleSpan| $crate::Span {
start: span.start,
end: span.end,
path_and_source: ($path, include_str!($path)),
}),
)
}};
}
#[macro_export]
#[allow(non_snake_case)]
macro_rules! __internal__span_macro_with_path {
($span:path, $path:expr, $source:expr) => {
macro_rules! s {
($expr:expr) => {{
$span {
start: $expr.start,
end: $expr.end,
path_and_source: ($path, $source),
}
}};
}
};
}
#[macro_export]
#[allow(non_snake_case)]
macro_rules! __internal__span_macro_include_str {
($span:path, $path:literal) => {
macro_rules! s {
($expr:expr) => {{
$span {
start: $expr.start,
end: $expr.end,
path_and_source: ($path, include_str!($path)),
}
}};
}
};
}
#[macro_export]
#[allow(non_snake_case)]
macro_rules! __internal__parser {
($vis:vis $name:ident, $output:ty, $content:block) => {
$vis fn $name<'s, F: Fn(::chumsky::span::SimpleSpan) -> $crate::Span<'s> + 's>() -> impl ::chumsky::Parser<'s, ::chumsky::input::MappedSpan<$crate::Span<'s>, &'s str, F>, $output, ::chumsky::extra::Err<$crate::parse::Error<'s>>> + Clone {
$content
}
};
($vis:vis $name:ident($($arg:ident: $arg_ty:ty),+), $output:ty, $content:block) => {
$vis fn $name<'s, F: Fn(::chumsky::span::SimpleSpan) -> $crate::Span<'s> + 's>($($arg: $arg_ty),+) -> impl ::chumsky::Parser<'s, ::chumsky::input::MappedSpan<$crate::Span<'s>, &'s str, F>, $output, ::chumsky::extra::Err<$crate::parse::Error<'s>>> + Clone {
$content
}
};
($vis:vis $name:ident, $output:ty, $extra:ty, $content:block) => {
$vis fn $name<'s, F: Fn(::chumsky::span::SimpleSpan) -> $crate::Span<'s> + 's>() -> impl ::chumsky::Parser<'s, ::chumsky::input::MappedSpan<$crate::Span<'s>, &'s str, F>, $output, $extra> + Clone {
$content
}
};
($vis:vis $name:ident($($arg:ident: $arg_ty:ty),+), $output:ty, $extra:ty, $content:block) => {
$vis fn $name<'s, F: Fn(::chumsky::span::SimpleSpan) -> $crate::Span<'s> + 's>($($arg: $arg_ty),+) -> impl ::chumsky::Parser<'s, ::chumsky::input::MappedSpan<$crate::Span<'s>, &'s str, F>, $output, $extra> + Clone {
$content
}
};
}
__internal__parser! {capturing_newline, &'s str, {
newline().to_slice()
}}
__internal__parser! {escape_sequence, EscapeSequence, {
choice((
just("\\n").to(EscapeSequence::Newline),
just("\\t").to(EscapeSequence::Tab),
just("\\r").to(EscapeSequence::CarriageReturn),
just("\\\\").to(EscapeSequence::Backslash),
just("\\").then(any()).try_map(|_, span| Err(Error::UnknownEscapeSequence(span)))
))
}}