diff --git a/crates/haku/src/system.rs b/crates/haku/src/system.rs index 56f1c32..9d39ac6 100644 --- a/crates/haku/src/system.rs +++ b/crates/haku/src/system.rs @@ -11,7 +11,7 @@ use crate::{ vm::{Exception, FnArgs, Vm}, }; -pub type SystemFn = fn(&mut Vm, FnArgs) -> Result; +pub type SystemFn = fn(&System, &mut Vm, FnArgs) -> Result; #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct ChunkId(u32); @@ -249,6 +249,11 @@ pub mod fns { 0x90 Nary "len" => len, 0x91 Nary "index" => index, + 0x92 Nary "range" => range, + 0x93 Nary "map" => map, + 0x94 Nary "filter" => filter, + 0x95 Nary "reduce" => reduce, + 0x96 Nary "flatten" => flatten, 0xc0 Nary "toShape" => to_shape_f, 0xc1 Nary "line" => line, @@ -282,23 +287,23 @@ pub mod fns { } } - pub fn add(vm: &mut Vm, args: FnArgs) -> Result { + pub fn add(_: &System, vm: &mut Vm, args: FnArgs) -> Result { binary_math_op(vm, args, ::add, ::add) } - pub fn sub(vm: &mut Vm, args: FnArgs) -> Result { + pub fn sub(_: &System, vm: &mut Vm, args: FnArgs) -> Result { binary_math_op(vm, args, ::sub, ::sub) } - pub fn mul(vm: &mut Vm, args: FnArgs) -> Result { + pub fn mul(_: &System, vm: &mut Vm, args: FnArgs) -> Result { binary_math_op(vm, args, ::mul, ::mul) } - pub fn div(vm: &mut Vm, args: FnArgs) -> Result { + pub fn div(_: &System, vm: &mut Vm, args: FnArgs) -> Result { binary_math_op(vm, args, ::div, ::div) } - pub fn neg(vm: &mut Vm, args: FnArgs) -> Result { + pub fn neg(_: &System, vm: &mut Vm, args: FnArgs) -> Result { match args.get(vm, 0) { Value::Number(x) => Ok(Value::Number(-x)), Value::Vec4(x) => Ok(Value::Vec4(-x)), @@ -346,7 +351,7 @@ pub mod fns { macro_rules! math_fns { ($($arity:tt $sysname:tt $name:tt),* $(,)?) => { $( - pub fn $name(vm: &mut Vm, args: FnArgs) -> Result { + pub fn $name(_: &System, vm: &mut Vm, args: FnArgs) -> Result { $arity(vm, args, $sysname, libm::$name) } )* @@ -385,48 +390,48 @@ pub mod fns { math1 "atanh" atanhf, } - pub fn not(vm: &mut Vm, args: FnArgs) -> Result { + pub fn not(_: &System, vm: &mut Vm, args: FnArgs) -> Result { let value = args.get(vm, 0); Ok(Value::from(value.is_falsy())) } - pub fn eq(vm: &mut Vm, args: FnArgs) -> Result { + pub fn eq(_: &System, vm: &mut Vm, args: FnArgs) -> Result { let a = args.get(vm, 0); let b = args.get(vm, 1); Ok(Value::from(a == b)) } - pub fn neq(vm: &mut Vm, args: FnArgs) -> Result { + pub fn neq(_: &System, vm: &mut Vm, args: FnArgs) -> Result { let a = args.get(vm, 0); let b = args.get(vm, 1); Ok(Value::from(a != b)) } - pub fn lt(vm: &mut Vm, args: FnArgs) -> Result { + pub fn lt(_: &System, vm: &mut Vm, args: FnArgs) -> Result { let a = args.get(vm, 0); let b = args.get(vm, 1); Ok(Value::from(a < b)) } - pub fn leq(vm: &mut Vm, args: FnArgs) -> Result { + pub fn leq(_: &System, vm: &mut Vm, args: FnArgs) -> Result { let a = args.get(vm, 0); let b = args.get(vm, 1); Ok(Value::from(a <= b)) } - pub fn gt(vm: &mut Vm, args: FnArgs) -> Result { + pub fn gt(_: &System, vm: &mut Vm, args: FnArgs) -> Result { let a = args.get(vm, 0); let b = args.get(vm, 1); Ok(Value::from(a > b)) } - pub fn geq(vm: &mut Vm, args: FnArgs) -> Result { + pub fn geq(_: &System, vm: &mut Vm, args: FnArgs) -> Result { let a = args.get(vm, 0); let b = args.get(vm, 1); Ok(Value::from(a >= b)) } - pub fn vec(vm: &mut Vm, args: FnArgs) -> Result { + pub fn vec(_: &System, vm: &mut Vm, args: FnArgs) -> Result { static ERROR: &str = "arguments to `vec` must be numbers (vec x y z w)"; match args.num() { 1 => { @@ -465,7 +470,7 @@ pub mod fns { } } - pub fn vec_x(vm: &mut Vm, args: FnArgs) -> Result { + pub fn vec_x(_: &System, vm: &mut Vm, args: FnArgs) -> Result { if args.num() != 1 { return Err(vm.create_exception("`vecX` expects a single argument (vecX vec)")); } @@ -474,7 +479,7 @@ pub mod fns { Ok(Value::Number(vec.x)) } - pub fn vec_y(vm: &mut Vm, args: FnArgs) -> Result { + pub fn vec_y(_: &System, vm: &mut Vm, args: FnArgs) -> Result { if args.num() != 1 { return Err(vm.create_exception("`vecY` expects a single argument (vecY vec)")); } @@ -483,7 +488,7 @@ pub mod fns { Ok(Value::Number(vec.y)) } - pub fn vec_z(vm: &mut Vm, args: FnArgs) -> Result { + pub fn vec_z(_: &System, vm: &mut Vm, args: FnArgs) -> Result { if args.num() != 1 { return Err(vm.create_exception("`vecZ` expects a single argument (vecZ vec)")); } @@ -492,7 +497,7 @@ pub mod fns { Ok(Value::Number(vec.z)) } - pub fn vec_w(vm: &mut Vm, args: FnArgs) -> Result { + pub fn vec_w(_: &System, vm: &mut Vm, args: FnArgs) -> Result { if args.num() != 1 { return Err(vm.create_exception("`vecW` expects a single argument (vecW vec)")); } @@ -501,7 +506,7 @@ pub mod fns { Ok(Value::Number(vec.w)) } - pub fn rgba(vm: &mut Vm, args: FnArgs) -> Result { + pub fn rgba(_: &System, vm: &mut Vm, args: FnArgs) -> Result { if args.num() != 4 { return Err(vm.create_exception("`rgba` expects four arguments (rgba r g b a)")); } @@ -515,7 +520,7 @@ pub mod fns { Ok(Value::Rgba(Rgba { r, g, b, a })) } - pub fn rgba_r(vm: &mut Vm, args: FnArgs) -> Result { + pub fn rgba_r(_: &System, vm: &mut Vm, args: FnArgs) -> Result { if args.num() != 1 { return Err(vm.create_exception("`rgbaR` expects a single argument (rgbaR rgba)")); } @@ -524,7 +529,7 @@ pub mod fns { Ok(Value::Number(rgba.r)) } - pub fn rgba_g(vm: &mut Vm, args: FnArgs) -> Result { + pub fn rgba_g(_: &System, vm: &mut Vm, args: FnArgs) -> Result { if args.num() != 1 { return Err(vm.create_exception("`rgbaG` expects a single argument (rgbaG rgba)")); } @@ -533,7 +538,7 @@ pub mod fns { Ok(Value::Number(rgba.r)) } - pub fn rgba_b(vm: &mut Vm, args: FnArgs) -> Result { + pub fn rgba_b(_: &System, vm: &mut Vm, args: FnArgs) -> Result { if args.num() != 1 { return Err(vm.create_exception("`rgbaB` expects a single argument (rgbaB rgba)")); } @@ -542,7 +547,7 @@ pub mod fns { Ok(Value::Number(rgba.r)) } - pub fn rgba_a(vm: &mut Vm, args: FnArgs) -> Result { + pub fn rgba_a(_: &System, vm: &mut Vm, args: FnArgs) -> Result { if args.num() != 1 { return Err(vm.create_exception("`rgbaA` expects a single argument (rgbaA rgba)")); } @@ -551,7 +556,7 @@ pub mod fns { Ok(Value::Number(rgba.r)) } - pub fn len(vm: &mut Vm, args: FnArgs) -> Result { + pub fn len(_: &System, vm: &mut Vm, args: FnArgs) -> Result { if args.num() != 1 { return Err(vm.create_exception("`len` expects a single argument (len list)")); } @@ -560,7 +565,7 @@ pub mod fns { Ok(Value::Number(list.elements.len() as f32)) } - pub fn index(vm: &mut Vm, args: FnArgs) -> Result { + pub fn index(_: &System, vm: &mut Vm, args: FnArgs) -> Result { if args.num() != 2 { return Err(vm.create_exception("`index` expects two arguments (index list i)")); } @@ -580,6 +585,174 @@ pub mod fns { ))) } + pub fn range(_: &System, vm: &mut Vm, args: FnArgs) -> Result { + if args.num() != 2 { + return Err(vm.create_exception("`range` expects two arguments (range min max)")); + } + + let min = + args.get_number(vm, 0, "1st argument to (range min max) must be a number")? as i64; + let max = + args.get_number(vm, 1, "2nd argument to (range min max) must be a number")? as i64; + + // Careful here. We don't want someone to generate a list that's so long it blows up the server. + // Therefore generating a list consumes fuel, in addition to bulk memory. + // An important thing here is to perform any checks ahead of time, before the list is allocated. + let elements: Vec<_> = if min < max { + let len = (min..=max).count(); + vm.consume_fuel(len)?; + vm.track_array::(len)?; + + (min..=max).map(|v| Value::Number(v as f32)).collect() + } else { + let len = (max..=min).count(); + vm.consume_fuel(len)?; + vm.track_array::(len)?; + + (max..=min).rev().map(|v| Value::Number(v as f32)).collect() + }; + Ok(Value::Ref(vm.create_ref(Ref::List(List { elements }))?)) + } + + pub fn map(system: &System, vm: &mut Vm, args: FnArgs) -> Result { + if args.num() != 2 { + return Err(vm.create_exception("`map` expects two arguments (map list function)")); + } + + let list = args.get_list(vm, 0, "1st argument to (map list function) must be a list")?; + let (function_id, _) = args.get_closure( + vm, + 1, + 1, + "2nd argument to (map list function) must be a single-argument function", + )?; + + // Unlike with (range), we don't consume fuel---because running the function itself is enough. + let len = list.elements.len(); + vm.track_array::(len)?; + + // Weird reborrow required due to track_array consuming a mutable Vm reference. + let (_, Ref::List(list)) = vm.get_ref_value(args.get(vm, 0)).unwrap() else { + unreachable!() + }; + let mut result = list.clone(); + for i in 0..result.elements.len() { + let value = result.elements[i]; + let mapped = vm.run(system, function_id, &[value])?; + result.elements[i] = mapped; + } + + Ok(Value::Ref(vm.create_ref(Ref::List(result))?)) + } + + pub fn filter(system: &System, vm: &mut Vm, args: FnArgs) -> Result { + if args.num() != 2 { + return Err( + vm.create_exception("`filter` expects two arguments (filter list function)") + ); + } + + let list = args.get_list( + vm, + 0, + "1st argument to (filter list function) must be a list", + )?; + let (function_id, _) = args.get_closure( + vm, + 1, + 1, + "2nd argument to (filter list function) must be a single-argument function", + )?; + + let len = list.elements.len(); + vm.track_array::(len)?; + + let (_, Ref::List(list)) = vm.get_ref_value(args.get(vm, 0)).unwrap() else { + unreachable!() + }; + let mut result = list.clone(); + let mut error = Ok(()); + result.elements.retain(|&value| { + if error.is_err() { + return true; + } + match vm.run(system, function_id, &[value]) { + Ok(value) => value.is_truthy(), + Err(err) => { + error = Err(err); + true + } + } + }); + error?; + + Ok(Value::Ref(vm.create_ref(Ref::List(result))?)) + } + + pub fn reduce(system: &System, vm: &mut Vm, args: FnArgs) -> Result { + if args.num() != 3 { + return Err( + vm.create_exception("`reduce` expects three arguments (reduce list init function)") + ); + } + + let num = args + .get_list( + vm, + 0, + "1st argument to (reduce list init function) must be a list", + )? + .elements + .len(); + let (function_id, _) = args.get_closure( + vm, + 2, + 2, + "3rd argument to (reduce list init function) must be a two-argument function", + )?; + + let mut result = args.get(vm, 1); + + for i in 0..num { + let (_, Ref::List(list)) = vm.get_ref_value(args.get(vm, 0)).unwrap() else { + unreachable!() + }; + let value = list.elements[i]; + result = vm.run(system, function_id, &[result, value])?; + } + + Ok(result) + } + + pub fn flatten(_: &System, vm: &mut Vm, args: FnArgs) -> Result { + if args.num() != 1 { + return Err(vm.create_exception("`flatten` expects a single argument")); + } + + let list = args.get_list(vm, 0, "1st argument to (map list function) must be a list")?; + let len = list.elements.len(); + + let mut elements = Vec::with_capacity(list.elements.len()); + for i in 0..len { + let value = args.get_list(vm, 0, "unreachable").unwrap().elements[i]; + if let Some((_, Ref::List(inner))) = vm.get_ref_value(value) { + let len = inner.elements.len(); + vm.consume_fuel(len)?; + vm.track_array::(len)?; + let Some((_, Ref::List(inner))) = vm.get_ref_value(value) else { + unreachable!() + }; + elements.extend_from_slice(&inner.elements); + } else { + vm.consume_fuel(1)?; + vm.track_array::(1)?; + elements.push(value); + } + } + + Ok(Value::Ref(vm.create_ref(Ref::List(List { elements }))?)) + } + fn to_shape(value: Value, vm: &Vm) -> Option { match value { Value::Nil @@ -601,7 +774,7 @@ pub mod fns { } } - pub fn to_shape_f(vm: &mut Vm, args: FnArgs) -> Result { + pub fn to_shape_f(_: &System, vm: &mut Vm, args: FnArgs) -> Result { if args.num() != 1 { return Err(vm.create_exception("`toShape` expects 1 argument (toShape value)")); } @@ -614,7 +787,7 @@ pub mod fns { } } - pub fn line(vm: &mut Vm, args: FnArgs) -> Result { + pub fn line(_: &System, vm: &mut Vm, args: FnArgs) -> Result { if args.num() != 2 { return Err(vm.create_exception("`line` expects 2 arguments (line start end)")); } @@ -627,7 +800,7 @@ pub mod fns { Ok(Value::Ref(id)) } - pub fn rect(vm: &mut Vm, args: FnArgs) -> Result { + pub fn rect(_: &System, vm: &mut Vm, args: FnArgs) -> Result { static ARGS2: &str = "arguments to 2-argument `rect` must be `vec`"; static ARGS4: &str = "arguments to 4-argument `rect` must be numbers"; @@ -650,7 +823,7 @@ pub mod fns { Ok(Value::Ref(id)) } - pub fn circle(vm: &mut Vm, args: FnArgs) -> Result { + pub fn circle(_: &System, vm: &mut Vm, args: FnArgs) -> Result { static ARGS2: &str = "arguments to 2-argument `circle` must be `vec` and a number"; static ARGS3: &str = "arguments to 3-argument `circle` must be numbers"; @@ -670,7 +843,7 @@ pub mod fns { Ok(Value::Ref(id)) } - pub fn stroke(vm: &mut Vm, args: FnArgs) -> Result { + pub fn stroke(_: &System, vm: &mut Vm, args: FnArgs) -> Result { if args.num() != 3 { return Err( vm.create_exception("`stroke` expects 3 arguments (stroke thickness color shape)") @@ -695,7 +868,7 @@ pub mod fns { } } - pub fn fill(vm: &mut Vm, args: FnArgs) -> Result { + pub fn fill(_: &System, vm: &mut Vm, args: FnArgs) -> Result { if args.num() != 2 { return Err(vm.create_exception("`fill` expects 2 arguments (fill color shape)")); } @@ -709,17 +882,19 @@ pub mod fns { } } - pub fn with_dotter(vm: &mut Vm, args: FnArgs) -> Result { + pub fn with_dotter(_: &System, vm: &mut Vm, args: FnArgs) -> Result { if args.num() != 1 { return Err(vm.create_exception( "`withDotter` expects a single argument (withDotter \\d -> [])", )); } - let draw = args.get_closure(vm, 0, "argument to `withDotter` must be a closure")?; - if draw.param_count != 1 { - return Err(vm.create_exception("function passed to `withDotter` must take in a single parameter (withDotter \\d -> [])")); - } + _ = args.get_closure( + vm, + 0, + 1, + "argument to `withDotter` must be a single-parameter function (withDotter \\d -> [])", + )?; let id = vm.create_ref(Ref::Reticle(Reticle::Dotter { draw: args.get(vm, 0), diff --git a/crates/haku/src/value.rs b/crates/haku/src/value.rs index 1d31be5..ce4c53f 100644 --- a/crates/haku/src/value.rs +++ b/crates/haku/src/value.rs @@ -47,6 +47,13 @@ impl Value { _ => None, } } + + pub fn to_ref(&self) -> Option { + match self { + Self::Ref(v) => Some(*v), + _ => None, + } + } } impl From<()> for Value { diff --git a/crates/haku/src/vm.rs b/crates/haku/src/vm.rs index 96700a8..ef61241 100644 --- a/crates/haku/src/vm.rs +++ b/crates/haku/src/vm.rs @@ -89,6 +89,15 @@ impl Vm { self.fuel = fuel; } + pub fn consume_fuel(&mut self, amount: usize) -> Result<(), Exception> { + let fuel = self + .remaining_fuel() + .checked_sub(amount) + .ok_or_else(|| self.create_exception("code ran for too long"))?; + self.set_fuel(fuel); + Ok(()) + } + pub fn image(&self) -> VmImage { assert!( self.stack.is_empty() && self.call_stack.is_empty(), @@ -197,6 +206,8 @@ impl Vm { .as_closure() .expect("a Closure-type Ref must be passed to `run`"); + vmtrace!("ENTER ref = {closure_id:?}, closure = {closure:?}"); + let mut chunk_id = closure.start.chunk_id; let mut chunk = system.chunk(chunk_id); let mut pc = closure.start.offset as usize; @@ -216,6 +227,7 @@ impl Vm { #[allow(unused)] let closure = (); // Do not use `closure` after this! Use `get_ref` on `closure_id` instead. + let call_bottom = self.call_stack.len(); self.push_call(CallFrame { closure_id, chunk_id, @@ -223,6 +235,8 @@ impl Vm { bottom, })?; + vmtrace!("LOOP"); + loop { fuel = fuel .checked_sub(1) @@ -231,7 +245,7 @@ impl Vm { #[allow(unused)] let pc2 = pc; let opcode = chunk.read_opcode(&mut pc)?; - vmtrace!("{pc2:2} {opcode:?}"); + vmtrace!("insn {pc2:2} {opcode:?}"); match opcode { Opcode::Nil => self.push(Value::Nil)?, Opcode::False => self.push(Value::False)?, @@ -300,6 +314,7 @@ impl Vm { Opcode::List => { let len = chunk.read_u16(&mut pc)? as usize; + self.track_array::(len)?; let bottom = self.stack.len().checked_sub(len).ok_or_else(|| { self.create_exception( "corrupted bytecode (list has more elements than stack)", @@ -307,7 +322,6 @@ impl Vm { })?; let elements = self.stack[bottom..].to_vec(); self.stack.resize_with(bottom, || unreachable!()); - self.track_array(&elements)?; let id = self.create_ref(Ref::List(List { elements }))?; self.push(Value::Ref(id))?; } @@ -349,7 +363,7 @@ impl Vm { local_count, captures, }; - vmtrace!("{closure:?}"); + vmtrace!(" : {closure:?}"); let id = self.create_ref(Ref::Closure(closure))?; self.push(Value::Ref(id))?; } @@ -449,6 +463,7 @@ impl Vm { self.store_context(Context { fuel }); let result = system_fn( + system, self, FnArgs { base: self @@ -475,7 +490,8 @@ impl Vm { self.push(value)?; // Once the initial frame is popped, halt the VM. - if self.call_stack.is_empty() { + debug_assert!(self.call_stack.len() >= call_bottom); + if self.call_stack.len() == call_bottom { self.store_context(Context { fuel }); break; } @@ -491,12 +507,15 @@ impl Vm { } } + vmtrace!("EXIT"); + let result = self - .stack .pop() .expect("there should be a result at the top of the stack"); self.stack.resize_with(init_bottom, || unreachable!()); + vmtrace!("result = {result:?}"); + Ok(result) } @@ -535,10 +554,12 @@ impl Vm { } } - pub fn track_array(&mut self, array: &[T]) -> Result<(), Exception> { + pub fn track_array(&mut self, len: usize) -> Result<(), Exception> { + let layout = core::alloc::Layout::array::(len) + .map_err(|_| self.create_exception("out of heap memory"))?; self.memory = self .memory - .checked_sub(core::mem::size_of_val(array)) + .checked_sub(layout.size()) .ok_or_else(|| self.create_exception("out of heap memory"))?; Ok(()) } @@ -611,16 +632,20 @@ impl FnArgs { &self, vm: &'vm Vm, index: usize, + param_count: u8, message: &'static str, - ) -> Result<&'vm Closure, Exception> { + ) -> Result<(RefId, &'vm Closure), Exception> { let value = self.get(vm, index); - let (_, any_ref) = vm + let (ref_id, any_ref) = vm .get_ref_value(value) .ok_or_else(|| vm.create_exception(message))?; let Ref::Closure(closure) = any_ref else { return Err(vm.create_exception(message)); }; - Ok(closure) + if closure.param_count != param_count { + return Err(vm.create_exception(message)); + } + Ok((ref_id, closure)) } #[inline(never)] diff --git a/docs/system.dj b/docs/system.dj index ec7c3e3..03a2a4b 100644 --- a/docs/system.dj +++ b/docs/system.dj @@ -624,6 +624,93 @@ The first element is located at index 0. Indexing out of bounds is an error. +```haku +range + min : number + max : number + -> list number +``` + +Generates a list of integers from `min` to `max` (inclusive). + +- When `max > min`, the integers go in an increasing order. +- When `min > max`, the integers go in a decreasing order. + +Note that this function consumes fuel proportional to the amount of elements in the generated list. + +```haku +map + l : list t + f : \t -> u + -> list u +``` + +Produces a new list containing all the elements of `l`, run through the function `f` one by one. + +```haku +filter + l : list t + f : \t -> boolean + -> list t +``` + +Produces a new list with only those elements of `l` for which `f` returns `True`. + +```haku +reduce + l : list t + init : a + f : \a, t -> a + -> a +``` + +Reduces the list to a single element, by repeatedly applying a reducing operation `f` on its elements. + +This reducing operation receives the current value of an _accumulator_ variable (first argument), and the current element being iterated (second argument). +Its task is to return the _next_ value of the accumulator variable, which then gets passed into the next element, and so on and so forth. + +```haku +flatten + l : list _ + -> list _ +``` + +Returns a new list with a single level of nesting flattened. + +--- + +Some of these operations may be a bit confusing, so here are some examples. + +```haku +-- To add two to all elements in a list: +list = range 1 4 -- [1, 2, 3, 4] +twoAdded = map list \x -> + x + 2 +``` + +```haku +-- To filter out only even numbers in a list: +list = range 1 10 +isEven = \x -> mod x 2 == 0 +onlyEven = filter list isEven +``` + +```haku +-- To sum all the numbers in a list: +list = [1, 3, 10, 2, 30, 4, 1] +sum = reduce list 0 \acc, value -> acc + value +``` + +```haku +-- To flatten a singly-nested list: +list = [[1, 2], [3, 4], [5, 6]] +flatList = flatten list -- [1, 2, 3, 4, 5, 6] + +-- Note that this only applies to a single level of nesting: +deepList = [[[1, 2, 3, 4]]] +lessDeepList = flatten deepList -- [[1, 2, 3, 4]] +``` + ## Shapes