use core::{
fmt::{Display, Write},
ops::Deref,
};
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum DatumCharClass {
Content,
Whitespace,
Newline,
LineComment,
String,
ListStart,
ListEnd,
SpecialID,
Sign,
Digit,
}
impl DatumCharClass {
#[inline]
pub const fn potential_identifier(&self) -> bool {
matches!(
self,
Self::Content | Self::Sign | Self::Digit | Self::SpecialID
)
}
#[inline]
pub const fn numeric_start(&self) -> bool {
matches!(self, Self::Sign | Self::Digit)
}
pub const fn identify(v: char) -> Option<Self> {
if v == '\n' {
Some(DatumCharClass::Newline)
} else if v == '\t' || v == ' ' {
Some(DatumCharClass::Whitespace)
} else if v < ' ' || v == '\x7F' || v == '\\' {
None
} else if v == ';' {
Some(DatumCharClass::LineComment)
} else if v == '"' {
Some(DatumCharClass::String)
} else if v == '(' {
Some(DatumCharClass::ListStart)
} else if v == ')' {
Some(DatumCharClass::ListEnd)
} else if v == '#' {
Some(DatumCharClass::SpecialID)
} else if v == '-' {
Some(DatumCharClass::Sign)
} else if v >= '0' && v <= '9' {
Some(DatumCharClass::Digit)
} else {
Some(DatumCharClass::Content)
}
}
}
const fn make_hex_digit(v: u8) -> char {
if v >= 0xA {
(b'a' + (v - 0xA)) as char
} else {
(b'0' + v) as char
}
}
pub fn datum_write_byte_hex_escape(v: u8, f: &mut dyn Write) -> core::fmt::Result {
f.write_char('\\')?;
f.write_char('x')?;
f.write_char(make_hex_digit(v >> 4))?;
f.write_char(make_hex_digit(v & 0xF))?;
f.write_char(';')
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub struct DatumChar {
char: char,
class: DatumCharClass,
}
impl DatumChar {
#[inline]
pub const fn char(&self) -> char {
self.char
}
#[inline]
pub const fn class(&self) -> DatumCharClass {
self.class
}
pub fn write(&self, f: &mut dyn Write) -> core::fmt::Result {
let v = self.char;
if self.class == DatumCharClass::Content {
if v == '\n' {
f.write_str("\\n")
} else if v == '\r' {
f.write_str("\\r")
} else if v == '\t' {
f.write_str("\\t")
} else if ((v as u32) < 32) || v == '\x7F' {
datum_write_byte_hex_escape(v as u8, f)
} else {
match DatumCharClass::identify(v) {
Some(DatumCharClass::Content) => f.write_char(v),
_ => {
f.write_char('\\')?;
f.write_char(v)
}
}
}
} else {
f.write_char(v)
}
}
#[inline]
pub const fn identify(v: char) -> Option<DatumChar> {
match DatumCharClass::identify(v) {
None => None,
Some(class) => Some(DatumChar { char: v, class }),
}
}
pub const fn content(v: char) -> DatumChar {
DatumChar {
char: v,
class: DatumCharClass::Content,
}
}
pub const fn string_content(v: char) -> DatumChar {
match v {
'\n' => Self::content(v),
'\t' => Self::content(v),
'"' => Self::content(v),
_ => match Self::identify(v) {
None => Self::content(v),
Some(rchr) => rchr,
},
}
}
pub const fn potential_identifier(v: char) -> DatumChar {
match Self::identify(v) {
None => Self::content(v),
Some(rchr) => {
if rchr.class().potential_identifier() {
rchr
} else {
Self::content(v)
}
}
}
}
}
impl Display for DatumChar {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
self.write(f)
}
}
impl Deref for DatumChar {
type Target = DatumCharClass;
fn deref(&self) -> &Self::Target {
&self.class
}
}
impl Default for DatumChar {
fn default() -> Self {
DatumChar {
char: ' ',
class: DatumCharClass::Whitespace,
}
}
}