Last active
June 25, 2021 11:10
-
-
Save emonkak/2f2f77d86c855315af21da9d06217efe to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
use std::array; | |
#[derive(Debug, PartialEq, Eq)] | |
pub struct Element<T> { | |
pub instance: T, | |
pub children: Box<[Element<T>]>, | |
} | |
#[derive(Debug, PartialEq, Eq)] | |
pub enum Child<T> { | |
Multiple(Vec<Element<T>>), | |
Single(Element<T>), | |
None, | |
} | |
#[macro_export] | |
macro_rules! element { | |
($expr:expr => { $($content:tt)* }) => { | |
Element::build($expr, __children!([] $($content)*)) | |
}; | |
($expr:expr) => { | |
Element::build($expr, []) | |
}; | |
} | |
#[macro_export] | |
macro_rules! __children { | |
([$($children:expr)*] $expr:expr => { $($content:tt)* } $($rest:tt)*) => { | |
__children!([$($children)* Element::build($expr, __children!([] $($content)*)).into()] $($rest)*) | |
}; | |
([$($children:expr)*] $expr:expr; $($rest:tt)*) => { | |
__children!([$($children)* Child::from($expr)] $($rest)*) | |
}; | |
([$($children:expr)*] $expr:expr) => { | |
__children!([$($children)* Child::from($expr)]) | |
}; | |
([$($children:expr)*]) => { | |
[$($children),*] | |
}; | |
} | |
impl<T> Element<T> { | |
pub fn new<const N: usize>(instance: T, children: [Element<T>; N]) -> Self { | |
Self { | |
instance, | |
children: Box::new(children), | |
} | |
} | |
pub fn build<const N: usize>(instance: T, children: [Child<T>; N]) -> Self { | |
let mut flatten_children = Vec::with_capacity(N); | |
for child in array::IntoIter::new(children) { | |
match child { | |
Child::Multiple(elements) => { | |
for element in elements { | |
flatten_children.push(element) | |
} | |
} | |
Child::Single(element) => { | |
flatten_children.push(element) | |
} | |
_ => {} | |
} | |
} | |
Self { | |
instance, | |
children: flatten_children.into_boxed_slice(), | |
} | |
} | |
fn to_string_rec(&self, level: usize) -> String where T: ToString { | |
let name = self.instance.to_string(); | |
let indent_str = unsafe { String::from_utf8_unchecked(vec![b'\t'; level]) }; | |
if self.children.len() > 0 { | |
let mut children_str = "".to_string(); | |
for i in 0..self.children.len() { | |
children_str.push('\n'); | |
children_str.push_str(&self.children[i].to_string_rec(level + 1)); | |
} | |
format!("{}<{}>{}\n{}</{}>", indent_str, name, children_str, indent_str, name) | |
} else { | |
format!("{}<{}></{}>", indent_str, name, name) | |
} | |
} | |
} | |
impl<T: ToString> ToString for Element<T> { | |
fn to_string(&self) -> String { | |
self.to_string_rec(0) | |
} | |
} | |
impl<T> From<Vec<Element<T>>> for Child<T> { | |
fn from(elements: Vec<Element<T>>) -> Self { | |
Child::Multiple(elements) | |
} | |
} | |
impl<T> From<Option<Element<T>>> for Child<T> { | |
fn from(element: Option<Element<T>>) -> Self { | |
match element { | |
Some(element) => Child::Single(element), | |
None => Child::None, | |
} | |
} | |
} | |
impl<T> From<Element<T>> for Child<T> { | |
fn from(element: Element<T>) -> Self { | |
Child::Single(element) | |
} | |
} | |
impl<T> From<T> for Child<T> { | |
fn from(value: T) -> Self { | |
Child::Single(Element::new(value, [])) | |
} | |
} | |
fn main() { | |
let el = element!( | |
"foo" => { | |
"bar" => { | |
"baz"; | |
if true { Some(element!("qux")) } else { None }; | |
vec![element!("quux" => { "corge" })]; | |
} | |
None; | |
"grault"; | |
} | |
); | |
assert_eq!(el, Element::new("foo", [ | |
Element::new("bar", [ | |
Element::new("baz", []), | |
Element::new("qux", []), | |
Element::new("quux", [ | |
Element::new("corge", []) | |
]), | |
]), | |
Element::new("grault", []), | |
])); | |
println!("{}", el.to_string()); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment