Files
orchid/orchid-base/src/location.rs

173 lines
5.7 KiB
Rust

//! Structures that show where code or semantic elements came from
use std::fmt;
use std::hash::Hash;
use std::ops::Range;
use trait_set::trait_set;
use crate::error::ErrPos;
use crate::interner::{Interner, Tok};
use crate::name::Sym;
use crate::{api, match_mapping, sym};
trait_set! {
pub trait GetSrc = FnMut(&Sym) -> Tok<String>;
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Pos {
None,
SlotTarget,
/// Used in functions to denote the generated code that carries on the
/// location of the call. Not allowed in the const tree.
Inherit,
Gen(CodeGenInfo),
/// Range and file
SrcRange(SrcRange),
}
impl Pos {
pub fn pretty_print(&self, get_src: &mut impl GetSrc) -> String {
match self {
Self::Gen(g) => g.to_string(),
Self::SrcRange(sr) => sr.pretty_print(&get_src(&sr.path)),
// Can't pretty print partial and meta-location
other => format!("{other:?}"),
}
}
pub async fn from_api(api: &api::Location, i: &Interner) -> Self {
match_mapping!(api, api::Location => Pos {
None, Inherit, SlotTarget,
Gen(cgi => CodeGenInfo::from_api(cgi, i).await),
} {
api::Location::SourceRange(sr) => Self::SrcRange(SrcRange::from_api(sr, i).await)
})
}
pub fn to_api(&self) -> api::Location {
match_mapping!(self, Pos => api::Location {
None, Inherit, SlotTarget,
Gen(cgi.to_api()),
} {
Self::SrcRange(sr) => api::Location::SourceRange(sr.to_api()),
})
}
}
impl fmt::Display for Pos {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Pos::Inherit => f.write_str("Unresolved inherited position"),
Pos::SlotTarget => f.write_str("Unresolved slot target position"),
Pos::None => f.write_str("N/A"),
Pos::Gen(g) => write!(f, "{g}"),
Pos::SrcRange(sr) => write!(f, "{sr}"),
}
}
}
/// Exact source code location. Includes where the code was loaded from, what
/// the original source code was, and a byte range.
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct SrcRange {
pub(crate) path: Sym,
pub(crate) range: Range<u32>,
}
impl SrcRange {
pub fn new(range: Range<u32>, path: &Sym) -> Self {
Self { range: range.clone(), path: path.clone() }
}
/// Create a dud [SourceRange] for testing. Its value is unspecified and
/// volatile.
pub async fn mock(i: &Interner) -> Self { Self { range: 0..1, path: sym!(test; i).await } }
/// Path the source text was loaded from
pub fn path(&self) -> Sym { self.path.clone() }
/// Byte range
pub fn range(&self) -> Range<u32> { self.range.clone() }
/// 0-based index of first byte
pub fn start(&self) -> u32 { self.range.start }
/// 0-based index of last byte + 1
pub fn end(&self) -> u32 { self.range.end }
/// Syntactic location
pub fn pos(&self) -> Pos { Pos::SrcRange(self.clone()) }
/// Transform the numeric byte range
pub fn map_range(&self, map: impl FnOnce(Range<u32>) -> Range<u32>) -> Self {
Self { range: map(self.range()), path: self.path() }
}
pub fn pretty_print(&self, src: &str) -> String {
let (sl, sc) = pos2lc(src, self.range.start);
let (el, ec) = pos2lc(src, self.range.end);
match (el == sl, ec <= sc + 1) {
(true, true) => format!("{sl}:{sc}"),
(true, false) => format!("{sl}:{sc}..{ec}"),
(false, _) => format!("{sl}:{sc}..{el}:{ec}"),
}
}
pub fn zw(path: Sym, pos: u32) -> Self { Self { path, range: pos..pos } }
pub async fn from_api(api: &api::SourceRange, i: &Interner) -> Self {
Self { path: Sym::from_api(api.path, i).await, range: api.range.clone() }
}
pub fn to_api(&self) -> api::SourceRange {
api::SourceRange { path: self.path.to_api(), range: self.range.clone() }
}
pub fn to(&self, rhs: &Self) -> Self {
assert_eq!(self.path, rhs.path, "Range continues across files");
Self { path: self.path(), range: self.start().min(rhs.start())..self.end().max(rhs.end()) }
}
}
impl From<SrcRange> for ErrPos {
fn from(val: SrcRange) -> Self { val.pos().into() }
}
impl fmt::Display for SrcRange {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.range.len() {
0 => write!(f, "{}:{}", self.path(), self.range.start),
n => write!(f, "{}:{}+{}", self.path(), self.range.start, n),
}
}
}
/// Information about a code generator attached to the generated code
#[derive(Clone, PartialEq, Eq, Hash)]
pub struct CodeGenInfo {
/// formatted like a Rust namespace
pub generator: Sym,
/// Unformatted user message with relevant circumstances and parameters
pub details: Tok<String>,
}
impl CodeGenInfo {
/// A codegen marker with no user message and parameters
pub async fn new_short(generator: Sym, i: &Interner) -> Self {
Self { generator, details: i.i("").await }
}
/// A codegen marker with a user message or parameters
pub async fn new_details(generator: Sym, details: impl AsRef<str>, i: &Interner) -> Self {
Self { generator, details: i.i(details.as_ref()).await }
}
/// Syntactic location
pub fn pos(&self) -> Pos { Pos::Gen(self.clone()) }
pub async fn from_api(api: &api::CodeGenInfo, i: &Interner) -> Self {
Self {
generator: Sym::from_api(api.generator, i).await,
details: Tok::from_api(api.details, i).await,
}
}
pub fn to_api(&self) -> api::CodeGenInfo {
api::CodeGenInfo { generator: self.generator.to_api(), details: self.details.to_api() }
}
}
impl fmt::Debug for CodeGenInfo {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "CodeGenInfo({self})") }
}
impl fmt::Display for CodeGenInfo {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "generated by {}", self.generator)?;
if !self.details.is_empty() { write!(f, ", details: {}", self.details) } else { write!(f, ".") }
}
}
#[must_use]
fn pos2lc(s: &str, i: u32) -> (u32, u32) {
s.chars()
.take(i.try_into().unwrap())
.fold((1, 1), |(line, col), char| if char == '\n' { (line + 1, 1) } else { (line, col + 1) })
}