use alloc::collections::BTreeMap;
use alloc::string::String;
use alloc::vec::Vec;
use crate::bitset::StateSet;
use crate::ir::{
Binding, Block, Chart, Data, DoneData, Expr, Invoke, StateElement, StateKind, TransitionType,
};
use crate::kernel;
use crate::validate::{Flat, Violation, resolve_initial, resolve_root_initial, validate};
pub type StateIdx = usize;
pub type TransIdx = usize;
pub const ROOT: StateIdx = 0;
#[derive(Clone, Debug, PartialEq)]
pub struct CState {
pub id: String,
pub kind: StateKind,
pub parent: Option<StateIdx>,
pub last: StateIdx,
pub children: Vec<StateIdx>,
pub history: Vec<StateIdx>,
pub transitions: Vec<TransIdx>,
pub initial: Option<TransIdx>,
pub onentry: Vec<Block>,
pub onexit: Vec<Block>,
pub data: Vec<Data>,
pub invokes: Vec<Invoke>,
pub donedata: Option<DoneData>,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Domain {
Targetless,
Static(StateIdx),
Dynamic,
}
#[derive(Clone, Debug, PartialEq)]
pub struct CTransition {
pub source: StateIdx,
pub index: Option<usize>,
pub events: Vec<String>,
pub cond: Option<Expr>,
pub targets: Vec<StateIdx>,
pub internal: bool,
pub body: Block,
pub domain: Domain,
}
#[derive(Clone, Debug)]
pub struct CompiledChart {
chart: Chart,
states: Vec<CState>,
transitions: Vec<CTransition>,
by_id: BTreeMap<String, StateIdx>,
}
impl CompiledChart {
pub fn new(chart: Chart) -> Result<Self, Vec<Violation>> {
validate(&chart)?;
Ok(Self::build(chart))
}
fn build(chart: Chart) -> Self {
let flat = Flat::new(&chart);
let n = flat.states.len() + 1;
let mut states: Vec<CState> = Vec::with_capacity(n);
let mut by_id = BTreeMap::new();
states.push(CState {
id: String::new(),
kind: StateKind::Compound,
parent: None,
last: n - 1,
children: Vec::new(),
history: Vec::new(),
transitions: Vec::new(),
initial: None,
onentry: Vec::new(),
onexit: Vec::new(),
data: chart.data.clone(),
invokes: Vec::new(),
donedata: None,
});
for (i, s) in flat.states.iter().enumerate() {
let idx = i + 1;
let parent = flat.parent[i].map(|p| p + 1).unwrap_or(ROOT);
by_id.insert(s.id.clone(), idx);
states.push(CState {
id: s.id.clone(),
kind: s.kind(),
parent: Some(parent),
last: flat.last[i] + 1,
children: Vec::new(),
history: Vec::new(),
transitions: Vec::new(),
initial: None,
onentry: s.onentry.clone(),
onexit: s.onexit.clone(),
data: s.data.clone(),
invokes: s.invokes.clone(),
donedata: s.donedata.clone(),
});
if matches!(s.element, StateElement::History(_)) {
states[parent].history.push(idx);
} else {
states[parent].children.push(idx);
}
}
let resolve = |ids: &[&str]| -> Vec<StateIdx> {
ids.iter()
.filter_map(|id| by_id.get(*id).copied())
.collect()
};
let mut transitions: Vec<CTransition> = Vec::new();
let root_init = resolve_root_initial(&chart);
transitions.push(CTransition {
source: ROOT,
index: None,
events: Vec::new(),
cond: None,
targets: resolve(&root_init.targets),
internal: true,
body: Block::new(),
domain: Domain::Static(ROOT),
});
states[ROOT].initial = Some(0);
for (i, s) in flat.states.iter().enumerate() {
let idx = i + 1;
if s.kind() == StateKind::Compound {
let r = resolve_initial(s);
let t = transitions.len();
transitions.push(CTransition {
source: idx,
index: None,
events: Vec::new(),
cond: None,
targets: resolve(&r.targets),
internal: true,
body: r.body.cloned().unwrap_or_default(),
domain: Domain::Static(idx),
});
states[idx].initial = Some(t);
}
for (ti, tr) in s.transitions.iter().enumerate() {
let t = transitions.len();
transitions.push(CTransition {
source: idx,
index: Some(ti),
events: tr.events.clone(),
cond: tr.cond.clone(),
targets: tr
.targets
.iter()
.filter_map(|id| by_id.get(id.as_str()).copied())
.collect(),
internal: tr.kind == TransitionType::Internal,
body: tr.body.clone(),
domain: Domain::Targetless,
});
states[idx].transitions.push(t);
}
}
for h in 0..states.len() {
if matches!(states[h].kind, StateKind::History(_)) && states[h].transitions.is_empty() {
let p = states[h].parent.unwrap_or(ROOT);
let targets = match states[p].initial {
Some(ti) => transitions[ti].targets.clone(),
None => states[p].children.clone(),
};
let t = transitions.len();
transitions.push(CTransition {
source: h,
index: None,
events: Vec::new(),
cond: None,
targets,
internal: false,
body: Block::new(),
domain: Domain::Targetless,
});
states[h].transitions.push(t);
}
}
let mut cc = CompiledChart {
chart,
states,
transitions,
by_id,
};
let empty = kernel::History::new(cc.states.len());
for t in 0..cc.transitions.len() {
let tr = &cc.transitions[t];
let default_entry =
tr.index.is_none() && !matches!(cc.states[tr.source].kind, StateKind::History(_));
if default_entry {
continue;
}
let domain = if tr.targets.is_empty() {
Domain::Targetless
} else if tr
.targets
.iter()
.any(|s| matches!(cc.states[*s].kind, StateKind::History(_)))
{
Domain::Dynamic
} else {
match kernel::compute_domain(&cc, &empty, t) {
Some(d) => Domain::Static(d),
None => Domain::Targetless,
}
};
cc.transitions[t].domain = domain;
}
cc
}
pub fn chart(&self) -> &Chart {
&self.chart
}
pub fn datamodel(&self) -> &str {
if self.chart.datamodel.is_empty() {
"null"
} else {
&self.chart.datamodel
}
}
pub fn binding(&self) -> Binding {
self.chart.binding
}
pub fn states(&self) -> &[CState] {
&self.states
}
pub fn state(&self, i: StateIdx) -> &CState {
&self.states[i]
}
pub fn transitions(&self) -> &[CTransition] {
&self.transitions
}
pub fn transition(&self, t: TransIdx) -> &CTransition {
&self.transitions[t]
}
pub fn len(&self) -> usize {
self.states.len()
}
pub fn is_empty(&self) -> bool {
self.states.len() <= 1
}
pub fn index_of(&self, id: &str) -> Option<StateIdx> {
self.by_id.get(id).copied()
}
pub fn is_descendant(&self, d: StateIdx, a: StateIdx) -> bool {
a < d && d <= self.states[a].last
}
pub fn default_entry(&self, s: StateIdx) -> &[StateIdx] {
match self.states[s].initial {
Some(t) => &self.transitions[t].targets,
None => &[],
}
}
pub fn empty_set(&self) -> StateSet {
StateSet::new(self.states.len())
}
pub fn ids(&self, set: &StateSet) -> Vec<String> {
set.to_vec()
.into_iter()
.map(|i| self.states[i].id.clone())
.collect()
}
}