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