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| {
511                    Ok(Condition {
512                        value: public_value(&storage, condition.value)?,
513                        disabled: condition.disabled,
514                    })
515                })
516                .collect::<Result<Vec<_>>>()?;
517            let mut actions = Vec::new();
518            let mut action_provenance = Vec::new();
519            for action in &rule.actions {
520                public_actions(&storage, *action, &mut actions)?;
521                public_action_provenance(&storage, *action, &mut action_provenance)?;
522            }
523            program
524                .provenance
525                .as_mut()
526                .expect("parsed programs retain provenance")
527                .rules
528                .push(RuleProvenance {
529                    span: rule.span,
530                    conditions: rule
531                        .conditions
532                        .iter()
533                        .map(|condition| {
534                            storage
535                                .values
536                                .get(condition.value)
537                                .and_then(|value| value.span)
538                        })
539                        .collect(),
540                    actions: action_provenance,
541                });
542            program.rules.push(Rule {
543                name: rule.name.clone(),
544                disabled: rule.disabled,
545                event,
546                conditions,
547                actions,
548            });
549        }
550        Ok(program)
551    }
552
553    pub(crate) fn to_wir(&self) -> Result<wir::Program> {
554        let mut storage = wir::Program {
555            settings: self.settings.clone(),
556            ..Default::default()
557        };
558
559        for file in &self.files {
560            storage.add_file(file.clone());
561        }
562
563        let mut globals = HashMap::new();
564        for (position, variable) in self.global_variables.iter().enumerate() {
565            let declaration = self.declaration_provenance(
566                |provenance| &provenance.global_variables,
567                self.global_variables.len(),
568                position,
569            );
570            let id = storage.global_variables.push(wir::WorkshopVariable {
571                name: variable.name.clone(),
572                index: variable.index.unwrap_or(position as u32),
573                span: declaration.span,
574                name_span: declaration.name_span,
575            });
576            globals.insert(variable.name.clone(), id);
577        }
578        let mut players = HashMap::new();
579        for (position, variable) in self.player_variables.iter().enumerate() {
580            let declaration = self.declaration_provenance(
581                |provenance| &provenance.player_variables,
582                self.player_variables.len(),
583                position,
584            );
585            let id = storage.player_variables.push(wir::WorkshopVariable {
586                name: variable.name.clone(),
587                index: variable.index.unwrap_or(position as u32),
588                span: declaration.span,
589                name_span: declaration.name_span,
590            });
591            players.insert(variable.name.clone(), id);
592        }
593        let mut subroutines = HashMap::new();
594        for (position, subroutine) in self.subroutines.iter().enumerate() {
595            let declaration = self.declaration_provenance(
596                |provenance| &provenance.subroutines,
597                self.subroutines.len(),
598                position,
599            );
600            let id = storage.subroutines.push(wir::WorkshopSubroutine {
601                name: subroutine.name.clone(),
602                index: subroutine.index.unwrap_or(position as u32),
603                span: declaration.span,
604                name_span: declaration.name_span,
605            });
606            subroutines.insert(subroutine.name.clone(), id);
607        }
608
609        for (rule_index, rule) in self.rules.iter().enumerate() {
610            let event = wir_event(&rule.event, &subroutines)?;
611            let conditions = rule
612                .conditions
613                .iter()
614                .enumerate()
615                .map(|(condition_index, condition)| {
616                    wir_value(
617                        &condition.value,
618                        &mut storage,
619                        &globals,
620                        &players,
621                        &subroutines,
622                    )
623                    .inspect(|&value| {
624                        if let Some(span) = self.condition_span(rule_index, condition_index) {
625                            storage.values.get_mut(value).unwrap().span = Some(span);
626                        }
627                    })
628                    .map(|value| wir::Condition {
629                        value,
630                        disabled: condition.disabled,
631                    })
632                })
633                .collect::<Result<Vec<_>>>()?;
634            let mut actions = Vec::new();
635            let mut position = 0;
636            lower_actions(
637                &rule.actions,
638                &mut position,
639                &mut actions,
640                &mut storage,
641                &globals,
642                &players,
643                &subroutines,
644            )?;
645            if let Some(provenance) = self
646                .rule_provenance(rule_index)
647                .filter(|provenance| provenance.actions.len() == rule.actions.len())
648            {
649                let mut public_position = 0;
650                apply_action_provenance(
651                    &mut storage,
652                    &actions,
653                    &provenance.actions,
654                    &mut public_position,
655                )?;
656            }
657            if position != rule.actions.len() {
658                return Err(WorkshopError::Malformed {
659                    message: "unexpected control-flow terminator in rule actions".to_string(),
660                    span: None,
661                });
662            }
663            storage.rules.push(wir::Rule {
664                name: rule.name.clone(),
665                span: self.rule_span(rule_index),
666                name_span: None,
667                disabled: rule.disabled,
668                event,
669                conditions,
670                actions,
671            });
672        }
673        Ok(storage)
674    }
675}
676
677fn fit<T: Default>(items: &mut Vec<T>, len: usize) {
678    items.truncate(len);
679    items.resize_with(len, T::default);
680}
681
682fn public_event(storage: &wir::Program, event: &wir::Event) -> Result<Event> {
683    Ok(match event {
684        wir::Event::Global => Event::Global,
685        wir::Event::EachPlayer => Event::EachPlayer,
686        wir::Event::EachPlayerWithFilters { team, target } => Event::EachPlayerWithFilters {
687            team: public_team(*team),
688            target: public_target(target),
689        },
690        wir::Event::Player { kind, team, target } => Event::Player {
691            kind: public_player_event(*kind),
692            team: public_team(*team),
693            target: public_target(target),
694        },
695        wir::Event::Subroutine(id) => Event::Subroutine(
696            storage
697                .subroutines
698                .get(*id)
699                .ok_or_else(|| malformed_id("subroutine", id.index()))?
700                .name
701                .clone(),
702        ),
703    })
704}
705
706fn public_team(team: wir::EventTeam) -> EventTeam {
707    match team {
708        wir::EventTeam::All => EventTeam::All,
709        wir::EventTeam::Team1 => EventTeam::Team1,
710        wir::EventTeam::Team2 => EventTeam::Team2,
711    }
712}
713
714fn public_target(target: &wir::EventTarget) -> EventTarget {
715    match target {
716        wir::EventTarget::All => EventTarget::All,
717        wir::EventTarget::Slot(slot) => EventTarget::Slot(*slot),
718        wir::EventTarget::Hero(hero) => EventTarget::Hero(hero.clone()),
719    }
720}
721
722fn public_player_event(kind: wir::PlayerEventKind) -> PlayerEventKind {
723    match kind {
724        wir::PlayerEventKind::DealtDamage => PlayerEventKind::DealtDamage,
725        wir::PlayerEventKind::DealtFinalBlow => PlayerEventKind::DealtFinalBlow,
726        wir::PlayerEventKind::DealtHealing => PlayerEventKind::DealtHealing,
727        wir::PlayerEventKind::DealtKnockback => PlayerEventKind::DealtKnockback,
728        wir::PlayerEventKind::Died => PlayerEventKind::Died,
729        wir::PlayerEventKind::EarnedElimination => PlayerEventKind::EarnedElimination,
730        wir::PlayerEventKind::Joined => PlayerEventKind::Joined,
731        wir::PlayerEventKind::Left => PlayerEventKind::Left,
732        wir::PlayerEventKind::ReceivedHealing => PlayerEventKind::ReceivedHealing,
733        wir::PlayerEventKind::ReceivedKnockback => PlayerEventKind::ReceivedKnockback,
734        wir::PlayerEventKind::TookDamage => PlayerEventKind::TookDamage,
735    }
736}
737
738fn public_value(storage: &wir::Program, id: wir::ValueId) -> Result<Value> {
739    let node = storage
740        .values
741        .get(id)
742        .ok_or_else(|| malformed_id("value", id.index()))?;
743    Ok(match &node.value {
744        wir::Value::Number { value, .. } => Value::Number(*value),
745        wir::Value::String(value) => Value::String(value.clone()),
746        wir::Value::LocalizedString(value) => Value::LocalizedString(value.clone()),
747        wir::Value::Bool(value) => Value::Bool(*value),
748        wir::Value::Null => Value::Null,
749        wir::Value::Array(values) => Value::Array(
750            values
751                .iter()
752                .map(|value| public_value(storage, *value))
753                .collect::<Result<Vec<_>>>()?,
754        ),
755        wir::Value::Vector { x, y, z } => Value::Vector {
756            x: Box::new(public_value(storage, *x)?),
757            y: Box::new(public_value(storage, *y)?),
758            z: Box::new(public_value(storage, *z)?),
759        },
760        wir::Value::Enum { value_type, value } => Value::Enum {
761            value_type: value_type.clone(),
762            value: value.clone(),
763        },
764        wir::Value::GlobalVariable(id) => Value::GlobalVariable(
765            storage
766                .global_variables
767                .get(*id)
768                .ok_or_else(|| malformed_id("global variable", id.index()))?
769                .name
770                .clone(),
771        ),
772        wir::Value::PlayerVariable { player, variable } => Value::PlayerVariable {
773            player: Box::new(public_value(storage, *player)?),
774            variable: storage
775                .player_variables
776                .get(*variable)
777                .ok_or_else(|| malformed_id("player variable", variable.index()))?
778                .name
779                .clone(),
780        },
781        wir::Value::Subroutine(id) => Value::Subroutine(
782            storage
783                .subroutines
784                .get(*id)
785                .ok_or_else(|| malformed_id("subroutine", id.index()))?
786                .name
787                .clone(),
788        ),
789        wir::Value::EventPlayer => Value::EventPlayer,
790        wir::Value::Call { name, args } => Value::Call {
791            name: name.clone(),
792            args: args
793                .iter()
794                .map(|arg| public_value(storage, *arg))
795                .collect::<Result<Vec<_>>>()?,
796        },
797    })
798}
799
800fn public_actions(
801    storage: &wir::Program,
802    id: wir::ActionId,
803    output: &mut Vec<Action>,
804) -> Result<()> {
805    let action = storage
806        .actions
807        .get(id)
808        .ok_or_else(|| malformed_id("action", id.index()))?;
809    match action {
810        wir::Action::SetGlobalVariable {
811            variable, value, ..
812        } => output.push(Action::SetGlobalVariable {
813            variable: storage
814                .global_variables
815                .get(*variable)
816                .ok_or_else(|| malformed_id("global variable", variable.index()))?
817                .name
818                .clone(),
819            value: public_value(storage, *value)?,
820        }),
821        wir::Action::ModifyGlobalVariable {
822            variable,
823            op,
824            value,
825            ..
826        } => output.push(Action::ModifyGlobalVariable {
827            variable: storage
828                .global_variables
829                .get(*variable)
830                .ok_or_else(|| malformed_id("global variable", variable.index()))?
831                .name
832                .clone(),
833            op: public_modify(*op),
834            value: public_value(storage, *value)?,
835        }),
836        wir::Action::SetPlayerVariable {
837            player,
838            variable,
839            value,
840            ..
841        } => output.push(Action::SetPlayerVariable {
842            player: public_value(storage, *player)?,
843            variable: storage
844                .player_variables
845                .get(*variable)
846                .ok_or_else(|| malformed_id("player variable", variable.index()))?
847                .name
848                .clone(),
849            value: public_value(storage, *value)?,
850        }),
851        wir::Action::ModifyPlayerVariable {
852            player,
853            variable,
854            op,
855            value,
856            ..
857        } => output.push(Action::ModifyPlayerVariable {
858            player: public_value(storage, *player)?,
859            variable: storage
860                .player_variables
861                .get(*variable)
862                .ok_or_else(|| malformed_id("player variable", variable.index()))?
863                .name
864                .clone(),
865            op: public_modify(*op),
866            value: public_value(storage, *value)?,
867        }),
868        wir::Action::AssignMember {
869            target, op, value, ..
870        } => output.push(Action::AssignMember {
871            target: public_value(storage, *target)?,
872            op: op.map(public_modify),
873            value: public_value(storage, *value)?,
874        }),
875        wir::Action::CallSubroutine { subroutine, .. } => output.push(Action::CallSubroutine {
876            subroutine: storage
877                .subroutines
878                .get(*subroutine)
879                .ok_or_else(|| malformed_id("subroutine", subroutine.index()))?
880                .name
881                .clone(),
882        }),
883        wir::Action::If {
884            branches,
885            else_body,
886            ..
887        } => {
888            for (index, branch) in branches.iter().enumerate() {
889                output.push(if index == 0 {
890                    Action::If {
891                        condition: public_value(storage, branch.condition)?,
892                    }
893                } else {
894                    Action::ElseIf {
895                        condition: public_value(storage, branch.condition)?,
896                    }
897                });
898                for action in &branch.body {
899                    public_actions(storage, *action, output)?;
900                }
901            }
902            if let Some(body) = else_body {
903                output.push(Action::Else);
904                for action in body {
905                    public_actions(storage, *action, output)?;
906                }
907            }
908            output.push(Action::End);
909        }
910        wir::Action::While {
911            condition, body, ..
912        } => {
913            output.push(Action::While {
914                condition: public_value(storage, *condition)?,
915            });
916            for action in body {
917                public_actions(storage, *action, output)?;
918            }
919            output.push(Action::End);
920        }
921        wir::Action::ForGlobalVariable {
922            variable,
923            start,
924            stop,
925            step,
926            body,
927            ..
928        } => {
929            output.push(Action::ForGlobalVariable {
930                variable: storage
931                    .global_variables
932                    .get(*variable)
933                    .ok_or_else(|| malformed_id("global variable", variable.index()))?
934                    .name
935                    .clone(),
936                start: public_value(storage, *start)?,
937                stop: public_value(storage, *stop)?,
938                step: public_value(storage, *step)?,
939            });
940            for action in body {
941                public_actions(storage, *action, output)?;
942            }
943            output.push(Action::End);
944        }
945        wir::Action::ForPlayerVariable {
946            player,
947            variable,
948            start,
949            stop,
950            step,
951            body,
952            ..
953        } => {
954            output.push(Action::ForPlayerVariable {
955                player: public_value(storage, *player)?,
956                variable: storage
957                    .player_variables
958                    .get(*variable)
959                    .ok_or_else(|| malformed_id("player variable", variable.index()))?
960                    .name
961                    .clone(),
962                start: public_value(storage, *start)?,
963                stop: public_value(storage, *stop)?,
964                step: public_value(storage, *step)?,
965            });
966            for action in body {
967                public_actions(storage, *action, output)?;
968            }
969            output.push(Action::End);
970        }
971        wir::Action::Disabled { action, .. } => {
972            let first = output.len();
973            public_actions(storage, *action, output)?;
974            let inner = output.remove(first);
975            output.insert(first, Action::disabled(inner));
976        }
977        wir::Action::Call { name, args, .. } => output.push(Action::Call {
978            name: name.clone(),
979            args: args
980                .iter()
981                .map(|arg| public_value(storage, *arg))
982                .collect::<Result<Vec<_>>>()?,
983        }),
984    }
985    Ok(())
986}
987
988fn public_action_provenance(
989    storage: &wir::Program,
990    id: wir::ActionId,
991    output: &mut Vec<ActionProvenance>,
992) -> Result<()> {
993    let action = storage
994        .actions
995        .get(id)
996        .ok_or_else(|| malformed_id("action", id.index()))?;
997    let push = |output: &mut Vec<ActionProvenance>, arguments: &[wir::ValueId]| {
998        output.push(ActionProvenance {
999            span: action.span(),
1000            arguments: arguments
1001                .iter()
1002                .map(|value| storage.values.get(*value).and_then(|value| value.span))
1003                .collect(),
1004        });
1005    };
1006    let push_without_span = |output: &mut Vec<ActionProvenance>, arguments: &[wir::ValueId]| {
1007        output.push(ActionProvenance {
1008            span: None,
1009            arguments: arguments
1010                .iter()
1011                .map(|value| storage.values.get(*value).and_then(|value| value.span))
1012                .collect(),
1013        });
1014    };
1015    match action {
1016        wir::Action::SetGlobalVariable { value, .. }
1017        | wir::Action::ModifyGlobalVariable { value, .. } => push(output, &[*value]),
1018        wir::Action::SetPlayerVariable { player, value, .. }
1019        | wir::Action::ModifyPlayerVariable { player, value, .. } => {
1020            push(output, &[*player, *value])
1021        }
1022        wir::Action::AssignMember { target, value, .. } => push(output, &[*target, *value]),
1023        wir::Action::CallSubroutine { .. } => push(output, &[]),
1024        wir::Action::If {
1025            branches,
1026            else_body,
1027            ..
1028        } => {
1029            for (index, branch) in branches.iter().enumerate() {
1030                if index == 0 {
1031                    push(output, &[branch.condition]);
1032                } else {
1033                    push_without_span(output, &[branch.condition]);
1034                }
1035                for action in &branch.body {
1036                    public_action_provenance(storage, *action, output)?;
1037                }
1038                if index + 1 == branches.len() && else_body.is_none() {
1039                    push_without_span(output, &[]);
1040                }
1041            }
1042            if let Some(body) = else_body {
1043                push_without_span(output, &[]);
1044                for action in body {
1045                    public_action_provenance(storage, *action, output)?;
1046                }
1047                push_without_span(output, &[]);
1048            }
1049        }
1050        wir::Action::While {
1051            condition, body, ..
1052        } => {
1053            push(output, &[*condition]);
1054            for action in body {
1055                public_action_provenance(storage, *action, output)?;
1056            }
1057            push_without_span(output, &[]);
1058        }
1059        wir::Action::ForGlobalVariable {
1060            start,
1061            stop,
1062            step,
1063            body,
1064            ..
1065        } => {
1066            push(output, &[*start, *stop, *step]);
1067            for action in body {
1068                public_action_provenance(storage, *action, output)?;
1069            }
1070            push_without_span(output, &[]);
1071        }
1072        wir::Action::ForPlayerVariable {
1073            player,
1074            start,
1075            stop,
1076            step,
1077            body,
1078            ..
1079        } => {
1080            push(output, &[*player, *start, *stop, *step]);
1081            for action in body {
1082                public_action_provenance(storage, *action, output)?;
1083            }
1084            push_without_span(output, &[]);
1085        }
1086        wir::Action::Disabled { action, .. } => {
1087            public_action_provenance(storage, *action, output)?;
1088        }
1089        wir::Action::Call { args, .. } => push(output, args),
1090    }
1091    Ok(())
1092}
1093
1094fn lower_actions(
1095    actions: &[Action],
1096    position: &mut usize,
1097    output: &mut Vec<wir::ActionId>,
1098    storage: &mut wir::Program,
1099    globals: &HashMap<String, wir::GlobalVarId>,
1100    players: &HashMap<String, wir::PlayerVarId>,
1101    subroutines: &HashMap<String, wir::SubroutineId>,
1102) -> Result<()> {
1103    while *position < actions.len() {
1104        let (disabled, current) = match &actions[*position] {
1105            Action::Disabled { action } => (true, action.as_ref()),
1106            action => (false, action),
1107        };
1108        if disabled
1109            && matches!(
1110                current,
1111                Action::ElseIf { .. } | Action::Else | Action::End | Action::Disabled { .. }
1112            )
1113        {
1114            return Err(WorkshopError::Unsupported {
1115                message: "the disabled modifier applies to a single executable action".to_string(),
1116                span: None,
1117            });
1118        }
1119        match current {
1120            Action::ElseIf { .. } | Action::Else | Action::End => return Ok(()),
1121            Action::If { condition } => {
1122                *position += 1;
1123                let mut branches = vec![wir::IfBranch {
1124                    condition: wir_value(condition, storage, globals, players, subroutines)?,
1125                    body: Vec::new(),
1126                }];
1127                lower_actions(
1128                    actions,
1129                    position,
1130                    &mut branches[0].body,
1131                    storage,
1132                    globals,
1133                    players,
1134                    subroutines,
1135                )?;
1136                while let Some(Action::ElseIf { condition }) = actions.get(*position) {
1137                    *position += 1;
1138                    let mut body = Vec::new();
1139                    lower_actions(
1140                        actions,
1141                        position,
1142                        &mut body,
1143                        storage,
1144                        globals,
1145                        players,
1146                        subroutines,
1147                    )?;
1148                    branches.push(wir::IfBranch {
1149                        condition: wir_value(condition, storage, globals, players, subroutines)?,
1150                        body,
1151                    });
1152                }
1153                let else_body = if matches!(actions.get(*position), Some(Action::Else)) {
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                    Some(body)
1166                } else {
1167                    None
1168                };
1169                if !matches!(actions.get(*position), Some(Action::End)) {
1170                    return Err(WorkshopError::Malformed {
1171                        message: "control-flow action is missing End".to_string(),
1172                        span: None,
1173                    });
1174                }
1175                *position += 1;
1176                output.push(storage.actions.push(wir::Action::If {
1177                    branches,
1178                    else_body,
1179                    span: None,
1180                }));
1181            }
1182            Action::While { condition } => {
1183                *position += 1;
1184                let mut body = Vec::new();
1185                lower_actions(
1186                    actions,
1187                    position,
1188                    &mut body,
1189                    storage,
1190                    globals,
1191                    players,
1192                    subroutines,
1193                )?;
1194                require_end(actions, position)?;
1195                let condition = wir_value(condition, storage, globals, players, subroutines)?;
1196                output.push(storage.actions.push(wir::Action::While {
1197                    condition,
1198                    body,
1199                    span: None,
1200                }));
1201            }
1202            Action::ForGlobalVariable {
1203                variable,
1204                start,
1205                stop,
1206                step,
1207            } => {
1208                *position += 1;
1209                let mut body = Vec::new();
1210                lower_actions(
1211                    actions,
1212                    position,
1213                    &mut body,
1214                    storage,
1215                    globals,
1216                    players,
1217                    subroutines,
1218                )?;
1219                require_end(actions, position)?;
1220                let variable = *globals
1221                    .get(variable)
1222                    .ok_or_else(|| unknown_name("global variable", variable))?;
1223                let start = wir_value(start, storage, globals, players, subroutines)?;
1224                let stop = wir_value(stop, storage, globals, players, subroutines)?;
1225                let step = wir_value(step, storage, globals, players, subroutines)?;
1226                output.push(storage.actions.push(wir::Action::ForGlobalVariable {
1227                    variable,
1228                    start,
1229                    stop,
1230                    step,
1231                    body,
1232                    span: None,
1233                    target_span: None,
1234                }));
1235            }
1236            Action::ForPlayerVariable {
1237                player,
1238                variable,
1239                start,
1240                stop,
1241                step,
1242            } => {
1243                *position += 1;
1244                let mut body = Vec::new();
1245                lower_actions(
1246                    actions,
1247                    position,
1248                    &mut body,
1249                    storage,
1250                    globals,
1251                    players,
1252                    subroutines,
1253                )?;
1254                require_end(actions, position)?;
1255                let player = wir_value(player, storage, globals, players, subroutines)?;
1256                let variable = *players
1257                    .get(variable)
1258                    .ok_or_else(|| unknown_name("player variable", variable))?;
1259                let start = wir_value(start, storage, globals, players, subroutines)?;
1260                let stop = wir_value(stop, storage, globals, players, subroutines)?;
1261                let step = wir_value(step, storage, globals, players, subroutines)?;
1262                output.push(storage.actions.push(wir::Action::ForPlayerVariable {
1263                    player,
1264                    variable,
1265                    start,
1266                    stop,
1267                    step,
1268                    body,
1269                    span: None,
1270                }));
1271            }
1272            action => {
1273                *position += 1;
1274                let lowered = wir_action(action, storage, globals, players, subroutines)?;
1275                output.push(lowered);
1276            }
1277        }
1278        if disabled {
1279            let action = output.pop().expect("a lowered action was just pushed");
1280            output.push(
1281                storage
1282                    .actions
1283                    .push(wir::Action::Disabled { action, span: None }),
1284            );
1285        }
1286    }
1287    Ok(())
1288}
1289
1290fn apply_action_provenance(
1291    storage: &mut wir::Program,
1292    actions: &[wir::ActionId],
1293    provenance: &[ActionProvenance],
1294    position: &mut usize,
1295) -> Result<()> {
1296    for id in actions {
1297        let action = storage
1298            .actions
1299            .get(*id)
1300            .cloned()
1301            .ok_or_else(|| malformed_id("action", id.index()))?;
1302        match action {
1303            wir::Action::If {
1304                branches,
1305                else_body,
1306                ..
1307            } => {
1308                let source = provenance.get(*position).cloned().unwrap_or_default();
1309                *position += 1;
1310                apply_action_source(storage, *id, &source);
1311                for (branch_index, branch) in branches.iter().enumerate() {
1312                    if branch_index > 0 {
1313                        let source = provenance.get(*position).cloned().unwrap_or_default();
1314                        *position += 1;
1315                        set_value_span(
1316                            storage,
1317                            branch.condition,
1318                            source.arguments.first().copied().flatten(),
1319                        );
1320                    }
1321                    apply_action_provenance(storage, &branch.body, provenance, position)?;
1322                }
1323                if let Some(body) = else_body {
1324                    *position += 1;
1325                    apply_action_provenance(storage, &body, provenance, position)?;
1326                }
1327                *position += 1;
1328            }
1329            wir::Action::While {
1330                condition, body, ..
1331            } => {
1332                let source = provenance.get(*position).cloned().unwrap_or_default();
1333                *position += 1;
1334                apply_action_source(storage, *id, &source);
1335                apply_action_provenance(storage, &body, provenance, position)?;
1336                *position += 1;
1337                set_value_span(
1338                    storage,
1339                    condition,
1340                    source.arguments.first().copied().flatten(),
1341                );
1342            }
1343            wir::Action::ForGlobalVariable {
1344                start,
1345                stop,
1346                step,
1347                body,
1348                ..
1349            } => {
1350                let source = provenance.get(*position).cloned().unwrap_or_default();
1351                *position += 1;
1352                apply_action_source(storage, *id, &source);
1353                apply_action_provenance(storage, &body, provenance, position)?;
1354                *position += 1;
1355                for (value, span) in [start, stop, step].into_iter().zip(source.arguments) {
1356                    set_value_span(storage, value, span);
1357                }
1358            }
1359            wir::Action::ForPlayerVariable {
1360                player,
1361                start,
1362                stop,
1363                step,
1364                body,
1365                ..
1366            } => {
1367                let source = provenance.get(*position).cloned().unwrap_or_default();
1368                *position += 1;
1369                apply_action_source(storage, *id, &source);
1370                apply_action_provenance(storage, &body, provenance, position)?;
1371                *position += 1;
1372                for (value, span) in [player, start, stop, step]
1373                    .into_iter()
1374                    .zip(source.arguments)
1375                {
1376                    set_value_span(storage, value, span);
1377                }
1378            }
1379            wir::Action::Disabled { action, .. } => {
1380                let start = *position;
1381                apply_action_provenance(storage, &[action], provenance, position)?;
1382                let source = provenance.get(start).cloned().unwrap_or_default();
1383                if let Some(wir::Action::Disabled { span, .. }) = storage.actions.get_mut(*id) {
1384                    *span = source.span;
1385                }
1386            }
1387            _ => {
1388                let source = provenance.get(*position).cloned().unwrap_or_default();
1389                *position += 1;
1390                apply_action_source(storage, *id, &source);
1391            }
1392        }
1393    }
1394    Ok(())
1395}
1396
1397fn apply_action_source(storage: &mut wir::Program, id: wir::ActionId, source: &ActionProvenance) {
1398    let arguments = source.arguments.clone();
1399    if let Some(action) = storage.actions.get_mut(id) {
1400        match action {
1401            wir::Action::SetGlobalVariable { span, .. }
1402            | wir::Action::ModifyGlobalVariable { span, .. }
1403            | wir::Action::SetPlayerVariable { span, .. }
1404            | wir::Action::ModifyPlayerVariable { span, .. }
1405            | wir::Action::AssignMember { span, .. }
1406            | wir::Action::CallSubroutine { span, .. }
1407            | wir::Action::If { span, .. }
1408            | wir::Action::While { span, .. }
1409            | wir::Action::ForGlobalVariable { span, .. }
1410            | wir::Action::ForPlayerVariable { span, .. }
1411            | wir::Action::Disabled { span, .. }
1412            | wir::Action::Call { span, .. } => *span = source.span,
1413        }
1414    }
1415    let value_ids = storage
1416        .actions
1417        .get(id)
1418        .map(action_value_ids)
1419        .unwrap_or_default();
1420    for (value, span) in value_ids.into_iter().zip(arguments) {
1421        set_value_span(storage, value, span);
1422    }
1423}
1424
1425fn action_value_ids(action: &wir::Action) -> Vec<wir::ValueId> {
1426    match action {
1427        wir::Action::SetGlobalVariable { value, .. }
1428        | wir::Action::ModifyGlobalVariable { value, .. } => vec![*value],
1429        wir::Action::SetPlayerVariable { player, value, .. }
1430        | wir::Action::ModifyPlayerVariable { player, value, .. } => vec![*player, *value],
1431        wir::Action::AssignMember { target, value, .. } => vec![*target, *value],
1432        wir::Action::If { branches, .. } => {
1433            branches.iter().map(|branch| branch.condition).collect()
1434        }
1435        wir::Action::While { condition, .. } => vec![*condition],
1436        wir::Action::ForGlobalVariable {
1437            start, stop, step, ..
1438        } => vec![*start, *stop, *step],
1439        wir::Action::ForPlayerVariable {
1440            player,
1441            start,
1442            stop,
1443            step,
1444            ..
1445        } => vec![*player, *start, *stop, *step],
1446        wir::Action::Call { args, .. } => args.clone(),
1447        wir::Action::CallSubroutine { .. } | wir::Action::Disabled { .. } => Vec::new(),
1448    }
1449}
1450
1451fn set_value_span(
1452    storage: &mut wir::Program,
1453    value: wir::ValueId,
1454    span: Option<crate::source::Span>,
1455) {
1456    if let Some(node) = storage.values.get_mut(value) {
1457        node.span = span;
1458    }
1459}
1460
1461fn require_end(actions: &[Action], position: &mut usize) -> Result<()> {
1462    if !matches!(actions.get(*position), Some(Action::End)) {
1463        return Err(WorkshopError::Malformed {
1464            message: "control-flow action is missing End".to_string(),
1465            span: None,
1466        });
1467    }
1468    *position += 1;
1469    Ok(())
1470}
1471
1472fn wir_action(
1473    action: &Action,
1474    storage: &mut wir::Program,
1475    globals: &HashMap<String, wir::GlobalVarId>,
1476    players: &HashMap<String, wir::PlayerVarId>,
1477    subroutines: &HashMap<String, wir::SubroutineId>,
1478) -> Result<wir::ActionId> {
1479    let action = match action {
1480        Action::SetGlobalVariable { variable, value } => wir::Action::SetGlobalVariable {
1481            variable: *globals
1482                .get(variable)
1483                .ok_or_else(|| unknown_name("global variable", variable))?,
1484            value: wir_value(value, storage, globals, players, subroutines)?,
1485            span: None,
1486            target_span: None,
1487        },
1488        Action::ModifyGlobalVariable {
1489            variable,
1490            op,
1491            value,
1492        } => wir::Action::ModifyGlobalVariable {
1493            variable: *globals
1494                .get(variable)
1495                .ok_or_else(|| unknown_name("global variable", variable))?,
1496            op: wir_modify(*op),
1497            value: wir_value(value, storage, globals, players, subroutines)?,
1498            span: None,
1499            target_span: None,
1500        },
1501        Action::SetPlayerVariable {
1502            player,
1503            variable,
1504            value,
1505        } => wir::Action::SetPlayerVariable {
1506            player: wir_value(player, storage, globals, players, subroutines)?,
1507            variable: *players
1508                .get(variable)
1509                .ok_or_else(|| unknown_name("player variable", variable))?,
1510            value: wir_value(value, storage, globals, players, subroutines)?,
1511            span: None,
1512            target_span: None,
1513        },
1514        Action::ModifyPlayerVariable {
1515            player,
1516            variable,
1517            op,
1518            value,
1519        } => wir::Action::ModifyPlayerVariable {
1520            player: wir_value(player, storage, globals, players, subroutines)?,
1521            variable: *players
1522                .get(variable)
1523                .ok_or_else(|| unknown_name("player variable", variable))?,
1524            op: wir_modify(*op),
1525            value: wir_value(value, storage, globals, players, subroutines)?,
1526            span: None,
1527            target_span: None,
1528        },
1529        Action::AssignMember { target, op, value } => wir::Action::AssignMember {
1530            target: wir_value(target, storage, globals, players, subroutines)?,
1531            op: op.map(wir_modify),
1532            value: wir_value(value, storage, globals, players, subroutines)?,
1533            span: None,
1534        },
1535        Action::CallSubroutine { subroutine } => wir::Action::CallSubroutine {
1536            subroutine: *subroutines
1537                .get(subroutine)
1538                .ok_or_else(|| unknown_name("subroutine", subroutine))?,
1539            span: None,
1540            callee_span: None,
1541        },
1542        Action::Disabled { .. } => {
1543            unreachable!("disabled actions are lowered by lower_actions")
1544        }
1545        Action::Call { name, args } => wir::Action::Call {
1546            name: name.clone(),
1547            args: args
1548                .iter()
1549                .map(|arg| wir_value(arg, storage, globals, players, subroutines))
1550                .collect::<Result<Vec<_>>>()?,
1551            span: None,
1552        },
1553        Action::ElseIf { .. }
1554        | Action::Else
1555        | Action::End
1556        | Action::If { .. }
1557        | Action::While { .. }
1558        | Action::ForGlobalVariable { .. }
1559        | Action::ForPlayerVariable { .. } => {
1560            unreachable!("structured actions are lowered by lower_actions")
1561        }
1562    };
1563    Ok(storage.actions.push(action))
1564}
1565
1566fn wir_value(
1567    value: &Value,
1568    storage: &mut wir::Program,
1569    globals: &HashMap<String, wir::GlobalVarId>,
1570    players: &HashMap<String, wir::PlayerVarId>,
1571    subroutines: &HashMap<String, wir::SubroutineId>,
1572) -> Result<wir::ValueId> {
1573    let value = match value {
1574        Value::Number(value) => wir::Value::Number {
1575            value: *value,
1576            text: crate::core::format::format_number(*value),
1577        },
1578        Value::String(value) => wir::Value::String(value.clone()),
1579        Value::LocalizedString(value) => wir::Value::LocalizedString(value.clone()),
1580        Value::Bool(value) => wir::Value::Bool(*value),
1581        Value::Null => wir::Value::Null,
1582        Value::Array(values) => wir::Value::Array(
1583            values
1584                .iter()
1585                .map(|value| wir_value(value, storage, globals, players, subroutines))
1586                .collect::<Result<Vec<_>>>()?,
1587        ),
1588        Value::Vector { x, y, z } => wir::Value::Vector {
1589            x: wir_value(x, storage, globals, players, subroutines)?,
1590            y: wir_value(y, storage, globals, players, subroutines)?,
1591            z: wir_value(z, storage, globals, players, subroutines)?,
1592        },
1593        Value::Enum { value_type, value } => wir::Value::Enum {
1594            value_type: value_type.clone(),
1595            value: value.clone(),
1596        },
1597        Value::GlobalVariable(name) => wir::Value::GlobalVariable(
1598            *globals
1599                .get(name)
1600                .ok_or_else(|| unknown_name("global variable", name))?,
1601        ),
1602        Value::PlayerVariable { player, variable } => wir::Value::PlayerVariable {
1603            player: wir_value(player, storage, globals, players, subroutines)?,
1604            variable: *players
1605                .get(variable)
1606                .ok_or_else(|| unknown_name("player variable", variable))?,
1607        },
1608        Value::Subroutine(name) => wir::Value::Subroutine(
1609            *subroutines
1610                .get(name)
1611                .ok_or_else(|| unknown_name("subroutine", name))?,
1612        ),
1613        Value::EventPlayer => wir::Value::EventPlayer,
1614        Value::Call { name, args } => wir::Value::Call {
1615            name: name.clone(),
1616            args: args
1617                .iter()
1618                .map(|arg| wir_value(arg, storage, globals, players, subroutines))
1619                .collect::<Result<Vec<_>>>()?,
1620        },
1621    };
1622    Ok(storage.values.push(wir::ValueNode::new(value, None)))
1623}
1624
1625fn wir_event(
1626    event: &Event,
1627    subroutines: &HashMap<String, wir::SubroutineId>,
1628) -> Result<wir::Event> {
1629    Ok(match event {
1630        Event::Global => wir::Event::Global,
1631        Event::EachPlayer => wir::Event::EachPlayer,
1632        Event::EachPlayerWithFilters { team, target } => wir::Event::EachPlayerWithFilters {
1633            team: wir_team(*team),
1634            target: wir_target(target),
1635        },
1636        Event::Player { kind, team, target } => wir::Event::Player {
1637            kind: wir_player_event(*kind),
1638            team: wir_team(*team),
1639            target: wir_target(target),
1640        },
1641        Event::Subroutine(name) => wir::Event::Subroutine(
1642            *subroutines
1643                .get(name)
1644                .ok_or_else(|| unknown_name("subroutine", name))?,
1645        ),
1646    })
1647}
1648
1649fn wir_team(team: EventTeam) -> wir::EventTeam {
1650    match team {
1651        EventTeam::All => wir::EventTeam::All,
1652        EventTeam::Team1 => wir::EventTeam::Team1,
1653        EventTeam::Team2 => wir::EventTeam::Team2,
1654    }
1655}
1656
1657fn wir_target(target: &EventTarget) -> wir::EventTarget {
1658    match target {
1659        EventTarget::All => wir::EventTarget::All,
1660        EventTarget::Slot(slot) => wir::EventTarget::Slot(*slot),
1661        EventTarget::Hero(hero) => wir::EventTarget::Hero(hero.clone()),
1662    }
1663}
1664
1665fn wir_player_event(kind: PlayerEventKind) -> wir::PlayerEventKind {
1666    match kind {
1667        PlayerEventKind::DealtDamage => wir::PlayerEventKind::DealtDamage,
1668        PlayerEventKind::DealtFinalBlow => wir::PlayerEventKind::DealtFinalBlow,
1669        PlayerEventKind::DealtHealing => wir::PlayerEventKind::DealtHealing,
1670        PlayerEventKind::DealtKnockback => wir::PlayerEventKind::DealtKnockback,
1671        PlayerEventKind::Died => wir::PlayerEventKind::Died,
1672        PlayerEventKind::EarnedElimination => wir::PlayerEventKind::EarnedElimination,
1673        PlayerEventKind::Joined => wir::PlayerEventKind::Joined,
1674        PlayerEventKind::Left => wir::PlayerEventKind::Left,
1675        PlayerEventKind::ReceivedHealing => wir::PlayerEventKind::ReceivedHealing,
1676        PlayerEventKind::ReceivedKnockback => wir::PlayerEventKind::ReceivedKnockback,
1677        PlayerEventKind::TookDamage => wir::PlayerEventKind::TookDamage,
1678    }
1679}
1680
1681fn public_modify(op: wir::ModifyOp) -> ModifyOp {
1682    match op {
1683        wir::ModifyOp::Add => ModifyOp::Add,
1684        wir::ModifyOp::Subtract => ModifyOp::Subtract,
1685        wir::ModifyOp::Multiply => ModifyOp::Multiply,
1686        wir::ModifyOp::Divide => ModifyOp::Divide,
1687        wir::ModifyOp::Modulo => ModifyOp::Modulo,
1688        wir::ModifyOp::Min => ModifyOp::Min,
1689        wir::ModifyOp::Max => ModifyOp::Max,
1690        wir::ModifyOp::RaiseToPower => ModifyOp::RaiseToPower,
1691        wir::ModifyOp::AppendToArray => ModifyOp::AppendToArray,
1692        wir::ModifyOp::RemoveFromArrayByValue => ModifyOp::RemoveFromArrayByValue,
1693        wir::ModifyOp::RemoveFromArrayByIndex => ModifyOp::RemoveFromArrayByIndex,
1694    }
1695}
1696
1697fn wir_modify(op: ModifyOp) -> wir::ModifyOp {
1698    match op {
1699        ModifyOp::Add => wir::ModifyOp::Add,
1700        ModifyOp::Subtract => wir::ModifyOp::Subtract,
1701        ModifyOp::Multiply => wir::ModifyOp::Multiply,
1702        ModifyOp::Divide => wir::ModifyOp::Divide,
1703        ModifyOp::Modulo => wir::ModifyOp::Modulo,
1704        ModifyOp::Min => wir::ModifyOp::Min,
1705        ModifyOp::Max => wir::ModifyOp::Max,
1706        ModifyOp::RaiseToPower => wir::ModifyOp::RaiseToPower,
1707        ModifyOp::AppendToArray => wir::ModifyOp::AppendToArray,
1708        ModifyOp::RemoveFromArrayByValue => wir::ModifyOp::RemoveFromArrayByValue,
1709        ModifyOp::RemoveFromArrayByIndex => wir::ModifyOp::RemoveFromArrayByIndex,
1710    }
1711}
1712
1713fn malformed_id(kind: &str, index: usize) -> WorkshopError {
1714    WorkshopError::Malformed {
1715        message: format!("dangling {kind} {index}"),
1716        span: None,
1717    }
1718}
1719
1720fn unknown_name(kind: &str, name: &str) -> WorkshopError {
1721    WorkshopError::Malformed {
1722        message: format!("unknown {kind} '{name}'"),
1723        span: None,
1724    }
1725}
1726
1727/// A Workshop global or player variable declaration.
1728#[derive(Debug, Clone, PartialEq, Eq)]
1729pub struct Variable {
1730    pub name: String,
1731    /// The raw Workshop declaration index, when the declaration has one.
1732    pub index: Option<u32>,
1733}
1734
1735impl Variable {
1736    pub fn new(name: impl Into<String>) -> Self {
1737        Self {
1738            name: name.into(),
1739            index: None,
1740        }
1741    }
1742
1743    pub fn with_index(name: impl Into<String>, index: u32) -> Self {
1744        Self {
1745            name: name.into(),
1746            index: Some(index),
1747        }
1748    }
1749}
1750
1751/// A Workshop subroutine declaration.
1752#[derive(Debug, Clone, PartialEq, Eq)]
1753pub struct Subroutine {
1754    pub name: String,
1755    /// The raw Workshop declaration index, when the declaration has one.
1756    pub index: Option<u32>,
1757}
1758
1759impl Subroutine {
1760    pub fn new(name: impl Into<String>) -> Self {
1761        Self {
1762            name: name.into(),
1763            index: None,
1764        }
1765    }
1766
1767    pub fn with_index(name: impl Into<String>, index: u32) -> Self {
1768        Self {
1769            name: name.into(),
1770            index: Some(index),
1771        }
1772    }
1773}
1774
1775/// A Workshop rule with explicit conditions and a linear action stream.
1776#[derive(Debug, Clone)]
1777#[non_exhaustive]
1778pub struct Rule {
1779    pub name: String,
1780    pub disabled: bool,
1781    pub event: Event,
1782    pub conditions: Vec<Condition>,
1783    pub actions: Vec<Action>,
1784}
1785
1786impl Rule {
1787    pub fn new(name: impl Into<String>, event: Event) -> Self {
1788        Self {
1789            name: name.into(),
1790            disabled: false,
1791            event,
1792            conditions: Vec::new(),
1793            actions: Vec::new(),
1794        }
1795    }
1796
1797    pub fn condition(mut self, condition: impl Into<Condition>) -> Self {
1798        self.conditions.push(condition.into());
1799        self
1800    }
1801
1802    pub fn action(mut self, action: Action) -> Self {
1803        self.actions.push(action);
1804        self
1805    }
1806}
1807
1808/// A rule condition. Conditions remain distinct from general value expressions.
1809#[derive(Debug, Clone)]
1810#[non_exhaustive]
1811pub struct Condition {
1812    pub value: Value,
1813    pub disabled: bool,
1814}
1815
1816impl Condition {
1817    pub fn new(value: Value) -> Self {
1818        Self {
1819            value,
1820            disabled: false,
1821        }
1822    }
1823
1824    pub fn disabled(value: Value) -> Self {
1825        Self {
1826            value,
1827            disabled: true,
1828        }
1829    }
1830}
1831
1832impl From<Value> for Condition {
1833    fn from(value: Value) -> Self {
1834        Self::new(value)
1835    }
1836}
1837
1838fn action_argument_count(action: &Action) -> usize {
1839    match action {
1840        Action::SetGlobalVariable { .. }
1841        | Action::ModifyGlobalVariable { .. }
1842        | Action::If { .. }
1843        | Action::ElseIf { .. }
1844        | Action::While { .. } => 1,
1845        Action::SetPlayerVariable { .. } | Action::ModifyPlayerVariable { .. } => 2,
1846        Action::AssignMember { .. } => 2,
1847        Action::ForGlobalVariable { .. } => 3,
1848        Action::ForPlayerVariable { .. } => 4,
1849        Action::Call { args, .. } => args.len(),
1850        Action::CallSubroutine { .. } | Action::Else | Action::End => 0,
1851        Action::Disabled { action } => action_argument_count(action),
1852    }
1853}
1854
1855/// A Workshop event identity and its native filters.
1856#[derive(Debug, Clone, PartialEq, Eq)]
1857pub enum Event {
1858    Global,
1859    EachPlayer,
1860    EachPlayerWithFilters {
1861        team: EventTeam,
1862        target: EventTarget,
1863    },
1864    Player {
1865        kind: PlayerEventKind,
1866        team: EventTeam,
1867        target: EventTarget,
1868    },
1869    Subroutine(String),
1870}
1871
1872#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1873pub enum EventTeam {
1874    All,
1875    Team1,
1876    Team2,
1877}
1878
1879#[derive(Debug, Clone, PartialEq, Eq)]
1880pub enum EventTarget {
1881    All,
1882    Slot(u8),
1883    Hero(String),
1884}
1885
1886/// A non-ongoing player event identity.
1887///
1888/// Workshop can add player-scoped event identities independently of this
1889/// crate. Consumers should use a wildcard arm when matching this type.
1890#[non_exhaustive]
1891#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1892pub enum PlayerEventKind {
1893    DealtDamage,
1894    DealtFinalBlow,
1895    DealtHealing,
1896    DealtKnockback,
1897    Died,
1898    EarnedElimination,
1899    Joined,
1900    Left,
1901    ReceivedHealing,
1902    ReceivedKnockback,
1903    TookDamage,
1904}
1905
1906/// A Workshop action line. Control flow is represented in the same order as
1907/// the Workshop source, including its explicit `End` lines.
1908#[derive(Debug, Clone)]
1909pub enum Action {
1910    SetGlobalVariable {
1911        variable: String,
1912        value: Value,
1913    },
1914    ModifyGlobalVariable {
1915        variable: String,
1916        op: ModifyOp,
1917        value: Value,
1918    },
1919    SetPlayerVariable {
1920        player: Value,
1921        variable: String,
1922        value: Value,
1923    },
1924    ModifyPlayerVariable {
1925        player: Value,
1926        variable: String,
1927        op: ModifyOp,
1928        value: Value,
1929    },
1930    AssignMember {
1931        target: Value,
1932        op: Option<ModifyOp>,
1933        value: Value,
1934    },
1935    CallSubroutine {
1936        subroutine: String,
1937    },
1938    If {
1939        condition: Value,
1940    },
1941    ElseIf {
1942        condition: Value,
1943    },
1944    Else,
1945    While {
1946        condition: Value,
1947    },
1948    ForGlobalVariable {
1949        variable: String,
1950        start: Value,
1951        stop: Value,
1952        step: Value,
1953    },
1954    ForPlayerVariable {
1955        player: Value,
1956        variable: String,
1957        start: Value,
1958        stop: Value,
1959        step: Value,
1960    },
1961    End,
1962    Disabled {
1963        action: Box<Action>,
1964    },
1965    Call {
1966        name: String,
1967        args: Vec<Value>,
1968    },
1969}
1970
1971impl Action {
1972    /// Mark an action as disabled.
1973    pub fn disabled(action: Action) -> Self {
1974        Self::Disabled {
1975            action: Box::new(action),
1976        }
1977    }
1978
1979    /// Construct a dynamic action call by canonical Workshop id.
1980    pub fn call(name: impl Into<String>, args: impl IntoIterator<Item = Value>) -> Self {
1981        Self::Call {
1982            name: name.into(),
1983            args: args.into_iter().collect(),
1984        }
1985    }
1986}
1987
1988/// The operation used by a Workshop variable modification action.
1989///
1990/// Workshop can add modification operations independently of this crate.
1991/// Consumers should use a wildcard arm when matching this type.
1992#[non_exhaustive]
1993#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1994pub enum ModifyOp {
1995    Add,
1996    Subtract,
1997    Multiply,
1998    Divide,
1999    Modulo,
2000    Min,
2001    Max,
2002    RaiseToPower,
2003    AppendToArray,
2004    RemoveFromArrayByValue,
2005    RemoveFromArrayByIndex,
2006}
2007
2008/// A composable Workshop value expression.
2009#[derive(Debug, Clone)]
2010pub enum Value {
2011    Number(f64),
2012    String(String),
2013    LocalizedString(String),
2014    Bool(bool),
2015    Null,
2016    Array(Vec<Value>),
2017    Vector {
2018        x: Box<Value>,
2019        y: Box<Value>,
2020        z: Box<Value>,
2021    },
2022    Enum {
2023        value_type: String,
2024        value: String,
2025    },
2026    GlobalVariable(String),
2027    PlayerVariable {
2028        player: Box<Value>,
2029        variable: String,
2030    },
2031    Subroutine(String),
2032    EventPlayer,
2033    Call {
2034        name: String,
2035        args: Vec<Value>,
2036    },
2037}
2038
2039impl Value {
2040    /// Construct a numeric Workshop literal.
2041    pub fn number(value: f64) -> Self {
2042        Self::Number(value)
2043    }
2044
2045    /// Construct a custom Workshop string literal.
2046    pub fn string(value: impl Into<String>) -> Self {
2047        Self::String(value.into())
2048    }
2049
2050    pub fn global_variable(name: impl Into<String>) -> Self {
2051        Self::GlobalVariable(name.into())
2052    }
2053
2054    pub fn player_variable(player: Value, name: impl Into<String>) -> Self {
2055        Self::PlayerVariable {
2056            player: Box::new(player),
2057            variable: name.into(),
2058        }
2059    }
2060
2061    /// Construct a dynamic value call by canonical Workshop id.
2062    pub fn call(name: impl Into<String>, args: impl IntoIterator<Item = Value>) -> Self {
2063        Self::Call {
2064            name: name.into(),
2065            args: args.into_iter().collect(),
2066        }
2067    }
2068}
2069
2070impl From<bool> for Value {
2071    fn from(value: bool) -> Self {
2072        Self::Bool(value)
2073    }
2074}
2075
2076impl From<f64> for Value {
2077    fn from(value: f64) -> Self {
2078        Self::Number(value)
2079    }
2080}
2081
2082impl From<f32> for Value {
2083    fn from(value: f32) -> Self {
2084        Self::Number(f64::from(value))
2085    }
2086}
2087
2088macro_rules! impl_integer_value {
2089    ($($type:ty),+ $(,)?) => {
2090        $(
2091            impl From<$type> for Value {
2092                fn from(value: $type) -> Self {
2093                    Self::Number(value as f64)
2094                }
2095            }
2096        )+
2097    };
2098}
2099
2100impl_integer_value!(i8, i16, i32, i64, isize, u8, u16, u32, u64, usize);
2101
2102impl From<String> for Value {
2103    fn from(value: String) -> Self {
2104        Self::String(value)
2105    }
2106}
2107
2108impl From<&str> for Value {
2109    fn from(value: &str) -> Self {
2110        Self::String(value.to_string())
2111    }
2112}
2113
2114impl<T: Into<Value>> From<Vec<T>> for Value {
2115    fn from(values: Vec<T>) -> Self {
2116        Self::Array(values.into_iter().map(Into::into).collect())
2117    }
2118}
2119
2120impl<T: Into<Value>, const N: usize> From<[T; N]> for Value {
2121    fn from(values: [T; N]) -> Self {
2122        Self::Array(values.into_iter().map(Into::into).collect())
2123    }
2124}
2125
2126include!(concat!(env!("OUT_DIR"), "/typed_api.rs"));