wood/
lib.rs

1#![feature(test)]
2
3use std::{
4    cmp::PartialEq,
5    error::Error,
6    fmt::{Debug, Display, Formatter},
7    mem::forget,
8    ptr::null_mut,
9    result::Result,
10    slice,
11    str::FromStr,
12};
13
14// pub trait Wood where Self:Sized {
15// 	type Iter:Iterator<Item=Self>;
16// 	fn initial_str(& self)-> &str;
17// 	fn contents<'a>(&'a self)-> Iter;
18// 	fn tail<'a>(&'a self)-> Iter;
19// }
20// impl<'a, St> PartialEq for &'a Woods<St> {
21// 	fn eq(&self, other: &&Woods<St>)-> bool {
22// 		self == other
23// 	}
24// }
25
26/// Line numbers aren't checked in equality comparisons
27impl PartialEq for Wood {
28    fn eq(&self, other: &Self) -> bool {
29        match *self {
30            Leafv(ref sa) => match *other {
31                Leafv(ref so) => sa.v == so.v,
32                _ => false,
33            },
34            Branchv(ref la) => match *other {
35                Branchv(ref lo) => la.v == lo.v,
36                _ => false,
37            },
38        }
39    }
40}
41
42#[derive(Debug, Clone, PartialEq, Eq)]
43pub struct Branch {
44    pub line: isize,
45    pub column: isize,
46    pub v: Vec<Wood>,
47}
48#[derive(Debug, Clone, PartialEq, Eq)]
49pub struct Leaf {
50    pub line: isize,
51    pub column: isize,
52    pub v: String,
53}
54#[derive(Debug, Clone, Eq)]
55pub enum Wood {
56    Branchv(Branch),
57    Leafv(Leaf),
58}
59pub use Wood::*;
60
61impl From<String> for Wood {
62    fn from(v: String) -> Wood {
63        Leafv(Leaf {
64            line: -1,
65            column: -1,
66            v,
67        })
68    }
69}
70impl<'a> From<&'a str> for Wood {
71    fn from(v: &'a str) -> Wood {
72        Wood::from(v.to_string())
73    }
74}
75impl From<Vec<Wood>> for Wood {
76    fn from(v: Vec<Wood>) -> Wood {
77        Branchv(Branch {
78            line: -1,
79            column: -1,
80            v,
81        })
82    }
83}
84
85fn tail<I: Iterator>(mut v: I) -> I {
86    v.next();
87    v
88}
89
90// fn stringify_with_separators<T:Deref<Target=str>, I:Iterator<Item=T>>(out_buf:&mut String, separator:&str, mut i:I){
91// 	if let Some(ref first) = i.next() {
92// 		out_buf.push_str(&**first);
93// 		while let Some(ref nexto) = i.next() {
94// 			out_buf.push_str(separator);
95// 			out_buf.push_str(&**nexto);
96// 		}
97// 	}
98// }
99
100fn push_escaped(take: &mut String, give: &str) {
101    for c in give.chars() {
102        match c {
103            '\n' => {
104                take.push('\\');
105                take.push('n');
106            }
107            '\t' => {
108                take.push('\\');
109                take.push('t');
110            }
111            '"' => {
112                take.push('\\');
113                take.push('"');
114            }
115            _ => {
116                take.push(c);
117            }
118        }
119    }
120}
121
122///A more succinct enum for discriminating leaves and branches, accessible via `Wood::what`
123pub enum LB<'a> {
124    L(&'a str),
125    B(&'a [Wood]),
126}
127pub use LB::*;
128
129impl Wood {
130    pub fn leaf(v: String) -> Wood {
131        Wood::Leafv(Leaf {
132            line: -1,
133            column: -1,
134            v: v,
135        })
136    }
137    pub fn branch(v: Vec<Wood>) -> Wood {
138        Wood::Branchv(Branch {
139            line: -1,
140            column: -1,
141            v: v,
142        })
143    }
144    pub fn empty() -> Wood {
145        Wood::branch(Vec::new())
146    }
147    pub fn is_leaf(&self) -> bool {
148        match self {
149            &Leafv(_) => true,
150            _ => false,
151        }
152    }
153    pub fn is_branch(&self) -> bool {
154        match self {
155            &Branchv(_) => true,
156            _ => false,
157        }
158    }
159    pub fn what(&self) -> LB<'_> {
160        match *self {
161            Leafv(ref s) => L(&s.v),
162            Branchv(ref s) => B(&s.v),
163        }
164    }
165    pub fn get_leaf(&self) -> Option<&str> {
166        match *self {
167            Leafv(ref s) => Some(&s.v),
168            Branchv(_) => None,
169        }
170    }
171    pub fn get_branch(&self) -> Option<&[Wood]> {
172        match *self {
173            Leafv(_) => None,
174            Branchv(ref s) => Some(&s.v),
175        }
176    }
177    pub fn line_and_col(&self) -> (isize, isize) {
178        match *self {
179            Wood::Branchv(ref l) => (l.line, l.column),
180            Wood::Leafv(ref a) => (a.line, a.column),
181        }
182    }
183    pub fn line(&self) -> isize {
184        match *self {
185            Leafv(ref s) => s.line,
186            Branchv(ref s) => s.line,
187        }
188    }
189    pub fn col(&self) -> isize {
190        match *self {
191            Leafv(ref s) => s.column,
192            Branchv(ref s) => s.column,
193        }
194    }
195    /// Seeks the earliest string in the tree by looking at the first element of each branch recursively until it hits a leaf. (If it runs into an empty list, returns the empty string.
196    /// I recommend this whenever you want to use the first string as a discriminator, or whenever you want to get leaf str contents in general.
197    /// This abstracts over similar structures in a way that I consider generally desirable. I would go as far as to say that the more obvious, less abstract way of getting initial string should be Considered Harmful.
198    /// A few motivating examples:
199    /// If you wanted to add a feature to a programming language that allows you to add a special tag to an invocation, you want to put the tag inside the invocation's ast node but you don't want it to be confused for a parameter, this pattern enables:
200    /// ((f tag(special_invoke_inline)) a b)
201    /// If the syntax of a sexp language had more structure to it than usual:
202    /// ((if condition) then...) would still get easily picked up as an 'if' node.
203    /// Annotations are a good example, more generally, if you're refactoring and you decide you want to add an extra field to what was previously a leaf, this pattern enables you to make that change, confident that your code will still read its string content in the same way
204    /// (list key:value "some prose") -> (list key:value ("some prose" modifier:italicise))
205    pub fn initial_str(&self) -> &str {
206        match *self {
207            Branchv(ref v) => {
208                if let Some(ref ss) = v.v.first() {
209                    ss.initial_str()
210                } else {
211                    ""
212                }
213            }
214            Leafv(ref v) => v.v.as_str(),
215        }
216    }
217    pub fn to_string(&self) -> String {
218        to_woodslist(self)
219    }
220
221    pub fn strip_comments_escape(&mut self, comment_str: &str, comment_escape_str: &str) {
222        match *self {
223            Branchv(ref mut b) => {
224                b.v.retain_mut(|i| {
225                    if i.initial_str() == comment_str {
226                        false
227                    } else {
228                        i.strip_comments_escape(comment_str, comment_escape_str);
229                        true
230                    }
231                });
232            }
233            Leafv(ref mut l) => {
234                if &l.v == comment_escape_str {
235                    l.v = comment_str.into();
236                }
237            }
238        }
239    }
240
241    /// Strips any branches with first element of comment_str. If you need to produce a leaf that is equivalent to comment_str.
242    /// If you need the wood to contain a leaf that is the comment_str, you can escape it with a backslash.
243    /// This is actually a highly flawed way of providing commenting, because this will also strip out any serialization of a list of strings where the first element happens to equal the `comment_str`. That's a really subtle error, that violates a lot of expectations.
244    /// You could get around it by escaping your wood so that any strs that resemble comment tags wont read that way, but it's a bit awkward and sometimes wont really work.
245    pub fn strip_comments(&mut self, comment_str: &str) {
246        let escstr = format!("\\{}", comment_str);
247        self.strip_comments_escape(comment_str, &escstr);
248    }
249
250    /// if Leaf, returns a slice iter containing just this, else Branch, iterates over branch contents
251    pub fn contents(&self) -> std::slice::Iter<'_, Self> {
252        match *self {
253            Branchv(ref v) => v.v.iter(),
254            Leafv(_) => std::slice::from_ref(self).iter(),
255        }
256    }
257    /// returns the first wood, or if it's a leaf, itself
258    pub fn head(&self) -> Result<&Wood, Box<WoodError>> {
259        self.contents().next().ok_or_else(|| {
260            Box::new(WoodError::new(
261                self,
262                "there shouldn't be an empty list here".to_string(),
263            ))
264        })
265    }
266    /// returns the second wood within this one, if it is a list wood, if there is a second wood
267    pub fn second(&self) -> Result<&Wood, Box<WoodError>> {
268        self.tail().next().ok_or_else(|| {
269            Box::new(WoodError::new(
270                self,
271                "a second wood was supposed to be present".to_string(),
272            ))
273        })
274    }
275    /// if Leaf, returns an empty slice iter, if Branch, returns contents after the first element
276    pub fn tail<'b>(&'b self) -> std::slice::Iter<'b, Self> {
277        match *self {
278            Branchv(ref v) => tail((*v).v.iter()),
279            Leafv(_) => [].iter(),
280        }
281    }
282    /// returns the tail of the first element of the wood chained with the rest of the wood. For dealing with a common situation with the termpose syntax where you want to ignore the structure of initial line items
283    /// ```plain
284    /// call a b car
285    ///   dog
286    ///   entropy
287    ///   foreign_adversary
288    /// ```
289    /// in this case, `flatter_tail` would give you `[a, b, car, dog, entropy, foreign_adversary]`. (by contrast, `tail` would only give you `[dog, entropy, foreign_adversary]`)
290    pub fn flatter_tail<'a>(
291        &'a self,
292    ) -> std::iter::Chain<slice::Iter<'a, Self>, slice::Iter<'a, Self>> {
293        let mut r = self.contents();
294        if let Some(first) = r.next() {
295            first.tail().chain(r)
296        } else {
297            [].iter().chain([].iter())
298        }
299    }
300    /// `self.contents().find(|el| el.initial_str() == key)`
301    // TODO: Make this seek the initial list. For instance, In (((key vv) val) (nonkey a)), it should return (key vv), not ((key vv) val). This allows the schema to be evolved so that things can be associated with a kv pair without changing it
302    pub fn seek<'a, 'b>(&'a self, key: &'b str) -> Option<&'a Wood> {
303        self.contents().find(|el| el.initial_str() == key)
304    }
305    pub fn seek_val<'a, 'b>(&'a self, key: &'b str) -> Option<&'a Wood> {
306        self.seek(key).and_then(|w| w.tail().next())
307    }
308
309    /// returns the first child term with initial_str == key, or if none is found, an error
310    pub fn find<'a, 'b>(&'a self, key: &'b str) -> Result<&'a Wood, Box<WoodError>> {
311        self.seek(key).ok_or_else(|| {
312            Box::new(WoodError::new(
313                self,
314                format!("could not find child with key \"{}\"", key),
315            ))
316        })
317    }
318    /// find(self, key).and_then(|v| v.second())
319    pub fn find_val<'a, 'b>(&'a self, key: &'b str) -> Result<&'a Wood, Box<WoodError>> {
320        self.find(key).and_then(|v| v.second())
321    }
322}
323
324//I really wanted to make this recurse over everything, in braces, making branches of each, but this did not work
325#[macro_export]
326macro_rules! woods {
327	($($el:expr),* $(,)?)=> {
328		$crate::Branchv($crate::Branch{line:-1, column:-1, v:vec!($($crate::Wood::from($el)),*)})
329	};
330	// ($e:expr)=> { Wood::from($e) }
331}
332
333pub trait Wooder<T> {
334    fn woodify(&self, v: &T) -> Wood;
335}
336pub trait Dewooder<T> {
337    fn dewoodify(&self, v: &Wood) -> Result<T, Box<WoodError>>;
338}
339
340#[derive(Debug)]
341pub struct WoodError {
342    pub line: isize,
343    pub column: isize,
344    pub msg: String,
345    pub cause: Option<Box<dyn Error>>,
346}
347impl Display for WoodError {
348    fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
349        Debug::fmt(self, f)
350    }
351}
352impl WoodError {
353    pub fn new(source: &Wood, msg: String) -> Self {
354        let (line, column) = source.line_and_col();
355        Self {
356            line,
357            column,
358            msg,
359            cause: None,
360        }
361    }
362    pub fn new_with_cause(source: &Wood, msg: String, cause: Box<dyn Error>) -> Self {
363        let (line, column) = source.line_and_col();
364        WoodError {
365            line,
366            column,
367            msg,
368            cause: Some(cause),
369        }
370    }
371}
372impl Error for WoodError {
373    fn cause(&self) -> Option<&dyn Error> {
374        self.cause.as_ref().map(|e| e.as_ref())
375    }
376}
377
378pub trait Woodable {
379    fn woodify(&self) -> Wood;
380}
381pub trait Dewoodable {
382    fn dewoodify(v: &Wood) -> Result<Self, Box<WoodError>>
383    where
384        Self: Sized;
385}
386
387pub fn woodify<T>(v: &T) -> Wood
388where
389    T: Woodable,
390{
391    v.woodify()
392}
393pub fn dewoodify<T>(v: &Wood) -> Result<T, Box<WoodError>>
394where
395    T: Dewoodable,
396{
397    T::dewoodify(v)
398}
399
400/// parse_termpose(v).and_then(dewoodify)
401pub fn deserialize<T>(v: &str) -> Result<T, Box<WoodError>>
402where
403    T: Dewoodable,
404{
405    parse_termpose(v).and_then(|w| dewoodify(&w))
406}
407/// woodify(v).to_string()
408pub fn serialize<T>(v: &T) -> String
409where
410    T: Woodable,
411{
412    woodify(v).to_string()
413}
414
415macro_rules! do_basic_stringifying_woodable_for {
416    ($Type:ident) => {
417        impl Woodable for $Type {
418            fn woodify(&self) -> Wood {
419                self.to_string().into()
420            }
421        }
422    };
423}
424macro_rules! do_basic_destringifying_dewoodable_for {
425    ($Type:ident) => {
426        impl Dewoodable for $Type {
427            fn dewoodify(v: &Wood) -> Result<$Type, Box<WoodError>> {
428                $Type::from_str(v.initial_str()).map_err(|er| {
429                    Box::new(WoodError::new_with_cause(
430                        v,
431                        format!("couldn't parse {}", stringify!($name)),
432                        Box::new(er),
433                    ))
434                })
435            }
436        }
437    };
438}
439
440do_basic_stringifying_woodable_for!(char);
441do_basic_destringifying_dewoodable_for!(char);
442do_basic_stringifying_woodable_for!(u32);
443do_basic_destringifying_dewoodable_for!(u32);
444do_basic_stringifying_woodable_for!(u64);
445do_basic_destringifying_dewoodable_for!(u64);
446do_basic_stringifying_woodable_for!(u128);
447do_basic_destringifying_dewoodable_for!(u128);
448do_basic_stringifying_woodable_for!(i32);
449do_basic_destringifying_dewoodable_for!(i32);
450do_basic_stringifying_woodable_for!(i64);
451do_basic_destringifying_dewoodable_for!(i64);
452do_basic_stringifying_woodable_for!(i128);
453do_basic_destringifying_dewoodable_for!(i128);
454do_basic_stringifying_woodable_for!(f32);
455do_basic_destringifying_dewoodable_for!(f32);
456do_basic_stringifying_woodable_for!(f64);
457do_basic_destringifying_dewoodable_for!(f64);
458do_basic_stringifying_woodable_for!(isize);
459do_basic_destringifying_dewoodable_for!(isize);
460do_basic_stringifying_woodable_for!(usize);
461do_basic_destringifying_dewoodable_for!(usize);
462
463do_basic_stringifying_woodable_for!(bool);
464impl Dewoodable for bool {
465    fn dewoodify(v: &Wood) -> Result<Self, Box<WoodError>> {
466        match v.initial_str() {
467            "true" | "⊤" | "yes" => Ok(true),
468            "false" | "⟂" | "no" => Ok(false),
469            _ => Err(Box::new(WoodError::new(v, "expected a bool here".into()))),
470        }
471    }
472}
473
474// impl<I, T> Woodable for I where I: Iterator<Item=T>, T:Woodable {
475
476// }
477
478impl Woodable for String {
479    fn woodify(&self) -> Wood {
480        self.as_str().into()
481    }
482}
483impl Dewoodable for String {
484    fn dewoodify(v: &Wood) -> Result<Self, Box<WoodError>> {
485        match *v {
486            Leafv(ref a) => Ok(a.v.clone()),
487            Branchv(_) => Err(Box::new(WoodError::new(
488                v,
489                "sought string, found branch".into(),
490            ))),
491        }
492    }
493}
494
495pub fn woodify_seq_into<'a, InnerTran, T, I>(inner: &InnerTran, v: I, output: &mut Vec<Wood>)
496where
497    InnerTran: Wooder<T>,
498    I: Iterator<Item = &'a T>,
499    T: 'a,
500{
501    for vi in v {
502        output.push(inner.woodify(vi));
503    }
504}
505pub fn dewoodify_seq_into<'a, InnerTran, T, I>(
506    inner: &InnerTran,
507    v: I,
508    output: &mut Vec<T>,
509) -> Result<(), Box<WoodError>>
510where
511    InnerTran: Dewooder<T>,
512    I: Iterator<Item = &'a Wood>,
513{
514    // let errors = Vec::new();
515    for vi in v {
516        match inner.dewoodify(vi) {
517            Ok(vii) => output.push(vii),
518            Err(e) => return Err(e),
519        }
520    }
521    Ok(())
522    // if errors.len() > 0 {
523    // 	let msgs = String::new();
524    // 	for e in errors {
525    // 		msgs.push(format!("{}\n"))
526    // 	}
527    // }
528}
529
530impl<T> Woodable for Vec<T>
531where
532    T: Woodable,
533{
534    fn woodify(&self) -> Wood {
535        let mut ret = Vec::new();
536        woodify_seq_into(&wooder::Iden, self.iter(), &mut ret);
537        ret.into()
538    }
539}
540impl<T> Dewoodable for Vec<T>
541where
542    T: Dewoodable,
543{
544    fn dewoodify(v: &Wood) -> Result<Vec<T>, Box<WoodError>> {
545        let mut ret = Vec::new();
546        dewoodify_seq_into(&wooder::Iden, v.contents(), &mut ret)?;
547        Ok(ret)
548    }
549}
550
551mod parsers;
552pub use parsers::*;
553
554pub mod wooder;
555
556#[cfg(test)]
557mod tests {
558    extern crate test;
559    use super::*;
560
561    #[test]
562    fn test_comment_stripping() {
563        let mut w = parse_multiline_termpose(
564            "
565
566#\"this function cannot be called with a false object criterion
567fn process_ishm_protocol_handling object criterion
568	if criterion(object)
569		#\"then it returns good
570		return good
571	else
572		return angered
573	return secret_egg
574
575",
576        )
577        .unwrap();
578        w.strip_comments("#");
579
580        let should_be = parse_multiline_termpose(
581            "
582
583fn process_ishm_protocol_handling object criterion
584	if criterion(object)
585		return good
586	else
587		return angered
588	return secret_egg
589
590",
591        )
592        .unwrap();
593
594        assert_eq!(&w, &should_be);
595    }
596
597    // unfortunately I wasn't able to implement this. Rust's macros are really deeply horrible to work with.
598    // #[test]
599    // fn test_build_macro(){
600    // 	let w = woodser!("yeah");
601    // 	// let wa = woodser!(["yeah", ["how", [[], "okay"]]]);
602    // 	let a = Wood::from([Wood::from("yeah")]);
603    // 	let wa = woodser!(["yeah", ["how", "d", [], w]]);
604    // 	panic!("{}", super::pretty_termpose(&wa))
605    // }
606}