From ba8ffa0d8607bfe84a132da69a8b865b24232d9c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Moritz=20H=C3=B6lting?=
 <87192362+moritz-hoelting@users.noreply.github.com>
Date: Wed, 12 Feb 2025 14:09:02 +0100
Subject: [PATCH] implement custom serialize for Span

---
 src/base/source_file.rs |  2 +-
 src/lib.rs              |  6 +++
 src/serde.rs            | 93 +++++++++++++++++++++++++++++++++++++++++
 3 files changed, 100 insertions(+), 1 deletion(-)
 create mode 100644 src/serde.rs

diff --git a/src/base/source_file.rs b/src/base/source_file.rs
index 369e210..a19d576 100644
--- a/src/base/source_file.rs
+++ b/src/base/source_file.rs
@@ -145,7 +145,7 @@ impl SourceFile {
 }
 
 /// Represents a range of characters in a source file.
-#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
+#[cfg_attr(feature = "serde", derive(serde::Deserialize))]
 #[derive(Clone, Getters, CopyGetters)]
 pub struct Span {
     /// Get the start byte index of the span.
diff --git a/src/lib.rs b/src/lib.rs
index 9e530a3..0472ae1 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -20,6 +20,12 @@ pub mod lexical;
 pub mod syntax;
 pub mod transpile;
 
+#[cfg(feature = "serde")]
+mod serde;
+#[cfg(feature = "serde")]
+#[cfg_attr(feature = "serde", doc(inline))]
+pub use serde::SerdeWrapper;
+
 use std::path::Path;
 
 use base::{source_file::SourceFile, Error, FileProvider, Handler, Result};
diff --git a/src/serde.rs b/src/serde.rs
new file mode 100644
index 0000000..d09f070
--- /dev/null
+++ b/src/serde.rs
@@ -0,0 +1,93 @@
+//! Utilities for (de-)serializing
+
+use std::{
+    collections::HashMap,
+    sync::{Arc, LazyLock, Mutex, RwLock},
+};
+
+use serde::{de::{self, Visitor}, ser::SerializeStruct, Deserialize, Serialize};
+
+use crate::base::source_file::{SourceFile, Span};
+
+static SERIALIZE_DATA: LazyLock<Mutex<SerializeData>> =
+    LazyLock::new(|| Mutex::new(SerializeData::default()));
+
+static DEDUPLICATE_SOURCE_FILES: LazyLock<RwLock<bool>> = LazyLock::new(|| RwLock::new(false));
+
+/// Wrapper to remove duplicate source file data during (de-)serialization
+#[derive(Debug)]
+pub struct SerdeWrapper<T>(pub T);
+
+impl<T> Serialize for SerdeWrapper<T>
+where
+    T: Serialize,
+{
+    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+    where
+        S: serde::Serializer,
+    {
+        *DEDUPLICATE_SOURCE_FILES.write().unwrap() = true;
+        SERIALIZE_DATA.lock().unwrap().clear();
+        let mut s = serializer.serialize_struct("SourceFileWrapper", 2)?;
+        s.serialize_field("data", &self.0)?;
+        *DEDUPLICATE_SOURCE_FILES.write().unwrap() = false;
+        s.serialize_field(
+            "source_files",
+            &SERIALIZE_DATA.lock().unwrap().id_to_source_file,
+        )?;
+        s.end()
+    }
+}
+
+/// Internally used for Serialization
+#[derive(Debug, Default)]
+struct SerializeData {
+    id_counter: usize,
+    ptr_to_id: HashMap<usize, usize>,
+    id_to_source_file: HashMap<usize, SourceFile>,
+}
+
+impl SerializeData {
+    fn clear(&mut self) {
+        self.id_counter = 0;
+        self.id_to_source_file.clear();
+        self.ptr_to_id.clear();
+    }
+
+    /// Get id of already stored [`Arc`] or store it and return new id
+    pub fn get_id_of(&mut self, source_file: &Arc<SourceFile>) -> usize {
+        let ptr = Arc::as_ptr(source_file);
+        if let Some(&id) = self.ptr_to_id.get(&(ptr as usize)) {
+            id
+        } else {
+            let id = self.id_counter;
+            self.id_counter += 1;
+
+            self.ptr_to_id.insert(ptr as usize, id);
+            self.id_to_source_file
+                .insert(id, Arc::unwrap_or_clone(source_file.to_owned()));
+
+            id
+        }
+    }
+}
+
+impl Serialize for Span {
+    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+    where
+        S: serde::Serializer,
+    {
+        let mut s = serializer.serialize_struct("Span", 3)?;
+        s.serialize_field("start", &self.start())?;
+        s.serialize_field("end", &self.end())?;
+
+        if *DEDUPLICATE_SOURCE_FILES.read().unwrap() {
+            let mut data = SERIALIZE_DATA.lock().unwrap();
+            s.serialize_field("source_file", &data.get_id_of(self.source_file()))?;
+        } else {
+            s.serialize_field("source_file", self.source_file())?;
+        }
+
+        s.end()
+    }
+}