Skip to main content

shaperail_codegen/
span.rs

1//! Path-indexed spans. Field paths use dot notation (`schema.email.type`)
2//! mirroring how diagnostics name fields in their `error` strings.
3//!
4//! ## Convention
5//!
6//! Every entry is the span of the **value** at the named field path. A
7//! `lookup("schema.email")` returns the position of the `email` field's
8//! value (the inline mapping or scalar), not the position of the `email:`
9//! key token. Diagnostics consumed by users are about value content, not
10//! key tokens, so the lookup contract is value-first.
11//!
12//! Mapping-key spans are not stored. If a future caller needs them, add a
13//! parallel `key_spans` field — do not overload the value paths.
14
15use crate::diagnostics::Span;
16use std::collections::HashMap;
17use std::path::PathBuf;
18
19/// Map from dotted field path to source span. Built by the saphyr parser;
20/// consumed by `diagnose_resource_with_spans` (Task 4) to attach positions
21/// to diagnostics.
22#[derive(Debug, Default, Clone)]
23pub struct SpanMap {
24    file: PathBuf,
25    inner: HashMap<String, (u32, u32, u32, u32)>,
26}
27
28impl SpanMap {
29    /// Create a new `SpanMap` associated with the given source file path.
30    pub fn new(file: PathBuf) -> Self {
31        Self {
32            file,
33            inner: HashMap::new(),
34        }
35    }
36
37    /// Insert a span for the given dotted path.
38    ///
39    /// Lines and columns are 1-indexed; `end_col` is exclusive.
40    pub fn insert(
41        &mut self,
42        path: impl Into<String>,
43        line: u32,
44        col: u32,
45        end_line: u32,
46        end_col: u32,
47    ) {
48        self.inner
49            .insert(path.into(), (line, col, end_line, end_col));
50    }
51
52    /// Look up the span for the given dotted path, returning a [`Span`] if found.
53    pub fn lookup(&self, path: &str) -> Option<Span> {
54        self.inner
55            .get(path)
56            .map(|&(line, col, end_line, end_col)| Span {
57                file: self.file.clone(),
58                line,
59                col,
60                end_line,
61                end_col,
62            })
63    }
64
65    /// Return `true` if no spans have been inserted.
66    pub fn is_empty(&self) -> bool {
67        self.inner.is_empty()
68    }
69
70    /// Return the number of spans in this map.
71    pub fn len(&self) -> usize {
72        self.inner.len()
73    }
74}