Skip to main content

workshop_rs/
program.rs

1//! Canonical Workshop program concepts.
2
3use std::collections::HashMap;
4
5use crate::core::error::{Result, WorkshopError};
6use crate::settings::Settings;
7use crate::source::{FileId, SourceDocument, SourceFile, Span};
8use crate::wir;
9
10mod source_map;
11pub use source_map::{MAPPED_TEXT_V1, MappedText, SourceMap, SourceMapError, TEXT_V1};
12
13/// A complete Workshop program built from Workshop concepts.
14#[derive(Debug, Clone, Default)]
15pub struct Program {
16    pub settings: Option<Settings>,
17    pub global_variables: Vec<Variable>,
18    pub player_variables: Vec<Variable>,
19    pub subroutines: Vec<Subroutine>,
20    pub rules: Vec<Rule>,
21    files: Vec<SourceFile>,
22    provenance: Option<Box<ProgramProvenance>>,
23}
24
25#[derive(Debug, Clone, Default)]
26struct ProgramProvenance {
27    global_variables: Vec<DeclarationProvenance>,
28    player_variables: Vec<DeclarationProvenance>,
29    subroutines: Vec<DeclarationProvenance>,
30    rules: Vec<RuleProvenance>,
31}
32
33#[derive(Debug, Clone, Copy, Default)]
34struct DeclarationProvenance {
35    span: Option<Span>,
36    name_span: Option<Span>,
37}
38
39#[derive(Debug, Clone, Default)]
40struct RuleProvenance {
41    span: Option<crate::source::Span>,
42    /// The recorded span of the rule's quoted name in `rule("name")`.
43    name: Option<Span>,
44    /// The recorded span of the subroutine name a `Subroutine` event binds.
45    event_name: Option<Span>,
46    conditions: Vec<ValueProvenance>,
47    actions: Vec<ActionProvenance>,
48}
49
50#[derive(Debug, Clone, Default)]
51struct ActionProvenance {
52    span: Option<Span>,
53    /// The recorded span of the variable or subroutine the action names: a
54    /// set/modify/for target or a `Call Subroutine` callee.
55    identifier: Option<Span>,
56    arguments: Vec<ValueProvenance>,
57}
58
59/// The recorded span of one value node and its children, mirroring the
60/// structure of the public [`Value`] tree.
61#[derive(Debug, Clone, Default)]
62struct ValueProvenance {
63    span: Option<Span>,
64    /// The recorded span of the variable or subroutine identifier the value
65    /// names, when the value is such a reference.
66    identifier: Option<Span>,
67    children: Vec<ValueProvenance>,
68}
69
70/// A failure while attaching source mappings to a public [`Program`].
71#[derive(Debug, Clone, Copy, PartialEq, Eq)]
72#[non_exhaustive]
73pub enum SourceMappingError {
74    UnknownFile(FileId),
75    InvalidSpan(Span),
76    InvalidRule(usize),
77    InvalidCondition {
78        rule: usize,
79        condition: usize,
80    },
81    InvalidAction {
82        rule: usize,
83        action: usize,
84    },
85    InvalidActionArgument {
86        rule: usize,
87        action: usize,
88        argument: usize,
89    },
90    InvalidGlobalVariable(usize),
91    InvalidPlayerVariable(usize),
92    InvalidSubroutine(usize),
93}
94
95impl std::fmt::Display for SourceMappingError {
96    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
97        match self {
98            Self::UnknownFile(file) => {
99                write!(formatter, "source span references unknown file {file}")
100            }
101            Self::InvalidSpan(span) => write!(formatter, "invalid source span {span:?}"),
102            Self::InvalidRule(rule) => write!(formatter, "invalid rule index {rule}"),
103            Self::InvalidCondition { rule, condition } => {
104                write!(
105                    formatter,
106                    "invalid condition index {condition} in rule {rule}"
107                )
108            }
109            Self::InvalidAction { rule, action } => {
110                write!(formatter, "invalid action index {action} in rule {rule}")
111            }
112            Self::InvalidActionArgument {
113                rule,
114                action,
115                argument,
116            } => write!(
117                formatter,
118                "invalid argument index {argument} in action {action} of rule {rule}"
119            ),
120            Self::InvalidGlobalVariable(variable) => {
121                write!(formatter, "invalid global variable index {variable}")
122            }
123            Self::InvalidPlayerVariable(variable) => {
124                write!(formatter, "invalid player variable index {variable}")
125            }
126            Self::InvalidSubroutine(subroutine) => {
127                write!(formatter, "invalid subroutine index {subroutine}")
128            }
129        }
130    }
131}
132
133impl std::error::Error for SourceMappingError {}
134
135impl Program {
136    pub fn new() -> Self {
137        Self::default()
138    }
139
140    /// Register a source file and return its public file identity.
141    pub fn add_file(&mut self, mut file: SourceFile) -> FileId {
142        let id = FileId::from_index(self.files.len());
143        file.bind_file(id);
144        self.files.push(file);
145        id
146    }
147
148    pub fn global_variable(&mut self, variable: Variable) -> &mut Self {
149        self.global_variables.push(variable);
150        self
151    }
152
153    pub fn player_variable(&mut self, variable: Variable) -> &mut Self {
154        self.player_variables.push(variable);
155        self
156    }
157
158    pub fn subroutine(&mut self, subroutine: Subroutine) -> &mut Self {
159        self.subroutines.push(subroutine);
160        self
161    }
162
163    pub fn rule(&mut self, rule: Rule) -> &mut Self {
164        self.rules.push(rule);
165        self
166    }
167
168    /// Return the retained source document for a parsed file.
169    pub fn source(&self, file: FileId) -> Option<&SourceDocument> {
170        self.files.get(file.index()).and_then(SourceFile::source)
171    }
172
173    /// Attach the authored span of a public rule.
174    pub fn set_rule_span(
175        &mut self,
176        rule: usize,
177        span: Option<Span>,
178    ) -> std::result::Result<(), SourceMappingError> {
179        self.validate_span(span)?;
180        self.rule_provenance_mut(rule)?.span = span;
181        Ok(())
182    }
183
184    /// Attach the authored span of a public rule condition value.
185    pub fn set_condition_span(
186        &mut self,
187        rule: usize,
188        condition: usize,
189        span: Option<Span>,
190    ) -> std::result::Result<(), SourceMappingError> {
191        self.validate_span(span)?;
192        let condition_count = self
193            .rules
194            .get(rule)
195            .ok_or(SourceMappingError::InvalidRule(rule))?
196            .conditions
197            .len();
198        if condition >= condition_count {
199            return Err(SourceMappingError::InvalidCondition { rule, condition });
200        }
201        let rule_data = self.rule_provenance_mut(rule)?;
202        fit(&mut rule_data.conditions, condition_count);
203        rule_data.conditions[condition].span = span;
204        Ok(())
205    }
206
207    /// Attach the authored span of a public action in its linear rule order.
208    pub fn set_action_span(
209        &mut self,
210        rule: usize,
211        action: usize,
212        span: Option<Span>,
213    ) -> std::result::Result<(), SourceMappingError> {
214        self.validate_span(span)?;
215        let action_count = self
216            .rules
217            .get(rule)
218            .ok_or(SourceMappingError::InvalidRule(rule))?
219            .actions
220            .len();
221        if action >= action_count {
222            return Err(SourceMappingError::InvalidAction { rule, action });
223        }
224        let rule_data = self.rule_provenance_mut(rule)?;
225        fit(&mut rule_data.actions, action_count);
226        rule_data.actions[action].span = span;
227        Ok(())
228    }
229
230    /// Attach the authored span of a direct value argument of a public action.
231    pub fn set_action_argument_span(
232        &mut self,
233        rule: usize,
234        action: usize,
235        argument: usize,
236        span: Option<Span>,
237    ) -> std::result::Result<(), SourceMappingError> {
238        self.validate_span(span)?;
239        let action_value = self
240            .rules
241            .get(rule)
242            .ok_or(SourceMappingError::InvalidRule(rule))?
243            .actions
244            .get(action)
245            .ok_or(SourceMappingError::InvalidAction { rule, action })?;
246        let argument_count = action_argument_count(action_value);
247        if argument >= argument_count {
248            return Err(SourceMappingError::InvalidActionArgument {
249                rule,
250                action,
251                argument,
252            });
253        }
254        let action_data = self.action_provenance_mut(rule, action)?;
255        action_data
256            .arguments
257            .resize_with(argument + 1, ValueProvenance::default);
258        action_data.arguments[argument].span = span;
259        Ok(())
260    }
261
262    /// Attach the authored and identifier spans of a global variable.
263    pub fn set_global_variable_spans(
264        &mut self,
265        variable: usize,
266        span: Option<Span>,
267        name_span: Option<Span>,
268    ) -> std::result::Result<(), SourceMappingError> {
269        self.validate_span(span)?;
270        self.validate_span(name_span)?;
271        if variable >= self.global_variables.len() {
272            return Err(SourceMappingError::InvalidGlobalVariable(variable));
273        }
274        let variable_count = self.global_variables.len();
275        let provenance = self.provenance_mut();
276        fit(&mut provenance.global_variables, variable_count);
277        provenance.global_variables[variable] = DeclarationProvenance { span, name_span };
278        Ok(())
279    }
280
281    /// Attach the authored and identifier spans of a player variable.
282    pub fn set_player_variable_spans(
283        &mut self,
284        variable: usize,
285        span: Option<Span>,
286        name_span: Option<Span>,
287    ) -> std::result::Result<(), SourceMappingError> {
288        self.validate_span(span)?;
289        self.validate_span(name_span)?;
290        if variable >= self.player_variables.len() {
291            return Err(SourceMappingError::InvalidPlayerVariable(variable));
292        }
293        let variable_count = self.player_variables.len();
294        let provenance = self.provenance_mut();
295        fit(&mut provenance.player_variables, variable_count);
296        provenance.player_variables[variable] = DeclarationProvenance { span, name_span };
297        Ok(())
298    }
299
300    /// Attach the authored and identifier spans of a subroutine.
301    pub fn set_subroutine_spans(
302        &mut self,
303        subroutine: usize,
304        span: Option<Span>,
305        name_span: Option<Span>,
306    ) -> std::result::Result<(), SourceMappingError> {
307        self.validate_span(span)?;
308        self.validate_span(name_span)?;
309        if subroutine >= self.subroutines.len() {
310            return Err(SourceMappingError::InvalidSubroutine(subroutine));
311        }
312        let subroutine_count = self.subroutines.len();
313        let provenance = self.provenance_mut();
314        fit(&mut provenance.subroutines, subroutine_count);
315        provenance.subroutines[subroutine] = DeclarationProvenance { span, name_span };
316        Ok(())
317    }
318
319    /// Return the authored span of a public rule, when source metadata exists.
320    ///
321    /// Attached mappings record the program shape they were attached to. Every
322    /// span accessor returns `None` once the public rules, conditions, or
323    /// actions have been inserted or removed since the mapping was attached.
324    pub fn rule_span(&self, rule: usize) -> Option<crate::source::Span> {
325        self.rule_provenance(rule)?.span
326    }
327
328    /// Return the authored span of a public rule condition value.
329    pub fn condition_span(&self, rule: usize, condition: usize) -> Option<crate::source::Span> {
330        self.condition_provenance(rule, condition)?.span
331    }
332
333    /// Return the authored span of a public action in its linear rule order.
334    pub fn action_span(&self, rule: usize, action: usize) -> Option<crate::source::Span> {
335        self.action_provenance(rule, action)?.span
336    }
337
338    /// Return the authored span of a direct value argument of a public action.
339    pub fn action_argument_span(
340        &self,
341        rule: usize,
342        action: usize,
343        argument: usize,
344    ) -> Option<crate::source::Span> {
345        self.action_provenance(rule, action)?
346            .arguments
347            .get(argument)?
348            .span
349    }
350
351    /// Return the authored span of the declared name of a global variable.
352    ///
353    /// Source-language providers attach an explicit identifier span through
354    /// [`set_global_variable_spans`](Self::set_global_variable_spans). For raw
355    /// Workshop parses the recorded declaration span already covers exactly
356    /// the declared name and is returned as the identifier span.
357    pub fn global_variable_name_span(&self, variable: usize) -> Option<Span> {
358        self.declaration_name_span(
359            |provenance| &provenance.global_variables,
360            self.global_variables.len(),
361            variable,
362        )
363    }
364
365    /// Return the authored span of the declared name of a player variable.
366    /// See [`global_variable_name_span`](Self::global_variable_name_span).
367    pub fn player_variable_name_span(&self, variable: usize) -> Option<Span> {
368        self.declaration_name_span(
369            |provenance| &provenance.player_variables,
370            self.player_variables.len(),
371            variable,
372        )
373    }
374
375    /// Return the authored span of the declared name of a subroutine.
376    /// See [`global_variable_name_span`](Self::global_variable_name_span).
377    pub fn subroutine_name_span(&self, subroutine: usize) -> Option<Span> {
378        self.declaration_name_span(
379            |provenance| &provenance.subroutines,
380            self.subroutines.len(),
381            subroutine,
382        )
383    }
384
385    /// Return the span recorded for the variable or subroutine identifier a
386    /// public action names: the target of set/modify and for-variable actions,
387    /// or the callee of a [`Call Subroutine`](Action::CallSubroutine) action.
388    ///
389    /// Raw Workshop parses record the variable name for `Set`/`Modify`
390    /// variable actions, `For` variable loops, and `Global.name`/`Event
391    /// Player.name` infix assignments, and the callee name for `Call
392    /// Subroutine`. Indexed writes lower to `... Variable At Index` calls and
393    /// record the name on their variable argument — see
394    /// [`action_argument_value_span`](Self::action_argument_value_span).
395    /// Other action forms always return `None`.
396    pub fn action_identifier_span(&self, rule: usize, action: usize) -> Option<Span> {
397        self.action_provenance(rule, action)?.identifier
398    }
399
400    /// Return the span recorded for a rule's name inside its `rule("name")`
401    /// string, or `None` when no provenance was recorded.
402    pub fn rule_name_span(&self, rule: usize) -> Option<Span> {
403        self.rule_provenance(rule)?.name
404    }
405
406    /// Return the span recorded for the subroutine name a rule's `Subroutine`
407    /// event binding names, or `None` for other event kinds and when no
408    /// provenance was recorded.
409    pub fn rule_event_name_span(&self, rule: usize) -> Option<Span> {
410        self.rule_provenance(rule)?.event_name
411    }
412
413    /// Return the authored span of a value nested inside a public rule
414    /// condition.
415    ///
416    /// `path` walks the public [`Value`] tree: each element selects a child by
417    /// position — `Value::Array` elements and `Value::Call` arguments by
418    /// index, `Value::Vector` components as `0`/`1`/`2` for x/y/z, and a
419    /// `Value::PlayerVariable` player at `0`. An empty path returns the
420    /// condition value's own span, matching [`condition_span`](Self::condition_span).
421    ///
422    /// For a variable or subroutine reference the identifier span is returned
423    /// when the parser recorded one: raw Workshop records the variable name
424    /// for `Global.name`, `Global/Player Variable(name)`, `Event Player.name`,
425    /// bare-name, and `... At Index` argument spellings. Other nodes return
426    /// the span recorded for the node itself.
427    pub fn condition_value_span(
428        &self,
429        rule: usize,
430        condition: usize,
431        path: &[usize],
432    ) -> Option<crate::source::Span> {
433        let mut value = self.condition_provenance(rule, condition)?;
434        for &index in path {
435            value = value.children.get(index)?;
436        }
437        value.identifier.or(value.span)
438    }
439
440    /// Return the authored span of a value nested inside a direct value
441    /// argument of a public action.
442    ///
443    /// `argument` selects the same direct argument as
444    /// [`action_argument_span`](Self::action_argument_span) and `path` walks
445    /// into it the way [`condition_value_span`](Self::condition_value_span)
446    /// describes; an empty path returns the argument's own span. Like
447    /// `condition_value_span`, a variable or subroutine reference returns its
448    /// recorded identifier span.
449    pub fn action_argument_value_span(
450        &self,
451        rule: usize,
452        action: usize,
453        argument: usize,
454        path: &[usize],
455    ) -> Option<crate::source::Span> {
456        let mut value = self
457            .action_provenance(rule, action)?
458            .arguments
459            .get(argument)?;
460        for &index in path {
461            value = value.children.get(index)?;
462        }
463        value.identifier.or(value.span)
464    }
465
466    /// Create a checked source edit through the authored source attached to
467    /// this canonical program.
468    pub fn edit_source(
469        &self,
470        span: crate::source::Span,
471        replacement: impl Into<String>,
472    ) -> std::result::Result<crate::source::SourceEdit, crate::source::SourceEditError> {
473        self.source(span.file)
474            .ok_or(crate::source::SourceEditError::InvalidRange)?
475            .edit_span(span, replacement)
476    }
477
478    /// Validate the structural invariants of the canonical program.
479    pub fn validate(&self) -> std::result::Result<(), WorkshopError> {
480        let storage = self.to_wir()?;
481        storage
482            .validate()
483            .map_err(|error| WorkshopError::Malformed {
484                message: error.to_string(),
485                span: error.span(),
486            })
487    }
488
489    /// Report constructs that are structurally preserved but not fully
490    /// understood by the canonical catalog.
491    pub fn semantic_issues(
492        &self,
493        catalog: &crate::catalog::Catalog,
494    ) -> Vec<crate::rules::SemanticIssue> {
495        crate::analysis::semantic::inspect(self, catalog)
496    }
497
498    /// Render the program through the canonical Workshop debug representation.
499    pub fn dump(&self) -> String {
500        self.to_wir().map_or_else(
501            |error| format!("invalid program: {error}"),
502            |program| program.dump(),
503        )
504    }
505
506    fn rule_provenance(&self, rule: usize) -> Option<&RuleProvenance> {
507        let recorded = &self.provenance.as_deref()?.rules;
508        if recorded.len() != self.rules.len() {
509            return None;
510        }
511        recorded.get(rule)
512    }
513
514    fn action_provenance(&self, rule: usize, action: usize) -> Option<&ActionProvenance> {
515        let recorded = self.rule_provenance(rule)?;
516        if recorded.actions.len() != self.rules[rule].actions.len() {
517            return None;
518        }
519        recorded.actions.get(action)
520    }
521
522    fn condition_provenance(&self, rule: usize, condition: usize) -> Option<&ValueProvenance> {
523        let recorded = self.rule_provenance(rule)?;
524        if recorded.conditions.len() != self.rules[rule].conditions.len() {
525            return None;
526        }
527        recorded.conditions.get(condition)
528    }
529
530    fn declaration_name_span(
531        &self,
532        recorded: impl Fn(&ProgramProvenance) -> &[DeclarationProvenance],
533        count: usize,
534        position: usize,
535    ) -> Option<Span> {
536        let declaration = self.declaration_provenance(recorded, count, position);
537        declaration.name_span.or(declaration.span)
538    }
539
540    fn declaration_provenance(
541        &self,
542        recorded: impl Fn(&ProgramProvenance) -> &[DeclarationProvenance],
543        count: usize,
544        position: usize,
545    ) -> DeclarationProvenance {
546        self.provenance
547            .as_deref()
548            .map(recorded)
549            .filter(|recorded| recorded.len() == count)
550            .and_then(|recorded| recorded.get(position))
551            .copied()
552            .unwrap_or_default()
553    }
554
555    fn validate_span(&self, span: Option<Span>) -> std::result::Result<(), SourceMappingError> {
556        let Some(span) = span else {
557            return Ok(());
558        };
559        if !span.is_valid() {
560            return Err(SourceMappingError::InvalidSpan(span));
561        }
562        if self.files.get(span.file.index()).is_none() {
563            return Err(SourceMappingError::UnknownFile(span.file));
564        }
565        Ok(())
566    }
567
568    fn provenance_mut(&mut self) -> &mut ProgramProvenance {
569        self.provenance
570            .get_or_insert_with(|| Box::new(ProgramProvenance::default()))
571            .as_mut()
572    }
573
574    fn rule_provenance_mut(
575        &mut self,
576        rule: usize,
577    ) -> std::result::Result<&mut RuleProvenance, SourceMappingError> {
578        if rule >= self.rules.len() {
579            return Err(SourceMappingError::InvalidRule(rule));
580        }
581        let rule_count = self.rules.len();
582        let provenance = self.provenance_mut();
583        fit(&mut provenance.rules, rule_count);
584        Ok(&mut provenance.rules[rule])
585    }
586
587    fn action_provenance_mut(
588        &mut self,
589        rule: usize,
590        action: usize,
591    ) -> std::result::Result<&mut ActionProvenance, SourceMappingError> {
592        let action_count = self
593            .rules
594            .get(rule)
595            .ok_or(SourceMappingError::InvalidRule(rule))?
596            .actions
597            .len();
598        if action >= action_count {
599            return Err(SourceMappingError::InvalidAction { rule, action });
600        }
601        let rule_data = self.rule_provenance_mut(rule)?;
602        fit(&mut rule_data.actions, action_count);
603        Ok(&mut rule_data.actions[action])
604    }
605
606    pub(crate) fn from_wir(storage: wir::Program) -> Result<Self> {
607        let mut program = Self {
608            settings: storage.settings.clone(),
609            global_variables: storage
610                .global_variables
611                .iter()
612                .map(|variable| Variable::with_index(variable.name.clone(), variable.index))
613                .collect(),
614            player_variables: storage
615                .player_variables
616                .iter()
617                .map(|variable| Variable::with_index(variable.name.clone(), variable.index))
618                .collect(),
619            subroutines: storage
620                .subroutines
621                .iter()
622                .map(|subroutine| Subroutine::with_index(subroutine.name.clone(), subroutine.index))
623                .collect(),
624            rules: Vec::with_capacity(storage.rules.len()),
625            files: storage.files.iter().cloned().collect(),
626            provenance: Some(Box::new(ProgramProvenance {
627                global_variables: storage
628                    .global_variables
629                    .iter()
630                    .map(|variable| DeclarationProvenance {
631                        span: variable.span,
632                        name_span: variable.name_span,
633                    })
634                    .collect(),
635                player_variables: storage
636                    .player_variables
637                    .iter()
638                    .map(|variable| DeclarationProvenance {
639                        span: variable.span,
640                        name_span: variable.name_span,
641                    })
642                    .collect(),
643                subroutines: storage
644                    .subroutines
645                    .iter()
646                    .map(|subroutine| DeclarationProvenance {
647                        span: subroutine.span,
648                        name_span: subroutine.name_span,
649                    })
650                    .collect(),
651                rules: Vec::with_capacity(storage.rules.len()),
652            })),
653        };
654        for rule in storage.rules.iter() {
655            let event = public_event(&storage, &rule.event)?;
656            let conditions = rule
657                .conditions
658                .iter()
659                .map(|condition| {
660                    Ok(Condition {
661                        value: public_value(&storage, condition.value)?,
662                        disabled: condition.disabled,
663                    })
664                })
665                .collect::<Result<Vec<_>>>()?;
666            let mut actions = Vec::new();
667            let mut action_provenance = Vec::new();
668            for action in &rule.actions {
669                public_actions(&storage, *action, &mut actions)?;
670                public_action_provenance(&storage, *action, &mut action_provenance)?;
671            }
672            let event_name = match &rule.event {
673                wir::Event::Subroutine { name_span, .. } => *name_span,
674                _ => None,
675            };
676            program
677                .provenance
678                .as_mut()
679                .expect("parsed programs retain provenance")
680                .rules
681                .push(RuleProvenance {
682                    span: rule.span,
683                    name: rule.name_span,
684                    event_name,
685                    conditions: rule
686                        .conditions
687                        .iter()
688                        .map(|condition| value_provenance(&storage, condition.value))
689                        .collect(),
690                    actions: action_provenance,
691                });
692            program.rules.push(Rule {
693                name: rule.name.clone(),
694                disabled: rule.disabled,
695                event,
696                conditions,
697                actions,
698            });
699        }
700        Ok(program)
701    }
702
703    pub(crate) fn to_wir(&self) -> Result<wir::Program> {
704        let mut storage = wir::Program {
705            settings: self.settings.clone(),
706            ..Default::default()
707        };
708
709        for file in &self.files {
710            storage.add_file(file.clone());
711        }
712
713        let mut globals = HashMap::new();
714        for (position, variable) in self.global_variables.iter().enumerate() {
715            let declaration = self.declaration_provenance(
716                |provenance| &provenance.global_variables,
717                self.global_variables.len(),
718                position,
719            );
720            let id = storage.global_variables.push(wir::WorkshopVariable {
721                name: variable.name.clone(),
722                index: variable.index.unwrap_or(position as u32),
723                span: declaration.span,
724                name_span: declaration.name_span,
725            });
726            globals.insert(variable.name.clone(), id);
727        }
728        let mut players = HashMap::new();
729        for (position, variable) in self.player_variables.iter().enumerate() {
730            let declaration = self.declaration_provenance(
731                |provenance| &provenance.player_variables,
732                self.player_variables.len(),
733                position,
734            );
735            let id = storage.player_variables.push(wir::WorkshopVariable {
736                name: variable.name.clone(),
737                index: variable.index.unwrap_or(position as u32),
738                span: declaration.span,
739                name_span: declaration.name_span,
740            });
741            players.insert(variable.name.clone(), id);
742        }
743        let mut subroutines = HashMap::new();
744        for (position, subroutine) in self.subroutines.iter().enumerate() {
745            let declaration = self.declaration_provenance(
746                |provenance| &provenance.subroutines,
747                self.subroutines.len(),
748                position,
749            );
750            let id = storage.subroutines.push(wir::WorkshopSubroutine {
751                name: subroutine.name.clone(),
752                index: subroutine.index.unwrap_or(position as u32),
753                span: declaration.span,
754                name_span: declaration.name_span,
755            });
756            subroutines.insert(subroutine.name.clone(), id);
757        }
758
759        for (rule_index, rule) in self.rules.iter().enumerate() {
760            let mut event = wir_event(&rule.event, &subroutines)?;
761            if let wir::Event::Subroutine { name_span, .. } = &mut event {
762                *name_span = self
763                    .rule_provenance(rule_index)
764                    .and_then(|provenance| provenance.event_name);
765            }
766            let conditions = rule
767                .conditions
768                .iter()
769                .enumerate()
770                .map(|(condition_index, condition)| {
771                    wir_value(
772                        &condition.value,
773                        &mut storage,
774                        &globals,
775                        &players,
776                        &subroutines,
777                    )
778                    .inspect(|&value| {
779                        if let Some(provenance) =
780                            self.condition_provenance(rule_index, condition_index)
781                        {
782                            apply_value_provenance(&mut storage, value, provenance);
783                        }
784                    })
785                    .map(|value| wir::Condition {
786                        value,
787                        disabled: condition.disabled,
788                    })
789                })
790                .collect::<Result<Vec<_>>>()?;
791            let mut actions = Vec::new();
792            let mut position = 0;
793            lower_actions(
794                &rule.actions,
795                &mut position,
796                &mut actions,
797                &mut storage,
798                &globals,
799                &players,
800                &subroutines,
801            )?;
802            if let Some(provenance) = self
803                .rule_provenance(rule_index)
804                .filter(|provenance| provenance.actions.len() == rule.actions.len())
805            {
806                let mut public_position = 0;
807                apply_action_provenance(
808                    &mut storage,
809                    &actions,
810                    &provenance.actions,
811                    &mut public_position,
812                )?;
813            }
814            if position != rule.actions.len() {
815                return Err(WorkshopError::Malformed {
816                    message: "unexpected control-flow terminator in rule actions".to_string(),
817                    span: None,
818                });
819            }
820            storage.rules.push(wir::Rule {
821                name: rule.name.clone(),
822                span: self.rule_span(rule_index),
823                name_span: self
824                    .rule_provenance(rule_index)
825                    .and_then(|provenance| provenance.name),
826                disabled: rule.disabled,
827                event,
828                conditions,
829                actions,
830            });
831        }
832        Ok(storage)
833    }
834}
835
836fn fit<T: Default>(items: &mut Vec<T>, len: usize) {
837    items.truncate(len);
838    items.resize_with(len, T::default);
839}
840
841fn public_event(storage: &wir::Program, event: &wir::Event) -> Result<Event> {
842    Ok(match event {
843        wir::Event::Global => Event::Global,
844        wir::Event::EachPlayer => Event::EachPlayer,
845        wir::Event::EachPlayerWithFilters { team, target } => Event::EachPlayerWithFilters {
846            team: public_team(*team),
847            target: public_target(target),
848        },
849        wir::Event::Player { kind, team, target } => Event::Player {
850            kind: public_player_event(*kind),
851            team: public_team(*team),
852            target: public_target(target),
853        },
854        wir::Event::Subroutine { subroutine, .. } => Event::Subroutine(
855            storage
856                .subroutines
857                .get(*subroutine)
858                .ok_or_else(|| malformed_id("subroutine", subroutine.index()))?
859                .name
860                .clone(),
861        ),
862    })
863}
864
865fn public_team(team: wir::EventTeam) -> EventTeam {
866    match team {
867        wir::EventTeam::All => EventTeam::All,
868        wir::EventTeam::Team1 => EventTeam::Team1,
869        wir::EventTeam::Team2 => EventTeam::Team2,
870    }
871}
872
873fn public_target(target: &wir::EventTarget) -> EventTarget {
874    match target {
875        wir::EventTarget::All => EventTarget::All,
876        wir::EventTarget::Slot(slot) => EventTarget::Slot(*slot),
877        wir::EventTarget::Hero(hero) => EventTarget::Hero(hero.clone()),
878    }
879}
880
881fn public_player_event(kind: wir::PlayerEventKind) -> PlayerEventKind {
882    match kind {
883        wir::PlayerEventKind::DealtDamage => PlayerEventKind::DealtDamage,
884        wir::PlayerEventKind::DealtFinalBlow => PlayerEventKind::DealtFinalBlow,
885        wir::PlayerEventKind::DealtHealing => PlayerEventKind::DealtHealing,
886        wir::PlayerEventKind::DealtKnockback => PlayerEventKind::DealtKnockback,
887        wir::PlayerEventKind::Died => PlayerEventKind::Died,
888        wir::PlayerEventKind::EarnedElimination => PlayerEventKind::EarnedElimination,
889        wir::PlayerEventKind::Joined => PlayerEventKind::Joined,
890        wir::PlayerEventKind::Left => PlayerEventKind::Left,
891        wir::PlayerEventKind::ReceivedHealing => PlayerEventKind::ReceivedHealing,
892        wir::PlayerEventKind::ReceivedKnockback => PlayerEventKind::ReceivedKnockback,
893        wir::PlayerEventKind::TookDamage => PlayerEventKind::TookDamage,
894    }
895}
896
897fn public_value(storage: &wir::Program, id: wir::ValueId) -> Result<Value> {
898    let node = storage
899        .values
900        .get(id)
901        .ok_or_else(|| malformed_id("value", id.index()))?;
902    Ok(match &node.value {
903        wir::Value::Number { value, .. } => Value::Number(*value),
904        wir::Value::String(value) => Value::String(value.clone()),
905        wir::Value::LocalizedString(value) => Value::LocalizedString(value.clone()),
906        wir::Value::Bool(value) => Value::Bool(*value),
907        wir::Value::Null => Value::Null,
908        wir::Value::Array(values) => Value::Array(
909            values
910                .iter()
911                .map(|value| public_value(storage, *value))
912                .collect::<Result<Vec<_>>>()?,
913        ),
914        wir::Value::Vector { x, y, z } => Value::Vector {
915            x: Box::new(public_value(storage, *x)?),
916            y: Box::new(public_value(storage, *y)?),
917            z: Box::new(public_value(storage, *z)?),
918        },
919        wir::Value::Enum { value_type, value } => Value::Enum {
920            value_type: value_type.clone(),
921            value: value.clone(),
922        },
923        wir::Value::GlobalVariable(id) => Value::GlobalVariable(
924            storage
925                .global_variables
926                .get(*id)
927                .ok_or_else(|| malformed_id("global variable", id.index()))?
928                .name
929                .clone(),
930        ),
931        wir::Value::PlayerVariable { player, variable } => Value::PlayerVariable {
932            player: Box::new(public_value(storage, *player)?),
933            variable: storage
934                .player_variables
935                .get(*variable)
936                .ok_or_else(|| malformed_id("player variable", variable.index()))?
937                .name
938                .clone(),
939        },
940        wir::Value::Subroutine(id) => Value::Subroutine(
941            storage
942                .subroutines
943                .get(*id)
944                .ok_or_else(|| malformed_id("subroutine", id.index()))?
945                .name
946                .clone(),
947        ),
948        wir::Value::EventPlayer => Value::EventPlayer,
949        wir::Value::Call { name, args } => Value::Call {
950            name: name.clone(),
951            args: args
952                .iter()
953                .map(|arg| public_value(storage, *arg))
954                .collect::<Result<Vec<_>>>()?,
955        },
956    })
957}
958
959fn public_actions(
960    storage: &wir::Program,
961    id: wir::ActionId,
962    output: &mut Vec<Action>,
963) -> Result<()> {
964    let action = storage
965        .actions
966        .get(id)
967        .ok_or_else(|| malformed_id("action", id.index()))?;
968    match action {
969        wir::Action::SetGlobalVariable {
970            variable, value, ..
971        } => output.push(Action::SetGlobalVariable {
972            variable: storage
973                .global_variables
974                .get(*variable)
975                .ok_or_else(|| malformed_id("global variable", variable.index()))?
976                .name
977                .clone(),
978            value: public_value(storage, *value)?,
979        }),
980        wir::Action::ModifyGlobalVariable {
981            variable,
982            op,
983            value,
984            ..
985        } => output.push(Action::ModifyGlobalVariable {
986            variable: storage
987                .global_variables
988                .get(*variable)
989                .ok_or_else(|| malformed_id("global variable", variable.index()))?
990                .name
991                .clone(),
992            op: public_modify(*op),
993            value: public_value(storage, *value)?,
994        }),
995        wir::Action::SetPlayerVariable {
996            player,
997            variable,
998            value,
999            ..
1000        } => output.push(Action::SetPlayerVariable {
1001            player: public_value(storage, *player)?,
1002            variable: storage
1003                .player_variables
1004                .get(*variable)
1005                .ok_or_else(|| malformed_id("player variable", variable.index()))?
1006                .name
1007                .clone(),
1008            value: public_value(storage, *value)?,
1009        }),
1010        wir::Action::ModifyPlayerVariable {
1011            player,
1012            variable,
1013            op,
1014            value,
1015            ..
1016        } => output.push(Action::ModifyPlayerVariable {
1017            player: public_value(storage, *player)?,
1018            variable: storage
1019                .player_variables
1020                .get(*variable)
1021                .ok_or_else(|| malformed_id("player variable", variable.index()))?
1022                .name
1023                .clone(),
1024            op: public_modify(*op),
1025            value: public_value(storage, *value)?,
1026        }),
1027        wir::Action::AssignMember {
1028            target, op, value, ..
1029        } => output.push(Action::AssignMember {
1030            target: public_value(storage, *target)?,
1031            op: op.map(public_modify),
1032            value: public_value(storage, *value)?,
1033        }),
1034        wir::Action::CallSubroutine { subroutine, .. } => output.push(Action::CallSubroutine {
1035            subroutine: storage
1036                .subroutines
1037                .get(*subroutine)
1038                .ok_or_else(|| malformed_id("subroutine", subroutine.index()))?
1039                .name
1040                .clone(),
1041        }),
1042        wir::Action::If {
1043            branches,
1044            else_body,
1045            ..
1046        } => {
1047            for (index, branch) in branches.iter().enumerate() {
1048                output.push(if index == 0 {
1049                    Action::If {
1050                        condition: public_value(storage, branch.condition)?,
1051                    }
1052                } else {
1053                    Action::ElseIf {
1054                        condition: public_value(storage, branch.condition)?,
1055                    }
1056                });
1057                for action in &branch.body {
1058                    public_actions(storage, *action, output)?;
1059                }
1060            }
1061            if let Some(body) = else_body {
1062                output.push(Action::Else);
1063                for action in body {
1064                    public_actions(storage, *action, output)?;
1065                }
1066            }
1067            output.push(Action::End);
1068        }
1069        wir::Action::While {
1070            condition, body, ..
1071        } => {
1072            output.push(Action::While {
1073                condition: public_value(storage, *condition)?,
1074            });
1075            for action in body {
1076                public_actions(storage, *action, output)?;
1077            }
1078            output.push(Action::End);
1079        }
1080        wir::Action::ForGlobalVariable {
1081            variable,
1082            start,
1083            stop,
1084            step,
1085            body,
1086            ..
1087        } => {
1088            output.push(Action::ForGlobalVariable {
1089                variable: storage
1090                    .global_variables
1091                    .get(*variable)
1092                    .ok_or_else(|| malformed_id("global variable", variable.index()))?
1093                    .name
1094                    .clone(),
1095                start: public_value(storage, *start)?,
1096                stop: public_value(storage, *stop)?,
1097                step: public_value(storage, *step)?,
1098            });
1099            for action in body {
1100                public_actions(storage, *action, output)?;
1101            }
1102            output.push(Action::End);
1103        }
1104        wir::Action::ForPlayerVariable {
1105            player,
1106            variable,
1107            start,
1108            stop,
1109            step,
1110            body,
1111            ..
1112        } => {
1113            output.push(Action::ForPlayerVariable {
1114                player: public_value(storage, *player)?,
1115                variable: storage
1116                    .player_variables
1117                    .get(*variable)
1118                    .ok_or_else(|| malformed_id("player variable", variable.index()))?
1119                    .name
1120                    .clone(),
1121                start: public_value(storage, *start)?,
1122                stop: public_value(storage, *stop)?,
1123                step: public_value(storage, *step)?,
1124            });
1125            for action in body {
1126                public_actions(storage, *action, output)?;
1127            }
1128            output.push(Action::End);
1129        }
1130        wir::Action::Disabled { action, .. } => {
1131            let first = output.len();
1132            public_actions(storage, *action, output)?;
1133            let inner = output.remove(first);
1134            output.insert(first, Action::disabled(inner));
1135        }
1136        wir::Action::Call { name, args, .. } => output.push(Action::Call {
1137            name: name.clone(),
1138            args: args
1139                .iter()
1140                .map(|arg| public_value(storage, *arg))
1141                .collect::<Result<Vec<_>>>()?,
1142        }),
1143    }
1144    Ok(())
1145}
1146
1147fn public_action_provenance(
1148    storage: &wir::Program,
1149    id: wir::ActionId,
1150    output: &mut Vec<ActionProvenance>,
1151) -> Result<()> {
1152    let action = storage
1153        .actions
1154        .get(id)
1155        .ok_or_else(|| malformed_id("action", id.index()))?;
1156    let identifier = action_identifier(action);
1157    let push = |output: &mut Vec<ActionProvenance>, arguments: &[wir::ValueId]| {
1158        output.push(ActionProvenance {
1159            span: action.span(),
1160            identifier,
1161            arguments: arguments
1162                .iter()
1163                .map(|value| value_provenance(storage, *value))
1164                .collect(),
1165        });
1166    };
1167    let push_without_span = |output: &mut Vec<ActionProvenance>, arguments: &[wir::ValueId]| {
1168        output.push(ActionProvenance {
1169            span: None,
1170            identifier: None,
1171            arguments: arguments
1172                .iter()
1173                .map(|value| value_provenance(storage, *value))
1174                .collect(),
1175        });
1176    };
1177    match action {
1178        wir::Action::SetGlobalVariable { value, .. }
1179        | wir::Action::ModifyGlobalVariable { value, .. } => push(output, &[*value]),
1180        wir::Action::SetPlayerVariable { player, value, .. }
1181        | wir::Action::ModifyPlayerVariable { player, value, .. } => {
1182            push(output, &[*player, *value])
1183        }
1184        wir::Action::AssignMember { target, value, .. } => push(output, &[*target, *value]),
1185        wir::Action::CallSubroutine { .. } => push(output, &[]),
1186        wir::Action::If {
1187            branches,
1188            else_body,
1189            ..
1190        } => {
1191            for (index, branch) in branches.iter().enumerate() {
1192                if index == 0 {
1193                    push(output, &[branch.condition]);
1194                } else {
1195                    push_without_span(output, &[branch.condition]);
1196                }
1197                for action in &branch.body {
1198                    public_action_provenance(storage, *action, output)?;
1199                }
1200                if index + 1 == branches.len() && else_body.is_none() {
1201                    push_without_span(output, &[]);
1202                }
1203            }
1204            if let Some(body) = else_body {
1205                push_without_span(output, &[]);
1206                for action in body {
1207                    public_action_provenance(storage, *action, output)?;
1208                }
1209                push_without_span(output, &[]);
1210            }
1211        }
1212        wir::Action::While {
1213            condition, body, ..
1214        } => {
1215            push(output, &[*condition]);
1216            for action in body {
1217                public_action_provenance(storage, *action, output)?;
1218            }
1219            push_without_span(output, &[]);
1220        }
1221        wir::Action::ForGlobalVariable {
1222            start,
1223            stop,
1224            step,
1225            body,
1226            ..
1227        } => {
1228            push(output, &[*start, *stop, *step]);
1229            for action in body {
1230                public_action_provenance(storage, *action, output)?;
1231            }
1232            push_without_span(output, &[]);
1233        }
1234        wir::Action::ForPlayerVariable {
1235            player,
1236            start,
1237            stop,
1238            step,
1239            body,
1240            ..
1241        } => {
1242            push(output, &[*player, *start, *stop, *step]);
1243            for action in body {
1244                public_action_provenance(storage, *action, output)?;
1245            }
1246            push_without_span(output, &[]);
1247        }
1248        wir::Action::Disabled { action, .. } => {
1249            public_action_provenance(storage, *action, output)?;
1250        }
1251        wir::Action::Call { args, .. } => push(output, args),
1252    }
1253    Ok(())
1254}
1255
1256fn lower_actions(
1257    actions: &[Action],
1258    position: &mut usize,
1259    output: &mut Vec<wir::ActionId>,
1260    storage: &mut wir::Program,
1261    globals: &HashMap<String, wir::GlobalVarId>,
1262    players: &HashMap<String, wir::PlayerVarId>,
1263    subroutines: &HashMap<String, wir::SubroutineId>,
1264) -> Result<()> {
1265    while *position < actions.len() {
1266        let (disabled, current) = match &actions[*position] {
1267            Action::Disabled { action } => (true, action.as_ref()),
1268            action => (false, action),
1269        };
1270        if disabled
1271            && matches!(
1272                current,
1273                Action::ElseIf { .. } | Action::Else | Action::End | Action::Disabled { .. }
1274            )
1275        {
1276            return Err(WorkshopError::Unsupported {
1277                message: "the disabled modifier applies to a single executable action".to_string(),
1278                span: None,
1279            });
1280        }
1281        match current {
1282            Action::ElseIf { .. } | Action::Else | Action::End => return Ok(()),
1283            Action::If { condition } => {
1284                *position += 1;
1285                let mut branches = vec![wir::IfBranch {
1286                    condition: wir_value(condition, storage, globals, players, subroutines)?,
1287                    body: Vec::new(),
1288                }];
1289                lower_actions(
1290                    actions,
1291                    position,
1292                    &mut branches[0].body,
1293                    storage,
1294                    globals,
1295                    players,
1296                    subroutines,
1297                )?;
1298                while let Some(Action::ElseIf { condition }) = actions.get(*position) {
1299                    *position += 1;
1300                    let mut body = Vec::new();
1301                    lower_actions(
1302                        actions,
1303                        position,
1304                        &mut body,
1305                        storage,
1306                        globals,
1307                        players,
1308                        subroutines,
1309                    )?;
1310                    branches.push(wir::IfBranch {
1311                        condition: wir_value(condition, storage, globals, players, subroutines)?,
1312                        body,
1313                    });
1314                }
1315                let else_body = if matches!(actions.get(*position), Some(Action::Else)) {
1316                    *position += 1;
1317                    let mut body = Vec::new();
1318                    lower_actions(
1319                        actions,
1320                        position,
1321                        &mut body,
1322                        storage,
1323                        globals,
1324                        players,
1325                        subroutines,
1326                    )?;
1327                    Some(body)
1328                } else {
1329                    None
1330                };
1331                if !matches!(actions.get(*position), Some(Action::End)) {
1332                    return Err(WorkshopError::Malformed {
1333                        message: "control-flow action is missing End".to_string(),
1334                        span: None,
1335                    });
1336                }
1337                *position += 1;
1338                output.push(storage.actions.push(wir::Action::If {
1339                    branches,
1340                    else_body,
1341                    span: None,
1342                }));
1343            }
1344            Action::While { condition } => {
1345                *position += 1;
1346                let mut body = Vec::new();
1347                lower_actions(
1348                    actions,
1349                    position,
1350                    &mut body,
1351                    storage,
1352                    globals,
1353                    players,
1354                    subroutines,
1355                )?;
1356                require_end(actions, position)?;
1357                let condition = wir_value(condition, storage, globals, players, subroutines)?;
1358                output.push(storage.actions.push(wir::Action::While {
1359                    condition,
1360                    body,
1361                    span: None,
1362                }));
1363            }
1364            Action::ForGlobalVariable {
1365                variable,
1366                start,
1367                stop,
1368                step,
1369            } => {
1370                *position += 1;
1371                let mut body = Vec::new();
1372                lower_actions(
1373                    actions,
1374                    position,
1375                    &mut body,
1376                    storage,
1377                    globals,
1378                    players,
1379                    subroutines,
1380                )?;
1381                require_end(actions, position)?;
1382                let variable = *globals
1383                    .get(variable)
1384                    .ok_or_else(|| unknown_name("global variable", variable))?;
1385                let start = wir_value(start, storage, globals, players, subroutines)?;
1386                let stop = wir_value(stop, storage, globals, players, subroutines)?;
1387                let step = wir_value(step, storage, globals, players, subroutines)?;
1388                output.push(storage.actions.push(wir::Action::ForGlobalVariable {
1389                    variable,
1390                    start,
1391                    stop,
1392                    step,
1393                    body,
1394                    span: None,
1395                    target_span: None,
1396                }));
1397            }
1398            Action::ForPlayerVariable {
1399                player,
1400                variable,
1401                start,
1402                stop,
1403                step,
1404            } => {
1405                *position += 1;
1406                let mut body = Vec::new();
1407                lower_actions(
1408                    actions,
1409                    position,
1410                    &mut body,
1411                    storage,
1412                    globals,
1413                    players,
1414                    subroutines,
1415                )?;
1416                require_end(actions, position)?;
1417                let player = wir_value(player, storage, globals, players, subroutines)?;
1418                let variable = *players
1419                    .get(variable)
1420                    .ok_or_else(|| unknown_name("player variable", variable))?;
1421                let start = wir_value(start, storage, globals, players, subroutines)?;
1422                let stop = wir_value(stop, storage, globals, players, subroutines)?;
1423                let step = wir_value(step, storage, globals, players, subroutines)?;
1424                output.push(storage.actions.push(wir::Action::ForPlayerVariable {
1425                    player,
1426                    variable,
1427                    start,
1428                    stop,
1429                    step,
1430                    body,
1431                    span: None,
1432                    target_span: None,
1433                }));
1434            }
1435            action => {
1436                *position += 1;
1437                let lowered = wir_action(action, storage, globals, players, subroutines)?;
1438                output.push(lowered);
1439            }
1440        }
1441        if disabled {
1442            let action = output.pop().expect("a lowered action was just pushed");
1443            output.push(
1444                storage
1445                    .actions
1446                    .push(wir::Action::Disabled { action, span: None }),
1447            );
1448        }
1449    }
1450    Ok(())
1451}
1452
1453fn apply_action_provenance(
1454    storage: &mut wir::Program,
1455    actions: &[wir::ActionId],
1456    provenance: &[ActionProvenance],
1457    position: &mut usize,
1458) -> Result<()> {
1459    for id in actions {
1460        let action = storage
1461            .actions
1462            .get(*id)
1463            .cloned()
1464            .ok_or_else(|| malformed_id("action", id.index()))?;
1465        match action {
1466            wir::Action::If {
1467                branches,
1468                else_body,
1469                ..
1470            } => {
1471                let source = provenance.get(*position).cloned().unwrap_or_default();
1472                *position += 1;
1473                apply_action_source(storage, *id, &source);
1474                for (branch_index, branch) in branches.iter().enumerate() {
1475                    if branch_index > 0 {
1476                        let source = provenance.get(*position).cloned().unwrap_or_default();
1477                        *position += 1;
1478                        if let Some(provenance) = source.arguments.first() {
1479                            apply_value_provenance(storage, branch.condition, provenance);
1480                        }
1481                    }
1482                    apply_action_provenance(storage, &branch.body, provenance, position)?;
1483                }
1484                if let Some(body) = else_body {
1485                    *position += 1;
1486                    apply_action_provenance(storage, &body, provenance, position)?;
1487                }
1488                *position += 1;
1489            }
1490            wir::Action::While { body, .. } => {
1491                let source = provenance.get(*position).cloned().unwrap_or_default();
1492                *position += 1;
1493                apply_action_source(storage, *id, &source);
1494                apply_action_provenance(storage, &body, provenance, position)?;
1495                *position += 1;
1496            }
1497            wir::Action::ForGlobalVariable { body, .. }
1498            | wir::Action::ForPlayerVariable { body, .. } => {
1499                let source = provenance.get(*position).cloned().unwrap_or_default();
1500                *position += 1;
1501                apply_action_source(storage, *id, &source);
1502                apply_action_provenance(storage, &body, provenance, position)?;
1503                *position += 1;
1504            }
1505            wir::Action::Disabled { action, .. } => {
1506                let start = *position;
1507                apply_action_provenance(storage, &[action], provenance, position)?;
1508                let source = provenance.get(start).cloned().unwrap_or_default();
1509                if let Some(wir::Action::Disabled { span, .. }) = storage.actions.get_mut(*id) {
1510                    *span = source.span;
1511                }
1512            }
1513            _ => {
1514                let source = provenance.get(*position).cloned().unwrap_or_default();
1515                *position += 1;
1516                apply_action_source(storage, *id, &source);
1517            }
1518        }
1519    }
1520    Ok(())
1521}
1522
1523fn apply_action_source(storage: &mut wir::Program, id: wir::ActionId, source: &ActionProvenance) {
1524    if let Some(action) = storage.actions.get_mut(id) {
1525        match action {
1526            wir::Action::SetGlobalVariable {
1527                span, target_span, ..
1528            }
1529            | wir::Action::ModifyGlobalVariable {
1530                span, target_span, ..
1531            }
1532            | wir::Action::SetPlayerVariable {
1533                span, target_span, ..
1534            }
1535            | wir::Action::ModifyPlayerVariable {
1536                span, target_span, ..
1537            }
1538            | wir::Action::ForGlobalVariable {
1539                span, target_span, ..
1540            }
1541            | wir::Action::ForPlayerVariable {
1542                span, target_span, ..
1543            } => {
1544                *span = source.span;
1545                *target_span = source.identifier;
1546            }
1547            wir::Action::CallSubroutine {
1548                span, callee_span, ..
1549            } => {
1550                *span = source.span;
1551                *callee_span = source.identifier;
1552            }
1553            wir::Action::AssignMember { span, .. }
1554            | wir::Action::If { span, .. }
1555            | wir::Action::While { span, .. }
1556            | wir::Action::Disabled { span, .. }
1557            | wir::Action::Call { span, .. } => *span = source.span,
1558        }
1559    }
1560    let value_ids = storage
1561        .actions
1562        .get(id)
1563        .map(action_value_ids)
1564        .unwrap_or_default();
1565    for (value, provenance) in value_ids.into_iter().zip(&source.arguments) {
1566        apply_value_provenance(storage, value, provenance);
1567    }
1568}
1569
1570fn action_value_ids(action: &wir::Action) -> Vec<wir::ValueId> {
1571    match action {
1572        wir::Action::SetGlobalVariable { value, .. }
1573        | wir::Action::ModifyGlobalVariable { value, .. } => vec![*value],
1574        wir::Action::SetPlayerVariable { player, value, .. }
1575        | wir::Action::ModifyPlayerVariable { player, value, .. } => vec![*player, *value],
1576        wir::Action::AssignMember { target, value, .. } => vec![*target, *value],
1577        wir::Action::If { branches, .. } => {
1578            branches.iter().map(|branch| branch.condition).collect()
1579        }
1580        wir::Action::While { condition, .. } => vec![*condition],
1581        wir::Action::ForGlobalVariable {
1582            start, stop, step, ..
1583        } => vec![*start, *stop, *step],
1584        wir::Action::ForPlayerVariable {
1585            player,
1586            start,
1587            stop,
1588            step,
1589            ..
1590        } => vec![*player, *start, *stop, *step],
1591        wir::Action::Call { args, .. } => args.clone(),
1592        wir::Action::CallSubroutine { .. } | wir::Action::Disabled { .. } => Vec::new(),
1593    }
1594}
1595
1596/// The recorded span of the variable or subroutine a WIR action names, when
1597/// the parse produced one.
1598fn action_identifier(action: &wir::Action) -> Option<Span> {
1599    match action {
1600        wir::Action::SetGlobalVariable { target_span, .. }
1601        | wir::Action::ModifyGlobalVariable { target_span, .. }
1602        | wir::Action::SetPlayerVariable { target_span, .. }
1603        | wir::Action::ModifyPlayerVariable { target_span, .. }
1604        | wir::Action::ForGlobalVariable { target_span, .. }
1605        | wir::Action::ForPlayerVariable { target_span, .. } => *target_span,
1606        wir::Action::CallSubroutine { callee_span, .. } => *callee_span,
1607        _ => None,
1608    }
1609}
1610
1611/// The recorded provenance of a WIR value node and its children, mirroring
1612/// the public [`Value`] tree.
1613fn value_provenance(storage: &wir::Program, id: wir::ValueId) -> ValueProvenance {
1614    let Some(node) = storage.values.get(id) else {
1615        return ValueProvenance::default();
1616    };
1617    ValueProvenance {
1618        span: node.span,
1619        identifier: node.identifier,
1620        children: wir_value_children(&node.value)
1621            .into_iter()
1622            .map(|child| value_provenance(storage, child))
1623            .collect(),
1624    }
1625}
1626
1627/// The child value ids of a WIR value, in the order the public [`Value`]
1628/// exposes them.
1629fn wir_value_children(value: &wir::Value) -> Vec<wir::ValueId> {
1630    match value {
1631        wir::Value::Array(values) => values.clone(),
1632        wir::Value::Vector { x, y, z } => vec![*x, *y, *z],
1633        wir::Value::PlayerVariable { player, .. } => vec![*player],
1634        wir::Value::Call { args, .. } => args.clone(),
1635        _ => Vec::new(),
1636    }
1637}
1638
1639/// Write a recorded value-provenance tree back onto a WIR value subtree.
1640fn apply_value_provenance(
1641    storage: &mut wir::Program,
1642    value: wir::ValueId,
1643    source: &ValueProvenance,
1644) {
1645    let Some(node) = storage.values.get(value) else {
1646        return;
1647    };
1648    let children = wir_value_children(&node.value);
1649    if let Some(node) = storage.values.get_mut(value) {
1650        node.span = source.span;
1651        node.identifier = source.identifier;
1652    }
1653    for (child, source) in children.into_iter().zip(&source.children) {
1654        apply_value_provenance(storage, child, source);
1655    }
1656}
1657
1658fn require_end(actions: &[Action], position: &mut usize) -> Result<()> {
1659    if !matches!(actions.get(*position), Some(Action::End)) {
1660        return Err(WorkshopError::Malformed {
1661            message: "control-flow action is missing End".to_string(),
1662            span: None,
1663        });
1664    }
1665    *position += 1;
1666    Ok(())
1667}
1668
1669fn wir_action(
1670    action: &Action,
1671    storage: &mut wir::Program,
1672    globals: &HashMap<String, wir::GlobalVarId>,
1673    players: &HashMap<String, wir::PlayerVarId>,
1674    subroutines: &HashMap<String, wir::SubroutineId>,
1675) -> Result<wir::ActionId> {
1676    let action = match action {
1677        Action::SetGlobalVariable { variable, value } => wir::Action::SetGlobalVariable {
1678            variable: *globals
1679                .get(variable)
1680                .ok_or_else(|| unknown_name("global variable", variable))?,
1681            value: wir_value(value, storage, globals, players, subroutines)?,
1682            span: None,
1683            target_span: None,
1684        },
1685        Action::ModifyGlobalVariable {
1686            variable,
1687            op,
1688            value,
1689        } => wir::Action::ModifyGlobalVariable {
1690            variable: *globals
1691                .get(variable)
1692                .ok_or_else(|| unknown_name("global variable", variable))?,
1693            op: wir_modify(*op),
1694            value: wir_value(value, storage, globals, players, subroutines)?,
1695            span: None,
1696            target_span: None,
1697        },
1698        Action::SetPlayerVariable {
1699            player,
1700            variable,
1701            value,
1702        } => wir::Action::SetPlayerVariable {
1703            player: wir_value(player, storage, globals, players, subroutines)?,
1704            variable: *players
1705                .get(variable)
1706                .ok_or_else(|| unknown_name("player variable", variable))?,
1707            value: wir_value(value, storage, globals, players, subroutines)?,
1708            span: None,
1709            target_span: None,
1710        },
1711        Action::ModifyPlayerVariable {
1712            player,
1713            variable,
1714            op,
1715            value,
1716        } => wir::Action::ModifyPlayerVariable {
1717            player: wir_value(player, storage, globals, players, subroutines)?,
1718            variable: *players
1719                .get(variable)
1720                .ok_or_else(|| unknown_name("player variable", variable))?,
1721            op: wir_modify(*op),
1722            value: wir_value(value, storage, globals, players, subroutines)?,
1723            span: None,
1724            target_span: None,
1725        },
1726        Action::AssignMember { target, op, value } => wir::Action::AssignMember {
1727            target: wir_value(target, storage, globals, players, subroutines)?,
1728            op: op.map(wir_modify),
1729            value: wir_value(value, storage, globals, players, subroutines)?,
1730            span: None,
1731        },
1732        Action::CallSubroutine { subroutine } => wir::Action::CallSubroutine {
1733            subroutine: *subroutines
1734                .get(subroutine)
1735                .ok_or_else(|| unknown_name("subroutine", subroutine))?,
1736            span: None,
1737            callee_span: None,
1738        },
1739        Action::Disabled { .. } => {
1740            unreachable!("disabled actions are lowered by lower_actions")
1741        }
1742        Action::Call { name, args } => wir::Action::Call {
1743            name: name.clone(),
1744            args: args
1745                .iter()
1746                .map(|arg| wir_value(arg, storage, globals, players, subroutines))
1747                .collect::<Result<Vec<_>>>()?,
1748            span: None,
1749        },
1750        Action::ElseIf { .. }
1751        | Action::Else
1752        | Action::End
1753        | Action::If { .. }
1754        | Action::While { .. }
1755        | Action::ForGlobalVariable { .. }
1756        | Action::ForPlayerVariable { .. } => {
1757            unreachable!("structured actions are lowered by lower_actions")
1758        }
1759    };
1760    Ok(storage.actions.push(action))
1761}
1762
1763fn wir_value(
1764    value: &Value,
1765    storage: &mut wir::Program,
1766    globals: &HashMap<String, wir::GlobalVarId>,
1767    players: &HashMap<String, wir::PlayerVarId>,
1768    subroutines: &HashMap<String, wir::SubroutineId>,
1769) -> Result<wir::ValueId> {
1770    let value = match value {
1771        Value::Number(value) => wir::Value::Number {
1772            value: *value,
1773            text: crate::core::format::format_number(*value),
1774        },
1775        Value::String(value) => wir::Value::String(value.clone()),
1776        Value::LocalizedString(value) => wir::Value::LocalizedString(value.clone()),
1777        Value::Bool(value) => wir::Value::Bool(*value),
1778        Value::Null => wir::Value::Null,
1779        Value::Array(values) => wir::Value::Array(
1780            values
1781                .iter()
1782                .map(|value| wir_value(value, storage, globals, players, subroutines))
1783                .collect::<Result<Vec<_>>>()?,
1784        ),
1785        Value::Vector { x, y, z } => wir::Value::Vector {
1786            x: wir_value(x, storage, globals, players, subroutines)?,
1787            y: wir_value(y, storage, globals, players, subroutines)?,
1788            z: wir_value(z, storage, globals, players, subroutines)?,
1789        },
1790        Value::Enum { value_type, value } => wir::Value::Enum {
1791            value_type: value_type.clone(),
1792            value: value.clone(),
1793        },
1794        Value::GlobalVariable(name) => wir::Value::GlobalVariable(
1795            *globals
1796                .get(name)
1797                .ok_or_else(|| unknown_name("global variable", name))?,
1798        ),
1799        Value::PlayerVariable { player, variable } => wir::Value::PlayerVariable {
1800            player: wir_value(player, storage, globals, players, subroutines)?,
1801            variable: *players
1802                .get(variable)
1803                .ok_or_else(|| unknown_name("player variable", variable))?,
1804        },
1805        Value::Subroutine(name) => wir::Value::Subroutine(
1806            *subroutines
1807                .get(name)
1808                .ok_or_else(|| unknown_name("subroutine", name))?,
1809        ),
1810        Value::EventPlayer => wir::Value::EventPlayer,
1811        Value::Call { name, args } => wir::Value::Call {
1812            name: name.clone(),
1813            args: args
1814                .iter()
1815                .map(|arg| wir_value(arg, storage, globals, players, subroutines))
1816                .collect::<Result<Vec<_>>>()?,
1817        },
1818    };
1819    Ok(storage.values.push(wir::ValueNode::new(value, None)))
1820}
1821
1822fn wir_event(
1823    event: &Event,
1824    subroutines: &HashMap<String, wir::SubroutineId>,
1825) -> Result<wir::Event> {
1826    Ok(match event {
1827        Event::Global => wir::Event::Global,
1828        Event::EachPlayer => wir::Event::EachPlayer,
1829        Event::EachPlayerWithFilters { team, target } => wir::Event::EachPlayerWithFilters {
1830            team: wir_team(*team),
1831            target: wir_target(target),
1832        },
1833        Event::Player { kind, team, target } => wir::Event::Player {
1834            kind: wir_player_event(*kind),
1835            team: wir_team(*team),
1836            target: wir_target(target),
1837        },
1838        Event::Subroutine(name) => wir::Event::Subroutine {
1839            subroutine: *subroutines
1840                .get(name)
1841                .ok_or_else(|| unknown_name("subroutine", name))?,
1842            name_span: None,
1843        },
1844    })
1845}
1846
1847fn wir_team(team: EventTeam) -> wir::EventTeam {
1848    match team {
1849        EventTeam::All => wir::EventTeam::All,
1850        EventTeam::Team1 => wir::EventTeam::Team1,
1851        EventTeam::Team2 => wir::EventTeam::Team2,
1852    }
1853}
1854
1855fn wir_target(target: &EventTarget) -> wir::EventTarget {
1856    match target {
1857        EventTarget::All => wir::EventTarget::All,
1858        EventTarget::Slot(slot) => wir::EventTarget::Slot(*slot),
1859        EventTarget::Hero(hero) => wir::EventTarget::Hero(hero.clone()),
1860    }
1861}
1862
1863fn wir_player_event(kind: PlayerEventKind) -> wir::PlayerEventKind {
1864    match kind {
1865        PlayerEventKind::DealtDamage => wir::PlayerEventKind::DealtDamage,
1866        PlayerEventKind::DealtFinalBlow => wir::PlayerEventKind::DealtFinalBlow,
1867        PlayerEventKind::DealtHealing => wir::PlayerEventKind::DealtHealing,
1868        PlayerEventKind::DealtKnockback => wir::PlayerEventKind::DealtKnockback,
1869        PlayerEventKind::Died => wir::PlayerEventKind::Died,
1870        PlayerEventKind::EarnedElimination => wir::PlayerEventKind::EarnedElimination,
1871        PlayerEventKind::Joined => wir::PlayerEventKind::Joined,
1872        PlayerEventKind::Left => wir::PlayerEventKind::Left,
1873        PlayerEventKind::ReceivedHealing => wir::PlayerEventKind::ReceivedHealing,
1874        PlayerEventKind::ReceivedKnockback => wir::PlayerEventKind::ReceivedKnockback,
1875        PlayerEventKind::TookDamage => wir::PlayerEventKind::TookDamage,
1876    }
1877}
1878
1879fn public_modify(op: wir::ModifyOp) -> ModifyOp {
1880    match op {
1881        wir::ModifyOp::Add => ModifyOp::Add,
1882        wir::ModifyOp::Subtract => ModifyOp::Subtract,
1883        wir::ModifyOp::Multiply => ModifyOp::Multiply,
1884        wir::ModifyOp::Divide => ModifyOp::Divide,
1885        wir::ModifyOp::Modulo => ModifyOp::Modulo,
1886        wir::ModifyOp::Min => ModifyOp::Min,
1887        wir::ModifyOp::Max => ModifyOp::Max,
1888        wir::ModifyOp::RaiseToPower => ModifyOp::RaiseToPower,
1889        wir::ModifyOp::AppendToArray => ModifyOp::AppendToArray,
1890        wir::ModifyOp::RemoveFromArrayByValue => ModifyOp::RemoveFromArrayByValue,
1891        wir::ModifyOp::RemoveFromArrayByIndex => ModifyOp::RemoveFromArrayByIndex,
1892    }
1893}
1894
1895fn wir_modify(op: ModifyOp) -> wir::ModifyOp {
1896    match op {
1897        ModifyOp::Add => wir::ModifyOp::Add,
1898        ModifyOp::Subtract => wir::ModifyOp::Subtract,
1899        ModifyOp::Multiply => wir::ModifyOp::Multiply,
1900        ModifyOp::Divide => wir::ModifyOp::Divide,
1901        ModifyOp::Modulo => wir::ModifyOp::Modulo,
1902        ModifyOp::Min => wir::ModifyOp::Min,
1903        ModifyOp::Max => wir::ModifyOp::Max,
1904        ModifyOp::RaiseToPower => wir::ModifyOp::RaiseToPower,
1905        ModifyOp::AppendToArray => wir::ModifyOp::AppendToArray,
1906        ModifyOp::RemoveFromArrayByValue => wir::ModifyOp::RemoveFromArrayByValue,
1907        ModifyOp::RemoveFromArrayByIndex => wir::ModifyOp::RemoveFromArrayByIndex,
1908    }
1909}
1910
1911fn malformed_id(kind: &str, index: usize) -> WorkshopError {
1912    WorkshopError::Malformed {
1913        message: format!("dangling {kind} {index}"),
1914        span: None,
1915    }
1916}
1917
1918fn unknown_name(kind: &str, name: &str) -> WorkshopError {
1919    WorkshopError::Malformed {
1920        message: format!("unknown {kind} '{name}'"),
1921        span: None,
1922    }
1923}
1924
1925/// A Workshop global or player variable declaration.
1926#[derive(Debug, Clone, PartialEq, Eq)]
1927pub struct Variable {
1928    pub name: String,
1929    /// The raw Workshop declaration index, when the declaration has one.
1930    pub index: Option<u32>,
1931}
1932
1933impl Variable {
1934    pub fn new(name: impl Into<String>) -> Self {
1935        Self {
1936            name: name.into(),
1937            index: None,
1938        }
1939    }
1940
1941    pub fn with_index(name: impl Into<String>, index: u32) -> Self {
1942        Self {
1943            name: name.into(),
1944            index: Some(index),
1945        }
1946    }
1947}
1948
1949/// A Workshop subroutine declaration.
1950#[derive(Debug, Clone, PartialEq, Eq)]
1951pub struct Subroutine {
1952    pub name: String,
1953    /// The raw Workshop declaration index, when the declaration has one.
1954    pub index: Option<u32>,
1955}
1956
1957impl Subroutine {
1958    pub fn new(name: impl Into<String>) -> Self {
1959        Self {
1960            name: name.into(),
1961            index: None,
1962        }
1963    }
1964
1965    pub fn with_index(name: impl Into<String>, index: u32) -> Self {
1966        Self {
1967            name: name.into(),
1968            index: Some(index),
1969        }
1970    }
1971}
1972
1973/// A Workshop rule with explicit conditions and a linear action stream.
1974#[derive(Debug, Clone)]
1975#[non_exhaustive]
1976pub struct Rule {
1977    pub name: String,
1978    pub disabled: bool,
1979    pub event: Event,
1980    pub conditions: Vec<Condition>,
1981    pub actions: Vec<Action>,
1982}
1983
1984impl Rule {
1985    pub fn new(name: impl Into<String>, event: Event) -> Self {
1986        Self {
1987            name: name.into(),
1988            disabled: false,
1989            event,
1990            conditions: Vec::new(),
1991            actions: Vec::new(),
1992        }
1993    }
1994
1995    pub fn condition(mut self, condition: impl Into<Condition>) -> Self {
1996        self.conditions.push(condition.into());
1997        self
1998    }
1999
2000    pub fn action(mut self, action: Action) -> Self {
2001        self.actions.push(action);
2002        self
2003    }
2004}
2005
2006/// A rule condition. Conditions remain distinct from general value expressions.
2007#[derive(Debug, Clone)]
2008#[non_exhaustive]
2009pub struct Condition {
2010    pub value: Value,
2011    pub disabled: bool,
2012}
2013
2014impl Condition {
2015    pub fn new(value: Value) -> Self {
2016        Self {
2017            value,
2018            disabled: false,
2019        }
2020    }
2021
2022    pub fn disabled(value: Value) -> Self {
2023        Self {
2024            value,
2025            disabled: true,
2026        }
2027    }
2028}
2029
2030impl From<Value> for Condition {
2031    fn from(value: Value) -> Self {
2032        Self::new(value)
2033    }
2034}
2035
2036fn action_argument_count(action: &Action) -> usize {
2037    match action {
2038        Action::SetGlobalVariable { .. }
2039        | Action::ModifyGlobalVariable { .. }
2040        | Action::If { .. }
2041        | Action::ElseIf { .. }
2042        | Action::While { .. } => 1,
2043        Action::SetPlayerVariable { .. } | Action::ModifyPlayerVariable { .. } => 2,
2044        Action::AssignMember { .. } => 2,
2045        Action::ForGlobalVariable { .. } => 3,
2046        Action::ForPlayerVariable { .. } => 4,
2047        Action::Call { args, .. } => args.len(),
2048        Action::CallSubroutine { .. } | Action::Else | Action::End => 0,
2049        Action::Disabled { action } => action_argument_count(action),
2050    }
2051}
2052
2053/// A Workshop event identity and its native filters.
2054#[derive(Debug, Clone, PartialEq, Eq)]
2055pub enum Event {
2056    Global,
2057    EachPlayer,
2058    EachPlayerWithFilters {
2059        team: EventTeam,
2060        target: EventTarget,
2061    },
2062    Player {
2063        kind: PlayerEventKind,
2064        team: EventTeam,
2065        target: EventTarget,
2066    },
2067    Subroutine(String),
2068}
2069
2070#[derive(Debug, Clone, Copy, PartialEq, Eq)]
2071pub enum EventTeam {
2072    All,
2073    Team1,
2074    Team2,
2075}
2076
2077#[derive(Debug, Clone, PartialEq, Eq)]
2078pub enum EventTarget {
2079    All,
2080    Slot(u8),
2081    Hero(String),
2082}
2083
2084/// A non-ongoing player event identity.
2085///
2086/// Workshop can add player-scoped event identities independently of this
2087/// crate. Consumers should use a wildcard arm when matching this type.
2088#[non_exhaustive]
2089#[derive(Debug, Clone, Copy, PartialEq, Eq)]
2090pub enum PlayerEventKind {
2091    DealtDamage,
2092    DealtFinalBlow,
2093    DealtHealing,
2094    DealtKnockback,
2095    Died,
2096    EarnedElimination,
2097    Joined,
2098    Left,
2099    ReceivedHealing,
2100    ReceivedKnockback,
2101    TookDamage,
2102}
2103
2104/// A Workshop action line. Control flow is represented in the same order as
2105/// the Workshop source, including its explicit `End` lines.
2106#[derive(Debug, Clone)]
2107pub enum Action {
2108    SetGlobalVariable {
2109        variable: String,
2110        value: Value,
2111    },
2112    ModifyGlobalVariable {
2113        variable: String,
2114        op: ModifyOp,
2115        value: Value,
2116    },
2117    SetPlayerVariable {
2118        player: Value,
2119        variable: String,
2120        value: Value,
2121    },
2122    ModifyPlayerVariable {
2123        player: Value,
2124        variable: String,
2125        op: ModifyOp,
2126        value: Value,
2127    },
2128    AssignMember {
2129        target: Value,
2130        op: Option<ModifyOp>,
2131        value: Value,
2132    },
2133    CallSubroutine {
2134        subroutine: String,
2135    },
2136    If {
2137        condition: Value,
2138    },
2139    ElseIf {
2140        condition: Value,
2141    },
2142    Else,
2143    While {
2144        condition: Value,
2145    },
2146    ForGlobalVariable {
2147        variable: String,
2148        start: Value,
2149        stop: Value,
2150        step: Value,
2151    },
2152    ForPlayerVariable {
2153        player: Value,
2154        variable: String,
2155        start: Value,
2156        stop: Value,
2157        step: Value,
2158    },
2159    End,
2160    Disabled {
2161        action: Box<Action>,
2162    },
2163    Call {
2164        name: String,
2165        args: Vec<Value>,
2166    },
2167}
2168
2169impl Action {
2170    /// Mark an action as disabled.
2171    pub fn disabled(action: Action) -> Self {
2172        Self::Disabled {
2173            action: Box::new(action),
2174        }
2175    }
2176
2177    /// Construct a dynamic action call by canonical Workshop id.
2178    pub fn call(name: impl Into<String>, args: impl IntoIterator<Item = Value>) -> Self {
2179        Self::Call {
2180            name: name.into(),
2181            args: args.into_iter().collect(),
2182        }
2183    }
2184}
2185
2186/// The operation used by a Workshop variable modification action.
2187///
2188/// Workshop can add modification operations independently of this crate.
2189/// Consumers should use a wildcard arm when matching this type.
2190#[non_exhaustive]
2191#[derive(Debug, Clone, Copy, PartialEq, Eq)]
2192pub enum ModifyOp {
2193    Add,
2194    Subtract,
2195    Multiply,
2196    Divide,
2197    Modulo,
2198    Min,
2199    Max,
2200    RaiseToPower,
2201    AppendToArray,
2202    RemoveFromArrayByValue,
2203    RemoveFromArrayByIndex,
2204}
2205
2206/// A composable Workshop value expression.
2207#[derive(Debug, Clone)]
2208pub enum Value {
2209    Number(f64),
2210    String(String),
2211    LocalizedString(String),
2212    Bool(bool),
2213    Null,
2214    Array(Vec<Value>),
2215    Vector {
2216        x: Box<Value>,
2217        y: Box<Value>,
2218        z: Box<Value>,
2219    },
2220    Enum {
2221        value_type: String,
2222        value: String,
2223    },
2224    GlobalVariable(String),
2225    PlayerVariable {
2226        player: Box<Value>,
2227        variable: String,
2228    },
2229    Subroutine(String),
2230    EventPlayer,
2231    Call {
2232        name: String,
2233        args: Vec<Value>,
2234    },
2235}
2236
2237impl Value {
2238    /// Construct a numeric Workshop literal.
2239    pub fn number(value: f64) -> Self {
2240        Self::Number(value)
2241    }
2242
2243    /// Construct a custom Workshop string literal.
2244    pub fn string(value: impl Into<String>) -> Self {
2245        Self::String(value.into())
2246    }
2247
2248    pub fn global_variable(name: impl Into<String>) -> Self {
2249        Self::GlobalVariable(name.into())
2250    }
2251
2252    pub fn player_variable(player: Value, name: impl Into<String>) -> Self {
2253        Self::PlayerVariable {
2254            player: Box::new(player),
2255            variable: name.into(),
2256        }
2257    }
2258
2259    /// Construct a dynamic value call by canonical Workshop id.
2260    pub fn call(name: impl Into<String>, args: impl IntoIterator<Item = Value>) -> Self {
2261        Self::Call {
2262            name: name.into(),
2263            args: args.into_iter().collect(),
2264        }
2265    }
2266}
2267
2268impl From<bool> for Value {
2269    fn from(value: bool) -> Self {
2270        Self::Bool(value)
2271    }
2272}
2273
2274impl From<f64> for Value {
2275    fn from(value: f64) -> Self {
2276        Self::Number(value)
2277    }
2278}
2279
2280impl From<f32> for Value {
2281    fn from(value: f32) -> Self {
2282        Self::Number(f64::from(value))
2283    }
2284}
2285
2286macro_rules! impl_integer_value {
2287    ($($type:ty),+ $(,)?) => {
2288        $(
2289            impl From<$type> for Value {
2290                fn from(value: $type) -> Self {
2291                    Self::Number(value as f64)
2292                }
2293            }
2294        )+
2295    };
2296}
2297
2298impl_integer_value!(i8, i16, i32, i64, isize, u8, u16, u32, u64, usize);
2299
2300impl From<String> for Value {
2301    fn from(value: String) -> Self {
2302        Self::String(value)
2303    }
2304}
2305
2306impl From<&str> for Value {
2307    fn from(value: &str) -> Self {
2308        Self::String(value.to_string())
2309    }
2310}
2311
2312impl<T: Into<Value>> From<Vec<T>> for Value {
2313    fn from(values: Vec<T>) -> Self {
2314        Self::Array(values.into_iter().map(Into::into).collect())
2315    }
2316}
2317
2318impl<T: Into<Value>, const N: usize> From<[T; N]> for Value {
2319    fn from(values: [T; N]) -> Self {
2320        Self::Array(values.into_iter().map(Into::into).collect())
2321    }
2322}