#[derive(Debug, Clone)]
#[cfg_attr(test, derive(Eq, PartialEq))]
pub enum Error {
InvalidEscapeSequence(InvalidEscapeSequence),
MissingVariableName(MissingVariableName),
UnexpectedCharacter(UnexpectedCharacter),
MissingClosingBrace(MissingClosingBrace),
NoSuchVariable(NoSuchVariable),
}
#[derive(Debug, Clone)]
#[cfg_attr(test, derive(Eq, PartialEq))]
pub enum ParseError {
InvalidEscapeSequence(InvalidEscapeSequence),
MissingVariableName(MissingVariableName),
UnexpectedCharacter(UnexpectedCharacter),
MissingClosingBrace(MissingClosingBrace),
}
#[derive(Debug, Clone)]
#[cfg_attr(test, derive(Eq, PartialEq))]
pub enum ExpandError {
NoSuchVariable(NoSuchVariable),
}
impl From<InvalidEscapeSequence> for Error {
#[inline]
fn from(other: InvalidEscapeSequence) -> Self {
Self::InvalidEscapeSequence(other)
}
}
impl From<MissingVariableName> for Error {
#[inline]
fn from(other: MissingVariableName) -> Self {
Self::MissingVariableName(other)
}
}
impl From<UnexpectedCharacter> for Error {
#[inline]
fn from(other: UnexpectedCharacter) -> Self {
Self::UnexpectedCharacter(other)
}
}
impl From<MissingClosingBrace> for Error {
#[inline]
fn from(other: MissingClosingBrace) -> Self {
Self::MissingClosingBrace(other)
}
}
impl From<NoSuchVariable> for Error {
#[inline]
fn from(other: NoSuchVariable) -> Self {
Self::NoSuchVariable(other)
}
}
impl From<ParseError> for Error {
#[inline]
fn from(other: ParseError) -> Self {
match other {
ParseError::InvalidEscapeSequence(e) => Self::InvalidEscapeSequence(e),
ParseError::MissingVariableName(e) => Self::MissingVariableName(e),
ParseError::UnexpectedCharacter(e) => Self::UnexpectedCharacter(e),
ParseError::MissingClosingBrace(e) => Self::MissingClosingBrace(e),
}
}
}
impl From<ExpandError> for Error {
#[inline]
fn from(other: ExpandError) -> Self {
match other {
ExpandError::NoSuchVariable(e) => Self::NoSuchVariable(e),
}
}
}
impl From<InvalidEscapeSequence> for ParseError {
#[inline]
fn from(other: InvalidEscapeSequence) -> Self {
Self::InvalidEscapeSequence(other)
}
}
impl From<MissingVariableName> for ParseError {
#[inline]
fn from(other: MissingVariableName) -> Self {
Self::MissingVariableName(other)
}
}
impl From<UnexpectedCharacter> for ParseError {
#[inline]
fn from(other: UnexpectedCharacter) -> Self {
Self::UnexpectedCharacter(other)
}
}
impl From<MissingClosingBrace> for ParseError {
#[inline]
fn from(other: MissingClosingBrace) -> Self {
Self::MissingClosingBrace(other)
}
}
impl From<NoSuchVariable> for ExpandError {
#[inline]
fn from(other: NoSuchVariable) -> Self {
Self::NoSuchVariable(other)
}
}
impl std::error::Error for Error {}
impl std::fmt::Display for Error {
#[inline]
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
Self::InvalidEscapeSequence(e) => e.fmt(f),
Self::MissingVariableName(e) => e.fmt(f),
Self::UnexpectedCharacter(e) => e.fmt(f),
Self::MissingClosingBrace(e) => e.fmt(f),
Self::NoSuchVariable(e) => e.fmt(f),
}
}
}
impl std::error::Error for ParseError {}
impl std::fmt::Display for ParseError {
#[inline]
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
Self::InvalidEscapeSequence(e) => e.fmt(f),
Self::MissingVariableName(e) => e.fmt(f),
Self::UnexpectedCharacter(e) => e.fmt(f),
Self::MissingClosingBrace(e) => e.fmt(f),
}
}
}
impl std::error::Error for ExpandError {}
impl std::fmt::Display for ExpandError {
#[inline]
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
Self::NoSuchVariable(e) => e.fmt(f),
}
}
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum CharOrByte {
Char(char),
Byte(u8),
}
impl CharOrByte {
#[inline]
pub fn source_len(&self) -> usize {
match self {
Self::Char(c) => c.len_utf8(),
Self::Byte(_) => 1,
}
}
pub fn quoted_printable(&self) -> impl std::fmt::Display {
#[derive(Copy, Clone, Debug)]
struct QuotedPrintable {
inner: CharOrByte,
}
impl std::fmt::Display for QuotedPrintable {
#[inline]
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self.inner {
CharOrByte::Char(value) => write!(f, "{value:?}"),
CharOrByte::Byte(value) => {
if value.is_ascii() {
write!(f, "{:?}", char::from(value))
} else {
write!(f, "'\\x{value:02X}'")
}
},
}
}
}
QuotedPrintable { inner: *self }
}
}
impl std::fmt::Display for CharOrByte {
#[inline]
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match *self {
Self::Char(value) => write!(f, "{value}"),
Self::Byte(value) => {
if value.is_ascii() {
write!(f, "{}", char::from(value))
} else {
write!(f, "0x{value:02X}")
}
},
}
}
}
#[derive(Debug, Clone)]
#[cfg_attr(test, derive(Eq, PartialEq))]
pub struct InvalidEscapeSequence {
pub position: usize,
pub character: Option<CharOrByte>,
}
impl std::error::Error for InvalidEscapeSequence {}
impl std::fmt::Display for InvalidEscapeSequence {
#[inline]
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
if let Some(c) = self.character {
write!(f, "Invalid escape sequence: \\{c}")
} else {
write!(f, "Invalid escape sequence: missing escape character")
}
}
}
#[derive(Debug, Clone)]
#[cfg_attr(test, derive(Eq, PartialEq))]
pub struct MissingVariableName {
pub position: usize,
pub len: usize,
}
impl std::error::Error for MissingVariableName {}
impl std::fmt::Display for MissingVariableName {
#[inline]
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "Missing variable name")
}
}
#[derive(Debug, Clone)]
#[cfg_attr(test, derive(Eq, PartialEq))]
pub struct UnexpectedCharacter {
pub position: usize,
pub character: CharOrByte,
pub expected: ExpectedCharacter,
}
impl std::error::Error for UnexpectedCharacter {}
impl std::fmt::Display for UnexpectedCharacter {
#[inline]
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(
f,
"Unexpected character: {}, expected {}",
self.character.quoted_printable(),
self.expected.message()
)
}
}
#[derive(Debug, Clone)]
#[cfg_attr(test, derive(Eq, PartialEq))]
pub struct ExpectedCharacter {
pub(crate) message: &'static str,
}
impl ExpectedCharacter {
#[inline]
pub fn message(&self) -> &str {
self.message
}
}
#[derive(Debug, Clone)]
#[cfg_attr(test, derive(Eq, PartialEq))]
pub struct MissingClosingBrace {
pub position: usize,
}
impl std::error::Error for MissingClosingBrace {}
impl std::fmt::Display for MissingClosingBrace {
#[inline]
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "Missing closing brace")
}
}
#[derive(Debug, Clone)]
#[cfg_attr(test, derive(Eq, PartialEq))]
pub struct NoSuchVariable {
pub position: usize,
pub name: String,
}
impl std::error::Error for NoSuchVariable {}
impl std::fmt::Display for NoSuchVariable {
#[inline]
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "No such variable: ${}", self.name)
}
}
impl Error {
#[inline]
pub fn source_range(&self) -> std::ops::Range<usize> {
let (start, len) = match &self {
Self::InvalidEscapeSequence(e) => {
let char_len = e.character.map_or(0, |x| x.source_len());
(e.position, 1 + char_len)
},
Self::MissingVariableName(e) => (e.position, e.len),
Self::UnexpectedCharacter(e) => (e.position, e.character.source_len()),
Self::MissingClosingBrace(e) => (e.position, 1),
Self::NoSuchVariable(e) => (e.position, e.name.len()),
};
std::ops::Range {
start,
end: start + len,
}
}
#[inline]
pub fn source_line<'a>(&self, source: &'a str) -> &'a str {
let position = self.source_range().start;
let start = line_start(source, position);
let end = line_end(source, position);
&source[start..end]
}
#[inline]
pub fn write_source_highlighting(&self, f: &mut impl std::fmt::Write, source: &str) -> std::fmt::Result {
use unicode_width::UnicodeWidthStr;
let range = self.source_range();
let line = self.source_line(source);
if line.width() > 60 {
return Ok(());
}
write!(f, " {line}\n ")?;
write_underline(f, line, range)?;
writeln!(f)
}
#[inline]
pub fn source_highlighting(&self, source: &str) -> String {
let mut output = String::new();
self.write_source_highlighting(&mut output, source).unwrap();
output
}
}
fn line_start(source: &str, position: usize) -> usize {
match source[..position].rfind(|c| c == '\n' || c == '\r') {
Some(line_end) => line_end + 1,
None => 0,
}
}
fn line_end(source: &str, position: usize) -> usize {
match source[position..].find(|c| c == '\n' || c == '\r') {
Some(line_end) => position + line_end,
None => source.len(),
}
}
fn write_underline(f: &mut impl std::fmt::Write, line: &str, range: std::ops::Range<usize>) -> std::fmt::Result {
use unicode_width::UnicodeWidthStr;
let spaces = line[..range.start].width();
let carets = line[range].width();
write!(f, "{}", " ".repeat(spaces))?;
write!(f, "{}", "^".repeat(carets))?;
Ok(())
}
#[cfg(test)]
mod test {
use assert2::check;
#[test]
fn test_char_or_byte_quoted_printable() {
use super::CharOrByte::{Byte, Char};
check!(Byte(0x81).quoted_printable().to_string() == r"'\x81'");
check!(Byte(0x79).quoted_printable().to_string() == r"'y'");
check!(Char('\x79').quoted_printable().to_string() == r"'y'");
check!(Byte(0).quoted_printable().to_string() == r"'\0'");
check!(Char('\0').quoted_printable().to_string() == r"'\0'");
}
}