rkgk/crates/haku/src/system.rs

1035 lines
35 KiB
Rust
Raw Normal View History

2024-08-10 23:10:03 +02:00
use core::{
error::Error,
fmt::{self, Display},
};
use alloc::vec::Vec;
use crate::{
bytecode::{Chunk, EmitError, Offset, Opcode, TagId},
value::{BytecodeLoc, Closure, FunctionName, Value, Vec4},
2024-08-10 23:10:03 +02:00
vm::{Exception, FnArgs, Vm},
};
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);
#[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.
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>,
structs_chunk_offsets: StructsChunkOffsets,
2024-08-10 23:10:03 +02:00
}
#[derive(Debug, Clone, Copy)]
pub struct SystemImage {
chunks: usize,
}
#[derive(Debug, Clone)]
struct StructsChunkOffsets {
dotter: BytecodeLoc,
2024-08-10 23:10:03 +02:00
}
impl System {
pub fn new(max_chunks: usize) -> Self {
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);
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),
structs_chunk_offsets,
2024-08-10 23:10:03 +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")
});
}
// 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 {}
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 {
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::{
value::{Fill, List, Ref, Reticle, Rgba, Scribble, Shape, Stroke, Value, Vec2, Vec4},
2024-08-10 23:10:03 +02:00
vm::{Exception, FnArgs, Vm},
};
use super::{System, SystemFnArity};
2024-08-10 23:10:03 +02:00
impl System {
def_fns! {
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,
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,
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
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,
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,
0xc2 Nary "rect" => rect,
0xc3 Nary "circle" => circle,
0xe0 Nary "stroke" => stroke,
0xe1 Nary "fill" => fill,
0xf0 Nary "withDotter" => with_dotter,
2024-08-10 23:10:03 +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)),
}
}
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)
2024-08-10 23:10:03 +02:00
}
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)
2024-08-10 23:10:03 +02:00
}
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)
2024-08-10 23:10:03 +02:00
}
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)
2024-08-10 23:10:03 +02:00
}
pub fn neg(_: &System, vm: &mut Vm, args: FnArgs) -> Result<Value, Exception> {
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-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),* $(,)?) => {
$(
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,
math1 "expMinus1" expm1f,
2024-09-07 14:30:58 +02:00
math1 "exp2" exp2f,
math1 "ln" logf,
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,
}
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"))
}
}
}
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()))
}
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))
}
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))
}
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))
}
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))
}
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))
}
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))
}
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)";
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 }))
}
_ => Err(vm.create_exception("`vec` expects 1-4 arguments (vec x y z w)")),
2024-08-10 23:10:03 +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 {
return Err(vm.create_exception("`vecX` expects a single argument (vecX vec)"));
2024-08-10 23:10:03 +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))
}
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 {
return Err(vm.create_exception("`vecY` expects a single argument (vecY vec)"));
2024-08-10 23:10:03 +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))
}
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 {
return Err(vm.create_exception("`vecZ` expects a single argument (vecZ vec)"));
2024-08-10 23:10:03 +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))
}
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 {
return Err(vm.create_exception("`vecW` expects a single argument (vecW vec)"));
2024-08-10 23:10:03 +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))
}
pub fn rgba(_: &System, vm: &mut Vm, args: FnArgs) -> Result<Value, Exception> {
2024-08-10 23:10:03 +02:00
if args.num() != 4 {
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 }))
}
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 {
return Err(vm.create_exception("`rgbaR` expects a single argument (rgbaR rgba)"));
2024-08-10 23:10:03 +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))
}
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 {
return Err(vm.create_exception("`rgbaG` expects a single argument (rgbaG rgba)"));
2024-08-10 23:10:03 +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
}
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 {
return Err(vm.create_exception("`rgbaB` expects a single argument (rgbaB rgba)"));
2024-08-10 23:10:03 +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))
}
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 {
return Err(vm.create_exception("`rgbaA` expects a single argument (rgbaA rgba)"));
2024-08-10 23:10:03 +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))
}
pub fn len(_: &System, vm: &mut Vm, args: FnArgs) -> Result<Value, Exception> {
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(_: &System, vm: &mut Vm, args: FnArgs) -> Result<Value, Exception> {
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
}
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 {
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-08-21 21:11:30 +02:00
Value::Vec4(vec) => Some(Shape::Point(vec.into())),
2024-08-10 23:10:03 +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 {
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)
}
}
pub fn line(_: &System, vm: &mut Vm, args: FnArgs) -> Result<Value, Exception> {
2024-08-10 23:13:20 +02:00
if args.num() != 2 {
return Err(vm.create_exception("`line` expects 2 arguments (line start end)"));
2024-08-10 23:13:20 +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))
}
pub fn rect(_: &System, vm: &mut Vm, args: FnArgs) -> Result<Value, Exception> {
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)?,
},
),
_ => 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))
}
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 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)?
),
_ => 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))
}
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(
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,
"1st argument to `stroke` must be a thickness in pixels (number)",
2024-08-10 23:10:03 +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
pub fn fill(_: &System, vm: &mut Vm, args: FnArgs) -> Result<Value, Exception> {
2024-08-21 22:08:41 +02:00
if args.num() != 2 {
return Err(vm.create_exception("`fill` expects 2 arguments (fill color shape)"));
2024-08-21 22:08: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)
}
}
pub fn with_dotter(_: &System, vm: &mut Vm, args: FnArgs) -> Result<Value, Exception> {
if args.num() != 1 {
return Err(vm.create_exception(
"`withDotter` expects a single argument (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),
}))?;
Ok(Value::Ref(id))
}
2024-08-10 23:10:03 +02:00
}