Skip to main content

workshop_rs/
program.rs

1//! Canonical Workshop program concepts.
2
3use crate::core::error::WorkshopError;
4use crate::settings::Settings;
5use crate::source::{FileId, SourceDocument, SourceFile, Span};
6
7mod conversion;
8pub(crate) mod shared;
9mod source_map;
10pub use shared::{EventTarget, EventTeam, ModifyOp, PlayerEventKind};
11pub use source_map::{MAPPED_TEXT_V1, MappedText, SourceMap, SourceMapError, TEXT_V1};
12
13/// A complete Workshop program built from Workshop concepts.
14#[derive(Debug, Clone, Default)]
15pub struct Program {
16    pub settings: Option<Settings>,
17    pub global_variables: Vec<Variable>,
18    pub player_variables: Vec<Variable>,
19    pub subroutines: Vec<Subroutine>,
20    pub rules: Vec<Rule>,
21    files: Vec<SourceFile>,
22    provenance: Option<Box<ProgramProvenance>>,
23}
24
25#[derive(Debug, Clone, Default)]
26struct ProgramProvenance {
27    global_variables: Vec<DeclarationProvenance>,
28    player_variables: Vec<DeclarationProvenance>,
29    subroutines: Vec<DeclarationProvenance>,
30    rules: Vec<RuleProvenance>,
31}
32
33#[derive(Debug, Clone, Copy, Default)]
34struct DeclarationProvenance {
35    span: Option<Span>,
36    name_span: Option<Span>,
37}
38
39#[derive(Debug, Clone, Default)]
40struct RuleProvenance {
41    span: Option<crate::source::Span>,
42    /// The recorded span of the rule's quoted name in `rule("name")`.
43    name: Option<Span>,
44    /// The recorded span of the subroutine name a `Subroutine` event binds.
45    event_name: Option<Span>,
46    conditions: Vec<ValueProvenance>,
47    actions: Vec<ActionProvenance>,
48}
49
50#[derive(Debug, Clone, Default)]
51struct ActionProvenance {
52    span: Option<Span>,
53    /// The recorded span of the variable or subroutine the action names: a
54    /// set/modify/for target or a `Call Subroutine` callee.
55    identifier: Option<Span>,
56    arguments: Vec<ValueProvenance>,
57}
58
59/// A provenance node carrying an authored span; shared by the child-slot
60/// writer used by the condition/action span setters.
61trait SpanSlot {
62    fn span_slot(&mut self) -> &mut Option<Span>;
63}
64
65impl SpanSlot for ValueProvenance {
66    fn span_slot(&mut self) -> &mut Option<Span> {
67        &mut self.span
68    }
69}
70
71impl SpanSlot for ActionProvenance {
72    fn span_slot(&mut self) -> &mut Option<Span> {
73        &mut self.span
74    }
75}
76
77/// The recorded span of one value node and its children, mirroring the
78/// structure of the public [`Value`] tree.
79#[derive(Debug, Clone, Default)]
80struct ValueProvenance {
81    span: Option<Span>,
82    /// The recorded span of the variable or subroutine identifier the value
83    /// names, when the value is such a reference.
84    identifier: Option<Span>,
85    children: Vec<ValueProvenance>,
86}
87
88/// A failure while attaching source mappings to a public [`Program`].
89#[derive(Debug, Clone, Copy, PartialEq, Eq)]
90#[non_exhaustive]
91pub enum SourceMappingError {
92    UnknownFile(FileId),
93    InvalidSpan(Span),
94    InvalidRule(usize),
95    InvalidCondition {
96        rule: usize,
97        condition: usize,
98    },
99    InvalidAction {
100        rule: usize,
101        action: usize,
102    },
103    InvalidActionArgument {
104        rule: usize,
105        action: usize,
106        argument: usize,
107    },
108    InvalidGlobalVariable(usize),
109    InvalidPlayerVariable(usize),
110    InvalidSubroutine(usize),
111}
112
113impl std::fmt::Display for SourceMappingError {
114    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
115        match self {
116            Self::UnknownFile(file) => {
117                write!(formatter, "source span references unknown file {file}")
118            }
119            Self::InvalidSpan(span) => write!(formatter, "invalid source span {span:?}"),
120            Self::InvalidRule(rule) => write!(formatter, "invalid rule index {rule}"),
121            Self::InvalidCondition { rule, condition } => {
122                write!(
123                    formatter,
124                    "invalid condition index {condition} in rule {rule}"
125                )
126            }
127            Self::InvalidAction { rule, action } => {
128                write!(formatter, "invalid action index {action} in rule {rule}")
129            }
130            Self::InvalidActionArgument {
131                rule,
132                action,
133                argument,
134            } => write!(
135                formatter,
136                "invalid argument index {argument} in action {action} of rule {rule}"
137            ),
138            Self::InvalidGlobalVariable(variable) => {
139                write!(formatter, "invalid global variable index {variable}")
140            }
141            Self::InvalidPlayerVariable(variable) => {
142                write!(formatter, "invalid player variable index {variable}")
143            }
144            Self::InvalidSubroutine(subroutine) => {
145                write!(formatter, "invalid subroutine index {subroutine}")
146            }
147        }
148    }
149}
150
151impl std::error::Error for SourceMappingError {}
152
153impl Program {
154    pub fn new() -> Self {
155        Self::default()
156    }
157
158    /// Register a source file and return its public file identity.
159    pub fn add_file(&mut self, mut file: SourceFile) -> FileId {
160        let id = FileId::from_index(self.files.len());
161        file.bind_file(id);
162        self.files.push(file);
163        id
164    }
165
166    pub fn global_variable(&mut self, variable: Variable) -> &mut Self {
167        self.global_variables.push(variable);
168        self
169    }
170
171    pub fn player_variable(&mut self, variable: Variable) -> &mut Self {
172        self.player_variables.push(variable);
173        self
174    }
175
176    pub fn subroutine(&mut self, subroutine: Subroutine) -> &mut Self {
177        self.subroutines.push(subroutine);
178        self
179    }
180
181    pub fn rule(&mut self, rule: Rule) -> &mut Self {
182        self.rules.push(rule);
183        self
184    }
185
186    /// Return the retained source document for a parsed file.
187    pub fn source(&self, file: FileId) -> Option<&SourceDocument> {
188        self.files.get(file.index()).and_then(SourceFile::source)
189    }
190
191    /// Attach the authored span of a public rule.
192    pub fn set_rule_span(
193        &mut self,
194        rule: usize,
195        span: Option<Span>,
196    ) -> std::result::Result<(), SourceMappingError> {
197        self.validate_span(span)?;
198        self.rule_provenance_mut(rule)?.span = span;
199        Ok(())
200    }
201
202    /// Attach the authored span of a public rule condition value.
203    pub fn set_condition_span(
204        &mut self,
205        rule: usize,
206        condition: usize,
207        span: Option<Span>,
208    ) -> std::result::Result<(), SourceMappingError> {
209        self.set_child_span(
210            rule,
211            condition,
212            span,
213            |rule| rule.conditions.len(),
214            |rule, index| SourceMappingError::InvalidCondition {
215                rule,
216                condition: index,
217            },
218            |rule_data| &mut rule_data.conditions,
219        )
220    }
221
222    /// Attach the authored span of a public action in its linear rule order.
223    pub fn set_action_span(
224        &mut self,
225        rule: usize,
226        action: usize,
227        span: Option<Span>,
228    ) -> std::result::Result<(), SourceMappingError> {
229        self.set_child_span(
230            rule,
231            action,
232            span,
233            |rule| rule.actions.len(),
234            |rule, index| SourceMappingError::InvalidAction {
235                rule,
236                action: index,
237            },
238            |rule_data| &mut rule_data.actions,
239        )
240    }
241
242    /// Shared span setter for condition/action provenance slots: validates the
243    /// span, bounds-checks the child index against the public rule shape, then
244    /// resizes and writes the provenance slot.
245    fn set_child_span<T: SpanSlot + Default>(
246        &mut self,
247        rule: usize,
248        index: usize,
249        span: Option<Span>,
250        count: impl Fn(&crate::Rule) -> usize,
251        invalid: impl Fn(usize, usize) -> SourceMappingError,
252        slots: impl Fn(&mut RuleProvenance) -> &mut Vec<T>,
253    ) -> std::result::Result<(), SourceMappingError> {
254        self.validate_span(span)?;
255        let child_count = self
256            .rules
257            .get(rule)
258            .ok_or(SourceMappingError::InvalidRule(rule))
259            .map(count)?;
260        if index >= child_count {
261            return Err(invalid(rule, index));
262        }
263        let rule_data = self.rule_provenance_mut(rule)?;
264        fit(slots(rule_data), child_count);
265        *slots(rule_data)[index].span_slot() = span;
266        Ok(())
267    }
268
269    /// Attach the authored span of a direct value argument of a public action.
270    pub fn set_action_argument_span(
271        &mut self,
272        rule: usize,
273        action: usize,
274        argument: usize,
275        span: Option<Span>,
276    ) -> std::result::Result<(), SourceMappingError> {
277        self.validate_span(span)?;
278        let action_value = self
279            .rules
280            .get(rule)
281            .ok_or(SourceMappingError::InvalidRule(rule))?
282            .actions
283            .get(action)
284            .ok_or(SourceMappingError::InvalidAction { rule, action })?;
285        let argument_count = action_argument_values(action_value).len();
286        if argument >= argument_count {
287            return Err(SourceMappingError::InvalidActionArgument {
288                rule,
289                action,
290                argument,
291            });
292        }
293        let action_data = self.action_provenance_mut(rule, action)?;
294        action_data
295            .arguments
296            .resize_with(argument + 1, ValueProvenance::default);
297        action_data.arguments[argument].span = span;
298        Ok(())
299    }
300
301    /// Attach the authored and identifier spans of a global variable.
302    pub fn set_global_variable_spans(
303        &mut self,
304        variable: usize,
305        span: Option<Span>,
306        name_span: Option<Span>,
307    ) -> std::result::Result<(), SourceMappingError> {
308        self.set_declaration_spans(DeclarationTable::GlobalVariables, variable, span, name_span)
309    }
310
311    /// Attach the authored and identifier spans of a player variable.
312    pub fn set_player_variable_spans(
313        &mut self,
314        variable: usize,
315        span: Option<Span>,
316        name_span: Option<Span>,
317    ) -> std::result::Result<(), SourceMappingError> {
318        self.set_declaration_spans(DeclarationTable::PlayerVariables, variable, span, name_span)
319    }
320
321    /// Attach the authored and identifier spans of a subroutine.
322    pub fn set_subroutine_spans(
323        &mut self,
324        subroutine: usize,
325        span: Option<Span>,
326        name_span: Option<Span>,
327    ) -> std::result::Result<(), SourceMappingError> {
328        self.set_declaration_spans(DeclarationTable::Subroutines, subroutine, span, name_span)
329    }
330
331    fn set_declaration_spans(
332        &mut self,
333        table: DeclarationTable,
334        index: usize,
335        span: Option<Span>,
336        name_span: Option<Span>,
337    ) -> std::result::Result<(), SourceMappingError> {
338        self.validate_span(span)?;
339        self.validate_span(name_span)?;
340        let count = table.count(self);
341        if index >= count {
342            return Err(table.invalid(index));
343        }
344        let slots = table.slots(self.provenance_mut());
345        fit(slots, count);
346        slots[index] = DeclarationProvenance { span, name_span };
347        Ok(())
348    }
349
350    /// Return the authored span of a public rule, when source metadata exists.
351    ///
352    /// Attached mappings record the program shape they were attached to. Every
353    /// span accessor returns `None` once the public rules, conditions, or
354    /// actions have been inserted or removed since the mapping was attached.
355    pub fn rule_span(&self, rule: usize) -> Option<crate::source::Span> {
356        self.rule_provenance(rule)?.span
357    }
358
359    /// Return the authored span of a public rule condition value.
360    pub fn condition_span(&self, rule: usize, condition: usize) -> Option<crate::source::Span> {
361        self.condition_provenance(rule, condition)?.span
362    }
363
364    /// Return the authored span of a public action in its linear rule order.
365    pub fn action_span(&self, rule: usize, action: usize) -> Option<crate::source::Span> {
366        self.action_provenance(rule, action)?.span
367    }
368
369    /// Return the authored span of a direct value argument of a public action.
370    pub fn action_argument_span(
371        &self,
372        rule: usize,
373        action: usize,
374        argument: usize,
375    ) -> Option<crate::source::Span> {
376        self.action_provenance(rule, action)?
377            .arguments
378            .get(argument)?
379            .span
380    }
381
382    /// Return the authored span of the declared name of a global variable.
383    ///
384    /// Source-language providers attach an explicit identifier span through
385    /// [`set_global_variable_spans`](Self::set_global_variable_spans). For raw
386    /// Workshop parses the recorded declaration span already covers exactly
387    /// the declared name and is returned as the identifier span.
388    pub fn global_variable_name_span(&self, variable: usize) -> Option<Span> {
389        self.declaration_name_span(
390            |provenance| &provenance.global_variables,
391            self.global_variables.len(),
392            variable,
393        )
394    }
395
396    /// Return the authored span of the declared name of a player variable.
397    /// See [`global_variable_name_span`](Self::global_variable_name_span).
398    pub fn player_variable_name_span(&self, variable: usize) -> Option<Span> {
399        self.declaration_name_span(
400            |provenance| &provenance.player_variables,
401            self.player_variables.len(),
402            variable,
403        )
404    }
405
406    /// Return the authored span of the declared name of a subroutine.
407    /// See [`global_variable_name_span`](Self::global_variable_name_span).
408    pub fn subroutine_name_span(&self, subroutine: usize) -> Option<Span> {
409        self.declaration_name_span(
410            |provenance| &provenance.subroutines,
411            self.subroutines.len(),
412            subroutine,
413        )
414    }
415
416    /// Return the span recorded for the variable or subroutine identifier a
417    /// public action names: the target of set/modify and for-variable actions,
418    /// or the callee of a [`Call Subroutine`](Action::CallSubroutine) action.
419    ///
420    /// Raw Workshop parses record the variable name for `Set`/`Modify`
421    /// variable actions, `For` variable loops, and `Global.name`/`Event
422    /// Player.name` infix assignments, and the callee name for `Call
423    /// Subroutine`. Indexed writes lower to `... Variable At Index` calls and
424    /// record the name on their variable argument — see
425    /// [`action_argument_value_span`](Self::action_argument_value_span).
426    /// Other action forms always return `None`.
427    pub fn action_identifier_span(&self, rule: usize, action: usize) -> Option<Span> {
428        self.action_provenance(rule, action)?.identifier
429    }
430
431    /// Return the span recorded for a rule's name inside its `rule("name")`
432    /// string, or `None` when no provenance was recorded.
433    pub fn rule_name_span(&self, rule: usize) -> Option<Span> {
434        self.rule_provenance(rule)?.name
435    }
436
437    /// Return the span recorded for the subroutine name a rule's `Subroutine`
438    /// event binding names, or `None` for other event kinds and when no
439    /// provenance was recorded.
440    pub fn rule_event_name_span(&self, rule: usize) -> Option<Span> {
441        self.rule_provenance(rule)?.event_name
442    }
443
444    /// Return the authored span of a value nested inside a public rule
445    /// condition.
446    ///
447    /// `path` walks the public [`Value`] tree: each element selects a child by
448    /// position — `Value::Array` elements and `Value::Call` arguments by
449    /// index, `Value::Vector` components as `0`/`1`/`2` for x/y/z, and a
450    /// `Value::PlayerVariable` player at `0`. An empty path returns the
451    /// condition value's own span, matching [`condition_span`](Self::condition_span).
452    ///
453    /// For a variable or subroutine reference the identifier span is returned
454    /// when the parser recorded one: raw Workshop records the variable name
455    /// for `Global.name`, `Global/Player Variable(name)`, `Event Player.name`,
456    /// bare-name, and `... At Index` argument spellings. Other nodes return
457    /// the span recorded for the node itself.
458    pub fn condition_value_span(
459        &self,
460        rule: usize,
461        condition: usize,
462        path: &[usize],
463    ) -> Option<crate::source::Span> {
464        let mut value = self.condition_provenance(rule, condition)?;
465        for &index in path {
466            value = value.children.get(index)?;
467        }
468        value.identifier.or(value.span)
469    }
470
471    /// Return the authored span of a value nested inside a direct value
472    /// argument of a public action.
473    ///
474    /// `argument` selects the same direct argument as
475    /// [`action_argument_span`](Self::action_argument_span) and `path` walks
476    /// into it the way [`condition_value_span`](Self::condition_value_span)
477    /// describes; an empty path returns the argument's own span. Like
478    /// `condition_value_span`, a variable or subroutine reference returns its
479    /// recorded identifier span.
480    pub fn action_argument_value_span(
481        &self,
482        rule: usize,
483        action: usize,
484        argument: usize,
485        path: &[usize],
486    ) -> Option<crate::source::Span> {
487        let mut value = self
488            .action_provenance(rule, action)?
489            .arguments
490            .get(argument)?;
491        for &index in path {
492            value = value.children.get(index)?;
493        }
494        value.identifier.or(value.span)
495    }
496
497    /// Create a checked source edit through the authored source attached to
498    /// this canonical program.
499    pub fn edit_source(
500        &self,
501        span: crate::source::Span,
502        replacement: impl Into<String>,
503    ) -> std::result::Result<crate::source::SourceEdit, crate::source::SourceEditError> {
504        self.source(span.file)
505            .ok_or(crate::source::SourceEditError::InvalidRange)?
506            .edit_span(span, replacement)
507    }
508
509    /// Validate the structural invariants of the canonical program.
510    ///
511    /// Settings are emission-checked too, so a program that would fail to
512    /// emit fails validation the same way.
513    pub fn validate(&self) -> std::result::Result<(), WorkshopError> {
514        let storage = self.to_wir()?;
515        storage
516            .validate()
517            .map_err(|error| WorkshopError::malformed(error.to_string(), error.span()))?;
518        if let Some(settings) = &self.settings {
519            if let Some(error) = crate::settings::check_emission(settings).into_iter().next() {
520                return Err(error);
521            }
522        }
523        Ok(())
524    }
525
526    /// Report every settings member the emission table rejects, each with
527    /// its source span and, when exactly one canonical spelling is close
528    /// enough, the structured suggestion a caller can apply. [`validate`]
529    /// already fails on the first of these; this exposes all of them.
530    ///
531    /// [`validate`]: Program::validate
532    pub fn settings_diagnostics(&self) -> Vec<crate::settings::SettingsDiagnostic> {
533        self.settings
534            .as_ref()
535            .map(crate::settings::check_emission_diagnostics)
536            .unwrap_or_default()
537    }
538
539    /// Report constructs that are structurally preserved but not fully
540    /// understood by the canonical catalog.
541    pub fn semantic_issues(
542        &self,
543        catalog: &crate::catalog::Catalog,
544    ) -> Vec<crate::rules::SemanticIssue> {
545        crate::analysis::semantic::inspect(self, catalog)
546    }
547
548    /// Render the program through the canonical Workshop debug representation.
549    pub fn dump(&self) -> String {
550        self.to_wir().map_or_else(
551            |error| format!("invalid program: {error}"),
552            |program| program.dump(),
553        )
554    }
555
556    fn rule_provenance(&self, rule: usize) -> Option<&RuleProvenance> {
557        let recorded = &self.provenance.as_deref()?.rules;
558        if recorded.len() != self.rules.len() {
559            return None;
560        }
561        recorded.get(rule)
562    }
563
564    fn action_provenance(&self, rule: usize, action: usize) -> Option<&ActionProvenance> {
565        let recorded = self.rule_provenance(rule)?;
566        if recorded.actions.len() != self.rules[rule].actions.len() {
567            return None;
568        }
569        recorded.actions.get(action)
570    }
571
572    fn condition_provenance(&self, rule: usize, condition: usize) -> Option<&ValueProvenance> {
573        let recorded = self.rule_provenance(rule)?;
574        if recorded.conditions.len() != self.rules[rule].conditions.len() {
575            return None;
576        }
577        recorded.conditions.get(condition)
578    }
579
580    fn declaration_name_span(
581        &self,
582        recorded: impl Fn(&ProgramProvenance) -> &[DeclarationProvenance],
583        count: usize,
584        position: usize,
585    ) -> Option<Span> {
586        let declaration = self.declaration_provenance(recorded, count, position);
587        declaration.name_span.or(declaration.span)
588    }
589
590    fn declaration_provenance(
591        &self,
592        recorded: impl Fn(&ProgramProvenance) -> &[DeclarationProvenance],
593        count: usize,
594        position: usize,
595    ) -> DeclarationProvenance {
596        self.provenance
597            .as_deref()
598            .map(recorded)
599            .filter(|recorded| recorded.len() == count)
600            .and_then(|recorded| recorded.get(position))
601            .copied()
602            .unwrap_or_default()
603    }
604
605    fn validate_span(&self, span: Option<Span>) -> std::result::Result<(), SourceMappingError> {
606        let Some(span) = span else {
607            return Ok(());
608        };
609        if !span.is_valid() {
610            return Err(SourceMappingError::InvalidSpan(span));
611        }
612        if self.files.get(span.file.index()).is_none() {
613            return Err(SourceMappingError::UnknownFile(span.file));
614        }
615        Ok(())
616    }
617
618    fn provenance_mut(&mut self) -> &mut ProgramProvenance {
619        self.provenance
620            .get_or_insert_with(|| Box::new(ProgramProvenance::default()))
621            .as_mut()
622    }
623
624    fn rule_provenance_mut(
625        &mut self,
626        rule: usize,
627    ) -> std::result::Result<&mut RuleProvenance, SourceMappingError> {
628        if rule >= self.rules.len() {
629            return Err(SourceMappingError::InvalidRule(rule));
630        }
631        let rule_count = self.rules.len();
632        let provenance = self.provenance_mut();
633        fit(&mut provenance.rules, rule_count);
634        Ok(&mut provenance.rules[rule])
635    }
636
637    fn action_provenance_mut(
638        &mut self,
639        rule: usize,
640        action: usize,
641    ) -> std::result::Result<&mut ActionProvenance, SourceMappingError> {
642        let action_count = self
643            .rules
644            .get(rule)
645            .ok_or(SourceMappingError::InvalidRule(rule))?
646            .actions
647            .len();
648        if action >= action_count {
649            return Err(SourceMappingError::InvalidAction { rule, action });
650        }
651        let rule_data = self.rule_provenance_mut(rule)?;
652        fit(&mut rule_data.actions, action_count);
653        Ok(&mut rule_data.actions[action])
654    }
655}
656
657fn fit<T: Default>(items: &mut Vec<T>, len: usize) {
658    items.truncate(len);
659    items.resize_with(len, T::default);
660}
661
662/// Selects one declaration-provenance table so the three span setters share
663/// their validate-bounds-fit-assign skeleton.
664#[derive(Copy, Clone)]
665enum DeclarationTable {
666    GlobalVariables,
667    PlayerVariables,
668    Subroutines,
669}
670
671impl DeclarationTable {
672    fn count(self, program: &Program) -> usize {
673        match self {
674            Self::GlobalVariables => program.global_variables.len(),
675            Self::PlayerVariables => program.player_variables.len(),
676            Self::Subroutines => program.subroutines.len(),
677        }
678    }
679
680    fn invalid(self, index: usize) -> SourceMappingError {
681        match self {
682            Self::GlobalVariables => SourceMappingError::InvalidGlobalVariable(index),
683            Self::PlayerVariables => SourceMappingError::InvalidPlayerVariable(index),
684            Self::Subroutines => SourceMappingError::InvalidSubroutine(index),
685        }
686    }
687
688    fn slots(self, provenance: &mut ProgramProvenance) -> &mut Vec<DeclarationProvenance> {
689        match self {
690            Self::GlobalVariables => &mut provenance.global_variables,
691            Self::PlayerVariables => &mut provenance.player_variables,
692            Self::Subroutines => &mut provenance.subroutines,
693        }
694    }
695}
696
697/// A Workshop global or player variable declaration.
698#[derive(Debug, Clone, PartialEq, Eq)]
699pub struct Variable {
700    pub name: String,
701    /// The raw Workshop declaration index, when the declaration has one.
702    pub index: Option<u32>,
703}
704
705impl Variable {
706    pub fn new(name: impl Into<String>) -> Self {
707        Self {
708            name: name.into(),
709            index: None,
710        }
711    }
712
713    pub fn with_index(name: impl Into<String>, index: u32) -> Self {
714        Self {
715            name: name.into(),
716            index: Some(index),
717        }
718    }
719}
720
721/// A Workshop subroutine declaration.
722#[derive(Debug, Clone, PartialEq, Eq)]
723pub struct Subroutine {
724    pub name: String,
725    /// The raw Workshop declaration index, when the declaration has one.
726    pub index: Option<u32>,
727}
728
729impl Subroutine {
730    pub fn new(name: impl Into<String>) -> Self {
731        Self {
732            name: name.into(),
733            index: None,
734        }
735    }
736
737    pub fn with_index(name: impl Into<String>, index: u32) -> Self {
738        Self {
739            name: name.into(),
740            index: Some(index),
741        }
742    }
743}
744
745/// A Workshop rule with explicit conditions and a linear action stream.
746#[derive(Debug, Clone)]
747#[non_exhaustive]
748pub struct Rule {
749    pub name: String,
750    pub disabled: bool,
751    pub event: Event,
752    pub conditions: Vec<Condition>,
753    pub actions: Vec<Action>,
754}
755
756impl Rule {
757    pub fn new(name: impl Into<String>, event: Event) -> Self {
758        Self {
759            name: name.into(),
760            disabled: false,
761            event,
762            conditions: Vec::new(),
763            actions: Vec::new(),
764        }
765    }
766
767    pub fn condition(mut self, condition: impl Into<Condition>) -> Self {
768        self.conditions.push(condition.into());
769        self
770    }
771
772    pub fn action(mut self, action: Action) -> Self {
773        self.actions.push(action);
774        self
775    }
776}
777
778/// A rule condition. Conditions remain distinct from general value expressions.
779#[derive(Debug, Clone)]
780#[non_exhaustive]
781pub struct Condition {
782    pub value: Value,
783    pub disabled: bool,
784}
785
786impl Condition {
787    pub fn new(value: Value) -> Self {
788        Self {
789            value,
790            disabled: false,
791        }
792    }
793
794    pub fn disabled(value: Value) -> Self {
795        Self {
796            value,
797            disabled: true,
798        }
799    }
800}
801
802impl From<Value> for Condition {
803    fn from(value: Value) -> Self {
804        Self::new(value)
805    }
806}
807
808/// The direct value arguments of a public action, in their mapped order.
809/// Provenance rows pair these positionally with `wir::Action::value_args`.
810fn action_argument_values(action: &Action) -> Vec<&Value> {
811    match action {
812        Action::SetGlobalVariable { value, .. } | Action::ModifyGlobalVariable { value, .. } => {
813            vec![value]
814        }
815        Action::SetPlayerVariable { player, value, .. }
816        | Action::ModifyPlayerVariable { player, value, .. } => vec![player, value],
817        Action::AssignMember { target, value, .. } => vec![target, value],
818        Action::If { condition } | Action::ElseIf { condition } | Action::While { condition } => {
819            vec![condition]
820        }
821        Action::ForGlobalVariable {
822            start, stop, step, ..
823        } => vec![start, stop, step],
824        Action::ForPlayerVariable {
825            player,
826            start,
827            stop,
828            step,
829            ..
830        } => vec![player, start, stop, step],
831        Action::Call { args, .. } => args.iter().collect(),
832        Action::CallSubroutine { .. } | Action::Else | Action::End => Vec::new(),
833        Action::Disabled { action } => action_argument_values(action),
834    }
835}
836
837/// The children a public value exposes to provenance addressing, in the order
838/// [`Program::condition_value_span`] documents.
839fn value_children(value: &Value) -> Vec<&Value> {
840    match value {
841        Value::Array(values) => values.iter().collect(),
842        Value::Vector { x, y, z } => vec![x.as_ref(), y.as_ref(), z.as_ref()],
843        Value::PlayerVariable { player, .. } => vec![player.as_ref()],
844        Value::Call { args, .. } => args.iter().collect(),
845        _ => Vec::new(),
846    }
847}
848
849/// A Workshop event identity and its native filters.
850#[derive(Debug, Clone, PartialEq, Eq)]
851pub enum Event {
852    Global,
853    EachPlayer,
854    EachPlayerWithFilters {
855        team: EventTeam,
856        target: EventTarget,
857    },
858    Player {
859        kind: PlayerEventKind,
860        team: EventTeam,
861        target: EventTarget,
862    },
863    Subroutine(String),
864}
865
866/// A Workshop action line. Control flow is represented in the same order as
867/// the Workshop source, including its explicit `End` lines.
868#[derive(Debug, Clone)]
869pub enum Action {
870    SetGlobalVariable {
871        variable: String,
872        value: Value,
873    },
874    ModifyGlobalVariable {
875        variable: String,
876        op: ModifyOp,
877        value: Value,
878    },
879    SetPlayerVariable {
880        player: Value,
881        variable: String,
882        value: Value,
883    },
884    ModifyPlayerVariable {
885        player: Value,
886        variable: String,
887        op: ModifyOp,
888        value: Value,
889    },
890    AssignMember {
891        target: Value,
892        op: Option<ModifyOp>,
893        value: Value,
894    },
895    CallSubroutine {
896        subroutine: String,
897    },
898    If {
899        condition: Value,
900    },
901    ElseIf {
902        condition: Value,
903    },
904    Else,
905    While {
906        condition: Value,
907    },
908    ForGlobalVariable {
909        variable: String,
910        start: Value,
911        stop: Value,
912        step: Value,
913    },
914    ForPlayerVariable {
915        player: Value,
916        variable: String,
917        start: Value,
918        stop: Value,
919        step: Value,
920    },
921    End,
922    Disabled {
923        action: Box<Action>,
924    },
925    Call {
926        name: String,
927        args: Vec<Value>,
928    },
929}
930
931impl Action {
932    /// Mark an action as disabled.
933    pub fn disabled(action: Action) -> Self {
934        Self::Disabled {
935            action: Box::new(action),
936        }
937    }
938
939    /// Construct a dynamic action call by canonical Workshop id.
940    pub fn call(name: impl Into<String>, args: impl IntoIterator<Item = Value>) -> Self {
941        Self::Call {
942            name: name.into(),
943            args: args.into_iter().collect(),
944        }
945    }
946}
947
948/// A composable Workshop value expression.
949#[derive(Debug, Clone)]
950pub enum Value {
951    Number(f64),
952    String(String),
953    LocalizedString(String),
954    Bool(bool),
955    Null,
956    Array(Vec<Value>),
957    Vector {
958        x: Box<Value>,
959        y: Box<Value>,
960        z: Box<Value>,
961    },
962    Enum {
963        value_type: String,
964        value: String,
965    },
966    GlobalVariable(String),
967    PlayerVariable {
968        player: Box<Value>,
969        variable: String,
970    },
971    Subroutine(String),
972    EventPlayer,
973    Call {
974        name: String,
975        args: Vec<Value>,
976    },
977}
978
979impl Value {
980    /// Construct a numeric Workshop literal.
981    pub fn number(value: f64) -> Self {
982        Self::Number(value)
983    }
984
985    /// Construct a custom Workshop string literal.
986    pub fn string(value: impl Into<String>) -> Self {
987        Self::String(value.into())
988    }
989
990    pub fn global_variable(name: impl Into<String>) -> Self {
991        Self::GlobalVariable(name.into())
992    }
993
994    pub fn player_variable(player: Value, name: impl Into<String>) -> Self {
995        Self::PlayerVariable {
996            player: Box::new(player),
997            variable: name.into(),
998        }
999    }
1000
1001    /// Construct a dynamic value call by canonical Workshop id.
1002    pub fn call(name: impl Into<String>, args: impl IntoIterator<Item = Value>) -> Self {
1003        Self::Call {
1004            name: name.into(),
1005            args: args.into_iter().collect(),
1006        }
1007    }
1008}
1009
1010impl From<bool> for Value {
1011    fn from(value: bool) -> Self {
1012        Self::Bool(value)
1013    }
1014}
1015
1016impl From<f64> for Value {
1017    fn from(value: f64) -> Self {
1018        Self::Number(value)
1019    }
1020}
1021
1022impl From<f32> for Value {
1023    fn from(value: f32) -> Self {
1024        Self::Number(f64::from(value))
1025    }
1026}
1027
1028macro_rules! impl_integer_value {
1029    ($($type:ty),+ $(,)?) => {
1030        $(
1031            impl From<$type> for Value {
1032                fn from(value: $type) -> Self {
1033                    Self::Number(value as f64)
1034                }
1035            }
1036        )+
1037    };
1038}
1039
1040impl_integer_value!(i8, i16, i32, i64, isize, u8, u16, u32, u64, usize);
1041
1042impl From<String> for Value {
1043    fn from(value: String) -> Self {
1044        Self::String(value)
1045    }
1046}
1047
1048impl From<&str> for Value {
1049    fn from(value: &str) -> Self {
1050        Self::String(value.to_string())
1051    }
1052}
1053
1054impl<T: Into<Value>> From<Vec<T>> for Value {
1055    fn from(values: Vec<T>) -> Self {
1056        Self::Array(values.into_iter().map(Into::into).collect())
1057    }
1058}
1059
1060impl<T: Into<Value>, const N: usize> From<[T; N]> for Value {
1061    fn from(values: [T; N]) -> Self {
1062        Self::Array(values.into_iter().map(Into::into).collect())
1063    }
1064}