Add currying support.

This commit is contained in:
Stephen Chung
2020-07-22 23:12:09 +08:00
parent e103c8e66c
commit 6d551f1596
10 changed files with 168 additions and 38 deletions

View File

@@ -137,7 +137,7 @@ pub enum Union {
Array(Box<Array>),
#[cfg(not(feature = "no_object"))]
Map(Box<Map>),
FnPtr(FnPtr),
FnPtr(Box<FnPtr>),
Variant(Box<Box<dyn Variant>>),
}
@@ -274,7 +274,7 @@ impl fmt::Debug for Dynamic {
f.write_str("#")?;
fmt::Debug::fmt(value, f)
}
Union::FnPtr(value) => fmt::Display::fmt(value, f),
Union::FnPtr(value) => fmt::Debug::fmt(value, f),
#[cfg(not(feature = "no_std"))]
Union::Variant(value) if value.is::<Instant>() => write!(f, "<timestamp>"),
@@ -481,7 +481,7 @@ impl Dynamic {
}
if type_id == TypeId::of::<FnPtr>() {
return match self.0 {
Union::FnPtr(value) => unsafe_try_cast(value),
Union::FnPtr(value) => unsafe_cast_box::<_, T>(value).ok().map(|v| *v),
_ => None,
};
}
@@ -582,7 +582,7 @@ impl Dynamic {
}
if type_id == TypeId::of::<FnPtr>() {
return match &self.0 {
Union::FnPtr(value) => <dyn Any>::downcast_ref::<T>(value),
Union::FnPtr(value) => <dyn Any>::downcast_ref::<T>(value.as_ref()),
_ => None,
};
}
@@ -656,7 +656,7 @@ impl Dynamic {
}
if type_id == TypeId::of::<FnPtr>() {
return match &mut self.0 {
Union::FnPtr(value) => <dyn Any>::downcast_mut::<T>(value),
Union::FnPtr(value) => <dyn Any>::downcast_mut::<T>(value.as_mut()),
_ => None,
};
}
@@ -801,6 +801,11 @@ impl<K: Into<ImmutableString>, T: Variant + Clone> From<HashMap<K, T>> for Dynam
}
impl From<FnPtr> for Dynamic {
fn from(value: FnPtr) -> Self {
Box::new(value).into()
}
}
impl From<Box<FnPtr>> for Dynamic {
fn from(value: Box<FnPtr>) -> Self {
Self(Union::FnPtr(value))
}
}

View File

