Skip to main content

oak_dot/ast/
mod.rs

1#![doc = include_str!("readme.md")]
2use core::range::Range;
3#[cfg(feature = "serde")]
4use serde::{Deserialize, Serialize};
5
6/// Root node of a DOT file.
7#[derive(Debug, Clone, PartialEq)]
8#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
9pub struct DotRoot {
10    /// The list of graphs defined in the DOT file.
11    pub graphs: Vec<Graph>,
12}
13
14impl std::fmt::Display for DotRoot {
15    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
16        for graph in &self.graphs {
17            write!(f, "{}", graph)?;
18        }
19        Ok(())
20    }
21}
22
23/// A graph definition (either a 'graph' or a 'digraph').
24#[derive(Debug, Clone, PartialEq)]
25#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
26pub struct Graph {
27    /// Whether the graph is strict (no multiple edges between same nodes).
28    pub strict: bool,
29    /// The type of the graph (graph or digraph).
30    pub graph_type: GraphType,
31    /// Optional identifier for the graph.
32    pub id: Option<String>,
33    /// The list of statements within the graph.
34    pub statements: Vec<Statement>,
35    /// The source range of this graph definition.
36    #[cfg_attr(feature = "serde", serde(with = "oak_core::serde_range"))]
37    pub span: Range<usize>,
38}
39
40impl std::fmt::Display for Graph {
41    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
42        if self.strict {
43            write!(f, "strict ")?;
44        }
45        let gtype = match self.graph_type {
46            GraphType::Graph => "graph",
47            GraphType::Digraph => "digraph",
48        };
49        write!(f, "{} ", gtype)?;
50        if let Some(id) = &self.id {
51            write!(f, "{} ", id)?;
52        }
53        writeln!(f, "{{")?;
54        for stmt in &self.statements {
55            write!(f, "    {}", stmt)?;
56        }
57        writeln!(f, "}}")?;
58        Ok(())
59    }
60}
61
62/// The type of a graph.
63#[derive(Debug, Clone, PartialEq)]
64#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
65pub enum GraphType {
66    /// An undirected graph.
67    Graph,
68    /// A directed graph.
69    Digraph,
70}
71
72/// A statement in a DOT graph.
73#[derive(Debug, Clone, PartialEq)]
74#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
75pub enum Statement {
76    /// A node definition or modification.
77    Node(NodeStatement),
78    /// An edge definition.
79    Edge(EdgeStatement),
80    /// A global attribute setting (graph, node, or edge).
81    Attribute(AttributeStatement),
82    /// A subgraph definition.
83    Subgraph(SubgraphStatement),
84    /// An assignment statement (key=value).
85    Assignment(AssignmentStatement),
86}
87
88impl std::fmt::Display for Statement {
89    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
90        match self {
91            Statement::Node(n) => {
92                write!(f, "{}", n.node_id.id)?;
93                if !n.attributes.is_empty() {
94                    write!(f, " [")?;
95                    for (i, attr) in n.attributes.iter().enumerate() {
96                        if i > 0 {
97                            write!(f, ", ")?;
98                        }
99                        write!(f, "{}", attr.name)?;
100                        if let Some(val) = &attr.value {
101                            write!(f, "={}", val)?;
102                        }
103                    }
104                    write!(f, "]")?;
105                }
106                writeln!(f, ";")
107            }
108            Statement::Edge(e) => {
109                match &e.left {
110                    EdgeOperand::Node(id) => write!(f, "{}", id.id)?,
111                    EdgeOperand::Subgraph(s) => write!(f, "subgraph {} {{ ... }}", s.id.as_deref().unwrap_or(""))?,
112                }
113                for (op, target) in &e.edges {
114                    let op_str = match op {
115                        EdgeOp::Directed => "->",
116                        EdgeOp::Undirected => "--",
117                    };
118                    match target {
119                        EdgeOperand::Node(id) => write!(f, " {} {}", op_str, id.id)?,
120                        EdgeOperand::Subgraph(s) => write!(f, " {} subgraph {} {{ ... }}", op_str, s.id.as_deref().unwrap_or(""))?,
121                    }
122                }
123                if !e.attributes.is_empty() {
124                    write!(f, " [")?;
125                    for (i, attr) in e.attributes.iter().enumerate() {
126                        if i > 0 {
127                            write!(f, ", ")?;
128                        }
129                        write!(f, "{}", attr.name)?;
130                        if let Some(val) = &attr.value {
131                            write!(f, "={}", val)?;
132                        }
133                    }
134                    write!(f, "]")?;
135                }
136                writeln!(f, ";")
137            }
138            Statement::Attribute(a) => {
139                let target = match a.target {
140                    AttributeTarget::Graph => "graph",
141                    AttributeTarget::Node => "node",
142                    AttributeTarget::Edge => "edge",
143                };
144                write!(f, "{} [", target)?;
145                for (i, attr) in a.attributes.iter().enumerate() {
146                    if i > 0 {
147                        write!(f, ", ")?;
148                    }
149                    write!(f, "{}", attr.name)?;
150                    if let Some(val) = &attr.value {
151                        write!(f, "={}", val)?;
152                    }
153                }
154                writeln!(f, "];")
155            }
156            Statement::Subgraph(s) => {
157                write!(f, "subgraph ")?;
158                if let Some(id) = &s.id {
159                    write!(f, "{} ", id)?;
160                }
161                writeln!(f, "{{")?;
162                for stmt in &s.statements {
163                    write!(f, "        {}", stmt)?;
164                }
165                writeln!(f, "    }}")
166            }
167            Statement::Assignment(a) => {
168                writeln!(f, "{}={};", a.id, a.value)
169            }
170        }
171    }
172}
173
174/// A node statement.
175#[derive(Debug, Clone, PartialEq)]
176#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
177pub struct NodeStatement {
178    /// The ID of the node.
179    pub node_id: NodeId,
180    /// Attributes associated with the node.
181    pub attributes: Vec<Attribute>,
182    /// The source range of this node statement.
183    #[cfg_attr(feature = "serde", serde(with = "oak_core::serde_range"))]
184    pub span: Range<usize>,
185}
186
187/// An edge statement.
188#[derive(Debug, Clone, PartialEq)]
189#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
190pub struct EdgeStatement {
191    /// The left-hand side operand of the edge.
192    pub left: EdgeOperand,
193    /// The list of edges following the left operand.
194    pub edges: Vec<(EdgeOp, EdgeOperand)>,
195    /// Attributes associated with the edge(s).
196    pub attributes: Vec<Attribute>,
197    /// The source range of this edge statement.
198    #[cfg_attr(feature = "serde", serde(with = "oak_core::serde_range"))]
199    pub span: Range<usize>,
200}
201
202/// An operand in an edge statement.
203#[derive(Debug, Clone, PartialEq)]
204#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
205pub enum EdgeOperand {
206    /// A single node ID.
207    Node(NodeId),
208    /// A subgraph.
209    Subgraph(SubgraphStatement),
210}
211
212/// An edge operator.
213#[derive(Debug, Clone, PartialEq)]
214#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
215pub enum EdgeOp {
216    /// A directed edge operator (->).
217    Directed, // ->
218    /// An undirected edge operator (--).
219    Undirected, // --
220}
221
222/// An attribute statement.
223#[derive(Debug, Clone, PartialEq)]
224#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
225pub struct AttributeStatement {
226    /// The target of the attribute setting.
227    pub target: AttributeTarget,
228    /// The list of attributes to set.
229    pub attributes: Vec<Attribute>,
230    /// The source range of this attribute statement.
231    #[cfg_attr(feature = "serde", serde(with = "oak_core::serde_range"))]
232    pub span: Range<usize>,
233}
234
235/// The target of an attribute statement.
236#[derive(Debug, Clone, PartialEq)]
237#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
238pub enum AttributeTarget {
239    /// Affects the graph.
240    Graph,
241    /// Affects all nodes.
242    Node,
243    /// Affects all edges.
244    Edge,
245}
246
247/// A subgraph statement.
248#[derive(Debug, Clone, PartialEq)]
249#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
250pub struct SubgraphStatement {
251    /// Optional identifier for the subgraph.
252    pub id: Option<String>,
253    /// The list of statements within the subgraph.
254    pub statements: Vec<Statement>,
255    /// The source range of this subgraph statement.
256    #[cfg_attr(feature = "serde", serde(with = "oak_core::serde_range"))]
257    pub span: Range<usize>,
258}
259
260/// An assignment statement.
261#[derive(Debug, Clone, PartialEq)]
262#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
263pub struct AssignmentStatement {
264    /// The key (identifier).
265    pub id: String,
266    /// The value.
267    pub value: String,
268    /// The source range of this assignment.
269    #[cfg_attr(feature = "serde", serde(with = "oak_core::serde_range"))]
270    pub span: Range<usize>,
271}
272
273/// A node identifier with optional port and compass point.
274#[derive(Debug, Clone, PartialEq)]
275#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
276pub struct NodeId {
277    /// The main identifier of the node.
278    pub id: String,
279    /// Optional port on the node.
280    pub port: Option<Port>,
281    /// The source range of this node ID.
282    #[cfg_attr(feature = "serde", serde(with = "oak_core::serde_range"))]
283    pub span: Range<usize>,
284}
285
286/// A port on a node.
287#[derive(Debug, Clone, PartialEq)]
288#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
289pub struct Port {
290    /// Optional identifier for the port.
291    pub id: Option<String>,
292    /// Optional compass point.
293    pub compass: Option<Compass>,
294    /// The source range of this port definition.
295    #[cfg_attr(feature = "serde", serde(with = "oak_core::serde_range"))]
296    pub span: Range<usize>,
297}
298
299/// Compass points for ports.
300#[derive(Debug, Clone, PartialEq)]
301#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
302pub enum Compass {
303    /// North.
304    N,
305    /// North-East.
306    NE,
307    /// East.
308    E,
309    /// South-East.
310    SE,
311    /// South.
312    S,
313    /// South-West.
314    SW,
315    /// West.
316    W,
317    /// North-West.
318    NW,
319    /// Center.
320    C,
321}
322
323/// A single attribute (key-value pair).
324#[derive(Debug, Clone, PartialEq)]
325#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
326pub struct Attribute {
327    /// The name of the attribute.
328    pub name: String,
329    /// The optional value of the attribute.
330    pub value: Option<String>,
331    /// The source range of this attribute.
332    #[cfg_attr(feature = "serde", serde(with = "oak_core::serde_range"))]
333    pub span: Range<usize>,
334}