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_count(action_value);
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    pub fn validate(&self) -> std::result::Result<(), WorkshopError> {
511        let storage = self.to_wir()?;
512        storage
513            .validate()
514            .map_err(|error| WorkshopError::Malformed {
515                message: error.to_string(),
516                span: error.span(),
517            })
518    }
519
520    /// Report constructs that are structurally preserved but not fully
521    /// understood by the canonical catalog.
522    pub fn semantic_issues(
523        &self,
524        catalog: &crate::catalog::Catalog,
525    ) -> Vec<crate::rules::SemanticIssue> {
526        crate::analysis::semantic::inspect(self, catalog)
527    }
528
529    /// Render the program through the canonical Workshop debug representation.
530    pub fn dump(&self) -> String {
531        self.to_wir().map_or_else(
532            |error| format!("invalid program: {error}"),
533            |program| program.dump(),
534        )
535    }
536
537    fn rule_provenance(&self, rule: usize) -> Option<&RuleProvenance> {
538        let recorded = &self.provenance.as_deref()?.rules;
539        if recorded.len() != self.rules.len() {
540            return None;
541        }
542        recorded.get(rule)
543    }
544
545    fn action_provenance(&self, rule: usize, action: usize) -> Option<&ActionProvenance> {
546        let recorded = self.rule_provenance(rule)?;
547        if recorded.actions.len() != self.rules[rule].actions.len() {
548            return None;
549        }
550        recorded.actions.get(action)
551    }
552
553    fn condition_provenance(&self, rule: usize, condition: usize) -> Option<&ValueProvenance> {
554        let recorded = self.rule_provenance(rule)?;
555        if recorded.conditions.len() != self.rules[rule].conditions.len() {
556            return None;
557        }
558        recorded.conditions.get(condition)
559    }
560
561    fn declaration_name_span(
562        &self,
563        recorded: impl Fn(&ProgramProvenance) -> &[DeclarationProvenance],
564        count: usize,
565        position: usize,
566    ) -> Option<Span> {
567        let declaration = self.declaration_provenance(recorded, count, position);
568        declaration.name_span.or(declaration.span)
569    }
570
571    fn declaration_provenance(
572        &self,
573        recorded: impl Fn(&ProgramProvenance) -> &[DeclarationProvenance],
574        count: usize,
575        position: usize,
576    ) -> DeclarationProvenance {
577        self.provenance
578            .as_deref()
579            .map(recorded)
580            .filter(|recorded| recorded.len() == count)
581            .and_then(|recorded| recorded.get(position))
582            .copied()
583            .unwrap_or_default()
584    }
585
586    fn validate_span(&self, span: Option<Span>) -> std::result::Result<(), SourceMappingError> {
587        let Some(span) = span else {
588            return Ok(());
589        };
590        if !span.is_valid() {
591            return Err(SourceMappingError::InvalidSpan(span));
592        }
593        if self.files.get(span.file.index()).is_none() {
594            return Err(SourceMappingError::UnknownFile(span.file));
595        }
596        Ok(())
597    }
598
599    fn provenance_mut(&mut self) -> &mut ProgramProvenance {
600        self.provenance
601            .get_or_insert_with(|| Box::new(ProgramProvenance::default()))
602            .as_mut()
603    }
604
605    fn rule_provenance_mut(
606        &mut self,
607        rule: usize,
608    ) -> std::result::Result<&mut RuleProvenance, SourceMappingError> {
609        if rule >= self.rules.len() {
610            return Err(SourceMappingError::InvalidRule(rule));
611        }
612        let rule_count = self.rules.len();
613        let provenance = self.provenance_mut();
614        fit(&mut provenance.rules, rule_count);
615        Ok(&mut provenance.rules[rule])
616    }
617
618    fn action_provenance_mut(
619        &mut self,
620        rule: usize,
621        action: usize,
622    ) -> std::result::Result<&mut ActionProvenance, SourceMappingError> {
623        let action_count = self
624            .rules
625            .get(rule)
626            .ok_or(SourceMappingError::InvalidRule(rule))?
627            .actions
628            .len();
629        if action >= action_count {
630            return Err(SourceMappingError::InvalidAction { rule, action });
631        }
632        let rule_data = self.rule_provenance_mut(rule)?;
633        fit(&mut rule_data.actions, action_count);
634        Ok(&mut rule_data.actions[action])
635    }
636}
637
638fn fit<T: Default>(items: &mut Vec<T>, len: usize) {
639    items.truncate(len);
640    items.resize_with(len, T::default);
641}
642
643/// Selects one declaration-provenance table so the three span setters share
644/// their validate-bounds-fit-assign skeleton.
645#[derive(Copy, Clone)]
646enum DeclarationTable {
647    GlobalVariables,
648    PlayerVariables,
649    Subroutines,
650}
651
652impl DeclarationTable {
653    fn count(self, program: &Program) -> usize {
654        match self {
655            Self::GlobalVariables => program.global_variables.len(),
656            Self::PlayerVariables => program.player_variables.len(),
657            Self::Subroutines => program.subroutines.len(),
658        }
659    }
660
661    fn invalid(self, index: usize) -> SourceMappingError {
662        match self {
663            Self::GlobalVariables => SourceMappingError::InvalidGlobalVariable(index),
664            Self::PlayerVariables => SourceMappingError::InvalidPlayerVariable(index),
665            Self::Subroutines => SourceMappingError::InvalidSubroutine(index),
666        }
667    }
668
669    fn slots(self, provenance: &mut ProgramProvenance) -> &mut Vec<DeclarationProvenance> {
670        match self {
671            Self::GlobalVariables => &mut provenance.global_variables,
672            Self::PlayerVariables => &mut provenance.player_variables,
673            Self::Subroutines => &mut provenance.subroutines,
674        }
675    }
676}
677
678/// A Workshop global or player variable declaration.
679#[derive(Debug, Clone, PartialEq, Eq)]
680pub struct Variable {
681    pub name: String,
682    /// The raw Workshop declaration index, when the declaration has one.
683    pub index: Option<u32>,
684}
685
686impl Variable {
687    pub fn new(name: impl Into<String>) -> Self {
688        Self {
689            name: name.into(),
690            index: None,
691        }
692    }
693
694    pub fn with_index(name: impl Into<String>, index: u32) -> Self {
695        Self {
696            name: name.into(),
697            index: Some(index),
698        }
699    }
700}
701
702/// A Workshop subroutine declaration.
703#[derive(Debug, Clone, PartialEq, Eq)]
704pub struct Subroutine {
705    pub name: String,
706    /// The raw Workshop declaration index, when the declaration has one.
707    pub index: Option<u32>,
708}
709
710impl Subroutine {
711    pub fn new(name: impl Into<String>) -> Self {
712        Self {
713            name: name.into(),
714            index: None,
715        }
716    }
717
718    pub fn with_index(name: impl Into<String>, index: u32) -> Self {
719        Self {
720            name: name.into(),
721            index: Some(index),
722        }
723    }
724}
725
726/// A Workshop rule with explicit conditions and a linear action stream.
727#[derive(Debug, Clone)]
728#[non_exhaustive]
729pub struct Rule {
730    pub name: String,
731    pub disabled: bool,
732    pub event: Event,
733    pub conditions: Vec<Condition>,
734    pub actions: Vec<Action>,
735}
736
737impl Rule {
738    pub fn new(name: impl Into<String>, event: Event) -> Self {
739        Self {
740            name: name.into(),
741            disabled: false,
742            event,
743            conditions: Vec::new(),
744            actions: Vec::new(),
745        }
746    }
747
748    pub fn condition(mut self, condition: impl Into<Condition>) -> Self {
749        self.conditions.push(condition.into());
750        self
751    }
752
753    pub fn action(mut self, action: Action) -> Self {
754        self.actions.push(action);
755        self
756    }
757}
758
759/// A rule condition. Conditions remain distinct from general value expressions.
760#[derive(Debug, Clone)]
761#[non_exhaustive]
762pub struct Condition {
763    pub value: Value,
764    pub disabled: bool,
765}
766
767impl Condition {
768    pub fn new(value: Value) -> Self {
769        Self {
770            value,
771            disabled: false,
772        }
773    }
774
775    pub fn disabled(value: Value) -> Self {
776        Self {
777            value,
778            disabled: true,
779        }
780    }
781}
782
783impl From<Value> for Condition {
784    fn from(value: Value) -> Self {
785        Self::new(value)
786    }
787}
788
789/// The direct value arguments of a public action, in their mapped order.
790fn action_argument_values(action: &Action) -> Vec<&Value> {
791    match action {
792        Action::SetGlobalVariable { value, .. } | Action::ModifyGlobalVariable { value, .. } => {
793            vec![value]
794        }
795        Action::SetPlayerVariable { player, value, .. }
796        | Action::ModifyPlayerVariable { player, value, .. } => vec![player, value],
797        Action::AssignMember { target, value, .. } => vec![target, value],
798        Action::If { condition } | Action::ElseIf { condition } | Action::While { condition } => {
799            vec![condition]
800        }
801        Action::ForGlobalVariable {
802            start, stop, step, ..
803        } => vec![start, stop, step],
804        Action::ForPlayerVariable {
805            player,
806            start,
807            stop,
808            step,
809            ..
810        } => vec![player, start, stop, step],
811        Action::Call { args, .. } => args.iter().collect(),
812        Action::CallSubroutine { .. } | Action::Else | Action::End => Vec::new(),
813        Action::Disabled { action } => action_argument_values(action),
814    }
815}
816
817/// The children a public value exposes to provenance addressing, in the order
818/// [`Program::condition_value_span`] documents.
819fn value_children(value: &Value) -> Vec<&Value> {
820    match value {
821        Value::Array(values) => values.iter().collect(),
822        Value::Vector { x, y, z } => vec![x.as_ref(), y.as_ref(), z.as_ref()],
823        Value::PlayerVariable { player, .. } => vec![player.as_ref()],
824        Value::Call { args, .. } => args.iter().collect(),
825        _ => Vec::new(),
826    }
827}
828
829fn action_argument_count(action: &Action) -> usize {
830    match action {
831        Action::SetGlobalVariable { .. }
832        | Action::ModifyGlobalVariable { .. }
833        | Action::If { .. }
834        | Action::ElseIf { .. }
835        | Action::While { .. } => 1,
836        Action::SetPlayerVariable { .. } | Action::ModifyPlayerVariable { .. } => 2,
837        Action::AssignMember { .. } => 2,
838        Action::ForGlobalVariable { .. } => 3,
839        Action::ForPlayerVariable { .. } => 4,
840        Action::Call { args, .. } => args.len(),
841        Action::CallSubroutine { .. } | Action::Else | Action::End => 0,
842        Action::Disabled { action } => action_argument_count(action),
843    }
844}
845
846/// A Workshop event identity and its native filters.
847#[derive(Debug, Clone, PartialEq, Eq)]
848pub enum Event {
849    Global,
850    EachPlayer,
851    EachPlayerWithFilters {
852        team: EventTeam,
853        target: EventTarget,
854    },
855    Player {
856        kind: PlayerEventKind,
857        team: EventTeam,
858        target: EventTarget,
859    },
860    Subroutine(String),
861}
862
863/// A Workshop action line. Control flow is represented in the same order as
864/// the Workshop source, including its explicit `End` lines.
865#[derive(Debug, Clone)]
866pub enum Action {
867    SetGlobalVariable {
868        variable: String,
869        value: Value,
870    },
871    ModifyGlobalVariable {
872        variable: String,
873        op: ModifyOp,
874        value: Value,
875    },
876    SetPlayerVariable {
877        player: Value,
878        variable: String,
879        value: Value,
880    },
881    ModifyPlayerVariable {
882        player: Value,
883        variable: String,
884        op: ModifyOp,
885        value: Value,
886    },
887    AssignMember {
888        target: Value,
889        op: Option<ModifyOp>,
890        value: Value,
891    },
892    CallSubroutine {
893        subroutine: String,
894    },
895    If {
896        condition: Value,
897    },
898    ElseIf {
899        condition: Value,
900    },
901    Else,
902    While {
903        condition: Value,
904    },
905    ForGlobalVariable {
906        variable: String,
907        start: Value,
908        stop: Value,
909        step: Value,
910    },
911    ForPlayerVariable {
912        player: Value,
913        variable: String,
914        start: Value,
915        stop: Value,
916        step: Value,
917    },
918    End,
919    Disabled {
920        action: Box<Action>,
921    },
922    Call {
923        name: String,
924        args: Vec<Value>,
925    },
926}
927
928impl Action {
929    /// Mark an action as disabled.
930    pub fn disabled(action: Action) -> Self {
931        Self::Disabled {
932            action: Box::new(action),
933        }
934    }
935
936    /// Construct a dynamic action call by canonical Workshop id.
937    pub fn call(name: impl Into<String>, args: impl IntoIterator<Item = Value>) -> Self {
938        Self::Call {
939            name: name.into(),
940            args: args.into_iter().collect(),
941        }
942    }
943}
944
945/// A composable Workshop value expression.
946#[derive(Debug, Clone)]
947pub enum Value {
948    Number(f64),
949    String(String),
950    LocalizedString(String),
951    Bool(bool),
952    Null,
953    Array(Vec<Value>),
954    Vector {
955        x: Box<Value>,
956        y: Box<Value>,
957        z: Box<Value>,
958    },
959    Enum {
960        value_type: String,
961        value: String,
962    },
963    GlobalVariable(String),
964    PlayerVariable {
965        player: Box<Value>,
966        variable: String,
967    },
968    Subroutine(String),
969    EventPlayer,
970    Call {
971        name: String,
972        args: Vec<Value>,
973    },
974}
975
976impl Value {
977    /// Construct a numeric Workshop literal.
978    pub fn number(value: f64) -> Self {
979        Self::Number(value)
980    }
981
982    /// Construct a custom Workshop string literal.
983    pub fn string(value: impl Into<String>) -> Self {
984        Self::String(value.into())
985    }
986
987    pub fn global_variable(name: impl Into<String>) -> Self {
988        Self::GlobalVariable(name.into())
989    }
990
991    pub fn player_variable(player: Value, name: impl Into<String>) -> Self {
992        Self::PlayerVariable {
993            player: Box::new(player),
994            variable: name.into(),
995        }
996    }
997
998    /// Construct a dynamic value call by canonical Workshop id.
999    pub fn call(name: impl Into<String>, args: impl IntoIterator<Item = Value>) -> Self {
1000        Self::Call {
1001            name: name.into(),
1002            args: args.into_iter().collect(),
1003        }
1004    }
1005}
1006
1007impl From<bool> for Value {
1008    fn from(value: bool) -> Self {
1009        Self::Bool(value)
1010    }
1011}
1012
1013impl From<f64> for Value {
1014    fn from(value: f64) -> Self {
1015        Self::Number(value)
1016    }
1017}
1018
1019impl From<f32> for Value {
1020    fn from(value: f32) -> Self {
1021        Self::Number(f64::from(value))
1022    }
1023}
1024
1025macro_rules! impl_integer_value {
1026    ($($type:ty),+ $(,)?) => {
1027        $(
1028            impl From<$type> for Value {
1029                fn from(value: $type) -> Self {
1030                    Self::Number(value as f64)
1031                }
1032            }
1033        )+
1034    };
1035}
1036
1037impl_integer_value!(i8, i16, i32, i64, isize, u8, u16, u32, u64, usize);
1038
1039impl From<String> for Value {
1040    fn from(value: String) -> Self {
1041        Self::String(value)
1042    }
1043}
1044
1045impl From<&str> for Value {
1046    fn from(value: &str) -> Self {
1047        Self::String(value.to_string())
1048    }
1049}
1050
1051impl<T: Into<Value>> From<Vec<T>> for Value {
1052    fn from(values: Vec<T>) -> Self {
1053        Self::Array(values.into_iter().map(Into::into).collect())
1054    }
1055}
1056
1057impl<T: Into<Value>, const N: usize> From<[T; N]> for Value {
1058    fn from(values: [T; N]) -> Self {
1059        Self::Array(values.into_iter().map(Into::into).collect())
1060    }
1061}