//! Structures that show where code or semantic elements came from use std::fmt; use std::hash::Hash; use std::ops::{Add, AddAssign, Range}; use futures::future::join_all; 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; } #[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), Multi(Vec), } 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), Multi(v => join_all(v.iter().map(|l| Pos::from_api(l, 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()), Multi(v => v.iter().map(|pos| pos.to_api()).collect()), } { 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}"), Pos::Multi(posv) => { write!(f, "{}", posv[0])?; for pos in posv { write!(f, "+{}", pos)?; } Ok(()) }, } } } impl Add for Pos { type Output = Pos; fn add(self, rhs: Self) -> Self::Output { match (self, rhs) { (Pos::Multi(l), Pos::Multi(r)) => Pos::Multi(l.into_iter().chain(r).collect()), (Pos::None, any) => any, (any, Pos::None) => any, (Pos::Multi(v), single) => Pos::Multi(v.into_iter().chain([single]).collect()), (single, Pos::Multi(v)) => Pos::Multi([single].into_iter().chain(v).collect()), (l, r) => Pos::Multi(vec![l, r]), } } } impl AddAssign for Pos { fn add_assign(&mut self, rhs: Self) { let mut tmp = Pos::None; std::mem::swap(&mut tmp, self); *self = tmp + rhs; } } /// 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, } impl SrcRange { pub fn new(range: Range, 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) } } /// Path the source text was loaded from pub fn path(&self) -> Sym { self.path.clone() } /// Byte range pub fn range(&self) -> Range { 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) -> Range) -> 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 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, } 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, 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) }) }