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