@@ -79,6 +79,7 @@ pub const KEYWORD_TYPE_OF: &str = "type_of";
pub const KEYWORD_EVAL: &str = "eval";
pub const KEYWORD_FN_PTR: &str = "Fn";
pub const KEYWORD_FN_PTR_CALL: &str = "call";
pub const KEYWORD_FN_PTR_CURRY: &str = "curry";
pub const KEYWORD_THIS: &str = "this";
pub const FN_TO_STRING: &str = "to_string";
pub const FN_GET: &str = "get$";
@@ -1042,12 +1043,17 @@ impl Engine {
let (result, updated) = if fn_name == KEYWORD_FN_PTR_CALL && obj.is::<FnPtr>() {
// FnPtr call
let fn_ptr = obj.downcast_ref::<FnPtr>().unwrap();
let mut curry: StaticVec<_> = fn_ptr.curry().iter().cloned().collect();
// Redirect function name
let fn_name = obj.as_str().unwrap();
let fn_name = fn_ptr.fn_name();
// Recalculate hash
let hash = calc_fn_hash(empty(), fn_name, idx.len(), empty());
// Arguments are passed as-is
let mut arg_values = idx.iter_mut().collect::<StaticVec<_>>();
let hash = calc_fn_hash(empty(), fn_name, curry.len() + idx.len(), empty());
// Arguments are passed as-is, adding the curried arguments
let mut arg_values = curry
.iter_mut()
.chain(idx.iter_mut())
.collect::<StaticVec<_>>();
let args = arg_values.as_mut();
// Map it to name(args) in function-call style
@@ -1056,16 +1062,15 @@ impl Engine {
)
} else if fn_name == KEYWORD_FN_PTR_CALL && idx.len() > 0 && idx[0].is::<FnPtr>() {
// FnPtr call on object
let fn_ptr = idx[0].downcast_ref::<FnPtr>().unwrap();
let mut curry: StaticVec<_> = fn_ptr.curry().iter().cloned().collect();
// Redirect function name
let fn_name = idx[0]
.downcast_ref::<FnPtr>()
.unwrap()
.get_fn_name()
.clone();
let fn_name = fn_ptr.get_fn_name().clone();
// Recalculate hash
let hash = calc_fn_hash(empty(), &fn_name, idx.len() - 1, empty());
// Replace the first argument with the object pointer
let hash = calc_fn_hash(empty(), &fn_name, curry.len() + idx.len() - 1, empty());
// Replace the first argument with the object pointer, adding the curried arguments
let mut arg_values = once(obj)
.chain(curry.iter_mut())
.chain(idx.iter_mut().skip(1))
.collect::<StaticVec<_>>();
let args = arg_values.as_mut();
@@ -1074,8 +1079,19 @@ impl Engine {
self.exec_fn_call(
state, lib, &fn_name, *native, hash, args, is_ref, true, *def_val, level,
)
} else if fn_name == KEYWORD_FN_PTR_CURRY && obj.is::<FnPtr>() {
// Curry call
let fn_ptr = obj.downcast_ref::<FnPtr>().unwrap();
Ok((
FnPtr::new_unchecked(
fn_ptr.get_fn_name().clone(),
fn_ptr.curry().iter().chain(idx.iter()).cloned().collect(),
)
.into(),
false,
))
} else {
let redirected: Option<ImmutableString>;
let redirected;
let mut hash = *hash;
// Check if it is a map method call in OOP style
@@ -1084,9 +1100,8 @@ impl Engine {
if let Some(val) = map.get(fn_name) {
if let Some(f) = val.downcast_ref::<FnPtr>() {
// Remap the function name
redirected = Some(f.get_fn_name().clone());
fn_name = redirected.as_ref().unwrap();
redirected = f.get_fn_name().clone();
fn_name = &redirected;
// Recalculate the hash based on the new function name
hash = calc_fn_hash(empty(), fn_name, idx.len(), empty());
}
@@ -1731,7 +1746,7 @@ impl Engine {
Expr::FloatConstant(x) => Ok(x.0.into()),
Expr::StringConstant(x) => Ok(x.0.to_string().into()),
Expr::CharConstant(x) => Ok(x.0.into()),
Expr::FnPointer(x) => Ok(FnPtr::new_unchecked(x.0.clone()).into()),
Expr::FnPointer(x) => Ok(FnPtr::new_unchecked(x.0.clone(), Default::default()).into()),
Expr::Variable(x) if (x.0).0 == KEYWORD_THIS => {
if let Some(val) = this_ptr {
Ok(val.clone())
@@ -1919,6 +1934,39 @@ impl Engine {
}
}
// Handle curry()
if name == KEYWORD_FN_PTR_CURRY && args_expr.len() > 1 {
let expr = args_expr.get(0);
let fn_ptr = self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?;
if !fn_ptr.is::<FnPtr>() {
return Err(Box::new(EvalAltResult::ErrorMismatchOutputType(
self.map_type_name(type_name::<FnPtr>()).into(),
self.map_type_name(fn_ptr.type_name()).into(),
expr.position(),
)));
}
let fn_ptr = fn_ptr.downcast_ref::<FnPtr>().unwrap();
let curry: StaticVec<_> = args_expr
.iter()
.skip(1)
.map(|expr| self.eval_expr(scope, mods, state, lib, this_ptr, expr, level))
.collect::<Result<_, _>>()?;
return Ok(FnPtr::new_unchecked(
fn_ptr.get_fn_name().clone(),
fn_ptr
.curry()
.iter()
.cloned()
.chain(curry.into_iter())
.collect(),
)
.into());
}
// Handle eval()
if name == KEYWORD_EVAL && args_expr.len() == 1 {
let hash_fn =
@@ -1948,6 +1996,7 @@ impl Engine {
let redirected;
let mut name = name.as_ref();
let mut args_expr = args_expr.as_ref();
let mut curry: StaticVec<_> = Default::default();
let mut hash = *hash;
if name == KEYWORD_FN_PTR_CALL
@@ -1955,20 +2004,22 @@ impl Engine {
&& !self.has_override(lib, 0, hash)
{
let expr = args_expr.get(0).unwrap();
let fn_ptr = self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?;
let fn_name = self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?;
if fn_ptr.is::<FnPtr>() {
if fn_name.is::<FnPtr>() {
let fn_ptr = fn_name.cast::<FnPtr>();
curry = fn_ptr.curry().iter().cloned().collect();
// Redirect function name
redirected = Some(fn_ptr.cast::<FnPtr>().take_fn_name());
name = redirected.as_ref().unwrap();
redirected = fn_ptr.take_fn_name();
name = &redirected;
// Skip the first argument
args_expr = &args_expr.as_ref()[1..];
// Recalculate hash
hash = calc_fn_hash(empty(), name, args_expr.len(), empty());
hash = calc_fn_hash(empty(), name, curry.len() + args_expr.len(), empty());
} else {
return Err(Box::new(EvalAltResult::ErrorMismatchOutputType(
self.map_type_name(type_name::<FnPtr>()).into(),
fn_ptr.type_name().into(),
fn_name.type_name().into(),
expr.position(),
)));
}
@@ -1979,7 +2030,7 @@ impl Engine {
let mut args: StaticVec<_>;
let mut is_ref = false;
if args_expr.is_empty() {
if args_expr.is_empty() && curry.is_empty() {
// No arguments
args = Default::default();
} else {
@@ -2002,7 +2053,10 @@ impl Engine {
self.inc_operations(state)
.map_err(|err| err.new_position(pos))?;
args = once(target).chain(arg_values.iter_mut()).collect();
args = once(target)
.chain(curry.iter_mut())
.chain(arg_values.iter_mut())
.collect();
is_ref = true;
}
@@ -2015,7 +2069,7 @@ impl Engine {
})
.collect::<Result<_, _>>()?;
args = arg_values.iter_mut().collect();
args = curry.iter_mut().chain(arg_values.iter_mut()).collect();
}
}
}

View File

@@ -50,13 +50,13 @@ pub fn shared_take<T: Clone>(value: Shared<T>) -> T {
pub type FnCallArgs<'a> = [&'a mut Dynamic];
/// A general function pointer.
#[derive(Debug, Clone, Eq, PartialEq, Hash, Default)]
pub struct FnPtr(ImmutableString);
#[derive(Debug, Clone, Default)]
pub struct FnPtr(ImmutableString, Vec<Dynamic>);
impl FnPtr {
/// Create a new function pointer.
pub(crate) fn new_unchecked<S: Into<ImmutableString>>(name: S) -> Self {
Self(name.into())
pub(crate) fn new_unchecked<S: Into<ImmutableString>>(name: S, curry: Vec<Dynamic>) -> Self {
Self(name.into(), curry)
}
/// Get the name of the function.
pub fn fn_name(&self) -> &str {
@@ -70,6 +70,10 @@ impl FnPtr {
pub(crate) fn take_fn_name(self) -> ImmutableString {
self.0
}
/// Get the curried data.
pub(crate) fn curry(&self) -> &[Dynamic] {
&self.1
}
}
impl fmt::Display for FnPtr {
@@ -83,7 +87,7 @@ impl TryFrom<ImmutableString> for FnPtr {
fn try_from(value: ImmutableString) -> Result<Self, Self::Error> {
if is_valid_identifier(value.chars()) {
Ok(Self(value))
Ok(Self(value, Default::default()))
} else {
Err(Box::new(EvalAltResult::ErrorFunctionNotFound(
value.into(),

View File

@@ -1,8 +1,8 @@
//! Main module defining the lexer and parser.
use crate::engine::{
Engine, KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_FN_PTR, KEYWORD_FN_PTR_CALL, KEYWORD_PRINT,
KEYWORD_THIS, KEYWORD_TYPE_OF,
Engine, KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_FN_PTR, KEYWORD_FN_PTR_CALL, KEYWORD_FN_PTR_CURRY,
KEYWORD_PRINT, KEYWORD_THIS, KEYWORD_TYPE_OF,
};
use crate::error::LexError;
@@ -404,7 +404,7 @@ impl Token {
Reserved(syntax.into())
}
KEYWORD_PRINT | KEYWORD_DEBUG | KEYWORD_TYPE_OF | KEYWORD_EVAL | KEYWORD_FN_PTR
| KEYWORD_FN_PTR_CALL | KEYWORD_THIS => Reserved(syntax.into()),
| KEYWORD_FN_PTR_CALL | KEYWORD_FN_PTR_CURRY | KEYWORD_THIS => Reserved(syntax.into()),
_ => return None,
})