diff options
| author | Laurenz <laurmaedje@gmail.com> | 2023-08-29 17:35:35 +0200 |
|---|---|---|
| committer | Laurenz <laurmaedje@gmail.com> | 2023-08-29 17:35:35 +0200 |
| commit | a71a2057f286677b5bf2e064fea05024aeca0dd2 (patch) | |
| tree | 716d85481aca232abdb6c2e01a0a545c003f4c6b /crates/typst-syntax/src | |
| parent | 7bdf1f57b09ea605045254013a8200373451baf0 (diff) | |
More type safety for spans
Diffstat (limited to 'crates/typst-syntax/src')
| -rw-r--r-- | crates/typst-syntax/src/file.rs | 44 | ||||
| -rw-r--r-- | crates/typst-syntax/src/node.rs | 6 | ||||
| -rw-r--r-- | crates/typst-syntax/src/reparser.rs | 4 | ||||
| -rw-r--r-- | crates/typst-syntax/src/source.rs | 24 | ||||
| -rw-r--r-- | crates/typst-syntax/src/span.rs | 69 |
5 files changed, 55 insertions, 92 deletions
diff --git a/crates/typst-syntax/src/file.rs b/crates/typst-syntax/src/file.rs index 6b3117cf..f5fa493b 100644 --- a/crates/typst-syntax/src/file.rs +++ b/crates/typst-syntax/src/file.rs @@ -16,9 +16,6 @@ use super::is_ident; static INTERNER: Lazy<RwLock<Interner>> = Lazy::new(|| RwLock::new(Interner { to_id: HashMap::new(), from_id: Vec::new() })); -/// The path that we use for detached file ids. -static DETACHED_PATH: Lazy<VirtualPath> = Lazy::new(|| VirtualPath::new("/unknown")); - /// A package-path interner. struct Interner { to_id: HashMap<Pair, FileId>, @@ -48,66 +45,41 @@ impl FileId { } let mut interner = INTERNER.write().unwrap(); - let len = interner.from_id.len(); - if len >= usize::from(u16::MAX) { - panic!("too many file specifications"); - } + let num = interner.from_id.len().try_into().expect("out of file ids"); // Create a new entry forever by leaking the pair. We can't leak more // than 2^16 pair (and typically will leak a lot less), so its not a // big deal. - let id = FileId(len as u16); + let id = FileId(num); let leaked = Box::leak(Box::new(pair)); interner.to_id.insert(leaked, id); interner.from_id.push(leaked); id } - /// Get an id that does not identify any real file. - pub const fn detached() -> Self { - Self(u16::MAX) - } - - /// Whether the id is the detached. - pub const fn is_detached(self) -> bool { - self.0 == Self::detached().0 - } - /// The package the file resides in, if any. pub fn package(&self) -> Option<&'static PackageSpec> { - if self.is_detached() { - None - } else { - self.pair().0.as_ref() - } + self.pair().0.as_ref() } /// The absolute and normalized path to the file _within_ the project or /// package. pub fn vpath(&self) -> &'static VirtualPath { - if self.is_detached() { - &DETACHED_PATH - } else { - &self.pair().1 - } + &self.pair().1 } /// Resolve a file location relative to this file. - pub fn join(self, path: &str) -> Result<Self, EcoString> { - if self.is_detached() { - Err("cannot access file system from here")?; - } - - Ok(Self::new(self.package().cloned(), self.vpath().join(path))) + pub fn join(self, path: &str) -> Self { + Self::new(self.package().cloned(), self.vpath().join(path)) } /// Construct from a raw number. - pub(crate) const fn from_u16(v: u16) -> Self { + pub(crate) const fn from_raw(v: u16) -> Self { Self(v) } /// Extract the raw underlying number. - pub(crate) const fn as_u16(self) -> u16 { + pub(crate) const fn into_raw(self) -> u16 { self.0 } diff --git a/crates/typst-syntax/src/node.rs b/crates/typst-syntax/src/node.rs index 8e4e056e..77c73f58 100644 --- a/crates/typst-syntax/src/node.rs +++ b/crates/typst-syntax/src/node.rs @@ -202,7 +202,7 @@ impl SyntaxNode { return Err(Unnumberable); } - let mid = Span::new(id, (within.start + within.end) / 2); + let mid = Span::new(id, (within.start + within.end) / 2).unwrap(); match &mut self.0 { Repr::Leaf(leaf) => leaf.span = mid, Repr::Inner(inner) => Arc::make_mut(inner).numberize(id, None, within)?, @@ -424,7 +424,7 @@ impl InnerNode { let mut start = within.start; if range.is_none() { let end = start + stride; - self.span = Span::new(id, (start + end) / 2); + self.span = Span::new(id, (start + end) / 2).unwrap(); self.upper = within.end; start = end; } @@ -448,6 +448,7 @@ impl InnerNode { mut range: Range<usize>, replacement: Vec<SyntaxNode>, ) -> NumberingResult { + let Some(id) = self.span.id() else { return Err(Unnumberable) }; let superseded = &self.children[range.clone()]; // Compute the new byte length. @@ -505,7 +506,6 @@ impl InnerNode { // Try to renumber. let within = start_number..end_number; - let id = self.span.id(); if self.numberize(id, Some(renumber), within).is_ok() { return Ok(()); } diff --git a/crates/typst-syntax/src/reparser.rs b/crates/typst-syntax/src/reparser.rs index a4186fa7..e03e1619 100644 --- a/crates/typst-syntax/src/reparser.rs +++ b/crates/typst-syntax/src/reparser.rs @@ -21,7 +21,9 @@ pub fn reparse( try_reparse(text, replaced, replacement_len, None, root, 0).unwrap_or_else(|| { let id = root.span().id(); *root = parse(text); - root.numberize(id, Span::FULL).unwrap(); + if let Some(id) = id { + root.numberize(id, Span::FULL).unwrap(); + } 0..text.len() }) } diff --git a/crates/typst-syntax/src/source.rs b/crates/typst-syntax/src/source.rs index 036499ab..56b27195 100644 --- a/crates/typst-syntax/src/source.rs +++ b/crates/typst-syntax/src/source.rs @@ -9,6 +9,7 @@ use comemo::Prehashed; use super::reparser::reparse; use super::{is_newline, parse, FileId, LinkedNode, Span, SyntaxNode}; +use crate::VirtualPath; /// A source file. /// @@ -44,19 +45,7 @@ impl Source { /// Create a source file without a real id and path, usually for testing. pub fn detached(text: impl Into<String>) -> Self { - Self::new(FileId::detached(), text.into()) - } - - /// Create a source file with the same synthetic span for all nodes. - pub fn synthesized(text: String, span: Span) -> Self { - let mut root = parse(&text); - root.synthesize(span); - Self(Arc::new(Repr { - id: FileId::detached(), - lines: lines(&text), - text: Prehashed::new(text), - root: Prehashed::new(root), - })) + Self::new(FileId::new(None, VirtualPath::new("main.typ")), text.into()) } /// The root node of the file's untyped syntax tree. @@ -151,12 +140,9 @@ impl Source { /// Get the byte range for the given span in this file. /// - /// Panics if the span does not point into this source file. - #[track_caller] - pub fn range(&self, span: Span) -> Range<usize> { - self.find(span) - .expect("span does not point into this source file") - .range() + /// Returns `None` if the span does not point into this source file. + pub fn range(&self, span: Span) -> Option<Range<usize>> { + Some(self.find(span)?.range()) } /// Return the index of the UTF-16 code unit at the byte index. diff --git a/crates/typst-syntax/src/span.rs b/crates/typst-syntax/src/span.rs index 8715e476..d715af1c 100644 --- a/crates/typst-syntax/src/span.rs +++ b/crates/typst-syntax/src/span.rs @@ -28,55 +28,58 @@ pub struct Span(NonZeroU64); impl Span { /// The full range of numbers available for span numbering. - pub const FULL: Range<u64> = 2..(1 << Self::BITS); + pub(super) const FULL: Range<u64> = 2..(1 << Self::BITS); + + /// The value reserved for the detached span. const DETACHED: u64 = 1; - // Data layout: - // | 16 bits source id | 48 bits number | + /// Data layout: + /// | 16 bits source id | 48 bits number | const BITS: usize = 48; /// Create a new span from a source id and a unique number. /// - /// Panics if the `number` is not contained in `FULL`. - #[track_caller] - pub const fn new(id: FileId, number: u64) -> Self { - assert!( - Self::FULL.start <= number && number < Self::FULL.end, - "span number outside valid range" - ); - - Self::pack(id, number) - } + /// Returns `None` if `number` is not contained in `FULL`. + pub(super) const fn new(id: FileId, number: u64) -> Option<Self> { + if number < Self::FULL.start || number >= Self::FULL.end { + return None; + } - /// A span that does not point into any source file. - pub const fn detached() -> Self { - Self::pack(FileId::detached(), Self::DETACHED) + let bits = ((id.into_raw() as u64) << Self::BITS) | number; + match NonZeroU64::new(bits) { + Some(v) => Some(Self(v)), + None => unreachable!(), + } } - /// Pack the components into a span. - #[track_caller] - const fn pack(id: FileId, number: u64) -> Span { - let bits = ((id.as_u16() as u64) << Self::BITS) | number; - match NonZeroU64::new(bits) { + /// Create a span that does not point into any source file. + pub const fn detached() -> Self { + match NonZeroU64::new(Self::DETACHED) { Some(v) => Self(v), - None => panic!("span encoding is zero"), + None => unreachable!(), } } + /// Whether the span is detached. + pub const fn is_detached(self) -> bool { + self.0.get() == Self::DETACHED + } + /// The id of the source file the span points into. - pub const fn id(self) -> FileId { - FileId::from_u16((self.0.get() >> Self::BITS) as u16) + /// + /// Returns `None` if the span is detached. + pub const fn id(self) -> Option<FileId> { + if self.is_detached() { + return None; + } + let bits = (self.0.get() >> Self::BITS) as u16; + Some(FileId::from_raw(bits)) } - /// The unique number of the span within its source file. + /// The unique number of the span within its [`Source`](super::Source). pub const fn number(self) -> u64 { self.0.get() & ((1 << Self::BITS) - 1) } - - /// Whether the span is detached. - pub const fn is_detached(self) -> bool { - self.id().is_detached() - } } /// A value with a span locating it in the source code. @@ -120,9 +123,9 @@ mod tests { #[test] fn test_span_encoding() { - let id = FileId::from_u16(5); - let span = Span::new(id, 10); - assert_eq!(span.id(), id); + let id = FileId::from_raw(5); + let span = Span::new(id, 10).unwrap(); + assert_eq!(span.id(), Some(id)); assert_eq!(span.number(), 10); } } |
