1use serde::{Deserialize, Serialize};
4
5use super::{DeclarationProvenance, Program, ProgramProvenance, action_argument_count, fit};
6use crate::source::{FileId, Position, SourceFile, Span};
7
8pub const TEXT_V1: &str = "workshop-rs/text-v1";
10
11pub const MAPPED_TEXT_V1: &str = "workshop-rs/mapped-text-v1";
14
15#[derive(Debug, Clone, PartialEq, Eq)]
28pub struct SourceMap {
29 files: Vec<String>,
30 shape: Shape,
31 spans: Vec<MappedNode>,
32}
33
34#[derive(Debug, Clone, PartialEq, Eq)]
37pub struct MappedText {
38 pub text: String,
40 pub map: SourceMap,
43}
44
45#[derive(Debug, Clone, PartialEq, Eq)]
49#[non_exhaustive]
50pub enum SourceMapError {
51 GlobalVariableCount {
52 expected: usize,
53 found: usize,
54 },
55 PlayerVariableCount {
56 expected: usize,
57 found: usize,
58 },
59 SubroutineCount {
60 expected: usize,
61 found: usize,
62 },
63 RuleCount {
64 expected: usize,
65 found: usize,
66 },
67 ConditionCount {
68 rule: usize,
69 expected: usize,
70 found: usize,
71 },
72 ActionCount {
73 rule: usize,
74 expected: usize,
75 found: usize,
76 },
77 InvalidPosition,
79 DuplicateEntry,
81 EmptyEntry,
83 UnknownFile(usize),
85 InvalidSpan(Span),
87 UnsupportedFormat(String),
89 Malformed(String),
91}
92
93impl std::fmt::Display for SourceMapError {
94 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
95 let mismatch = |formatter: &mut std::fmt::Formatter<'_>, what, expected, found| {
96 write!(
97 formatter,
98 "source map shape mismatch: expected {expected} {what}, found {found}"
99 )
100 };
101 match self {
102 Self::GlobalVariableCount { expected, found } => {
103 mismatch(formatter, "global variables", expected, found)
104 }
105 Self::PlayerVariableCount { expected, found } => {
106 mismatch(formatter, "player variables", expected, found)
107 }
108 Self::SubroutineCount { expected, found } => {
109 mismatch(formatter, "subroutines", expected, found)
110 }
111 Self::RuleCount { expected, found } => mismatch(formatter, "rules", expected, found),
112 Self::ConditionCount {
113 rule,
114 expected,
115 found,
116 } => mismatch(
117 formatter,
118 &format!("conditions in rule {rule}"),
119 expected,
120 found,
121 ),
122 Self::ActionCount {
123 rule,
124 expected,
125 found,
126 } => mismatch(
127 formatter,
128 &format!("actions in rule {rule}"),
129 expected,
130 found,
131 ),
132 Self::InvalidPosition => {
133 write!(formatter, "source map entry is outside the program shape")
134 }
135 Self::DuplicateEntry => write!(formatter, "source map maps a node twice"),
136 Self::EmptyEntry => write!(formatter, "source map declaration entry has no span"),
137 Self::UnknownFile(file) => {
138 write!(formatter, "source map span references unknown file {file}")
139 }
140 Self::InvalidSpan(span) => write!(formatter, "invalid source map span {span:?}"),
141 Self::UnsupportedFormat(format) => {
142 write!(formatter, "unsupported mapped text format {format:?}")
143 }
144 Self::Malformed(message) => write!(formatter, "malformed mapped text: {message}"),
145 }
146 }
147}
148
149impl std::error::Error for SourceMapError {}
150
151impl SourceMap {
152 pub fn extract(program: &Program) -> Self {
157 let mut spans = Vec::new();
158 push_declarations(
159 program,
160 program.global_variables.len(),
161 |provenance| &provenance.global_variables,
162 |index, span, name_span| MappedNode::GlobalVariable {
163 index,
164 span,
165 name_span,
166 },
167 &mut spans,
168 );
169 push_declarations(
170 program,
171 program.player_variables.len(),
172 |provenance| &provenance.player_variables,
173 |index, span, name_span| MappedNode::PlayerVariable {
174 index,
175 span,
176 name_span,
177 },
178 &mut spans,
179 );
180 push_declarations(
181 program,
182 program.subroutines.len(),
183 |provenance| &provenance.subroutines,
184 |index, span, name_span| MappedNode::Subroutine {
185 index,
186 span,
187 name_span,
188 },
189 &mut spans,
190 );
191 for (rule, public) in program.rules.iter().enumerate() {
192 if let Some(span) = program.rule_span(rule) {
193 spans.push(MappedNode::Rule {
194 rule,
195 span: span.into(),
196 });
197 }
198 for condition in 0..public.conditions.len() {
199 if let Some(span) = program.condition_span(rule, condition) {
200 spans.push(MappedNode::Condition {
201 rule,
202 condition,
203 span: span.into(),
204 });
205 }
206 }
207 for (action, public_action) in public.actions.iter().enumerate() {
208 if let Some(span) = program.action_span(rule, action) {
209 spans.push(MappedNode::Action {
210 rule,
211 action,
212 span: span.into(),
213 });
214 }
215 for argument in 0..action_argument_count(public_action) {
216 if let Some(span) = program.action_argument_span(rule, action, argument) {
217 spans.push(MappedNode::ActionArgument {
218 rule,
219 action,
220 argument,
221 span: span.into(),
222 });
223 }
224 }
225 }
226 }
227 Self {
228 files: program.files.iter().map(|file| file.path.clone()).collect(),
229 shape: Shape::of(program),
230 spans,
231 }
232 }
233
234 pub fn files(&self) -> &[String] {
236 &self.files
237 }
238
239 pub fn apply(&self, program: &mut Program) -> Result<(), SourceMapError> {
246 self.shape.check(program)?;
247
248 let mut provenance = ProgramProvenance::default();
249 fit(
250 &mut provenance.global_variables,
251 self.shape.global_variables,
252 );
253 fit(
254 &mut provenance.player_variables,
255 self.shape.player_variables,
256 );
257 fit(&mut provenance.subroutines, self.shape.subroutines);
258 fit(&mut provenance.rules, self.shape.rules.len());
259 for (rule, shape) in provenance.rules.iter_mut().zip(&self.shape.rules) {
260 fit(&mut rule.conditions, shape.conditions);
261 fit(&mut rule.actions, shape.actions);
262 }
263
264 for node in &self.spans {
265 match node {
266 MappedNode::GlobalVariable {
267 index,
268 span,
269 name_span,
270 } => {
271 let declaration = provenance
272 .global_variables
273 .get_mut(*index)
274 .ok_or(SourceMapError::InvalidPosition)?;
275 let mapped = self.declaration(*span, *name_span)?;
276 if declaration.span.is_some() || declaration.name_span.is_some() {
277 return Err(SourceMapError::DuplicateEntry);
278 }
279 *declaration = mapped;
280 }
281 MappedNode::PlayerVariable {
282 index,
283 span,
284 name_span,
285 } => {
286 let declaration = provenance
287 .player_variables
288 .get_mut(*index)
289 .ok_or(SourceMapError::InvalidPosition)?;
290 let mapped = self.declaration(*span, *name_span)?;
291 if declaration.span.is_some() || declaration.name_span.is_some() {
292 return Err(SourceMapError::DuplicateEntry);
293 }
294 *declaration = mapped;
295 }
296 MappedNode::Subroutine {
297 index,
298 span,
299 name_span,
300 } => {
301 let declaration = provenance
302 .subroutines
303 .get_mut(*index)
304 .ok_or(SourceMapError::InvalidPosition)?;
305 let mapped = self.declaration(*span, *name_span)?;
306 if declaration.span.is_some() || declaration.name_span.is_some() {
307 return Err(SourceMapError::DuplicateEntry);
308 }
309 *declaration = mapped;
310 }
311 MappedNode::Rule { rule, span } => {
312 let span = self.span(*span)?;
313 let slot = &mut provenance
314 .rules
315 .get_mut(*rule)
316 .ok_or(SourceMapError::InvalidPosition)?
317 .span;
318 if slot.replace(span).is_some() {
319 return Err(SourceMapError::DuplicateEntry);
320 }
321 }
322 MappedNode::Condition {
323 rule,
324 condition,
325 span,
326 } => {
327 let span = self.span(*span)?;
328 let slot = provenance
329 .rules
330 .get_mut(*rule)
331 .and_then(|rule| rule.conditions.get_mut(*condition))
332 .ok_or(SourceMapError::InvalidPosition)?;
333 if slot.replace(span).is_some() {
334 return Err(SourceMapError::DuplicateEntry);
335 }
336 }
337 MappedNode::Action { rule, action, span } => {
338 let span = self.span(*span)?;
339 let slot = &mut provenance
340 .rules
341 .get_mut(*rule)
342 .and_then(|rule| rule.actions.get_mut(*action))
343 .ok_or(SourceMapError::InvalidPosition)?
344 .span;
345 if slot.replace(span).is_some() {
346 return Err(SourceMapError::DuplicateEntry);
347 }
348 }
349 MappedNode::ActionArgument {
350 rule,
351 action,
352 argument,
353 span,
354 } => {
355 let span = self.span(*span)?;
356 let count = program
357 .rules
358 .get(*rule)
359 .and_then(|rule| rule.actions.get(*action))
360 .map(action_argument_count)
361 .ok_or(SourceMapError::InvalidPosition)?;
362 if *argument >= count {
363 return Err(SourceMapError::InvalidPosition);
364 }
365 let arguments = &mut provenance
366 .rules
367 .get_mut(*rule)
368 .and_then(|rule| rule.actions.get_mut(*action))
369 .ok_or(SourceMapError::InvalidPosition)?
370 .arguments;
371 fit(arguments, count);
372 if arguments[*argument].replace(span).is_some() {
373 return Err(SourceMapError::DuplicateEntry);
374 }
375 }
376 }
377 }
378
379 program.files.clear();
380 for path in &self.files {
381 program.add_file(SourceFile::new(path.clone()));
382 }
383 program.provenance = Some(Box::new(provenance));
384 Ok(())
385 }
386
387 fn span(&self, wire: WireSpan) -> Result<Span, SourceMapError> {
388 if wire.file >= self.files.len() {
389 return Err(SourceMapError::UnknownFile(wire.file));
390 }
391 let span = Span::from(wire);
392 if !span.is_valid() {
393 return Err(SourceMapError::InvalidSpan(span));
394 }
395 Ok(span)
396 }
397
398 fn declaration(
399 &self,
400 span: Option<WireSpan>,
401 name_span: Option<WireSpan>,
402 ) -> Result<DeclarationProvenance, SourceMapError> {
403 if span.is_none() && name_span.is_none() {
404 return Err(SourceMapError::EmptyEntry);
405 }
406 Ok(DeclarationProvenance {
407 span: span.map(|span| self.span(span)).transpose()?,
408 name_span: name_span.map(|span| self.span(span)).transpose()?,
409 })
410 }
411}
412
413impl MappedText {
414 pub fn to_json(&self) -> String {
416 let artifact = Artifact {
417 format: MAPPED_TEXT_V1.to_string(),
418 text: self.text.clone(),
419 files: self
420 .map
421 .files
422 .iter()
423 .map(|path| WireFile { path: path.clone() })
424 .collect(),
425 shape: self.map.shape.clone(),
426 spans: self.map.spans.clone(),
427 };
428 serde_json::to_string(&artifact).expect("mapped text serializes to JSON")
429 }
430
431 pub fn from_json(json: &str) -> Result<Self, SourceMapError> {
436 let value: serde_json::Value = serde_json::from_str(json)
437 .map_err(|error| SourceMapError::Malformed(error.to_string()))?;
438 match value.get("format").and_then(serde_json::Value::as_str) {
439 Some(MAPPED_TEXT_V1) => {}
440 Some(other) => return Err(SourceMapError::UnsupportedFormat(other.to_string())),
441 None => return Err(SourceMapError::Malformed("missing format".to_string())),
442 }
443 let artifact: Artifact = serde_json::from_value(value)
444 .map_err(|error| SourceMapError::Malformed(error.to_string()))?;
445 Ok(Self {
446 text: artifact.text,
447 map: SourceMap {
448 files: artifact.files.into_iter().map(|file| file.path).collect(),
449 shape: artifact.shape,
450 spans: artifact.spans,
451 },
452 })
453 }
454}
455
456fn push_declarations(
457 program: &Program,
458 count: usize,
459 recorded: impl Fn(&ProgramProvenance) -> &[DeclarationProvenance],
460 node: impl Fn(usize, Option<WireSpan>, Option<WireSpan>) -> MappedNode,
461 output: &mut Vec<MappedNode>,
462) {
463 for index in 0..count {
464 let declaration = program.declaration_provenance(&recorded, count, index);
465 if declaration.span.is_some() || declaration.name_span.is_some() {
466 output.push(node(
467 index,
468 declaration.span.map(WireSpan::from),
469 declaration.name_span.map(WireSpan::from),
470 ));
471 }
472 }
473}
474
475#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
476struct Artifact {
477 format: String,
478 text: String,
479 files: Vec<WireFile>,
480 shape: Shape,
481 spans: Vec<MappedNode>,
482}
483
484#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
485struct WireFile {
486 path: String,
487}
488
489#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
490struct Shape {
491 global_variables: usize,
492 player_variables: usize,
493 subroutines: usize,
494 rules: Vec<RuleShape>,
495}
496
497#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
498struct RuleShape {
499 conditions: usize,
500 actions: usize,
501}
502
503impl Shape {
504 fn of(program: &Program) -> Self {
505 Self {
506 global_variables: program.global_variables.len(),
507 player_variables: program.player_variables.len(),
508 subroutines: program.subroutines.len(),
509 rules: program
510 .rules
511 .iter()
512 .map(|rule| RuleShape {
513 conditions: rule.conditions.len(),
514 actions: rule.actions.len(),
515 })
516 .collect(),
517 }
518 }
519
520 fn check(&self, program: &Program) -> Result<(), SourceMapError> {
521 let found = Self::of(program);
522 if self.global_variables != found.global_variables {
523 return Err(SourceMapError::GlobalVariableCount {
524 expected: self.global_variables,
525 found: found.global_variables,
526 });
527 }
528 if self.player_variables != found.player_variables {
529 return Err(SourceMapError::PlayerVariableCount {
530 expected: self.player_variables,
531 found: found.player_variables,
532 });
533 }
534 if self.subroutines != found.subroutines {
535 return Err(SourceMapError::SubroutineCount {
536 expected: self.subroutines,
537 found: found.subroutines,
538 });
539 }
540 if self.rules.len() != found.rules.len() {
541 return Err(SourceMapError::RuleCount {
542 expected: self.rules.len(),
543 found: found.rules.len(),
544 });
545 }
546 for (rule, (expected, found)) in self.rules.iter().zip(&found.rules).enumerate() {
547 if expected.conditions != found.conditions {
548 return Err(SourceMapError::ConditionCount {
549 rule,
550 expected: expected.conditions,
551 found: found.conditions,
552 });
553 }
554 if expected.actions != found.actions {
555 return Err(SourceMapError::ActionCount {
556 rule,
557 expected: expected.actions,
558 found: found.actions,
559 });
560 }
561 }
562 Ok(())
563 }
564}
565
566#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
567#[serde(tag = "node", rename_all = "snake_case")]
568enum MappedNode {
569 Rule {
570 rule: usize,
571 span: WireSpan,
572 },
573 Condition {
574 rule: usize,
575 condition: usize,
576 span: WireSpan,
577 },
578 Action {
579 rule: usize,
580 action: usize,
581 span: WireSpan,
582 },
583 ActionArgument {
584 rule: usize,
585 action: usize,
586 argument: usize,
587 span: WireSpan,
588 },
589 GlobalVariable {
590 index: usize,
591 #[serde(default, skip_serializing_if = "Option::is_none")]
592 span: Option<WireSpan>,
593 #[serde(default, skip_serializing_if = "Option::is_none")]
594 name_span: Option<WireSpan>,
595 },
596 PlayerVariable {
597 index: usize,
598 #[serde(default, skip_serializing_if = "Option::is_none")]
599 span: Option<WireSpan>,
600 #[serde(default, skip_serializing_if = "Option::is_none")]
601 name_span: Option<WireSpan>,
602 },
603 Subroutine {
604 index: usize,
605 #[serde(default, skip_serializing_if = "Option::is_none")]
606 span: Option<WireSpan>,
607 #[serde(default, skip_serializing_if = "Option::is_none")]
608 name_span: Option<WireSpan>,
609 },
610}
611
612#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
613struct WireSpan {
614 file: usize,
615 start: WirePosition,
616 end: WirePosition,
617}
618
619#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
620struct WirePosition {
621 line: u32,
622 column: u32,
623}
624
625impl From<Span> for WireSpan {
626 fn from(span: Span) -> Self {
627 Self {
628 file: span.file.index(),
629 start: span.start.into(),
630 end: span.end.into(),
631 }
632 }
633}
634
635impl From<WireSpan> for Span {
636 fn from(wire: WireSpan) -> Self {
637 Span::new(
638 FileId::from_index(wire.file),
639 wire.start.into(),
640 wire.end.into(),
641 )
642 }
643}
644
645impl From<Position> for WirePosition {
646 fn from(position: Position) -> Self {
647 Self {
648 line: position.line,
649 column: position.col,
650 }
651 }
652}
653
654impl From<WirePosition> for Position {
655 fn from(position: WirePosition) -> Self {
656 Position::new(position.line, position.column)
657 }
658}