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