use std::collections::BTreeMap;
use sim_kernel::{Error, Expr, NumberLiteral, Result, Symbol};
use sim_value::kind::expr_kind;
const LIB_NS: &str = "plugin-core";
#[derive(Clone, Debug, Default, PartialEq)]
pub struct PluginState {
params: BTreeMap<u32, f64>,
data: BTreeMap<String, Expr>,
}
impl PluginState {
pub fn new() -> Self {
Self::default()
}
pub fn params(&self) -> &BTreeMap<u32, f64> {
&self.params
}
pub fn data(&self) -> &BTreeMap<String, Expr> {
&self.data
}
pub fn set_param(&mut self, id: u32, value: f64) {
self.params.insert(id, value);
}
pub fn param(&self, id: u32) -> Option<f64> {
self.params.get(&id).copied()
}
pub fn insert_data(&mut self, key: impl Into<String>, value: Expr) {
self.data.insert(key.into(), value);
}
pub fn to_expr(&self) -> Expr {
Expr::Map(vec![
(
field("tag"),
Expr::Symbol(Symbol::qualified(LIB_NS, "state")),
),
(
field("params"),
Expr::Vector(
self.params
.iter()
.map(|(id, value)| {
Expr::Map(vec![
(field("id"), number_u32(*id)),
(field("value"), number_f64(*value)),
])
})
.collect(),
),
),
(
field("data"),
Expr::Vector(
self.data
.iter()
.map(|(key, value)| {
Expr::Map(vec![
(field("key"), Expr::String(key.clone())),
(field("value"), value.clone()),
])
})
.collect(),
),
),
])
}
pub fn from_expr(expr: &Expr) -> Result<Self> {
let map = expr_map(expr, "plugin state")?;
match lookup(map, "tag") {
Some(Expr::Symbol(symbol)) if is_symbol(symbol, LIB_NS, "state") => {}
Some(_) => return Err(Error::Eval("plugin state tag is invalid".to_owned())),
None => return Err(missing("tag")),
}
let mut state = Self::new();
for entry in expr_vector(lookup_required(map, "params")?, "params")? {
let entry = expr_map(entry, "param entry")?;
state.set_param(
expr_u32(lookup_required(entry, "id")?, "param id")?,
expr_f64(lookup_required(entry, "value")?, "param value")?,
);
}
for entry in expr_vector(lookup_required(map, "data")?, "data")? {
let entry = expr_map(entry, "data entry")?;
state.insert_data(
expr_string(lookup_required(entry, "key")?, "data key")?.to_owned(),
lookup_required(entry, "value")?.clone(),
);
}
Ok(state)
}
}
fn field(name: &'static str) -> Expr {
sim_value::build::qsym(LIB_NS, name)
}
fn number_u32(value: u32) -> Expr {
Expr::Number(NumberLiteral {
domain: Symbol::qualified("numbers", "i64"),
canonical: value.to_string(),
})
}
fn number_f64(value: f64) -> Expr {
Expr::Number(NumberLiteral {
domain: Symbol::qualified("numbers", "f64"),
canonical: value.to_string(),
})
}
fn expr_map<'a>(expr: &'a Expr, context: &str) -> Result<&'a [(Expr, Expr)]> {
match expr {
Expr::Map(entries) => Ok(entries),
other => Err(Error::Eval(format!(
"expected {context} map, found {}",
expr_kind(other)
))),
}
}
fn expr_vector<'a>(expr: &'a Expr, context: &str) -> Result<&'a [Expr]> {
match expr {
Expr::Vector(items) => Ok(items),
other => Err(Error::Eval(format!(
"expected {context} vector, found {}",
expr_kind(other)
))),
}
}
fn expr_string<'a>(expr: &'a Expr, context: &str) -> Result<&'a str> {
match expr {
Expr::String(text) => Ok(text),
other => Err(Error::Eval(format!(
"expected {context} string, found {}",
expr_kind(other)
))),
}
}
fn expr_u32(expr: &Expr, context: &str) -> Result<u32> {
let text = number_text(expr, context)?;
text.parse::<u32>()
.map_err(|_| Error::Eval(format!("expected {context} u32 number, found {text}")))
}
fn expr_f64(expr: &Expr, context: &str) -> Result<f64> {
let text = number_text(expr, context)?;
text.parse::<f64>()
.map_err(|_| Error::Eval(format!("expected {context} f64 number, found {text}")))
}
fn number_text<'a>(expr: &'a Expr, context: &str) -> Result<&'a str> {
match expr {
Expr::Number(number) => Ok(number.canonical.as_str()),
Expr::String(text) => Ok(text),
other => Err(Error::Eval(format!(
"expected {context} number, found {}",
expr_kind(other)
))),
}
}
fn lookup_required<'a>(map: &'a [(Expr, Expr)], name: &str) -> Result<&'a Expr> {
lookup(map, name).ok_or_else(|| missing(name))
}
fn lookup<'a>(map: &'a [(Expr, Expr)], name: &str) -> Option<&'a Expr> {
map.iter().find_map(|(key, value)| match key {
Expr::Symbol(symbol) if is_symbol(symbol, LIB_NS, name) => Some(value),
_ => None,
})
}
fn is_symbol(symbol: &Symbol, namespace: &str, name: &str) -> bool {
symbol.namespace.as_deref() == Some(namespace) && symbol.name.as_ref() == name
}
fn missing(field: &str) -> Error {
Error::Eval(format!("plugin state field is missing: {field}"))
}