diff --git a/src/interpreter/interpreter.rs b/src/interpreter/interpreter.rs index e4c111d..d2a8c4e 100644 --- a/src/interpreter/interpreter.rs +++ b/src/interpreter/interpreter.rs @@ -274,30 +274,46 @@ pub fn evaluate_expression(env: &mut Env, expr: Box) -> BoxFuture<'_ Expression::PreIncrement(_, expr) => { let current_value = evaluate_expression(env, expr.clone()).await?; let lhs = Lhs::try_from_expr(expr.as_ref().clone(), env).await?; - let new_value = (current_value + 1.into())?; + let new_value = (current_value + 1u64.into())?; lhs.execute_assign(new_value.clone(), env)?; Ok(new_value) } Expression::PreDecrement(_, expr) => { let current_value = evaluate_expression(env, expr.clone()).await?; let lhs = Lhs::try_from_expr(expr.as_ref().clone(), env).await?; - let new_value = (current_value - 1.into())?; + let new_value = (current_value - 1u64.into())?; lhs.execute_assign(new_value.clone(), env)?; Ok(new_value) } Expression::PostIncrement(_, expr) => { let current_value = evaluate_expression(env, expr.clone()).await?; let lhs = Lhs::try_from_expr(expr.as_ref().clone(), env).await?; - lhs.execute_assign((current_value.clone() + 1.into())?, env)?; + lhs.execute_assign((current_value.clone() + 1u64.into())?, env)?; Ok(current_value) } Expression::PostDecrement(_, expr) => { let current_value = evaluate_expression(env, expr.clone()).await?; let lhs = Lhs::try_from_expr(expr.as_ref().clone(), env).await?; - lhs.execute_assign((current_value.clone() - 1.into())?, env)?; + lhs.execute_assign((current_value.clone() - 1u64.into())?, env)?; Ok(current_value) } + Expression::AssignAdd(_, left, right) => { + _eval_binop_assign(env, left, right, |a, b| a + b).await + } + Expression::AssignSubtract(_, left, right) => { + _eval_binop_assign(env, left, right, |a, b| a - b).await + } + Expression::AssignMultiply(_, left, right) => { + _eval_binop_assign(env, left, right, |a, b| a * b).await + } + Expression::AssignDivide(_, left, right) => { + _eval_binop_assign(env, left, right, |a, b| a / b).await + } + Expression::AssignModulo(_, left, right) => { + _eval_binop_assign(env, left, right, |a, b| a % b).await + } + Expression::HexNumberLiteral(_, n, _) => { let result = if n.len() == 42 { Value::Addr(Address::from_hex(n)?) @@ -610,3 +626,18 @@ where let rhs = evaluate_expression(env, rexpr).await?; f(lhs, rhs) } + +async fn _eval_binop_assign( + env: &mut Env, + lexpr: Box, + rexpr: Box, + f: F, +) -> Result +where + F: FnOnce(Value, Value) -> Result, +{ + let lhs = Lhs::try_from_expr(lexpr.as_ref().clone(), env).await?; + let new_value = _eval_binop(env, lexpr, rexpr, f).await?; + lhs.execute_assign(new_value.clone(), env)?; + Ok(new_value) +} diff --git a/src/interpreter/value.rs b/src/interpreter/value.rs index 86f5a7a..425115d 100644 --- a/src/interpreter/value.rs +++ b/src/interpreter/value.rs @@ -202,6 +202,12 @@ impl From for Value { } } +impl From<&str> for Value { + fn from(s: &str) -> Self { + Value::Str(s.to_string()) + } +} + impl From> for Value { fn from(bytes: alloy::primitives::FixedBytes) -> Self { Value::FixBytes(B256::from_slice(&bytes[..]), N) @@ -268,6 +274,7 @@ impl Value { } } + #[allow(clippy::len_without_is_empty)] pub fn len(&self) -> Result { let len = match self { Value::Array(items) => items.len(), diff --git a/tests/interpreter_test.rs b/tests/interpreter_test.rs new file mode 100644 index 0000000..1fbb2d4 --- /dev/null +++ b/tests/interpreter_test.rs @@ -0,0 +1,121 @@ +use eclair::interpreter::{self, Config, Env, Type, Value}; + +#[tokio::test] +async fn test_binops() { + let mut env = _create_env(); + + _check_result(&mut env, "1 + 8", Value::from(9u64)).await; + _check_result(&mut env, "int256(1) - 8", Value::from(-7)).await; + _check_result(&mut env, "3 + 8 * 4", Value::from(35u64)).await; + _check_result(&mut env, "(10 + 4) % 3", Value::from(2u64)).await; +} + +#[tokio::test] +async fn test_string() { + let mut env = _create_env(); + + _check_result(&mut env, "\"foo\"", Value::from("foo")).await; + _check_result(&mut env, "\"foo\".length", Value::from(3u64)).await; + _check_result(&mut env, "\"foo\".concat(\"bar\")", Value::from("foobar")).await; +} + +#[tokio::test] +async fn test_builtin_type() { + let mut env = _create_env(); + + _check_result(&mut env, "type(1)", Value::TypeObject(Type::Uint(256))).await; + _check_result( + &mut env, + "type(uint256)", + Value::TypeObject(Type::Type(Box::new(Type::Uint(256)))), + ) + .await; +} + +#[tokio::test] +async fn test_builtin_format() { + let mut env = _create_env(); + + _check_result(&mut env, "2e18.format()", Value::from("2.00")).await; + _check_result(&mut env, "3.5678e7.format(6)", Value::from("35.68")).await; + _check_result(&mut env, "\"foo\".format()", Value::from("foo")).await; +} + +#[tokio::test] +async fn test_defined_functions() { + let mut env = _create_env(); + + _execute(&mut env, "function add(a, b) { return a + b; }").await; + _check_result(&mut env, "add(1, 2)", Value::from(3u64)).await; + + _execute( + &mut env, + "function cond(pred, yes, no) { if (pred) return yes; else return no; }", + ) + .await; + _check_result(&mut env, "cond(true, 1, 2)", Value::from(1u64)).await; + _check_result(&mut env, "cond(false, 1, 2)", Value::from(2u64)).await; +} + +#[tokio::test] +async fn test_for_loop() { + let mut env = _create_env(); + + let res = _execute( + &mut env, + r#" + a = 1; + for (i = 1; i <= 5; i++) { + a *= i; + } + a; + "#, + ) + .await; + assert_eq!(res, Some(Value::from(120u64))); + + let res = _execute( + &mut env, + r#" + a = 1; + for (i = 1; i <= 5; i++) { + if (a > 10) break; + a *= i; + } + a + "#, + ) + .await; + assert_eq!(res, Some(Value::from(24u64))); + + let res = _execute( + &mut env, + r#" + a = 1; + for (i = 1; i <= 5; i++) { + if (i % 2 == 0) continue; + a *= i; + } + a + "#, + ) + .await; + assert_eq!(res, Some(Value::from(15u64))); +} + +async fn _execute(env: &mut Env, code: &str) -> Option { + interpreter::evaluate_code(env, code).await.unwrap() +} + +async fn _check_result(env: &mut Env, code: &str, expected: Value) { + let res = _execute(env, code).await; + assert_eq!(res, Some(expected)); +} + +fn _create_env() -> Env { + let foundry_conf = foundry_config::load_config(); + let config = Config::new(None, false, foundry_conf); + let mut env = Env::new(config); + interpreter::load_builtins(&mut env); + env +}