use alloc::boxed::Box;
use alloc::string::String;
use alloc::vec::Vec;
use crate::compile::CompiledChart;
use crate::datamodel::{
Datamodel, DatamodelRegistry, Environment, EvalError, Event, SessionInfo, StateQuery, Value,
};
use crate::ir::Chart;
use crate::session::{Outcome, StartError, StartInput, start};
pub const NULL_DATAMODEL: &str = "null";
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct NullDatamodel;
impl NullDatamodel {
pub fn new() -> Self {
NullDatamodel
}
}
pub fn parse_in_predicate(expr: &str) -> Option<&str> {
let e = expr.trim();
let inner = e.strip_prefix("In")?.trim_start();
let inner = inner.strip_prefix('(')?.strip_suffix(')')?.trim();
let quote = inner.chars().next()?;
if quote != '\'' && quote != '"' {
return None;
}
let id = inner.strip_prefix(quote)?.strip_suffix(quote)?;
if id.is_empty() || id.contains(quote) {
return None;
}
Some(id)
}
fn unsupported(what: &str) -> EvalError {
EvalError::new(alloc::format!("the null datamodel does not support {what}"))
}
impl Datamodel for NullDatamodel {
fn name(&self) -> &str {
NULL_DATAMODEL
}
fn init_session(&mut self, _info: &SessionInfo) -> Result<(), EvalError> {
Ok(())
}
fn set_environment(&mut self, _env: &Environment) {}
fn declare(&mut self, _id: &str) -> Result<(), EvalError> {
Err(unsupported("data"))
}
fn set_event(&mut self, _event: &Event) {}
fn eval_cond(&mut self, expr: &str, states: &dyn StateQuery) -> Result<bool, EvalError> {
match parse_in_predicate(expr) {
Some(id) => Ok(states.in_state(id)),
None => Err(unsupported("conditions other than In()")),
}
}
fn eval_value(&mut self, _expr: &str, _states: &dyn StateQuery) -> Result<Value, EvalError> {
Err(unsupported("value expressions"))
}
fn eval_content(&mut self, content: &str) -> Result<Value, EvalError> {
Ok(Value::String(String::from(content)))
}
fn assign_expr(
&mut self,
_location: &str,
_expr: &str,
_states: &dyn StateQuery,
) -> Result<(), EvalError> {
Err(unsupported("assign"))
}
fn assign_value(&mut self, _location: &str, _value: Value) -> Result<(), EvalError> {
Err(unsupported("assign"))
}
fn bind_var(&mut self, _name: &str, _value: Value) -> Result<(), EvalError> {
Err(unsupported("variables"))
}
fn is_location(&mut self, _location: &str) -> bool {
false
}
fn exec_script(&mut self, _script: &str, _states: &dyn StateQuery) -> Result<(), EvalError> {
Err(unsupported("script"))
}
fn foreach_items(
&mut self,
_array: &str,
_states: &dyn StateQuery,
) -> Result<Vec<Value>, EvalError> {
Err(unsupported("foreach"))
}
fn snapshot(&self) -> Value {
Value::Undefined
}
fn restore(&mut self, snapshot: &Value) -> Result<(), EvalError> {
match snapshot {
Value::Undefined => Ok(()),
_ => Err(EvalError::new("a null datamodel snapshot is undefined")),
}
}
}
#[derive(Clone, Copy, Debug, Default)]
pub struct NullRegistry;
impl DatamodelRegistry for NullRegistry {
fn create(&self, name: &str) -> Option<Box<dyn Datamodel>> {
if name == NULL_DATAMODEL || name.is_empty() {
Some(Box::new(NullDatamodel))
} else {
None
}
}
}
#[derive(Clone, Debug, Default)]
pub struct WithNull<R>(pub R);
impl<R: DatamodelRegistry> DatamodelRegistry for WithNull<R> {
fn create(&self, name: &str) -> Option<Box<dyn Datamodel>> {
self.0.create(name).or_else(|| NullRegistry.create(name))
}
fn create_for(&self, chart: &Chart) -> Option<Result<Box<dyn Datamodel>, Vec<String>>> {
self.0
.create_for(chart)
.or_else(|| NullRegistry.create_for(chart))
}
fn check(&self, chart: &Chart) -> Result<(), Vec<String>> {
self.0.check(chart)
}
}
pub fn create_datamodel(
registry: &dyn DatamodelRegistry,
name: &str,
) -> Result<Box<dyn Datamodel>, StartError> {
let name = if name.is_empty() {
NULL_DATAMODEL
} else {
name
};
registry
.create(name)
.or_else(|| NullRegistry.create(name))
.ok_or_else(|| StartError::UnsupportedDatamodel {
name: String::from(name),
})
}
pub fn create_datamodel_for(
registry: &dyn DatamodelRegistry,
chart: &Chart,
) -> Result<Box<dyn Datamodel>, StartError> {
if chart.datamodel.is_empty() {
return create_datamodel(registry, &chart.datamodel);
}
match registry.create_for(chart) {
Some(Ok(dm)) => Ok(dm),
Some(Err(problems)) => Err(StartError::ChartRefused {
datamodel: chart.datamodel.clone(),
problems,
}),
None => create_datamodel(registry, &chart.datamodel),
}
}
pub fn start_with_registry(
chart: &CompiledChart,
registry: &dyn DatamodelRegistry,
input: StartInput,
) -> Result<(Outcome, Box<dyn Datamodel>), StartError> {
let mut dm = create_datamodel_for(registry, chart.chart())?;
let outcome = start(chart, input, dm.as_mut())?;
Ok((outcome, dm))
}