use std::{borrow::Cow, fmt::Display, num::IntErrorKind};
use ltk_hash::{BinHash, WadHash};
use ltk_meta::PropertyKind;
use crate::{
ast::{
diagnostics::{RitoTypeOrVirtual, TypeMismatch},
hash::{HashedLiteral, Originally},
Value,
},
escaping,
parse::{Token, TokenKind},
RitoType, RitobinName, Spanned, SpannedExt,
};
use crate::{span::Span, ScalarType};
use ValueEvalError as E;
impl Value {
pub(crate) fn eval_unknown_hash(text: &str, span: Span) -> Result<Self, ValueEvalError> {
let src = text[span].strip_prefix("0x").ok_or(E::InvalidHash(span))?;
Ok(match BinHash::from_str_radix(src, 16) {
Ok(hash) => Self::Hash(HashedLiteral::new(span, Originally::HexLit, hash.into())),
Err(_) => match WadHash::from_str_radix(src, 16) {
Ok(hash) => Self::WadChunkLink(HashedLiteral::new(span, Originally::HexLit, hash)),
Err(_) => return Err(E::InvalidHash(span)),
},
})
}
}
#[derive(Debug, thiserror::Error, Clone, Copy)]
pub struct ParseNumericError {
pub expected: ScalarType,
pub error: Option<std::num::IntErrorKind>,
pub span: Span,
}
impl Display for ParseNumericError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let reason = match self.error {
Some(IntErrorKind::Empty) => "cannot parse integer from empty literal",
Some(IntErrorKind::InvalidDigit) => "invalid digit found in literal",
Some(IntErrorKind::PosOverflow) => "number too large to fit in target type",
Some(IntErrorKind::NegOverflow) => "number too small to fit in target type",
Some(IntErrorKind::Zero) => "number would be zero",
_ => "invalid literal",
};
write!(
f,
"Could not parse {} - {reason}",
self.expected.kind().to_rito_name()
)
}
}
fn parse_int<T: std::str::FromStr<Err = std::num::ParseIntError>>(
txt: &str,
kind_hint: ScalarType,
span: Span,
wrap: impl FnOnce(T, Span) -> Value,
) -> Result<Value, ParseNumericError> {
txt.parse::<T>()
.map(|v| wrap(v, span))
.map_err(|e| ParseNumericError {
expected: kind_hint,
error: Some(*e.kind()),
span,
})
}
fn parse_f32(txt: &str, span: Span) -> Result<Value, ParseNumericError> {
Ok(Value::F32(
txt.parse::<f32>()
.map_err(|_| ParseNumericError {
expected: ScalarType::F32,
error: None,
span,
})?
.with_span(span),
))
}
#[derive(Debug, thiserror::Error, Clone, Copy)]
pub enum ValueEvalError {
#[error("Ambiguous numeric literal - it needs a type to be resolved against")]
AmbiguousNumeric(Span),
#[error("Invalid hash")]
InvalidHash(Span),
#[error("Unexpected token - {:?}", .0.kind)]
UnexpectedToken(Token),
#[error(transparent)]
ParseNumericError(#[from] ParseNumericError),
#[error(transparent)]
TypeMismatch(#[from] TypeMismatch),
#[error(transparent)]
InvalidEscape(#[from] Spanned<escaping::InvalidEscape>),
}
impl ValueEvalError {
pub fn span(&self) -> Span {
match self {
ValueEvalError::AmbiguousNumeric(span) | ValueEvalError::InvalidHash(span) => *span,
ValueEvalError::UnexpectedToken(token) => token.span,
ValueEvalError::ParseNumericError(err) => err.span,
ValueEvalError::TypeMismatch(err) => err.span,
ValueEvalError::InvalidEscape(err) => err.span,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ExpectedType {
Any,
Exact(RitoType),
}
impl Display for ExpectedType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Any => f.write_str("numeric"),
Self::Exact(r) => r.fmt(f),
}
}
}
impl<R: Into<RitoType>> From<R> for ExpectedType {
fn from(value: R) -> Self {
Self::Exact(value.into())
}
}
impl Value {
pub(crate) fn eval(
text: &str,
token: &Token,
numeric_hint: Option<ExpectedType>,
numeric_hint_span: Option<Span>,
) -> Result<Self, ValueEvalError> {
use PropertyKind as K;
use ScalarType as S;
Ok(match token {
Token {
kind: TokenKind::String,
span,
} => Self::String(Spanned::new(
*span,
escaping::unescape(&text[Span::new(span.start + 1, span.end - 1)])
.map_err(|e| e.with_span(*span))?,
)),
Token {
kind: TokenKind::Null,
span,
} => Self::None(*span),
Token {
kind: TokenKind::True,
span,
} => Self::bool(*span, true),
Token {
kind: TokenKind::False,
span,
} => Self::bool(*span, false),
Token {
kind: TokenKind::HexLit,
span,
} => Self::eval_unknown_hash(text, *span)?,
Token {
kind: TokenKind::Number,
span,
} => {
let txt = &text[span];
let txt = match txt.contains('_') {
true => Cow::Owned(txt.replace('_', "")),
false => Cow::Borrowed(txt),
};
use ExpectedType as H;
match numeric_hint {
Some(H::Exact(hint)) => {
let h = match hint.base() {
K::Optional => {
hint.subtype(0).ok_or(E::AmbiguousNumeric(*span))?.kind()
}
base => base,
};
match h {
K::U8 => parse_int::<u8>(&txt, S::U8, *span, |v, s| {
Self::U8(Spanned::new(s, v))
})?,
K::U16 => parse_int::<u16>(&txt, S::U16, *span, |v, s| {
Self::U16(Spanned::new(s, v))
})?,
K::U32 => parse_int::<u32>(&txt, S::U32, *span, |v, s| {
Self::U32(Spanned::new(s, v))
})?,
K::U64 => parse_int::<u64>(&txt, S::U64, *span, |v, s| {
Self::U64(Spanned::new(s, v))
})?,
K::I8 => parse_int::<i8>(&txt, S::I8, *span, |v, s| {
Self::I8(Spanned::new(s, v))
})?,
K::I16 => parse_int::<i16>(&txt, S::I16, *span, |v, s| {
Self::I16(Spanned::new(s, v))
})?,
K::I32 => parse_int::<i32>(&txt, S::I32, *span, |v, s| {
Self::I32(Spanned::new(s, v))
})?,
K::I64 => parse_int::<i64>(&txt, S::I64, *span, |v, s| {
Self::I64(Spanned::new(s, v))
})?,
K::F32 => Self::F32(Spanned::new(
*span,
txt.parse().map_err(|_| ParseNumericError {
expected: S::F32,
error: None,
span: *span,
})?,
)),
expected => {
return Err(TypeMismatch {
span: *span,
expected: RitoType::from_property_kind(expected).into(),
expected_span: numeric_hint_span,
got: RitoTypeOrVirtual::numeric(),
}
.into());
}
}
}
Some(H::Any) => {
(parse_int::<u64>(&txt, S::U64, *span, |v, s| {
Self::U64(Spanned::new(s, v))
})
.or_else(|_| {
parse_int::<i64>(&txt, S::I64, *span, |v, s| {
Self::I64(Spanned::new(s, v))
})
})
.or_else(|_| parse_f32(&txt, *span)))?
}
None => {
return Err(E::AmbiguousNumeric(*span));
}
}
}
token => return Err(E::UnexpectedToken(*token)),
})
}
}