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, each with
639    /// its source span and, when exactly one canonical spelling is close
640    /// enough, the structured suggestion a caller can apply. [`validate`]
641    /// already fails on the first of these; this exposes all of them.
642    ///
643    /// [`validate`]: Program::validate
644    pub fn settings_diagnostics(&self) -> Vec<crate::settings::SettingsDiagnostic> {
645        self.settings
646            .as_ref()
647            .map(crate::settings::check_emission_diagnostics)
648            .unwrap_or_default()
649    }
650
651    /// Report constructs that are structurally preserved but not fully
652    /// understood by the canonical catalog.
653    ///
654    /// Residual inspection is defined over this public canonical model and is
655    /// independent from [`validate`](Self::validate): it may be used on
656    /// programs `validate` rejects, and an internal materialization failure
657    /// does not suppress observable residuals. Callers that also require
658    /// structural validity call `validate` separately; this inventory does
659    /// not report validation errors.
660    pub fn semantic_issues(
661        &self,
662        catalog: &crate::catalog::Catalog,
663    ) -> Vec<crate::rules::SemanticIssue> {
664        crate::analysis::semantic::inspect(self, catalog)
665    }
666
667    /// Render the program through the canonical Workshop debug representation.
668    pub fn dump(&self) -> String {
669        self.to_wir().map_or_else(
670            |error| format!("invalid program: {error}"),
671            |program| program.dump(),
672        )
673    }
674
675    /// The recorded provenance row for a rule, while the rule table still
676    /// matches the public rule count. Used both for the record's own fields
677    /// — [`rule_provenance`] adds the identity check — and for descending
678    /// into its condition and action tables, which carry records of their
679    /// own.
680    fn rule_record(&self, rule: usize) -> Option<&RuleProvenance> {
681        let recorded = &self.provenance.as_deref()?.rules;
682        if recorded.len() != self.rules.len() {
683            return None;
684        }
685        recorded.get(rule)
686    }
687
688    /// The recorded provenance of a rule's own fields: the record is
689    /// returned only while the rule at that position still has the recorded
690    /// identity.
691    fn rule_provenance(&self, rule: usize) -> Option<&RuleProvenance> {
692        self.rule_record(rule)
693            .filter(|record| record.identity == rule_identity(&self.rules[rule]))
694    }
695
696    fn action_record(&self, rule: usize, action: usize) -> Option<&ActionProvenance> {
697        let recorded = self.rule_record(rule)?;
698        if recorded.actions.len() != self.rules[rule].actions.len() {
699            return None;
700        }
701        recorded.actions.get(action)
702    }
703
704    /// The recorded provenance of an action's own fields: the record is
705    /// returned only while the action at that position still has the
706    /// recorded identity.
707    fn action_provenance(&self, rule: usize, action: usize) -> Option<&ActionProvenance> {
708        self.action_record(rule, action)
709            .filter(|record| record.identity == action_identity(&self.rules[rule].actions[action]))
710    }
711
712    fn argument_record(
713        &self,
714        rule: usize,
715        action: usize,
716        argument: usize,
717    ) -> Option<&ValueProvenance> {
718        let recorded = self.action_record(rule, action)?;
719        let values = action_argument_values(&self.rules[rule].actions[action]);
720        if recorded.arguments.len() != values.len() {
721            return None;
722        }
723        recorded.arguments.get(argument)
724    }
725
726    /// The recorded provenance of an action argument's own fields: the
727    /// record is returned only while the argument value at that position
728    /// still has the recorded identity.
729    fn action_argument_provenance(
730        &self,
731        rule: usize,
732        action: usize,
733        argument: usize,
734    ) -> Option<&ValueProvenance> {
735        let record = self.argument_record(rule, action, argument)?;
736        let values = action_argument_values(&self.rules[rule].actions[action]);
737        (record.identity == value_identity(values[argument])).then_some(record)
738    }
739
740    fn condition_record(&self, rule: usize, condition: usize) -> Option<&ValueProvenance> {
741        let recorded = self.rule_record(rule)?;
742        if recorded.conditions.len() != self.rules[rule].conditions.len() {
743            return None;
744        }
745        recorded.conditions.get(condition)
746    }
747
748    /// The recorded provenance of a condition's own fields: the record is
749    /// returned only while the condition at that position still has the
750    /// recorded identity.
751    fn condition_provenance(&self, rule: usize, condition: usize) -> Option<&ValueProvenance> {
752        self.condition_record(rule, condition).filter(|record| {
753            record.identity == condition_identity(&self.rules[rule].conditions[condition])
754        })
755    }
756
757    fn declaration_name_span<T: std::fmt::Debug>(
758        &self,
759        recorded: impl Fn(&ProgramProvenance) -> &[DeclarationProvenance],
760        nodes: &[T],
761        position: usize,
762    ) -> Option<Span> {
763        let declaration = self.declaration_provenance(recorded, nodes, position);
764        declaration.name_span.or(declaration.span)
765    }
766
767    fn declaration_provenance<T: std::fmt::Debug>(
768        &self,
769        recorded: impl Fn(&ProgramProvenance) -> &[DeclarationProvenance],
770        nodes: &[T],
771        position: usize,
772    ) -> DeclarationProvenance {
773        self.provenance
774            .as_deref()
775            .map(recorded)
776            .filter(|recorded| recorded.len() == nodes.len())
777            .and_then(|recorded| recorded.get(position).zip(nodes.get(position)))
778            .filter(|(record, node)| record.identity == declaration_identity(node))
779            .map(|(record, _)| *record)
780            .unwrap_or_default()
781    }
782
783    fn validate_span(&self, span: Option<Span>) -> std::result::Result<(), SourceMappingError> {
784        let Some(span) = span else {
785            return Ok(());
786        };
787        if !span.is_valid() {
788            return Err(SourceMappingError::InvalidSpan(span));
789        }
790        if self.files.get(span.file.index()).is_none() {
791            return Err(SourceMappingError::UnknownFile(span.file));
792        }
793        Ok(())
794    }
795
796    fn provenance_mut(&mut self) -> &mut ProgramProvenance {
797        self.provenance
798            .get_or_insert_with(|| Box::new(ProgramProvenance::default()))
799            .as_mut()
800    }
801
802    /// Mutable provenance access for the span setters: the addressed record
803    /// is reseated for the current node so attaching a span never lets a
804    /// record displaced by mutation resurface on a different node.
805    fn rule_provenance_mut(
806        &mut self,
807        rule: usize,
808    ) -> std::result::Result<&mut RuleProvenance, SourceMappingError> {
809        if rule >= self.rules.len() {
810            return Err(SourceMappingError::InvalidRule(rule));
811        }
812        let identity = rule_identity(&self.rules[rule]);
813        let rule_count = self.rules.len();
814        let provenance = self.provenance_mut();
815        fit(&mut provenance.rules, rule_count);
816        let record = &mut provenance.rules[rule];
817        record.reseat(identity);
818        Ok(record)
819    }
820
821    fn action_provenance_mut(
822        &mut self,
823        rule: usize,
824        action: usize,
825    ) -> std::result::Result<&mut ActionProvenance, SourceMappingError> {
826        let public = self
827            .rules
828            .get(rule)
829            .ok_or(SourceMappingError::InvalidRule(rule))?;
830        let action_count = public.actions.len();
831        if action >= action_count {
832            return Err(SourceMappingError::InvalidAction { rule, action });
833        }
834        let identity = action_identity(&public.actions[action]);
835        let rule_data = self.rule_provenance_mut(rule)?;
836        fit(&mut rule_data.actions, action_count);
837        let record = &mut rule_data.actions[action];
838        record.reseat(identity);
839        Ok(record)
840    }
841
842    /// Record the identity of the public node every attached provenance record
843    /// describes, so records are only returned for the content they were
844    /// attached to. Called once after parsing or applying a [`SourceMap`].
845    fn record_identities(&mut self) {
846        let Some(mut provenance) = self.provenance.take() else {
847            return;
848        };
849        identity::record_identities(&mut provenance, self);
850        self.provenance = Some(provenance);
851    }
852}
853
854/// Walk the provenance tree of `value` along `path`. Descending only
855/// requires each level's child table to match the public child count, so a
856/// node that went stale does not hide unaffected siblings; the record at the
857/// path's end is returned only while the node there still has the recorded
858/// identity — `root_identity` for the starting record, its own
859/// [`value_identity`] for every record reached by path.
860fn value_provenance_at<'a>(
861    record: &'a ValueProvenance,
862    value: &'a Value,
863    path: &[usize],
864    root_identity: NodeIdentity,
865) -> Option<&'a ValueProvenance> {
866    let mut record = record;
867    let mut value = value;
868    let mut identity = root_identity;
869    for &index in path {
870        let children = value_children(value);
871        if record.children.len() != children.len() {
872            return None;
873        }
874        record = record.children.get(index)?;
875        value = children[index];
876        identity = value_identity(value);
877    }
878    (record.identity == identity).then_some(record)
879}
880
881fn fit<T: Default>(items: &mut Vec<T>, len: usize) {
882    items.truncate(len);
883    items.resize_with(len, T::default);
884}
885
886/// Selects one declaration-provenance table so the three span setters share
887/// their validate-bounds-fit-assign skeleton.
888#[derive(Copy, Clone)]
889enum DeclarationTable {
890    GlobalVariables,
891    PlayerVariables,
892    Subroutines,
893}
894
895impl DeclarationTable {
896    fn count(self, program: &Program) -> usize {
897        match self {
898            Self::GlobalVariables => program.global_variables.len(),
899            Self::PlayerVariables => program.player_variables.len(),
900            Self::Subroutines => program.subroutines.len(),
901        }
902    }
903
904    fn invalid(self, index: usize) -> SourceMappingError {
905        match self {
906            Self::GlobalVariables => SourceMappingError::InvalidGlobalVariable(index),
907            Self::PlayerVariables => SourceMappingError::InvalidPlayerVariable(index),
908            Self::Subroutines => SourceMappingError::InvalidSubroutine(index),
909        }
910    }
911
912    fn identity(self, program: &Program, index: usize) -> NodeIdentity {
913        match self {
914            Self::GlobalVariables => declaration_identity(&program.global_variables[index]),
915            Self::PlayerVariables => declaration_identity(&program.player_variables[index]),
916            Self::Subroutines => declaration_identity(&program.subroutines[index]),
917        }
918    }
919
920    fn slots(self, provenance: &mut ProgramProvenance) -> &mut Vec<DeclarationProvenance> {
921        match self {
922            Self::GlobalVariables => &mut provenance.global_variables,
923            Self::PlayerVariables => &mut provenance.player_variables,
924            Self::Subroutines => &mut provenance.subroutines,
925        }
926    }
927}
928
929/// A Workshop global or player variable declaration.
930#[derive(Debug, Clone, PartialEq, Eq)]
931pub struct Variable {
932    pub name: String,
933    /// The raw Workshop declaration index, when the declaration has one.
934    pub index: Option<u32>,
935}
936
937impl Variable {
938    pub fn new(name: impl Into<String>) -> Self {
939        Self {
940            name: name.into(),
941            index: None,
942        }
943    }
944
945    pub fn with_index(name: impl Into<String>, index: u32) -> Self {
946        Self {
947            name: name.into(),
948            index: Some(index),
949        }
950    }
951}
952
953/// A Workshop subroutine declaration.
954#[derive(Debug, Clone, PartialEq, Eq)]
955pub struct Subroutine {
956    pub name: String,
957    /// The raw Workshop declaration index, when the declaration has one.
958    pub index: Option<u32>,
959}
960
961impl Subroutine {
962    pub fn new(name: impl Into<String>) -> Self {
963        Self {
964            name: name.into(),
965            index: None,
966        }
967    }
968
969    pub fn with_index(name: impl Into<String>, index: u32) -> Self {
970        Self {
971            name: name.into(),
972            index: Some(index),
973        }
974    }
975}
976
977/// A Workshop rule with explicit conditions and a linear action stream.
978#[derive(Debug, Clone)]
979#[non_exhaustive]
980pub struct Rule {
981    pub name: String,
982    pub disabled: bool,
983    pub event: Event,
984    pub conditions: Vec<Condition>,
985    pub actions: Vec<Action>,
986}
987
988impl Rule {
989    pub fn new(name: impl Into<String>, event: Event) -> Self {
990        Self {
991            name: name.into(),
992            disabled: false,
993            event,
994            conditions: Vec::new(),
995            actions: Vec::new(),
996        }
997    }
998
999    pub fn condition(mut self, condition: impl Into<Condition>) -> Self {
1000        self.conditions.push(condition.into());
1001        self
1002    }
1003
1004    pub fn action(mut self, action: Action) -> Self {
1005        self.actions.push(action);
1006        self
1007    }
1008}
1009
1010/// A rule condition. Conditions remain distinct from general value expressions.
1011#[derive(Debug, Clone)]
1012#[non_exhaustive]
1013pub struct Condition {
1014    pub value: Value,
1015    pub disabled: bool,
1016}
1017
1018impl Condition {
1019    pub fn new(value: Value) -> Self {
1020        Self {
1021            value,
1022            disabled: false,
1023        }
1024    }
1025
1026    pub fn disabled(value: Value) -> Self {
1027        Self {
1028            value,
1029            disabled: true,
1030        }
1031    }
1032}
1033
1034impl From<Value> for Condition {
1035    fn from(value: Value) -> Self {
1036        Self::new(value)
1037    }
1038}
1039
1040/// The direct value arguments of a public action, in their mapped order.
1041/// Provenance rows pair these positionally with `wir::Action::value_args`.
1042pub(crate) fn action_argument_values(action: &Action) -> Vec<&Value> {
1043    match action {
1044        Action::SetGlobalVariable { value, .. } | Action::ModifyGlobalVariable { value, .. } => {
1045            vec![value]
1046        }
1047        Action::SetPlayerVariable { player, value, .. }
1048        | Action::ModifyPlayerVariable { player, value, .. } => vec![player, value],
1049        Action::AssignMember { target, value, .. } => vec![target, value],
1050        Action::If { condition } | Action::ElseIf { condition } | Action::While { condition } => {
1051            vec![condition]
1052        }
1053        Action::ForGlobalVariable {
1054            start, stop, step, ..
1055        } => vec![start, stop, step],
1056        Action::ForPlayerVariable {
1057            player,
1058            start,
1059            stop,
1060            step,
1061            ..
1062        } => vec![player, start, stop, step],
1063        Action::Call { args, .. } => args.iter().collect(),
1064        Action::CallSubroutine { .. } | Action::Else | Action::End => Vec::new(),
1065        Action::Disabled { action } => action_argument_values(action),
1066    }
1067}
1068
1069/// The children a public value exposes to provenance addressing, in the order
1070/// [`Program::condition_value_span`] documents.
1071pub(crate) fn value_children(value: &Value) -> Vec<&Value> {
1072    match value {
1073        Value::Array(values) => values.iter().collect(),
1074        Value::Vector { x, y, z } => vec![x.as_ref(), y.as_ref(), z.as_ref()],
1075        Value::PlayerVariable { player, .. } => vec![player.as_ref()],
1076        Value::Call { args, .. } => args.iter().collect(),
1077        _ => Vec::new(),
1078    }
1079}
1080
1081/// A Workshop event identity and its native filters.
1082#[derive(Debug, Clone, PartialEq, Eq)]
1083pub enum Event {
1084    Global,
1085    EachPlayer,
1086    EachPlayerWithFilters {
1087        team: EventTeam,
1088        target: EventTarget,
1089    },
1090    Player {
1091        kind: PlayerEventKind,
1092        team: EventTeam,
1093        target: EventTarget,
1094    },
1095    Subroutine(String),
1096}
1097
1098/// A Workshop action line. Control flow is represented in the same order as
1099/// the Workshop source, including its explicit `End` lines.
1100#[derive(Debug, Clone)]
1101pub enum Action {
1102    SetGlobalVariable {
1103        variable: String,
1104        value: Value,
1105    },
1106    ModifyGlobalVariable {
1107        variable: String,
1108        op: ModifyOp,
1109        value: Value,
1110    },
1111    SetPlayerVariable {
1112        player: Value,
1113        variable: String,
1114        value: Value,
1115    },
1116    ModifyPlayerVariable {
1117        player: Value,
1118        variable: String,
1119        op: ModifyOp,
1120        value: Value,
1121    },
1122    AssignMember {
1123        target: Value,
1124        op: Option<ModifyOp>,
1125        value: Value,
1126    },
1127    CallSubroutine {
1128        subroutine: String,
1129    },
1130    If {
1131        condition: Value,
1132    },
1133    ElseIf {
1134        condition: Value,
1135    },
1136    Else,
1137    While {
1138        condition: Value,
1139    },
1140    ForGlobalVariable {
1141        variable: String,
1142        start: Value,
1143        stop: Value,
1144        step: Value,
1145    },
1146    ForPlayerVariable {
1147        player: Value,
1148        variable: String,
1149        start: Value,
1150        stop: Value,
1151        step: Value,
1152    },
1153    End,
1154    Disabled {
1155        action: Box<Action>,
1156    },
1157    Call {
1158        name: String,
1159        args: Vec<Value>,
1160    },
1161}
1162
1163impl Action {
1164    /// Mark an action as disabled.
1165    pub fn disabled(action: Action) -> Self {
1166        Self::Disabled {
1167            action: Box::new(action),
1168        }
1169    }
1170
1171    /// Construct a dynamic action call by canonical Workshop id.
1172    pub fn call(name: impl Into<String>, args: impl IntoIterator<Item = Value>) -> Self {
1173        Self::Call {
1174            name: name.into(),
1175            args: args.into_iter().collect(),
1176        }
1177    }
1178}
1179
1180/// A composable Workshop value expression.
1181#[derive(Debug, Clone)]
1182pub enum Value {
1183    Number(f64),
1184    String(String),
1185    LocalizedString(String),
1186    Bool(bool),
1187    Null,
1188    Array(Vec<Value>),
1189    Vector {
1190        x: Box<Value>,
1191        y: Box<Value>,
1192        z: Box<Value>,
1193    },
1194    Enum {
1195        value_type: String,
1196        value: String,
1197    },
1198    GlobalVariable(String),
1199    PlayerVariable {
1200        player: Box<Value>,
1201        variable: String,
1202    },
1203    Subroutine(String),
1204    EventPlayer,
1205    Call {
1206        name: String,
1207        args: Vec<Value>,
1208    },
1209}
1210
1211impl Value {
1212    /// Construct a numeric Workshop literal.
1213    pub fn number(value: f64) -> Self {
1214        Self::Number(value)
1215    }
1216
1217    /// Construct a custom Workshop string literal.
1218    pub fn string(value: impl Into<String>) -> Self {
1219        Self::String(value.into())
1220    }
1221
1222    pub fn global_variable(name: impl Into<String>) -> Self {
1223        Self::GlobalVariable(name.into())
1224    }
1225
1226    pub fn player_variable(player: Value, name: impl Into<String>) -> Self {
1227        Self::PlayerVariable {
1228            player: Box::new(player),
1229            variable: name.into(),
1230        }
1231    }
1232
1233    /// Construct a dynamic value call by canonical Workshop id.
1234    pub fn call(name: impl Into<String>, args: impl IntoIterator<Item = Value>) -> Self {
1235        Self::Call {
1236            name: name.into(),
1237            args: args.into_iter().collect(),
1238        }
1239    }
1240}
1241
1242impl From<bool> for Value {
1243    fn from(value: bool) -> Self {
1244        Self::Bool(value)
1245    }
1246}
1247
1248impl From<f64> for Value {
1249    fn from(value: f64) -> Self {
1250        Self::Number(value)
1251    }
1252}
1253
1254impl From<f32> for Value {
1255    fn from(value: f32) -> Self {
1256        Self::Number(f64::from(value))
1257    }
1258}
1259
1260macro_rules! impl_integer_value {
1261    ($($type:ty),+ $(,)?) => {
1262        $(
1263            impl From<$type> for Value {
1264                fn from(value: $type) -> Self {
1265                    Self::Number(value as f64)
1266                }
1267            }
1268        )+
1269    };
1270}
1271
1272impl_integer_value!(i8, i16, i32, i64, isize, u8, u16, u32, u64, usize);
1273
1274impl From<String> for Value {
1275    fn from(value: String) -> Self {
1276        Self::String(value)
1277    }
1278}
1279
1280impl From<&str> for Value {
1281    fn from(value: &str) -> Self {
1282        Self::String(value.to_string())
1283    }
1284}
1285
1286impl<T: Into<Value>> From<Vec<T>> for Value {
1287    fn from(values: Vec<T>) -> Self {
1288        Self::Array(values.into_iter().map(Into::into).collect())
1289    }
1290}
1291
1292impl<T: Into<Value>, const N: usize> From<[T; N]> for Value {
1293    fn from(values: [T; N]) -> Self {
1294        Self::Array(values.into_iter().map(Into::into).collect())
1295    }
1296}