Skip to main content

workshop_rs/actions/
layout.rs

1use crate::catalog::{Catalog, Locale};
2use crate::output::emitter::EmitContext;
3use crate::wir;
4
5/// The number of native Workshop actions emitted by a canonical Program action
6/// sequence.
7#[derive(Debug, Clone, Copy, PartialEq, Eq)]
8pub struct ActionLayout {
9    /// The action count in the canonical native action stream.
10    pub width: usize,
11}
12
13/// Errors returned while querying public canonical native action layout.
14#[derive(Debug, Clone, PartialEq, Eq)]
15#[non_exhaustive]
16pub enum ActionLayoutError {
17    /// The public program cannot be materialized as a valid Workshop program.
18    InvalidProgram { message: String },
19    /// Canonical emission could not expand the requested actions.
20    Emission(crate::WorkshopError),
21}
22
23impl std::fmt::Display for ActionLayoutError {
24    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
25        match self {
26            Self::InvalidProgram { message } => write!(f, "invalid program: {message}"),
27            Self::Emission(error) => write!(f, "action layout emission failed: {error}"),
28        }
29    }
30}
31
32impl std::error::Error for ActionLayoutError {}
33
34#[derive(Debug, Clone, PartialEq, Eq)]
35enum WIRActionLayoutError {
36    InvalidWIR(crate::wir::error::IrError),
37    Emission(crate::WorkshopError),
38}
39
40impl std::fmt::Display for WIRActionLayoutError {
41    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
42        match self {
43            Self::InvalidWIR(error) => write!(f, "invalid WIR: {error}"),
44            Self::Emission(error) => write!(f, "action layout emission failed: {error}"),
45        }
46    }
47}
48
49impl std::error::Error for WIRActionLayoutError {}
50
51/// Query the native Workshop action width of a validated public action sequence.
52///
53/// The sequence is expanded using the same recursive action implementation as
54/// [`crate::emitter::emit`]. Every action in the sequence is treated as
55/// non-rule-final, which is the canonical stream contract needed for relative
56/// action offsets. The returned width counts native Workshop action lines,
57/// including structural headers and terminators.
58pub fn action_width(
59    program: &crate::Program,
60    catalog: &Catalog,
61    locale: &Locale,
62    actions: &[crate::Action],
63) -> std::result::Result<ActionLayout, ActionLayoutError> {
64    let mut input = crate::Program::new();
65    input.settings = program.settings.clone();
66    input.global_variables = program.global_variables.clone();
67    input.player_variables = program.player_variables.clone();
68    input.subroutines = program.subroutines.clone();
69    input
70        .rules
71        .push(crate::Rule::new("action layout", crate::Event::Global));
72    input.rules[0].actions = actions.to_vec();
73    let storage = input
74        .to_wir()
75        .map_err(|error| ActionLayoutError::InvalidProgram {
76            message: error.to_string(),
77        })?;
78    let rule = storage.rules.iter().next().ok_or_else(|| {
79        ActionLayoutError::Emission(crate::WorkshopError::malformed(
80            "action layout program has no rule".to_string(),
81            None,
82        ))
83    })?;
84    action_width_wir(&storage, catalog, locale, &rule.actions).map_err(|error| match error {
85        WIRActionLayoutError::InvalidWIR(error) => ActionLayoutError::InvalidProgram {
86            message: error.to_string(),
87        },
88        WIRActionLayoutError::Emission(error) => ActionLayoutError::Emission(error),
89    })
90}
91
92fn action_width_wir(
93    program: &wir::Program,
94    catalog: &Catalog,
95    locale: &Locale,
96    actions: &[wir::ActionId],
97) -> std::result::Result<ActionLayout, WIRActionLayoutError> {
98    program
99        .validate()
100        .map_err(WIRActionLayoutError::InvalidWIR)?;
101    let mut emitter = EmitContext {
102        program,
103        catalog,
104        locale: locale.clone(),
105        fallback: None,
106        fallback_ids: Vec::new(),
107        out: String::new(),
108        line_count: 0,
109    };
110    for action in actions {
111        emitter
112            .action(*action, 0, false)
113            .map_err(WIRActionLayoutError::Emission)?;
114    }
115    Ok(ActionLayout {
116        width: emitter.line_count,
117    })
118}