mod fmt;
pub mod parser;
use crate::ArcStr;
use core::{
fmt::Write,
num::{ParseFloatError, ParseIntError},
str::FromStr,
};
pub use fmt::{
CodeFormatter, DefaultCodeFormatter, DefaultIndentation, Indentation,
TestCodeFormatter, TestIndentation,
};
use itertools::Itertools;
use nom::{error::Error, IResult};
use ordered_float::ParseNotNanError;
pub type SimpleWrapper = ArcStr;
pub type ComplexWrapper = Vec<Vec<ArcStr>>;
#[derive(Debug, Clone, Default)]
#[derive(serde::Serialize, serde::Deserialize)]
pub struct GroupWrapper {
pub title: Vec<ArcStr>,
pub attr_list: AttributeList,
}
pub type AttributeList = Vec<(ArcStr, AttriValue)>;
#[derive(Debug, Clone)]
#[derive(serde::Serialize, serde::Deserialize)]
pub enum AttriValue {
Simple(SimpleWrapper),
Complex(ComplexWrapper),
Group(GroupWrapper),
}
#[derive(Debug)]
#[derive(thiserror::Error)]
pub enum LinkError {
#[error("Can not find in hashset!")]
NotFind,
#[error("{0}")]
BorrowError(core::cell::BorrowError),
}
impl PartialEq for LinkError {
#[allow(clippy::match_like_matches_macro)]
#[inline]
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(Self::NotFind, Self::NotFind) | (Self::BorrowError(_), Self::BorrowError(_)) => {
true
}
_ => false,
}
}
}
pub(crate) type SimpleParseErr<'a, T> =
IResult<&'a str, Result<T, AttriValue>, Error<&'a str>>;
#[inline]
pub fn nom_parse_from_str<'a, T: SimpleAttri + FromStr>(
i: &'a str,
line_num: &mut usize,
) -> SimpleParseErr<'a, T> {
let (input, s) = parser::simple(i, line_num)?;
s.parse()
.map_or(Ok((input, Err(AttriValue::Simple(ArcStr::from(s))))), |simple| {
Ok((input, Ok(simple)))
})
}
pub trait SimpleAttri: Sized + core::fmt::Display {
fn nom_parse<'a>(i: &'a str, line_num: &mut usize) -> SimpleParseErr<'a, Self>;
#[inline]
fn is_set(&self) -> bool {
true
}
#[inline]
fn fmt_self<T: Write, I: Indentation>(
&self,
f: &mut CodeFormatter<'_, T, I>,
) -> core::fmt::Result {
write!(f, "{self}")
}
#[inline]
fn fmt_liberty<T: Write, I: Indentation>(
&self,
key: &str,
f: &mut CodeFormatter<'_, T, I>,
) -> core::fmt::Result {
if self.is_set() {
write!(f, "\n{}{key} : ", f.indentation())?;
self.fmt_self(f)?;
write!(f, ";")
} else {
Ok(())
}
}
}
#[derive(thiserror::Error, Debug)]
pub enum ComplexParseError {
#[error("{0}")]
Float(#[from] ParseNotNanError<ParseFloatError>),
#[error("{0}")]
Int(#[from] ParseIntError),
#[error("title length mismatch")]
LengthDismatch,
#[error("other")]
Other,
#[error("unsurpport word")]
UnsupportedWord,
}
pub trait NameAttri: Sized + Clone {
fn parse(v: Vec<ArcStr>) -> Result<Self, IdError>;
fn to_vec(self) -> Vec<ArcStr>;
}
#[inline]
pub fn join_fmt<
'a,
T: Sized + 'a,
I: Iterator<Item = T>,
W: Write,
F: FnMut(T, &mut W) -> core::fmt::Result,
>(
mut iter: I,
f: &mut W,
mut func: F,
sep: &str,
) -> core::fmt::Result {
write!(f, "\"")?;
if let Some(first) = iter.next() {
func(first, f)?;
while let Some(t) = iter.next() {
write!(f, "{sep}")?;
func(t, f)?;
}
}
write!(f, "\"")
}
pub trait ComplexAttri: Sized {
fn parse(v: &[&str]) -> Result<Self, ComplexParseError>;
#[inline]
fn nom_parse<'a>(
i: &'a str,
line_num: &mut usize,
) -> IResult<&'a str, Result<Self, (ComplexParseError, AttriValue)>, Error<&'a str>> {
let (input, complex) = parser::complex(i, line_num)?;
match Self::parse(&complex) {
Ok(s) => Ok((input, Ok(s))),
Err(e) => Ok((
input,
Err((
e,
AttriValue::Complex(vec![complex.into_iter().map(ArcStr::from).collect()]),
)),
)),
}
}
#[inline]
fn is_set(&self) -> bool {
true
}
fn fmt_self<T: Write, I: Indentation>(
&self,
f: &mut CodeFormatter<'_, T, I>,
) -> core::fmt::Result;
#[inline]
fn fmt_liberty<T: Write, I: Indentation>(
&self,
key: &str,
f: &mut CodeFormatter<'_, T, I>,
) -> core::fmt::Result {
if self.is_set() {
let indent1 = f.indentation();
write!(f, "\n{indent1}{key} (")?;
f.indent(1);
self.fmt_self(f)?;
f.dedent(1);
write!(f, ");")
} else {
Ok(())
}
}
}
pub type GroupComments<T> = <T as GroupAttri>::Comments;
pub type AttriComment = Vec<ArcStr>;
pub trait GroupFn {
#[inline]
fn post_process(&mut self) {}
}
pub trait GroupAttri: Sized {
type Name;
type Comments;
#[inline]
fn test_wrapper(self) -> TestWrapper<Self> {
TestWrapper { inner: self, line_count: 0 }
}
fn name(&self) -> Self::Name;
fn set_name(&mut self, name: Self::Name);
fn nom_parse<'a>(
i: &'a str,
line_num: &mut usize,
) -> IResult<&'a str, Result<Self, IdError>, Error<&'a str>>;
fn fmt_liberty<T: Write, I: Indentation>(
&self,
key: &str,
f: &mut CodeFormatter<'_, T, I>,
) -> core::fmt::Result;
}
#[derive(Debug)]
#[derive(thiserror::Error)]
pub enum IdError {
#[error("title length dismatch (want={0},got={1}), title={2:?}")]
LengthDismatch(usize, usize, Vec<ArcStr>),
#[error("replace same id")]
RepeatIdx,
#[error("replace same attribute")]
RepeatAttri,
#[error("{0}")]
Int(ParseIntError),
#[error("{0}")]
Other(String),
}
pub trait NamedGroup: GroupAttri {
fn parse(v: Vec<ArcStr>) -> Result<Self::Name, IdError>;
fn name2vec(name: Self::Name) -> Vec<ArcStr>;
#[inline]
fn fmt_liberty<T: Write, I: Indentation>(
&self,
f: &mut CodeFormatter<'_, T, I>,
) -> core::fmt::Result {
write!(
f,
"{}",
Self::name2vec(self.name())
.into_iter()
.map(|s| if is_word(&s) { s } else { format!("\"{s}\"").into() })
.join(", ")
)
}
}
fn display_nom_error(e: &nom::Err<Error<&str>>) -> ArcStr {
match e {
nom::Err::Incomplete(_) => e.to_string(),
nom::Err::Failure(_e) | nom::Err::Error(_e) => format!(
"type[{}] at[{}]",
_e.code.description(),
_e.input.lines().next().unwrap_or("")
),
}
.into()
}
#[derive(Debug, thiserror::Error)]
pub enum ParserError<'a> {
#[error("Line#{0}, {1}")]
IdError(usize, IdError),
#[error("Line#{0}, {}", display_nom_error(.1))]
NomError(usize, nom::Err<Error<&'a str>>),
#[error("Line#{0}, {1}")]
Other(usize, String),
}
#[derive(Debug)]
pub struct TestWrapper<G> {
pub inner: G,
pub line_count: usize,
}
impl<G: GroupAttri> FromStr for TestWrapper<G> {
type Err = String;
#[inline]
fn from_str(s: &str) -> Result<Self, Self::Err> {
let mut line_count = 1;
match G::nom_parse(s, &mut line_count) {
Ok((_, Ok(inner))) => Ok(Self { inner, line_count }),
Ok((_, Err(e))) => Err(format!("{e:#?}")),
Err(e) => Err(format!("{e:#?}")),
}
}
}
impl<G: GroupAttri> core::fmt::Display for TestWrapper<G> {
#[inline]
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
let mut ff = TestCodeFormatter::new(f);
self.inner.fmt_liberty(core::any::type_name::<G>(), &mut ff)
}
}
#[cfg(test)]
#[inline]
pub fn test_parse<G: GroupAttri + core::fmt::Debug>(input: &str) -> G {
let wrapper = input.parse::<TestWrapper<G>>().expect("Group parse failed");
println!("{:?}", wrapper.inner);
println!("{wrapper}");
wrapper.inner
}
#[cfg(test)]
#[inline]
pub fn test_parse_fmt<G: GroupAttri + core::fmt::Debug>(
input: &str,
fmt_want: &str,
) -> G {
let wrapper = input.parse::<TestWrapper<G>>().expect("Group parse failed");
println!("{:?}", wrapper.inner);
let fmt_str = wrapper.to_string();
println!("{fmt_str}");
assert_eq!(fmt_want, fmt_str, "Group fmt assert");
wrapper.inner
}
pub trait Format {
fn liberty<T: Write, I: Indentation>(
&self,
key: &str,
f: &mut CodeFormatter<'_, T, I>,
) -> core::fmt::Result;
#[inline]
fn db<T: Write, I: Indentation>(
&self,
key: &str,
f: &mut CodeFormatter<'_, T, I>,
) -> core::fmt::Result {
_ = key;
_ = f;
todo!()
}
#[inline]
fn json<T: Write, I: Indentation>(
&self,
key: &str,
f: &mut CodeFormatter<'_, T, I>,
) -> core::fmt::Result {
_ = key;
_ = f;
todo!()
}
}
#[inline]
pub(crate) fn is_word(s: &ArcStr) -> bool {
!s.is_empty() && s.chars().all(parser::char_in_word)
}
impl Format for AttriComment {
#[allow(clippy::arithmetic_side_effects, clippy::undocumented_unsafe_blocks)]
#[inline]
fn liberty<T: Write, I: Indentation>(
&self,
_: &str,
f: &mut CodeFormatter<'_, T, I>,
) -> core::fmt::Result {
if self.is_empty() {
Ok(())
} else {
let mut first_line = true;
let mut first_char = || {
if first_line {
first_line = false;
'/'
} else {
'*'
}
};
let indent = f.indentation();
self.iter().try_for_each(|line| {
let mut last_end = 0;
line.match_indices('\n').try_for_each(|(start, _)| {
let s = unsafe { line.get_unchecked(last_end..start) };
last_end = start + 1;
f.write_fmt(format_args!("\n{indent}{}* {s}", first_char()))
})?;
let s = unsafe { line.get_unchecked(last_end..line.len()) };
f.write_fmt(format_args!("\n{indent}{}* {s}", first_char()))
})?;
f.write_str(" */")
}
}
}
#[allow(clippy::arithmetic_side_effects, clippy::undocumented_unsafe_blocks)]
#[inline]
pub(crate) fn fmt_first_line_comment<T: Write, I: Indentation>(
comment: &AttriComment,
f: &mut CodeFormatter<'_, T, I>,
) -> core::fmt::Result {
if comment.is_empty() {
write!(
f,
"/* Generated by {} {} */",
env!("CARGO_PKG_NAME"),
env!("CARGO_PKG_VERSION"),
)
} else {
let mut first_line = true;
let mut first_chars = || {
if first_line {
first_line = false;
"/"
} else {
"\n*"
}
};
comment.iter().try_for_each(|line| {
let mut last_end = 0;
line.match_indices('\n').try_for_each(|(start, _)| {
let s = unsafe { line.get_unchecked(last_end..start) };
last_end = start + 1;
f.write_fmt(format_args!("{}* {s}", first_chars()))
})?;
let s = unsafe { line.get_unchecked(last_end..line.len()) };
f.write_fmt(format_args!("{}* {s}", first_chars()))
})?;
f.write_str(" */")
}
}
impl Format for SimpleWrapper {
#[inline]
fn liberty<T: Write, I: Indentation>(
&self,
key: &str,
f: &mut CodeFormatter<'_, T, I>,
) -> core::fmt::Result {
if self.is_empty() {
Ok(())
} else if is_word(self) {
write!(f, "\n{}{key} : {self};", f.indentation())
} else {
write!(f, "\n{}{key} : \"{self}\";", f.indentation())
}
}
}
impl Format for ComplexWrapper {
#[allow(clippy::indexing_slicing)]
#[inline]
fn liberty<T: Write, I: Indentation>(
&self,
key: &str,
f: &mut CodeFormatter<'_, T, I>,
) -> core::fmt::Result {
if self.is_empty() || (self.len() == 1 && self[0].is_empty()) {
return Ok(());
};
let indent1 = f.indentation();
if self[0].iter().all(is_word) {
write!(f, "\n{indent1}{key} ({}", self[0].join(", "))?;
} else {
write!(f, "\n{indent1}{key} (\"{}\"", self[0].join(", "))?;
}
f.indent(1);
let indent2 = f.indentation();
for v in self.iter().skip(1) {
if v.iter().all(is_word) {
write!(f, ", \\\n{indent2}{}", v.join(", "))?;
} else {
write!(f, ", \\\n{indent2}\"{}\"", v.join(", "))?;
}
}
f.dedent(1);
write!(f, ");")
}
}
#[inline]
pub(crate) fn liberty_attr_list<T: Write, I: Indentation>(
attr_list: &AttributeList,
f: &mut CodeFormatter<'_, T, I>,
) -> core::fmt::Result {
for (key, attr) in attr_list {
match attr {
AttriValue::Simple(a) => Format::liberty(a, key, f)?,
AttriValue::Complex(a) => Format::liberty(a, key, f)?,
AttriValue::Group(a) => Format::liberty(a, key, f)?,
}
}
Ok(())
}
impl Format for GroupWrapper {
#[inline]
fn liberty<T: Write, I: Indentation>(
&self,
key: &str,
f: &mut CodeFormatter<'_, T, I>,
) -> core::fmt::Result {
let indent = f.indentation();
write!(
f,
"\n{indent}{key} ({}) {{",
self
.title
.iter()
.map(|s| if is_word(s) { s.clone() } else { format!("\"{s}\"").into() })
.join(",")
)?;
f.indent(1);
liberty_attr_list(&self.attr_list, f)?;
f.dedent(1);
write!(f, "\n{indent}}}")
}
}