From 526aac370e1b465dc8c82e6f999d032391f6efa5 Mon Sep 17 00:00:00 2001
From: Kubat <maelle.martin@proton.me>
Date: Sat, 3 May 2025 20:43:37 +0200
Subject: [PATCH] [VN] Add the base setters & getters for the graph

NOTE: We ensures that block's ids are unique.
---
 grimoire/src/ast/block.rs |  11 ++--
 grimoire/src/error.rs     |   3 ++
 grimoire/src/graph.rs     | 104 +++++++++++++++++++++++++++++++++++---
 3 files changed, 106 insertions(+), 12 deletions(-)

diff --git a/grimoire/src/ast/block.rs b/grimoire/src/ast/block.rs
index 28bb960..bfbb844 100644
--- a/grimoire/src/ast/block.rs
+++ b/grimoire/src/ast/block.rs
@@ -14,10 +14,13 @@ impl Default for Block {
     fn default() -> Self {
         use std::sync::atomic::{AtomicU64, Ordering};
         static INTERNAL_ID: AtomicU64 = AtomicU64::new(1);
-        Self {
-            internal_id: INTERNAL_ID.fetch_add(1, Ordering::Relaxed),
-            name: Default::default(),
-            spells: Default::default(),
+        match INTERNAL_ID.fetch_add(1, Ordering::Relaxed) {
+            u64::MAX => panic!("reached the max number of blocks, how did you did it?"),
+            internal_id => Self {
+                internal_id,
+                name: Default::default(),
+                spells: Default::default(),
+            },
         }
     }
 }
diff --git a/grimoire/src/error.rs b/grimoire/src/error.rs
index 7750588..b957f9a 100644
--- a/grimoire/src/error.rs
+++ b/grimoire/src/error.rs
@@ -14,6 +14,9 @@ pub enum Error {
     #[error("unexpect: {0}")]
     Unexpected(std::borrow::Cow<'static, str>),
 
+    #[error("redefined: {0}")]
+    Redefined(std::borrow::Cow<'static, str>),
+
     #[error("undefined spell '{0}'")]
     Undefined(std::borrow::Cow<'static, str>),
 
diff --git a/grimoire/src/graph.rs b/grimoire/src/graph.rs
index 87231da..87c7f81 100644
--- a/grimoire/src/graph.rs
+++ b/grimoire/src/graph.rs
@@ -2,30 +2,89 @@ use crate::{
     ast::{Block, BlockId, Expression},
     error::Error,
 };
-use std::collections::HashMap;
+use derive_more::Display;
+use std::{
+    collections::{HashMap, HashSet},
+    hash::Hash,
+};
+
+#[derive(Debug, Display)]
+#[display("Edge({_0} -> {_1})")]
+pub struct Edge(BlockId, BlockId, Option<Expression>);
+
+impl Edge {
+    pub fn new(from: BlockId, to: BlockId) -> Self {
+        Self(from, to, None)
+    }
+
+    pub fn with_condition(from: BlockId, to: BlockId, predicate: Expression) -> Self {
+        Self(from, to, Some(predicate))
+    }
+}
+
+impl Eq for Edge {}
+impl PartialEq for Edge {
+    fn eq(&self, other: &Self) -> bool {
+        self.0 == other.0 && self.1 == other.1
+    }
+}
+
+impl Hash for Edge {
+    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
+        self.0.hash(state);
+        self.1.hash(state);
+    }
+}
 
 #[derive(Default)]
 pub struct Graph {
     nodes: HashMap<BlockId, Block>,
-    edges: Vec<(BlockId, BlockId, Option<Expression>)>,
+    edges: HashSet<Edge>,
     id_mapping: HashMap<String, BlockId>,
 }
 
 impl Graph {
-    pub fn add_node(&mut self, block: Block) -> Result<BlockId, Error> {
-        todo!()
+    /// Note, can't fail becose block's ids are unique!
+    pub fn add_node(&mut self, block: Block) -> BlockId {
+        let (id, block) = block.into_parts();
+
+        if let Some(name) = block.name() {
+            self.id_mapping.insert(name.to_string(), id);
+        }
+        self.nodes.insert(id, block);
+
+        id
     }
 
     pub fn get_node(&self, id: BlockId) -> Result<&Block, Error> {
-        todo!()
+        self.nodes
+            .get(&id)
+            .ok_or(Error::Undefined(format!("block id {id}").into()))
+    }
+
+    pub fn get_node_by_name(&self, name: impl AsRef<str>) -> Result<&Block, Error> {
+        self.get_node(*self.id_mapping.get(name.as_ref()).ok_or(Error::Undefined(
+            format!("block with name {}", name.as_ref()).into(),
+        ))?)
     }
 
     pub fn mut_node(&mut self, id: BlockId) -> Result<&mut Block, Error> {
-        todo!()
+        self.nodes
+            .get_mut(&id)
+            .ok_or(Error::Undefined(format!("block id {id}").into()))
+    }
+
+    pub fn mut_node_by_name(&mut self, name: impl AsRef<str>) -> Result<&mut Block, Error> {
+        self.mut_node(*self.id_mapping.get(name.as_ref()).ok_or(Error::Undefined(
+            format!("block with name {}", name.as_ref()).into(),
+        ))?)
     }
 
     pub fn add_edge(&mut self, from: BlockId, to: BlockId) -> Result<(), Error> {
-        todo!()
+        self.edges
+            .insert(Edge::new(from, to))
+            .then_some(())
+            .ok_or_else(|| Error::Redefined(format!("edge from block {from} to block {to}").into()))
     }
 
     pub fn add_conditional_edge(
@@ -34,6 +93,35 @@ impl Graph {
         to: BlockId,
         predicate: Expression,
     ) -> Result<(), Error> {
-        todo!()
+        self.edges
+            .insert(Edge::with_condition(from, to, predicate))
+            .then_some(())
+            .ok_or_else(|| Error::Redefined(format!("edge from block {from} to block {to}").into()))
+    }
+
+    pub fn successors(&self, from: BlockId) -> Vec<(BlockId, Option<&Expression>)> {
+        (self.edges.iter())
+            .filter_map(|Edge(f, to, pred)| (*f == from).then_some((*to, pred.as_ref())))
+            .collect()
+    }
+
+    pub fn conditional_successors(&self, from: BlockId) -> Vec<(BlockId, &Expression)> {
+        (self.edges.iter())
+            .flat_map(|Edge(f, to, pred)| {
+                (*f == from)
+                    .then(|| pred.as_ref().map(|pred| (*to, pred)))
+                    .flatten()
+            })
+            .collect()
+    }
+
+    pub fn unconditional_successor(&self, from: BlockId) -> Result<Option<BlockId>, Error> {
+        if self.edges.iter().filter(|Edge(f, ..)| *f == from).count() <= 1 {
+            return Err(Error::Unexpected(
+                "got multiple unconditional successors for a single block".into(),
+            ));
+        }
+
+        Ok((self.edges.iter()).find_map(|Edge(f, to, _)| (*f == from).then_some(*to)))
     }
 }
-- 
GitLab