From a345c35f80f536f8dbbdd5f42da045f0257c9ec7 Mon Sep 17 00:00:00 2001 From: Jesse Braham Date: Thu, 28 Nov 2024 18:50:14 +0100 Subject: [PATCH] Create the `Span` struct, along with its constituent types --- onihime/src/lib.rs | 2 + onihime/src/span.rs | 141 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 143 insertions(+) create mode 100644 onihime/src/span.rs diff --git a/onihime/src/lib.rs b/onihime/src/lib.rs index e66f533..0cffc7e 100644 --- a/onihime/src/lib.rs +++ b/onihime/src/lib.rs @@ -1,3 +1,5 @@ //! Onihime programming language. #![deny(missing_debug_implementations, missing_docs, rust_2018_idioms)] + +mod span; diff --git a/onihime/src/span.rs b/onihime/src/span.rs new file mode 100644 index 0000000..7290588 --- /dev/null +++ b/onihime/src/span.rs @@ -0,0 +1,141 @@ +use std::{cmp::Ordering, iter, ops::Range, sync::Arc}; + +/// A location within some source text. +#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] +pub struct Location { + line: usize, + column: usize, +} + +impl Location { + /// Construct a new instance of `Location`. + #[must_use] + pub const fn new(line: usize, column: usize) -> Self { + Self { line, column } + } +} + +impl PartialOrd for Location { + fn partial_cmp(&self, other: &Self) -> Option { + match self.line.partial_cmp(&other.line) { + Some(Ordering::Equal) => self.column.partial_cmp(&other.column), + ord => ord, + } + } +} + +/// Some (optionally named) source text. +#[derive(Debug, Default, Clone, PartialEq, Eq)] +pub struct Source { + name: Option, + contents: String, + lines: Vec, +} + +impl Source { + /// Construct a new instance of `Source`. + #[must_use] + pub fn new(name: Option, contents: String) -> Self { + let lines = contents + .match_indices('\n') + .map(|(i, _)| i) + .chain(iter::once(contents.len())) + .collect(); + + Self { + name, + contents, + lines, + } + } + + /// Get the name of the source. + #[must_use] + pub fn name(&self) -> Option<&str> { + self.name.as_deref() + } + + /// Set the name of the source. + pub fn set_name(&mut self, name: String) { + self.name = Some(name); + } + + /// Get the [Location] of the specified byte in the source. + #[must_use] + pub fn location(&self, byte: usize) -> Location { + let line = self.lines.partition_point(|&x| x < byte); + let start = line.checked_sub(1).map_or(0, |n| self.lines[n] + 1); + let column = self.contents[start..byte].chars().count(); + + Location::new(line, column) + } + + /// Get the full contents of the source. + #[must_use] + pub fn contents(&self) -> &str { + &self.contents + } + + /// Get the specified line from the source. + #[must_use] + pub fn get_line(&self, line: usize) -> &str { + let end = self.lines[line]; + let start = line.checked_sub(1).map_or(0, |n| self.lines[n] + 1); + + &self.contents[start..end] + } +} + +/// A contiguous sequence of bytes within some source. +#[derive(Debug, Default, Clone)] +pub struct Span { + bytes: Range, + source: Arc, +} + +impl Span { + /// Construct a new instance of `Span`. + #[must_use] + pub fn new(bytes: Range, source: Arc) -> Self { + Self { bytes, source } + } + + /// Join two spans, creating a new span. + #[must_use] + pub fn join(self, other: &Self) -> Self { + debug_assert!(self.same_source(other)); + + Self::new(self.bytes.start..other.bytes.end, self.source) + } + + /// Extend one span to include another. + pub fn extend(&mut self, other: &Self) { + debug_assert!(self.same_source(other)); + + self.bytes.end = other.bytes.end; + } + + /// The start location of a span within some source. + #[must_use] + pub fn location(&self) -> Location { + self.source.location(self.bytes.start) + } + + /// The end location of a span within some source. + #[must_use] + pub fn end_location(&self) -> Location { + self.source.location(self.bytes.end) + } + + /// Do two spans share the same source? + #[must_use] + pub fn same_source(&self, other: &Self) -> bool { + Arc::ptr_eq(&self.source, &other.source) + } +} + +impl PartialEq for Span { + fn eq(&self, other: &Self) -> bool { + self.same_source(other) && self.bytes == other.bytes + } +}