2024-08-10 23:10:03 +02:00
|
|
|
use core::{
|
|
|
|
error::Error,
|
|
|
|
fmt::{self, Display},
|
|
|
|
};
|
|
|
|
|
|
|
|
use alloc::vec::Vec;
|
|
|
|
|
|
|
|
use crate::{
|
2024-09-08 13:53:29 +02:00
|
|
|
bytecode::{Chunk, EmitError, Offset, Opcode, TagId},
|
|
|
|
value::{BytecodeLoc, Closure, FunctionName, Value, Vec4},
|
2024-08-10 23:10:03 +02:00
|
|
|
vm::{Exception, FnArgs, Vm},
|
|
|
|
};
|
|
|
|
|
2025-05-27 20:20:10 +02:00
|
|
|
pub type SystemFn = fn(&System, &mut Vm, FnArgs) -> Result<Value, Exception>;
|
2024-08-10 23:10:03 +02:00
|
|
|
|
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
|
|
pub struct ChunkId(u32);
|
|
|
|
|
2024-08-27 20:43:14 +02:00
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
|
|
pub enum SystemFnArity {
|
|
|
|
Unary,
|
|
|
|
Binary,
|
|
|
|
Nary,
|
|
|
|
}
|
|
|
|
|
2024-08-10 23:10:03 +02:00
|
|
|
#[derive(Debug, Clone)]
|
|
|
|
pub struct System {
|
|
|
|
/// Resolves a system function name to an index into `fn`s.
|
2024-08-27 20:43:14 +02:00
|
|
|
pub resolve_fn: fn(SystemFnArity, &str) -> Option<u8>,
|
2024-08-10 23:10:03 +02:00
|
|
|
pub fns: [Option<SystemFn>; 256],
|
|
|
|
pub chunks: Vec<Chunk>,
|
2024-09-08 13:53:29 +02:00
|
|
|
structs_chunk_offsets: StructsChunkOffsets,
|
2024-08-10 23:10:03 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug, Clone, Copy)]
|
|
|
|
pub struct SystemImage {
|
|
|
|
chunks: usize,
|
|
|
|
}
|
|
|
|
|
2024-09-08 13:53:29 +02:00
|
|
|
#[derive(Debug, Clone)]
|
|
|
|
struct StructsChunkOffsets {
|
|
|
|
dotter: BytecodeLoc,
|
2024-08-10 23:10:03 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
impl System {
|
|
|
|
pub fn new(max_chunks: usize) -> Self {
|
2024-09-08 13:53:29 +02:00
|
|
|
assert!(
|
|
|
|
max_chunks > 1,
|
|
|
|
"the 0th chunk is allocated for internal purposes; therefore there must be more than one chunk to execute bytecode"
|
|
|
|
);
|
2024-08-10 23:10:03 +02:00
|
|
|
assert!(max_chunks < u32::MAX as usize);
|
|
|
|
|
2024-09-08 13:53:29 +02:00
|
|
|
let (structs_chunk, structs_chunk_offsets) = Self::structs_chunk().unwrap();
|
|
|
|
|
2024-08-10 23:10:03 +02:00
|
|
|
let mut system = Self {
|
|
|
|
resolve_fn: Self::resolve,
|
|
|
|
fns: [None; 256],
|
|
|
|
chunks: Vec::with_capacity(max_chunks),
|
2024-09-08 13:53:29 +02:00
|
|
|
structs_chunk_offsets,
|
2024-08-10 23:10:03 +02:00
|
|
|
};
|
2024-09-08 13:53:29 +02:00
|
|
|
system.chunks.push(structs_chunk);
|
2024-08-10 23:10:03 +02:00
|
|
|
Self::init_fns(&mut system);
|
|
|
|
system
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn add_chunk(&mut self, chunk: Chunk) -> Result<ChunkId, ChunkError> {
|
|
|
|
if self.chunks.len() >= self.chunks.capacity() {
|
|
|
|
return Err(ChunkError);
|
|
|
|
}
|
|
|
|
|
|
|
|
let id = ChunkId(self.chunks.len() as u32);
|
|
|
|
self.chunks.push(chunk);
|
|
|
|
Ok(id)
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn chunk(&self, id: ChunkId) -> &Chunk {
|
|
|
|
&self.chunks[id.0 as usize]
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn image(&self) -> SystemImage {
|
|
|
|
SystemImage {
|
|
|
|
chunks: self.chunks.len(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn restore_image(&mut self, image: &SystemImage) {
|
|
|
|
self.chunks.resize_with(image.chunks, || {
|
|
|
|
panic!("image must be a subset of the current system")
|
|
|
|
});
|
|
|
|
}
|
2024-09-08 13:53:29 +02:00
|
|
|
|
|
|
|
// The structs chunk contains bytecode for _struct functions_.
|
|
|
|
//
|
|
|
|
// Struct functions are a way of encoding structures with arbitrary named data fields.
|
|
|
|
// They are called like regular functions, except they always expect a single tag-type
|
|
|
|
// argument. They're used where performance is not a primary concern---in structures that appear
|
|
|
|
// once or a couple times throughout the lifetime of a program, in which convenient,
|
|
|
|
// backwards-compatible field access is a priority.
|
|
|
|
//
|
|
|
|
// Each struct function has only two opcodes: `Field`, followed by a `Return`.
|
|
|
|
// The `Field` opcode is an efficient way of encoding an if chain made solely out of tag
|
|
|
|
// comparisons, returning a closure capture (or error if there's no field with the given name.)
|
|
|
|
//
|
|
|
|
// if (tag == A) capture_0
|
|
|
|
// else if (tag == B) capture_1
|
|
|
|
// else if (tag == C) capture_2
|
|
|
|
// else error
|
|
|
|
//
|
|
|
|
// Closure captures are used here, because they're a convenient way of attaching indexed data
|
|
|
|
// to any function, even created outside the language itself.
|
|
|
|
//
|
|
|
|
// All of this results in a function that can be called like `d From` to obtain a piece of data
|
|
|
|
// stored inside of the `d` structure.
|
|
|
|
fn structs_chunk() -> Result<(Chunk, StructsChunkOffsets), EmitError> {
|
|
|
|
let mut chunk = Chunk::new(128).unwrap();
|
|
|
|
|
|
|
|
let dotter = chunk.offset();
|
|
|
|
chunk.emit_opcode(Opcode::Field)?;
|
|
|
|
chunk.emit_u8(3)?;
|
|
|
|
chunk.emit_u16(TagId::From.to_u16())?;
|
|
|
|
chunk.emit_u16(TagId::To.to_u16())?;
|
|
|
|
chunk.emit_u16(TagId::Num.to_u16())?;
|
|
|
|
chunk.emit_opcode(Opcode::Return)?;
|
|
|
|
|
|
|
|
fn loc(offset: Offset) -> BytecodeLoc {
|
|
|
|
BytecodeLoc {
|
|
|
|
chunk_id: ChunkId(0),
|
|
|
|
offset: offset.to_u16(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok((
|
|
|
|
chunk,
|
|
|
|
StructsChunkOffsets {
|
|
|
|
dotter: loc(dotter),
|
|
|
|
},
|
|
|
|
))
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn create_dotter(&self, from: Vec4, to: Vec4, num: f32) -> Closure {
|
|
|
|
Closure {
|
|
|
|
start: self.structs_chunk_offsets.dotter,
|
|
|
|
name: FunctionName::Anonymous,
|
|
|
|
param_count: 1,
|
|
|
|
local_count: 0,
|
|
|
|
captures: Vec::from_iter([Value::Vec4(from), Value::Vec4(to), Value::Number(num)]),
|
|
|
|
}
|
|
|
|
}
|
2024-08-10 23:10:03 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
|
|
pub struct ChunkError;
|
|
|
|
|
|
|
|
impl Display for ChunkError {
|
|
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
|
|
f.write_str("too many chunks")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Error for ChunkError {}
|
|
|
|
|
2024-09-08 13:53:29 +02:00
|
|
|
macro_rules! def_fns {
|
|
|
|
($($index:tt $arity:tt $name:tt => $fnref:expr),* $(,)?) => {
|
|
|
|
pub(crate) fn init_fns(system: &mut System) {
|
|
|
|
$(
|
|
|
|
debug_assert!(system.fns[$index].is_none());
|
|
|
|
system.fns[$index] = Some($fnref);
|
|
|
|
)*
|
|
|
|
}
|
|
|
|
|
|
|
|
pub(crate) fn resolve(arity: SystemFnArity, name: &str) -> Option<u8> {
|
|
|
|
match (arity, name){
|
|
|
|
$((SystemFnArity::$arity, $name) => Some($index),)*
|
|
|
|
_ => None,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2024-08-10 23:10:03 +02:00
|
|
|
pub mod fns {
|
2024-10-22 21:58:17 +02:00
|
|
|
use core::ops::{Add, Div, Mul, Sub};
|
|
|
|
|
2024-09-07 14:30:58 +02:00
|
|
|
use alloc::{format, vec::Vec};
|
2024-08-20 23:00:39 +02:00
|
|
|
|
2024-08-10 23:10:03 +02:00
|
|
|
use crate::{
|
2024-09-08 13:53:29 +02:00
|
|
|
value::{Fill, List, Ref, Reticle, Rgba, Scribble, Shape, Stroke, Value, Vec2, Vec4},
|
2024-08-10 23:10:03 +02:00
|
|
|
vm::{Exception, FnArgs, Vm},
|
|
|
|
};
|
|
|
|
|
2024-08-27 20:43:14 +02:00
|
|
|
use super::{System, SystemFnArity};
|
2024-08-10 23:10:03 +02:00
|
|
|
|
|
|
|
impl System {
|
|
|
|
def_fns! {
|
2024-08-27 20:43:14 +02:00
|
|
|
0x00 Binary "+" => add,
|
|
|
|
0x01 Binary "-" => sub,
|
|
|
|
0x02 Binary "*" => mul,
|
|
|
|
0x03 Binary "/" => div,
|
|
|
|
0x04 Unary "-" => neg,
|
|
|
|
|
2024-09-07 14:30:58 +02:00
|
|
|
0x10 Nary "floor" => floorf,
|
|
|
|
0x11 Nary "ceil" => ceilf,
|
|
|
|
0x12 Nary "round" => roundf,
|
|
|
|
0x13 Nary "abs" => fabsf,
|
|
|
|
0x14 Nary "mod" => fmodf,
|
|
|
|
0x15 Nary "pow" => powf,
|
|
|
|
0x16 Nary "sqrt" => sqrtf,
|
|
|
|
0x17 Nary "cbrt" => cbrtf,
|
|
|
|
0x18 Nary "exp" => expf,
|
|
|
|
0x19 Nary "exp2" => exp2f,
|
2025-06-01 23:13:34 +02:00
|
|
|
0x1a Nary "ln" => logf,
|
|
|
|
0x1b Nary "log2" => log2f,
|
|
|
|
0x1c Nary "log10" => log10f,
|
|
|
|
0x1d Nary "hypot" => hypotf,
|
|
|
|
0x1e Nary "sin" => sinf,
|
|
|
|
0x1f Nary "cos" => cosf,
|
2024-09-07 14:30:58 +02:00
|
|
|
0x20 Nary "tan" => tanf,
|
|
|
|
0x21 Nary "asin" => asinf,
|
|
|
|
0x22 Nary "acos" => acosf,
|
|
|
|
0x23 Nary "atan" => atanf,
|
|
|
|
0x24 Nary "atan2" => atan2f,
|
|
|
|
0x25 Nary "expMinus1" => expm1f,
|
|
|
|
0x26 Nary "ln1Plus" => log1pf,
|
|
|
|
0x27 Nary "sinh" => sinhf,
|
|
|
|
0x28 Nary "cosh" => coshf,
|
|
|
|
0x29 Nary "tanh" => tanhf,
|
2025-06-01 23:13:34 +02:00
|
|
|
0x2a Nary "asinh" => asinhf,
|
|
|
|
0x2b Nary "acosh" => acoshf,
|
|
|
|
0x2c Nary "atanh" => atanhf,
|
|
|
|
0x2d Nary "min" => min,
|
|
|
|
0x2e Nary "max" => max,
|
|
|
|
0x30 Nary "lerp" => lerp_f,
|
2024-09-07 14:30:58 +02:00
|
|
|
|
2024-08-27 20:43:14 +02:00
|
|
|
0x40 Unary "!" => not,
|
|
|
|
0x41 Binary "==" => eq,
|
|
|
|
0x42 Binary "!=" => neq,
|
|
|
|
0x43 Binary "<" => lt,
|
|
|
|
0x44 Binary "<=" => leq,
|
|
|
|
0x45 Binary ">" => gt,
|
|
|
|
0x46 Binary ">=" => geq,
|
|
|
|
|
|
|
|
0x80 Nary "vec" => vec,
|
|
|
|
0x81 Nary "vecX" => vec_x,
|
|
|
|
0x82 Nary "vecY" => vec_y,
|
|
|
|
0x83 Nary "vecZ" => vec_z,
|
|
|
|
0x84 Nary "vecW" => vec_w,
|
|
|
|
|
|
|
|
0x85 Nary "rgba" => rgba,
|
|
|
|
0x86 Nary "rgbaR" => rgba_r,
|
|
|
|
0x87 Nary "rgbaG" => rgba_g,
|
|
|
|
0x88 Nary "rgbaB" => rgba_b,
|
|
|
|
0x89 Nary "rgbaA" => rgba_a,
|
|
|
|
|
2024-10-25 23:21:28 +02:00
|
|
|
0x90 Nary "len" => len,
|
|
|
|
0x91 Nary "index" => index,
|
2025-05-27 20:20:10 +02:00
|
|
|
0x92 Nary "range" => range,
|
|
|
|
0x93 Nary "map" => map,
|
|
|
|
0x94 Nary "filter" => filter,
|
|
|
|
0x95 Nary "reduce" => reduce,
|
|
|
|
0x96 Nary "flatten" => flatten,
|
2024-08-27 20:43:14 +02:00
|
|
|
|
|
|
|
0xc0 Nary "toShape" => to_shape_f,
|
|
|
|
0xc1 Nary "line" => line,
|
|
|
|
0xc2 Nary "rect" => rect,
|
|
|
|
0xc3 Nary "circle" => circle,
|
2024-09-08 13:53:29 +02:00
|
|
|
|
2024-08-27 20:43:14 +02:00
|
|
|
0xe0 Nary "stroke" => stroke,
|
|
|
|
0xe1 Nary "fill" => fill,
|
2024-09-08 13:53:29 +02:00
|
|
|
|
|
|
|
0xf0 Nary "withDotter" => with_dotter,
|
2024-08-10 23:10:03 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-10-22 21:58:17 +02:00
|
|
|
fn binary_math_op(
|
|
|
|
vm: &mut Vm,
|
|
|
|
args: FnArgs,
|
|
|
|
on_number_number: fn(f32, f32) -> f32,
|
|
|
|
on_vec_vec: fn(Vec4, Vec4) -> Vec4,
|
|
|
|
) -> Result<Value, Exception> {
|
|
|
|
static WRONG_ARG: &str = "arguments to binary math operators may be numbers, vecs, or rgba, and must be of the same type";
|
|
|
|
match args.get(vm, 0) {
|
|
|
|
Value::Number(x) => Ok(Value::Number(on_number_number(
|
|
|
|
x,
|
|
|
|
args.get_number(vm, 1, WRONG_ARG)?,
|
|
|
|
))),
|
|
|
|
Value::Vec4(x) => Ok(Value::Vec4(on_vec_vec(x, args.get_vec4(vm, 1, WRONG_ARG)?))),
|
|
|
|
Value::Rgba(x) => Ok(Value::Rgba(
|
|
|
|
on_vec_vec(x.into(), args.get_rgba(vm, 1, WRONG_ARG)?.into()).into(),
|
|
|
|
)),
|
|
|
|
_ => Err(vm.create_exception(WRONG_ARG)),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-05-27 20:20:10 +02:00
|
|
|
pub fn add(_: &System, vm: &mut Vm, args: FnArgs) -> Result<Value, Exception> {
|
2024-10-22 21:58:17 +02:00
|
|
|
binary_math_op(vm, args, <f32 as Add>::add, <Vec4 as Add>::add)
|
2024-08-10 23:10:03 +02:00
|
|
|
}
|
|
|
|
|
2025-05-27 20:20:10 +02:00
|
|
|
pub fn sub(_: &System, vm: &mut Vm, args: FnArgs) -> Result<Value, Exception> {
|
2024-10-22 21:58:17 +02:00
|
|
|
binary_math_op(vm, args, <f32 as Sub>::sub, <Vec4 as Sub>::sub)
|
2024-08-10 23:10:03 +02:00
|
|
|
}
|
|
|
|
|
2025-05-27 20:20:10 +02:00
|
|
|
pub fn mul(_: &System, vm: &mut Vm, args: FnArgs) -> Result<Value, Exception> {
|
2024-10-22 21:58:17 +02:00
|
|
|
binary_math_op(vm, args, <f32 as Mul>::mul, <Vec4 as Mul>::mul)
|
2024-08-10 23:10:03 +02:00
|
|
|
}
|
|
|
|
|
2025-05-27 20:20:10 +02:00
|
|
|
pub fn div(_: &System, vm: &mut Vm, args: FnArgs) -> Result<Value, Exception> {
|
2024-10-22 21:58:17 +02:00
|
|
|
binary_math_op(vm, args, <f32 as Div>::div, <Vec4 as Div>::div)
|
2024-08-10 23:10:03 +02:00
|
|
|
}
|
|
|
|
|
2025-05-27 20:20:10 +02:00
|
|
|
pub fn neg(_: &System, vm: &mut Vm, args: FnArgs) -> Result<Value, Exception> {
|
2024-10-22 21:58:17 +02:00
|
|
|
match args.get(vm, 0) {
|
|
|
|
Value::Number(x) => Ok(Value::Number(-x)),
|
|
|
|
Value::Vec4(x) => Ok(Value::Vec4(-x)),
|
|
|
|
_ => Err(vm.create_exception("`-` can only work with numbers and vectors")),
|
|
|
|
}
|
2024-08-27 20:43:14 +02:00
|
|
|
}
|
|
|
|
|
2024-09-07 14:30:58 +02:00
|
|
|
#[inline(never)]
|
|
|
|
fn math1(vm: &mut Vm, args: FnArgs, name: &str, f: fn(f32) -> f32) -> Result<Value, Exception> {
|
|
|
|
if args.num() != 1 {
|
|
|
|
return Err(
|
|
|
|
vm.create_exception(format!("`{name}` expects a single argument ({name} x)"))
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
let x = args
|
|
|
|
.get(vm, 0)
|
|
|
|
.to_number()
|
|
|
|
.ok_or_else(|| vm.create_exception(format!("`{name}` argument must be a number")))?;
|
|
|
|
Ok(Value::Number(f(x)))
|
|
|
|
}
|
|
|
|
|
|
|
|
#[inline(never)]
|
|
|
|
fn math2(
|
|
|
|
vm: &mut Vm,
|
|
|
|
args: FnArgs,
|
|
|
|
name: &str,
|
|
|
|
f: fn(f32, f32) -> f32,
|
|
|
|
) -> Result<Value, Exception> {
|
|
|
|
if args.num() != 2 {
|
|
|
|
return Err(vm.create_exception(format!("`{name}` expects two arguments ({name} x y)")));
|
|
|
|
}
|
|
|
|
|
|
|
|
let x = args
|
|
|
|
.get(vm, 0)
|
|
|
|
.to_number()
|
|
|
|
.ok_or_else(|| vm.create_exception(format!("`{name}` arguments must be numbers")))?;
|
|
|
|
let y = args
|
|
|
|
.get(vm, 1)
|
|
|
|
.to_number()
|
|
|
|
.ok_or_else(|| vm.create_exception(format!("`{name}` arguments must be numbers")))?;
|
|
|
|
Ok(Value::Number(f(x, y)))
|
|
|
|
}
|
|
|
|
|
|
|
|
macro_rules! math_fns {
|
|
|
|
($($arity:tt $sysname:tt $name:tt),* $(,)?) => {
|
|
|
|
$(
|
2025-05-27 20:20:10 +02:00
|
|
|
pub fn $name(_: &System, vm: &mut Vm, args: FnArgs) -> Result<Value, Exception> {
|
2024-09-07 14:30:58 +02:00
|
|
|
$arity(vm, args, $sysname, libm::$name)
|
|
|
|
}
|
|
|
|
)*
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
math_fns! {
|
|
|
|
math1 "floor" floorf,
|
|
|
|
math1 "ceil" ceilf,
|
|
|
|
math1 "round" roundf,
|
|
|
|
math1 "abs" fabsf,
|
|
|
|
math2 "mod" fmodf,
|
|
|
|
math2 "pow" powf,
|
|
|
|
math1 "sqrt" sqrtf,
|
|
|
|
math1 "cbrt" cbrtf,
|
|
|
|
math1 "exp" expf,
|
2024-09-07 15:39:29 +02:00
|
|
|
math1 "expMinus1" expm1f,
|
2024-09-07 14:30:58 +02:00
|
|
|
math1 "exp2" exp2f,
|
|
|
|
math1 "ln" logf,
|
2024-09-07 15:39:29 +02:00
|
|
|
math1 "ln1Plus" log1pf,
|
2024-09-07 14:30:58 +02:00
|
|
|
math1 "log2" log2f,
|
|
|
|
math1 "log10" log10f,
|
|
|
|
math2 "hypot" hypotf,
|
|
|
|
math1 "sin" sinf,
|
|
|
|
math1 "cos" cosf,
|
|
|
|
math1 "tan" tanf,
|
|
|
|
math1 "asin" asinf,
|
|
|
|
math1 "acos" acosf,
|
|
|
|
math1 "atan" atanf,
|
|
|
|
math2 "atan2" atan2f,
|
|
|
|
math1 "sinh" sinhf,
|
|
|
|
math1 "cosh" coshf,
|
|
|
|
math1 "tanh" tanhf,
|
|
|
|
math1 "asinh" asinhf,
|
|
|
|
math1 "acosh" acoshf,
|
|
|
|
math1 "atanh" atanhf,
|
|
|
|
}
|
|
|
|
|
2025-06-01 23:13:34 +02:00
|
|
|
pub fn min(_: &System, vm: &mut Vm, args: FnArgs) -> Result<Value, Exception> {
|
|
|
|
if args.num() != 2 {
|
|
|
|
return Err(vm.create_exception("`min` expects 2 arguments (min a b)"));
|
|
|
|
}
|
|
|
|
|
|
|
|
let a = args.get(vm, 0);
|
|
|
|
let b = args.get(vm, 1);
|
|
|
|
if !a.is_same_type_as(&b) {
|
|
|
|
return Err(vm.create_exception("arguments to `min` must have the same type"));
|
|
|
|
}
|
|
|
|
|
|
|
|
match a {
|
|
|
|
Value::Nil | Value::False | Value::True | Value::Tag(_) | Value::Number(_) => {
|
|
|
|
Ok(if a < b { a } else { b })
|
|
|
|
}
|
|
|
|
Value::Vec4(a) => {
|
|
|
|
let b = b.to_vec4().unwrap();
|
|
|
|
Ok(Value::Vec4(Vec4 {
|
|
|
|
x: a.x.min(b.x),
|
|
|
|
y: a.y.min(b.y),
|
|
|
|
z: a.z.min(b.z),
|
|
|
|
w: a.w.min(b.w),
|
|
|
|
}))
|
|
|
|
}
|
|
|
|
Value::Rgba(a) => {
|
|
|
|
let b = b.to_rgba().unwrap();
|
|
|
|
Ok(Value::Rgba(Rgba {
|
|
|
|
r: a.r.min(b.r),
|
|
|
|
g: a.g.min(b.g),
|
|
|
|
b: a.b.min(b.b),
|
|
|
|
a: a.a.min(b.a),
|
|
|
|
}))
|
|
|
|
}
|
|
|
|
Value::Ref(_) => {
|
|
|
|
Err(vm.create_exception("arguments passed to `min` cannot be compared"))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn max(_: &System, vm: &mut Vm, args: FnArgs) -> Result<Value, Exception> {
|
|
|
|
if args.num() != 2 {
|
|
|
|
return Err(vm.create_exception("`max` expects 2 arguments (max a b)"));
|
|
|
|
}
|
|
|
|
|
|
|
|
let a = args.get(vm, 0);
|
|
|
|
let b = args.get(vm, 1);
|
|
|
|
if !a.is_same_type_as(&b) {
|
|
|
|
return Err(vm.create_exception("arguments to `max` must have the same type"));
|
|
|
|
}
|
|
|
|
|
|
|
|
match a {
|
|
|
|
Value::Nil | Value::False | Value::True | Value::Tag(_) | Value::Number(_) => {
|
|
|
|
Ok(if a < b { a } else { b })
|
|
|
|
}
|
|
|
|
Value::Vec4(a) => {
|
|
|
|
let b = b.to_vec4().unwrap();
|
|
|
|
Ok(Value::Vec4(Vec4 {
|
|
|
|
x: a.x.max(b.x),
|
|
|
|
y: a.y.max(b.y),
|
|
|
|
z: a.z.max(b.z),
|
|
|
|
w: a.w.max(b.w),
|
|
|
|
}))
|
|
|
|
}
|
|
|
|
Value::Rgba(a) => {
|
|
|
|
let b = b.to_rgba().unwrap();
|
|
|
|
Ok(Value::Rgba(Rgba {
|
|
|
|
r: a.r.max(b.r),
|
|
|
|
g: a.g.max(b.g),
|
|
|
|
b: a.b.max(b.b),
|
|
|
|
a: a.a.max(b.a),
|
|
|
|
}))
|
|
|
|
}
|
|
|
|
Value::Ref(_) => {
|
|
|
|
Err(vm.create_exception("arguments passed to `max` cannot be compared"))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn lerp(a: f32, b: f32, t: f32) -> f32 {
|
|
|
|
a + (b - a) * t
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn lerp_f(_: &System, vm: &mut Vm, args: FnArgs) -> Result<Value, Exception> {
|
|
|
|
if args.num() != 3 {
|
|
|
|
return Err(vm.create_exception("`lerp` expects 3 arguments (max a b)"));
|
|
|
|
}
|
|
|
|
|
|
|
|
let a = args.get(vm, 0);
|
|
|
|
let b = args.get(vm, 1);
|
|
|
|
if !a.is_same_type_as(&b) {
|
|
|
|
return Err(
|
|
|
|
vm.create_exception("1st and 2nd arguments to `lerp` must have the same type")
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
let t = args.get_number(vm, 2, "3rd argument to `lerp` must be a number")?;
|
|
|
|
|
|
|
|
match a {
|
|
|
|
Value::Number(a) => {
|
|
|
|
let b = b.to_number().unwrap();
|
|
|
|
Ok(Value::Number(lerp(a, b, t)))
|
|
|
|
}
|
|
|
|
Value::Vec4(a) => {
|
|
|
|
let b = b.to_vec4().unwrap();
|
|
|
|
Ok(Value::Vec4(Vec4 {
|
|
|
|
x: lerp(a.x, b.x, t),
|
|
|
|
y: lerp(a.y, b.y, t),
|
|
|
|
z: lerp(a.z, b.z, t),
|
|
|
|
w: lerp(a.w, b.w, t),
|
|
|
|
}))
|
|
|
|
}
|
|
|
|
Value::Rgba(a) => {
|
|
|
|
let b = b.to_rgba().unwrap();
|
|
|
|
Ok(Value::Rgba(Rgba {
|
|
|
|
r: lerp(a.r, b.r, t),
|
|
|
|
g: lerp(a.g, b.g, t),
|
|
|
|
b: lerp(a.b, b.b, t),
|
|
|
|
a: lerp(a.a, b.a, t),
|
|
|
|
}))
|
|
|
|
}
|
|
|
|
Value::Nil | Value::False | Value::True | Value::Tag(_) | Value::Ref(_) => {
|
|
|
|
Err(vm
|
|
|
|
.create_exception("arguments passed to `lerp` cannot be linearly interpolated"))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-05-27 20:20:10 +02:00
|
|
|
pub fn not(_: &System, vm: &mut Vm, args: FnArgs) -> Result<Value, Exception> {
|
2024-08-10 23:10:03 +02:00
|
|
|
let value = args.get(vm, 0);
|
|
|
|
Ok(Value::from(value.is_falsy()))
|
|
|
|
}
|
|
|
|
|
2025-05-27 20:20:10 +02:00
|
|
|
pub fn eq(_: &System, vm: &mut Vm, args: FnArgs) -> Result<Value, Exception> {
|
2024-08-10 23:10:03 +02:00
|
|
|
let a = args.get(vm, 0);
|
|
|
|
let b = args.get(vm, 1);
|
|
|
|
Ok(Value::from(a == b))
|
|
|
|
}
|
|
|
|
|
2025-05-27 20:20:10 +02:00
|
|
|
pub fn neq(_: &System, vm: &mut Vm, args: FnArgs) -> Result<Value, Exception> {
|
2024-08-10 23:10:03 +02:00
|
|
|
let a = args.get(vm, 0);
|
|
|
|
let b = args.get(vm, 1);
|
|
|
|
Ok(Value::from(a != b))
|
|
|
|
}
|
|
|
|
|
2025-05-27 20:20:10 +02:00
|
|
|
pub fn lt(_: &System, vm: &mut Vm, args: FnArgs) -> Result<Value, Exception> {
|
2024-08-10 23:10:03 +02:00
|
|
|
let a = args.get(vm, 0);
|
|
|
|
let b = args.get(vm, 1);
|
|
|
|
Ok(Value::from(a < b))
|
|
|
|
}
|
|
|
|
|
2025-05-27 20:20:10 +02:00
|
|
|
pub fn leq(_: &System, vm: &mut Vm, args: FnArgs) -> Result<Value, Exception> {
|
2024-08-10 23:10:03 +02:00
|
|
|
let a = args.get(vm, 0);
|
|
|
|
let b = args.get(vm, 1);
|
|
|
|
Ok(Value::from(a <= b))
|
|
|
|
}
|
|
|
|
|
2025-05-27 20:20:10 +02:00
|
|
|
pub fn gt(_: &System, vm: &mut Vm, args: FnArgs) -> Result<Value, Exception> {
|
2024-08-10 23:10:03 +02:00
|
|
|
let a = args.get(vm, 0);
|
|
|
|
let b = args.get(vm, 1);
|
|
|
|
Ok(Value::from(a > b))
|
|
|
|
}
|
|
|
|
|
2025-05-27 20:20:10 +02:00
|
|
|
pub fn geq(_: &System, vm: &mut Vm, args: FnArgs) -> Result<Value, Exception> {
|
2024-08-10 23:10:03 +02:00
|
|
|
let a = args.get(vm, 0);
|
|
|
|
let b = args.get(vm, 1);
|
|
|
|
Ok(Value::from(a >= b))
|
|
|
|
}
|
|
|
|
|
2025-05-27 20:20:10 +02:00
|
|
|
pub fn vec(_: &System, vm: &mut Vm, args: FnArgs) -> Result<Value, Exception> {
|
2024-09-01 19:15:41 +02:00
|
|
|
static ERROR: &str = "arguments to `vec` must be numbers (vec x y z w)";
|
2024-08-10 23:10:03 +02:00
|
|
|
match args.num() {
|
|
|
|
1 => {
|
|
|
|
let x = args.get_number(vm, 0, ERROR)?;
|
|
|
|
Ok(Value::Vec4(Vec4 {
|
|
|
|
x,
|
|
|
|
y: 0.0,
|
|
|
|
z: 0.0,
|
|
|
|
w: 0.0,
|
|
|
|
}))
|
|
|
|
}
|
|
|
|
2 => {
|
|
|
|
let x = args.get_number(vm, 0, ERROR)?;
|
|
|
|
let y = args.get_number(vm, 1, ERROR)?;
|
|
|
|
Ok(Value::Vec4(Vec4 {
|
|
|
|
x,
|
|
|
|
y,
|
|
|
|
z: 0.0,
|
|
|
|
w: 0.0,
|
|
|
|
}))
|
|
|
|
}
|
|
|
|
3 => {
|
|
|
|
let x = args.get_number(vm, 0, ERROR)?;
|
|
|
|
let y = args.get_number(vm, 1, ERROR)?;
|
|
|
|
let z = args.get_number(vm, 2, ERROR)?;
|
|
|
|
Ok(Value::Vec4(Vec4 { x, y, z, w: 0.0 }))
|
|
|
|
}
|
|
|
|
4 => {
|
|
|
|
let x = args.get_number(vm, 0, ERROR)?;
|
|
|
|
let y = args.get_number(vm, 1, ERROR)?;
|
|
|
|
let z = args.get_number(vm, 2, ERROR)?;
|
|
|
|
let w = args.get_number(vm, 3, ERROR)?;
|
|
|
|
Ok(Value::Vec4(Vec4 { x, y, z, w }))
|
|
|
|
}
|
2024-09-01 19:15:41 +02:00
|
|
|
_ => Err(vm.create_exception("`vec` expects 1-4 arguments (vec x y z w)")),
|
2024-08-10 23:10:03 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-05-27 20:20:10 +02:00
|
|
|
pub fn vec_x(_: &System, vm: &mut Vm, args: FnArgs) -> Result<Value, Exception> {
|
2024-08-10 23:10:03 +02:00
|
|
|
if args.num() != 1 {
|
2024-09-01 19:15:41 +02:00
|
|
|
return Err(vm.create_exception("`vecX` expects a single argument (vecX vec)"));
|
2024-08-10 23:10:03 +02:00
|
|
|
}
|
|
|
|
|
2024-09-01 19:15:41 +02:00
|
|
|
let vec = args.get_vec4(vm, 0, "argument to (vecX vec) must be a `vec`")?;
|
2024-08-10 23:10:03 +02:00
|
|
|
Ok(Value::Number(vec.x))
|
|
|
|
}
|
|
|
|
|
2025-05-27 20:20:10 +02:00
|
|
|
pub fn vec_y(_: &System, vm: &mut Vm, args: FnArgs) -> Result<Value, Exception> {
|
2024-08-10 23:10:03 +02:00
|
|
|
if args.num() != 1 {
|
2024-09-01 19:15:41 +02:00
|
|
|
return Err(vm.create_exception("`vecY` expects a single argument (vecY vec)"));
|
2024-08-10 23:10:03 +02:00
|
|
|
}
|
|
|
|
|
2024-09-01 19:15:41 +02:00
|
|
|
let vec = args.get_vec4(vm, 0, "argument to (vecY vec) must be a `vec`")?;
|
2024-08-10 23:10:03 +02:00
|
|
|
Ok(Value::Number(vec.y))
|
|
|
|
}
|
|
|
|
|
2025-05-27 20:20:10 +02:00
|
|
|
pub fn vec_z(_: &System, vm: &mut Vm, args: FnArgs) -> Result<Value, Exception> {
|
2024-08-10 23:10:03 +02:00
|
|
|
if args.num() != 1 {
|
2024-09-01 19:15:41 +02:00
|
|
|
return Err(vm.create_exception("`vecZ` expects a single argument (vecZ vec)"));
|
2024-08-10 23:10:03 +02:00
|
|
|
}
|
|
|
|
|
2024-09-01 19:15:41 +02:00
|
|
|
let vec = args.get_vec4(vm, 0, "argument to (vecZ vec) must be a `vec`")?;
|
2024-08-10 23:10:03 +02:00
|
|
|
Ok(Value::Number(vec.z))
|
|
|
|
}
|
|
|
|
|
2025-05-27 20:20:10 +02:00
|
|
|
pub fn vec_w(_: &System, vm: &mut Vm, args: FnArgs) -> Result<Value, Exception> {
|
2024-08-10 23:10:03 +02:00
|
|
|
if args.num() != 1 {
|
2024-09-01 19:15:41 +02:00
|
|
|
return Err(vm.create_exception("`vecW` expects a single argument (vecW vec)"));
|
2024-08-10 23:10:03 +02:00
|
|
|
}
|
|
|
|
|
2024-09-01 19:15:41 +02:00
|
|
|
let vec = args.get_vec4(vm, 0, "argument to (vecW vec) must be a `vec`")?;
|
2024-08-10 23:10:03 +02:00
|
|
|
Ok(Value::Number(vec.w))
|
|
|
|
}
|
|
|
|
|
2025-05-27 20:20:10 +02:00
|
|
|
pub fn rgba(_: &System, vm: &mut Vm, args: FnArgs) -> Result<Value, Exception> {
|
2024-08-10 23:10:03 +02:00
|
|
|
if args.num() != 4 {
|
2024-09-01 19:15:41 +02:00
|
|
|
return Err(vm.create_exception("`rgba` expects four arguments (rgba r g b a)"));
|
2024-08-10 23:10:03 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
static ERROR: &str = "arguments to (rgba r g b a) must be numbers";
|
|
|
|
let r = args.get_number(vm, 0, ERROR)?;
|
|
|
|
let g = args.get_number(vm, 1, ERROR)?;
|
|
|
|
let b = args.get_number(vm, 2, ERROR)?;
|
|
|
|
let a = args.get_number(vm, 3, ERROR)?;
|
|
|
|
|
|
|
|
Ok(Value::Rgba(Rgba { r, g, b, a }))
|
|
|
|
}
|
|
|
|
|
2025-05-27 20:20:10 +02:00
|
|
|
pub fn rgba_r(_: &System, vm: &mut Vm, args: FnArgs) -> Result<Value, Exception> {
|
2024-08-10 23:10:03 +02:00
|
|
|
if args.num() != 1 {
|
2024-09-01 19:15:41 +02:00
|
|
|
return Err(vm.create_exception("`rgbaR` expects a single argument (rgbaR rgba)"));
|
2024-08-10 23:10:03 +02:00
|
|
|
}
|
|
|
|
|
2024-09-01 19:15:41 +02:00
|
|
|
let rgba = args.get_rgba(vm, 0, "argument to (rgbaR rgba) must be an `rgba`")?;
|
2024-08-10 23:10:03 +02:00
|
|
|
Ok(Value::Number(rgba.r))
|
|
|
|
}
|
|
|
|
|
2025-05-27 20:20:10 +02:00
|
|
|
pub fn rgba_g(_: &System, vm: &mut Vm, args: FnArgs) -> Result<Value, Exception> {
|
2024-08-10 23:10:03 +02:00
|
|
|
if args.num() != 1 {
|
2024-09-01 19:15:41 +02:00
|
|
|
return Err(vm.create_exception("`rgbaG` expects a single argument (rgbaG rgba)"));
|
2024-08-10 23:10:03 +02:00
|
|
|
}
|
|
|
|
|
2024-09-01 19:15:41 +02:00
|
|
|
let rgba = args.get_rgba(vm, 0, "argument to (rgbaG rgba) must be an `rgba`")?;
|
|
|
|
Ok(Value::Number(rgba.r))
|
2024-08-10 23:10:03 +02:00
|
|
|
}
|
|
|
|
|
2025-05-27 20:20:10 +02:00
|
|
|
pub fn rgba_b(_: &System, vm: &mut Vm, args: FnArgs) -> Result<Value, Exception> {
|
2024-08-10 23:10:03 +02:00
|
|
|
if args.num() != 1 {
|
2024-09-01 19:15:41 +02:00
|
|
|
return Err(vm.create_exception("`rgbaB` expects a single argument (rgbaB rgba)"));
|
2024-08-10 23:10:03 +02:00
|
|
|
}
|
|
|
|
|
2024-09-01 19:15:41 +02:00
|
|
|
let rgba = args.get_rgba(vm, 0, "argument to (rgbaB rgba) must be an `rgba`")?;
|
2024-08-10 23:10:03 +02:00
|
|
|
Ok(Value::Number(rgba.r))
|
|
|
|
}
|
|
|
|
|
2025-05-27 20:20:10 +02:00
|
|
|
pub fn rgba_a(_: &System, vm: &mut Vm, args: FnArgs) -> Result<Value, Exception> {
|
2024-08-10 23:10:03 +02:00
|
|
|
if args.num() != 1 {
|
2024-09-01 19:15:41 +02:00
|
|
|
return Err(vm.create_exception("`rgbaA` expects a single argument (rgbaA rgba)"));
|
2024-08-10 23:10:03 +02:00
|
|
|
}
|
|
|
|
|
2024-09-01 19:15:41 +02:00
|
|
|
let rgba = args.get_rgba(vm, 0, "argument to (rgbaA rgba) must be an `rgba`")?;
|
2024-08-10 23:10:03 +02:00
|
|
|
Ok(Value::Number(rgba.r))
|
|
|
|
}
|
|
|
|
|
2025-05-27 20:20:10 +02:00
|
|
|
pub fn len(_: &System, vm: &mut Vm, args: FnArgs) -> Result<Value, Exception> {
|
2024-10-25 23:21:28 +02:00
|
|
|
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))
|
|
|
|
}
|
|
|
|
|
2025-05-27 20:20:10 +02:00
|
|
|
pub fn index(_: &System, vm: &mut Vm, args: FnArgs) -> Result<Value, Exception> {
|
2024-10-25 23:21:28 +02:00
|
|
|
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
|
|
|
|
)))
|
2024-08-20 23:00:39 +02:00
|
|
|
}
|
|
|
|
|
2025-05-27 20:20:10 +02:00
|
|
|
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 }))?))
|
|
|
|
}
|
|
|
|
|
2024-08-10 23:13:20 +02:00
|
|
|
fn to_shape(value: Value, vm: &Vm) -> Option<Shape> {
|
2024-08-10 23:10:03 +02:00
|
|
|
match value {
|
2024-09-08 13:53:29 +02:00
|
|
|
Value::Nil
|
|
|
|
| Value::False
|
|
|
|
| Value::True
|
|
|
|
| Value::Tag(_)
|
|
|
|
| Value::Number(_)
|
|
|
|
| Value::Rgba(_) => None,
|
|
|
|
|
2024-08-10 23:13:20 +02:00
|
|
|
Value::Ref(id) => {
|
|
|
|
if let Ref::Shape(shape) = vm.get_ref(id) {
|
|
|
|
Some(shape.clone())
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
}
|
|
|
|
}
|
2024-09-08 13:53:29 +02:00
|
|
|
|
2024-08-21 21:11:30 +02:00
|
|
|
Value::Vec4(vec) => Some(Shape::Point(vec.into())),
|
2024-08-10 23:10:03 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-05-27 20:20:10 +02:00
|
|
|
pub fn to_shape_f(_: &System, vm: &mut Vm, args: FnArgs) -> Result<Value, Exception> {
|
2024-08-10 23:10:03 +02:00
|
|
|
if args.num() != 1 {
|
2024-09-01 19:15:41 +02:00
|
|
|
return Err(vm.create_exception("`toShape` expects 1 argument (toShape value)"));
|
2024-08-10 23:10:03 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if let Some(shape) = to_shape(args.get(vm, 0), vm) {
|
|
|
|
let id = vm.create_ref(Ref::Shape(shape))?;
|
|
|
|
Ok(Value::Ref(id))
|
|
|
|
} else {
|
|
|
|
Ok(Value::Nil)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-05-27 20:20:10 +02:00
|
|
|
pub fn line(_: &System, vm: &mut Vm, args: FnArgs) -> Result<Value, Exception> {
|
2024-08-10 23:13:20 +02:00
|
|
|
if args.num() != 2 {
|
2024-09-01 19:15:41 +02:00
|
|
|
return Err(vm.create_exception("`line` expects 2 arguments (line start end)"));
|
2024-08-10 23:13:20 +02:00
|
|
|
}
|
|
|
|
|
2024-09-01 19:15:41 +02:00
|
|
|
static ERROR: &str = "arguments to `line` must be `vec`";
|
2024-08-10 23:13:20 +02:00
|
|
|
let start = args.get_vec4(vm, 0, ERROR)?;
|
|
|
|
let end = args.get_vec4(vm, 1, ERROR)?;
|
|
|
|
|
2024-08-21 21:11:30 +02:00
|
|
|
let id = vm.create_ref(Ref::Shape(Shape::Line(start.into(), end.into())))?;
|
|
|
|
Ok(Value::Ref(id))
|
|
|
|
}
|
|
|
|
|
2025-05-27 20:20:10 +02:00
|
|
|
pub fn rect(_: &System, vm: &mut Vm, args: FnArgs) -> Result<Value, Exception> {
|
2024-09-01 19:15:41 +02:00
|
|
|
static ARGS2: &str = "arguments to 2-argument `rect` must be `vec`";
|
|
|
|
static ARGS4: &str = "arguments to 4-argument `rect` must be numbers";
|
2024-08-21 21:11:30 +02:00
|
|
|
|
|
|
|
let (position, size) = match args.num() {
|
|
|
|
2 => (args.get_vec4(vm, 0, ARGS2)?.into(), args.get_vec4(vm, 1, ARGS2)?.into()),
|
|
|
|
4 => (
|
|
|
|
Vec2 {
|
|
|
|
x: args.get_number(vm, 0, ARGS4)?,
|
|
|
|
y: args.get_number(vm, 1, ARGS4)?,
|
|
|
|
},
|
|
|
|
Vec2 {
|
|
|
|
x: args.get_number(vm, 2, ARGS4)?,
|
|
|
|
y: args.get_number(vm, 3, ARGS4)?,
|
|
|
|
},
|
|
|
|
),
|
2024-09-01 19:15:41 +02:00
|
|
|
_ => return Err(vm.create_exception("`rect` expects 2 arguments (rect position size) or 4 arguments (rect x y width height)"))
|
2024-08-21 21:11:30 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
let id = vm.create_ref(Ref::Shape(Shape::Rect(position, size)))?;
|
|
|
|
Ok(Value::Ref(id))
|
|
|
|
}
|
|
|
|
|
2025-05-27 20:20:10 +02:00
|
|
|
pub fn circle(_: &System, vm: &mut Vm, args: FnArgs) -> Result<Value, Exception> {
|
2024-09-01 19:15:41 +02:00
|
|
|
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";
|
2024-08-21 21:11:30 +02:00
|
|
|
|
|
|
|
let (position, radius) = match args.num() {
|
|
|
|
2 => (args.get_vec4(vm, 0, ARGS2)?.into(), args.get_number(vm, 1, ARGS2)?),
|
|
|
|
3 => (
|
|
|
|
Vec2 {
|
|
|
|
x: args.get_number(vm, 0, ARGS3)?,
|
|
|
|
y: args.get_number(vm, 1, ARGS3)?,
|
|
|
|
},
|
|
|
|
args.get_number(vm, 2, ARGS3)?
|
|
|
|
),
|
2024-09-01 19:15:41 +02:00
|
|
|
_ => return Err(vm.create_exception("`circle` expects 2 arguments (circle position radius) or 3 arguments (circle x y radius)"))
|
2024-08-21 21:11:30 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
let id = vm.create_ref(Ref::Shape(Shape::Circle(position, radius)))?;
|
2024-08-10 23:13:20 +02:00
|
|
|
Ok(Value::Ref(id))
|
|
|
|
}
|
|
|
|
|
2025-05-27 20:20:10 +02:00
|
|
|
pub fn stroke(_: &System, vm: &mut Vm, args: FnArgs) -> Result<Value, Exception> {
|
2024-08-10 23:10:03 +02:00
|
|
|
if args.num() != 3 {
|
|
|
|
return Err(
|
2024-09-01 19:15:41 +02:00
|
|
|
vm.create_exception("`stroke` expects 3 arguments (stroke thickness color shape)")
|
2024-08-10 23:10:03 +02:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
let thickness = args.get_number(
|
|
|
|
vm,
|
|
|
|
0,
|
2024-09-01 19:15:41 +02:00
|
|
|
"1st argument to `stroke` must be a thickness in pixels (number)",
|
2024-08-10 23:10:03 +02:00
|
|
|
)?;
|
2024-09-01 19:15:41 +02:00
|
|
|
let color = args.get_rgba(vm, 1, "2nd argument to `stroke` must be a color (rgba)")?;
|
2024-08-10 23:10:03 +02:00
|
|
|
if let Some(shape) = to_shape(args.get(vm, 2), vm) {
|
|
|
|
let id = vm.create_ref(Ref::Scribble(Scribble::Stroke(Stroke {
|
|
|
|
thickness,
|
|
|
|
color,
|
|
|
|
shape,
|
|
|
|
})))?;
|
|
|
|
Ok(Value::Ref(id))
|
|
|
|
} else {
|
|
|
|
Ok(Value::Nil)
|
|
|
|
}
|
|
|
|
}
|
2024-08-21 22:08:41 +02:00
|
|
|
|
2025-05-27 20:20:10 +02:00
|
|
|
pub fn fill(_: &System, vm: &mut Vm, args: FnArgs) -> Result<Value, Exception> {
|
2024-08-21 22:08:41 +02:00
|
|
|
if args.num() != 2 {
|
2024-09-01 19:15:41 +02:00
|
|
|
return Err(vm.create_exception("`fill` expects 2 arguments (fill color shape)"));
|
2024-08-21 22:08:41 +02:00
|
|
|
}
|
|
|
|
|
2024-09-01 19:15:41 +02:00
|
|
|
let color = args.get_rgba(vm, 0, "1st argument to `fill` must be a color (rgba)")?;
|
2024-08-21 22:08:41 +02:00
|
|
|
if let Some(shape) = to_shape(args.get(vm, 1), vm) {
|
|
|
|
let id = vm.create_ref(Ref::Scribble(Scribble::Fill(Fill { color, shape })))?;
|
|
|
|
Ok(Value::Ref(id))
|
|
|
|
} else {
|
|
|
|
Ok(Value::Nil)
|
|
|
|
}
|
|
|
|
}
|
2024-09-08 13:53:29 +02:00
|
|
|
|
2025-05-27 20:20:10 +02:00
|
|
|
pub fn with_dotter(_: &System, vm: &mut Vm, args: FnArgs) -> Result<Value, Exception> {
|
2024-09-08 13:53:29 +02:00
|
|
|
if args.num() != 1 {
|
|
|
|
return Err(vm.create_exception(
|
|
|
|
"`withDotter` expects a single argument (withDotter \\d -> [])",
|
|
|
|
));
|
|
|
|
}
|
|
|
|
|
2025-05-27 20:20:10 +02:00
|
|
|
_ = args.get_closure(
|
|
|
|
vm,
|
|
|
|
0,
|
|
|
|
1,
|
|
|
|
"argument to `withDotter` must be a single-parameter function (withDotter \\d -> [])",
|
|
|
|
)?;
|
2024-09-08 13:53:29 +02:00
|
|
|
|
|
|
|
let id = vm.create_ref(Ref::Reticle(Reticle::Dotter {
|
|
|
|
draw: args.get(vm, 0),
|
|
|
|
}))?;
|
|
|
|
Ok(Value::Ref(id))
|
|
|
|
}
|
2024-08-10 23:10:03 +02:00
|
|
|
}
|