databoard 0.4.0

Provides a hierarchical key-value-store
Documentation
// Copyright © 2025 Stephan Kunz
//! tinyscript Environment implementation for databoard
extern crate alloc;

use alloc::string::{String, ToString};

use core::str::FromStr;

use dataport::{PortCollectionAccessors, PortCollectionAccessorsCommon};
use tinyscript::{Environment, ScriptingValue};

use crate::{Error, databoard::Databoard};

impl Environment for Databoard {
	fn define_env(&mut self, name: &str, value: impl Into<ScriptingValue>) -> Result<(), tinyscript::environment::Error> {
		if self.contains_name(name) {
			self.set_env(name, value)
		} else {
			let value = value.into();
			match value {
				// this is unreachable
				ScriptingValue::Nil() => {
					return Err(tinyscript::environment::Error::EnvVarUnknownType { name: name.into() });
				}
				ScriptingValue::Boolean(b) => match self.set(name, b) {
					Ok(()) => {}
					Err(cause) => {
						return Err(tinyscript::environment::Error::EnvVarSet {
							name: name.into(),
							cause: cause.to_string().into(),
						});
					}
				},
				ScriptingValue::Float64(f) => match self.set(name, f) {
					Ok(()) => {}
					Err(cause) => {
						return Err(tinyscript::environment::Error::EnvVarSet {
							name: name.into(),
							cause: cause.to_string().into(),
						});
					}
				},
				ScriptingValue::Int64(i) => match self.set(name, i) {
					Ok(()) => {}
					Err(cause) => {
						return Err(tinyscript::environment::Error::EnvVarSet {
							name: name.into(),
							cause: cause.to_string().into(),
						});
					}
				},
				ScriptingValue::String(s) => match self.set_from_str(name, &s) {
					Ok(()) => {}
					Err(cause) => {
						return Err(tinyscript::environment::Error::EnvVarSet {
							name: name.into(),
							cause: cause.to_string().into(),
						});
					}
				},
			}
			Ok(())
		}
	}

	// Option::map_or_else would be unreadable!
	#[allow(clippy::option_if_let_else)]
	fn get_env(&self, name: &str) -> Result<ScriptingValue, tinyscript::environment::Error> {
		self.entry(name).map_or_else(
			|err| match err {
				Error::Assignment { name: _, value } => i64::from_str(&value).map_or_else(
					|_| {
						f64::from_str(&value).map_or_else(
							|_| {
								bool::from_str(&value).map_or_else(
									|_| Ok(ScriptingValue::String(value.to_string())),
									|b| Ok(ScriptingValue::Boolean(b)),
								)
							},
							|f| Ok(ScriptingValue::Float64(f)),
						)
					},
					|i| Ok(ScriptingValue::Int64(i)),
				),
				_ => Err(tinyscript::environment::Error::EnvVarNotDefined { name: name.into() }),
			},
			|entry| {
				if let Some(s) = entry.get::<String>().unwrap_or_default() {
					Ok(ScriptingValue::String(s))
				} else if let Some(b) = entry.get::<bool>().unwrap_or_default() {
					Ok(ScriptingValue::Boolean(b))
				} else if let Some(f) = entry.get::<f64>().unwrap_or_default() {
					Ok(ScriptingValue::Float64(f))
				} else if let Some(f) = entry.get::<f32>().unwrap_or_default() {
					Ok(ScriptingValue::Float64(f64::from(f)))
				} else if let Some(u) = entry.get::<u64>().unwrap_or_default() {
					if u < i64::MAX as u64 {
						#[allow(clippy::cast_possible_wrap)]
						Ok(ScriptingValue::Int64(u as i64))
					} else {
						Err(tinyscript::environment::Error::EnvVarExceedsLimits { name: name.into() })
					}
				} else if let Some(i) = entry.get::<i64>().unwrap_or_default() {
					Ok(ScriptingValue::Int64(i))
				} else if let Some(u) = entry.get::<u32>().unwrap_or_default() {
					Ok(ScriptingValue::Int64(i64::from(u)))
				} else if let Some(i) = entry.get::<i32>().unwrap_or_default() {
					Ok(ScriptingValue::Int64(i64::from(i)))
				} else if let Some(u) = entry.get::<u16>().unwrap_or_default() {
					Ok(ScriptingValue::Int64(i64::from(u)))
				} else if let Some(i) = entry.get::<i16>().unwrap_or_default() {
					Ok(ScriptingValue::Int64(i64::from(i)))
				} else if let Some(u) = entry.get::<u8>().unwrap_or_default() {
					Ok(ScriptingValue::Int64(i64::from(u)))
				} else if let Some(i) = entry.get::<i8>().unwrap_or_default() {
					Ok(ScriptingValue::Int64(i64::from(i)))
				} else {
					Err(tinyscript::environment::Error::EnvVarUnknownType { name: name.into() })
				}
			},
		)
	}

