use std::{
fmt,
fmt::{Debug, Display},
intrinsics::unlikely,
};
use gazebo::prelude::*;
use once_cell::sync::Lazy;
use crate::{
codemap::{CodeMap, FileSpan, Span},
errors::Frame,
values::{error::ControlError, FrozenRef, Trace, Tracer, Value},
};
#[derive(Debug, Clone, Copy, Dupe)]
pub(crate) struct FrozenFileSpan {
file: FrozenRef<'static, CodeMap>,
span: Span,
}
impl FrozenFileSpan {
pub(crate) const fn new_unchecked(file: FrozenRef<'static, CodeMap>, span: Span) -> Self {
FrozenFileSpan { file, span }
}
pub(crate) fn new(file: FrozenRef<'static, CodeMap>, span: Span) -> Self {
file.source_span(span);
Self::new_unchecked(file, span)
}
pub(crate) fn file(&self) -> FrozenRef<'static, CodeMap> {
self.file
}
pub(crate) fn span(&self) -> Span {
self.span
}
}
impl Default for FrozenFileSpan {
fn default() -> Self {
static EMPTY_FILE: Lazy<CodeMap> = Lazy::new(CodeMap::default);
FrozenFileSpan::new(FrozenRef::new(&EMPTY_FILE), Span::default())
}
}
impl Display for FrozenFileSpan {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
Display::fmt(&self.to_file_span(), f)
}
}
impl FrozenFileSpan {
fn to_file_span(&self) -> FileSpan {
FileSpan {
file: (*self.file).dupe(),
span: self.span,
}
}
pub(crate) fn merge(&self, other: &FrozenFileSpan) -> FrozenFileSpan {
if self.file == other.file {
FrozenFileSpan {
file: self.file,
span: self.span.merge(other.span),
}
} else {
*self
}
}
}
#[derive(Clone, Copy, Dupe)]
struct CheapFrame<'v> {
function: Value<'v>,
span: Option<FrozenRef<'static, FrozenFileSpan>>,
}
impl CheapFrame<'_> {
fn location(&self) -> Option<FileSpan> {
self.span.map(|span| span.to_file_span())
}
fn to_frame(&self) -> Frame {
Frame {
name: self.function.name_for_call_stack(),
location: self.location(),
}
}
}
impl Debug for CheapFrame<'_> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let mut x = f.debug_struct("Frame");
x.field("function", &self.function);
x.field("span", &self.span);
x.finish()
}
}
#[derive(Debug)]
pub(crate) struct CheapCallStack<'v> {
count: usize,
stack: [CheapFrame<'v>; MAX_CALLSTACK_RECURSION],
}
impl<'v> Default for CheapCallStack<'v> {
fn default() -> Self {
Self {
count: 0,
stack: [CheapFrame {
function: Value::new_none(),
span: None,
}; MAX_CALLSTACK_RECURSION],
}
}
}
const MAX_CALLSTACK_RECURSION: usize = 40;
unsafe impl<'v> Trace<'v> for CheapCallStack<'v> {
fn trace(&mut self, tracer: &Tracer<'v>) {
let (used, unused) = self.stack.split_at_mut(self.count);
for x in used {
x.function.trace(tracer);
}
for x in unused {
x.function = Value::new_none();
x.span = None;
}
}
}
impl<'v> CheapCallStack<'v> {
pub(crate) fn push(
&mut self,
function: Value<'v>,
span: Option<FrozenRef<'static, FrozenFileSpan>>,
) -> anyhow::Result<()> {
if unlikely(self.count >= MAX_CALLSTACK_RECURSION) {
return Err(ControlError::TooManyRecursionLevel.into());
}
self.stack[self.count] = CheapFrame { function, span };
self.count += 1;
Ok(())
}
pub(crate) fn pop(&mut self) {
debug_assert!(self.count >= 1);
self.count -= 1;
}
pub(crate) fn top_location(&self) -> Option<FileSpan> {
if self.count == 0 {
None
} else {
self.stack[self.count - 1].location()
}
}
pub(crate) fn to_diagnostic_frames(&self) -> CallStack {
let frames = self.stack[1..self.count].map(CheapFrame::to_frame);
CallStack { frames }
}
pub(crate) fn to_function_values(&self) -> Vec<Value<'v>> {
self.stack[1..self.count].map(|x| x.function)
}
}
#[derive(Debug, Default, Clone, PartialEq, Eq, Hash)]
pub struct CallStack {
frames: Vec<Frame>,
}
impl CallStack {
pub fn is_empty(&self) -> bool {
self.frames.is_empty()
}
pub fn into_frames(self) -> Vec<Frame> {
self.frames
}
}
impl Display for CallStack {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if !self.frames.is_empty() {
writeln!(f, "Traceback (most recent call last):")?;
let mut prev = "<module>";
for x in &self.frames {
x.write_two_lines(" ", prev, f)?;
prev = &x.name;
}
}
Ok(())
}
}