Skip to main content

workshop_rs/program/
source_map.rs

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