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    identity: NodeIdentity,
43}
44
45#[derive(Debug, Clone, Default)]
46struct RuleProvenance {
47    span: Option<crate::source::Span>,
48    name: Option<Span>,
49    event_name: Option<Span>,
50    identity: NodeIdentity,
51    conditions: Vec<ValueProvenance>,
52    actions: Vec<ActionProvenance>,
53}
54
55impl RuleProvenance {
56    fn reseat(&mut self, identity: NodeIdentity) {
57        if self.identity != identity {
58            self.span = None;
59            self.name = None;
60            self.event_name = None;
61        }
62        self.identity = identity;
63    }
64}
65
66#[derive(Debug, Clone, Default)]
67struct ActionProvenance {
68    span: Option<Span>,
69    identifier: Option<Span>,
70    identity: NodeIdentity,
71    arguments: Vec<ValueProvenance>,
72}
73
74trait SpanSlot {
75    fn span_slot(&mut self) -> &mut Option<Span>;
76    fn reseat(&mut self, identity: NodeIdentity);
77}
78
79impl SpanSlot for ValueProvenance {
80    fn span_slot(&mut self) -> &mut Option<Span> {
81        &mut self.span
82    }
83    fn reseat(&mut self, identity: NodeIdentity) {
84        if self.identity != identity {
85            self.span = None;
86            self.identifier = None;
87        }
88        self.identity = identity;
89    }
90}
91
92impl SpanSlot for ActionProvenance {
93    fn span_slot(&mut self) -> &mut Option<Span> {
94        &mut self.span
95    }
96    fn reseat(&mut self, identity: NodeIdentity) {
97        if self.identity != identity {
98            self.span = None;
99            self.identifier = None;
100        }
101        self.identity = identity;
102    }
103}
104
105fn prune_spans_outside(
106    provenance: &mut ProgramProvenance,
107    file: FileId,
108    document: &SourceDocument,
109) {
110    fn prune_slot(slot: &mut Option<Span>, file: FileId, document: &SourceDocument) {
111        if let Some(span) = slot {
112            if span.file == file && document.byte_range(*span).is_none() {
113                *slot = None;
114            }
115        }
116    }
117    fn prune_value(record: &mut ValueProvenance, file: FileId, document: &SourceDocument) {
118        prune_slot(&mut record.span, file, document);
119        prune_slot(&mut record.identifier, file, document);
120        for child in &mut record.children {
121            prune_value(child, file, document);
122        }
123    }
124    for declaration in provenance
125        .global_variables
126        .iter_mut()
127        .chain(&mut provenance.player_variables)
128        .chain(&mut provenance.subroutines)
129    {
130        prune_slot(&mut declaration.span, file, document);
131        prune_slot(&mut declaration.name_span, file, document);
132    }
133    for rule in &mut provenance.rules {
134        prune_slot(&mut rule.span, file, document);
135        prune_slot(&mut rule.name, file, document);
136        prune_slot(&mut rule.event_name, file, document);
137        for condition in &mut rule.conditions {
138            prune_value(condition, file, document);
139        }
140        for action in &mut rule.actions {
141            prune_slot(&mut action.span, file, document);
142            prune_slot(&mut action.identifier, file, document);
143            for argument in &mut action.arguments {
144                prune_value(argument, file, document);
145            }
146        }
147    }
148}
149
150#[derive(Debug, Clone, Default)]
151struct ValueProvenance {
152    span: Option<Span>,
153    identifier: Option<Span>,
154    identity: NodeIdentity,
155    children: Vec<ValueProvenance>,
156}
157
158/// A failure while attaching source mappings to a public [`Program`].
159#[derive(Debug, Clone, Copy, PartialEq, Eq)]
160#[non_exhaustive]
161pub enum SourceMappingError {
162    UnknownFile(FileId),
163    InvalidSpan(Span),
164    InvalidRule(usize),
165    InvalidCondition {
166        rule: usize,
167        condition: usize,
168    },
169    InvalidAction {
170        rule: usize,
171        action: usize,
172    },
173    InvalidActionArgument {
174        rule: usize,
175        action: usize,
176        argument: usize,
177    },
178    InvalidGlobalVariable(usize),
179    InvalidPlayerVariable(usize),
180    InvalidSubroutine(usize),
181}
182
183impl std::fmt::Display for SourceMappingError {
184    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
185        match self {
186            Self::UnknownFile(file) => {
187                write!(formatter, "source span references unknown file {file}")
188            }
189            Self::InvalidSpan(span) => write!(formatter, "invalid source span {span:?}"),
190            Self::InvalidRule(rule) => write!(formatter, "invalid rule index {rule}"),
191            Self::InvalidCondition { rule, condition } => {
192                write!(
193                    formatter,
194                    "invalid condition index {condition} in rule {rule}"
195                )
196            }
197            Self::InvalidAction { rule, action } => {
198                write!(formatter, "invalid action index {action} in rule {rule}")
199            }
200            Self::InvalidActionArgument {
201                rule,
202                action,
203                argument,
204            } => write!(
205                formatter,
206                "invalid argument index {argument} in action {action} of rule {rule}"
207            ),
208            Self::InvalidGlobalVariable(variable) => {
209                write!(formatter, "invalid global variable index {variable}")
210            }
211            Self::InvalidPlayerVariable(variable) => {
212                write!(formatter, "invalid player variable index {variable}")
213            }
214            Self::InvalidSubroutine(subroutine) => {
215                write!(formatter, "invalid subroutine index {subroutine}")
216            }
217        }
218    }
219}
220
221impl std::error::Error for SourceMappingError {}
222
223impl Program {
224    pub fn new() -> Self {
225        Self::default()
226    }
227
228    /// Register a source file and return its public file identity.
229    pub fn add_file(&mut self, mut file: SourceFile) -> FileId {
230        let id = FileId::from_index(self.files.len());
231        file.bind_file(id);
232        self.files.push(file);
233        id
234    }
235
236    pub fn global_variable(&mut self, variable: Variable) -> &mut Self {
237        self.global_variables.push(variable);
238        self
239    }
240
241    pub fn player_variable(&mut self, variable: Variable) -> &mut Self {
242        self.player_variables.push(variable);
243        self
244    }
245
246    pub fn subroutine(&mut self, subroutine: Subroutine) -> &mut Self {
247        self.subroutines.push(subroutine);
248        self
249    }
250
251    pub fn rule(&mut self, rule: Rule) -> &mut Self {
252        self.rules.push(rule);
253        self
254    }
255
256    /// Return the retained source document for a parsed file.
257    pub fn source(&self, file: FileId) -> Option<&SourceDocument> {
258        self.files.get(file.index()).and_then(SourceFile::source)
259    }
260
261    /// Attach authored source text to a registered file. Returns `false` when
262    /// `file` is not a known file entry.
263    ///
264    /// Attaching text audits the provenance that belongs to this file: a
265    /// recorded span in `file` that does not resolve inside the retained
266    /// document is stale data — for a provider-mapped program it can describe
267    /// the pre-expansion token stream or a coordinate space the map no longer
268    /// owns — and is cleared so consumers see no span rather than a wrong
269    /// one, matching how `record_identities` retires displaced records
270    /// (wrightkit/wright#583). Spans recorded against other files are left
271    /// untouched; they are audited when their own source is attached.
272    pub fn set_file_source(&mut self, file: FileId, source: impl Into<String>) -> bool {
273        let Some(entry) = self.files.get_mut(file.index()) else {
274            return false;
275        };
276        entry.set_source(source);
277        if let (Some(provenance), Some(document)) = (
278            self.provenance.as_mut(),
279            self.files.get(file.index()).and_then(SourceFile::source),
280        ) {
281            prune_spans_outside(provenance, file, document);
282        }
283        true
284    }
285
286    /// Attach the authored span of a public rule.
287    pub fn set_rule_span(
288        &mut self,
289        rule: usize,
290        span: Option<Span>,
291    ) -> std::result::Result<(), SourceMappingError> {
292        self.validate_span(span)?;
293        self.rule_provenance_mut(rule)?.span = span;
294        Ok(())
295    }
296
297    /// Attach the authored span of a public rule condition value.
298    pub fn set_condition_span(
299        &mut self,
300        rule: usize,
301        condition: usize,
302        span: Option<Span>,
303    ) -> std::result::Result<(), SourceMappingError> {
304        self.set_child_span(
305            rule,
306            condition,
307            span,
308            |rule| rule.conditions.len(),
309            |rule, index| condition_identity(&rule.conditions[index]),
310            |rule, index| SourceMappingError::InvalidCondition {
311                rule,
312                condition: index,
313            },
314            |rule_data| &mut rule_data.conditions,
315        )
316    }
317
318    /// Attach the authored span of a public action in its linear rule order.
319    pub fn set_action_span(
320        &mut self,
321        rule: usize,
322        action: usize,
323        span: Option<Span>,
324    ) -> std::result::Result<(), SourceMappingError> {
325        self.set_child_span(
326            rule,
327            action,
328            span,
329            |rule| rule.actions.len(),
330            |rule, index| action_identity(&rule.actions[index]),
331            |rule, index| SourceMappingError::InvalidAction {
332                rule,
333                action: index,
334            },
335            |rule_data| &mut rule_data.actions,
336        )
337    }
338
339    #[allow(clippy::too_many_arguments)]
340    fn set_child_span<T: SpanSlot + Default>(
341        &mut self,
342        rule: usize,
343        index: usize,
344        span: Option<Span>,
345        count: impl Fn(&crate::Rule) -> usize,
346        identity: impl Fn(&crate::Rule, usize) -> NodeIdentity,
347        invalid: impl Fn(usize, usize) -> SourceMappingError,
348        slots: impl Fn(&mut RuleProvenance) -> &mut Vec<T>,
349    ) -> std::result::Result<(), SourceMappingError> {
350        self.validate_span(span)?;
351        let public = self
352            .rules
353            .get(rule)
354            .ok_or(SourceMappingError::InvalidRule(rule))?;
355        let child_count = count(public);
356        if index >= child_count {
357            return Err(invalid(rule, index));
358        }
359        let identity = identity(public, index);
360        let rule_data = self.rule_provenance_mut(rule)?;
361        fit(slots(rule_data), child_count);
362        let record = &mut slots(rule_data)[index];
363        record.reseat(identity);
364        *record.span_slot() = span;
365        Ok(())
366    }
367
368    /// Attach the authored span of a direct value argument of a public action.
369    pub fn set_action_argument_span(
370        &mut self,
371        rule: usize,
372        action: usize,
373        argument: usize,
374        span: Option<Span>,
375    ) -> std::result::Result<(), SourceMappingError> {
376        self.validate_span(span)?;
377        let (identity, argument_count) = {
378            let action_value = self
379                .rules
380                .get(rule)
381                .ok_or(SourceMappingError::InvalidRule(rule))?
382                .actions
383                .get(action)
384                .ok_or(SourceMappingError::InvalidAction { rule, action })?;
385            let argument_values = action_argument_values(action_value);
386            let Some(&argument_value) = argument_values.get(argument) else {
387                return Err(SourceMappingError::InvalidActionArgument {
388                    rule,
389                    action,
390                    argument,
391                });
392            };
393            (value_identity(argument_value), argument_values.len())
394        };
395        let action_data = self.action_provenance_mut(rule, action)?;
396        fit(&mut action_data.arguments, argument_count);
397        let record = &mut action_data.arguments[argument];
398        record.reseat(identity);
399        record.span = span;
400        Ok(())
401    }
402
403    /// Attach the authored and identifier spans of a global variable.
404    pub fn set_global_variable_spans(
405        &mut self,
406        variable: usize,
407        span: Option<Span>,
408        name_span: Option<Span>,
409    ) -> std::result::Result<(), SourceMappingError> {
410        self.set_declaration_spans(DeclarationTable::GlobalVariables, variable, span, name_span)
411    }
412
413    /// Attach the authored and identifier spans of a player variable.
414    pub fn set_player_variable_spans(
415        &mut self,
416        variable: usize,
417        span: Option<Span>,
418        name_span: Option<Span>,
419    ) -> std::result::Result<(), SourceMappingError> {
420        self.set_declaration_spans(DeclarationTable::PlayerVariables, variable, span, name_span)
421    }
422
423    /// Attach the authored and identifier spans of a subroutine.
424    pub fn set_subroutine_spans(
425        &mut self,
426        subroutine: usize,
427        span: Option<Span>,
428        name_span: Option<Span>,
429    ) -> std::result::Result<(), SourceMappingError> {
430        self.set_declaration_spans(DeclarationTable::Subroutines, subroutine, span, name_span)
431    }
432
433    fn set_declaration_spans(
434        &mut self,
435        table: DeclarationTable,
436        index: usize,
437        span: Option<Span>,
438        name_span: Option<Span>,
439    ) -> std::result::Result<(), SourceMappingError> {
440        self.validate_span(span)?;
441        self.validate_span(name_span)?;
442        let count = table.count(self);
443        if index >= count {
444            return Err(table.invalid(index));
445        }
446        let identity = table.identity(self, index);
447        let slots = table.slots(self.provenance_mut());
448        fit(slots, count);
449        slots[index] = DeclarationProvenance {
450            span,
451            name_span,
452            identity,
453        };
454        Ok(())
455    }
456
457    /// Return the authored span of a public rule, when source metadata exists.
458    ///
459    /// Attached mappings record the program shape and node content they were
460    /// attached to. Every span accessor returns `None` once the corresponding
461    /// public list has been inserted into or removed from, or once the node
462    /// at that position no longer has the content the mapping was attached
463    /// to — see "Shape and content guard" in `docs/source-preservation.md`.
464    pub fn rule_span(&self, rule: usize) -> Option<crate::source::Span> {
465        self.rule_provenance(rule)?.span
466    }
467
468    /// Return the authored span of a public rule condition value.
469    pub fn condition_span(&self, rule: usize, condition: usize) -> Option<crate::source::Span> {
470        self.condition_provenance(rule, condition)?.span
471    }
472
473    /// Return the authored span of a public action in its linear rule order.
474    pub fn action_span(&self, rule: usize, action: usize) -> Option<crate::source::Span> {
475        self.action_provenance(rule, action)?.span
476    }
477
478    /// Return the authored span of a direct value argument of a public action.
479    pub fn action_argument_span(
480        &self,
481        rule: usize,
482        action: usize,
483        argument: usize,
484    ) -> Option<crate::source::Span> {
485        self.action_argument_provenance(rule, action, argument)?
486            .span
487    }
488
489    /// Return the authored span of the declared name of a global variable.
490    ///
491    /// Source-language providers attach an explicit identifier span through
492    /// [`set_global_variable_spans`](Self::set_global_variable_spans). For raw
493    /// Workshop parses the recorded declaration span already covers exactly
494    /// the declared name and is returned as the identifier span.
495    pub fn global_variable_name_span(&self, variable: usize) -> Option<Span> {
496        self.declaration_name_span(
497            |provenance| &provenance.global_variables,
498            &self.global_variables,
499            variable,
500        )
501    }
502
503    /// Return the authored span of the declared name of a player variable.
504    /// See [`global_variable_name_span`](Self::global_variable_name_span).
505    pub fn player_variable_name_span(&self, variable: usize) -> Option<Span> {
506        self.declaration_name_span(
507            |provenance| &provenance.player_variables,
508            &self.player_variables,
509            variable,
510        )
511    }
512
513    /// Return the authored span of the declared name of a subroutine.
514    /// See [`global_variable_name_span`](Self::global_variable_name_span).
515    pub fn subroutine_name_span(&self, subroutine: usize) -> Option<Span> {
516        self.declaration_name_span(
517            |provenance| &provenance.subroutines,
518            &self.subroutines,
519            subroutine,
520        )
521    }
522
523    /// Return the span recorded for the variable or subroutine identifier a
524    /// public action names: the target of set/modify and for-variable actions,
525    /// or the callee of a [`Call Subroutine`](Action::CallSubroutine) action.
526    ///
527    /// Raw Workshop parses record the variable name for `Set`/`Modify`
528    /// variable actions, `For` variable loops, and `Global.name`/`Event
529    /// Player.name` infix assignments, and the callee name for `Call
530    /// Subroutine`. Indexed writes lower to `... Variable At Index` calls and
531    /// record the name on their variable argument — see
532    /// [`action_argument_value_span`](Self::action_argument_value_span).
533    /// Other action forms always return `None`.
534    pub fn action_identifier_span(&self, rule: usize, action: usize) -> Option<Span> {
535        self.action_provenance(rule, action)?.identifier
536    }
537
538    /// Return the span recorded for a rule's name inside its `rule("name")`
539    /// string, or `None` when no provenance was recorded.
540    pub fn rule_name_span(&self, rule: usize) -> Option<Span> {
541        self.rule_provenance(rule)?.name
542    }
543
544    /// Return the span recorded for the subroutine name a rule's `Subroutine`
545    /// event binding names, or `None` for other event kinds and when no
546    /// provenance was recorded.
547    pub fn rule_event_name_span(&self, rule: usize) -> Option<Span> {
548        self.rule_provenance(rule)?.event_name
549    }
550
551    /// Return the authored span of a value nested inside a public rule
552    /// condition.
553    ///
554    /// `path` walks the public [`Value`] tree: each element selects a child by
555    /// position — `Value::Array` elements and `Value::Call` arguments by
556    /// index, `Value::Vector` components as `0`/`1`/`2` for x/y/z, and a
557    /// `Value::PlayerVariable` player at `0`. An empty path returns the
558    /// condition value's own span, matching [`condition_span`](Self::condition_span).
559    ///
560    /// For a variable or subroutine reference the identifier span is returned
561    /// when the parser recorded one: raw Workshop records the variable name
562    /// for `Global.name`, `Global/Player Variable(name)`, `Event Player.name`,
563    /// bare-name, and `... At Index` argument spellings. Other nodes return
564    /// the span recorded for the node itself.
565    pub fn condition_value_span(
566        &self,
567        rule: usize,
568        condition: usize,
569        path: &[usize],
570    ) -> Option<crate::source::Span> {
571        let record = self.condition_value_record(rule, condition, path)?;
572        record.identifier.or(record.span)
573    }
574
575    fn condition_value_record(
576        &self,
577        rule: usize,
578        condition: usize,
579        path: &[usize],
580    ) -> Option<&ValueProvenance> {
581        let record = self.condition_record(rule, condition)?;
582        let value = &self.rules[rule].conditions[condition].value;
583        let identity = condition_identity(&self.rules[rule].conditions[condition]);
584        value_provenance_at(record, value, path, identity)
585    }
586
587    pub(crate) fn condition_value_node_span(
588        &self,
589        rule: usize,
590        condition: usize,
591        path: &[usize],
592    ) -> Option<crate::source::Span> {
593        self.condition_value_record(rule, condition, path)?.span
594    }
595
596    /// Return the authored span of a value nested inside a direct value
597    /// argument of a public action.
598    ///
599    /// `argument` selects the same direct argument as
600    /// [`action_argument_span`](Self::action_argument_span) and `path` walks
601    /// into it the way [`condition_value_span`](Self::condition_value_span)
602    /// describes; an empty path returns the argument's own span. Like
603    /// `condition_value_span`, a variable or subroutine reference returns its
604    /// recorded identifier span.
605    pub fn action_argument_value_span(
606        &self,
607        rule: usize,
608        action: usize,
609        argument: usize,
610        path: &[usize],
611    ) -> Option<crate::source::Span> {
612        let record = self.action_argument_value_record(rule, action, argument, path)?;
613        record.identifier.or(record.span)
614    }
615
616    fn action_argument_value_record(
617        &self,
618        rule: usize,
619        action: usize,
620        argument: usize,
621        path: &[usize],
622    ) -> Option<&ValueProvenance> {
623        let record = self.argument_record(rule, action, argument)?;
624        let value = action_argument_values(&self.rules[rule].actions[action])[argument];
625        value_provenance_at(record, value, path, value_identity(value))
626    }
627
628    pub(crate) fn action_argument_value_node_span(
629        &self,
630        rule: usize,
631        action: usize,
632        argument: usize,
633        path: &[usize],
634    ) -> Option<crate::source::Span> {
635        self.action_argument_value_record(rule, action, argument, path)?
636            .span
637    }
638
639    /// Create a checked source edit through the authored source attached to
640    /// this canonical program.
641    pub fn edit_source(
642        &self,
643        span: crate::source::Span,
644        replacement: impl Into<String>,
645    ) -> std::result::Result<crate::source::SourceEdit, crate::source::SourceEditError> {
646        self.source(span.file)
647            .ok_or(crate::source::SourceEditError::InvalidRange)?
648            .edit_span(span, replacement)
649    }
650
651    /// Validate the structural invariants of the canonical program.
652    ///
653    /// Settings are emission-checked too, so a program that would fail to
654    /// emit fails validation the same way.
655    pub fn validate(&self) -> std::result::Result<(), WorkshopError> {
656        let storage = self.to_wir()?;
657        storage
658            .validate()
659            .map_err(|error| WorkshopError::malformed(error.to_string(), error.span()))?;
660        if let Some(settings) = &self.settings {
661            if let Some(error) = crate::settings::check_emission(settings).into_iter().next() {
662                return Err(error);
663            }
664        }
665        Ok(())
666    }
667
668    /// Report every settings member the emission table rejects — plus, as
669    /// [`crate::settings::DiagnosticSeverity::Warning`] entries, carried
670    /// members close enough to a declared spelling to look like a
671    /// misspelling — each with its source span and, when exactly one
672    /// canonical spelling is close enough, the structured suggestion a
673    /// caller can apply. [`validate`] already fails on the first error;
674    /// warnings describe accepted members and never fail validation.
675    ///
676    /// [`validate`]: Program::validate
677    pub fn settings_diagnostics(&self) -> Vec<crate::settings::SettingsDiagnostic> {
678        self.settings
679            .as_ref()
680            .map(crate::settings::check_emission_diagnostics)
681            .unwrap_or_default()
682    }
683
684    /// Report constructs that are structurally preserved but not fully
685    /// understood by the canonical catalog.
686    ///
687    /// Residual inspection is defined over this public canonical model and is
688    /// independent from [`validate`](Self::validate): it may be used on
689    /// programs `validate` rejects, and an internal materialization failure
690    /// does not suppress observable residuals. Callers that also require
691    /// structural validity call `validate` separately; this inventory does
692    /// not report validation errors.
693    pub fn semantic_issues(
694        &self,
695        catalog: &crate::catalog::Catalog,
696    ) -> Vec<crate::rules::SemanticIssue> {
697        crate::analysis::semantic::inspect(self, catalog)
698    }
699
700    /// Render the program through the canonical Workshop debug representation.
701    pub fn dump(&self) -> String {
702        self.to_wir().map_or_else(
703            |error| format!("invalid program: {error}"),
704            |program| program.dump(),
705        )
706    }
707
708    fn rule_record(&self, rule: usize) -> Option<&RuleProvenance> {
709        let recorded = &self.provenance.as_deref()?.rules;
710        if recorded.len() != self.rules.len() {
711            return None;
712        }
713        recorded.get(rule)
714    }
715
716    fn rule_provenance(&self, rule: usize) -> Option<&RuleProvenance> {
717        self.rule_record(rule)
718            .filter(|record| record.identity == rule_identity(&self.rules[rule]))
719    }
720
721    fn action_record(&self, rule: usize, action: usize) -> Option<&ActionProvenance> {
722        let recorded = self.rule_record(rule)?;
723        if recorded.actions.len() != self.rules[rule].actions.len() {
724            return None;
725        }
726        recorded.actions.get(action)
727    }
728
729    fn action_provenance(&self, rule: usize, action: usize) -> Option<&ActionProvenance> {
730        self.action_record(rule, action)
731            .filter(|record| record.identity == action_identity(&self.rules[rule].actions[action]))
732    }
733
734    fn argument_record(
735        &self,
736        rule: usize,
737        action: usize,
738        argument: usize,
739    ) -> Option<&ValueProvenance> {
740        let recorded = self.action_record(rule, action)?;
741        let values = action_argument_values(&self.rules[rule].actions[action]);
742        if recorded.arguments.len() != values.len() {
743            return None;
744        }
745        recorded.arguments.get(argument)
746    }
747
748    fn action_argument_provenance(
749        &self,
750        rule: usize,
751        action: usize,
752        argument: usize,
753    ) -> Option<&ValueProvenance> {
754        let record = self.argument_record(rule, action, argument)?;
755        let values = action_argument_values(&self.rules[rule].actions[action]);
756        (record.identity == value_identity(values[argument])).then_some(record)
757    }
758
759    fn condition_record(&self, rule: usize, condition: usize) -> Option<&ValueProvenance> {
760        let recorded = self.rule_record(rule)?;
761        if recorded.conditions.len() != self.rules[rule].conditions.len() {
762            return None;
763        }
764        recorded.conditions.get(condition)
765    }
766
767    fn condition_provenance(&self, rule: usize, condition: usize) -> Option<&ValueProvenance> {
768        self.condition_record(rule, condition).filter(|record| {
769            record.identity == condition_identity(&self.rules[rule].conditions[condition])
770        })
771    }
772
773    fn declaration_name_span<T: std::fmt::Debug>(
774        &self,
775        recorded: impl Fn(&ProgramProvenance) -> &[DeclarationProvenance],
776        nodes: &[T],
777        position: usize,
778    ) -> Option<Span> {
779        let declaration = self.declaration_provenance(recorded, nodes, position);
780        declaration.name_span.or(declaration.span)
781    }
782
783    fn declaration_provenance<T: std::fmt::Debug>(
784        &self,
785        recorded: impl Fn(&ProgramProvenance) -> &[DeclarationProvenance],
786        nodes: &[T],
787        position: usize,
788    ) -> DeclarationProvenance {
789        self.provenance
790            .as_deref()
791            .map(recorded)
792            .filter(|recorded| recorded.len() == nodes.len())
793            .and_then(|recorded| recorded.get(position).zip(nodes.get(position)))
794            .filter(|(record, node)| record.identity == declaration_identity(node))
795            .map(|(record, _)| *record)
796            .unwrap_or_default()
797    }
798
799    fn validate_span(&self, span: Option<Span>) -> std::result::Result<(), SourceMappingError> {
800        let Some(span) = span else {
801            return Ok(());
802        };
803        if !span.is_valid() {
804            return Err(SourceMappingError::InvalidSpan(span));
805        }
806        if self.files.get(span.file.index()).is_none() {
807            return Err(SourceMappingError::UnknownFile(span.file));
808        }
809        Ok(())
810    }
811
812    fn provenance_mut(&mut self) -> &mut ProgramProvenance {
813        self.provenance
814            .get_or_insert_with(|| Box::new(ProgramProvenance::default()))
815            .as_mut()
816    }
817
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    fn record_identities(&mut self) {
856        let Some(mut provenance) = self.provenance.take() else {
857            return;
858        };
859        identity::record_identities(&mut provenance, self);
860        self.provenance = Some(provenance);
861    }
862}
863
864fn value_provenance_at<'a>(
865    record: &'a ValueProvenance,
866    value: &'a Value,
867    path: &[usize],
868    root_identity: NodeIdentity,
869) -> Option<&'a ValueProvenance> {
870    let mut record = record;
871    let mut value = value;
872    let mut identity = root_identity;
873    for &index in path {
874        let children = value_children(value);
875        if record.children.len() != children.len() {
876            return None;
877        }
878        record = record.children.get(index)?;
879        value = children[index];
880        identity = value_identity(value);
881    }
882    (record.identity == identity).then_some(record)
883}
884
885fn fit<T: Default>(items: &mut Vec<T>, len: usize) {
886    items.truncate(len);
887    items.resize_with(len, T::default);
888}
889
890#[derive(Copy, Clone)]
891enum DeclarationTable {
892    GlobalVariables,
893    PlayerVariables,
894    Subroutines,
895}
896
897impl DeclarationTable {
898    fn count(self, program: &Program) -> usize {
899        match self {
900            Self::GlobalVariables => program.global_variables.len(),
901            Self::PlayerVariables => program.player_variables.len(),
902            Self::Subroutines => program.subroutines.len(),
903        }
904    }
905
906    fn invalid(self, index: usize) -> SourceMappingError {
907        match self {
908            Self::GlobalVariables => SourceMappingError::InvalidGlobalVariable(index),
909            Self::PlayerVariables => SourceMappingError::InvalidPlayerVariable(index),
910            Self::Subroutines => SourceMappingError::InvalidSubroutine(index),
911        }
912    }
913
914    fn identity(self, program: &Program, index: usize) -> NodeIdentity {
915        match self {
916            Self::GlobalVariables => declaration_identity(&program.global_variables[index]),
917            Self::PlayerVariables => declaration_identity(&program.player_variables[index]),
918            Self::Subroutines => declaration_identity(&program.subroutines[index]),
919        }
920    }
921
922    fn slots(self, provenance: &mut ProgramProvenance) -> &mut Vec<DeclarationProvenance> {
923        match self {
924            Self::GlobalVariables => &mut provenance.global_variables,
925            Self::PlayerVariables => &mut provenance.player_variables,
926            Self::Subroutines => &mut provenance.subroutines,
927        }
928    }
929}
930
931/// A Workshop global or player variable declaration.
932#[derive(Debug, Clone, PartialEq, Eq)]
933pub struct Variable {
934    pub name: String,
935    /// The raw Workshop declaration index, when the declaration has one.
936    pub index: Option<u32>,
937}
938
939impl Variable {
940    pub fn new(name: impl Into<String>) -> Self {
941        Self {
942            name: name.into(),
943            index: None,
944        }
945    }
946
947    pub fn with_index(name: impl Into<String>, index: u32) -> Self {
948        Self {
949            name: name.into(),
950            index: Some(index),
951        }
952    }
953}
954
955/// A Workshop subroutine declaration.
956#[derive(Debug, Clone, PartialEq, Eq)]
957pub struct Subroutine {
958    pub name: String,
959    /// The raw Workshop declaration index, when the declaration has one.
960    pub index: Option<u32>,
961}
962
963impl Subroutine {
964    pub fn new(name: impl Into<String>) -> Self {
965        Self {
966            name: name.into(),
967            index: None,
968        }
969    }
970
971    pub fn with_index(name: impl Into<String>, index: u32) -> Self {
972        Self {
973            name: name.into(),
974            index: Some(index),
975        }
976    }
977}
978
979/// A Workshop rule with explicit conditions and a linear action stream.
980#[derive(Debug, Clone)]
981#[non_exhaustive]
982pub struct Rule {
983    pub name: String,
984    pub disabled: bool,
985    pub event: Event,
986    pub conditions: Vec<Condition>,
987    pub actions: Vec<Action>,
988}
989
990impl Rule {
991    pub fn new(name: impl Into<String>, event: Event) -> Self {
992        Self {
993            name: name.into(),
994            disabled: false,
995            event,
996            conditions: Vec::new(),
997            actions: Vec::new(),
998        }
999    }
1000
1001    pub fn condition(mut self, condition: impl Into<Condition>) -> Self {
1002        self.conditions.push(condition.into());
1003        self
1004    }
1005
1006    pub fn action(mut self, action: Action) -> Self {
1007        self.actions.push(action);
1008        self
1009    }
1010}
1011
1012/// A rule condition. Conditions remain distinct from general value expressions.
1013#[derive(Debug, Clone)]
1014#[non_exhaustive]
1015pub struct Condition {
1016    pub value: Value,
1017    pub disabled: bool,
1018}
1019
1020impl Condition {
1021    pub fn new(value: Value) -> Self {
1022        Self {
1023            value,
1024            disabled: false,
1025        }
1026    }
1027
1028    pub fn disabled(value: Value) -> Self {
1029        Self {
1030            value,
1031            disabled: true,
1032        }
1033    }
1034}
1035
1036impl From<Value> for Condition {
1037    fn from(value: Value) -> Self {
1038        Self::new(value)
1039    }
1040}
1041
1042pub(crate) fn action_argument_values(action: &Action) -> Vec<&Value> {
1043    match action {
1044        Action::SetGlobalVariable { value, .. } | Action::ModifyGlobalVariable { value, .. } => {
1045            vec![value]
1046        }
1047        Action::SetPlayerVariable { player, value, .. }
1048        | Action::ModifyPlayerVariable { player, value, .. } => vec![player, value],
1049        Action::AssignMember { target, value, .. } => vec![target, value],
1050        Action::If { condition } | Action::ElseIf { condition } | Action::While { condition } => {
1051            vec![condition]
1052        }
1053        Action::ForGlobalVariable {
1054            start, stop, step, ..
1055        } => vec![start, stop, step],
1056        Action::ForPlayerVariable {
1057            player,
1058            start,
1059            stop,
1060            step,
1061            ..
1062        } => vec![player, start, stop, step],
1063        Action::Call { args, .. } => args.iter().collect(),
1064        Action::CallSubroutine { .. } | Action::Else | Action::End => Vec::new(),
1065        Action::Disabled { action } => action_argument_values(action),
1066    }
1067}
1068
1069pub(crate) fn value_children(value: &Value) -> Vec<&Value> {
1070    match value {
1071        Value::Array(values) => values.iter().collect(),
1072        Value::Vector { x, y, z } => vec![x.as_ref(), y.as_ref(), z.as_ref()],
1073        Value::PlayerVariable { player, .. } => vec![player.as_ref()],
1074        Value::Call { args, .. } => args.iter().collect(),
1075        _ => Vec::new(),
1076    }
1077}
1078
1079/// A Workshop event identity and its native filters.
1080#[derive(Debug, Clone, PartialEq, Eq)]
1081pub enum Event {
1082    Global,
1083    EachPlayer,
1084    EachPlayerWithFilters {
1085        team: EventTeam,
1086        target: EventTarget,
1087    },
1088    Player {
1089        kind: PlayerEventKind,
1090        team: EventTeam,
1091        target: EventTarget,
1092    },
1093    Subroutine(String),
1094}
1095
1096impl Event {
1097    /// The canonical catalog event id (`global`, `eachPlayer`, `subroutine`,
1098    /// or a [`PlayerEventKind`] catalog id) this event declares. A
1099    /// `Subroutine` event's declared name is data, not part of the id.
1100    /// Public since `rule-content-v1` names events by this id.
1101    pub fn catalog_id(&self) -> &'static str {
1102        match self {
1103            Self::Global => "global",
1104            Self::EachPlayer | Self::EachPlayerWithFilters { .. } => "eachPlayer",
1105            Self::Player { kind, .. } => kind.catalog_id(),
1106            Self::Subroutine(_) => "subroutine",
1107        }
1108    }
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}