#![allow(clippy::inline_always)]
use std::{any, borrow::Cow, convert::Infallible, io, marker::PhantomData, str, str::FromStr};
use bevy::asset::{Asset, Handle, LoadContext};
use bevy::reflect::erased_serde::__private::serde::de::DeserializeSeed;
use bevy::reflect::serde::TypedReflectDeserializer;
use bevy::reflect::{FromReflect, Reflect, TypeRegistry};
use thiserror::Error;
use super::escape_literal;
use crate::parser;
fn tyname<T>() -> &'static str {
any::type_name::<T>()
}
#[allow(missing_docs)] #[derive(Debug, Error)]
pub enum HandleDslDeserError<T> {
#[error(
"Didn't provide a LoadContext when deserializing a 'Handle<{}>'. \
This is required to enable loading assets",
tyname::<T>(),
)]
NoLoadContext,
#[error("Failed to load 'Handle<{}>' from file system", tyname::<T>())]
FileIo(#[from] io::Error),
#[error("Loading handles is not supported with non-FileSystem IO. It will be available starting bevy 0.12")]
UnsupportedIo,
#[error("Couldn't load 'Handle<{}>'", tyname::<T>())]
BadLoad(anyhow::Error),
#[doc(hidden)]
#[error("==OPTIMIZEDOUT== This error never occurs")]
_Ignore(PhantomData<fn(T)>, Infallible),
}
#[allow(missing_docs)] #[derive(Debug, Error)]
pub enum ReflectDslDeserError {
#[error("Tried to deserialize a DSL argument using reflection, yet '{0}' is not registered.")]
NotRegistered(&'static str),
#[error("Ron couldn't deserialize the DSL argument of type '{1}': {0}")]
RonDeser(#[source] Box<ron::error::SpannedError>, &'static str),
#[error(
"The DSL argument of type '{0}' was parsed by bevy in RON, but the \
generated reflect proxy type couldn't be converted into '{0}'"
)]
BadReflect(&'static str),
}
impl ReflectDslDeserError {
fn ron_deser<T>(source: ron::error::SpannedError) -> Self {
Self::RonDeser(Box::new(source), tyname::<T>())
}
fn not_registered<T>() -> Self {
Self::NotRegistered(tyname::<T>())
}
fn bad_reflect<T>() -> Self {
Self::BadReflect(tyname::<T>())
}
pub(crate) fn maybe_offset(&self) -> Option<u32> {
match self {
Self::BadReflect(_) | Self::NotRegistered(_) => None,
Self::RonDeser(ron, _) => {
(ron.position.line <= 1).then(|| u32::try_from(ron.position.col).unwrap())
}
}
}
}
#[derive(Debug, Error)]
#[error("Expected {expected} arguments, got {got} arguments")]
pub struct ArgumentError {
pub expected: usize,
pub got: usize,
}
pub fn from_reflect<T: Reflect + FromReflect>(
registry: &TypeRegistry,
_: Option<&mut LoadContext>,
input: &str,
) -> Result<T, ReflectDslDeserError> {
use ron::de::Deserializer as Ronzer;
use ReflectDslDeserError as Error;
let id = any::TypeId::of::<T>();
let registration = registry.get(id).ok_or_else(Error::not_registered::<T>)?;
let mut ron_de = Ronzer::from_str(input).map_err(Error::ron_deser::<T>)?;
let de = TypedReflectDeserializer::new(registration, registry);
let deserialized = match de.deserialize(&mut ron_de) {
Ok(ok) => ok,
Err(err) => return Err(Error::ron_deser::<T>(ron_de.span_error(err))),
};
T::from_reflect(deserialized.as_ref()).ok_or_else(Error::bad_reflect::<T>)
}
#[inline(always)]
pub fn from_str<T: FromStr>(
_: &TypeRegistry,
_: Option<&mut LoadContext>,
input: &str,
) -> Result<T, T::Err>
where
T::Err: std::error::Error + Send + Sync,
{
input.parse()
}
#[inline(always)]
pub fn to_handle<T: Asset>(
reg: &TypeRegistry,
mut load_context: Option<&mut LoadContext>,
input: &str,
) -> Result<Handle<T>, HandleDslDeserError<T>> {
let input = match quoted(reg, load_context.as_deref_mut(), input) {
Ok(input) => input,
Err(_infallible) => unreachable!(),
};
let Some(ctx) = load_context else {
return Err(HandleDslDeserError::<T>::NoLoadContext);
};
Ok(ctx.load(String::from(input)))
}
#[inline(always)]
pub fn quoted<'a>(
_: &TypeRegistry,
_: Option<&mut LoadContext>,
input: &'a str,
) -> Result<Cow<'a, str>, Infallible> {
Ok(interpret_str(input))
}
fn interpret_str(mut input: &str) -> Cow<str> {
if input.len() > 2 && input.starts_with('"') && input.ends_with('"') {
input = &input[1..input.len() - 1];
}
unsafe {
match escape_literal(input.as_bytes()) {
Cow::Borrowed(bytes) => Cow::Borrowed(str::from_utf8_unchecked(bytes)),
Cow::Owned(bytes_vec) => Cow::Owned(String::from_utf8_unchecked(bytes_vec)),
}
}
}
enum ArgumentsInner<'i, 'a> {
Parser(&'a parser::Arguments<'i, 'a>),
Named(Cow<'i, [u8]>),
}
pub struct Arguments<'i, 'a>(ArgumentsInner<'i, 'a>);
impl<'i, 'a> Arguments<'i, 'a> {
pub(crate) fn for_name(name: &'i [u8]) -> Self {
let surrounded_by = |quote| name.starts_with(quote) && name.ends_with(quote);
let name = if name.len() >= 2 && (surrounded_by(b"\"") || surrounded_by(b"'")) {
escape_literal(&name[1..name.len() - 1])
} else {
Cow::Borrowed(name)
};
Self(ArgumentsInner::Named(name))
}
#[must_use]
pub const fn is_empty(&self) -> bool {
self.len() == 0
}
#[must_use]
pub const fn len(&self) -> usize {
match &self.0 {
ArgumentsInner::Parser(p) => p.len(),
ArgumentsInner::Named(_) => 1,
}
}
#[must_use]
pub fn get(&self, index: usize) -> Option<Cow<'_, [u8]>> {
match &self.0 {
ArgumentsInner::Parser(p) => p.get(index),
ArgumentsInner::Named(n) if index == 0 => Some(Cow::Borrowed(n.as_ref())),
ArgumentsInner::Named(_) => None,
}
}
#[must_use]
pub fn get_str(&self, index: usize) -> Option<Cow<str>> {
match &self.0 {
ArgumentsInner::Parser(p) => p.get(index).map(|p| match p {
Cow::Borrowed(p) => String::from_utf8_lossy(p),
Cow::Owned(p) => Cow::Owned(String::from_utf8(p).unwrap()),
}),
ArgumentsInner::Named(n) if index == 0 => Some(String::from_utf8_lossy(n)),
ArgumentsInner::Named(_) => None,
}
}
}
impl<'i, 'a> From<&'a parser::Arguments<'i, 'a>> for Arguments<'i, 'a> {
fn from(value: &'a parser::Arguments<'i, 'a>) -> Self {
Self(ArgumentsInner::Parser(value))
}
}