1use std::collections::BTreeMap;
2
3use sim_kernel::{Error, Expr, NumberLiteral, Result, Symbol};
4use sim_value::kind::expr_kind;
5
6const LIB_NS: &str = "plugin-core";
7
8#[derive(Clone, Debug, Default, PartialEq)]
15pub struct PluginState {
16 params: BTreeMap<u32, f64>,
17 data: BTreeMap<String, Expr>,
18}
19
20impl PluginState {
21 pub fn new() -> Self {
23 Self::default()
24 }
25
26 pub fn params(&self) -> &BTreeMap<u32, f64> {
28 &self.params
29 }
30
31 pub fn data(&self) -> &BTreeMap<String, Expr> {
33 &self.data
34 }
35
36 pub fn set_param(&mut self, id: u32, value: f64) {
39 self.params.insert(id, value);
40 }
41
42 pub fn param(&self, id: u32) -> Option<f64> {
44 self.params.get(&id).copied()
45 }
46
47 pub fn insert_data(&mut self, key: impl Into<String>, value: Expr) {
49 self.data.insert(key.into(), value);
50 }
51
52 pub fn to_expr(&self) -> Expr {
57 Expr::Map(vec![
58 (
59 field("tag"),
60 Expr::Symbol(Symbol::qualified(LIB_NS, "state")),
61 ),
62 (
63 field("params"),
64 Expr::Vector(
65 self.params
66 .iter()
67 .map(|(id, value)| {
68 Expr::Map(vec![
69 (field("id"), number_u32(*id)),
70 (field("value"), number_f64(*value)),
71 ])
72 })
73 .collect(),
74 ),
75 ),
76 (
77 field("data"),
78 Expr::Vector(
79 self.data
80 .iter()
81 .map(|(key, value)| {
82 Expr::Map(vec![
83 (field("key"), Expr::String(key.clone())),
84 (field("value"), value.clone()),
85 ])
86 })
87 .collect(),
88 ),
89 ),
90 ])
91 }
92
93 pub fn from_expr(expr: &Expr) -> Result<Self> {
100 let map = expr_map(expr, "plugin state")?;
101 match lookup(map, "tag") {
102 Some(Expr::Symbol(symbol)) if is_symbol(symbol, LIB_NS, "state") => {}
103 Some(_) => return Err(Error::Eval("plugin state tag is invalid".to_owned())),
104 None => return Err(missing("tag")),
105 }
106 let mut state = Self::new();
107 for entry in expr_vector(lookup_required(map, "params")?, "params")? {
108 let entry = expr_map(entry, "param entry")?;
109 state.set_param(
110 expr_u32(lookup_required(entry, "id")?, "param id")?,
111 expr_f64(lookup_required(entry, "value")?, "param value")?,
112 );
113 }
114 for entry in expr_vector(lookup_required(map, "data")?, "data")? {
115 let entry = expr_map(entry, "data entry")?;
116 state.insert_data(
117 expr_string(lookup_required(entry, "key")?, "data key")?.to_owned(),
118 lookup_required(entry, "value")?.clone(),
119 );
120 }
121 Ok(state)
122 }
123}
124
125fn field(name: &'static str) -> Expr {
126 sim_value::build::qsym(LIB_NS, name)
127}
128
129fn number_u32(value: u32) -> Expr {
130 Expr::Number(NumberLiteral {
131 domain: Symbol::qualified("numbers", "i64"),
132 canonical: value.to_string(),
133 })
134}
135
136fn number_f64(value: f64) -> Expr {
137 Expr::Number(NumberLiteral {
138 domain: Symbol::qualified("numbers", "f64"),
139 canonical: value.to_string(),
140 })
141}
142
143fn expr_map<'a>(expr: &'a Expr, context: &str) -> Result<&'a [(Expr, Expr)]> {
144 match expr {
145 Expr::Map(entries) => Ok(entries),
146 other => Err(Error::Eval(format!(
147 "expected {context} map, found {}",
148 expr_kind(other)
149 ))),
150 }
151}
152
153fn expr_vector<'a>(expr: &'a Expr, context: &str) -> Result<&'a [Expr]> {
154 match expr {
155 Expr::Vector(items) => Ok(items),
156 other => Err(Error::Eval(format!(
157 "expected {context} vector, found {}",
158 expr_kind(other)
159 ))),
160 }
161}
162
163fn expr_string<'a>(expr: &'a Expr, context: &str) -> Result<&'a str> {
164 match expr {
165 Expr::String(text) => Ok(text),
166 other => Err(Error::Eval(format!(
167 "expected {context} string, found {}",
168 expr_kind(other)
169 ))),
170 }
171}
172
173fn expr_u32(expr: &Expr, context: &str) -> Result<u32> {
174 let text = number_text(expr, context)?;
175 text.parse::<u32>()
176 .map_err(|_| Error::Eval(format!("expected {context} u32 number, found {text}")))
177}
178
179fn expr_f64(expr: &Expr, context: &str) -> Result<f64> {
180 let text = number_text(expr, context)?;
181 text.parse::<f64>()
182 .map_err(|_| Error::Eval(format!("expected {context} f64 number, found {text}")))
183}
184
185fn number_text<'a>(expr: &'a Expr, context: &str) -> Result<&'a str> {
186 match expr {
187 Expr::Number(number) => Ok(number.canonical.as_str()),
188 Expr::String(text) => Ok(text),
189 other => Err(Error::Eval(format!(
190 "expected {context} number, found {}",
191 expr_kind(other)
192 ))),
193 }
194}
195
196fn lookup_required<'a>(map: &'a [(Expr, Expr)], name: &str) -> Result<&'a Expr> {
197 lookup(map, name).ok_or_else(|| missing(name))
198}
199
200fn lookup<'a>(map: &'a [(Expr, Expr)], name: &str) -> Option<&'a Expr> {
201 map.iter().find_map(|(key, value)| match key {
202 Expr::Symbol(symbol) if is_symbol(symbol, LIB_NS, name) => Some(value),
203 _ => None,
204 })
205}
206
207fn is_symbol(symbol: &Symbol, namespace: &str, name: &str) -> bool {
208 symbol.namespace.as_deref() == Some(namespace) && symbol.name.as_ref() == name
209}
210
211fn missing(field: &str) -> Error {
212 Error::Eval(format!("plugin state field is missing: {field}"))
213}