polka 0.1.2

A dot language parser for Rust; based on Parser Expression Grammar (PEG) using the excellent pest crate as the underpinning.
Documentation
use crate::dot::{Edge, EdgeOp, EdgeRhs, EdgeStatement};
use crate::parser::graph::sub_graph;
use crate::parser::node::node_id;
use pom::parser::{tag, Parser};

use crate::parser::statement::attribute_list;
use crate::parser::symbols::space;
use std::char;

pub fn edge_op<'a>() -> Parser<'a, char, EdgeOp> {
  let arrow = tag("->").map(|_| EdgeOp::Arrow);
  let line = tag("--").map(|_| EdgeOp::Line);
  arrow | line
}

fn edge_node_or_subgraph<'a>() -> Parser<'a, char, Edge> {
  (sub_graph().map(Edge::Subgraph)) | node_id().map(Edge::NodeId)
}

pub fn edge_rhs_list<'a>() -> Parser<'a, char, Vec<EdgeRhs>> {
  let pattern = edge_op() - space() + edge_node_or_subgraph() - space();
  let edge_rhs = pattern.map(|(edge_op, edge)| EdgeRhs { edge_op, edge });

  edge_rhs.repeat(1..)
}

pub fn edge_statement<'a>() -> Parser<'a, char, EdgeStatement> {
  let pattern =
    edge_node_or_subgraph() - space() + edge_rhs_list() - space() + attribute_list().opt();
  pattern.map(|((edge, edge_rhs_list), attributes)| EdgeStatement {
    edge,
    edge_rhs_list,
    attributes,
  })
}

#[cfg(test)]
mod test {

  #[cfg(test)]
  mod edge_statement {
    use super::super::*;
    use crate::dot::{
      Attribute, Compass, Edge, EdgeOp, EdgeRhs, EdgeStatement, NodeId, Port, Statement, SubGraph,
    };

    use pretty_assertions::assert_eq;

    #[test]
    fn mixed_tokens() {
      let edge1 = Edge::Subgraph(SubGraph {
        id: Some("test".to_string()),
        statements: vec![Statement::IdPair(Attribute {
          key: "a".to_string(),
          value: "b".to_string(),
        })],
      });
      let edge2 = Edge::NodeId(NodeId {
        node_id: "awesome_edge".to_string(),
        port: None,
      });
      let edge_rhs_list = vec![
        EdgeRhs {
          edge_op: EdgeOp::Line,
          edge: Edge::NodeId(NodeId {
            node_id: "awesome_id".to_string(),
            port: None,
          }),
        },
        EdgeRhs {
          edge_op: EdgeOp::Arrow,
          edge: Edge::NodeId(NodeId {
            node_id: "cool_id".to_string(),
            port: Some(Port {
              port_id: Some("a34".to_string()),
              compass: Some(Compass::E),
            }),
          }),
        },
        EdgeRhs {
          edge_op: EdgeOp::Line,
          edge: Edge::NodeId(NodeId {
            node_id: "niceId".to_string(),
            port: None,
          }),
        },
      ];
      let attributes = None;
      let res1 = EdgeStatement {
        edge: edge1,
        edge_rhs_list: edge_rhs_list.clone(),
        attributes: attributes.clone(),
      };
      let res2 = EdgeStatement {
        edge: edge2,
        edge_rhs_list: edge_rhs_list.clone(),
        attributes: attributes.clone(),
      };

      let input1: Vec<char> = "subgraph test {a=b} -- awesome_id -> cool_id:a34:E -- niceId"
        .chars()
        .collect();
      match edge_statement().parse(&input1) {
        Ok(r) => assert_eq!(r, res1),
        Err(e) => panic!(format!(
          "failed with input: {:?} and error: {:?}",
          input1, e
        )),
      };

      let input2: Vec<char> = "awesome_edge -- awesome_id -> cool_id:a34:E -- niceId"
        .chars()
        .collect();
      match edge_statement().parse(&input2) {
        Ok(r) => assert_eq!(r, res2),
        Err(e) => panic!(format!(
          "failed with input: {:?} and error: {:?}",
          input1, e
        )),
      };

      // let input4: Vec<char> = "a -> b[c=1]".chars().collect();
      // match edge_statement().parse(&input4) {
      //   Ok(r) => assert_eq!(r, res2),
      //   Err(e) => panic!(format!(
      //     "failed with input: {:?} and error: {:?}",
      //     input1, e
      //   )),
      // };

      let input3: Vec<char> = "- > awesome - - test".chars().collect();
      if edge_statement().parse(&input3).is_ok() {
        panic!(
          "Should have failed with input {:?}, passed instead.",
          input3
        )
      }
    }
  }

  #[cfg(test)]
  mod edge_rhs_list {
    use super::super::*;
    use crate::dot::{Compass, Edge, EdgeOp, EdgeRhs, NodeId, Port};

    use pretty_assertions::assert_eq;
    #[test]
    fn mixed_tokens() {
      let input1: Vec<char> = "-- awesome_id -> cool_id:a34:E -- niceId".chars().collect();
      let res1 = vec![
        EdgeRhs {
          edge_op: EdgeOp::Line,
          edge: Edge::NodeId(NodeId {
            node_id: "awesome_id".to_string(),
            port: None,
          }),
        },
        EdgeRhs {
          edge_op: EdgeOp::Arrow,
          edge: Edge::NodeId(NodeId {
            node_id: "cool_id".to_string(),
            port: Some(Port {
              port_id: Some("a34".to_string()),
              compass: Some(Compass::E),
            }),
          }),
        },
        EdgeRhs {
          edge_op: EdgeOp::Line,
          edge: Edge::NodeId(NodeId {
            node_id: "niceId".to_string(),
            port: None,
          }),
        },
      ];
      match edge_rhs_list().parse(&input1) {
        Ok(r) => assert_eq!(r, res1),
        Err(e) => panic!(format!(
          "failed with input: {:?} and error: {:?}",
          input1, e
        )),
      };

      let input2: Vec<char> = "- > awesome - - test".chars().collect();
      if edge_rhs_list().parse(&input2).is_ok() {
        panic!(
          "Should have failed with input {:?}, passed instead.",
          input2
        )
      }
    }
  }

  #[cfg(test)]
  mod edge_op {
    use super::super::*;
    use crate::dot::EdgeOp;

    use pretty_assertions::assert_eq;
    #[test]
    fn mixed_tokens() {
      let input1: Vec<char> = "->".chars().collect();
      let res1 = EdgeOp::Arrow;
      match edge_op().parse(&input1) {
        Ok(r) => assert_eq!(r, res1),
        Err(e) => panic!(format!(
          "failed with input: {:?} and error: {:?}",
          input1, e
        )),
      };

      let input2: Vec<char> = "--".chars().collect();
      let res2 = EdgeOp::Line;
      match edge_op().parse(&input2) {
        Ok(r) => assert_eq!(r, res2),
        Err(e) => panic!(format!(
          "failed with input: {:?} and error: {:?}",
          input2, e
        )),
      };

      let input3: Vec<char> = "- >".chars().collect();
      if edge_op().parse(&input3).is_ok() {
        panic!(
          "Should have failed with input {:?}, passed instead.",
          input3
        )
      }
    }
  }
}