use core::{
convert::TryFrom,
fmt::{Display, Write},
ops::Deref,
};
#[cfg(feature = "alloc")]
use alloc::string::String;
use crate::{
datum_error, DatumChar, DatumCharClass, DatumError, DatumOffset, DatumPipe, DatumResult,
DatumTokenType, DatumTokenizer, DatumTokenizerAction,
};
#[derive(Clone, Copy, PartialEq, Debug)]
pub enum DatumToken<B: Deref<Target = str>> {
String(DatumOffset, B),
Symbol(DatumOffset, B),
SpecialID(DatumOffset, B),
Integer(DatumOffset, i64),
Float(DatumOffset, f64),
ListStart(DatumOffset),
ListEnd(DatumOffset),
}
impl<B: Deref<Target = str>> Default for DatumToken<B> {
fn default() -> Self {
Self::ListEnd(0)
}
}
impl<B: Deref<Target = str>> TryFrom<(DatumTokenType, DatumOffset, B)> for DatumToken<B> {
type Error = DatumError;
fn try_from(value: (DatumTokenType, DatumOffset, B)) -> Result<Self, Self::Error> {
match value {
(DatumTokenType::String, at, v) => Ok(DatumToken::String(at, v)),
(DatumTokenType::Symbol, at, v) => Ok(DatumToken::Symbol(at, v)),
(DatumTokenType::SpecialID, at, v) => Ok(DatumToken::SpecialID(at, v)),
(DatumTokenType::Numeric, at, v) => {
if let Ok(v) = v.parse() {
Ok(DatumToken::Integer(at, v))
} else if let Ok(v) = v.parse() {
Ok(DatumToken::Float(at, v))
} else {
Err(datum_error!(BadData, at, "token2: bad numeric"))
}
}
(DatumTokenType::ListStart, at, _) => Ok(DatumToken::ListStart(at)),
(DatumTokenType::ListEnd, at, _) => Ok(DatumToken::ListEnd(at)),
}
}
}
impl<B: Deref<Target = str>> DatumToken<B> {
#[cfg(not(tarpaulin_include))]
pub fn token_type(&self) -> DatumTokenType {
match self {
Self::String(_, _) => DatumTokenType::String,
Self::Symbol(_, _) => DatumTokenType::Symbol,
Self::SpecialID(_, _) => DatumTokenType::SpecialID,
Self::Integer(_, _) => DatumTokenType::Numeric,
Self::Float(_, _) => DatumTokenType::Numeric,
Self::ListStart(_) => DatumTokenType::ListStart,
Self::ListEnd(_) => DatumTokenType::ListEnd,
}
}
#[cfg(not(tarpaulin_include))]
pub fn buffer(&self) -> Option<&B> {
match self {
Self::String(_, b) => Some(b),
Self::Symbol(_, b) => Some(b),
Self::SpecialID(_, b) => Some(b),
_ => None,
}
}
#[cfg(not(tarpaulin_include))]
pub fn offset(&self) -> DatumOffset {
match self {
Self::String(at, _) => *at,
Self::Symbol(at, _) => *at,
Self::SpecialID(at, _) => *at,
Self::Integer(at, _) => *at,
Self::Float(at, _) => *at,
Self::ListStart(at) => *at,
Self::ListEnd(at) => *at,
}
}
pub fn write(&self, f: &mut dyn Write) -> core::fmt::Result {
match self {
Self::String(_, b) => {
f.write_char('\"')?;
for v in b.deref().chars() {
DatumChar::string_content(v).write(f)?;
}
f.write_char('\"')
}
Self::Symbol(_, b) => {
let mut chars = b.chars();
match chars.next() {
Some(v) => {
if DatumCharClass::identify(v) == Some(DatumCharClass::Sign) {
match chars.next() {
Some(v2) => {
DatumChar::content(v).write(f)?;
DatumChar::content(v2).write(f)?;
}
None => {
return f.write_char(v);
}
}
} else {
DatumChar::content(v).write(f)?;
}
for remainder in chars {
DatumChar::potential_identifier(remainder).write(f)?;
}
core::fmt::Result::Ok(())
}
None => f.write_str("#{}#"),
}
}
Self::SpecialID(_, b) => {
f.write_char('#')?;
let chars = b.chars();
for remainder in chars {
DatumChar::potential_identifier(remainder).write(f)?;
}
core::fmt::Result::Ok(())
}
Self::Integer(_, v) => core::fmt::write(f, format_args!("{}", v)),
Self::Float(_, v) => {
if v.is_nan() {
f.write_str("#i+nan.0")
} else if v.is_infinite() {
if v.is_sign_positive() {
f.write_str("#i+inf.0")
} else {
f.write_str("#i-inf.0")
}
} else {
let mut res = DatumFloatObserver(f, false);
core::fmt::write(&mut res, format_args!("{}", v))?;
if !res.1 {
f.write_str(".0")?;
}
core::fmt::Result::Ok(())
}
}
Self::ListStart(_) => f.write_char('('),
Self::ListEnd(_) => f.write_char(')'),
}
}
}
impl<B: Deref<Target = str>> Display for DatumToken<B> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
self.write(f)
}
}
pub fn datum_write_display_as_string<T: core::fmt::Display + ?Sized>(
f: &mut dyn Write,
value: &T,
) -> core::fmt::Result {
f.write_char('"')?;
write!(DatumStringContentWriter(f), "{}", value)?;
f.write_char('"')
}
struct DatumStringContentWriter<'writer>(&'writer mut dyn Write);
impl<'writer> Write for DatumStringContentWriter<'writer> {
fn write_str(&mut self, s: &str) -> core::fmt::Result {
for v in s.chars() {
self.write_char(v)?;
}
Ok(())
}
fn write_char(&mut self, c: char) -> core::fmt::Result {
DatumChar::string_content(c).write(self.0)
}
}
struct DatumFloatObserver<'a>(&'a mut dyn Write, bool);
impl Write for DatumFloatObserver<'_> {
fn write_str(&mut self, s: &str) -> core::fmt::Result {
for c in s.bytes() {
if c == b'.' || c == b'e' || c == b'E' {
self.1 = true;
}
}
self.0.write_str(s)
}
}
#[derive(Clone, Default, Debug)]
pub struct DatumPipeTokenizer<B: Write + Deref<Target = str> + Default>(B, DatumTokenizer);
#[cfg(feature = "alloc")]
pub type DatumStringTokenizer = DatumPipeTokenizer<String>;
impl<B: Write + Deref<Target = str> + Default> DatumPipe for DatumPipeTokenizer<B> {
type Input = DatumChar;
type Output = DatumToken<B>;
fn feed<F: FnMut(DatumOffset, Self::Output) -> DatumResult<()>>(
&mut self,
at: DatumOffset,
i: Option<Self::Input>,
f: &mut F,
) -> DatumResult<()> {
let m0 = &mut self.0;
self.1.feed(at, i, &mut |offset, action| match action {
DatumTokenizerAction::Push(chr) => m0.write_char(chr).map_err(|_| {
datum_error!(OutOfRoom, at, "token2: failed to write to token buffer")
}),
DatumTokenizerAction::Token(v) => {
f(offset, DatumToken::try_from((v, at, core::mem::take(m0)))?)
}
})
}
}