Skip to main content

mech_interpreter/
state_machines.rs

1use crate::tracing::{
2    format_fsm_trace, summarize_guard_condition, summarize_pattern, summarize_value,
3};
4use crate::*;
5use crate::patterns::*;
6use std::collections::HashSet;
7
8// Finite State Machines
9// ----------------------------------------------------------------------------
10
11// Review: how does this fail?
12pub fn register_fsm_implementation(fsm: &FsmImplementation, p: &Interpreter) -> MResult<()> {
13  let fsm_id = fsm.name.hash();
14  p.user_state_machines
15    .borrow_mut()
16    .insert(fsm_id, fsm.clone());
17  p.state
18    .borrow()
19    .dictionary
20    .borrow_mut()
21    .insert(fsm_id, fsm.name.to_string());
22  Ok(())
23}
24
25pub fn execute_fsm_pipe(fsm_pipe: &FsmPipe, env: Option<&Environment>, p: &Interpreter) -> MResult<Value> {
26  let fsm_id = fsm_pipe.start.name.hash();
27  let fsm = {
28    let fsms = p.user_state_machines.borrow();
29    fsms.get(&fsm_id).cloned()
30  }
31  .ok_or_else(|| {
32    MechError::new(
33      MissingFsmError {
34        fsm_id,
35      },
36      None,
37    )
38    .with_compiler_loc()
39    .with_tokens(fsm_pipe.start.tokens())
40  })?;
41
42  let mut call_env = Environment::new();
43  let mut args = Vec::new();
44  if let Some(start_args) = &fsm_pipe.start.args {
45    for (_, arg_expr) in start_args {
46      args.push(expression(arg_expr, env, p)?);
47    }
48  }
49  if fsm.input.len() != args.len() {
50    return Err(MechError::new(
51      IncorrectNumberOfArguments {
52        expected: fsm.input.len(),
53        found: args.len(),
54      },
55      None,
56    )
57    .with_compiler_loc()
58    .with_tokens(fsm_pipe.start.tokens()));
59  }
60  for (arg_decl, arg_value) in fsm.input.iter().zip(args.iter()) {
61    let detached_arg = detach_value(arg_value);
62    #[cfg(feature = "kind_annotation")]
63    if let Some(kind_annotation_node) = &arg_decl.kind {
64      let expected_kind = kind_annotation(&kind_annotation_node.kind, p)?
65          .to_value_kind(&p.state.borrow().kinds)?;
66      let actual_kind = arg_value.kind();
67      let converted_arg = detached_arg.convert_to(&expected_kind);
68      if actual_kind != expected_kind && converted_arg.is_none() {
69        return Err(MechError::new(
70          FsmArgumentKindMismatchError {
71            argument: arg_decl.name.to_string(),
72            expected_kind,
73            actual_kind,
74          },
75          None,
76        )
77        .with_compiler_loc()
78        .with_tokens(fsm_pipe.start.tokens()));
79      }
80      call_env.insert(arg_decl.name.hash(), converted_arg.unwrap_or(detached_arg));
81      continue;
82    }
83    call_env.insert(arg_decl.name.hash(), detached_arg);
84  }
85  let mut state = pattern_to_value(&fsm.start, &call_env, p)?;
86  validate_fsm_state_coverage(&fsm, fsm_pipe)?;
87  execute_fsm_pipe_impl(&fsm, &mut state, &mut call_env, p)
88}
89
90fn execute_fsm_pipe_impl(fsm: &FsmImplementation, state: &mut Value, call_env: &mut Environment, p: &Interpreter) -> MResult<Value> {
91  trace_println!(
92    p,
93    "{}",
94    format_fsm_trace(
95      "start",
96      format!(
97        "name={} state={}",
98        fsm.name.to_string(),
99        summarize_value(state)
100      )
101    )
102  );
103  // Step through the FSM, applying transitions until we hit a terminal state (no applicable transitions) or exceed the step limit.
104  for step in 0..p.max_steps {
105    trace_println!(
106      p,
107      "{}",
108      format_fsm_trace(
109        "step",
110        format!("{step:>4} state={}", summarize_value(state))
111      )
112    );
113    let mut transitioned = false;
114    for (arm_idx, arm) in fsm.arms.iter().enumerate() {
115      match arm {
116        FsmArm::Comment(_) => continue,
117        FsmArm::Transition(pattern, transitions) => {
118          let mut arm_env = call_env.clone();
119          clear_pattern_bindings(pattern, &mut arm_env);
120          let matched = pattern_matches_value(pattern, state, &mut arm_env, p)?;
121          trace_println!(
122            p,
123            "{}",
124            format_fsm_trace(
125              "arm",
126              format!(
127                "[{arm_idx}] check transition pattern={} {}",
128                summarize_pattern(pattern),
129                if matched { "✓" } else { "✗" }
130              )
131            )
132          );
133          if matched {
134            let previous_state = summarize_value(state);
135            let out = apply_transitions(transitions, state, &mut arm_env, p)?;
136            *call_env = arm_env;
137            if let Some(value) = out {
138              trace_println!(
139                p,
140                "{}",
141                format_fsm_trace(
142                  "output",
143                  format!("value={}", summarize_value(&value))
144                )
145              );
146              return Ok(value);
147            }
148            trace_println!(
149              p,
150              "{}",
151              format_fsm_trace(
152                "transition",
153                format!(
154                  "arm[{arm_idx}] {} -> {}",
155                  previous_state,
156                  summarize_value(state)
157                )
158              )
159            );
160            transitioned = true;
161            break;
162          }
163        }
164        FsmArm::Guard(pattern, guards) => {
165          let mut arm_env = call_env.clone();
166          clear_pattern_bindings(pattern, &mut arm_env);
167          let pattern_matched = pattern_matches_value(pattern, state, &mut arm_env, p)?;
168          trace_println!(
169            p,
170            "{}",
171            format_fsm_trace(
172              "arm",
173              format!(
174                "[{arm_idx}] check guard pattern={} {}",
175                summarize_pattern(pattern),
176                if pattern_matched { "✓" } else { "✗" }
177              )
178            )
179          );
180          if !pattern_matched {
181            continue;
182          }
183          for (guard_idx, guard) in guards.iter().enumerate() {
184            let guard_passes = match &guard.condition {
185              Pattern::Wildcard => true,
186              _ => {
187                let cond = pattern_to_value(&guard.condition,&arm_env,p)?;
188                match cond {
189                  Value::Bool(x) => *x.borrow(),
190                  other => {
191                    return Err(MechError::new(
192                      FsmGuardConditionKindMismatchError {
193                        arm_index: arm_idx,
194                        guard_index: guard_idx,
195                        actual_kind: other.kind(),
196                      },
197                      None,
198                    )
199                    .with_compiler_loc());
200                  }
201                }
202              }
203            };
204            trace_println!(
205              p,
206              "{}",
207              format_fsm_trace(
208                "guard",
209                format!(
210                  "arm[{arm_idx}] check guard[{guard_idx}] condition={} {}",
211                  summarize_guard_condition(&guard.condition),
212                  if guard_passes { "✓" } else { "✗" }
213                )
214              )
215            );
216            if !guard_passes {
217              continue;
218            }
219            let previous_state = summarize_value(state);
220            let out = apply_transitions(&guard.transitions, state, &mut arm_env, p)?;
221            *call_env = arm_env;
222            if let Some(value) = out {
223              trace_println!(
224                p,
225                "{}",
226                format_fsm_trace(
227                  "output",
228                  format!("value={}", summarize_value(&value))
229                )
230              );
231              return Ok(value);
232            }
233            trace_println!(
234              p,
235              "{}",
236              format_fsm_trace(
237                "transition",
238                format!(
239                  "arm[{arm_idx}] {} -> {}",
240                  previous_state,
241                  summarize_value(state)
242                )
243              )
244            );
245            transitioned = true;
246            break;
247          }
248          if transitioned {
249            break;
250          }
251        }
252      }
253    }
254    if !transitioned {
255      trace_println!(
256        p,
257        "{}",
258        format_fsm_trace("halt", format!("state={}", summarize_value(state)))
259      );
260      return Ok(state.clone());
261    }
262  }
263  Err(MechError::new(
264    FsmExceededTransitionLimitError {
265      max_transitions: p.max_steps,
266    },
267    None,
268  )
269  .with_compiler_loc())
270}
271
272fn validate_fsm_state_coverage(fsm: &FsmImplementation, fsm_pipe: &FsmPipe) -> MResult<()> {
273  let state_names: HashSet<String> = fsm
274    .arms
275    .iter()
276    .filter_map(|arm| {
277      let pattern = match arm {
278        FsmArm::Guard(pattern, _) | FsmArm::Transition(pattern, _) => pattern,
279        FsmArm::Comment(_) => return None,
280      };
281      state_name_from_pattern(pattern)
282    })
283    .collect();
284  if state_names.is_empty() {
285    return Ok(());
286  }
287
288  let start_state = state_name_from_pattern(&fsm.start).ok_or_else(|| {
289    MechError::new(
290      FsmUndefinedStateError {
291        fsm_name: fsm.name.to_string(),
292        state_name: summarize_pattern(&fsm.start),
293      },
294      None,
295    )
296    .with_compiler_loc()
297    .with_tokens(fsm_pipe.start.tokens())
298  })?;
299  if !state_names.contains(&start_state) {
300    return Err(MechError::new(
301      FsmUndefinedStateError {
302        fsm_name: fsm.name.to_string(),
303        state_name: start_state,
304      },
305      None,
306    )
307    .with_compiler_loc()
308    .with_tokens(fsm_pipe.start.tokens()));
309  }
310
311  for arm in &fsm.arms {
312    let transitions = match arm {
313      FsmArm::Comment(_) => continue,
314      FsmArm::Transition(_, transitions) => transitions.as_slice(),
315      FsmArm::Guard(_, guards) => {
316        for guard in guards {
317          for transition in &guard.transitions {
318              validate_transition_target_state(transition, fsm, &state_names, fsm_pipe)?;
319          }
320        }
321        &[]
322      }
323    };
324    for transition in transitions {
325      validate_transition_target_state(transition, fsm, &state_names, fsm_pipe)?;
326    }
327  }
328  Ok(())
329}
330
331fn validate_transition_target_state(transition: &Transition, fsm: &FsmImplementation, state_names: &HashSet<String>, fsm_pipe: &FsmPipe) -> MResult<()> {
332  let target = match transition {
333    Transition::Next(pattern) | Transition::Async(pattern) => state_name_from_pattern(pattern),
334    _ => None,
335  };
336  if let Some(state_name) = target {
337    if !state_names.contains(&state_name) {
338      return Err(MechError::new(
339        FsmUndefinedStateError {
340          fsm_name: fsm.name.to_string(),
341          state_name,
342        },
343        None,
344      )
345      .with_compiler_loc()
346      .with_tokens(fsm_pipe.start.tokens()));
347    }
348  }
349  Ok(())
350}
351
352fn state_name_from_pattern(pattern: &Pattern) -> Option<String> {
353  match pattern {
354    Pattern::TupleStruct(tuple_struct) => Some(tuple_struct.name.to_string()),
355    Pattern::Expression(Expression::Literal(Literal::Atom(atom))) => {
356      Some(atom.name.to_string())
357    }
358    _ => None,
359  }
360}
361
362fn apply_transitions(transitions: &[Transition], state: &mut Value, env: &mut Environment, p: &Interpreter) -> MResult<Option<Value>> {
363  for transition in transitions {
364    match transition {
365      Transition::Next(next_pattern) | Transition::Async(next_pattern) => {
366        *state = pattern_to_value(next_pattern, env, p)?;
367      }
368      Transition::Output(output_pattern) => {
369        return Ok(Some(pattern_to_value(
370          output_pattern,
371          env,
372          p,
373        )?));
374      }
375      Transition::Statement(stmt) => {
376        statement(stmt, Some(env), p)?;
377      }
378      Transition::CodeBlock(code) => {
379        for (line, _) in code {
380          mech_code(line, p)?;
381        }
382      }
383    }
384  }
385  Ok(None)
386}
387
388// FSM Errors
389// ----------------------------------------------------------------------------
390
391#[derive(Debug, Clone)]
392pub struct MissingFsmError {
393  pub fsm_id: u64,
394}
395
396impl MechErrorKind for MissingFsmError {
397  fn name(&self) -> &str {
398    "MissingFsm"
399  }
400  fn message(&self) -> String {
401    format!("FSM with id {} not found", self.fsm_id)
402  }
403}
404
405#[derive(Debug, Clone)]
406pub struct FsmUndefinedStateError {
407  pub fsm_name: String,
408  pub state_name: String,
409}
410
411impl MechErrorKind for FsmUndefinedStateError {
412  fn name(&self) -> &str {
413    "FsmUndefinedState"
414  }
415  fn message(&self) -> String {
416    format!(
417      "FSM '{}' references undefined state '{}'",
418      self.fsm_name, self.state_name
419    )
420  }
421}
422
423#[derive(Debug, Clone)]
424pub struct FsmGuardConditionKindMismatchError {
425  pub arm_index: usize,
426  pub guard_index: usize,
427  pub actual_kind: ValueKind,
428}
429
430impl MechErrorKind for FsmGuardConditionKindMismatchError {
431  fn name(&self) -> &str {
432    "FsmGuardConditionKindMismatch"
433  }
434
435  fn message(&self) -> String {
436    format!(
437      "FSM guard condition arm[{}] guard[{}] must evaluate to Bool, got '{}'",
438      self.arm_index,
439      self.guard_index,
440      self.actual_kind.to_string(),
441    )
442  }
443}
444
445#[derive(Debug, Clone)]
446pub struct FsmExceededTransitionLimitError {
447  pub max_transitions: usize,
448}
449
450impl MechErrorKind for FsmExceededTransitionLimitError {
451  fn name(&self) -> &str {
452    "FsmExceededTransitionLimit"
453  }
454
455  fn message(&self) -> String {
456    format!(
457      "FSM exceeded maximum transition limit of {} steps",
458      self.max_transitions
459    )
460  }
461}
462
463#[derive(Debug, Clone)]
464pub struct FsmArgumentKindMismatchError {
465  pub argument: String,
466  pub expected_kind: ValueKind,
467  pub actual_kind: ValueKind,
468}
469
470impl MechErrorKind for FsmArgumentKindMismatchError {
471  fn name(&self) -> &str {
472    "FsmArgumentKindMismatch"
473  }
474  fn message(&self) -> String {
475    format!(
476      "FSM argument '{}' expected kind '{}' but received '{}'",
477      self.argument,
478      self.expected_kind.to_string(),
479      self.actual_kind.to_string()
480    )
481  }
482}