depict 0.2.0

Picture systems, tell stories
Documentation

//! depict is library for automatically drawing beautiful, readable pictures of 
//! models of systems, processes, and concepts of operation (ConOps).
//! 
//! # Summary
//! 
//! dpict may be best understood as a compiler from a textual language of 
//! "depict-expressions" ("depictions") to "pictures". It is implemented as a 
//! library for easy use by downstream packages like [depict_desktop], [depict_web], 
//! [depict_server], [depict_tikz], and [depict_parse].
//! 
//! [depict_desktop]: ../depict_desktop/index.html
//! [depict_parse]: ../depict_parse/index.html
//! [depict_server]: ../depict_server/index.html
//! [depict_tikz]: ../depict_tikz/index.html
//! [depict_web]: ../depict_web/index.html
pub mod printer {
    //! A pretty-printer for "depiction" parse trees
    //! 
    //! (The main purpose of the pretty-printer is to help test the 
    //! [parser](super::parser) via [proptest].)
    use std::borrow::Cow;

    use itertools::Itertools;

    use super::parser::{Item};

    pub fn print(model: &[Item]) -> String {
        model.iter().map(print1).join("\n")
    }

    pub fn print1(i: &Item) -> String {
        let mut v = Vec::new();
        match i {
            Item::Text(s) => v.push(s.clone()),
            Item::Seq(s) => v.extend(s.iter().map(|i| Cow::from(print1(i)))),
            Item::Comma(s) => {
                v.extend(itertools::intersperse(s.iter().map(|i| Cow::from(print1(i))), Cow::from(",")));
                if s.len() <= 1 {
                    v.push(Cow::from(","));
                }
            },
            Item::Colon(l, r) => {
                v.extend(l.iter().map(|i| Cow::from(print1(i))));
                v.push(Cow::from(":"));
                v.extend(r.iter().map(|i| Cow::from(print1(i))));
            },
            Item::Slash(l, r) => {
                v.extend(l.iter().map(|i| Cow::from(print1(i))));
                v.push(Cow::from("/"));
                v.extend(r.iter().map(|i| Cow::from(print1(i))));
            },
            Item::Sq(s) => {
                v.push(Cow::from("["));
                v.extend(s.iter().map(|i| Cow::from(print1(i))));
                v.push(Cow::from("]"));
            },
            Item::Br(s) => {
                v.push(Cow::from("{"));
                v.extend(s.iter().map(|i| Cow::from(print1(i))));
                v.push(Cow::from("}"));
            },
        }
        v.join(" ")
    }

    #[cfg(test)]
    mod test {
        use std::borrow::Cow;

        use proptest::prelude::*;
        use logos::Logos;
        use crate::parser::{Item, Parser, Token};

        fn deseq(v: Vec<Item>) -> Vec<Item> {
            if let [Item::Seq(v)] = &v[..] { 
                v.clone()
            } else { 
                v
            }
        }
        
        /// Generate an arbitrary [Item]
        /// 
        /// (Note: one challenge in this area is that in normal use, [Item] has 
        /// associativity and precedence invariants enforced by [Parser] and, 
        /// as a consequence, "arbitary" items need to be carefully constructed 
        /// to enforce these invariants.)
        fn arb_item() -> impl Strategy<Value = Item<'static>> {
            let leaf = "[a-z]+".prop_map(|s| Item::Text(Cow::from(s)));
            let leaf2 = leaf.clone().prop_recursive(1, 4, 3, |inner| {
                prop::collection::vec(inner.clone(), 2..3).prop_map(Item::Seq)
            });
            let leaf3 = prop_oneof![
                leaf.clone(),
                leaf2.clone(),
                prop::collection::vec(leaf.clone(), 0..3).prop_map(Item::Comma),
            ];
            leaf3.prop_recursive(
                1, 4, 3, |inner| {
                    prop_oneof![
                        // note: this pair of cases, plus the depth limitation above, 
                        // is a crude work-around for needing to generate only right-
                        // associated trees of colons
                        (inner.clone(), inner.clone()).prop_map(|(i, j)| Item::Colon(deseq(vec![i]), deseq(vec![j]))),
                        (inner.clone(), inner.clone(), inner.clone()).prop_map(|(i, j, k)| 
                            Item::Colon(
                                deseq(vec![i]), 
                                vec![Item::Colon(deseq(vec![j]), deseq(vec![k]))]
                            )),

                        (inner.clone(), inner.clone()).prop_map(|(i, j)| Item::Slash(deseq(vec![i]), deseq(vec![j]))),

                        inner.clone().prop_map(|i| Item::Br(vec![i])),

                        inner.clone().prop_map(|i| Item::Sq(vec![i])),
                    ]
                }
            )
        }

