Skip to main content

workshop_rs/program/
source_map.rs

1//! Canonical Workshop artifact formats and the public [`SourceMap`].
2
3use serde::{Deserialize, Serialize};
4
5use super::identity::{condition_identity, value_identity};
6use super::{
7    DeclarationProvenance, Program, ProgramProvenance, Value, ValueProvenance,
8    action_argument_values, fit, value_children,
9};
10use crate::source::{FileId, Position, SourceFile, Span};
11
12/// Identifier of the canonical Workshop text artifact: the Workshop text alone.
13pub const TEXT_V1: &str = "workshop-rs/text-v1";
14
15/// Identifier of the canonical mapped Workshop artifact: Workshop text plus a
16/// [`SourceMap`], serialized by [`MappedText::to_json`].
17pub const MAPPED_TEXT_V1: &str = "workshop-rs/mapped-text-v1";
18
19/// A source mapping detached from a [`Program`].
20///
21/// A source map records the file table, the program shape, and the
22/// position-keyed spans of a span-bearing program. Extract it with
23/// [`SourceMap::extract`], carry it beside the emitted Workshop text, and
24/// [`apply`](Self::apply) it to a program parsed from that text. Spans use
25/// [`Position`] units: 1-based lines and columns counted in Unicode scalar
26/// values.
27///
28/// The mapping granularity is rule, condition, action, direct action argument,
29/// and variable and subroutine declarations. Entries may additionally carry the
30/// identifier span the node names — a rule's name, a rule's subroutine event
31/// binding, an action's target or callee, a value's variable or subroutine
32/// identifier — and condition and action-argument entries carry the provenance
33/// of the value's children, keyed by position in the public [`Value`] tree.
34/// Every field of an entry is optional: a node with only finer-grained
35/// provenance, such as a member read that records just its identifier,
36/// appears as an entry without `span`. Nodes without an authored origin have
37/// no entry, so consumers report evidence on them as unmapped.
38#[derive(Debug, Clone, PartialEq, Eq)]
39pub struct SourceMap {
40    files: Vec<String>,
41    shape: Shape,
42    spans: Vec<MappedNode>,
43}
44
45/// Workshop text together with the [`SourceMap`] of its authored origin: the
46/// `workshop-rs/mapped-text-v1` artifact.
47#[derive(Debug, Clone, PartialEq, Eq)]
48#[non_exhaustive]
49pub struct MappedText {
50    /// The Workshop text, itself a `workshop-rs/text-v1` artifact.
51    pub text: String,
52    /// The mapping from the program parsed from [`text`](Self::text) to the
53    /// authored source.
54    pub map: SourceMap,
55}
56
57/// A failure while decoding or applying a [`SourceMap`].
58///
59/// A failed [`SourceMap::apply`] leaves the program unchanged.
60#[derive(Debug, Clone, PartialEq, Eq)]
61#[non_exhaustive]
62pub enum SourceMapError {
63    GlobalVariableCount {
64        expected: usize,
65        found: usize,
66    },
67    PlayerVariableCount {
68        expected: usize,
69        found: usize,
70    },
71    SubroutineCount {
72        expected: usize,
73        found: usize,
74    },
75    RuleCount {
76        expected: usize,
77        found: usize,
78    },
79    ConditionCount {
80        rule: usize,
81        expected: usize,
82        found: usize,
83    },
84    ActionCount {
85        rule: usize,
86        expected: usize,
87        found: usize,
88    },
89    /// An entry addresses a node outside the program shape.
90    InvalidPosition,
91    /// Two entries map the same node.
92    DuplicateEntry,
93    /// An entry carries no position at all.
94    EmptyEntry,
95    /// A span references a file outside the file table.
96    UnknownFile(usize),
97    /// A span is not a valid 1-based interval.
98    InvalidSpan(Span),
99    /// The artifact declares a format other than `workshop-rs/mapped-text-v1`.
100    UnsupportedFormat(String),
101    /// The artifact is not well-formed JSON of the expected structure.
102    Malformed(String),
103}
104
105impl std::fmt::Display for SourceMapError {
106    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
107        let mismatch = |formatter: &mut std::fmt::Formatter<'_>, what, expected, found| {
108            write!(
109                formatter,
110                "source map shape mismatch: expected {expected} {what}, found {found}"
111            )
112        };
113        match self {
114            Self::GlobalVariableCount { expected, found } => {
115                mismatch(formatter, "global variables", expected, found)
116            }
117            Self::PlayerVariableCount { expected, found } => {
118                mismatch(formatter, "player variables", expected, found)
119            }
120            Self::SubroutineCount { expected, found } => {
121                mismatch(formatter, "subroutines", expected, found)
122            }
123            Self::RuleCount { expected, found } => mismatch(formatter, "rules", expected, found),
124            Self::ConditionCount {
125                rule,
126                expected,
127                found,
128            } => mismatch(
129                formatter,
130                &format!("conditions in rule {rule}"),
131                expected,
132                found,
133            ),
134            Self::ActionCount {
135                rule,
136                expected,
137                found,
138            } => mismatch(
139                formatter,
140                &format!("actions in rule {rule}"),
141                expected,
142                found,
143            ),
144            Self::InvalidPosition => {
145                write!(formatter, "source map entry is outside the program shape")
146            }
147            Self::DuplicateEntry => write!(formatter, "source map maps a node twice"),
148            Self::EmptyEntry => write!(formatter, "source map entry has no position"),
149            Self::UnknownFile(file) => {
150                write!(formatter, "source map span references unknown file {file}")
151            }
152            Self::InvalidSpan(span) => write!(formatter, "invalid source map span {span:?}"),
153            Self::UnsupportedFormat(format) => {
154                write!(formatter, "unsupported mapped text format {format:?}")
155            }
156            Self::Malformed(message) => write!(formatter, "malformed mapped text: {message}"),
157        }
158    }
159}
160
161impl std::error::Error for SourceMapError {}
162
163impl SourceMap {
164    /// Extract the current mapping of a span-bearing program.
165    ///
166    /// Only spans that are still valid for the program's current shape and
167    /// node content are extracted; see [`Program::rule_span`].
168    pub fn extract(program: &Program) -> Self {
169        let mut spans = Vec::new();
170        push_declarations(
171            program,
172            &program.global_variables,
173            |provenance| &provenance.global_variables,
174            |index, span, name_span| MappedNode::GlobalVariable {
175                index,
176                span,
177                name_span,
178            },
179            &mut spans,
180        );
181        push_declarations(
182            program,
183            &program.player_variables,
184            |provenance| &provenance.player_variables,
185            |index, span, name_span| MappedNode::PlayerVariable {
186                index,
187                span,
188                name_span,
189            },
190            &mut spans,
191        );
192        push_declarations(
193            program,
194            &program.subroutines,
195            |provenance| &provenance.subroutines,
196            |index, span, name_span| MappedNode::Subroutine {
197                index,
198                span,
199                name_span,
200            },
201            &mut spans,
202        );
203        for (rule, public) in program.rules.iter().enumerate() {
204            if let Some(provenance) = program.rule_provenance(rule) {
205                if provenance.span.is_some()
206                    || provenance.name.is_some()
207                    || provenance.event_name.is_some()
208                {
209                    spans.push(MappedNode::Rule {
210                        rule,
211                        span: provenance.span.map(WireSpan::from),
212                        name_span: provenance.name.map(WireSpan::from),
213                        event_name_span: provenance.event_name.map(WireSpan::from),
214                    });
215                }
216            }
217            for (condition, public_condition) in public.conditions.iter().enumerate() {
218                let Some(record) = program.condition_record(rule, condition) else {
219                    continue;
220                };
221                let children =
222                    wire_children(&record.children, &value_children(&public_condition.value));
223                let (span, identifier) = if record.identity == condition_identity(public_condition)
224                {
225                    (record.span, record.identifier)
226                } else {
227                    Default::default()
228                };
229                if span.is_some() || identifier.is_some() || !children.is_empty() {
230                    spans.push(MappedNode::Condition {
231                        rule,
232                        condition,
233                        span: span.map(WireSpan::from),
234                        identifier_span: identifier.map(WireSpan::from),
235                        children,
236                    });
237                }
238            }
239            for (action, public_action) in public.actions.iter().enumerate() {
240                if let Some(provenance) = program.action_provenance(rule, action) {
241                    if provenance.span.is_some() || provenance.identifier.is_some() {
242                        spans.push(MappedNode::Action {
243                            rule,
244                            action,
245                            span: provenance.span.map(WireSpan::from),
246                            identifier_span: provenance.identifier.map(WireSpan::from),
247                        });
248                    }
249                }
250                for (argument, value) in action_argument_values(public_action)
251                    .iter()
252                    .copied()
253                    .enumerate()
254                {
255                    let Some(record) = program.argument_record(rule, action, argument) else {
256                        continue;
257                    };
258                    let children = wire_children(&record.children, &value_children(value));
259                    let (span, identifier) = if record.identity == value_identity(value) {
260                        (record.span, record.identifier)
261                    } else {
262                        Default::default()
263                    };
264                    if span.is_some() || identifier.is_some() || !children.is_empty() {
265                        spans.push(MappedNode::ActionArgument {
266                            rule,
267                            action,
268                            argument,
269                            span: span.map(WireSpan::from),
270                            identifier_span: identifier.map(WireSpan::from),
271                            children,
272                        });
273                    }
274                }
275            }
276        }
277        Self {
278            files: program.files.iter().map(|file| file.path.clone()).collect(),
279            shape: Shape::of(program),
280            spans,
281        }
282    }
283
284    /// The paths of the file table that mapped spans refer to by file index.
285    pub fn files(&self) -> &[String] {
286        &self.files
287    }
288
289    /// Replace the source mapping of `program` with this map.
290    ///
291    /// The program's file table becomes this map's file table, and nodes
292    /// without an entry carry no span. The program must have exactly the shape
293    /// the map was extracted from; otherwise the whole mapping is rejected and
294    /// `program` is unchanged.
295    pub fn apply(&self, program: &mut Program) -> Result<(), SourceMapError> {
296        self.shape.check(program)?;
297
298        let mut provenance = ProgramProvenance::default();
299        fit(
300            &mut provenance.global_variables,
301            self.shape.global_variables,
302        );
303        fit(
304            &mut provenance.player_variables,
305            self.shape.player_variables,
306        );
307        fit(&mut provenance.subroutines, self.shape.subroutines);
308        fit(&mut provenance.rules, self.shape.rules.len());
309        for (rule, shape) in provenance.rules.iter_mut().zip(&self.shape.rules) {
310            fit(&mut rule.conditions, shape.conditions);
311            fit(&mut rule.actions, shape.actions);
312        }
313
314        for node in &self.spans {
315            match node {
316                MappedNode::GlobalVariable {
317                    index,
318                    span,
319                    name_span,
320                } => {
321                    let declaration = provenance
322                        .global_variables
323                        .get_mut(*index)
324                        .ok_or(SourceMapError::InvalidPosition)?;
325                    let mapped = self.declaration(*span, *name_span)?;
326                    if declaration.span.is_some() || declaration.name_span.is_some() {
327                        return Err(SourceMapError::DuplicateEntry);
328                    }
329                    *declaration = mapped;
330                }
331                MappedNode::PlayerVariable {
332                    index,
333                    span,
334                    name_span,
335                } => {
336                    let declaration = provenance
337                        .player_variables
338                        .get_mut(*index)
339                        .ok_or(SourceMapError::InvalidPosition)?;
340                    let mapped = self.declaration(*span, *name_span)?;
341                    if declaration.span.is_some() || declaration.name_span.is_some() {
342                        return Err(SourceMapError::DuplicateEntry);
343                    }
344                    *declaration = mapped;
345                }
346                MappedNode::Subroutine {
347                    index,
348                    span,
349                    name_span,
350                } => {
351                    let declaration = provenance
352                        .subroutines
353                        .get_mut(*index)
354                        .ok_or(SourceMapError::InvalidPosition)?;
355                    let mapped = self.declaration(*span, *name_span)?;
356                    if declaration.span.is_some() || declaration.name_span.is_some() {
357                        return Err(SourceMapError::DuplicateEntry);
358                    }
359                    *declaration = mapped;
360                }
361                MappedNode::Rule {
362                    rule,
363                    span,
364                    name_span,
365                    event_name_span,
366                } => {
367                    let span = span.map(|span| self.span(span)).transpose()?;
368                    let name = name_span.map(|span| self.span(span)).transpose()?;
369                    let event_name = event_name_span.map(|span| self.span(span)).transpose()?;
370                    let slot = provenance
371                        .rules
372                        .get_mut(*rule)
373                        .ok_or(SourceMapError::InvalidPosition)?;
374                    if slot.span.is_some() || slot.name.is_some() || slot.event_name.is_some() {
375                        return Err(SourceMapError::DuplicateEntry);
376                    }
377                    if span.is_none() && name.is_none() && event_name.is_none() {
378                        return Err(SourceMapError::EmptyEntry);
379                    }
380                    slot.span = span;
381                    slot.name = name;
382                    slot.event_name = event_name;
383                }
384                MappedNode::Condition {
385                    rule,
386                    condition,
387                    span,
388                    identifier_span,
389                    children,
390                } => {
391                    let span = span.map(|span| self.span(span)).transpose()?;
392                    let identifier = identifier_span.map(|span| self.span(span)).transpose()?;
393                    let has_children = children.iter().any(|child| !wire_value_unmapped(child));
394                    let children = self.mapped_children(
395                        children,
396                        &program
397                            .rules
398                            .get(*rule)
399                            .and_then(|rule| rule.conditions.get(*condition))
400                            .ok_or(SourceMapError::InvalidPosition)?
401                            .value,
402                    )?;
403                    let slot = provenance
404                        .rules
405                        .get_mut(*rule)
406                        .and_then(|rule| rule.conditions.get_mut(*condition))
407                        .ok_or(SourceMapError::InvalidPosition)?;
408                    if slot.span.is_some() || slot.identifier.is_some() || !slot.children.is_empty()
409                    {
410                        return Err(SourceMapError::DuplicateEntry);
411                    }
412                    if span.is_none() && identifier.is_none() && !has_children {
413                        return Err(SourceMapError::EmptyEntry);
414                    }
415                    slot.span = span;
416                    slot.identifier = identifier;
417                    slot.children = children;
418                }
419                MappedNode::Action {
420                    rule,
421                    action,
422                    span,
423                    identifier_span,
424                } => {
425                    let span = span.map(|span| self.span(span)).transpose()?;
426                    let identifier = identifier_span.map(|span| self.span(span)).transpose()?;
427                    let slot = provenance
428                        .rules
429                        .get_mut(*rule)
430                        .and_then(|rule| rule.actions.get_mut(*action))
431                        .ok_or(SourceMapError::InvalidPosition)?;
432                    if slot.span.is_some() || slot.identifier.is_some() {
433                        return Err(SourceMapError::DuplicateEntry);
434                    }
435                    if span.is_none() && identifier.is_none() {
436                        return Err(SourceMapError::EmptyEntry);
437                    }
438                    slot.span = span;
439                    slot.identifier = identifier;
440                }
441                MappedNode::ActionArgument {
442                    rule,
443                    action,
444                    argument,
445                    span,
446                    identifier_span,
447                    children,
448                } => {
449                    let span = span.map(|span| self.span(span)).transpose()?;
450                    let identifier = identifier_span.map(|span| self.span(span)).transpose()?;
451                    let argument_values = program
452                        .rules
453                        .get(*rule)
454                        .and_then(|rule| rule.actions.get(*action))
455                        .map(action_argument_values)
456                        .ok_or(SourceMapError::InvalidPosition)?;
457                    let Some(&value) = argument_values.get(*argument) else {
458                        return Err(SourceMapError::InvalidPosition);
459                    };
460                    let count = argument_values.len();
461                    let has_children = children.iter().any(|child| !wire_value_unmapped(child));
462                    let children = self.mapped_children(children, value)?;
463                    let arguments = &mut provenance
464                        .rules
465                        .get_mut(*rule)
466                        .and_then(|rule| rule.actions.get_mut(*action))
467                        .ok_or(SourceMapError::InvalidPosition)?
468                        .arguments;
469                    fit(arguments, count);
470                    let slot = &mut arguments[*argument];
471                    if slot.span.is_some() || slot.identifier.is_some() || !slot.children.is_empty()
472                    {
473                        return Err(SourceMapError::DuplicateEntry);
474                    }
475                    if span.is_none() && identifier.is_none() && !has_children {
476                        return Err(SourceMapError::EmptyEntry);
477                    }
478                    slot.span = span;
479                    slot.identifier = identifier;
480                    slot.children = children;
481                }
482            }
483        }
484
485        program.files.clear();
486        for path in &self.files {
487            program.add_file(SourceFile::new(path.clone()));
488        }
489        program.provenance = Some(Box::new(provenance));
490        program.record_identities();
491        Ok(())
492    }
493
494    fn span(&self, wire: WireSpan) -> Result<Span, SourceMapError> {
495        if wire.file >= self.files.len() {
496            return Err(SourceMapError::UnknownFile(wire.file));
497        }
498        let span = Span::from(wire);
499        if !span.is_valid() {
500            return Err(SourceMapError::InvalidSpan(span));
501        }
502        Ok(span)
503    }
504
505    fn declaration(
506        &self,
507        span: Option<WireSpan>,
508        name_span: Option<WireSpan>,
509    ) -> Result<DeclarationProvenance, SourceMapError> {
510        if span.is_none() && name_span.is_none() {
511            return Err(SourceMapError::EmptyEntry);
512        }
513        Ok(DeclarationProvenance {
514            span: span.map(|span| self.span(span)).transpose()?,
515            name_span: name_span.map(|span| self.span(span)).transpose()?,
516            ..DeclarationProvenance::default()
517        })
518    }
519
520    /// Map serialized children onto a public value's children by position.
521    ///
522    /// Emission can canonicalize a value into a form with a different public
523    /// arity — `(expr).member` reparse as a variable read drops the member-name
524    /// child, for example — so children are matched positionally: entries
525    /// beyond the value's children are dropped and positions with no entry are
526    /// left unmapped.
527    fn mapped_children(
528        &self,
529        wire: &[WireValue],
530        value: &Value,
531    ) -> Result<Vec<ValueProvenance>, SourceMapError> {
532        let children = value_children(value);
533        let mut mapped = wire
534            .iter()
535            .zip(children.iter().copied())
536            .map(|(wire, value)| self.mapped_value(wire, value))
537            .collect::<Result<Vec<_>, _>>()?;
538        mapped.resize_with(children.len(), ValueProvenance::default);
539        Ok(mapped)
540    }
541
542    fn mapped_value(
543        &self,
544        wire: &WireValue,
545        value: &Value,
546    ) -> Result<ValueProvenance, SourceMapError> {
547        Ok(ValueProvenance {
548            span: wire.span.map(|span| self.span(span)).transpose()?,
549            identifier: wire
550                .identifier_span
551                .map(|span| self.span(span))
552                .transpose()?,
553            children: self.mapped_children(&wire.children, value)?,
554            ..ValueProvenance::default()
555        })
556    }
557}
558
559impl MappedText {
560    pub fn new(text: impl Into<String>, map: SourceMap) -> Self {
561        Self {
562            text: text.into(),
563            map,
564        }
565    }
566
567    /// Serialize as a `workshop-rs/mapped-text-v1` JSON document.
568    pub fn to_json(&self) -> String {
569        let artifact = Artifact {
570            format: MAPPED_TEXT_V1.to_string(),
571            text: self.text.clone(),
572            files: self
573                .map
574                .files
575                .iter()
576                .map(|path| WireFile { path: path.clone() })
577                .collect(),
578            shape: self.map.shape.clone(),
579            spans: self.map.spans.clone(),
580        };
581        serde_json::to_string(&artifact).expect("mapped text serializes to JSON")
582    }
583
584    /// Decode a `workshop-rs/mapped-text-v1` JSON document.
585    ///
586    /// Decoding checks the format and structure only; [`SourceMap::apply`]
587    /// validates the mapping against the program it is applied to.
588    pub fn from_json(json: &str) -> Result<Self, SourceMapError> {
589        let value: serde_json::Value = serde_json::from_str(json)
590            .map_err(|error| SourceMapError::Malformed(error.to_string()))?;
591        match value.get("format").and_then(serde_json::Value::as_str) {
592            Some(MAPPED_TEXT_V1) => {}
593            Some(other) => return Err(SourceMapError::UnsupportedFormat(other.to_string())),
594            None => return Err(SourceMapError::Malformed("missing format".to_string())),
595        }
596        let artifact: Artifact = serde_json::from_value(value)
597            .map_err(|error| SourceMapError::Malformed(error.to_string()))?;
598        Ok(Self {
599            text: artifact.text,
600            map: SourceMap {
601                files: artifact.files.into_iter().map(|file| file.path).collect(),
602                shape: artifact.shape,
603                spans: artifact.spans,
604            },
605        })
606    }
607}
608
609/// The wire form of recorded value-provenance children, paired with the
610/// public children they describe: children keep their position up to the
611/// last mapped one, a record whose value no longer has the recorded identity
612/// serializes as unmapped, a level whose recorded count no longer matches is
613/// dropped entirely, and a level with no recorded provenance at all is
614/// omitted.
615fn wire_children(children: &[ValueProvenance], values: &[&Value]) -> Vec<WireValue> {
616    if children.len() != values.len() {
617        return Vec::new();
618    }
619    let mut wire: Vec<WireValue> = children
620        .iter()
621        .zip(values.iter().copied())
622        .map(|(child, value)| wire_value(child, value))
623        .collect();
624    let last = wire
625        .iter()
626        .rposition(|child| !wire_value_unmapped(child))
627        .map(|index| index + 1)
628        .unwrap_or(0);
629    wire.truncate(last);
630    wire
631}
632
633fn wire_value(provenance: &ValueProvenance, value: &Value) -> WireValue {
634    let (span, identifier) = if provenance.identity == value_identity(value) {
635        (provenance.span, provenance.identifier)
636    } else {
637        Default::default()
638    };
639    WireValue {
640        span: span.map(WireSpan::from),
641        identifier_span: identifier.map(WireSpan::from),
642        children: wire_children(&provenance.children, &value_children(value)),
643    }
644}
645
646/// Whether a serialized value entry contributes any position.
647fn wire_value_unmapped(value: &WireValue) -> bool {
648    value.span.is_none()
649        && value.identifier_span.is_none()
650        && value.children.iter().all(wire_value_unmapped)
651}
652
653fn push_declarations<T: std::fmt::Debug>(
654    program: &Program,
655    nodes: &[T],
656    recorded: impl Fn(&ProgramProvenance) -> &[DeclarationProvenance],
657    node: impl Fn(usize, Option<WireSpan>, Option<WireSpan>) -> MappedNode,
658    output: &mut Vec<MappedNode>,
659) {
660    for index in 0..nodes.len() {
661        let declaration = program.declaration_provenance(&recorded, nodes, index);
662        if declaration.span.is_some() || declaration.name_span.is_some() {
663            output.push(node(
664                index,
665                declaration.span.map(WireSpan::from),
666                declaration.name_span.map(WireSpan::from),
667            ));
668        }
669    }
670}
671
672#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
673struct Artifact {
674    format: String,
675    text: String,
676    files: Vec<WireFile>,
677    shape: Shape,
678    spans: Vec<MappedNode>,
679}
680
681#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
682struct WireFile {
683    path: String,
684}
685
686#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
687struct Shape {
688    global_variables: usize,
689    player_variables: usize,
690    subroutines: usize,
691    rules: Vec<RuleShape>,
692}
693
694#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
695struct RuleShape {
696    conditions: usize,
697    actions: usize,
698}
699
700impl Shape {
701    fn of(program: &Program) -> Self {
702        Self {
703            global_variables: program.global_variables.len(),
704            player_variables: program.player_variables.len(),
705            subroutines: program.subroutines.len(),
706            rules: program
707                .rules
708                .iter()
709                .map(|rule| RuleShape {
710                    conditions: rule.conditions.len(),
711                    actions: rule.actions.len(),
712                })
713                .collect(),
714        }
715    }
716
717    fn check(&self, program: &Program) -> Result<(), SourceMapError> {
718        let found = Self::of(program);
719        if self.global_variables != found.global_variables {
720            return Err(SourceMapError::GlobalVariableCount {
721                expected: self.global_variables,
722                found: found.global_variables,
723            });
724        }
725        if self.player_variables != found.player_variables {
726            return Err(SourceMapError::PlayerVariableCount {
727                expected: self.player_variables,
728                found: found.player_variables,
729            });
730        }
731        if self.subroutines != found.subroutines {
732            return Err(SourceMapError::SubroutineCount {
733                expected: self.subroutines,
734                found: found.subroutines,
735            });
736        }
737        if self.rules.len() != found.rules.len() {
738            return Err(SourceMapError::RuleCount {
739                expected: self.rules.len(),
740                found: found.rules.len(),
741            });
742        }
743        for (rule, (expected, found)) in self.rules.iter().zip(&found.rules).enumerate() {
744            if expected.conditions != found.conditions {
745                return Err(SourceMapError::ConditionCount {
746                    rule,
747                    expected: expected.conditions,
748                    found: found.conditions,
749                });
750            }
751            if expected.actions != found.actions {
752                return Err(SourceMapError::ActionCount {
753                    rule,
754                    expected: expected.actions,
755                    found: found.actions,
756                });
757            }
758        }
759        Ok(())
760    }
761}
762
763#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
764#[serde(tag = "node", rename_all = "snake_case")]
765enum MappedNode {
766    Rule {
767        rule: usize,
768        #[serde(default, skip_serializing_if = "Option::is_none")]
769        span: Option<WireSpan>,
770        #[serde(default, skip_serializing_if = "Option::is_none")]
771        name_span: Option<WireSpan>,
772        #[serde(default, skip_serializing_if = "Option::is_none")]
773        event_name_span: Option<WireSpan>,
774    },
775    Condition {
776        rule: usize,
777        condition: usize,
778        #[serde(default, skip_serializing_if = "Option::is_none")]
779        span: Option<WireSpan>,
780        #[serde(default, skip_serializing_if = "Option::is_none")]
781        identifier_span: Option<WireSpan>,
782        #[serde(default, skip_serializing_if = "Vec::is_empty")]
783        children: Vec<WireValue>,
784    },
785    Action {
786        rule: usize,
787        action: usize,
788        #[serde(default, skip_serializing_if = "Option::is_none")]
789        span: Option<WireSpan>,
790        #[serde(default, skip_serializing_if = "Option::is_none")]
791        identifier_span: Option<WireSpan>,
792    },
793    ActionArgument {
794        rule: usize,
795        action: usize,
796        argument: usize,
797        #[serde(default, skip_serializing_if = "Option::is_none")]
798        span: Option<WireSpan>,
799        #[serde(default, skip_serializing_if = "Option::is_none")]
800        identifier_span: Option<WireSpan>,
801        #[serde(default, skip_serializing_if = "Vec::is_empty")]
802        children: Vec<WireValue>,
803    },
804    GlobalVariable {
805        index: usize,
806        #[serde(default, skip_serializing_if = "Option::is_none")]
807        span: Option<WireSpan>,
808        #[serde(default, skip_serializing_if = "Option::is_none")]
809        name_span: Option<WireSpan>,
810    },
811    PlayerVariable {
812        index: usize,
813        #[serde(default, skip_serializing_if = "Option::is_none")]
814        span: Option<WireSpan>,
815        #[serde(default, skip_serializing_if = "Option::is_none")]
816        name_span: Option<WireSpan>,
817    },
818    Subroutine {
819        index: usize,
820        #[serde(default, skip_serializing_if = "Option::is_none")]
821        span: Option<WireSpan>,
822        #[serde(default, skip_serializing_if = "Option::is_none")]
823        name_span: Option<WireSpan>,
824    },
825}
826
827/// The mapped provenance of one value node, mirroring the structure of the
828/// public [`Value`] tree: `children` addresses child values by position.
829#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
830struct WireValue {
831    #[serde(default, skip_serializing_if = "Option::is_none")]
832    span: Option<WireSpan>,
833    #[serde(default, skip_serializing_if = "Option::is_none")]
834    identifier_span: Option<WireSpan>,
835    #[serde(default, skip_serializing_if = "Vec::is_empty")]
836    children: Vec<WireValue>,
837}
838
839#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
840struct WireSpan {
841    file: usize,
842    start: WirePosition,
843    end: WirePosition,
844}
845
846#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
847struct WirePosition {
848    line: u32,
849    column: u32,
850}
851
852impl From<Span> for WireSpan {
853    fn from(span: Span) -> Self {
854        Self {
855            file: span.file.index(),
856            start: span.start.into(),
857            end: span.end.into(),
858        }
859    }
860}
861
862impl From<WireSpan> for Span {
863    fn from(wire: WireSpan) -> Self {
864        Span::new(
865            FileId::from_index(wire.file),
866            wire.start.into(),
867            wire.end.into(),
868        )
869    }
870}
871
872impl From<Position> for WirePosition {
873    fn from(position: Position) -> Self {
874        Self {
875            line: position.line,
876            column: position.col,
877        }
878    }
879}
880
881impl From<WirePosition> for Position {
882    fn from(position: WirePosition) -> Self {
883        Position::new(position.line, position.column)
884    }
885}