mod line_col;
mod line_index;
mod refer;
mod src_referrer;
pub use line_col::*;
pub use line_index::*;
pub use refer::*;
pub use src_referrer::*;
use crate::HashId;
use derive_more::Deref;
use miette::SourceSpan;
use serde::Serialize;
pub type Span = std::ops::Range<usize>;
#[derive(Debug, PartialEq, Deref, Clone)]
pub struct Spanned<T> {
pub span: Span,
#[deref]
pub value: T,
}
impl<T> Spanned<T> {
pub fn new(span: Span, value: T) -> Self {
Self { span, value }
}
}
impl<T: PartialEq> PartialEq<T> for Spanned<T> {
fn eq(&self, other: &T) -> bool {
self.value.eq(other)
}
}
#[derive(Clone, Copy, Default)]
#[repr(C)]
pub struct SrcRef {
pub start: usize,
pub end: usize,
pub at: LineCol,
pub source_hash: HashId,
}
pub trait SrcRefIndex {
fn span_to_src_ref(&self, span: Span) -> SrcRef;
fn spanned_to_refer<T>(&self, spanned: Spanned<T>) -> Refer<T> {
Refer {
value: spanned.value,
src_ref: self.span_to_src_ref(spanned.span),
}
}
}
impl SrcRef {
pub fn from_span(span: Span, src_ref_index: impl SrcRefIndex) -> Self {
src_ref_index.span_to_src_ref(span)
}
pub fn new(span: &Span, at: LineCol, source_hash: HashId) -> Self {
Self {
start: span.start,
end: span.end,
at,
source_hash,
}
}
pub fn none() -> Self {
Self::default()
}
pub fn as_miette_span(&self) -> Option<SourceSpan> {
if self.is_some() {
Some(SourceSpan::new(self.start.into(), self.len()))
} else {
None
}
}
pub fn is_none(&self) -> bool {
self.source_hash == 0
}
pub fn is_some(&self) -> bool {
!self.is_none()
}
pub fn is_overlapping(&self, other: &Self) -> bool {
self.is_some() && other.is_some() && (self.start < other.end) && (other.start < self.end)
}
pub fn with_line_offset(&self, line_offset: u32) -> Self {
let mut s = *self;
s.at.line += line_offset;
s
}
pub fn span(&self) -> Span {
self.start..self.end
}
pub fn len(&self) -> usize {
self.span().len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn source_hash(&self) -> u64 {
self.source_hash
}
pub fn source_slice<'a>(&self, src: &'a str) -> &'a str {
assert!(self.is_some());
&src[self.span().to_owned()]
}
pub fn merge(lhs: &impl SrcReferrer, rhs: &impl SrcReferrer) -> SrcRef {
let lhs = lhs.src_ref();
let rhs = rhs.src_ref();
match (lhs.is_some(), rhs.is_some()) {
(true, true) => {
if lhs.source_hash == rhs.source_hash {
let source_hash = lhs.source_hash;
if lhs.span() == rhs.span() {
lhs
} else if lhs.end > rhs.start || lhs.start > rhs.end {
log::warn!(
"ranges not in correct order: {lhs} vs {rhs} @ {source_hash}",
lhs = lhs.at,
rhs = rhs.at
);
SrcRef::none()
} else {
assert!(lhs.end <= rhs.end);
assert!(lhs.start <= rhs.start);
SrcRef {
start: lhs.start,
end: rhs.end,
at: lhs.at,
source_hash,
}
}
} else {
log::warn!("references are not in the same file");
SrcRef::none()
}
}
(true, false) => lhs,
(false, true) => rhs,
(false, false) => SrcRef::none(),
}
}
pub fn merge_all<S: SrcReferrer>(referrers: impl Iterator<Item = S>) -> SrcRef {
let mut result = SrcRef::none();
for referrer in referrers {
let src_ref = referrer.src_ref();
if src_ref.is_some() {
if result.is_some() {
if result.source_hash != src_ref.source_hash {
panic!("can only merge source references of the same file");
}
if src_ref.start < result.start {
result.start = src_ref.start;
result.at = src_ref.at;
}
result.end = std::cmp::max(src_ref.end, result.end);
} else {
result = src_ref;
}
}
}
result
}
pub fn at(&self) -> Option<LineCol> {
if self.is_some() { Some(self.at) } else { None }
}
pub fn line(&self) -> Option<u32> {
self.at().map(|at| at.line)
}
pub fn col(&self) -> Option<u32> {
self.at().map(|at| at.col)
}
}
impl From<SrcRef> for SourceSpan {
fn from(value: SrcRef) -> Self {
value
.as_miette_span()
.unwrap_or(SourceSpan::new(0.into(), 0))
}
}
impl std::fmt::Display for SrcRef {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match &self.is_some() {
true => write!(f, "{}", self.at),
false => write!(f, "<NO REF>"),
}
}
}
impl std::fmt::Debug for SrcRef {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match &self.is_some() {
true => write!(
f,
"{} ({}..{}) in {:#x}",
self.at, self.start, self.end, self.source_hash
),
false => write!(f, "<NO REF>"),
}
}
}
impl PartialEq for SrcRef {
fn eq(&self, _: &Self) -> bool {
true
}
}
impl PartialOrd for SrcRef {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Eq for SrcRef {}
impl Ord for SrcRef {
fn cmp(&self, _: &Self) -> std::cmp::Ordering {
std::cmp::Ordering::Equal
}
}
unsafe impl bytemuck::Zeroable for SrcRef {}
unsafe impl bytemuck::Pod for SrcRef {}
impl Serialize for SrcRef {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
if serializer.is_human_readable() {
let compact = match self.is_some() {
true => format!(
"{}:{} @ {:x} ({}..{})",
self.line().unwrap(),
self.col().unwrap(),
self.source_hash,
self.start,
self.end
),
false => String::from("None"),
};
serializer.serialize_str(&compact)
} else {
let bytes: &[u8] = bytemuck::bytes_of(self);
serializer.serialize_bytes(bytes)
}
}
}
#[test]
fn merge_all() {
assert_eq!(
SrcRef::merge_all(
[
SrcRef::new(&Span { start: 5, end: 8 }, LineCol { line: 1, col: 6 }, 123),
SrcRef::new(
&Span { start: 8, end: 10 },
LineCol { line: 2, col: 1 },
123
),
SrcRef::new(
&Span { start: 12, end: 16 },
LineCol { line: 3, col: 1 },
123
),
SrcRef::new(
&Span { start: 0, end: 10 },
LineCol { line: 1, col: 1 },
123
),
]
.iter(),
),
SrcRef::new(
&Span { start: 0, end: 16 },
LineCol { line: 1, col: 1 },
123
),
);
}
#[test]
fn test_src_ref() {
use microcad_core::hash::{ComputedHash, Hashed};
let input = Hashed::new("geo3d::Cube(size_x = 3.0, size_y = 3.0, size_z = 3.0);");
let cube = 7..11;
let size_y = 26..32;
let cube = SrcRef::new(&cube, LineCol { line: 1, col: 0 }, input.computed_hash());
let size_y = SrcRef::new(&size_y, LineCol { line: 1, col: 0 }, input.computed_hash());
assert_eq!(cube.source_slice(input.value()), "Cube");
assert_eq!(size_y.source_slice(input.value()), "size_y");
}