Skip to main content

workshop_rs/
program.rs

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