diff --git a/crates/haku/src/system.rs b/crates/haku/src/system.rs index 3f9201d..56f1c32 100644 --- a/crates/haku/src/system.rs +++ b/crates/haku/src/system.rs @@ -247,9 +247,8 @@ pub mod fns { 0x88 Nary "rgbaB" => rgba_b, 0x89 Nary "rgbaA" => rgba_a, - // NOTE: Not used right now, has been replaced with Opcode::List. - // Keeping it around to reserve a slot for data structure operations. - 0x90 Nary "list (unused)" => list, + 0x90 Nary "len" => len, + 0x91 Nary "index" => index, 0xc0 Nary "toShape" => to_shape_f, 0xc1 Nary "line" => line, @@ -552,11 +551,33 @@ pub mod fns { Ok(Value::Number(rgba.r)) } - pub fn list(vm: &mut Vm, args: FnArgs) -> Result { - let elements: Vec<_> = (0..args.num()).map(|i| args.get(vm, i)).collect(); - vm.track_array(&elements)?; - let id = vm.create_ref(Ref::List(List { elements }))?; - Ok(Value::Ref(id)) + pub fn len(vm: &mut Vm, args: FnArgs) -> Result { + if args.num() != 1 { + return Err(vm.create_exception("`len` expects a single argument (len list)")); + } + + let list = args.get_list(vm, 0, "argument to (len list) must be a list")?; + Ok(Value::Number(list.elements.len() as f32)) + } + + pub fn index(vm: &mut Vm, args: FnArgs) -> Result { + if args.num() != 2 { + return Err(vm.create_exception("`index` expects two arguments (index list i)")); + } + + let list = args.get_list(vm, 0, "1st argument to (index list i) must be a list")?; + let i = args.get_number(vm, 1, "2nd argument to (index list i) must be a number")?; + if i >= 0.0 { + let i = i as usize; + if i < list.elements.len() { + return Ok(list.elements[i]); + } + } + Err(vm.create_exception(format!( + "list index out of bounds (length of list is {}, index is {})", + list.elements.len(), + i + ))) } fn to_shape(value: Value, vm: &Vm) -> Option { diff --git a/crates/haku/src/vm.rs b/crates/haku/src/vm.rs index 59aa97b..96700a8 100644 --- a/crates/haku/src/vm.rs +++ b/crates/haku/src/vm.rs @@ -622,6 +622,23 @@ impl FnArgs { }; Ok(closure) } + + #[inline(never)] + pub fn get_list<'vm>( + &self, + vm: &'vm Vm, + index: usize, + message: &'static str, + ) -> Result<&'vm List, Exception> { + let value = self.get(vm, index); + let (_, any_ref) = vm + .get_ref_value(value) + .ok_or_else(|| vm.create_exception(message))?; + let Ref::List(list) = any_ref else { + return Err(vm.create_exception(message)); + }; + Ok(list) + } } #[derive(Debug, Clone, PartialEq, Eq)] diff --git a/crates/haku/tests/language.rs b/crates/haku/tests/language.rs index d88d1ff..dad93b6 100644 --- a/crates/haku/tests/language.rs +++ b/crates/haku/tests/language.rs @@ -292,3 +292,20 @@ fn with_dotter_identity() { "#; assert_eq!(eval(code).unwrap(), Value::Ref(RefId::from_u32(2))) } + +#[test] +fn system_len() { + expect_number("len []", 0.0, 0.0001); + expect_number("len [1, 2]", 2.0, 0.0001); +} + +#[test] +fn system_index() { + expect_number("index [1] 0", 1.0, 0.0001); + expect_number("index [1, 2] 0", 1.0, 0.0001); + expect_number("index [1, 2] 1", 2.0, 0.0001); + expect_number("index [1] 0.5", 1.0, 0.0001); + expect_number("index [1, 2] 0.5", 1.0, 0.0001); + assert!(eval("index [1] (-1)").is_err()); + assert!(eval("index [1] 1").is_err()); +}