diff --git a/implants/lib/eldritchv2/eldritch-core/src/interpreter/builtins/all.rs b/implants/lib/eldritchv2/eldritch-core/src/interpreter/builtins/all.rs index d243cdf45..accb6b75a 100644 --- a/implants/lib/eldritchv2/eldritch-core/src/interpreter/builtins/all.rs +++ b/implants/lib/eldritchv2/eldritch-core/src/interpreter/builtins/all.rs @@ -2,6 +2,7 @@ use crate::ast::{Environment, Value}; use crate::interpreter::introspection::{get_type_name, is_truthy}; use alloc::format; use alloc::string::String; +use alloc::string::ToString; use alloc::sync::Arc; use spin::RwLock; @@ -22,6 +23,7 @@ pub fn builtin_all(_env: &Arc>, args: &[Value]) -> Result t.clone(), Value::Set(s) => s.read().iter().cloned().collect(), Value::Dictionary(d) => d.read().keys().cloned().collect(), + Value::String(s) => s.chars().map(|c| Value::String(c.to_string())).collect(), _ => { return Err(format!( "'{}' object is not iterable", diff --git a/implants/lib/eldritchv2/eldritch-core/src/interpreter/builtins/any.rs b/implants/lib/eldritchv2/eldritch-core/src/interpreter/builtins/any.rs index dbee812ae..185d59066 100644 --- a/implants/lib/eldritchv2/eldritch-core/src/interpreter/builtins/any.rs +++ b/implants/lib/eldritchv2/eldritch-core/src/interpreter/builtins/any.rs @@ -2,6 +2,7 @@ use crate::ast::{Environment, Value}; use crate::interpreter::introspection::{get_type_name, is_truthy}; use alloc::format; use alloc::string::String; +use alloc::string::ToString; use alloc::sync::Arc; use spin::RwLock; @@ -22,6 +23,7 @@ pub fn builtin_any(_env: &Arc>, args: &[Value]) -> Result t.clone(), Value::Set(s) => s.read().iter().cloned().collect(), Value::Dictionary(d) => d.read().keys().cloned().collect(), + Value::String(s) => s.chars().map(|c| Value::String(c.to_string())).collect(), _ => { return Err(format!( "'{}' object is not iterable", diff --git a/implants/lib/eldritchv2/eldritch-core/tests/builtins_extended.rs b/implants/lib/eldritchv2/eldritch-core/tests/builtins_extended.rs new file mode 100644 index 000000000..1185a3dc6 --- /dev/null +++ b/implants/lib/eldritchv2/eldritch-core/tests/builtins_extended.rs @@ -0,0 +1,142 @@ +mod assert; + +#[test] +fn test_any_extended() { + // Strings + assert::pass("assert(any('abc') == True)"); + assert::pass("assert(any('') == False)"); + + // Lists + assert::pass("assert(any([False, True, False]) == True)"); + assert::pass("assert(any([False, False]) == False)"); + assert::pass("assert(any([]) == False)"); + + // Sets + assert::pass("assert(any({False, True}) == True)"); + assert::pass("assert(any({False}) == False)"); + assert::pass("assert(any(set()) == False)"); +} + +#[test] +fn test_all_extended() { + // Strings + assert::pass("assert(all('abc') == True)"); + assert::pass("assert(all('') == True)"); + + // Lists + assert::pass("assert(all([True, True]) == True)"); + assert::pass("assert(all([True, False]) == False)"); + assert::pass("assert(all([]) == True)"); + + // Sets + assert::pass("assert(all({True}) == True)"); + assert::pass("assert(all({True, False}) == False)"); + assert::pass("assert(all(set()) == True)"); +} + +#[test] +fn test_map_extended() { + // map with different length iterables (not supported yet, checking fail/behavior) + // Python supports it, stops at shortest. Eldritch map takes exactly 2 args (func, iterable). + // So we can only test single iterable map for now. + + // Test mapping over string + assert::pass( + r#" + res = map(lambda x: x + x, "abc") + assert_eq(res, ["aa", "bb", "cc"]) + "#, + ); + + // Test mapping over empty list + assert::pass( + r#" + res = map(lambda x: x, []) + assert_eq(res, []) + "#, + ); +} + +#[test] +fn test_filter_extended() { + // Filter string + assert::pass( + r#" + res = filter(lambda x: x != "b", "abc") + assert_eq(res, ["a", "c"]) + "#, + ); + + // Filter None with string + assert::pass( + r#" + res = filter(None, "abc") + assert_eq(res, ["a", "b", "c"]) + "#, + ); +} + +#[test] +fn test_zip_extended() { + // Zip strings + assert::pass( + r#" + z = zip("ab", "cd") + assert_eq(z, [("a", "c"), ("b", "d")]) + "#, + ); + + // Zip string and list + assert::pass( + r#" + z = zip("ab", [1, 2]) + assert_eq(z, [("a", 1), ("b", 2)]) + "#, + ); +} + +#[test] +fn test_sorted_extended() { + // Reverse + assert::pass( + r#" + l = [1, 3, 2] + res = sorted(l, reverse=True) + assert_eq(res, [3, 2, 1]) + "#, + ); + + // Key + assert::pass( + r#" + l = ["ccc", "a", "bb"] + res = sorted(l, key=len) + assert_eq(res, ["a", "bb", "ccc"]) + "#, + ); + + // Key and Reverse + assert::pass( + r#" + l = ["ccc", "a", "bb"] + res = sorted(l, key=len, reverse=True) + assert_eq(res, ["ccc", "bb", "a"]) + "#, + ); + + // Sorting string (returns list of chars) + assert::pass( + r#" + s = "bca" + res = sorted(s) + assert_eq(res, ["a", "b", "c"]) + "#, + ); +} + +#[test] +fn test_max_min_extended() { + // String + assert::pass("assert(max('abc') == 'c')"); + assert::pass("assert(min('abc') == 'a')"); +}