rkgk/crates/haku/src/value.rs
リキ萌 01d4514a65 beginning of haku2: a reimplementation of haku in Zig
the goal is to rewrite haku completely, starting with the VM---because it was the most obvious point of improvement
the reason is because Rust is kinda too verbose for low level stuff like this. compare the line numbers between haku1 and haku2's VM and how verbose those lines are, and it's kind of an insane difference
it also feels like Zig's compilation model can work better for small wasm binary sizes

and of course, I also just wanted an excuse to try out Zig :3
2025-06-01 23:19:05 +02:00

346 lines
6.5 KiB
Rust

use core::{
mem::discriminant,
ops::{Add, Div, Mul, Neg, Sub},
};
use alloc::vec::Vec;
use crate::{bytecode::TagId, compiler::ClosureSpec, system::ChunkId};
// TODO: Probably needs some pretty hardcore space optimization.
// Maybe when we have static typing.
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
pub enum Value {
Nil,
False,
True,
Tag(TagId),
Number(f32),
Vec4(Vec4),
Rgba(Rgba),
Ref(RefId),
}
impl Value {
pub fn is_falsy(&self) -> bool {
matches!(self, Self::Nil | Self::False)
}
pub fn is_truthy(&self) -> bool {
!self.is_falsy()
}
pub fn to_number(&self) -> Option<f32> {
match self {
Self::Number(v) => Some(*v),
_ => None,
}
}
pub fn to_vec4(&self) -> Option<Vec4> {
match self {
Self::Vec4(v) => Some(*v),
_ => None,
}
}
pub fn to_rgba(&self) -> Option<Rgba> {
match self {
Self::Rgba(v) => Some(*v),
_ => None,
}
}
pub fn to_ref(&self) -> Option<RefId> {
match self {
Self::Ref(v) => Some(*v),
_ => None,
}
}
pub fn is_same_type_as(&self, other: &Value) -> bool {
discriminant(self) == discriminant(other)
}
}
impl From<()> for Value {
fn from(_: ()) -> Self {
Self::Nil
}
}
impl From<bool> for Value {
fn from(value: bool) -> Self {
match value {
true => Self::True,
false => Self::False,
}
}
}
impl From<f32> for Value {
fn from(value: f32) -> Self {
Self::Number(value)
}
}
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default)]
pub struct Vec2 {
pub x: f32,
pub y: f32,
}
impl Vec2 {
pub fn new(x: f32, y: f32) -> Self {
Self { x, y }
}
}
impl From<Vec4> for Vec2 {
fn from(value: Vec4) -> Self {
Self {
x: value.x,
y: value.y,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default)]
pub struct Vec4 {
pub x: f32,
pub y: f32,
pub z: f32,
pub w: f32,
}
impl From<f32> for Vec4 {
fn from(value: f32) -> Self {
Self {
x: value,
y: value,
z: value,
w: value,
}
}
}
impl From<Vec2> for Vec4 {
fn from(value: Vec2) -> Self {
Self {
x: value.x,
y: value.y,
z: 0.0,
w: 0.0,
}
}
}
impl From<Rgba> for Vec4 {
fn from(value: Rgba) -> Self {
Self {
x: value.r,
y: value.g,
z: value.b,
w: value.a,
}
}
}
impl Add for Vec4 {
type Output = Vec4;
fn add(self, rhs: Self) -> Self::Output {
Self {
x: self.x + rhs.x,
y: self.y + rhs.y,
z: self.z + rhs.z,
w: self.w + rhs.w,
}
}
}
impl Sub for Vec4 {
type Output = Vec4;
fn sub(self, rhs: Self) -> Self::Output {
Self {
x: self.x - rhs.x,
y: self.y - rhs.y,
z: self.z - rhs.z,
w: self.w - rhs.w,
}
}
}
impl Mul for Vec4 {
type Output = Vec4;
fn mul(self, rhs: Self) -> Self::Output {
Self {
x: self.x * rhs.x,
y: self.y * rhs.y,
z: self.z * rhs.z,
w: self.w * rhs.w,
}
}
}
impl Div for Vec4 {
type Output = Vec4;
fn div(self, rhs: Self) -> Self::Output {
Self {
x: self.x / rhs.x,
y: self.y / rhs.y,
z: self.z / rhs.z,
w: self.w / rhs.w,
}
}
}
impl Neg for Vec4 {
type Output = Vec4;
fn neg(self) -> Self::Output {
Self {
x: -self.x,
y: -self.y,
z: -self.z,
w: -self.w,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default)]
#[repr(C)]
pub struct Rgba {
pub r: f32,
pub g: f32,
pub b: f32,
pub a: f32,
}
impl From<Vec4> for Rgba {
fn from(value: Vec4) -> Self {
Self {
r: value.x,
g: value.y,
b: value.z,
a: value.w,
}
}
}
// NOTE: This is not a pointer, because IDs are safer and easier to clone.
//
// Since this only ever refers to refs inside the current VM, there is no need to walk through all
// the values and update pointers when a VM is cloned.
//
// This ensures it's quick and easy to spin up a new VM from an existing image, as well as being
// extremely easy to serialize a VM image into a file for quick loading back later.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct RefId(pub(crate) u32);
impl RefId {
// DO NOT USE outside tests!
#[doc(hidden)]
pub fn from_u32(x: u32) -> Self {
Self(x)
}
}
#[derive(Debug, Clone)]
pub enum Ref {
Closure(Closure),
List(List),
Shape(Shape),
Scribble(Scribble),
Reticle(Reticle),
}
impl Ref {
pub fn as_closure(&self) -> Option<&Closure> {
match self {
Self::Closure(v) => Some(v),
_ => None,
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct BytecodeLoc {
pub chunk_id: ChunkId,
pub offset: u16,
}
#[derive(Debug, Clone, Copy)]
pub struct BytecodeSpan {
pub loc: BytecodeLoc,
pub len: u16,
}
#[derive(Debug, Clone, Copy)]
pub enum FunctionName {
Anonymous,
}
#[derive(Debug, Clone)]
pub struct Closure {
pub start: BytecodeLoc,
pub name: FunctionName,
pub param_count: u8,
pub local_count: u8,
pub captures: Vec<Value>,
}
impl Closure {
pub fn chunk(chunk_id: ChunkId, spec: ClosureSpec) -> Self {
Self {
start: BytecodeLoc {
chunk_id,
offset: 0,
},
name: FunctionName::Anonymous,
param_count: 0,
local_count: spec.local_count,
captures: Vec::new(),
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct List {
pub elements: Vec<Value>,
}
#[derive(Debug, Clone)]
pub enum Shape {
Point(Vec2),
Line(Vec2, Vec2),
Rect(Vec2, Vec2),
Circle(Vec2, f32),
}
#[derive(Debug, Clone)]
pub struct Stroke {
pub thickness: f32,
pub color: Rgba,
pub shape: Shape,
}
#[derive(Debug, Clone)]
pub struct Fill {
pub color: Rgba,
pub shape: Shape,
}
#[derive(Debug, Clone)]
pub enum Scribble {
Stroke(Stroke),
Fill(Fill),
}
#[derive(Debug, Clone)]
pub enum Reticle {
Dotter { draw: Value },
}