introduce tags, structs, and reticles

this was meant to be split into smaller changes, but I realised I edited my existing revision too late.
This commit is contained in:
りき萌 2024-09-08 13:53:29 +02:00
parent 8356b6c750
commit 5b7d9586ea
26 changed files with 1113 additions and 351 deletions

View file

@ -6,8 +6,8 @@ use core::{
use alloc::vec::Vec;
use crate::{
bytecode::Chunk,
value::Value,
bytecode::{Chunk, EmitError, Offset, Opcode, TagId},
value::{BytecodeLoc, Closure, FunctionName, Value, Vec4},
vm::{Exception, FnArgs, Vm},
};
@ -29,6 +29,7 @@ pub struct System {
pub resolve_fn: fn(SystemFnArity, &str) -> Option<u8>,
pub fns: [Option<SystemFn>; 256],
pub chunks: Vec<Chunk>,
structs_chunk_offsets: StructsChunkOffsets,
}
#[derive(Debug, Clone, Copy)]
@ -36,33 +37,28 @@ pub struct SystemImage {
chunks: usize,
}
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,
}
}
};
#[derive(Debug, Clone)]
struct StructsChunkOffsets {
dotter: BytecodeLoc,
}
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"
);
assert!(max_chunks < u32::MAX as usize);
let (structs_chunk, structs_chunk_offsets) = Self::structs_chunk().unwrap();
let mut system = Self {
resolve_fn: Self::resolve,
fns: [None; 256],
chunks: Vec::with_capacity(max_chunks),
structs_chunk_offsets,
};
system.chunks.push(structs_chunk);
Self::init_fns(&mut system);
system
}
@ -92,6 +88,64 @@ impl System {
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)]),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
@ -105,11 +159,29 @@ impl Display for ChunkError {
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,
}
}
};
}
pub mod fns {
use alloc::{format, vec::Vec};
use crate::{
value::{Fill, List, Ref, Rgba, Scribble, Shape, Stroke, Value, Vec2, Vec4},
value::{Fill, List, Ref, Reticle, Rgba, Scribble, Shape, Stroke, Value, Vec2, Vec4},
vm::{Exception, FnArgs, Vm},
};
@ -181,8 +253,11 @@ pub mod fns {
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,
}
}
@ -469,7 +544,13 @@ pub mod fns {
fn to_shape(value: Value, vm: &Vm) -> Option<Shape> {
match value {
Value::Nil | Value::False | Value::True | Value::Number(_) | Value::Rgba(_) => None,
Value::Nil
| Value::False
| Value::True
| Value::Tag(_)
| Value::Number(_)
| Value::Rgba(_) => None,
Value::Ref(id) => {
if let Ref::Shape(shape) = vm.get_ref(id) {
Some(shape.clone())
@ -477,6 +558,7 @@ pub mod fns {
None
}
}
Value::Vec4(vec) => Some(Shape::Point(vec.into())),
}
}
@ -588,4 +670,22 @@ pub mod fns {
Ok(Value::Nil)
}
}
pub fn with_dotter(vm: &mut Vm, args: FnArgs) -> Result<Value, Exception> {
if args.num() != 1 {
return Err(vm.create_exception(
"`withDotter` expects a single argument (withDotter \\d -> [])",
));
}
let draw = args.get_closure(vm, 0, "argument to `withDotter` must be a closure")?;
if draw.param_count != 1 {
return Err(vm.create_exception("function passed to `withDotter` must take in a single parameter (withDotter \\d -> [])"));
}
let id = vm.create_ref(Ref::Reticle(Reticle::Dotter {
draw: args.get(vm, 0),
}))?;
Ok(Value::Ref(id))
}
}