use std::fmt::Display;
use crate::{span::Span, ContainerType, ScalarType};
use ltk_hash::{BinHash, Hash as _};
use ltk_meta::{path::PropertyPathError, PropertyKind};
use crate::{
ast::{
node::root::{FileKind, RootKind},
resolve::{literals::ValueEvalError, AnnotationResolveError},
},
cst,
escaping::InvalidEscape,
parse::TokenKind,
ItemShape, RitoType, Spanned,
};
#[derive(Debug, Clone, Copy)]
pub enum RitoTypeOrVirtual {
Unknown,
RitoType(RitoType),
Numeric,
StructOrEmbedded,
Token(TokenKind),
Tree(cst::Kind),
}
impl RitoTypeOrVirtual {
pub fn numeric() -> Self {
Self::Numeric
}
}
macro_rules! impl_from_rito_type {
($($ty:ty),*) => {$(
impl From<$ty> for RitoTypeOrVirtual {
fn from(value: $ty) -> Self {
Self::RitoType(value.into())
}
}
impl From<Option<$ty>> for RitoTypeOrVirtual {
fn from(value: Option<$ty>) -> Self {
value.map_or(Self::Unknown, Self::from)
}
}
)*};
}
impl_from_rito_type!(RitoType, ScalarType, ContainerType);
impl Display for RitoTypeOrVirtual {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Unknown => f.write_str("unknown type"),
Self::RitoType(rito_type) => Display::fmt(rito_type, f),
Self::Numeric => f.write_str("numeric type"),
Self::StructOrEmbedded => f.write_str("struct/embedded"),
Self::Token(kind) => Display::fmt(kind, f),
Self::Tree(kind) => Display::fmt(kind, f),
}
}
}
#[derive(Debug, Clone, Copy)]
pub enum ListLike {
Color,
Vec2,
Vec3,
Vec4,
Mat44,
}
impl ListLike {
pub fn from_kind(kind: PropertyKind) -> Option<Self> {
Some(match kind {
PropertyKind::Color => ListLike::Color,
PropertyKind::Vector2 => ListLike::Vec2,
PropertyKind::Vector3 => ListLike::Vec3,
PropertyKind::Vector4 => ListLike::Vec4,
PropertyKind::Matrix44 => ListLike::Mat44,
_ => return None,
})
}
pub fn needed_children(&self) -> u8 {
match self {
ListLike::Color => 4,
ListLike::Vec2 => 2,
ListLike::Vec3 => 3,
ListLike::Vec4 => 4,
ListLike::Mat44 => 16,
}
}
}
impl Display for ListLike {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ListLike::Color => ScalarType::Color,
ListLike::Vec2 => ScalarType::Vector2,
ListLike::Vec3 => ScalarType::Vector3,
ListLike::Vec4 => ScalarType::Vector4,
ListLike::Mat44 => ScalarType::Matrix44,
}
.fmt(f)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum PatchField {
Path,
Value,
}
impl PatchField {
pub const ALL: [Self; 2] = [Self::Path, Self::Value];
pub fn as_str(&self) -> &'static str {
match self {
Self::Path => "path",
Self::Value => "value",
}
}
pub fn named(name: BinHash) -> Option<Self> {
Self::ALL
.into_iter()
.find(|field| BinHash::hash_str(field.as_str()) == name)
}
}
impl Display for PatchField {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Debug, thiserror::Error, Clone, Copy)]
#[error("Type mismatch - expected {expected}, got {got}")]
pub struct TypeMismatch {
pub span: Span,
pub expected: RitoTypeOrVirtual,
pub expected_span: Option<Span>,
pub got: RitoTypeOrVirtual,
}
#[derive(Debug, Clone, Copy, thiserror::Error)]
#[non_exhaustive]
pub enum Diagnostic {
CustomSpan(&'static str, Span),
UnexpectedTree {
tree: cst::Kind,
expected: Option<cst::Kind>,
span: Span,
},
MissingTree(cst::Kind),
EmptyTree(cst::Kind),
MissingToken(TokenKind),
UnknownType(Span),
UnknownHashFunction(Span),
UnknownHashWidth(Span),
MissingType(Span),
InvalidHash(Span),
InvalidEscape(#[from] Spanned<InvalidEscape>),
AnnotationResolve(#[from] AnnotationResolveError),
TrailingAnnotations(Span),
TypeMismatch(#[from] TypeMismatch),
ValueEvalError(#[from] ValueEvalError),
UnexpectedContainerItem {
span: Span,
expected: RitoType,
expected_span: Option<Span>,
},
MissingClassName {
span: Span,
expected: RitoType,
},
UnexpectedItem {
span: Span,
parent: RitoType,
expected: ItemShape,
},
QuotedPropertyName {
span: Span,
parent: RitoType,
},
ResolveLiteral,
NotEnoughItems {
span: Span,
got: u8,
expected: ListLike,
},
TooManyItems {
span: Span,
extra: u8,
expected: ListLike,
},
RootNonEntry,
UnknownRoot {
span: Span,
},
MissingRootEntry {
root_kind: RootKind,
},
MissingEntryValue {
key_span: Span,
expected: Option<Spanned<RitoType>>,
},
MissingEntryType {
key_span: Span,
},
InvalidRootEntryType {
root_kind: RootKind,
key_span: Span,
type_span: Span,
got: RitoTypeOrVirtual,
expected: RitoType,
},
PatchOnlyRoot {
span: Span,
root_kind: RootKind,
},
PatchLinked {
span: Span,
},
UnsupportedPatchVersion {
span: Span,
version: u32,
},
UnexpectedPatchClass {
span: Span,
},
MissingPatchField {
span: Span,
field: PatchField,
},
DuplicatePatchField {
span: Span,
field: PatchField,
},
UnexpectedPatchField {
span: Span,
},
InvalidPropertyPath {
span: Span,
error: PropertyPathError,
},
UnexpectedFileKind {
span: Span,
expected: FileKind,
found: FileKind,
},
ShadowedEntry {
shadowee: Span,
shadower: Span,
},
ShadowedRoot {
shadowee: usize,
shadower: usize,
},
SubtypeCountMismatch {
span: Span,
got: u8,
expected: u8,
},
HashArgCountMismatch {
span: Span,
got: u8,
},
UnexpectedSubtypes {
span: Span,
base_type: Span,
},
InvalidNesting {
span: Span,
kind: RitoType,
},
InvalidMapKey {
span: Span,
kind: RitoType,
},
}
impl Display for Diagnostic {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
use Diagnostic::*;
match self {
CustomSpan(msg, _) => f.write_str(msg),
InvalidEscape { .. } => write!(f, "Invalid escape character"),
UnexpectedTree {
tree,
expected: Some(expected),
..
} => write!(f, "Unexpected {tree}, expected {expected}"),
UnexpectedTree {
tree,
expected: None,
..
} => write!(f, "Unexpected {tree}"),
MissingTree(kind) => write!(f, "Missing {kind}"),
EmptyTree(kind) => write!(f, "Empty {kind}"),
MissingToken(kind) => write!(f, "Missing {kind}"),
AnnotationResolve(err) => err.fmt(f),
TrailingAnnotations(_) => {
write!(f, "Trailing annotation(s) don't apply to anything!")
}
ValueEvalError(err) => err.fmt(f),
InvalidHash(_) => f.write_str("Invalid hash"),
UnknownType(_) => f.write_str("Unknown type"),
UnknownHashFunction(_) => {
f.write_str("Unknown hash function - expected fnv1a, xxh64 or xxh3")
}
UnknownHashWidth(_) => f.write_str("Unknown hash width - expected u32 or u64"),
MissingType(_) => {
f.write_str("Missing type - entries are written 'name: type = value'")
}
TypeMismatch(e) => e.fmt(f),
UnexpectedContainerItem { expected, .. } => write!(
f,
"{expected} does not accept container items / blocks - remove the curly \
braces around the value"
),
MissingClassName { expected, .. } => write!(
f,
"Missing class name - {expected} values are written 'ClassName {{ .. }}'"
),
UnexpectedItem {
parent, expected, ..
} => write!(f, "{parent} takes {expected}"),
QuotedPropertyName { parent, .. } => write!(
f,
"Quoted property name - {parent} bodies take 'name: type = value', with the \
name unquoted or a '0x..' hash"
),
ResolveLiteral => f.write_str("Could not resolve literal"),
NotEnoughItems { got, expected, .. } => write!(
f,
"{expected} needs {} items - got {got}",
expected.needed_children()
),
TooManyItems {
extra, expected, ..
} => write!(
f,
"{expected} needs {} items - got {extra} too many",
expected.needed_children()
),
RootNonEntry => f.write_str("Top-level bin entries are written 'name: type = value'"),
UnknownRoot { .. } => f.write_str("Unknown root entry"),
MissingRootEntry { root_kind } => write!(f, "Missing root entry '{root_kind}'"),
InvalidRootEntryType {
root_kind,
got,
expected,
..
} => write!(f, "Root entry '{root_kind}' must be {expected}, got {got}"),
PatchOnlyRoot { root_kind, .. } => {
write!(f, "Root entry '{root_kind}' only belongs in a PTCH file")
}
PatchLinked { .. } => {
f.write_str("A PTCH file cannot link other bins - 'linked' must be empty")
}
UnsupportedPatchVersion { version, .. } => {
write!(
f,
"Unsupported PTCH version {version} - the only version is 3"
)
}
UnexpectedPatchClass { .. } => f.write_str(
"Patch records are written 'patch { path: string = .., value: type = .. }'",
),
MissingPatchField { field, .. } => write!(f, "Patch record is missing '{field}'"),
DuplicatePatchField { field, .. } => {
write!(f, "Patch record already has a '{field}'")
}
UnexpectedPatchField { .. } => {
f.write_str("Patch records only have the fields 'path' and 'value'")
}
InvalidPropertyPath { error, .. } => write!(f, "Invalid property path - {error}"),
UnexpectedFileKind {
expected, found, ..
} => write!(f, "Expected a {expected} file, found a {found} file"),
MissingEntryValue {
key_span: _,
expected,
} => {
f.write_str("Entry is missing value")?;
if let Some(expected) = expected {
write!(f, " (expected {})", expected.value)?;
}
Ok(())
}
MissingEntryType { key_span: _ } => f.write_str("Entry is missing type expression"),
ShadowedEntry { .. } | ShadowedRoot { .. } => {
f.write_str("Entry shadows a previous entry with the same key")
}
SubtypeCountMismatch { got, expected, .. } => {
write!(f, "Expected {expected} type parameters, got {got}")
}
HashArgCountMismatch { got, .. } => write!(
f,
"Expected 1 or 2 type parameters - hash[width] or hash[width,algorithm], got {got}"
),
UnexpectedSubtypes { .. } => f.write_str("This type does not accept type parameters"),
InvalidNesting { kind, .. } => {
write!(f, "{kind} cannot be nested inside a container")
}
InvalidMapKey { kind, .. } => {
write!(f, "{kind} is not a valid map key type")
}
}
}
}
impl Diagnostic {
pub fn span(&self) -> Option<Span> {
use Diagnostic::*;
match self {
MissingTree(_)
| EmptyTree(_)
| MissingToken(_)
| RootNonEntry
| ShadowedRoot { .. }
| ResolveLiteral
| MissingRootEntry { .. } => None,
InvalidEscape(Spanned { span, value }) => Some(Span::new(
span.start + value.index as u32,
(span.start + value.index as u32 + 1).min(span.end),
)),
UnknownType(span)
| UnknownHashFunction(span)
| UnknownHashWidth(span)
| UnknownRoot { span }
| InvalidHash(span)
| UnexpectedTree { span, .. }
| CustomSpan(_, span)
| SubtypeCountMismatch { span, .. }
| HashArgCountMismatch { span, .. }
| UnexpectedSubtypes { span, .. }
| UnexpectedContainerItem { span, .. }
| MissingClassName { span, .. }
| UnexpectedItem { span, .. }
| QuotedPropertyName { span, .. }
| MissingType(span)
| MissingEntryType { key_span: span, .. }
| MissingEntryValue { key_span: span, .. }
| ShadowedEntry { shadower: span, .. }
| NotEnoughItems { span, .. }
| TooManyItems { span, .. }
| InvalidNesting { span, .. }
| InvalidMapKey { span, .. }
| PatchOnlyRoot { span, .. }
| PatchLinked { span }
| UnsupportedPatchVersion { span, .. }
| UnexpectedPatchClass { span }
| MissingPatchField { span, .. }
| DuplicatePatchField { span, .. }
| UnexpectedPatchField { span }
| InvalidPropertyPath { span, .. }
| UnexpectedFileKind { span, .. }
| InvalidRootEntryType { key_span: span, .. } => Some(*span),
AnnotationResolve(err) => Some(err.span()),
TrailingAnnotations(span) => Some(*span),
TypeMismatch(err) => Some(err.span),
ValueEvalError(err) => Some(err.span()),
}
}
pub fn default_span(self, span: Span) -> DiagnosticWithSpan {
DiagnosticWithSpan {
span: self.span().unwrap_or(span),
diagnostic: self,
}
}
pub fn unwrap(self) -> DiagnosticWithSpan {
DiagnosticWithSpan {
span: self.span().unwrap(),
diagnostic: self,
}
}
}
#[derive(Debug, Clone, Copy)]
#[cfg_attr(feature = "span_print", derive(crate::span::DebugSpans))]
pub struct DiagnosticWithSpan {
pub diagnostic: Diagnostic,
pub span: Span,
}
#[derive(Debug, Clone, Copy)]
pub struct MaybeSpanDiag {
pub diagnostic: Diagnostic,
pub span: Option<Span>,
}
impl MaybeSpanDiag {
pub fn fallback(self, span: Span) -> DiagnosticWithSpan {
DiagnosticWithSpan {
span: self.span.unwrap_or(span),
diagnostic: self.diagnostic,
}
}
}
impl From<Diagnostic> for MaybeSpanDiag {
fn from(diagnostic: Diagnostic) -> Self {
Self {
span: diagnostic.span(),
diagnostic,
}
}
}