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