#![allow(unused)]
#[cfg(feature = "std")]
extern crate std;
use core::str::FromStr;
use alloc::string::String;
use dataport::{
AnyPortValue, BindCommons, BoundValueReadGuard, BoundValueWriteGuard, PortCollection, PortCollectionAccessors,
PortCollectionAccessorsCommon, PortCollectionMut, PortCollectionProviderMut, PortList, PortMap, PortVariant,
};
use crate::{Arc, ConstString, Error, RemappingList, RemappingTarget, RwLock, check_local_key, check_top_level_key};
#[derive(Clone, Default)]
#[repr(transparent)]
pub struct Databoard(Arc<RwLock<DataboardInner>>);
enum ShortCircuit {
Assignment { name: ConstString, value: ConstString },
NoParent { name: ConstString, remapped: ConstString },
}
impl From<ShortCircuit> for Error {
fn from(sc: ShortCircuit) -> Self {
match sc {
ShortCircuit::Assignment { name, value } => Self::Assignment { name, value },
ShortCircuit::NoParent { name, remapped } => Self::NoParent { name, remapped },
}
}
}
impl From<ShortCircuit> for dataport::Error {
fn from(sc: ShortCircuit) -> Self {
Error::from(sc).into()
}
}
enum Resolve {
Redirect(Databoard, ConstString),
Local(ConstString),
ShortCircuit(ShortCircuit),
}
enum ResolveMut<'a> {
Redirect(Databoard, ConstString),
Local(ConstString, crate::RwLockWriteGuard<'a, DataboardInner>),
ShortCircuit(ShortCircuit),
}
impl Databoard {
pub fn insert(&mut self, name: ConstString, port: PortVariant) -> Result<(), dataport::Error> {
match self.resolve_mut(&name, false) {
ResolveMut::Redirect(mut board, resolved) => board.insert(resolved, port),
ResolveMut::Local(resolved, mut guard) => guard.insert(resolved, port),
ResolveMut::ShortCircuit(sc) => Err(sc.into()),
}
}
pub fn remove(&mut self, name: &str) -> Result<PortVariant, dataport::Error> {
match self.resolve_mut(name, false) {
ResolveMut::Redirect(mut board, resolved) => board.remove(&resolved),
ResolveMut::Local(resolved, mut guard) => PortCollectionMut::delete(&mut guard.database, &resolved),
ResolveMut::ShortCircuit(sc) => Err(sc.into()),
}
}
#[must_use]
pub fn with(parent: Option<Self>, remappings: Option<RemappingList>, autoremap: bool) -> Self {
Self(Arc::new(RwLock::new(DataboardInner {
database: PortMap::default(),
parent,
remappings: remappings.unwrap_or_default(),
autoremap,
})))
}
#[must_use]
pub fn with_parent(parent: Self) -> Self {
Self(Arc::new(RwLock::new(DataboardInner {
database: PortMap::default(),
parent: Some(parent),
remappings: RemappingList::default(),
autoremap: true,
})))
}
#[cfg(feature = "std")]
pub fn debug_message(&self) {
let _ = self.0.read().parent;
std::println!("not yet implemented");
}
fn root(&self) -> Self {
let mut current = self.clone();
loop {
let next = current.0.read().parent.clone();
match next {
Some(parent) => current = parent,
None => return current,
}
}
}
fn resolve(&self, name: &str, strict_root: bool) -> Resolve {
if let Ok(stripped) = check_top_level_key(name) {
return Resolve::Redirect(self.root(), stripped.into());
}
if let Ok(local) = check_local_key(name) {
return Resolve::Local(local.into());
}
let (target, parent, autoremap) = {
let guard = self.0.read();
(guard.remappings.find(name), guard.parent.clone(), guard.autoremap)
};
match target {
RemappingTarget::BoardPointer(remapped) => match parent {
Some(p) => Resolve::Redirect(p, remapped),
None => Resolve::ShortCircuit(ShortCircuit::NoParent {
name: name.into(),
remapped,
}),
},
RemappingTarget::LocalPointer(remapped) => Resolve::Local(remapped),
RemappingTarget::RootPointer(remapped) => {
if parent.is_some() {
Resolve::Redirect(self.root(), remapped)
} else if strict_root {
Resolve::ShortCircuit(ShortCircuit::NoParent {
name: name.into(),
remapped,
})
} else {
Resolve::Local(remapped)
}
}
RemappingTarget::StringAssignment(value) => Resolve::ShortCircuit(ShortCircuit::Assignment {
name: name.into(),
value,
}),
RemappingTarget::None(original) => {
if autoremap && let Some(parent) = parent {
Resolve::Redirect(parent, original)
} else {
Resolve::Local(original)
}
}
}
}
fn resolve_mut(&self, name: &str, strict_root: bool) -> ResolveMut<'_> {
if let Ok(stripped) = check_top_level_key(name) {
return ResolveMut::Redirect(self.root(), stripped.into());
}
if let Ok(local) = check_local_key(name) {
return ResolveMut::Local(local.into(), self.0.write());
}
let guard = self.0.write();
let target = guard.remappings.find(name);
match target {
RemappingTarget::BoardPointer(remapped) => {
let parent = guard.parent.clone();
drop(guard);
match parent {
Some(p) => ResolveMut::Redirect(p, remapped),
None => ResolveMut::ShortCircuit(ShortCircuit::NoParent {
name: name.into(),
remapped,
}),
}
}
RemappingTarget::LocalPointer(remapped) => ResolveMut::Local(remapped, guard),
RemappingTarget::RootPointer(remapped) => {
if guard.parent.is_some() {
drop(guard);
ResolveMut::Redirect(self.root(), remapped)
} else if strict_root {
ResolveMut::ShortCircuit(ShortCircuit::NoParent {
name: name.into(),
remapped,
})
} else {
ResolveMut::Local(remapped, guard)
}
}
RemappingTarget::StringAssignment(value) => ResolveMut::ShortCircuit(ShortCircuit::Assignment {
name: name.into(),
value,
}),
RemappingTarget::None(original) => {
if guard.autoremap
&& let Some(parent) = guard.parent.clone()
{
drop(guard);
ResolveMut::Redirect(parent, original)
} else {
ResolveMut::Local(original, guard)
}
}
}
}
pub fn add_remappings(&mut self, remappings: RemappingList) -> Result<(), Error> {
let mut guard = self.0.write();
let list = &mut *guard;
let mut remappings = remappings.into_inner();
while let Some(entry) = remappings.pop() {
list.remappings.add_target(entry)?;
}
Ok(())
}
#[must_use]
pub fn remappings(&self) -> RemappingList {
self.0.read().remappings.clone()
}
pub fn entry(&self, name: &str) -> Result<PortVariant, Error> {
match self.resolve(name, false) {
Resolve::Redirect(board, resolved) => board.entry(&resolved),
Resolve::Local(resolved) => self
.0
.read()
.database
.find(&resolved)
.map_or_else(|| Err(Error::NotFound { name: name.into() }), |p| Ok(p.clone())),
Resolve::ShortCircuit(sc) => Err(sc.into()),
}
}
pub fn get_from_str<T: AnyPortValue + FromStr + Clone>(&self, key: &str) -> Result<T, Error> {
match self.get(key) {
Ok(res) => Ok(res),
Err(err) => match err {
dataport::Error::ReturnTypeMismatch { port, value } => {
T::from_str(&value).map_or_else(|_| Err(Error::ConversionFromStr { value, port }), |val| Ok(val))
}
dataport::Error::NotFound { name } => Err(Error::NotFound { name }),
dataport::Error::NoValueSet => Err(Error::NoValue { name: key.into() }),
dataport::Error::DataType => Err(Error::DataType { name: key.into() }),
_ => Err(Error::Unreachable("get_from_str".into(), line!())),
},
}
}
pub fn set_from_str(&mut self, name: &str, value: &str) -> Result<(), dataport::Error> {
match self.resolve_mut(name, false) {
ResolveMut::Redirect(mut board, resolved) => board.set_from_str(&resolved, value),
ResolveMut::Local(resolved, mut guard) => {
if guard.database.contains_name(&resolved) {
guard.database.set_from_str(&resolved, value)
} else {
guard
.database
.add(resolved, PortVariant::create_inoutbound(String::from(value)))
}
}
ResolveMut::ShortCircuit(sc) => Err(sc.into()),
}
}
pub fn set_autoremap(&mut self, autoremap: bool) {
self.0.write().autoremap = autoremap;
}
pub fn create_from_collection(
&mut self,
name: ConstString,
portlist: &dyn PortList,
portlist_name: &str,
) -> Result<(), dataport::Error> {
portlist.find(portlist_name).map_or_else(
|| {
Err(dataport::Error::NotFound {
name: portlist_name.into(),
})
},
|variant| self.insert(name, variant.clone()),
)
}
pub fn create_from_variant(&mut self, name: ConstString, variant: &PortVariant) -> Result<(), dataport::Error> {
self.insert(name, variant.clone())
}
#[allow(clippy::option_if_let_else)]
pub fn add_remapping(&mut self, key: &str, target: &str) -> Result<(), Error> {
let target = target.replace('=', key);
let new_target = RemappingTarget::from_str(&target)?;
let old_target = self.0.read().remappings.find(key);
match old_target {
RemappingTarget::None(_) => self
.0
.write()
.remappings
.add_target((key.into(), new_target)),
_ => {
if old_target == new_target {
Err(Error::AlreadyExists { name: key.into() })
} else {
Err(Error::AlreadyRemapped {
name: key.into(),
remapped: target.into(),
})
}
}
}
}
#[must_use]
pub fn find_origin(&self, name: &str) -> Option<Arc<str>> {
self.remappings().find_origin(name)
}
pub fn with_collection<F, R>(&self, f: F) -> R
where
F: FnOnce(&PortMap) -> R,
{
let guard = self.0.read();
f(&guard.database)
}
pub fn with_collection_mut<F, R>(&self, f: F) -> R
where
F: FnOnce(&mut PortMap) -> R,
{
let mut guard = self.0.write();
f(&mut guard.database)
}
}
impl core::fmt::Debug for Databoard {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
let inner = self.0.read();
write!(f, "Databoard {{ ")?;
write!(f, "autoremap: {:?}", &inner.autoremap)?;
write!(f, ", {:?}", &inner.database)?;
write!(f, ", {:?}", &inner.remappings)?;
write!(f, ", parent: ")?;
if let Some(parent) = &inner.parent {
write!(f, "{parent:?}",)
} else {
write!(f, "None")
}?;
write!(f, " }}")
}
}
impl PortCollectionAccessorsCommon for Databoard {
fn contains_name(&self, key: &str) -> bool {
match self.resolve(key, false) {
Resolve::Redirect(board, resolved) => board.contains_name(&resolved),
Resolve::Local(resolved) => self.0.read().database.contains_name(&resolved),
Resolve::ShortCircuit(_) => false,
}
}
fn give_to_bound(&self, name: &str, bound: &mut dyn BindCommons) -> Result<(), dataport::Error> {
match self.resolve_mut(name, true) {
ResolveMut::Redirect(board, resolved) => board.give_to_bound(&resolved, bound),
ResolveMut::Local(resolved, guard) => guard.database.give_to_bound(&resolved, bound),
ResolveMut::ShortCircuit(sc) => Err(sc.into()),
}
}
fn give_to_collection(
&self,
name: &str,
other_collection: &mut dyn PortCollection,
other_name: &str,
) -> Result<(), dataport::Error> {
other_collection.find_mut(other_name).map_or_else(
|| Err(dataport::Error::OtherNotFound { name: other_name.into() }),
|other| self.give_to_variant(name, other),
)
}
fn give_to_variant(&self, name: &str, variant: &mut PortVariant) -> Result<(), dataport::Error> {
match variant {
PortVariant::InBound(bound) => self.give_to_bound(name, bound),
PortVariant::InOutBound(bound) => self.give_to_bound(name, bound),
PortVariant::OutBound(bound) => self.give_to_bound(name, bound),
}
}
fn sequence_number(&self, name: &str) -> Result<u32, dataport::Error> {
match self.resolve(name, false) {
Resolve::Redirect(board, resolved) => board.sequence_number(&resolved),
Resolve::Local(resolved) => self.0.read().database.sequence_number(&resolved),
Resolve::ShortCircuit(sc) => Err(sc.into()),
}
}
fn use_from_bound(&mut self, name: &str, bound: &dyn BindCommons) -> Result<(), dataport::Error> {
match self.resolve_mut(name, true) {
ResolveMut::Redirect(mut board, resolved) => board.use_from_bound(&resolved, bound),
ResolveMut::Local(resolved, mut guard) => guard.database.use_from_bound(&resolved, bound),
ResolveMut::ShortCircuit(sc) => Err(sc.into()),
}
}
fn use_from_collection(
&mut self,
name: &str,
other_collection: &dyn PortCollection,
other_name: &str,
) -> Result<(), dataport::Error> {
other_collection.find(other_name).map_or_else(
|| Err(dataport::Error::OtherNotFound { name: other_name.into() }),
|other| self.use_from_variant(name, other),
)
}
fn use_from_variant(&mut self, name: &str, variant: &PortVariant) -> Result<(), dataport::Error> {
match variant {
PortVariant::InBound(bound) => self.use_from_bound(name, bound),
PortVariant::InOutBound(bound) => self.use_from_bound(name, bound),
PortVariant::OutBound(bound) => self.use_from_bound(name, bound),
}
}
}
impl PortCollectionAccessors for Databoard {
fn contains<T: AnyPortValue>(&self, key: &str) -> Result<bool, dataport::Error> {
match self.resolve(key, false) {
Resolve::Redirect(board, resolved) => board.contains::<T>(&resolved),
Resolve::Local(resolved) => self.0.read().database.contains::<T>(&resolved),
Resolve::ShortCircuit(sc) => Err(sc.into()),
}
}
fn get<T: AnyPortValue + Clone>(&self, key: &str) -> Result<T, dataport::Error> {
match self.resolve(key, false) {
Resolve::Redirect(board, resolved) => board.get::<T>(&resolved),
Resolve::Local(resolved) => self.0.read().database.get::<T>(&resolved),
Resolve::ShortCircuit(sc) => Err(sc.into()),
}
}
fn read<T: AnyPortValue>(&self, key: &str) -> Result<BoundValueReadGuard<T>, dataport::Error> {
match self.resolve(key, false) {
Resolve::Redirect(board, resolved) => board.read(&resolved),
Resolve::Local(resolved) => self.0.read().database.read(&resolved),
Resolve::ShortCircuit(sc) => Err(sc.into()),
}
}
fn try_read<T: AnyPortValue>(&self, key: &str) -> Result<BoundValueReadGuard<T>, dataport::Error> {
match self.resolve(key, false) {
Resolve::Redirect(board, resolved) => board.try_read(&resolved),
Resolve::Local(resolved) => self.0.read().database.try_read(&resolved),
Resolve::ShortCircuit(sc) => Err(sc.into()),
}
}
fn replace<T: AnyPortValue>(&mut self, name: &str, value: T) -> Result<Option<T>, dataport::Error> {
match self.resolve_mut(name, false) {
ResolveMut::Redirect(mut board, resolved) => board.replace::<T>(&resolved, value),
ResolveMut::Local(resolved, mut guard) => guard.database.replace::<T>(&resolved, value),
ResolveMut::ShortCircuit(sc) => Err(sc.into()),
}
}
fn take<T: AnyPortValue>(&mut self, name: &str) -> Result<Option<T>, dataport::Error> {
match self.resolve_mut(name, false) {
ResolveMut::Redirect(mut board, resolved) => board.take::<T>(&resolved),
ResolveMut::Local(resolved, mut guard) => PortCollectionAccessors::take::<T>(&mut guard.database, &resolved),
ResolveMut::ShortCircuit(sc) => Err(sc.into()),
}
}
fn set<T: AnyPortValue>(&mut self, name: &str, value: T) -> Result<(), dataport::Error> {
match self.resolve_mut(name, false) {
ResolveMut::Redirect(mut board, resolved) => board.set::<T>(&resolved, value),
ResolveMut::Local(resolved, mut guard) => {
if guard.database.contains_name(&resolved) {
guard.database.set::<T>(&resolved, value)
} else {
guard
.database
.add(resolved, PortVariant::create_inoutbound(value))
}
}
ResolveMut::ShortCircuit(sc) => Err(sc.into()),
}
}
fn write<T: AnyPortValue>(&mut self, name: &str) -> Result<BoundValueWriteGuard<T>, dataport::Error> {
match self.resolve_mut(name, false) {
ResolveMut::Redirect(mut board, resolved) => board.write::<T>(&resolved),
ResolveMut::Local(resolved, mut guard) => guard.database.write(&resolved),
ResolveMut::ShortCircuit(sc) => Err(sc.into()),
}
}
fn try_write<T: AnyPortValue>(&mut self, name: &str) -> Result<BoundValueWriteGuard<T>, dataport::Error> {
match self.resolve_mut(name, false) {
ResolveMut::Redirect(mut board, resolved) => board.try_write::<T>(&resolved),
ResolveMut::Local(resolved, mut guard) => guard.database.try_write(&resolved),
ResolveMut::ShortCircuit(sc) => Err(sc.into()),
}
}
}
#[derive(Default)]
struct DataboardInner {
database: PortMap,
parent: Option<Databoard>,
remappings: RemappingList,
autoremap: bool,
}
impl DataboardInner {
fn insert(&mut self, name: ConstString, port: PortVariant) -> Result<(), dataport::Error> {
let io_port = port.to_in_out();
PortMap::add(&mut self.database, name, io_port)
}
}
#[cfg(test)]
mod tests {
use super::*;
const fn is_normal<T: Sized + Send + Sync>() {}
#[test]
const fn normal_types() {
is_normal::<&DataboardInner>();
is_normal::<DataboardInner>();
is_normal::<&Databoard>();
is_normal::<Databoard>();
}
#[test]
fn resolve_root_pointer_strict_no_parent() {
let mut remappings = RemappingList::default();
remappings.push(("alias".into(), RemappingTarget::RootPointer("rkey".into())));
let db = Databoard::with(None, Some(remappings), false);
let results = [
db.resolve("alias", true),
db.resolve("alias", false),
];
let mut got_no_parent = false;
for result in results {
if let Resolve::ShortCircuit(ShortCircuit::NoParent { .. }) = result {
got_no_parent = true;
}
}
assert!(got_no_parent);
}
}