use std::sync::Arc;
use tatara_lisp::Span;
use super::op::Op;
use crate::value::Value;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CaptureSource {
Local(usize),
Captured(usize),
}
#[derive(Debug, Clone)]
pub struct CompiledFn {
pub params: Vec<Arc<str>>,
pub rest: Option<Arc<str>>,
pub locals: usize,
pub captures: Vec<(Arc<str>, CaptureSource)>,
pub ops: Vec<Op>,
pub spans: Vec<Span>,
pub source_span: Span,
pub source_body: Vec<tatara_lisp::Spanned>,
}
impl Default for CompiledFn {
fn default() -> Self {
Self {
params: Vec::new(),
rest: None,
locals: 0,
captures: Vec::new(),
ops: Vec::new(),
spans: Vec::new(),
source_span: Span::synthetic(),
source_body: Vec::new(),
}
}
}
#[derive(Debug, Default, Clone)]
pub struct ConstPool {
pub values: Vec<Value>,
}
impl ConstPool {
pub fn new() -> Self {
Self::default()
}
pub fn push(&mut self, v: Value) -> usize {
if let Some(idx) = self.lookup_existing(&v) {
return idx;
}
let idx = self.values.len();
self.values.push(v);
idx
}
fn lookup_existing(&self, v: &Value) -> Option<usize> {
for (i, existing) in self.values.iter().enumerate() {
if Self::scalar_equal(existing, v) {
return Some(i);
}
}
None
}
fn scalar_equal(a: &Value, b: &Value) -> bool {
match (a, b) {
(Value::Symbol(x), Value::Symbol(y))
| (Value::Keyword(x), Value::Keyword(y))
| (Value::Str(x), Value::Str(y)) => std::sync::Arc::ptr_eq(x, y) || **x == **y,
(Value::Float(x), Value::Float(y)) => x.to_bits() == y.to_bits(),
_ => false,
}
}
pub fn get(&self, idx: usize) -> &Value {
&self.values[idx]
}
}
#[derive(Debug, Default, Clone)]
pub struct NamePool {
pub names: Vec<Arc<str>>,
map: Vec<(Arc<str>, usize)>,
}
impl NamePool {
pub fn new() -> Self {
Self::default()
}
pub fn intern(&mut self, name: impl Into<Arc<str>>) -> usize {
let name = name.into();
for (existing, idx) in &self.map {
if &**existing == &*name {
return *idx;
}
}
let idx = self.names.len();
self.names.push(name.clone());
self.map.push((name, idx));
idx
}
pub fn get(&self, idx: usize) -> &Arc<str> {
&self.names[idx]
}
}
#[derive(Debug, Default, Clone)]
pub struct Chunk {
pub top: CompiledFn,
pub fn_table: Vec<CompiledFn>,
pub consts: ConstPool,
pub names: NamePool,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn const_pool_dedupes_repeat_strings() {
let mut pool = ConstPool::new();
let a = pool.push(Value::Str(Arc::from("hello")));
let b = pool.push(Value::Str(Arc::from("hello")));
let c = pool.push(Value::Str(Arc::from("world")));
assert_eq!(a, b, "repeat string must dedupe");
assert_ne!(a, c);
assert_eq!(pool.values.len(), 2);
}
#[test]
fn const_pool_dedupes_interned_symbols() {
let mut pool = ConstPool::new();
let s1 = crate::interner::intern("foo");
let s2 = crate::interner::intern("foo");
let a = pool.push(Value::Symbol(s1));
let b = pool.push(Value::Symbol(s2));
assert_eq!(a, b);
assert_eq!(pool.values.len(), 1);
}
#[test]
fn const_pool_dedupes_floats_by_bits() {
let mut pool = ConstPool::new();
let a = pool.push(Value::Float(3.14));
let b = pool.push(Value::Float(3.14));
let c = pool.push(Value::Float(2.71));
assert_eq!(a, b);
assert_ne!(a, c);
assert_eq!(pool.values.len(), 2);
}
#[test]
fn const_pool_does_not_dedupe_lists() {
let mut pool = ConstPool::new();
let _ = pool.push(Value::list(vec![Value::Int(1), Value::Int(2)]));
let _ = pool.push(Value::list(vec![Value::Int(1), Value::Int(2)]));
assert_eq!(pool.values.len(), 2, "lists deliberately not deduped");
}
#[test]
fn name_pool_intern_returns_same_index() {
let mut p = NamePool::new();
let a = p.intern("foo");
let b = p.intern("foo");
assert_eq!(a, b);
assert_eq!(p.names.len(), 1);
}
}