mod raw;
use std::{any::type_name, borrow::Cow, fmt};
pub use raw::*;
use self::into_value::{DoesNotUseEngine, EngineUse, UsesEngine};
use crate::{
ll::{
bytecode::{DispatchTable, Environment},
gc::Gc,
value::{self, Closure, Dict, List, RawValue, Struct, Trait},
},
Error, Object, UserData,
};
#[doc(hidden)]
#[derive(Debug)]
pub struct Hidden<T>(pub(crate) Gc<T>);
impl<T> Clone for Hidden<T> {
fn clone(&self) -> Self {
Self(self.0.clone())
}
}
#[derive(Clone)]
pub enum Value {
Nil,
False,
True,
Number(f64),
String(Gc<String>),
Function(Hidden<Closure>),
Struct(Hidden<Struct>),
Trait(Hidden<Trait>),
List(Hidden<List>),
Dict(Hidden<Dict>),
UserData(Gc<Box<dyn value::UserData>>),
}
impl Value {
pub fn new(from: impl IntoValue<EngineUse = DoesNotUseEngine>) -> Self {
from.into_value(&())
}
pub fn type_name(&self) -> &str {
match self {
Value::Nil => "Nil",
Value::False => "False",
Value::True => "True",
Value::Number(_) => "Number",
Value::String(_) => "String",
Value::Function(_) => "Function",
Value::Struct(s) => &unsafe { s.0.dtable() }.type_name,
Value::Trait(s) => &s.0.dtable().type_name,
Value::List(_) => "List",
Value::Dict(_) => "Dict",
Value::UserData(u) => &unsafe { u.dtable() }.type_name,
}
}
pub fn is_falsy(&self) -> bool {
matches!(self, Self::Nil | Self::False)
}
pub fn is_truthy(&self) -> bool {
!self.is_falsy()
}
}
impl fmt::Debug for Value {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Debug::fmt(&self.to_raw_unmanaged(), f)
}
}
impl fmt::Display for Value {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(&self.to_raw_unmanaged(), f)
}
}
#[allow(missing_debug_implementations)]
pub mod into_value {
use crate::ll::bytecode::Environment;
pub trait EngineUse {
#[doc(hidden)]
type Environment;
#[doc(hidden)]
fn change_type(env: &Environment) -> &Self::Environment;
}
pub enum UsesEngine {}
impl EngineUse for UsesEngine {
type Environment = Environment;
fn change_type(env: &Environment) -> &Self::Environment {
env
}
}
pub enum DoesNotUseEngine {}
impl EngineUse for DoesNotUseEngine {
type Environment = ();
fn change_type(_: &Environment) -> &Self::Environment {
&()
}
}
}
pub trait IntoValue {
type EngineUse: into_value::EngineUse;
#[doc(hidden)]
fn into_value(self, env: &<Self::EngineUse as EngineUse>::Environment) -> Value;
#[doc(hidden)]
fn into_value_with_environment(self, env: &Environment) -> Value
where
Self: Sized,
{
self.into_value(<Self::EngineUse as EngineUse>::change_type(env))
}
}
impl IntoValue for Value {
type EngineUse = DoesNotUseEngine;
fn into_value(self, _: &()) -> Value {
self
}
}
#[doc(hidden)]
impl IntoValue for RawValue {
type EngineUse = DoesNotUseEngine;
fn into_value(self, _: &()) -> Value {
Value::from_raw(self)
}
}
impl IntoValue for () {
type EngineUse = DoesNotUseEngine;
fn into_value(self, _: &()) -> Value {
Value::Nil
}
}
impl IntoValue for bool {
type EngineUse = DoesNotUseEngine;
fn into_value(self, _: &()) -> Value {
match self {
true => Value::True,
false => Value::False,
}
}
}
macro_rules! value_from_number {
($T:ty $(, $doc:literal)?) => {
$(#[doc = $doc])?
impl IntoValue for $T {
type EngineUse = DoesNotUseEngine;
fn into_value(self, _: &()) -> Value {
Value::Number(self as f64)
}
}
};
}
value_from_number!(i8);
value_from_number!(i16);
value_from_number!(i32);
value_from_number!(i64, "**NOTE:** This is a lossy conversion, as an `f64` cannot represent the entire range of an `i64`.");
value_from_number!(isize, "**NOTE:** This is a lossy conversion, as an `f64` cannot represent the entire range of an `isize`.");
value_from_number!(u8);
value_from_number!(u16);
value_from_number!(u32);
value_from_number!(u64, "**NOTE:** This is a lossy conversion, as an `f64` cannot represent the entire range of a `u64`.");
value_from_number!(usize, "**NOTE:** This is a lossy conversion, as an `f64` cannot represent the entire range of a `usize`.");
value_from_number!(f32);
value_from_number!(f64);
impl IntoValue for char {
type EngineUse = DoesNotUseEngine;
fn into_value(self, _: &()) -> Value {
Value::String(Gc::new(self.to_string()))
}
}
impl IntoValue for &str {
type EngineUse = DoesNotUseEngine;
fn into_value(self, _: &()) -> Value {
Value::String(Gc::new(self.to_string()))
}
}
impl IntoValue for String {
type EngineUse = DoesNotUseEngine;
fn into_value(self, _: &()) -> Value {
Value::String(Gc::new(self))
}
}
impl IntoValue for Gc<String> {
type EngineUse = DoesNotUseEngine;
fn into_value(self, _: &()) -> Value {
Value::String(self)
}
}
impl<T> IntoValue for Option<T>
where
T: IntoValue,
{
type EngineUse = T::EngineUse;
fn into_value(self, env: &<Self::EngineUse as EngineUse>::Environment) -> Value {
match self {
Some(value) => value.into_value(env),
None => Value::Nil,
}
}
}
#[doc(hidden)]
impl IntoValue for Vec<RawValue> {
type EngineUse = DoesNotUseEngine;
fn into_value(self, _: &()) -> Value {
Value::List(Hidden(Gc::new(List::new(self))))
}
}
#[doc(hidden)]
impl IntoValue for Dict {
type EngineUse = DoesNotUseEngine;
fn into_value(self, _: &()) -> Value {
Value::Dict(Hidden(Gc::new(self)))
}
}
impl<T> IntoValue for T
where
T: UserData,
{
type EngineUse = UsesEngine;
fn into_value(self, env: &Environment) -> Value {
let dtable = env.get_user_dtable::<T>().map(Gc::clone).unwrap_or_else(|| {
let ad_hoc_dtable = DispatchTable::new_for_instance(type_name::<T>());
Gc::new(ad_hoc_dtable)
});
let object = Object::new(Gc::as_raw(&dtable), self);
Value::UserData(Gc::new(Box::new(object)))
}
}
pub trait TryFromValue
where
Self: Sized,
{
fn try_from_value(value: &Value, env: &Environment) -> Result<Self, Error>;
}
fn type_mismatch(expected: impl Into<Cow<'static, str>>, got: &Value) -> Error {
Error::TypeMismatch { expected: expected.into(), got: got.type_name().to_string().into() }
}
impl TryFromValue for Value {
fn try_from_value(value: &Value, _: &Environment) -> Result<Self, Error> {
Ok(value.clone())
}
}
#[doc(hidden)]
impl TryFromValue for RawValue {
fn try_from_value(value: &Value, _: &Environment) -> Result<Self, Error> {
Ok(value.to_raw_unmanaged())
}
}
impl TryFromValue for () {
fn try_from_value(value: &Value, _: &Environment) -> Result<Self, Error> {
if let Value::Nil = value {
Ok(())
} else {
Err(type_mismatch("Nil", value))
}
}
}
impl TryFromValue for bool {
fn try_from_value(value: &Value, _: &Environment) -> Result<Self, Error> {
match value {
Value::True => Ok(true),
Value::False => Ok(false),
_ => Err(type_mismatch("Boolean", value)),
}
}
}
macro_rules! try_from_value_numeric {
($T:ty) => {
impl TryFromValue for $T {
fn try_from_value(value: &Value, _: &Environment) -> Result<Self, Error> {
if let Value::Number(number) = value {
Ok(*number as $T)
} else {
Err(type_mismatch("Number", value))
}
}
}
};
}
try_from_value_numeric!(u8);
try_from_value_numeric!(u16);
try_from_value_numeric!(u32);
try_from_value_numeric!(u64);
try_from_value_numeric!(usize);
try_from_value_numeric!(i8);
try_from_value_numeric!(i16);
try_from_value_numeric!(i32);
try_from_value_numeric!(i64);
try_from_value_numeric!(isize);
try_from_value_numeric!(f32);
try_from_value_numeric!(f64);
impl TryFromValue for Gc<String> {
fn try_from_value(value: &Value, _: &Environment) -> Result<Self, Error> {
if let Value::String(s) = value {
Ok(Gc::clone(s))
} else {
Err(type_mismatch("String", value))
}
}
}
impl TryFromValue for String {
fn try_from_value(value: &Value, env: &Environment) -> Result<Self, Error> {
<Gc<String>>::try_from_value(value, env).map(|s| s.to_string())
}
}
impl<T> TryFromValue for Option<T>
where
T: TryFromValue,
{
fn try_from_value(value: &Value, env: &Environment) -> Result<Self, Error> {
match value {
Value::Nil => Ok(None),
_ => Ok(Some(T::try_from_value(value, env).map_err(|error| {
if let Error::TypeMismatch { expected, got } = error {
Error::TypeMismatch { expected: format!("{expected} or Nil").into(), got }
} else {
unreachable!()
}
})?)),
}
}
}
impl<T> TryFromValue for Vec<T>
where
T: TryFromValue,
{
fn try_from_value(value: &Value, env: &Environment) -> Result<Self, Error> {
if let Value::List(l) = value {
let elements = unsafe { l.0.as_slice() };
let mut result = vec![];
for &element in elements {
result.push(T::try_from_value(&Value::from_raw(element), env)?);
}
Ok(result)
} else {
Err(type_mismatch("List", value))
}
}
}
impl<T> TryFromValue for T
where
T: UserData + Clone,
{
fn try_from_value(value: &Value, env: &Environment) -> Result<Self, Error> {
if let Value::UserData(u) = value {
let u: &dyn value::UserData = (**u).as_ref();
if let Some(object) = u.as_any().downcast_ref::<Object<T>>() {
let (object, _guard) = unsafe { object.unsafe_borrow()? };
return Ok(object.clone());
}
}
let type_name = if let Some(dtable) = env.get_user_dtable::<T>() {
dtable.type_name.to_string()
} else {
type_name::<T>().to_string()
};
Err(type_mismatch(type_name, value))
}
}