        proptest! {
            #[test]
            fn doesnt_crash(s in "\\PC*") {
                let mut lex = Token::lexer(&s);
                let mut p = Parser::new();

                for tk in lex.by_ref() {
                    if p.parse(tk).is_err() {
                        return Ok(());
                    }
                }
                let v = p.end_of_input();
                if v.is_err() {
                    return Ok(());
                }
            }

            #[test]
            fn has_partial_inverse(i in arb_item()) {
                let s = super::print1(&i);
                let mut lex = Token::lexer(&s);
                let mut p = Parser::new();

                for tk in lex.by_ref() {
                    p.parse(tk).unwrap();
                }
                let v = p.end_of_input().unwrap();
                assert!(i == v[0] || i == (if let Item::Seq(v) = &v[0] { v[0].clone() } else { v[0].clone() }), "\n\ni: {i:#?}\ns: {s:?}\no: {v:#?}\n\n");
            }
        }
    }
}

pub mod parser {
    //! The parser for "depictions"
    //! 
    //! # Summary
    //! 
    //! The language of depictions loosely consists of:
    //! 
    //! * definitions ::= *name* **:** *expr*,
    //! * relations ::=  *name* *name* ... (**:** *labels* (**/** */ *labels*)?)*
    //! * labels ::= *label*... for single-word labels or *label* (**,** *label*)* for multi-word labels
    //! * nesting ::= **[** *model* **]**
    //! * alternatives ::= **{** *model* **}**
    //! 
    //! # Links
    //! 
    //! [Model] and [Item] values can be pretty-printed by [`print()`](crate::printer::print) and [`print1()`](crate::printer::print1), respectively.
    //! 
    use enum_kinds::EnumKind;
    use std::borrow::Cow;
    use std::hash::Hash;

    use pomelo::pomelo;

    /// Depictions consist of [Item]s.
    pub type Model<'s> = Vec<Item<'s>>;

