Skip to content

Commit

Permalink
Add support for anonymous functions
Browse files Browse the repository at this point in the history
  • Loading branch information
danhper committed Aug 8, 2024
1 parent 9e8937e commit 35a73d5
Show file tree
Hide file tree
Showing 5 changed files with 121 additions and 2 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
* Add support for array concatenation
* Add `array.filter` function
* Add support for bitwise operators
* [EXPERIMENTAL] Add support for anonymous functions

### Bug fixes

Expand Down
22 changes: 22 additions & 0 deletions docs/src/differences_with_solidity.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,3 +59,25 @@ For example, the following is valid in Eclair:
>> tu16.max
65535
```

## Anonymous functions

Eclair supports anonymous functions, which are functions that are not bound to a name.
These functions are quite limited in features since they need to be written as a valid Solidity expression.
We adopted the following syntax:

```javascript
>> (x) >> x + 1
function(any x)
```

This is particularly helpful when using higher-order functions like `map` and `filter`:

```javascript
>> [1, 2, 3].map((x) >> x + 1)
[2, 3, 4]
>> [1, 2, 3].filter((x) >> (x % 2 == 0))
[2]
```

Note that because `>>` is parsed as the right shift operator, you need to wrap the anonymous function in parentheses if it contains an operator with a lower priority than `>>`, such as `==`.
83 changes: 83 additions & 0 deletions src/interpreter/functions/anonymous.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
use std::sync::Arc;

use crate::interpreter::{evaluate_expression, types::HashableIndexMap, Env, Type, Value};

use super::{Function, FunctionDef, FunctionParam};
use anyhow::{anyhow, bail, Result};
use futures::{future::BoxFuture, FutureExt};
use solang_parser::pt::Expression;

#[derive(Debug)]
pub struct AnonymousFunction {
params: Vec<FunctionParam>,
body: Expression,
}

impl AnonymousFunction {
pub fn new(params: Vec<FunctionParam>, body: Expression) -> Self {
Self { params, body }
}

pub fn parse_lhs(lhs: &Expression) -> Result<Vec<FunctionParam>> {
match lhs {
Expression::Parenthesis(_, expr) => {
if let Expression::Variable(id) = expr.as_ref() {
Ok(vec![FunctionParam::new(&id.name, Type::Any)])
} else {
bail!("expected variable, found {:?}", expr)
}
}
Expression::List(_, params) => params
.iter()
.map(|(_, param)| {
let param = param.clone().ok_or(anyhow!("no param given"))?;
match (param.name, param.ty) {
(Some(id), Expression::Type(_, t)) => {
Ok(FunctionParam::new(&id.name, t.try_into()?))
}
(None, Expression::Variable(id)) => {
Ok(FunctionParam::new(&id.name, Type::Any))
}
_ => bail!("invalid function parameter"),
}
})
.collect::<Result<Vec<_>>>(),
_ => bail!("Invalid function parameter"),
}
}
}

impl From<AnonymousFunction> for Value {
fn from(f: AnonymousFunction) -> Self {
Value::Func(Box::new(Function::new(Arc::new(f), None)))
}
}

impl FunctionDef for AnonymousFunction {
fn name(&self) -> String {
"function".to_string()
}

fn get_valid_args(&self, _receiver: &Option<Value>) -> Vec<Vec<FunctionParam>> {
vec![self.params.clone()]
}

fn is_property(&self) -> bool {
false
}

fn execute<'a>(
&'a self,
env: &'a mut Env,
values: &'a [Value],
_options: &'a HashableIndexMap<String, Value>,
) -> BoxFuture<'a, Result<Value>> {
async move {
for (param, arg) in self.params.iter().zip(values.iter()) {
env.set_var(param.get_name(), arg.clone());
}
evaluate_expression(env, Box::new(self.body.clone())).await
}
.boxed()
}
}
2 changes: 2 additions & 0 deletions src/interpreter/functions/mod.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
mod anonymous;
mod contract;
mod definition;
mod function;
mod param;
mod user_defined;

pub use anonymous::AnonymousFunction;
pub use contract::ContractFunction;
pub use definition::{
AsyncMethod, AsyncProperty, FunctionDef, SyncFunction, SyncMethod, SyncProperty,
Expand Down
15 changes: 13 additions & 2 deletions src/interpreter/interpreter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ use crate::loaders::types::Project;

use super::assignment::Lhs;
use super::builtins;
use super::functions::{FunctionDef, UserDefinedFunction};
use super::functions::{AnonymousFunction, FunctionDef, UserDefinedFunction};
use super::parsing::ParsedCode;
use super::types::{HashableIndexMap, Type};
use super::utils::parse_rational_literal;
Expand Down Expand Up @@ -493,7 +493,18 @@ pub fn evaluate_expression(env: &mut Env, expr: Box<Expression>) -> BoxFuture<'_
Expression::BitwiseOr(_, lhs, rhs) => _eval_binop(env, lhs, rhs, Value::bitor).await,
Expression::BitwiseXor(_, lhs, rhs) => _eval_binop(env, lhs, rhs, Value::bitxor).await,
Expression::ShiftLeft(_, lhs, rhs) => _eval_binop(env, lhs, rhs, Value::shl).await,
Expression::ShiftRight(_, lhs, rhs) => _eval_binop(env, lhs, rhs, Value::shr).await,

// We overload shift right to also create anonymous functions
Expression::ShiftRight(_, lhs, rhs) => {
match AnonymousFunction::parse_lhs(lhs.as_ref()) {
Result::Ok(params) => {
let body = rhs.as_ref();
let func = AnonymousFunction::new(params, body.clone());
Ok(func.into())
}
Result::Err(_) => _eval_binop(env, lhs, rhs, Value::shr).await,
}
}

Expression::Power(_, lhs, rhs) => {
let left = evaluate_expression(env, lhs).await?;
Expand Down

0 comments on commit 35a73d5

Please sign in to comment.