Skip to main content

workshop_rs/actions/
layout.rs

1//! Native Workshop layout queries for canonical actions.
2
3use crate::catalog::{Catalog, Locale};
4use crate::output::emitter::EmitContext;
5use crate::wir;
6
7/// The number of native Workshop actions emitted by a canonical WIR action
8/// sequence.
9#[derive(Debug, Clone, Copy, PartialEq, Eq)]
10pub struct ActionLayout {
11    /// The action count in the canonical native action stream.
12    pub width: usize,
13}
14
15/// Errors returned while querying canonical native action layout.
16#[derive(Debug, Clone, PartialEq, Eq)]
17pub enum ActionLayoutError {
18    /// The WIR does not satisfy its structural invariants.
19    InvalidWIR(crate::wir::error::IrError),
20    /// Canonical emission could not expand the requested actions.
21    Emission(crate::WorkshopError),
22}
23
24impl std::fmt::Display for ActionLayoutError {
25    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
26        match self {
27            Self::InvalidWIR(error) => write!(f, "invalid WIR: {error}"),
28            Self::Emission(error) => write!(f, "action layout emission failed: {error}"),
29        }
30    }
31}
32
33impl std::error::Error for ActionLayoutError {}
34
35/// Query the native Workshop action width of a validated WIR action sequence.
36///
37/// The sequence is expanded using the same recursive action implementation as
38/// [`crate::emitter::emit`]. Every action in the sequence is treated as
39/// non-rule-final, which is the canonical stream contract needed for relative
40/// action offsets. The returned width counts native Workshop action lines,
41/// including structural headers and terminators.
42pub fn action_width(
43    program: &wir::Program,
44    catalog: &Catalog,
45    locale: &Locale,
46    actions: &[wir::ActionId],
47) -> std::result::Result<ActionLayout, ActionLayoutError> {
48    program.validate().map_err(ActionLayoutError::InvalidWIR)?;
49    let mut emitter = EmitContext {
50        program,
51        catalog,
52        locale: locale.clone(),
53        fallback: None,
54        fallback_ids: Vec::new(),
55        force_hero_constructors: false,
56        out: String::new(),
57        line_count: 0,
58    };
59    for action in actions {
60        emitter
61            .action(*action, 0, false)
62            .map_err(ActionLayoutError::Emission)?;
63    }
64    Ok(ActionLayout {
65        width: emitter.line_count,
66    })
67}