    /// Items are the main "expression" type of depictions.
    #[derive(Clone, Debug, EnumKind, PartialEq)]
    #[enum_kind(ItemKind)]
    pub enum Item<'s> {
        Text(Cow<'s, str>),
        Seq(Vec<Item<'s>>),
        Comma(Vec<Item<'s>>),
        Colon(Vec<Item<'s>>, Vec<Item<'s>>),
        Slash(Vec<Item<'s>>, Vec<Item<'s>>),
        Sq(Vec<Item<'s>>),
        Br(Vec<Item<'s>>),
    }

    // A couple of relations guide the merging process.
    // 
    // Textual items are Texts, Tildes, and Seqs.
    // Binary items are Colons and Slashes.
    // Unary items are Sq and Br.
    // 
    // First, eats: a < b or a > b for "a eats b" or "b eats a".
    // 
    // In general, binary items eat textual items.
    // Colons eat Slashes on their right.
    // Colons eat textual items to their left.
    // While a right colon is eating the *hs of a seq, 
    // the right colon will eat the right-most textual elements, 
    // but the seq will eat the colon thereafter unless it has been emptied.
    // While a right colon is eating the rhs of a left-colon, 
    // the right colon will eat the right-most textual elements, 
    // but if another left colon appears, recurse.
    // Slashes eat textual items on their left, and steal them from colon-rights.
    // Colons do not eat commas on their right; Commas make sequences.
    // Otherwise, when a left colon is eating an item to the right, the eating is delegated
    // to the colon's rhs' end if any, or to the colon's rhs otherwise
    // When fully eaten, Seqs dissolve.
    // Non-seq textual items are conceptually wrapped in a Seq for eating purposes.
    
    pub fn seq<'s>(i: Item<'s>, j: Item<'s>) -> Item<'s> {
        Item::Seq(vec![i, j])
    }

    impl<'s> Item<'s> {
        /// Fold the item on the right (`self` or `j`) with an item on the left (`i`).
        // when eating a left item, eat as much as you can.
        // if you ate the whole item, then only you remain.
        // otherwise, what's left of the item eats you.
        fn eat_left(mut self, mut i: Item<'s>) -> Self {
            if matches!(i, Item::Text(..)  | Item::Sq(..) | Item::Br(..)) {
                i = Item::Seq(vec![i]);
            }
            if matches!(i, Item::Comma(..)) && matches!(self, Item::Slash(..) | Item::Colon(..)) {
                self.left().insert(0, i);
                return self;
            }
            let ikind = ItemKind::from(&i);
            let jkind = ItemKind::from(&self);
            let mut comma_buffer = vec![];
            use ItemKind::*;
            while !i.right().is_empty() {
                let mut end = i.right().pop().unwrap();
                let ekind = ItemKind::from(&end);
                match (ikind, ekind, jkind) {
                    (Seq  , Text  | Br | Sq        , Slash | Colon | Seq) | 
                    (Seq  , Slash | Colon          , Colon              ) |
                    (Colon, Text  | Br | Sq | Comma, Slash | Colon | Seq) => { 
                        // we eat end; i eats us.
                        self.left().insert(0, end);
                    },
                    (_, Comma, Comma) => {
                        end.right().append(self.left());
                        i.right().push(end);
                        return i;
                    },
                    (_, Text, Comma) => {
                        comma_buffer.push(end);
                    },
                    (Seq, _, Slash | Colon) => { 
                        // i eats us.
                        i.right().push(end);
                    },
                    (Seq, _, Seq) => {
                        // i eats us and we dissolve.
                        i.right().push(end);
                    },
                    (_, Colon, _) | (_, Slash, Comma) => {
                        let end = merge_item(end, self);
                        i.right().push(end);
                        return i
                    },
                    (_, Slash, Colon) => {
                        self.left().insert(0, end);
                    },
                    (_, Slash, _) => {
                        end.right().push(self);
                        i.right().push(end);
                        return i
                    },
                    _ => {
                        i.right().push(end);
                        break
                    }
                }
            }
            if jkind == Comma {
                match comma_buffer.len() {
                    0 => {},
                    1 => {
                        self.left().insert(0, comma_buffer.pop().unwrap());
                        if ikind == Seq && i.right().is_empty() {
                            return self;
                        }
                    },
                    _n => {
                        self.left().insert(0, Item::Seq(comma_buffer.into_iter().rev().collect::<Vec<_>>()));
                    },
                }
                i.right().push(self);
                i
            } else if jkind == Seq && ikind != Comma {
                i.right().append(self.left());
                i
            } else if ikind == Seq && i.right().is_empty() { 
                self
            } else {
                i.right().push(self);
                i
            }
        }

        /// Get a &mut reference to `self`'s right-most sequence if one exists, or panic.
        fn right(&mut self) -> &mut Vec<Item<'s>> {
            match self {
                Item::Seq(ref mut r) => r,
                Item::Comma(ref mut r) => r,
                Item::Colon(_, ref mut r) => r,
                Item::Slash(_, ref mut r) => r,
                _ => unreachable!(),
            }
        }

        /// Get a &mut reference to `self`'s left-most sequence if one exists, or panic.
        fn left(&mut self) -> &mut Vec<Item<'s>> {
            match self {
                Item::Seq(ref mut l) => l,
                Item::Comma(ref mut l) => l,
                Item::Colon(ref mut l, _) => l,
                Item::Slash(ref mut l, _) => l,
                _ => unreachable!(),
            }
        }
    }

    /// Combine the two right-most items.
    fn merge_item<'s>(i: Item<'s>, j: Item<'s>) -> Item<'s> {
        eprint!("MERGE {i:?} {j:?}");
        let r = j.eat_left(i);
        eprintln!(" -> {r:?}");
        r
    }
    
    pomelo! {
        %module fact;
        // %parser #[derive(Clone)] pub struct Parser<'s> {};
        // %stack_type 
        %include {
            use std::borrow::Cow;
            use super::{Model, Item, merge_item};
            use logos::{Logos};
        }
        %token #[derive(Copy, Clone, Debug, Logos)] pub enum Token<'s> {};
        %type #[error] #[regex(r#"[\p{Pattern_White_Space}&&[^\r\n]]+"#, logos::skip)] Error;
        %type #[token("{")] Lbr;
        %type #[token("}")] Rbr;
        %type #[token("[")] Lsq;
        %type #[token("]")] Rsq;
        // %type #[token(";")] Semi;
        %type #[token(",")] Comma;
        %type #[token(":")] Colon;
        // %type #[token("~")] Tilde;
        %type #[token("/")] Slash;
        // %type #[token("|")] Pipe;
        // %type #[token("-")] Dash;
        %type #[token("!")] Bang;
        %type #[regex("[\r\n]+")] Nl;
        %type #[regex(r#"[\p{XID_Start}$<()][\p{XID_Continue}().\->&&[^:/]]*(\\/[\p{XID_Continue}().\->&&[^:/]]*)*"#)] Text &'s str;
        %type start Model<'s>;
        %type model Vec<Item<'s>>;
        %type item Item<'s>;
        %type expr1 Item<'s>;
        %type expr2 Item<'s>;
        %type expr2a Item<'s>;
        %type expr3 Item<'s>;
        %type expr4 Item<'s>;
        %type expr5 Item<'s>;
        %right Bang;
        %left Nl;
        %right Slash;
        %right Colon;
        %left Dash;
        %right Comma;
        %right Lsq Lbr;
        %right Rsq Rbr;
        %left Semi;
        %right Text;
        // %left Tilde;
        // %verbose;
        // %trace;

        start ::= model;
        model ::= model?(i) Nl expr1?(j) { 
            let mut i = i.unwrap_or_default();
            if let Some(j) = j { i.push(j) }; 
            i 
        };
        model ::= expr1(j) { vec![j] };

        expr1 ::= Lsq model(j) Rsq { Item::Sq(j) };
        expr1 ::= Lbr model(j) Rbr { Item::Br(j) };
        expr1 ::= expr3(i) [Bang] { i };

        expr3 ::= Text(t) { Item::Text(Cow::Borrowed(t)) };
        expr3 ::= Slash { Item::Slash(vec![], vec![]) };
        expr3 ::= Colon { Item::Colon(vec![], vec![]) };
        expr3 ::= Comma { Item::Comma(vec![]) };
        expr3 ::= expr1(i) expr1(j) [Text] { merge_item(i, j) };
    }

    /// The [pomelo!]-generated depiction parser
    pub use fact::Parser;

    /// The [pomelo!]-generated depiction lexer.
    /// 
    /// To use, please bring the [Logos] trait into scope like so:
    /// ```ignore
    /// use logos::Logos;
    /// ```
    pub use fact::Token;


    pub type Labels<I> = Vec<Option<I>>;

    /// The intermediate representation (IR) of depictions
    /// 
    /// In depict, models are viewed as asserting a claimed set of "facts" 
    /// to be depicted.
    /// 
    /// These "facts" are represented by [Fact]s, each of which record a 
    /// claim like "the sequence `path` model entities are related with 
    /// labels for the forward and backward dimensions of each such 
    /// atomic relationship in the corresponding entries of `labels_by_level`."
    #[derive(Clone, Debug, Eq, Hash, PartialEq)]
    pub struct Fact<I> {
        pub path: Vec<I>,
        pub labels_by_level: Vec<(Labels<I>, Labels<I>)>,
    }
}

#[cfg(all(feature="minion", feature="osqp"))]
pub mod graph_drawing;

#[cfg(any(feature="client", feature="server"))]
pub mod rest {
    //! Message types and codecs for client-server implementations of depict APIs
    use serde::{Deserialize, Serialize};
    use petgraph::Graph;

    /// Labels describe positioned boxes of text.
    #[derive(Clone, Debug, PartialEq, PartialOrd, Deserialize, Serialize)]
    pub struct Label {
        pub text: String,
        pub hpos: f64,
        pub width: f64,
        pub vpos: f64,
    }

    /// Positioned graphical elements, with unique keys.
    #[derive(Clone, Debug, PartialEq, PartialOrd, Deserialize, Serialize)]
    pub enum Node {
        /// Boxes
        Div { key: String, label: String, hpos: f64, vpos: f64, width: f64 },
        /// Arrows with optional textual labels
        Svg { key: String, path: String, rel: String, label: Option<Label> },
    }

    /// The data of a drawing of a "depiction".
    #[derive(Clone, Debug, Deserialize, Serialize)]
    pub struct Drawing {
        pub crossing_number: Option<usize>,
        pub viewbox_width: f64,
        pub layout_debug: Graph<String, String>,
        pub nodes: Vec<Node>,
    }

    /// A drawing request containing a "depiction" to draw.
    #[derive(Clone, Debug, Deserialize, Serialize)]
    pub struct Draw {
        pub text: String
    }

    /// A drawing response containing a [Drawing].
    #[derive(Clone, Debug, Deserialize, Serialize)]
    pub struct DrawResp {
        pub drawing: Drawing
    }

}