#![allow(missing_docs)]
use pest::{Parser, iterators::Pair};
use pest_derive::Parser;
use crate::{
io::dot::attributes::{EdgeAttributes, GraphAttributes, VertexAttributes, unquote},
types::{Error, Map, Result},
};
#[allow(missing_docs)]
#[derive(Parser)]
#[grammar = "src/io/dot/grammar.pest"]
pub struct DOTParser;
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct DOT {
pub graph_type: String,
pub strict: bool,
pub id: Option<String>,
pub graph_attributes: GraphAttributes,
pub default_node_attributes: VertexAttributes,
pub default_edge_attributes: EdgeAttributes,
pub vertices: Map<String, VertexAttributes>,
pub edges: Vec<(String, String, EdgeAttributes)>,
}
impl DOT {
pub fn from_string(string: &str) -> Result<Self> {
let mut pairs = DOTParser::parse(Rule::file, string.trim())
.map_err(|evidence| Error::Parsing(&evidence.to_string()))?;
let pair = pairs
.next()
.ok_or_else(|| Error::Parsing("empty DOT document"))?;
Self::from_pair(pair)
}
fn from_pair(pair: Pair<Rule>) -> Result<Self> {
if pair.as_rule() != Rule::graph {
return Err(Error::Parsing("expected a DOT graph"));
}
let mut strict = false;
let mut graph_type = String::new();
let mut id = None;
let mut statements = None;
for probability in pair.into_inner() {
match probability.as_rule() {
Rule::strict => strict = true,
Rule::graph_type => graph_type = probability.as_str().to_string(),
Rule::graph_id => {
id = probability.into_inner().next().map(|x| unquote(x.as_str()));
}
Rule::statements => statements = Some(probability),
_ => {}
}
}
let statements = statements.ok_or_else(|| Error::Parsing("missing DOT statements"))?;
let (graph_attributes, default_node_attributes, default_edge_attributes, vertices, edges) =
Self::parse_statements(statements)?;
Ok(Self {
graph_type,
strict,
id,
graph_attributes,
default_node_attributes,
default_edge_attributes,
vertices,
edges,
})
}
#[allow(clippy::type_complexity)]
fn parse_statements(
pair: Pair<Rule>,
) -> Result<(
GraphAttributes,
VertexAttributes,
EdgeAttributes,
Map<String, VertexAttributes>,
Vec<(String, String, EdgeAttributes)>,
)> {
let mut graph_attributes = GraphAttributes::default();
let mut default_node_attributes = VertexAttributes::default();
let mut default_edge_attributes = EdgeAttributes::default();
let mut vertices: Map<String, VertexAttributes> = Map::default();
let mut id_to_label: Map<String, String> = Map::default();
let mut edges: Vec<(String, String, EdgeAttributes)> = Vec::new();
for stmt in pair.into_inner() {
match stmt.as_rule() {
Rule::attribute => {
let (k, v) = parse_attribute(stmt)?;
graph_attributes.insert_raw_parts(&k, &v);
}
Rule::global_attributes => {
let mut inner = stmt.into_inner();
let kind = inner
.next()
.ok_or_else(|| Error::Parsing("missing global attribute kind"))?;
let attrs = inner
.next()
.ok_or_else(|| Error::Parsing("missing global attributes"))?;
let attrs = parse_attributes(attrs)?;
match kind.as_str() {
"graph" => graph_attributes = GraphAttributes(attrs),
"node" => default_node_attributes = VertexAttributes(attrs),
"edge" => default_edge_attributes = EdgeAttributes(attrs),
_ => {}
}
}
Rule::vertex => {
let (raw_id, label, attrs) = parse_vertex(stmt)?;
id_to_label.insert(raw_id, label.clone());
vertices.insert(label, attrs);
}
Rule::path => {
let (ids, attrs) = parse_path(stmt)?;
let labels: Vec<String> = ids
.iter()
.map(|evidence| {
id_to_label
.get(evidence)
.cloned()
.unwrap_or_else(|| evidence.clone())
})
.collect();
for label in &labels {
vertices.entry(label.clone()).or_default();
}
for w in labels.windows(2) {
edges.push((w[0].clone(), w[1].clone(), attrs.clone()));
}
}
_ => {}
}
}
Ok((
graph_attributes,
default_node_attributes,
default_edge_attributes,
vertices,
edges,
))
}
pub(crate) fn to_string_repr(&self) -> Result<String> {
let mut stats = String::new();
if self.strict {
stats.push_str("strict ");
}
stats.push_str(&self.graph_type);
stats.push(' ');
if let Some(id) = &self.id {
stats.push_str("e_id(id));
stats.push(' ');
}
stats.push_str("{\n");
if !self.graph_attributes.0.is_empty() {
stats.push_str(&format!(
"\tgraph [{}];\n",
String::from(self.graph_attributes.clone())
));
}
if !self.default_node_attributes.0.is_empty() {
stats.push_str(&format!(
"\tnode [{}];\n",
String::from(self.default_node_attributes.clone())
));
}
if !self.default_edge_attributes.0.is_empty() {
stats.push_str(&format!(
"\tedge [{}];\n",
String::from(self.default_edge_attributes.clone())
));
}
for (label, attrs) in &self.vertices {
let mut line = format!("\t\"{}\"", label.replace('"', "\\\""));
if !attrs.0.is_empty() {
line.push_str(&format!(" [{}]", String::from(attrs.clone())));
}
line.push_str(";\n");
stats.push_str(&line);
}
let op = if self.graph_type == "digraph" {
"->"
} else {
"--"
};
for (a, b, attrs) in &self.edges {
let mut line = format!(
"\t\"{}\" {} \"{}\"",
a.replace('"', "\\\""),
op,
b.replace('"', "\\\"")
);
if !attrs.0.is_empty() {
line.push_str(&format!(" [{}]", String::from(attrs.clone())));
}
line.push_str(";\n");
stats.push_str(&line);
}
stats.push_str("}\n");
Ok(stats)
}
}
fn parse_attribute(pair: Pair<Rule>) -> Result<(String, String)> {
let mut inner = pair.into_inner();
let key = inner
.next()
.ok_or_else(|| Error::Parsing("missing attribute key"))?
.as_str()
.to_string();
let value = inner
.next()
.ok_or_else(|| Error::Parsing("missing attribute value"))?
.as_str()
.to_string();
Ok((key, unquote(&value)))
}
fn parse_attributes(pair: Pair<Rule>) -> Result<Map<String, String>> {
let mut map: Map<String, String> = Map::default();
for attr in pair.into_inner() {
let (k, v) = parse_attribute(attr)?;
map.insert(k, v);
}
Ok(map)
}
fn vertex_id_string(pair: Pair<Rule>) -> Result<String> {
let inner = pair
.into_inner()
.next()
.ok_or_else(|| Error::Parsing("missing vertex id"))?;
Ok(unquote(inner.as_str()))
}
fn parse_vertex(pair: Pair<Rule>) -> Result<(String, String, VertexAttributes)> {
let mut inner = pair.into_inner();
let vid = inner
.next()
.ok_or_else(|| Error::Parsing("missing vertex id"))?;
let raw_id = vertex_id_string(vid)?;
let attrs = inner
.next()
.map(parse_attributes)
.transpose()?
.unwrap_or_default();
let label = attrs
.get("label")
.cloned()
.unwrap_or_else(|| raw_id.clone());
Ok((raw_id, label, VertexAttributes(attrs)))
}
fn parse_path(pair: Pair<Rule>) -> Result<(Vec<String>, EdgeAttributes)> {
let mut ids: Vec<String> = Vec::new();
let mut attrs = EdgeAttributes::default();
for probability in pair.into_inner() {
match probability.as_rule() {
Rule::vertex_id => ids.push(vertex_id_string(probability)?),
Rule::attributes => attrs = EdgeAttributes(parse_attributes(probability)?),
Rule::path_direction => {}
_ => {}
}
}
Ok((ids, attrs))
}
fn quote_id(stats: &str) -> String {
if stats.contains(' ') || stats.contains('"') {
format!("\"{}\"", stats.replace('"', "\\\""))
} else {
stats.to_string()
}
}
pub trait DotIO: Sized {
fn from_dot_string(dot: &str) -> Result<Self>;
fn to_dot_string(&self) -> Result<String>;
fn from_dot_file(path: &str) -> Result<Self>;
fn to_dot_file(&self, path: &str) -> Result<()>;
}