use crate::op::Op;
use crate::value::Value;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct Chunk {
pub ops: Vec<Op>,
pub constants: Vec<Value>,
pub names: Vec<String>,
pub lines: Vec<u32>,
pub sub_entries: Vec<(u16, usize)>,
pub block_ranges: Vec<(usize, usize)>,
pub sub_chunks: Vec<Chunk>,
pub source: String,
#[serde(skip)]
pub op_hash: u64,
}
impl Chunk {
pub fn new() -> Self {
Self::default()
}
pub fn find_sub(&self, name_idx: u16) -> Option<usize> {
self.sub_entries
.iter()
.find(|(n, _)| *n == name_idx)
.map(|(_, ip)| *ip)
}
}
pub struct ChunkBuilder {
chunk: Chunk,
name_map: std::collections::HashMap<String, u16>,
}
impl ChunkBuilder {
pub fn new() -> Self {
Self {
chunk: Chunk::new(),
name_map: std::collections::HashMap::new(),
}
}
pub fn emit(&mut self, op: Op, line: u32) -> usize {
let idx = self.chunk.ops.len();
self.chunk.ops.push(op);
self.chunk.lines.push(line);
idx
}
pub fn add_constant(&mut self, val: Value) -> u16 {
let idx = self.chunk.constants.len();
self.chunk.constants.push(val);
idx as u16
}
pub fn add_name(&mut self, name: &str) -> u16 {
if let Some(&idx) = self.name_map.get(name) {
return idx;
}
let idx = self.chunk.names.len() as u16;
self.chunk.names.push(name.to_string());
self.name_map.insert(name.to_string(), idx);
idx
}
pub fn current_pos(&self) -> usize {
self.chunk.ops.len()
}
pub fn patch_jump(&mut self, op_idx: usize, target: usize) {
match &mut self.chunk.ops[op_idx] {
Op::Jump(t)
| Op::JumpIfTrue(t)
| Op::JumpIfFalse(t)
| Op::JumpIfTrueKeep(t)
| Op::JumpIfFalseKeep(t) => *t = target,
_ => panic!("patch_jump on non-jump op at {}", op_idx),
}
}
pub fn add_sub_entry(&mut self, name_idx: u16, ip: usize) {
self.chunk.sub_entries.push((name_idx, ip));
}
pub fn add_block_range(&mut self, start: usize, end: usize) -> u16 {
let idx = self.chunk.block_ranges.len();
self.chunk.block_ranges.push((start, end));
idx as u16
}
pub fn add_sub_chunk(&mut self, sub: Chunk) -> u16 {
let idx = self.chunk.sub_chunks.len();
self.chunk.sub_chunks.push(sub);
idx as u16
}
pub fn set_source(&mut self, source: impl Into<String>) {
self.chunk.source = source.into();
}
pub fn build(mut self) -> Chunk {
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
let mut h = DefaultHasher::new();
self.chunk.ops.hash(&mut h);
self.chunk.constants.hash(&mut h);
self.chunk.op_hash = h.finish();
self.chunk
}
}
impl Default for ChunkBuilder {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::op::Op;
use crate::value::Value;
#[test]
fn new_and_default_are_equivalent() {
let a = ChunkBuilder::new().build();
let b = ChunkBuilder::default().build();
assert_eq!(a.ops, b.ops);
assert_eq!(a.names, b.names);
assert_eq!(a.constants.len(), b.constants.len());
assert_eq!(a.op_hash, b.op_hash);
}
#[test]
fn find_sub_returns_first_match_when_duplicate_names_registered() {
let mut b = ChunkBuilder::new();
let n = b.add_name("foo");
b.add_sub_entry(n, 10);
b.add_sub_entry(n, 20);
let chunk = b.build();
assert_eq!(chunk.find_sub(n), Some(10));
}
#[test]
fn find_sub_distinguishes_multiple_subs() {
let mut b = ChunkBuilder::new();
let f = b.add_name("f");
let g = b.add_name("g");
b.add_sub_entry(f, 7);
b.add_sub_entry(g, 13);
let chunk = b.build();
assert_eq!(chunk.find_sub(f), Some(7));
assert_eq!(chunk.find_sub(g), Some(13));
}
#[test]
fn add_sub_chunk_returns_sequential_indices() {
let mut b = ChunkBuilder::new();
let i0 = b.add_sub_chunk(Chunk::new());
let i1 = b.add_sub_chunk(Chunk::new());
let i2 = b.add_sub_chunk(Chunk::new());
assert_eq!((i0, i1, i2), (0, 1, 2));
assert_eq!(b.build().sub_chunks.len(), 3);
}
#[test]
fn sub_chunks_preserve_inner_content() {
let inner = {
let mut ib = ChunkBuilder::new();
ib.emit(Op::LoadInt(7), 1);
ib.build()
};
let mut b = ChunkBuilder::new();
let idx = b.add_sub_chunk(inner);
let outer = b.build();
assert_eq!(idx, 0);
assert_eq!(outer.sub_chunks[0].ops, vec![Op::LoadInt(7)]);
}
#[test]
fn add_constant_returns_monotonic_indices() {
let mut b = ChunkBuilder::new();
for i in 0..5u16 {
assert_eq!(b.add_constant(Value::Int(i as i64)), i);
}
}
#[test]
fn add_name_first_index_is_zero() {
let mut b = ChunkBuilder::new();
assert_eq!(b.add_name("first"), 0);
assert_eq!(b.add_name("second"), 1);
}
#[test]
fn build_computes_nonzero_hash_for_nonempty_chunk() {
let mut b = ChunkBuilder::new();
b.emit(Op::LoadInt(1), 1);
let c = b.build();
assert_ne!(c.op_hash, 0);
}
#[test]
fn op_hash_ignores_line_and_name_pool() {
let a = {
let mut b = ChunkBuilder::new();
b.add_name("alpha");
b.emit(Op::LoadInt(1), 5);
b.build()
};
let b = {
let mut b = ChunkBuilder::new();
b.add_name("beta");
b.emit(Op::LoadInt(1), 99);
b.build()
};
assert_eq!(a.op_hash, b.op_hash);
}
#[test]
fn set_source_overwrites_previous_value() {
let mut b = ChunkBuilder::new();
b.set_source("first.fuse");
b.set_source("second.fuse");
assert_eq!(b.build().source, "second.fuse");
}
}