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                if 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            }
215            for (condition, public_condition) in public.conditions.iter().enumerate() {
216                let Some(record) = program.condition_record(rule, condition) else {
217                    continue;
218                };
219                let children =
220                    wire_children(&record.children, &value_children(&public_condition.value));
221                let (span, identifier) = if record.identity == condition_identity(public_condition)
222                {
223                    (record.span, record.identifier)
224                } else {
225                    Default::default()
226                };
227                if span.is_some() || identifier.is_some() || !children.is_empty() {
228                    spans.push(MappedNode::Condition {
229                        rule,
230                        condition,
231                        span: span.map(WireSpan::from),
232                        identifier_span: identifier.map(WireSpan::from),
233                        children,
234                    });
235                }
236            }
237            for (action, public_action) in public.actions.iter().enumerate() {
238                if let Some(provenance) = program.action_provenance(rule, action) {
239                    if provenance.span.is_some() || provenance.identifier.is_some() {
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                }
248                for (argument, value) in action_argument_values(public_action)
249                    .iter()
250                    .copied()
251                    .enumerate()
252                {
253                    let Some(record) = program.argument_record(rule, action, argument) else {
254                        continue;
255                    };
256                    let children = wire_children(&record.children, &value_children(value));
257                    let (span, identifier) = if record.identity == value_identity(value) {
258                        (record.span, record.identifier)
259                    } else {
260                        Default::default()
261                    };
262                    if span.is_some() || identifier.is_some() || !children.is_empty() {
263                        spans.push(MappedNode::ActionArgument {
264                            rule,
265                            action,
266                            argument,
267                            span: span.map(WireSpan::from),
268                            identifier_span: identifier.map(WireSpan::from),
269                            children,
270                        });
271                    }
272                }
273            }
274        }
275        Self {
276            files: program.files.iter().map(|file| file.path.clone()).collect(),
277            shape: Shape::of(program),
278            spans,
279        }
280    }
281
282    /// The paths of the file table that mapped spans refer to by file index.
283    pub fn files(&self) -> &[String] {
284        &self.files
285    }
286
287    /// Replace the source mapping of `program` with this map.
288    ///
289    /// The program's file table becomes this map's file table, and nodes
290    /// without an entry carry no span. The program must have exactly the shape
291    /// the map was extracted from; otherwise the whole mapping is rejected and
292    /// `program` is unchanged.
293    pub fn apply(&self, program: &mut Program) -> Result<(), SourceMapError> {
294        self.shape.check(program)?;
295
296        let mut provenance = ProgramProvenance::default();
297        fit(
298            &mut provenance.global_variables,
299            self.shape.global_variables,
300        );
301        fit(
302            &mut provenance.player_variables,
303            self.shape.player_variables,
304        );
305        fit(&mut provenance.subroutines, self.shape.subroutines);
306        fit(&mut provenance.rules, self.shape.rules.len());
307        for (rule, shape) in provenance.rules.iter_mut().zip(&self.shape.rules) {
308            fit(&mut rule.conditions, shape.conditions);
309            fit(&mut rule.actions, shape.actions);
310        }
311
312        for node in &self.spans {
313            match node {
314                MappedNode::GlobalVariable {
315                    index,
316                    span,
317                    name_span,
318                } => {
319                    let declaration = provenance
320                        .global_variables
321                        .get_mut(*index)
322                        .ok_or(SourceMapError::InvalidPosition)?;
323                    let mapped = self.declaration(*span, *name_span)?;
324                    if declaration.span.is_some() || declaration.name_span.is_some() {
325                        return Err(SourceMapError::DuplicateEntry);
326                    }
327                    *declaration = mapped;
328                }
329                MappedNode::PlayerVariable {
330                    index,
331                    span,
332                    name_span,
333                } => {
334                    let declaration = provenance
335                        .player_variables
336                        .get_mut(*index)
337                        .ok_or(SourceMapError::InvalidPosition)?;
338                    let mapped = self.declaration(*span, *name_span)?;
339                    if declaration.span.is_some() || declaration.name_span.is_some() {
340                        return Err(SourceMapError::DuplicateEntry);
341                    }
342                    *declaration = mapped;
343                }
344                MappedNode::Subroutine {
345                    index,
346                    span,
347                    name_span,
348                } => {
349                    let declaration = provenance
350                        .subroutines
351                        .get_mut(*index)
352                        .ok_or(SourceMapError::InvalidPosition)?;
353                    let mapped = self.declaration(*span, *name_span)?;
354                    if declaration.span.is_some() || declaration.name_span.is_some() {
355                        return Err(SourceMapError::DuplicateEntry);
356                    }
357                    *declaration = mapped;
358                }
359                MappedNode::Rule {
360                    rule,
361                    span,
362                    name_span,
363                    event_name_span,
364                } => {
365                    let span = span.map(|span| self.span(span)).transpose()?;
366                    let name = name_span.map(|span| self.span(span)).transpose()?;
367                    let event_name = event_name_span.map(|span| self.span(span)).transpose()?;
368                    let slot = provenance
369                        .rules
370                        .get_mut(*rule)
371                        .ok_or(SourceMapError::InvalidPosition)?;
372                    if slot.span.is_some() || slot.name.is_some() || slot.event_name.is_some() {
373                        return Err(SourceMapError::DuplicateEntry);
374                    }
375                    if span.is_none() && name.is_none() && event_name.is_none() {
376                        return Err(SourceMapError::EmptyEntry);
377                    }
378                    slot.span = span;
379                    slot.name = name;
380                    slot.event_name = event_name;
381                }
382                MappedNode::Condition {
383                    rule,
384                    condition,
385                    span,
386                    identifier_span,
387                    children,
388                } => {
389                    let span = span.map(|span| self.span(span)).transpose()?;
390                    let identifier = identifier_span.map(|span| self.span(span)).transpose()?;
391                    let has_children = children.iter().any(|child| !wire_value_unmapped(child));
392                    let children = self.mapped_children(
393                        children,
394                        &program
395                            .rules
396                            .get(*rule)
397                            .and_then(|rule| rule.conditions.get(*condition))
398                            .ok_or(SourceMapError::InvalidPosition)?
399                            .value,
400                    )?;
401                    let slot = provenance
402                        .rules
403                        .get_mut(*rule)
404                        .and_then(|rule| rule.conditions.get_mut(*condition))
405                        .ok_or(SourceMapError::InvalidPosition)?;
406                    if slot.span.is_some() || slot.identifier.is_some() || !slot.children.is_empty()
407                    {
408                        return Err(SourceMapError::DuplicateEntry);
409                    }
410                    if span.is_none() && identifier.is_none() && !has_children {
411                        return Err(SourceMapError::EmptyEntry);
412                    }
413                    slot.span = span;
414                    slot.identifier = identifier;
415                    slot.children = children;
416                }
417                MappedNode::Action {
418                    rule,
419                    action,
420                    span,
421                    identifier_span,
422                } => {
423                    let span = span.map(|span| self.span(span)).transpose()?;
424                    let identifier = identifier_span.map(|span| self.span(span)).transpose()?;
425                    let slot = provenance
426                        .rules
427                        .get_mut(*rule)
428                        .and_then(|rule| rule.actions.get_mut(*action))
429                        .ok_or(SourceMapError::InvalidPosition)?;
430                    if slot.span.is_some() || slot.identifier.is_some() {
431                        return Err(SourceMapError::DuplicateEntry);
432                    }
433                    if span.is_none() && identifier.is_none() {
434                        return Err(SourceMapError::EmptyEntry);
435                    }
436                    slot.span = span;
437                    slot.identifier = identifier;
438                }
439                MappedNode::ActionArgument {
440                    rule,
441                    action,
442                    argument,
443                    span,
444                    identifier_span,
445                    children,
446                } => {
447                    let span = span.map(|span| self.span(span)).transpose()?;
448                    let identifier = identifier_span.map(|span| self.span(span)).transpose()?;
449                    let argument_values = program
450                        .rules
451                        .get(*rule)
452                        .and_then(|rule| rule.actions.get(*action))
453                        .map(action_argument_values)
454                        .ok_or(SourceMapError::InvalidPosition)?;
455                    let Some(&value) = argument_values.get(*argument) else {
456                        return Err(SourceMapError::InvalidPosition);
457                    };
458                    let count = argument_values.len();
459                    let has_children = children.iter().any(|child| !wire_value_unmapped(child));
460                    let children = self.mapped_children(children, value)?;
461                    let arguments = &mut provenance
462                        .rules
463                        .get_mut(*rule)
464                        .and_then(|rule| rule.actions.get_mut(*action))
465                        .ok_or(SourceMapError::InvalidPosition)?
466                        .arguments;
467                    fit(arguments, count);
468                    let slot = &mut arguments[*argument];
469                    if slot.span.is_some() || slot.identifier.is_some() || !slot.children.is_empty()
470                    {
471                        return Err(SourceMapError::DuplicateEntry);
472                    }
473                    if span.is_none() && identifier.is_none() && !has_children {
474                        return Err(SourceMapError::EmptyEntry);
475                    }
476                    slot.span = span;
477                    slot.identifier = identifier;
478                    slot.children = children;
479                }
480            }
481        }
482
483        program.files.clear();
484        for path in &self.files {
485            program.add_file(SourceFile::new(path.clone()));
486        }
487        // The map carries no settings entries, so the settings tree's spans
488        // still index the text the program was parsed from, not the authored
489        // members the rebound file table names. Clear them rather than leave
490        // stale positions that would fail validation once a member retains
491        // source text (wrightkit/wright#583).
492        if let Some(settings) = &mut program.settings {
493            settings.clear_spans();
494        }
495        program.provenance = Some(Box::new(provenance));
496        program.record_identities();
497        Ok(())
498    }
499
500    fn span(&self, wire: WireSpan) -> Result<Span, SourceMapError> {
501        if wire.file >= self.files.len() {
502            return Err(SourceMapError::UnknownFile(wire.file));
503        }
504        let span = Span::from(wire);
505        if !span.is_valid() {
506            return Err(SourceMapError::InvalidSpan(span));
507        }
508        Ok(span)
509    }
510
511    fn declaration(
512        &self,
513        span: Option<WireSpan>,
514        name_span: Option<WireSpan>,
515    ) -> Result<DeclarationProvenance, SourceMapError> {
516        if span.is_none() && name_span.is_none() {
517            return Err(SourceMapError::EmptyEntry);
518        }
519        Ok(DeclarationProvenance {
520            span: span.map(|span| self.span(span)).transpose()?,
521            name_span: name_span.map(|span| self.span(span)).transpose()?,
522            ..DeclarationProvenance::default()
523        })
524    }
525
526    fn mapped_children(
527        &self,
528        wire: &[WireValue],
529        value: &Value,
530    ) -> Result<Vec<ValueProvenance>, SourceMapError> {
531        let children = value_children(value);
532        let mut mapped = wire
533            .iter()
534            .zip(children.iter().copied())
535            .map(|(wire, value)| self.mapped_value(wire, value))
536            .collect::<Result<Vec<_>, _>>()?;
537        mapped.resize_with(children.len(), ValueProvenance::default);
538        Ok(mapped)
539    }
540
541    fn mapped_value(
542        &self,
543        wire: &WireValue,
544        value: &Value,
545    ) -> Result<ValueProvenance, SourceMapError> {
546        Ok(ValueProvenance {
547            span: wire.span.map(|span| self.span(span)).transpose()?,
548            identifier: wire
549                .identifier_span
550                .map(|span| self.span(span))
551                .transpose()?,
552            children: self.mapped_children(&wire.children, value)?,
553            ..ValueProvenance::default()
554        })
555    }
556}
557
558impl MappedText {
559    pub fn new(text: impl Into<String>, map: SourceMap) -> Self {
560        Self {
561            text: text.into(),
562            map,
563        }
564    }
565
566    /// Serialize as a `workshop-rs/mapped-text-v1` JSON document.
567    pub fn to_json(&self) -> String {
568        let artifact = Artifact {
569            format: MAPPED_TEXT_V1.to_string(),
570            text: self.text.clone(),
571            files: self
572                .map
573                .files
574                .iter()
575                .map(|path| WireFile { path: path.clone() })
576                .collect(),
577            shape: self.map.shape.clone(),
578            spans: self.map.spans.clone(),
579        };
580        serde_json::to_string(&artifact).expect("mapped text serializes to JSON")
581    }
582
583    /// Decode a `workshop-rs/mapped-text-v1` JSON document.
584    ///
585    /// Decoding checks the format and structure only; [`SourceMap::apply`]
586    /// validates the mapping against the program it is applied to.
587    pub fn from_json(json: &str) -> Result<Self, SourceMapError> {
588        let value: serde_json::Value = serde_json::from_str(json)
589            .map_err(|error| SourceMapError::Malformed(error.to_string()))?;
590        match value.get("format").and_then(serde_json::Value::as_str) {
591            Some(MAPPED_TEXT_V1) => {}
592            Some(other) => return Err(SourceMapError::UnsupportedFormat(other.to_string())),
593            None => return Err(SourceMapError::Malformed("missing format".to_string())),
594        }
595        let artifact: Artifact = serde_json::from_value(value)
596            .map_err(|error| SourceMapError::Malformed(error.to_string()))?;
597        Ok(Self {
598            text: artifact.text,
599            map: SourceMap {
600                files: artifact.files.into_iter().map(|file| file.path).collect(),
601                shape: artifact.shape,
602                spans: artifact.spans,
603            },
604        })
605    }
606}
607
608fn wire_children(children: &[ValueProvenance], values: &[&Value]) -> Vec<WireValue> {
609    if children.len() != values.len() {
610        return Vec::new();
611    }
612    let mut wire: Vec<WireValue> = children
613        .iter()
614        .zip(values.iter().copied())
615        .map(|(child, value)| wire_value(child, value))
616        .collect();
617    let last = wire
618        .iter()
619        .rposition(|child| !wire_value_unmapped(child))
620        .map(|index| index + 1)
621        .unwrap_or(0);
622    wire.truncate(last);
623    wire
624}
625
626fn wire_value(provenance: &ValueProvenance, value: &Value) -> WireValue {
627    let (span, identifier) = if provenance.identity == value_identity(value) {
628        (provenance.span, provenance.identifier)
629    } else {
630        Default::default()
631    };
632    WireValue {
633        span: span.map(WireSpan::from),
634        identifier_span: identifier.map(WireSpan::from),
635        children: wire_children(&provenance.children, &value_children(value)),
636    }
637}
638
639fn wire_value_unmapped(value: &WireValue) -> bool {
640    value.span.is_none()
641        && value.identifier_span.is_none()
642        && value.children.iter().all(wire_value_unmapped)
643}
644
645fn push_declarations<T: std::fmt::Debug>(
646    program: &Program,
647    nodes: &[T],
648    recorded: impl Fn(&ProgramProvenance) -> &[DeclarationProvenance],
649    node: impl Fn(usize, Option<WireSpan>, Option<WireSpan>) -> MappedNode,
650    output: &mut Vec<MappedNode>,
651) {
652    for index in 0..nodes.len() {
653        let declaration = program.declaration_provenance(&recorded, nodes, index);
654        if declaration.span.is_some() || declaration.name_span.is_some() {
655            output.push(node(
656                index,
657                declaration.span.map(WireSpan::from),
658                declaration.name_span.map(WireSpan::from),
659            ));
660        }
661    }
662}
663
664#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
665struct Artifact {
666    format: String,
667    text: String,
668    files: Vec<WireFile>,
669    shape: Shape,
670    spans: Vec<MappedNode>,
671}
672
673#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
674struct WireFile {
675    path: String,
676}
677
678#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
679struct Shape {
680    global_variables: usize,
681    player_variables: usize,
682    subroutines: usize,
683    rules: Vec<RuleShape>,
684}
685
686#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
687struct RuleShape {
688    conditions: usize,
689    actions: usize,
690}
691
692impl Shape {
693    fn of(program: &Program) -> Self {
694        Self {
695            global_variables: program.global_variables.len(),
696            player_variables: program.player_variables.len(),
697            subroutines: program.subroutines.len(),
698            rules: program
699                .rules
700                .iter()
701                .map(|rule| RuleShape {
702                    conditions: rule.conditions.len(),
703                    actions: rule.actions.len(),
704                })
705                .collect(),
706        }
707    }
708
709    fn check(&self, program: &Program) -> Result<(), SourceMapError> {
710        let found = Self::of(program);
711        if self.global_variables != found.global_variables {
712            return Err(SourceMapError::GlobalVariableCount {
713                expected: self.global_variables,
714                found: found.global_variables,
715            });
716        }
717        if self.player_variables != found.player_variables {
718            return Err(SourceMapError::PlayerVariableCount {
719                expected: self.player_variables,
720                found: found.player_variables,
721            });
722        }
723        if self.subroutines != found.subroutines {
724            return Err(SourceMapError::SubroutineCount {
725                expected: self.subroutines,
726                found: found.subroutines,
727            });
728        }
729        if self.rules.len() != found.rules.len() {
730            return Err(SourceMapError::RuleCount {
731                expected: self.rules.len(),
732                found: found.rules.len(),
733            });
734        }
735        for (rule, (expected, found)) in self.rules.iter().zip(&found.rules).enumerate() {
736            if expected.conditions != found.conditions {
737                return Err(SourceMapError::ConditionCount {
738                    rule,
739                    expected: expected.conditions,
740                    found: found.conditions,
741                });
742            }
743            if expected.actions != found.actions {
744                return Err(SourceMapError::ActionCount {
745                    rule,
746                    expected: expected.actions,
747                    found: found.actions,
748                });
749            }
750        }
751        Ok(())
752    }
753}
754
755#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
756#[serde(tag = "node", rename_all = "snake_case")]
757enum MappedNode {
758    Rule {
759        rule: usize,
760        #[serde(default, skip_serializing_if = "Option::is_none")]
761        span: Option<WireSpan>,
762        #[serde(default, skip_serializing_if = "Option::is_none")]
763        name_span: Option<WireSpan>,
764        #[serde(default, skip_serializing_if = "Option::is_none")]
765        event_name_span: Option<WireSpan>,
766    },
767    Condition {
768        rule: usize,
769        condition: usize,
770        #[serde(default, skip_serializing_if = "Option::is_none")]
771        span: Option<WireSpan>,
772        #[serde(default, skip_serializing_if = "Option::is_none")]
773        identifier_span: Option<WireSpan>,
774        #[serde(default, skip_serializing_if = "Vec::is_empty")]
775        children: Vec<WireValue>,
776    },
777    Action {
778        rule: usize,
779        action: usize,
780        #[serde(default, skip_serializing_if = "Option::is_none")]
781        span: Option<WireSpan>,
782        #[serde(default, skip_serializing_if = "Option::is_none")]
783        identifier_span: Option<WireSpan>,
784    },
785    ActionArgument {
786        rule: usize,
787        action: usize,
788        argument: usize,
789        #[serde(default, skip_serializing_if = "Option::is_none")]
790        span: Option<WireSpan>,
791        #[serde(default, skip_serializing_if = "Option::is_none")]
792        identifier_span: Option<WireSpan>,
793        #[serde(default, skip_serializing_if = "Vec::is_empty")]
794        children: Vec<WireValue>,
795    },
796    GlobalVariable {
797        index: usize,
798        #[serde(default, skip_serializing_if = "Option::is_none")]
799        span: Option<WireSpan>,
800        #[serde(default, skip_serializing_if = "Option::is_none")]
801        name_span: Option<WireSpan>,
802    },
803    PlayerVariable {
804        index: usize,
805        #[serde(default, skip_serializing_if = "Option::is_none")]
806        span: Option<WireSpan>,
807        #[serde(default, skip_serializing_if = "Option::is_none")]
808        name_span: Option<WireSpan>,
809    },
810    Subroutine {
811        index: usize,
812        #[serde(default, skip_serializing_if = "Option::is_none")]
813        span: Option<WireSpan>,
814        #[serde(default, skip_serializing_if = "Option::is_none")]
815        name_span: Option<WireSpan>,
816    },
817}
818
819#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
820struct WireValue {
821    #[serde(default, skip_serializing_if = "Option::is_none")]
822    span: Option<WireSpan>,
823    #[serde(default, skip_serializing_if = "Option::is_none")]
824    identifier_span: Option<WireSpan>,
825    #[serde(default, skip_serializing_if = "Vec::is_empty")]
826    children: Vec<WireValue>,
827}
828
829#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
830struct WireSpan {
831    file: usize,
832    start: WirePosition,
833    end: WirePosition,
834}
835
836#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
837struct WirePosition {
838    line: u32,
839    column: u32,
840}
841
842impl From<Span> for WireSpan {
843    fn from(span: Span) -> Self {
844        Self {
845            file: span.file.index(),
846            start: span.start.into(),
847            end: span.end.into(),
848        }
849    }
850}
851
852impl From<WireSpan> for Span {
853    fn from(wire: WireSpan) -> Self {
854        Span::new(
855            FileId::from_index(wire.file),
856            wire.start.into(),
857            wire.end.into(),
858        )
859    }
860}
861
862impl From<Position> for WirePosition {
863    fn from(position: Position) -> Self {
864        Self {
865            line: position.line,
866            column: position.col,
867        }
868    }
869}
870
871impl From<WirePosition> for Position {
872    fn from(position: WirePosition) -> Self {
873        Position::new(position.line, position.column)
874    }
875}