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:
parent
8356b6c750
commit
5b7d9586ea
26 changed files with 1113 additions and 351 deletions
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue