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
8pub 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 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#[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}