use std::cell::RefCell;
use std::fmt;
use std::fmt::Write;
use std::iter;
use std::ops::Bound;
use std::ops::Index;
use std::ops::RangeBounds;
use std::ptr;
use std::slice;
use std::sync::RwLockReadGuard;
use camino::Utf8Path;
use crate::report::Fatal;
use crate::report::Report;
use crate::rt;
use crate::spec::Spec;
use crate::token;
use crate::Never;
mod context;
pub use context::Context;
#[derive(Copy, Clone)]
pub struct File<'ctx> {
path: &'ctx Utf8Path,
text: &'ctx str,
ctx: &'ctx Context,
idx: usize,
}
impl<'ctx> File<'ctx> {
pub fn path(self) -> &'ctx Utf8Path {
self.path
}
pub fn text<R>(self, range: R) -> &'ctx str
where
str: Index<R, Output = str>,
{
let text = &self.text.get(..self.text.len() - 1).unwrap();
&text[range]
}
#[allow(clippy::len_without_is_empty)]
pub fn len(self) -> usize {
self.text(..).len()
}
pub(crate) fn text_with_extra_space(self) -> &'ctx str {
self.text
}
pub fn context(self) -> &'ctx Context {
self.ctx
}
pub fn span(self, range: impl RangeBounds<usize>) -> Span {
Span::new(self, range)
}
pub(crate) fn idx(self) -> usize {
self.idx
}
pub fn lex(
self,
spec: &'ctx Spec,
report: &Report,
) -> Result<token::Stream<'ctx>, Fatal> {
rt::lex(self, report, spec)
}
}
impl PartialEq for File<'_> {
fn eq(&self, other: &Self) -> bool {
ptr::eq(self.ctx, other.ctx) && self.idx == other.idx
}
}
#[derive(Copy, Clone)]
pub struct Span {
file: u32,
start: u32,
end: u32,
}
#[derive(Copy, Clone)]
pub struct SpanId(u32);
impl fmt::Debug for SpanId {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
CTX_FOR_SPAN_DEBUG.with(|ctx| {
let ctx = ctx.borrow();
let Some(ctx) = &*ctx else {
return f.write_str("<elided>");
};
fmt::Debug::fmt(&Spanned::span(&self, ctx), f)
})
}
}
impl Span {
#[track_caller]
pub(crate) fn new<T: Copy + TryInto<u32> + fmt::Debug>(
file: File,
range: impl RangeBounds<T>,
) -> Span {
let start = match range.start_bound() {
Bound::Included(&x) => cast(x),
Bound::Excluded(&x) => cast(x).saturating_add(1),
Bound::Unbounded => 0,
};
let end = match range.end_bound() {
Bound::Included(&x) => cast(x).saturating_add(1),
Bound::Excluded(&x) => cast(x),
Bound::Unbounded => file.len() as u32,
};
assert!(start <= end, "out of order range: {start} > {end}",);
assert!(
end as usize <= file.text.len(),
"got out of bounds range: {end} > {}",
file.text.len(),
);
Span { file: file.idx() as u32, start, end }
}
pub fn file(self, ctx: &Context) -> File {
ctx.file(self.file as usize).unwrap()
}
pub fn start(self) -> usize {
self.start as usize
}
pub fn end(self) -> usize {
self.end as usize
}
pub fn is_empty(self) -> bool {
self.len() == 0
}
pub fn len(self) -> usize {
(self.end - self.start) as usize
}
pub fn comments(self, ctx: &Context) -> Comments {
Comments {
slice: ctx.lookup_comments(self.file(ctx), self.start()),
ctx,
}
}
pub fn subspan<T: Copy + TryInto<u32> + fmt::Debug>(
self,
range: impl RangeBounds<T>,
) -> Span {
let start = match range.start_bound() {
Bound::Included(&x) => cast(x),
Bound::Excluded(&x) => cast(x).saturating_add(1),
Bound::Unbounded => 0,
};
let end = match range.end_bound() {
Bound::Included(&x) => cast(x).saturating_add(1),
Bound::Excluded(&x) => cast(x),
Bound::Unbounded => self.len() as u32,
};
assert!(start <= end, "out of order range: {start} > {end}");
assert!(
end <= (self.len() as u32),
"subspan ends past end of range: {end} > {}",
self.len()
);
Span {
file: self.file,
start: self.start + start,
end: self.start + end,
}
}
pub fn split_at(self, at: usize) -> (Span, Span) {
(self.subspan(..at), self.subspan(at..))
}
pub fn split_around(self, pre: usize, suf: usize) -> [Span; 3] {
let (pre, range) = self.split_at(pre);
let (range, suf) = range.split_at(range.len() - suf);
[pre, range, suf]
}
pub fn text(self, ctx: &Context) -> &str {
self.file(ctx).text(self.start as usize..self.end as usize)
}
pub fn union(ranges: impl IntoIterator<Item = Span>) -> Span {
let mut best = None;
for range in ranges {
let best = best.get_or_insert(range);
assert_eq!(
best.file, range.file,
"attempted to join spans of different files"
);
best.start = u32::min(best.start, range.start);
best.end = u32::max(best.end, range.end);
}
best.expect("attempted to join zero spans")
}
pub(crate) fn intern(self, ctx: &Context) -> SpanId {
ctx.new_span(self)
}
pub(crate) fn intern_nonempty(self, ctx: &Context) -> Option<SpanId> {
if self.is_empty() {
return None;
}
Some(self.intern(ctx))
}
pub(crate) fn append_comment_span(self, ctx: &Context, comment: SpanId) {
ctx.add_comment(self.file(ctx), self.start(), comment)
}
}
pub trait Spanned {
fn span(&self, ctx: &Context) -> Span;
fn file<'ctx>(&self, ctx: &'ctx Context) -> File<'ctx> {
self.span(ctx).file(ctx)
}
fn start(&self, ctx: &Context) -> usize {
self.span(ctx).start()
}
fn end(&self, ctx: &Context) -> usize {
self.span(ctx).end()
}
fn is_empty(&self, ctx: &Context) -> bool {
self.span(ctx).is_empty()
}
fn len(&self, ctx: &Context) -> usize {
self.span(ctx).len()
}
fn text<'ctx>(&self, ctx: &'ctx Context) -> &'ctx str {
self.span(ctx).text(ctx)
}
fn comments<'ctx>(&self, ctx: &'ctx Context) -> Comments<'ctx> {
self.span(ctx).comments(ctx)
}
}
impl Spanned for SpanId {
fn span(&self, ctx: &Context) -> Span {
ctx.lookup_range(*self)
}
}
impl Spanned for Span {
fn span(&self, _ctx: &Context) -> Span {
*self
}
}
impl<S: Spanned> Spanned for &S {
fn span(&self, ctx: &Context) -> Span {
S::span(self, ctx)
}
}
impl Spanned for Never {
fn span(&self, _ctx: &Context) -> Span {
self.from_nothing_anything()
}
}
thread_local! {
static CTX_FOR_SPAN_DEBUG: RefCell<Option<Context>> = RefCell::new(None);
}
impl fmt::Debug for Span {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
CTX_FOR_SPAN_DEBUG.with(|ctx| {
if let Some(ctx) = &*ctx.borrow() {
let text = self.text(ctx);
write!(f, "`")?;
for c in text.chars() {
if ('\x20'..'\x7e').contains(&c) {
f.write_char(c)?;
} else if c < '\x20' {
write!(f, "{}", c.escape_debug())?
} else {
write!(f, "<U+{:04X}>", c as u32)?;
}
}
write!(f, "` @ {}", self.file(ctx).path())?;
} else {
write!(f, "<#{}>", self.file)?;
}
write!(f, "[{}..{}]", Span::start(*self), Span::end(*self))
})
}
}
pub struct Comments<'ctx> {
slice: (RwLockReadGuard<'ctx, context::State>, *const [SpanId]),
ctx: &'ctx Context,
}
impl<'ctx> Comments<'ctx> {
pub fn as_strings(&self) -> impl Iterator<Item = &'_ str> {
unsafe { &*self.slice.1 }
.iter()
.map(|span| span.text(self.ctx))
}
}
impl<'a> IntoIterator for &'a Comments<'_> {
type Item = SpanId;
type IntoIter = iter::Copied<slice::Iter<'a, SpanId>>;
fn into_iter(self) -> Self::IntoIter {
unsafe { &*self.slice.1 }.iter().copied()
}
}
#[track_caller]
fn cast<T: Copy + TryInto<u32> + fmt::Debug>(value: T) -> u32 {
value
.try_into()
.unwrap_or_else(|_| bug!("range bound does not fit into u32: {:?}", value))
}