feat: add List.findIndex, List.zip, List.flatten, List.contains

Add missing List operations requested by ergon game engine project:
- findIndex(list, predicate) -> Option<Int>
- zip(list1, list2) -> List<(A, B)>
- flatten(listOfLists) -> List<A>
- contains(list, element) -> Bool

Resolves ergon porting blocker #4.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-20 16:40:32 -05:00
parent 26b94935e9
commit 7b40421a6a
3 changed files with 202 additions and 7 deletions

View File

@@ -83,10 +83,14 @@ pub enum BuiltinFn {
// Additional List operations
ListIsEmpty,
ListFind,
ListFindIndex,
ListAny,
ListAll,
ListTake,
ListDrop,
ListZip,
ListFlatten,
ListContains,
// Additional String operations
StringStartsWith,
@@ -974,10 +978,14 @@ impl Interpreter {
Value::Builtin(BuiltinFn::ListIsEmpty),
),
("find".to_string(), Value::Builtin(BuiltinFn::ListFind)),
("findIndex".to_string(), Value::Builtin(BuiltinFn::ListFindIndex)),
("any".to_string(), Value::Builtin(BuiltinFn::ListAny)),
("all".to_string(), Value::Builtin(BuiltinFn::ListAll)),
("take".to_string(), Value::Builtin(BuiltinFn::ListTake)),
("drop".to_string(), Value::Builtin(BuiltinFn::ListDrop)),
("zip".to_string(), Value::Builtin(BuiltinFn::ListZip)),
("flatten".to_string(), Value::Builtin(BuiltinFn::ListFlatten)),
("contains".to_string(), Value::Builtin(BuiltinFn::ListContains)),
(
"forEach".to_string(),
Value::Builtin(BuiltinFn::ListForEach),
@@ -2723,6 +2731,55 @@ impl Interpreter {
Ok(EvalResult::Value(Value::Bool(true)))
}
BuiltinFn::ListFindIndex => {
let (list, func) = Self::expect_args_2::<Vec<Value>, Value>(&args, "List.findIndex", span)?;
for (i, item) in list.iter().enumerate() {
let v = self.eval_call_to_value(func.clone(), vec![item.clone()], span)?;
match v {
Value::Bool(true) => {
return Ok(EvalResult::Value(Value::Constructor {
name: "Some".to_string(),
fields: vec![Value::Int(i as i64)],
}));
}
Value::Bool(false) => {}
_ => return Err(err("List.findIndex predicate must return Bool")),
}
}
Ok(EvalResult::Value(Value::Constructor {
name: "None".to_string(),
fields: vec![],
}))
}
BuiltinFn::ListZip => {
let (list1, list2) = Self::expect_args_2::<Vec<Value>, Vec<Value>>(&args, "List.zip", span)?;
let result: Vec<Value> = list1
.into_iter()
.zip(list2.into_iter())
.map(|(a, b)| Value::Tuple(vec![a, b]))
.collect();
Ok(EvalResult::Value(Value::List(result)))
}
BuiltinFn::ListFlatten => {
let list = Self::expect_arg_1::<Vec<Value>>(&args, "List.flatten", span)?;
let mut result = Vec::new();
for item in list {
match item {
Value::List(inner) => result.extend(inner),
other => result.push(other),
}
}
Ok(EvalResult::Value(Value::List(result)))
}
BuiltinFn::ListContains => {
let (list, target) = Self::expect_args_2::<Vec<Value>, Value>(&args, "List.contains", span)?;
let found = list.iter().any(|item| Value::values_equal(item, &target));
Ok(EvalResult::Value(Value::Bool(found)))
}
BuiltinFn::ListTake => {
let (list, n) = Self::expect_args_2::<Vec<Value>, i64>(&args, "List.take", span)?;
let n = n.max(0) as usize;