use super::Expansion;
use super::Quirk;
use super::Value;
use crate::source::Location;
use std::ops::Deref;
use thiserror::Error;
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct Variable {
pub value: Option<Value>,
pub last_assigned_location: Option<Location>,
pub is_exported: bool,
pub read_only_location: Option<Location>,
pub quirk: Option<Quirk>,
}
impl Variable {
#[must_use]
pub fn new<S: Into<String>>(value: S) -> Self {
Variable {
value: Some(Value::scalar(value)),
..Default::default()
}
}
#[must_use]
pub fn new_array<I, S>(values: I) -> Self
where
I: IntoIterator<Item = S>,
S: Into<String>,
{
Variable {
value: Some(Value::array(values)),
..Default::default()
}
}
#[must_use]
pub fn new_empty_array() -> Self {
Self::new_array([] as [&str; 0])
}
#[inline]
#[must_use]
pub fn set_assigned_location(mut self, location: Location) -> Self {
self.last_assigned_location = Some(location);
self
}
#[inline]
#[must_use]
pub fn export(mut self) -> Self {
self.is_exported = true;
self
}
#[inline]
#[must_use]
pub fn make_read_only(mut self, location: Location) -> Self {
self.read_only_location = Some(location);
self
}
#[must_use]
pub const fn is_read_only(&self) -> bool {
self.read_only_location.is_some()
}
#[inline]
pub fn expand(&self, location: &Location) -> Expansion<'_> {
super::quirk::expand(self, location)
}
}
#[derive(Debug, Eq, PartialEq)]
pub struct VariableRefMut<'a>(&'a mut Variable);
#[derive(Clone, Debug, Eq, Error, PartialEq)]
#[error("cannot assign to read-only variable")]
pub struct AssignError {
pub new_value: Value,
pub assigned_location: Option<Location>,
pub read_only_location: Location,
}
impl<'a> From<&'a mut Variable> for VariableRefMut<'a> {
fn from(variable: &'a mut Variable) -> Self {
VariableRefMut(variable)
}
}
impl Deref for VariableRefMut<'_> {
type Target = Variable;
fn deref(&self) -> &Variable {
self.0
}
}
impl VariableRefMut<'_> {
#[inline]
pub fn assign<V: Into<Value>, L: Into<Option<Location>>>(
&mut self,
value: V,
location: L,
) -> Result<(Option<Value>, Option<Location>), AssignError> {
self.assign_impl(value.into(), location.into())
}
fn assign_impl(
&mut self,
value: Value,
location: Option<Location>,
) -> Result<(Option<Value>, Option<Location>), AssignError> {
if let Some(read_only_location) = self.0.read_only_location.clone() {
return Err(AssignError {
new_value: value,
assigned_location: location,
read_only_location,
});
}
let old_value = self.0.value.replace(value);
let old_location = std::mem::replace(&mut self.0.last_assigned_location, location);
Ok((old_value, old_location))
}
pub fn export(&mut self, is_exported: bool) {
self.0.is_exported = is_exported;
}
pub fn make_read_only(&mut self, location: Location) {
self.0.read_only_location.get_or_insert(location);
}
pub fn set_quirk(&mut self, quirk: Option<Quirk>) {
self.0.quirk = quirk;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn assigning_values() {
let mut var = Variable::default();
let mut var = VariableRefMut::from(&mut var);
let result = var.assign(Value::scalar("foo value"), None);
assert_eq!(result, Ok((None, None)));
assert_eq!(*var, Variable::new("foo value"));
let location = Location::dummy("bar location");
let result = var.assign(Value::scalar("bar value"), location.clone());
assert_eq!(result, Ok((Some(Value::scalar("foo value")), None)));
assert_eq!(var.value, Some(Value::scalar("bar value")));
assert_eq!(var.last_assigned_location.as_ref(), Some(&location));
assert_eq!(
var.assign(Value::array(["a", "b", "c"]), None),
Ok((Some(Value::scalar("bar value")), Some(location))),
);
assert_eq!(var.value, Some(Value::array(["a", "b", "c"])));
}
#[test]
fn exporting() {
let mut var = Variable::default();
let mut var = VariableRefMut::from(&mut var);
assert!(!var.is_exported);
var.export(true);
assert!(var.is_exported);
var.export(false);
assert!(!var.is_exported);
}
#[test]
fn making_variables_read_only() {
let mut var = Variable::default();
let mut var = VariableRefMut::from(&mut var);
let location = Location::dummy("read-only location");
var.make_read_only(location.clone());
assert_eq!(var.read_only_location.as_ref(), Some(&location));
var.make_read_only(Location::dummy("ignored location"));
assert_eq!(var.read_only_location.as_ref(), Some(&location));
}
#[test]
fn assigning_to_readonly_variable() {
let mut var = Variable::default();
let mut var = VariableRefMut::from(&mut var);
let assigned_location = Some(Location::dummy("assigned location"));
let read_only_location = Location::dummy("read-only location");
var.make_read_only(read_only_location.clone());
assert_eq!(
var.assign(Value::scalar("foo value"), assigned_location.clone()),
Err(AssignError {
new_value: Value::scalar("foo value"),
assigned_location,
read_only_location,
})
)
}
}