#![feature(test)]
use std::{
cmp::PartialEq,
error::Error,
fmt::{Debug, Display, Formatter},
mem::forget,
ptr::null_mut,
result::Result,
slice,
str::FromStr,
};
impl PartialEq for Wood {
fn eq(&self, other: &Self) -> bool {
match *self {
Leafv(ref sa) => match *other {
Leafv(ref so) => sa.v == so.v,
_ => false,
},
Branchv(ref la) => match *other {
Branchv(ref lo) => la.v == lo.v,
_ => false,
},
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Branch {
pub line: isize,
pub column: isize,
pub v: Vec<Wood>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Leaf {
pub line: isize,
pub column: isize,
pub v: String,
}
#[derive(Debug, Clone, Eq)]
pub enum Wood {
Branchv(Branch),
Leafv(Leaf),
}
pub use Wood::*;
impl From<String> for Wood {
fn from(v: String) -> Wood {
Leafv(Leaf {
line: -1,
column: -1,
v,
})
}
}
impl<'a> From<&'a str> for Wood {
fn from(v: &'a str) -> Wood {
Wood::from(v.to_string())
}
}
impl From<Vec<Wood>> for Wood {
fn from(v: Vec<Wood>) -> Wood {
Branchv(Branch {
line: -1,
column: -1,
v,
})
}
}
fn tail<I: Iterator>(mut v: I) -> I {
v.next();
v
}
fn push_escaped(take: &mut String, give: &str) {
for c in give.chars() {
match c {
'\n' => {
take.push('\\');
take.push('n');
}
'\t' => {
take.push('\\');
take.push('t');
}
'"' => {
take.push('\\');
take.push('"');
}
_ => {
take.push(c);
}
}
}
}
pub enum LB<'a> {
L(&'a str),
B(&'a [Wood]),
}
pub use LB::*;
impl Wood {
pub fn leaf(v: String) -> Wood {
Wood::Leafv(Leaf {
line: -1,
column: -1,
v: v,
})
}
pub fn branch(v: Vec<Wood>) -> Wood {
Wood::Branchv(Branch {
line: -1,
column: -1,
v: v,
})
}
pub fn empty() -> Wood {
Wood::branch(Vec::new())
}
pub fn is_leaf(&self) -> bool {
match self {
&Leafv(_) => true,
_ => false,
}
}
pub fn is_branch(&self) -> bool {
match self {
&Branchv(_) => true,
_ => false,
}
}
pub fn what(&self) -> LB<'_> {
match *self {
Leafv(ref s) => L(&s.v),
Branchv(ref s) => B(&s.v),
}
}
pub fn get_leaf(&self) -> Option<&str> {
match *self {
Leafv(ref s) => Some(&s.v),
Branchv(_) => None,
}
}
pub fn get_branch(&self) -> Option<&[Wood]> {
match *self {
Leafv(_) => None,
Branchv(ref s) => Some(&s.v),
}
}
pub fn line_and_col(&self) -> (isize, isize) {
match *self {
Wood::Branchv(ref l) => (l.line, l.column),
Wood::Leafv(ref a) => (a.line, a.column),
}
}
pub fn line(&self) -> isize {
match *self {
Leafv(ref s) => s.line,
Branchv(ref s) => s.line,
}
}
pub fn col(&self) -> isize {
match *self {
Leafv(ref s) => s.column,
Branchv(ref s) => s.column,
}
}
pub fn initial_str(&self) -> &str {
match *self {
Branchv(ref v) => {
if let Some(ref ss) = v.v.first() {
ss.initial_str()
} else {
""
}
}
Leafv(ref v) => v.v.as_str(),
}
}
pub fn to_string(&self) -> String {
to_woodslist(self)
}
pub fn strip_comments_escape(&mut self, comment_str: &str, comment_escape_str: &str) {
match *self {
Branchv(ref mut b) => {
b.v.retain_mut(|i| {
if i.initial_str() == comment_str {
false
} else {
i.strip_comments_escape(comment_str, comment_escape_str);
true
}
});
}
Leafv(ref mut l) => {
if &l.v == comment_escape_str {
l.v = comment_str.into();
}
}
}
}
pub fn strip_comments(&mut self, comment_str: &str) {
let escstr = format!("\\{}", comment_str);
self.strip_comments_escape(comment_str, &escstr);
}
pub fn contents(&self) -> std::slice::Iter<'_, Self> {
match *self {
Branchv(ref v) => v.v.iter(),
Leafv(_) => std::slice::from_ref(self).iter(),
}
}
pub fn head(&self) -> Result<&Wood, Box<WoodError>> {
self.contents().next().ok_or_else(|| {
Box::new(WoodError::new(
self,
"there shouldn't be an empty list here".to_string(),
))
})
}
pub fn second(&self) -> Result<&Wood, Box<WoodError>> {
self.tail().next().ok_or_else(|| {
Box::new(WoodError::new(
self,
"a second wood was supposed to be present".to_string(),
))
})
}
pub fn tail<'b>(&'b self) -> std::slice::Iter<'b, Self> {
match *self {
Branchv(ref v) => tail((*v).v.iter()),
Leafv(_) => [].iter(),
}
}
pub fn flatter_tail<'a>(
&'a self,
) -> std::iter::Chain<slice::Iter<'a, Self>, slice::Iter<'a, Self>> {
let mut r = self.contents();
if let Some(first) = r.next() {
first.tail().chain(r)
} else {
[].iter().chain([].iter())
}
}
pub fn seek<'a, 'b>(&'a self, key: &'b str) -> Option<&'a Wood> {
self.contents().find(|el| el.initial_str() == key)
}
pub fn seek_val<'a, 'b>(&'a self, key: &'b str) -> Option<&'a Wood> {
self.seek(key).and_then(|w| w.tail().next())
}
pub fn find<'a, 'b>(&'a self, key: &'b str) -> Result<&'a Wood, Box<WoodError>> {
self.seek(key).ok_or_else(|| {
Box::new(WoodError::new(
self,
format!("could not find child with key \"{}\"", key),
))
})
}
pub fn find_val<'a, 'b>(&'a self, key: &'b str) -> Result<&'a Wood, Box<WoodError>> {
self.find(key).and_then(|v| v.second())
}
}
#[macro_export]
macro_rules! woods {
($($el:expr),* $(,)?)=> {
$crate::Branchv($crate::Branch{line:-1, column:-1, v:vec!($($crate::Wood::from($el)),*)})
};
}
pub trait Wooder<T> {
fn woodify(&self, v: &T) -> Wood;
}
pub trait Dewooder<T> {
fn dewoodify(&self, v: &Wood) -> Result<T, Box<WoodError>>;
}
#[derive(Debug)]
pub struct WoodError {
pub line: isize,
pub column: isize,
pub msg: String,
pub cause: Option<Box<dyn Error>>,
}
impl Display for WoodError {
fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
Debug::fmt(self, f)
}
}
impl WoodError {
pub fn new(source: &Wood, msg: String) -> Self {
let (line, column) = source.line_and_col();
Self {
line,
column,
msg,
cause: None,
}
}
pub fn new_with_cause(source: &Wood, msg: String, cause: Box<dyn Error>) -> Self {
let (line, column) = source.line_and_col();
WoodError {
line,
column,
msg,
cause: Some(cause),
}
}
}
impl Error for WoodError {
fn cause(&self) -> Option<&dyn Error> {
self.cause.as_ref().map(|e| e.as_ref())
}
}
pub trait Woodable {
fn woodify(&self) -> Wood;
}
pub trait Dewoodable {
fn dewoodify(v: &Wood) -> Result<Self, Box<WoodError>>
where
Self: Sized;
}
pub fn woodify<T>(v: &T) -> Wood
where
T: Woodable,
{
v.woodify()
}
pub fn dewoodify<T>(v: &Wood) -> Result<T, Box<WoodError>>
where
T: Dewoodable,
{
T::dewoodify(v)
}
pub fn deserialize<T>(v: &str) -> Result<T, Box<WoodError>>
where
T: Dewoodable,
{
parse_termpose(v).and_then(|w| dewoodify(&w))
}
pub fn serialize<T>(v: &T) -> String
where
T: Woodable,
{
woodify(v).to_string()
}
macro_rules! do_basic_stringifying_woodable_for {
($Type:ident) => {
impl Woodable for $Type {
fn woodify(&self) -> Wood {
self.to_string().into()
}
}
};
}
macro_rules! do_basic_destringifying_dewoodable_for {
($Type:ident) => {
impl Dewoodable for $Type {
fn dewoodify(v: &Wood) -> Result<$Type, Box<WoodError>> {
$Type::from_str(v.initial_str()).map_err(|er| {
Box::new(WoodError::new_with_cause(
v,
format!("couldn't parse {}", stringify!($name)),
Box::new(er),
))
})
}
}
};
}
do_basic_stringifying_woodable_for!(char);
do_basic_destringifying_dewoodable_for!(char);
do_basic_stringifying_woodable_for!(u32);
do_basic_destringifying_dewoodable_for!(u32);
do_basic_stringifying_woodable_for!(u64);
do_basic_destringifying_dewoodable_for!(u64);
do_basic_stringifying_woodable_for!(u128);
do_basic_destringifying_dewoodable_for!(u128);
do_basic_stringifying_woodable_for!(i32);
do_basic_destringifying_dewoodable_for!(i32);
do_basic_stringifying_woodable_for!(i64);
do_basic_destringifying_dewoodable_for!(i64);
do_basic_stringifying_woodable_for!(i128);
do_basic_destringifying_dewoodable_for!(i128);
do_basic_stringifying_woodable_for!(f32);
do_basic_destringifying_dewoodable_for!(f32);
do_basic_stringifying_woodable_for!(f64);
do_basic_destringifying_dewoodable_for!(f64);
do_basic_stringifying_woodable_for!(isize);
do_basic_destringifying_dewoodable_for!(isize);
do_basic_stringifying_woodable_for!(usize);
do_basic_destringifying_dewoodable_for!(usize);
do_basic_stringifying_woodable_for!(bool);
impl Dewoodable for bool {
fn dewoodify(v: &Wood) -> Result<Self, Box<WoodError>> {
match v.initial_str() {
"true" | "⊤" | "yes" => Ok(true),
"false" | "⟂" | "no" => Ok(false),
_ => Err(Box::new(WoodError::new(v, "expected a bool here".into()))),
}
}
}
impl Woodable for String {
fn woodify(&self) -> Wood {
self.as_str().into()
}
}
impl Dewoodable for String {
fn dewoodify(v: &Wood) -> Result<Self, Box<WoodError>> {
match *v {
Leafv(ref a) => Ok(a.v.clone()),
Branchv(_) => Err(Box::new(WoodError::new(
v,
"sought string, found branch".into(),
))),
}
}
}
pub fn woodify_seq_into<'a, InnerTran, T, I>(inner: &InnerTran, v: I, output: &mut Vec<Wood>)
where
InnerTran: Wooder<T>,
I: Iterator<Item = &'a T>,
T: 'a,
{
for vi in v {
output.push(inner.woodify(vi));
}
}
pub fn dewoodify_seq_into<'a, InnerTran, T, I>(
inner: &InnerTran,
v: I,
output: &mut Vec<T>,
) -> Result<(), Box<WoodError>>
where
InnerTran: Dewooder<T>,
I: Iterator<Item = &'a Wood>,
{
for vi in v {
match inner.dewoodify(vi) {
Ok(vii) => output.push(vii),
Err(e) => return Err(e),
}
}
Ok(())
}
impl<T> Woodable for Vec<T>
where
T: Woodable,
{
fn woodify(&self) -> Wood {
let mut ret = Vec::new();
woodify_seq_into(&wooder::Iden, self.iter(), &mut ret);
ret.into()
}
}
impl<T> Dewoodable for Vec<T>
where
T: Dewoodable,
{
fn dewoodify(v: &Wood) -> Result<Vec<T>, Box<WoodError>> {
let mut ret = Vec::new();
dewoodify_seq_into(&wooder::Iden, v.contents(), &mut ret)?;
Ok(ret)
}
}
mod parsers;
pub use parsers::*;
pub mod wooder;
#[cfg(test)]
mod tests {
extern crate test;
use super::*;
#[test]
fn test_comment_stripping() {
let mut w = parse_multiline_termpose(
"
#\"this function cannot be called with a false object criterion
fn process_ishm_protocol_handling object criterion
if criterion(object)
#\"then it returns good
return good
else
return angered
return secret_egg
",
)
.unwrap();
w.strip_comments("#");
let should_be = parse_multiline_termpose(
"
fn process_ishm_protocol_handling object criterion
if criterion(object)
return good
else
return angered
return secret_egg
",
)
.unwrap();
assert_eq!(&w, &should_be);
}
}