additional list functions (range, map, filter, reduce, flatten) (#74)

also make the VM cope with reentrancy
This commit is contained in:
りき萌 2025-05-27 20:20:10 +02:00
parent 2b924c3efb
commit 65645f410f
4 changed files with 341 additions and 47 deletions

View file

@ -11,7 +11,7 @@ use crate::{
vm::{Exception, FnArgs, Vm}, vm::{Exception, FnArgs, Vm},
}; };
pub type SystemFn = fn(&mut Vm, FnArgs) -> Result<Value, Exception>; pub type SystemFn = fn(&System, &mut Vm, FnArgs) -> Result<Value, Exception>;
#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ChunkId(u32); pub struct ChunkId(u32);
@ -249,6 +249,11 @@ pub mod fns {
0x90 Nary "len" => len, 0x90 Nary "len" => len,
0x91 Nary "index" => index, 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, 0xc0 Nary "toShape" => to_shape_f,
0xc1 Nary "line" => line, 0xc1 Nary "line" => line,
@ -282,23 +287,23 @@ pub mod fns {
} }
} }
pub fn add(vm: &mut Vm, args: FnArgs) -> Result<Value, Exception> { pub fn add(_: &System, vm: &mut Vm, args: FnArgs) -> Result<Value, Exception> {
binary_math_op(vm, args, <f32 as Add>::add, <Vec4 as Add>::add) binary_math_op(vm, args, <f32 as Add>::add, <Vec4 as Add>::add)
} }
pub fn sub(vm: &mut Vm, args: FnArgs) -> Result<Value, Exception> { pub fn sub(_: &System, vm: &mut Vm, args: FnArgs) -> Result<Value, Exception> {
binary_math_op(vm, args, <f32 as Sub>::sub, <Vec4 as Sub>::sub) binary_math_op(vm, args, <f32 as Sub>::sub, <Vec4 as Sub>::sub)
} }
pub fn mul(vm: &mut Vm, args: FnArgs) -> Result<Value, Exception> { pub fn mul(_: &System, vm: &mut Vm, args: FnArgs) -> Result<Value, Exception> {
binary_math_op(vm, args, <f32 as Mul>::mul, <Vec4 as Mul>::mul) binary_math_op(vm, args, <f32 as Mul>::mul, <Vec4 as Mul>::mul)
} }
pub fn div(vm: &mut Vm, args: FnArgs) -> Result<Value, Exception> { pub fn div(_: &System, vm: &mut Vm, args: FnArgs) -> Result<Value, Exception> {
binary_math_op(vm, args, <f32 as Div>::div, <Vec4 as Div>::div) binary_math_op(vm, args, <f32 as Div>::div, <Vec4 as Div>::div)
} }
pub fn neg(vm: &mut Vm, args: FnArgs) -> Result<Value, Exception> { pub fn neg(_: &System, vm: &mut Vm, args: FnArgs) -> Result<Value, Exception> {
match args.get(vm, 0) { match args.get(vm, 0) {
Value::Number(x) => Ok(Value::Number(-x)), Value::Number(x) => Ok(Value::Number(-x)),
Value::Vec4(x) => Ok(Value::Vec4(-x)), Value::Vec4(x) => Ok(Value::Vec4(-x)),
@ -346,7 +351,7 @@ pub mod fns {
macro_rules! math_fns { macro_rules! math_fns {
($($arity:tt $sysname:tt $name:tt),* $(,)?) => { ($($arity:tt $sysname:tt $name:tt),* $(,)?) => {
$( $(
pub fn $name(vm: &mut Vm, args: FnArgs) -> Result<Value, Exception> { pub fn $name(_: &System, vm: &mut Vm, args: FnArgs) -> Result<Value, Exception> {
$arity(vm, args, $sysname, libm::$name) $arity(vm, args, $sysname, libm::$name)
} }
)* )*
@ -385,48 +390,48 @@ pub mod fns {
math1 "atanh" atanhf, math1 "atanh" atanhf,
} }
pub fn not(vm: &mut Vm, args: FnArgs) -> Result<Value, Exception> { pub fn not(_: &System, vm: &mut Vm, args: FnArgs) -> Result<Value, Exception> {
let value = args.get(vm, 0); let value = args.get(vm, 0);
Ok(Value::from(value.is_falsy())) Ok(Value::from(value.is_falsy()))
} }
pub fn eq(vm: &mut Vm, args: FnArgs) -> Result<Value, Exception> { pub fn eq(_: &System, vm: &mut Vm, args: FnArgs) -> Result<Value, Exception> {
let a = args.get(vm, 0); let a = args.get(vm, 0);
let b = args.get(vm, 1); let b = args.get(vm, 1);
Ok(Value::from(a == b)) Ok(Value::from(a == b))
} }
pub fn neq(vm: &mut Vm, args: FnArgs) -> Result<Value, Exception> { pub fn neq(_: &System, vm: &mut Vm, args: FnArgs) -> Result<Value, Exception> {
let a = args.get(vm, 0); let a = args.get(vm, 0);
let b = args.get(vm, 1); let b = args.get(vm, 1);
Ok(Value::from(a != b)) Ok(Value::from(a != b))
} }
pub fn lt(vm: &mut Vm, args: FnArgs) -> Result<Value, Exception> { pub fn lt(_: &System, vm: &mut Vm, args: FnArgs) -> Result<Value, Exception> {
let a = args.get(vm, 0); let a = args.get(vm, 0);
let b = args.get(vm, 1); let b = args.get(vm, 1);
Ok(Value::from(a < b)) Ok(Value::from(a < b))
} }
pub fn leq(vm: &mut Vm, args: FnArgs) -> Result<Value, Exception> { pub fn leq(_: &System, vm: &mut Vm, args: FnArgs) -> Result<Value, Exception> {
let a = args.get(vm, 0); let a = args.get(vm, 0);
let b = args.get(vm, 1); let b = args.get(vm, 1);
Ok(Value::from(a <= b)) Ok(Value::from(a <= b))
} }
pub fn gt(vm: &mut Vm, args: FnArgs) -> Result<Value, Exception> { pub fn gt(_: &System, vm: &mut Vm, args: FnArgs) -> Result<Value, Exception> {
let a = args.get(vm, 0); let a = args.get(vm, 0);
let b = args.get(vm, 1); let b = args.get(vm, 1);
Ok(Value::from(a > b)) Ok(Value::from(a > b))
} }
pub fn geq(vm: &mut Vm, args: FnArgs) -> Result<Value, Exception> { pub fn geq(_: &System, vm: &mut Vm, args: FnArgs) -> Result<Value, Exception> {
let a = args.get(vm, 0); let a = args.get(vm, 0);
let b = args.get(vm, 1); let b = args.get(vm, 1);
Ok(Value::from(a >= b)) Ok(Value::from(a >= b))
} }
pub fn vec(vm: &mut Vm, args: FnArgs) -> Result<Value, Exception> { pub fn vec(_: &System, vm: &mut Vm, args: FnArgs) -> Result<Value, Exception> {
static ERROR: &str = "arguments to `vec` must be numbers (vec x y z w)"; static ERROR: &str = "arguments to `vec` must be numbers (vec x y z w)";
match args.num() { match args.num() {
1 => { 1 => {
@ -465,7 +470,7 @@ pub mod fns {
} }
} }
pub fn vec_x(vm: &mut Vm, args: FnArgs) -> Result<Value, Exception> { pub fn vec_x(_: &System, vm: &mut Vm, args: FnArgs) -> Result<Value, Exception> {
if args.num() != 1 { if args.num() != 1 {
return Err(vm.create_exception("`vecX` expects a single argument (vecX vec)")); return Err(vm.create_exception("`vecX` expects a single argument (vecX vec)"));
} }
@ -474,7 +479,7 @@ pub mod fns {
Ok(Value::Number(vec.x)) Ok(Value::Number(vec.x))
} }
pub fn vec_y(vm: &mut Vm, args: FnArgs) -> Result<Value, Exception> { pub fn vec_y(_: &System, vm: &mut Vm, args: FnArgs) -> Result<Value, Exception> {
if args.num() != 1 { if args.num() != 1 {
return Err(vm.create_exception("`vecY` expects a single argument (vecY vec)")); return Err(vm.create_exception("`vecY` expects a single argument (vecY vec)"));
} }
@ -483,7 +488,7 @@ pub mod fns {
Ok(Value::Number(vec.y)) Ok(Value::Number(vec.y))
} }
pub fn vec_z(vm: &mut Vm, args: FnArgs) -> Result<Value, Exception> { pub fn vec_z(_: &System, vm: &mut Vm, args: FnArgs) -> Result<Value, Exception> {
if args.num() != 1 { if args.num() != 1 {
return Err(vm.create_exception("`vecZ` expects a single argument (vecZ vec)")); return Err(vm.create_exception("`vecZ` expects a single argument (vecZ vec)"));
} }
@ -492,7 +497,7 @@ pub mod fns {
Ok(Value::Number(vec.z)) Ok(Value::Number(vec.z))
} }
pub fn vec_w(vm: &mut Vm, args: FnArgs) -> Result<Value, Exception> { pub fn vec_w(_: &System, vm: &mut Vm, args: FnArgs) -> Result<Value, Exception> {
if args.num() != 1 { if args.num() != 1 {
return Err(vm.create_exception("`vecW` expects a single argument (vecW vec)")); return Err(vm.create_exception("`vecW` expects a single argument (vecW vec)"));
} }
@ -501,7 +506,7 @@ pub mod fns {
Ok(Value::Number(vec.w)) Ok(Value::Number(vec.w))
} }
pub fn rgba(vm: &mut Vm, args: FnArgs) -> Result<Value, Exception> { pub fn rgba(_: &System, vm: &mut Vm, args: FnArgs) -> Result<Value, Exception> {
if args.num() != 4 { if args.num() != 4 {
return Err(vm.create_exception("`rgba` expects four arguments (rgba r g b a)")); 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 })) Ok(Value::Rgba(Rgba { r, g, b, a }))
} }
pub fn rgba_r(vm: &mut Vm, args: FnArgs) -> Result<Value, Exception> { pub fn rgba_r(_: &System, vm: &mut Vm, args: FnArgs) -> Result<Value, Exception> {
if args.num() != 1 { if args.num() != 1 {
return Err(vm.create_exception("`rgbaR` expects a single argument (rgbaR rgba)")); return Err(vm.create_exception("`rgbaR` expects a single argument (rgbaR rgba)"));
} }
@ -524,7 +529,7 @@ pub mod fns {
Ok(Value::Number(rgba.r)) Ok(Value::Number(rgba.r))
} }
pub fn rgba_g(vm: &mut Vm, args: FnArgs) -> Result<Value, Exception> { pub fn rgba_g(_: &System, vm: &mut Vm, args: FnArgs) -> Result<Value, Exception> {
if args.num() != 1 { if args.num() != 1 {
return Err(vm.create_exception("`rgbaG` expects a single argument (rgbaG rgba)")); return Err(vm.create_exception("`rgbaG` expects a single argument (rgbaG rgba)"));
} }
@ -533,7 +538,7 @@ pub mod fns {
Ok(Value::Number(rgba.r)) Ok(Value::Number(rgba.r))
} }
pub fn rgba_b(vm: &mut Vm, args: FnArgs) -> Result<Value, Exception> { pub fn rgba_b(_: &System, vm: &mut Vm, args: FnArgs) -> Result<Value, Exception> {
if args.num() != 1 { if args.num() != 1 {
return Err(vm.create_exception("`rgbaB` expects a single argument (rgbaB rgba)")); return Err(vm.create_exception("`rgbaB` expects a single argument (rgbaB rgba)"));
} }
@ -542,7 +547,7 @@ pub mod fns {
Ok(Value::Number(rgba.r)) Ok(Value::Number(rgba.r))
} }
pub fn rgba_a(vm: &mut Vm, args: FnArgs) -> Result<Value, Exception> { pub fn rgba_a(_: &System, vm: &mut Vm, args: FnArgs) -> Result<Value, Exception> {
if args.num() != 1 { if args.num() != 1 {
return Err(vm.create_exception("`rgbaA` expects a single argument (rgbaA rgba)")); return Err(vm.create_exception("`rgbaA` expects a single argument (rgbaA rgba)"));
} }
@ -551,7 +556,7 @@ pub mod fns {
Ok(Value::Number(rgba.r)) Ok(Value::Number(rgba.r))
} }
pub fn len(vm: &mut Vm, args: FnArgs) -> Result<Value, Exception> { pub fn len(_: &System, vm: &mut Vm, args: FnArgs) -> Result<Value, Exception> {
if args.num() != 1 { if args.num() != 1 {
return Err(vm.create_exception("`len` expects a single argument (len list)")); 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)) Ok(Value::Number(list.elements.len() as f32))
} }
pub fn index(vm: &mut Vm, args: FnArgs) -> Result<Value, Exception> { pub fn index(_: &System, vm: &mut Vm, args: FnArgs) -> Result<Value, Exception> {
if args.num() != 2 { if args.num() != 2 {
return Err(vm.create_exception("`index` expects two arguments (index list i)")); 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<Value, Exception> {
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::<Value>(len)?;
(min..=max).map(|v| Value::Number(v as f32)).collect()
} else {
let len = (max..=min).count();
vm.consume_fuel(len)?;
vm.track_array::<Value>(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<Value, Exception> {
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::<Value>(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<Value, Exception> {
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::<Value>(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<Value, Exception> {
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<Value, Exception> {
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::<Value>(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::<Value>(1)?;
elements.push(value);
}
}
Ok(Value::Ref(vm.create_ref(Ref::List(List { elements }))?))
}
fn to_shape(value: Value, vm: &Vm) -> Option<Shape> { fn to_shape(value: Value, vm: &Vm) -> Option<Shape> {
match value { match value {
Value::Nil Value::Nil
@ -601,7 +774,7 @@ pub mod fns {
} }
} }
pub fn to_shape_f(vm: &mut Vm, args: FnArgs) -> Result<Value, Exception> { pub fn to_shape_f(_: &System, vm: &mut Vm, args: FnArgs) -> Result<Value, Exception> {
if args.num() != 1 { if args.num() != 1 {
return Err(vm.create_exception("`toShape` expects 1 argument (toShape value)")); 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<Value, Exception> { pub fn line(_: &System, vm: &mut Vm, args: FnArgs) -> Result<Value, Exception> {
if args.num() != 2 { if args.num() != 2 {
return Err(vm.create_exception("`line` expects 2 arguments (line start end)")); return Err(vm.create_exception("`line` expects 2 arguments (line start end)"));
} }
@ -627,7 +800,7 @@ pub mod fns {
Ok(Value::Ref(id)) Ok(Value::Ref(id))
} }
pub fn rect(vm: &mut Vm, args: FnArgs) -> Result<Value, Exception> { pub fn rect(_: &System, vm: &mut Vm, args: FnArgs) -> Result<Value, Exception> {
static ARGS2: &str = "arguments to 2-argument `rect` must be `vec`"; static ARGS2: &str = "arguments to 2-argument `rect` must be `vec`";
static ARGS4: &str = "arguments to 4-argument `rect` must be numbers"; static ARGS4: &str = "arguments to 4-argument `rect` must be numbers";
@ -650,7 +823,7 @@ pub mod fns {
Ok(Value::Ref(id)) Ok(Value::Ref(id))
} }
pub fn circle(vm: &mut Vm, args: FnArgs) -> Result<Value, Exception> { pub fn circle(_: &System, vm: &mut Vm, args: FnArgs) -> Result<Value, Exception> {
static ARGS2: &str = "arguments to 2-argument `circle` must be `vec` and a number"; 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"; static ARGS3: &str = "arguments to 3-argument `circle` must be numbers";
@ -670,7 +843,7 @@ pub mod fns {
Ok(Value::Ref(id)) Ok(Value::Ref(id))
} }
pub fn stroke(vm: &mut Vm, args: FnArgs) -> Result<Value, Exception> { pub fn stroke(_: &System, vm: &mut Vm, args: FnArgs) -> Result<Value, Exception> {
if args.num() != 3 { if args.num() != 3 {
return Err( return Err(
vm.create_exception("`stroke` expects 3 arguments (stroke thickness color shape)") 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<Value, Exception> { pub fn fill(_: &System, vm: &mut Vm, args: FnArgs) -> Result<Value, Exception> {
if args.num() != 2 { if args.num() != 2 {
return Err(vm.create_exception("`fill` expects 2 arguments (fill color shape)")); 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<Value, Exception> { pub fn with_dotter(_: &System, vm: &mut Vm, args: FnArgs) -> Result<Value, Exception> {
if args.num() != 1 { if args.num() != 1 {
return Err(vm.create_exception( return Err(vm.create_exception(
"`withDotter` expects a single argument (withDotter \\d -> [])", "`withDotter` expects a single argument (withDotter \\d -> [])",
)); ));
} }
let draw = args.get_closure(vm, 0, "argument to `withDotter` must be a closure")?; _ = args.get_closure(
if draw.param_count != 1 { vm,
return Err(vm.create_exception("function passed to `withDotter` must take in a single parameter (withDotter \\d -> [])")); 0,
} 1,
"argument to `withDotter` must be a single-parameter function (withDotter \\d -> [])",
)?;
let id = vm.create_ref(Ref::Reticle(Reticle::Dotter { let id = vm.create_ref(Ref::Reticle(Reticle::Dotter {
draw: args.get(vm, 0), draw: args.get(vm, 0),

View file

@ -47,6 +47,13 @@ impl Value {
_ => None, _ => None,
} }
} }
pub fn to_ref(&self) -> Option<RefId> {
match self {
Self::Ref(v) => Some(*v),
_ => None,
}
}
} }
impl From<()> for Value { impl From<()> for Value {

View file

@ -89,6 +89,15 @@ impl Vm {
self.fuel = fuel; 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 { pub fn image(&self) -> VmImage {
assert!( assert!(
self.stack.is_empty() && self.call_stack.is_empty(), self.stack.is_empty() && self.call_stack.is_empty(),
@ -197,6 +206,8 @@ impl Vm {
.as_closure() .as_closure()
.expect("a Closure-type Ref must be passed to `run`"); .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_id = closure.start.chunk_id;
let mut chunk = system.chunk(chunk_id); let mut chunk = system.chunk(chunk_id);
let mut pc = closure.start.offset as usize; let mut pc = closure.start.offset as usize;
@ -216,6 +227,7 @@ impl Vm {
#[allow(unused)] #[allow(unused)]
let closure = (); // Do not use `closure` after this! Use `get_ref` on `closure_id` instead. 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 { self.push_call(CallFrame {
closure_id, closure_id,
chunk_id, chunk_id,
@ -223,6 +235,8 @@ impl Vm {
bottom, bottom,
})?; })?;
vmtrace!("LOOP");
loop { loop {
fuel = fuel fuel = fuel
.checked_sub(1) .checked_sub(1)
@ -231,7 +245,7 @@ impl Vm {
#[allow(unused)] #[allow(unused)]
let pc2 = pc; let pc2 = pc;
let opcode = chunk.read_opcode(&mut pc)?; let opcode = chunk.read_opcode(&mut pc)?;
vmtrace!("{pc2:2} {opcode:?}"); vmtrace!("insn {pc2:2} {opcode:?}");
match opcode { match opcode {
Opcode::Nil => self.push(Value::Nil)?, Opcode::Nil => self.push(Value::Nil)?,
Opcode::False => self.push(Value::False)?, Opcode::False => self.push(Value::False)?,
@ -300,6 +314,7 @@ impl Vm {
Opcode::List => { Opcode::List => {
let len = chunk.read_u16(&mut pc)? as usize; let len = chunk.read_u16(&mut pc)? as usize;
self.track_array::<Value>(len)?;
let bottom = self.stack.len().checked_sub(len).ok_or_else(|| { let bottom = self.stack.len().checked_sub(len).ok_or_else(|| {
self.create_exception( self.create_exception(
"corrupted bytecode (list has more elements than stack)", "corrupted bytecode (list has more elements than stack)",
@ -307,7 +322,6 @@ impl Vm {
})?; })?;
let elements = self.stack[bottom..].to_vec(); let elements = self.stack[bottom..].to_vec();
self.stack.resize_with(bottom, || unreachable!()); self.stack.resize_with(bottom, || unreachable!());
self.track_array(&elements)?;
let id = self.create_ref(Ref::List(List { elements }))?; let id = self.create_ref(Ref::List(List { elements }))?;
self.push(Value::Ref(id))?; self.push(Value::Ref(id))?;
} }
@ -349,7 +363,7 @@ impl Vm {
local_count, local_count,
captures, captures,
}; };
vmtrace!("{closure:?}"); vmtrace!(" : {closure:?}");
let id = self.create_ref(Ref::Closure(closure))?; let id = self.create_ref(Ref::Closure(closure))?;
self.push(Value::Ref(id))?; self.push(Value::Ref(id))?;
} }
@ -449,6 +463,7 @@ impl Vm {
self.store_context(Context { fuel }); self.store_context(Context { fuel });
let result = system_fn( let result = system_fn(
system,
self, self,
FnArgs { FnArgs {
base: self base: self
@ -475,7 +490,8 @@ impl Vm {
self.push(value)?; self.push(value)?;
// Once the initial frame is popped, halt the VM. // 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 }); self.store_context(Context { fuel });
break; break;
} }
@ -491,12 +507,15 @@ impl Vm {
} }
} }
vmtrace!("EXIT");
let result = self let result = self
.stack
.pop() .pop()
.expect("there should be a result at the top of the stack"); .expect("there should be a result at the top of the stack");
self.stack.resize_with(init_bottom, || unreachable!()); self.stack.resize_with(init_bottom, || unreachable!());
vmtrace!("result = {result:?}");
Ok(result) Ok(result)
} }
@ -535,10 +554,12 @@ impl Vm {
} }
} }
pub fn track_array<T>(&mut self, array: &[T]) -> Result<(), Exception> { pub fn track_array<T>(&mut self, len: usize) -> Result<(), Exception> {
let layout = core::alloc::Layout::array::<T>(len)
.map_err(|_| self.create_exception("out of heap memory"))?;
self.memory = self self.memory = self
.memory .memory
.checked_sub(core::mem::size_of_val(array)) .checked_sub(layout.size())
.ok_or_else(|| self.create_exception("out of heap memory"))?; .ok_or_else(|| self.create_exception("out of heap memory"))?;
Ok(()) Ok(())
} }
@ -611,16 +632,20 @@ impl FnArgs {
&self, &self,
vm: &'vm Vm, vm: &'vm Vm,
index: usize, index: usize,
param_count: u8,
message: &'static str, message: &'static str,
) -> Result<&'vm Closure, Exception> { ) -> Result<(RefId, &'vm Closure), Exception> {
let value = self.get(vm, index); let value = self.get(vm, index);
let (_, any_ref) = vm let (ref_id, any_ref) = vm
.get_ref_value(value) .get_ref_value(value)
.ok_or_else(|| vm.create_exception(message))?; .ok_or_else(|| vm.create_exception(message))?;
let Ref::Closure(closure) = any_ref else { let Ref::Closure(closure) = any_ref else {
return Err(vm.create_exception(message)); 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)] #[inline(never)]

View file

@ -624,6 +624,93 @@ The first element is located at index 0.
Indexing out of bounds is an error. 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 ## Shapes