1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
//! Adapted from <https://github.com/YarnSpinnerTool/YarnSpinner/blob/da39c7195107d8211f21c263e4084f773b84eaff/YarnSpinner/Dialogue.cs>, which we split off into multiple files
use std::any::Any;
use std::collections::HashMap;
use std::fmt::Debug;
use std::sync::{Arc, RwLock};
use thiserror::Error;
use yarnspinner_core::prelude::*;

#[allow(missing_docs)]
pub type Result<T> = std::result::Result<T, VariableStorageError>;

/// Provides a mechanism for storing and retrieving instances
/// of the [`YarnValue`] type.
///
/// ## Implementation notes
///
/// The interface has been changed to make use of our [`YarnValue`] type,
/// which is more domain specific than the semi-corresponding `Convertible`.
/// We also cannot use generics in this trait because we need to be able to clone this box.
pub trait VariableStorage: Debug + Send + Sync {
    /// Creates a shallow clone of this variable storage, i.e. a clone that
    /// shares the same underlying storage and will thus be perfectly in sync
    /// with the original instance.
    fn clone_shallow(&self) -> Box<dyn VariableStorage>;
    /// Sets the value of a variable. Must fail with a [`VariableStorageError::InvalidVariableName`] if the variable name does not start with a `$`.
    fn set(&mut self, name: String, value: YarnValue) -> Result<()>;
    /// Gets the value of a variable. Must fail with a [`VariableStorageError::InvalidVariableName`] if the variable name does not start with a `$`.
    /// If the variable is not defined, must fail with a [`VariableStorageError::VariableNotFound`].
    fn get(&self, name: &str) -> Result<YarnValue>;
    /// Returns `true` if the variable is defined, `false` otherwise.
    fn contains(&self, name: &str) -> bool {
        self.get(name).is_ok()
    }
    /// Extends this variable storage with the given values. Must fail with a [`VariableStorageError::InvalidVariableName`] if any of the variable names do not start with a `$`.
    /// Existing variables must be overwritten.
    fn extend(&mut self, values: HashMap<String, YarnValue>) -> Result<()>;
    /// Returns a map of all variables in this variable storage.
    fn variables(&self) -> HashMap<String, YarnValue>;
    /// Clears all variables in this variable storage.
    fn clear(&mut self);
    /// Gets the [`VariableStorage`] as a trait object.
    /// This allows retrieving the concrete type by downcasting, using the `downcast_ref` method available through the `Any` trait.
    fn as_any(&self) -> &dyn Any;
    /// Gets the [`VariableStorage`] as a mutable trait object.
    /// This allows retrieving the concrete type by downcasting, using the `downcast_mut` method available through the `Any` trait.
    fn as_any_mut(&mut self) -> &mut dyn Any;
}

impl Extend<(String, YarnValue)> for Box<dyn VariableStorage> {
    fn extend<T: IntoIterator<Item = (String, YarnValue)>>(&mut self, iter: T) {
        let hash_map = iter.into_iter().collect();
        VariableStorage::extend(self.as_mut(), hash_map)
            .unwrap_or_else(|e| panic!("Failed to extend variable storage with values: {e}",));
    }
}

#[allow(missing_docs)]
#[derive(Debug, Error)]
pub enum VariableStorageError {
    #[error("{name} is not a valid variable name: Variable names must start with a '$'. (Did you mean to use '${name}'?)")]
    InvalidVariableName { name: String },
    #[error("Variable name {name} is not defined")]
    VariableNotFound { name: String },
    #[error("Internal variable storage error: {error}")]
    InternalError {
        error: Box<dyn std::error::Error + Send + Sync>,
    },
}

impl Clone for Box<dyn VariableStorage> {
    fn clone(&self) -> Self {
        self.clone_shallow()
    }
}

/// A simple concrete implementation of [`VariableStorage`] that keeps all variables in memory.
#[derive(Debug, Clone, Default)]
pub struct MemoryVariableStorage(Arc<RwLock<HashMap<String, YarnValue>>>);

impl MemoryVariableStorage {
    /// Creates a new empty `MemoryVariableStorage`.
    pub fn new() -> Self {
        Self::default()
    }
}

impl VariableStorage for MemoryVariableStorage {
    fn clone_shallow(&self) -> Box<dyn VariableStorage> {
        Box::new(self.clone())
    }

    fn set(&mut self, name: String, value: YarnValue) -> Result<()> {
        Self::validate_name(&name)?;
        self.0.write().unwrap().insert(name, value);
        Ok(())
    }

    fn get(&self, name: &str) -> Result<YarnValue> {
        Self::validate_name(name)?;
        self.0.read().unwrap().get(name).cloned().ok_or_else(|| {
            VariableStorageError::VariableNotFound {
                name: name.to_string(),
            }
        })
    }

    fn extend(&mut self, values: HashMap<String, YarnValue>) -> Result<()> {
        for name in values.keys() {
            Self::validate_name(name)?;
        }
        self.0.write().unwrap().extend(values);
        Ok(())
    }

    fn variables(&self) -> HashMap<String, YarnValue> {
        self.0.read().unwrap().clone()
    }

    fn clear(&mut self) {
        self.0.write().unwrap().clear();
    }

    fn as_any(&self) -> &dyn Any {
        self
    }

    fn as_any_mut(&mut self) -> &mut dyn Any {
        self
    }
}

impl MemoryVariableStorage {
    fn validate_name(name: impl AsRef<str>) -> Result<()> {
        let name = name.as_ref();
        if name.starts_with('$') {
            Ok(())
        } else {
            Err(VariableStorageError::InvalidVariableName {
                name: name.to_string(),
            })
        }
    }
}