	fn set_env(&mut self, name: &str, value: impl Into<ScriptingValue>) -> Result<(), tinyscript::environment::Error> {
		if !self.contains_name(name) {
			return Err(tinyscript::environment::Error::EnvVarNotDefined { name: name.into() });
		}
		let value = value.into();
		match value {
			// this is unreachable
			ScriptingValue::Nil() => Err(tinyscript::environment::Error::EnvVarUnknownType { name: name.into() }),
			ScriptingValue::Boolean(b) => match self.set::<bool>(name, b) {
				Ok(()) => Ok(()),
				Err(err) => Err(tinyscript::environment::Error::EnvVarSet {
					name: name.into(),
					cause: err.to_string().into(),
				}),
			},
			ScriptingValue::Float64(f) => match self.set::<f64>(name, f) {
				Ok(()) => Ok(()),
				Err(_) => {
					if f < f64::from(f32::MAX) {
						#[allow(clippy::cast_possible_truncation)]
						match self.set::<f32>(name, f as f32) {
							Ok(()) => Ok(()),
							Err(_err) => {
								// We may have an uninitialized value defined different than Float.
								// This happens with ScriptingEnums and remappings.
								let str_val = f.to_string();
								if self.set_from_str(name, &str_val).is_ok() {
									Ok(())
								} else {
									Err(tinyscript::environment::Error::EnvVarWrongType { name: name.into() })
								}

								// Err(tinyscript::environment::Error::EnvVarSet {
								// 	name: name.into(),
								// 	cause: err.to_string().into(),
								// })
							}
						}
					} else {
						Err(tinyscript::environment::Error::EnvVarExceedsLimits { name: name.into() })
					}
				}
			},
			ScriptingValue::Int64(i_raw) => {
				if self.set::<i64>(name, i_raw).is_ok() {
					Ok(())
				} else if let Ok(i) = i32::try_from(i_raw)
					&& self.set::<i32>(name, i).is_ok()
				{
					Ok(())
				} else if let Ok(i) = i16::try_from(i_raw)
					&& self.set::<i16>(name, i).is_ok()
				{
					Ok(())
				} else if let Ok(i) = i8::try_from(i_raw)
					&& self.set::<i8>(name, i).is_ok()
				{
					Ok(())
				} else if i_raw > 0 {
					if let Ok(i) = u64::try_from(i_raw)
						&& self.set::<u64>(name, i).is_ok()
					{
						Ok(())
					} else if let Ok(i) = u32::try_from(i_raw)
						&& self.set::<u32>(name, i).is_ok()
					{
						Ok(())
					} else if let Ok(i) = u16::try_from(i_raw)
						&& self.set::<u16>(name, i).is_ok()
					{
						Ok(())
					} else if let Ok(i) = u8::try_from(i_raw)
						&& self.set::<u8>(name, i).is_ok()
					{
						Ok(())
					} else {
						// We may have an uninitialized value defined different than Integer.
						// This happens with ScriptingEnums and remappings.
						let str_val = i_raw.to_string();
						if self.set_from_str(name, &str_val).is_ok() {
							Ok(())
						} else {
							Err(tinyscript::environment::Error::EnvVarWrongType { name: name.into() })
						}
					}
				} else {
					// We may have an uninitialized value defined different than Integer.
					// This happens with ScriptingEnums and remappings.
					let str_val = i_raw.to_string();
					if self.set_from_str(name, &str_val).is_ok() {
						Ok(())
					} else {
						Err(tinyscript::environment::Error::EnvVarWrongType { name: name.into() })
					}
				}
			}
			ScriptingValue::String(s) => match self.set_from_str(name, &s) {
				Ok(()) => Ok(()),
				Err(_) => match self.set(name, s) {
					Ok(()) => Ok(()),
					Err(err) => Err(tinyscript::environment::Error::EnvVarSet {
						name: name.into(),
						cause: err.to_string().into(),
					}),
				},
			},
		}
	}
}