use std::{
cmp::Ordering,
fmt::{Debug, Display, Formatter},
hash::{Hash, Hasher},
ops::{Bound, Range, RangeFrom, RangeInclusive},
};
use compact_str::CompactString;
use lady_deirdre::{
arena::{Entry, Id, Identifiable},
lexis::{
Column,
Line,
Position,
PositionSpan,
SiteRef,
SiteRefSpan,
SiteSpan,
SourceCode,
ToSpan,
TokenRef,
},
syntax::PolyRef,
};
use crate::runtime::{Ident, PackageMeta, ScriptIdent};
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum Origin {
Rust(&'static RustOrigin),
Script(ScriptOrigin),
}
impl Default for Origin {
#[inline(always)]
fn default() -> Self {
Self::nil()
}
}
impl Debug for Origin {
#[inline(always)]
fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
match self {
Self::Rust(origin) => Debug::fmt(origin, formatter),
Self::Script(origin) => Debug::fmt(origin, formatter),
}
}
}
impl From<&'static RustOrigin> for Origin {
#[inline(always)]
fn from(value: &'static RustOrigin) -> Self {
Self::Rust(value)
}
}
impl From<ScriptOrigin> for Origin {
#[inline(always)]
fn from(value: ScriptOrigin) -> Self {
Self::Script(value)
}
}
impl From<SiteRefSpan> for Origin {
#[inline(always)]
fn from(value: SiteRefSpan) -> Self {
Self::Script(ScriptOrigin::from(value))
}
}
impl<'a> From<&'a SiteRefSpan> for Origin {
#[inline(always)]
fn from(value: &'a SiteRefSpan) -> Self {
Self::Script(ScriptOrigin::from(value))
}
}
impl From<SiteRef> for Origin {
#[inline(always)]
fn from(value: SiteRef) -> Self {
Self::Script(ScriptOrigin::from(value))
}
}
impl<'a> From<&'a SiteRef> for Origin {
#[inline(always)]
fn from(value: &'a SiteRef) -> Self {
Self::Script(ScriptOrigin::from(value))
}
}
impl From<TokenRef> for Origin {
#[inline(always)]
fn from(value: TokenRef) -> Self {
Self::Script(ScriptOrigin::from(value))
}
}
impl<'a> From<&'a TokenRef> for Origin {
fn from(value: &'a TokenRef) -> Self {
Self::Script(ScriptOrigin::from(value))
}
}
impl From<Range<TokenRef>> for Origin {
#[inline(always)]
fn from(value: Range<TokenRef>) -> Self {
Self::Script(ScriptOrigin::from(value))
}
}
impl<'a> From<Range<&'a TokenRef>> for Origin {
#[inline(always)]
fn from(value: Range<&'a TokenRef>) -> Self {
Self::Script(ScriptOrigin::from(value))
}
}
impl From<RangeInclusive<TokenRef>> for Origin {
#[inline(always)]
fn from(value: RangeInclusive<TokenRef>) -> Self {
Self::Script(ScriptOrigin::from(value))
}
}
impl<'a> From<RangeInclusive<&'a TokenRef>> for Origin {
#[inline(always)]
fn from(value: RangeInclusive<&'a TokenRef>) -> Self {
Self::Script(ScriptOrigin::from(value))
}
}
impl From<RangeFrom<TokenRef>> for Origin {
#[inline(always)]
fn from(value: RangeFrom<TokenRef>) -> Self {
Self::Script(ScriptOrigin::from(value))
}
}
impl<'a> From<RangeFrom<&'a TokenRef>> for Origin {
#[inline(always)]
fn from(value: RangeFrom<&'a TokenRef>) -> Self {
Self::Script(ScriptOrigin::from(value))
}
}
impl From<RangeFrom<SiteRef>> for Origin {
#[inline(always)]
fn from(value: RangeFrom<SiteRef>) -> Self {
Self::Script(ScriptOrigin::from(value))
}
}
impl<'a> From<RangeFrom<&'a SiteRef>> for Origin {
#[inline(always)]
fn from(value: RangeFrom<&'a SiteRef>) -> Self {
Self::Script(ScriptOrigin::from(value))
}
}
impl Origin {
#[inline(always)]
pub fn nil() -> Self {
Self::Rust(&RustOrigin::nil())
}
#[inline(always)]
pub fn is_nil(&self) -> bool {
match self {
Self::Rust(origin) => origin.is_nil(),
Self::Script(origin) => origin.is_nil(),
}
}
#[inline(always)]
pub fn package(&self) -> Option<&'static PackageMeta> {
match self {
Self::Rust(origin) => origin.package(),
Self::Script(origin) => origin.package(),
}
}
#[inline(always)]
pub(crate) fn into_ident(self, string: impl Into<CompactString>) -> Ident {
match self {
Self::Rust(..) => Ident::Script(ScriptIdent::from_string(TokenRef::nil(), string)),
Self::Script(origin) => {
Ident::Script(ScriptIdent::from_string(origin.into_token_ref(), string))
}
}
}
}
static NIL_RUST_ORIGIN: RustOrigin = RustOrigin {
package: None,
code: None,
};
#[derive(Clone, Copy, PartialOrd, Ord, Hash)]
pub struct RustOrigin {
pub package: Option<(&'static str, &'static str)>,
pub code: Option<RustCode>,
}
impl Default for RustOrigin {
#[inline(always)]
fn default() -> Self {
NIL_RUST_ORIGIN
}
}
impl PartialEq for RustOrigin {
#[inline]
fn eq(&self, other: &Self) -> bool {
match (&self.package, &other.package) {
(Some(this), Some(other)) => {
if this.ne(other) {
return false;
}
}
_ => return false,
}
if let (Some(this), Some(other)) = (&self.code, &other.code) {
if this.ne(other) {
return false;
}
}
true
}
}
impl Eq for RustOrigin {}
impl Debug for RustOrigin {
fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
if self.package.is_none() && self.code.is_none() {
return formatter.write_str("RustOrigin(invalid)");
}
let mut debug_struct = formatter.debug_struct("RustOrigin");
if let Some((name, version)) = &self.package {
debug_struct.field("package", &format_args!("{name}@{version}"));
}
if let Some(code) = &self.code {
debug_struct.field("code", &code);
}
debug_struct.finish()
}
}
impl Display for RustOrigin {
fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
if let Some(code) = &self.code {
return Display::fmt(code, formatter);
}
if let Some((name, _)) = self.package {
return formatter.write_str(name);
}
formatter.write_str("[?]")
}
}
impl RustOrigin {
#[inline(always)]
pub fn nil() -> &'static Self {
&NIL_RUST_ORIGIN
}
pub fn is_nil(&self) -> bool {
self == &NIL_RUST_ORIGIN
}
#[inline(always)]
pub fn package(&self) -> Option<&'static PackageMeta> {
if let Some((name, version)) = self.package {
return PackageMeta::of(name, &format!("={}", version));
}
None
}
#[inline(never)]
pub fn blame<T>(&self, message: &str) -> T {
if let Some(code) = self.code {
(code.blame_fn)(message);
}
match self.package {
Some((name, _)) => panic!("{}: {}", name, message),
None => panic!("{}", message),
}
}
}
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
pub struct ScriptOrigin {
id: Id,
start: Option<Entry>,
end: Bound<Entry>,
}
impl Default for ScriptOrigin {
#[inline(always)]
fn default() -> Self {
Self::nil()
}
}
impl PartialOrd for ScriptOrigin {
#[inline(always)]
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for ScriptOrigin {
#[inline(always)]
fn cmp(&self, other: &Self) -> Ordering {
match self.id.cmp(&other.id) {
Ordering::Equal => match self.start.cmp(&other.start) {
Ordering::Equal => match (&self.end, &other.end) {
(Bound::Included(this), Bound::Included(other)) => this.cmp(other),
(Bound::Included(..), Bound::Excluded(..)) => Ordering::Less,
(Bound::Included(..), Bound::Unbounded) => Ordering::Less,
(Bound::Excluded(..), Bound::Included(..)) => Ordering::Greater,
(Bound::Excluded(this), Bound::Excluded(other)) => this.cmp(other),
(Bound::Excluded(..), Bound::Unbounded) => Ordering::Less,
(Bound::Unbounded, Bound::Unbounded) => Ordering::Equal,
(Bound::Unbounded, _) => Ordering::Greater,
},
other => other,
},
other => other,
}
}
}
impl Identifiable for ScriptOrigin {
#[inline(always)]
fn id(&self) -> Id {
self.id
}
}
impl From<SiteRefSpan> for ScriptOrigin {
#[inline(always)]
fn from(value: SiteRefSpan) -> Self {
Self::from(&value)
}
}
impl<'a> From<&'a SiteRefSpan> for ScriptOrigin {
#[inline(always)]
fn from(value: &'a SiteRefSpan) -> Self {
let id = value.start.id();
if id.is_nil() || id != value.end.id() {
return Self::default();
}
Self {
id,
start: {
let bound = value.start.token_ref();
match bound.is_nil() {
true => None,
false => Some(bound.entry),
}
},
end: {
let bound = value.end.token_ref();
match bound.is_nil() {
true => Bound::Unbounded,
false => Bound::Excluded(bound.entry),
}
},
}
}
}
impl From<SiteRef> for ScriptOrigin {
#[inline(always)]
fn from(value: SiteRef) -> Self {
Self::from(&value)
}
}
impl<'a> From<&'a SiteRef> for ScriptOrigin {
#[inline(always)]
fn from(value: &'a SiteRef) -> Self {
let id = value.id();
if id.is_nil() {
return Self::default();
}
let bound = value.token_ref();
match bound.id.is_nil() {
true => Self {
id,
start: None,
end: Bound::Unbounded,
},
false => Self {
id,
start: Some(bound.entry),
end: Bound::Excluded(bound.entry),
},
}
}
}
impl From<TokenRef> for ScriptOrigin {
#[inline(always)]
fn from(value: TokenRef) -> Self {
Self::from(&value)
}
}
impl<'a> From<&'a TokenRef> for ScriptOrigin {
#[inline(always)]
fn from(value: &'a TokenRef) -> Self {
Self {
id: value.id,
start: Some(value.entry),
end: Bound::Included(value.entry),
}
}
}
impl From<Range<TokenRef>> for ScriptOrigin {
#[inline(always)]
fn from(value: Range<TokenRef>) -> Self {
Self::from(&value.start..&value.end)
}
}
impl<'a> From<Range<&'a TokenRef>> for ScriptOrigin {
#[inline(always)]
fn from(value: Range<&'a TokenRef>) -> Self {
let id = value.start.id;
if id.is_nil() || id != value.end.id {
return Self::default();
}
Self {
id,
start: Some(value.start.entry),
end: Bound::Excluded(value.end.entry),
}
}
}
impl From<RangeInclusive<TokenRef>> for ScriptOrigin {
#[inline(always)]
fn from(value: RangeInclusive<TokenRef>) -> Self {
Self::from(value.start()..=value.end())
}
}
impl<'a> From<RangeInclusive<&'a TokenRef>> for ScriptOrigin {
#[inline(always)]
fn from(value: RangeInclusive<&'a TokenRef>) -> Self {
let id = value.start().id;
if id.is_nil() || id != value.end().id {
return Self::default();
}
Self {
id,
start: Some(value.start().entry),
end: Bound::Included(value.end().entry),
}
}
}
impl From<RangeFrom<TokenRef>> for ScriptOrigin {
#[inline(always)]
fn from(value: RangeFrom<TokenRef>) -> Self {
Self::from(&value.start..)
}
}
impl<'a> From<RangeFrom<&'a TokenRef>> for ScriptOrigin {
#[inline(always)]
fn from(value: RangeFrom<&'a TokenRef>) -> Self {
let id = value.start.id();
if id.is_nil() {
return Self::default();
}
Self {
id: value.start.id,
start: Some(value.start.entry),
end: Bound::Unbounded,
}
}
}
impl From<RangeFrom<SiteRef>> for ScriptOrigin {
#[inline(always)]
fn from(value: RangeFrom<SiteRef>) -> Self {
Self::from(&value.start..)
}
}
impl<'a> From<RangeFrom<&'a SiteRef>> for ScriptOrigin {
#[inline(always)]
fn from(value: RangeFrom<&'a SiteRef>) -> Self {
let id = value.start.id();
if id.is_nil() {
return Self::default();
}
let bound = value.start.token_ref();
match bound.id.is_nil() {
true => Self {
id,
start: None,
end: Bound::Unbounded,
},
false => Self {
id,
start: Some(bound.entry),
end: Bound::Unbounded,
},
}
}
}
unsafe impl ToSpan for ScriptOrigin {
#[inline(always)]
fn to_site_span(&self, code: &impl SourceCode) -> Option<SiteSpan> {
if self.id != code.id() {
return None;
}
let length = code.length();
let start = match &self.start {
Some(entry) => code.get_site(entry)?.min(length),
None => length,
};
let end = match &self.end {
Bound::Included(entry) => {
let chunk_length = code.get_length(entry)?;
let bound = (code.get_site(entry)? + chunk_length).min(length);
if bound < start {
return None;
}
bound
}
Bound::Excluded(entry) => {
let bound = code.get_site(entry)?;
if bound < start {
return None;
}
bound
}
Bound::Unbounded => length,
};
Some(start..end)
}
#[inline(always)]
fn is_valid_span(&self, code: &impl SourceCode) -> bool {
self.to_site_span(code).is_some()
}
}
impl ScriptOrigin {
#[inline(always)]
pub const fn nil() -> Self {
Self {
id: Id::nil(),
start: None,
end: Bound::Unbounded,
}
}
#[inline(always)]
pub(crate) fn invalid(id: Id) -> Self {
Self {
id,
start: Some(Entry::nil()),
end: Bound::Unbounded,
}
}
#[inline(always)]
pub(crate) fn eoi(id: Id) -> Self {
Self {
id,
start: None,
end: Bound::Unbounded,
}
}
#[inline(always)]
pub fn package(&self) -> Option<&'static PackageMeta> {
PackageMeta::by_id(self.id)
}
#[inline(always)]
pub const fn is_nil(&self) -> bool {
if self.id.is_nil() {
return true;
}
if let Some(entry) = &self.start {
if entry.is_nil() {
return true;
}
}
match &self.end {
Bound::Included(entry) | Bound::Excluded(entry) => {
if entry.is_nil() {
return true;
}
}
_ => (),
}
false
}
#[inline(always)]
pub(crate) fn union(&mut self, other: &Self) {
self.end = other.end;
}
#[inline(always)]
pub(crate) fn unbound(&mut self) {
self.end = Bound::Unbounded;
}
fn into_token_ref(self) -> TokenRef {
let Some(entry) = self.start else {
return TokenRef::nil();
};
TokenRef { id: self.id, entry }
}
}
#[derive(Clone, Copy, PartialOrd, Ord)]
pub struct RustCode {
pub module: &'static str,
pub line: u32,
pub column: u32,
pub blame_fn: fn(&str),
}
impl Hash for RustCode {
#[inline]
fn hash<H: Hasher>(&self, state: &mut H) {
self.module.hash(state);
self.line.hash(state);
self.column.hash(state);
}
}
impl PartialEq for RustCode {
#[inline]
fn eq(&self, other: &Self) -> bool {
if self.module.ne(other.module) {
return false;
}
if self.line.ne(&other.line) {
return false;
}
if self.column.ne(&other.column) {
return false;
}
true
}
}
impl Eq for RustCode {}
impl Debug for RustCode {
#[inline(always)]
fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
formatter
.debug_struct("RustCode")
.field("module", &self.module)
.field("position", &format_args!("{}", self.position()))
.finish()
}
}
impl Display for RustCode {
#[inline(always)]
fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
formatter.write_fmt(format_args!("{} [{}]", self.module, self.span_string()))
}
}
impl RustCode {
#[inline(never)]
pub fn blame(&self, message: &str) {
(self.blame_fn)(message);
panic!("{}: {}", self, message);
}
#[inline(always)]
pub fn position(&self) -> Position {
Position::new(self.line as Line, self.column as Column)
}
#[inline(always)]
pub fn span(&self) -> PositionSpan {
let bound = self.position();
bound..bound
}
pub fn span_string(&self) -> String {
format!("{}:{}", self.line, self.column)
}
}