tinyscript 0.6.0

Tiny, C-like scripting language.
Documentation
// Copyright © 2025 Stephan Kunz
//! A tratt to work with the outside world and a default implementation.

use alloc::{
	collections::btree_map::BTreeMap,
	string::{String, ToString},
};
use thiserror::Error;

use crate::{ConstString, RwLock, scripting_value::ScriptingValue};

/// The trait for providing an [`Environment`] to a [`VM`](crate::execution::VM)
/// that stores the [`ScriptingValue`]s persistent and external available.
///
/// An environment must be a key-value-store that can store [`ScriptingValue`]s.
pub trait Environment: Send + Sync {
	/// Creates or updates the [`ScriptingValue`] behind `key`.
	/// Value will be created if it does not already exist.
	/// # Errors
	/// [`Error::EnvVarWrongType`] if the variable exists with a different type.
	fn define_env(&mut self, key: &str, value: impl Into<ScriptingValue>) -> Result<(), Error>;

	/// Returns the [`ScriptingValue`] stored behind `key`.
	/// # Errors
	/// [`Error::EnvVarNotDefined`] if the variable does not exist
	fn get_env(&self, key: &str) -> Result<ScriptingValue, Error>;

	/// Set the variable with `key` to `value`.
	/// # Errors
	/// if variable does not exist.
	fn set_env(&mut self, key: &str, value: impl Into<ScriptingValue>) -> Result<(), Error>;
}

/// Errors that can happen when interacting with an [`Environment`].
#[derive(Error, Debug)]
pub enum Error {
	/// A variable exceeds the limits of its type.
	#[error("the environment variable {name} exceeds the limits of its defined type")]
	EnvVarExceedsLimits {
		/// Name of the variable
		name: ConstString,
	},
	/// A variable has not been defined/created in the [`Environment`].
	#[error("the environment variable {name} has not been defined")]
	EnvVarNotDefined {
		/// Name of the variable
		name: ConstString,
	},
	/// A variable has an unkown type.
	#[error("the environment variable {name} has an unknown type")]
	EnvVarUnknownType {
		/// Name of the variable
		name: ConstString,
	},
	/// A variable has a different type than in the [`Environment`].
	#[error("the type of the environment variable {name} does not match its former definition")]
	EnvVarWrongType {
		/// Name of the variable
		name: ConstString,
	},

	/// An external error when setting the variable.
	#[error("setting environment variable {name} failed: {cause}")]
	EnvVarSet {
		/// Name of the variable
		name: ConstString,
		/// Cause of error
		cause: ConstString,
	},

	/// An error casting the type of the variable.
	#[error("cast of variable {name} to {var_type} failed")]
	EnvVarTypeCast {
		/// Name of the variable
		name: ConstString,
		/// Expected type ofthe variable
		var_type: ConstString,
	},
}

/// A very simple default Environment for testing purpose and the REPL.
#[derive(Debug, Default)]
pub struct DefaultEnvironment {
	storage: RwLock<BTreeMap<String, ScriptingValue>>,
}

impl Environment for DefaultEnvironment {
	fn define_env(&mut self, key: &str, value: impl Into<ScriptingValue>) -> Result<(), Error> {
		self.storage
			.write()
			.insert(key.to_string(), value.into());
		Ok(())
	}

	fn get_env(&self, key: &str) -> Result<ScriptingValue, Error> {
		self.storage.read().get(key).map_or_else(
			|| Err(Error::EnvVarNotDefined { name: key.into() }),
			|value| Ok(value.clone()),
		)
	}

	fn set_env(&mut self, key: &str, value: impl Into<ScriptingValue>) -> Result<(), Error> {
		if self.storage.read().contains_key(key) {
			self.storage
				.write()
				.insert(key.to_string(), value.into());
			Ok(())
		} else {
			Err(Error::EnvVarNotDefined { name: key.into() })
		}
	}
}