use std::any::Any;
use std::cell::{Cell, RefCell};
use std::collections::BTreeMap;
use std::fmt;
use std::hash::{Hash, Hasher};
use std::rc::Rc;
use hashbrown::HashMap as SpurMap;
use lasso::{Rodeo, Spur};
use crate::error::SemaError;
use crate::EvalContext;
#[cfg(not(any(target_pointer_width = "64", target_arch = "wasm32")))]
compile_error!("sema-core NaN-boxed Value requires a 64-bit platform (or wasm32)");
thread_local! {
static INTERNER: RefCell<Rodeo> = RefCell::new(Rodeo::default());
}
pub fn intern(s: &str) -> Spur {
INTERNER.with(|r| r.borrow_mut().get_or_intern(s))
}
pub fn resolve(spur: Spur) -> String {
INTERNER.with(|r| r.borrow().resolve(&spur).to_string())
}
pub fn with_resolved<F, R>(spur: Spur, f: F) -> R
where
F: FnOnce(&str) -> R,
{
INTERNER.with(|r| {
let interner = r.borrow();
f(interner.resolve(&spur))
})
}
pub fn interner_stats() -> (usize, usize) {
INTERNER.with(|r| {
let interner = r.borrow();
let count = interner.len();
let bytes = count * 16; (count, bytes)
})
}
thread_local! {
static GENSYM_COUNTER: Cell<u64> = const { Cell::new(0) };
}
pub fn next_gensym(prefix: &str) -> String {
GENSYM_COUNTER.with(|c| {
let val = c.get();
c.set(val.wrapping_add(1));
format!("{prefix}__{val}")
})
}
pub fn compare_spurs(a: Spur, b: Spur) -> std::cmp::Ordering {
if a == b {
return std::cmp::Ordering::Equal;
}
INTERNER.with(|r| {
let interner = r.borrow();
interner.resolve(&a).cmp(interner.resolve(&b))
})
}
pub type NativeFnInner = dyn Fn(&EvalContext, &[Value]) -> Result<Value, SemaError>;
pub struct NativeFn {
pub name: String,
pub func: Box<NativeFnInner>,
pub payload: Option<Rc<dyn Any>>,
}
impl NativeFn {
pub fn simple(
name: impl Into<String>,
f: impl Fn(&[Value]) -> Result<Value, SemaError> + 'static,
) -> Self {
Self {
name: name.into(),
func: Box::new(move |_ctx, args| f(args)),
payload: None,
}
}
pub fn with_ctx(
name: impl Into<String>,
f: impl Fn(&EvalContext, &[Value]) -> Result<Value, SemaError> + 'static,
) -> Self {
Self {
name: name.into(),
func: Box::new(f),
payload: None,
}
}
pub fn with_payload(
name: impl Into<String>,
payload: Rc<dyn Any>,
f: impl Fn(&EvalContext, &[Value]) -> Result<Value, SemaError> + 'static,
) -> Self {
Self {
name: name.into(),
func: Box::new(f),
payload: Some(payload),
}
}
}
impl fmt::Debug for NativeFn {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "<native-fn {}>", self.name)
}
}
#[derive(Debug, Clone)]
pub struct Lambda {
pub params: Vec<Spur>,
pub rest_param: Option<Spur>,
pub body: Vec<Value>,
pub env: Env,
pub name: Option<Spur>,
}
#[derive(Debug, Clone)]
pub struct Macro {
pub params: Vec<Spur>,
pub rest_param: Option<Spur>,
pub body: Vec<Value>,
pub name: Spur,
}
pub struct Thunk {
pub body: Value,
pub forced: RefCell<Option<Value>>,
}
impl fmt::Debug for Thunk {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.forced.borrow().is_some() {
write!(f, "<promise (forced)>")
} else {
write!(f, "<promise>")
}
}
}
impl Clone for Thunk {
fn clone(&self) -> Self {
Thunk {
body: self.body.clone(),
forced: RefCell::new(self.forced.borrow().clone()),
}
}
}
#[derive(Debug, Clone)]
pub struct Record {
pub type_tag: Spur,
pub fields: Vec<Value>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Role {
System,
User,
Assistant,
Tool,
}
impl fmt::Display for Role {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Role::System => write!(f, "system"),
Role::User => write!(f, "user"),
Role::Assistant => write!(f, "assistant"),
Role::Tool => write!(f, "tool"),
}
}
}
#[derive(Debug, Clone)]
pub struct ImageAttachment {
pub data: String,
pub media_type: String,
}
#[derive(Debug, Clone)]
pub struct Message {
pub role: Role,
pub content: String,
pub images: Vec<ImageAttachment>,
}
#[derive(Debug, Clone)]
pub struct Prompt {
pub messages: Vec<Message>,
}
#[derive(Debug, Clone)]
pub struct Conversation {
pub messages: Vec<Message>,
pub model: String,
pub metadata: BTreeMap<String, String>,
}
#[derive(Debug, Clone)]
pub struct ToolDefinition {
pub name: String,
pub description: String,
pub parameters: Value,
pub handler: Value,
}
#[derive(Debug, Clone)]
pub struct Agent {
pub name: String,
pub system: String,
pub tools: Vec<Value>,
pub max_turns: usize,
pub model: String,
}
pub struct MultiMethod {
pub name: Spur,
pub dispatch_fn: Value,
pub methods: RefCell<BTreeMap<Value, Value>>,
pub default: RefCell<Option<Value>>,
}
impl fmt::Debug for MultiMethod {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "<multimethod {}>", resolve(self.name))
}
}
pub trait SemaStream: fmt::Debug {
fn read(&self, buf: &mut [u8]) -> Result<usize, SemaError>;
fn write(&self, data: &[u8]) -> Result<usize, SemaError>;
fn available(&self) -> Result<bool, SemaError> {
Ok(false)
}
fn flush(&self) -> Result<(), SemaError> {
Ok(())
}
fn close(&self) -> Result<(), SemaError> {
Ok(())
}
fn is_readable(&self) -> bool {
true
}
fn is_writable(&self) -> bool {
true
}
fn stream_type(&self) -> &'static str;
fn as_any(&self) -> &dyn std::any::Any;
}
pub struct StreamBox {
inner: RefCell<Box<dyn SemaStream>>,
closed: Cell<bool>,
}
impl StreamBox {
pub fn new(s: impl SemaStream + 'static) -> Self {
StreamBox {
inner: RefCell::new(Box::new(s)),
closed: Cell::new(false),
}
}
pub fn read(&self, buf: &mut [u8]) -> Result<usize, SemaError> {
if self.closed.get() {
return Err(SemaError::eval("stream/read: stream is closed"));
}
self.inner.borrow().read(buf)
}
pub fn write(&self, data: &[u8]) -> Result<usize, SemaError> {
if self.closed.get() {
return Err(SemaError::eval("stream/write: stream is closed"));
}
self.inner.borrow().write(data)
}
pub fn flush(&self) -> Result<(), SemaError> {
if self.closed.get() {
return Err(SemaError::eval("stream/flush: stream is closed"));
}
self.inner.borrow().flush()
}
pub fn close(&self) -> Result<(), SemaError> {
if self.closed.get() {
return Ok(()); }
self.inner.borrow().close()?;
self.closed.set(true);
Ok(())
}
pub fn is_closed(&self) -> bool {
self.closed.get()
}
pub fn is_readable(&self) -> bool {
!self.closed.get() && self.inner.borrow().is_readable()
}
pub fn is_writable(&self) -> bool {
!self.closed.get() && self.inner.borrow().is_writable()
}
pub fn available(&self) -> Result<bool, SemaError> {
if self.closed.get() {
return Ok(false);
}
self.inner.borrow().available()
}
pub fn stream_type(&self) -> &'static str {
self.inner.borrow().stream_type()
}
pub fn borrow_inner(&self) -> std::cell::Ref<'_, Box<dyn SemaStream>> {
self.inner.borrow()
}
}
impl fmt::Debug for StreamBox {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "<stream:{}>", self.stream_type())
}
}
impl Clone for MultiMethod {
fn clone(&self) -> Self {
MultiMethod {
name: self.name,
dispatch_fn: self.dispatch_fn.clone(),
methods: RefCell::new(self.methods.borrow().clone()),
default: RefCell::new(self.default.borrow().clone()),
}
}
}
const BOX_MASK: u64 = 0xFFF8_0000_0000_0000;
const PAYLOAD_MASK: u64 = (1u64 << 45) - 1;
const INT_SIGN_BIT: u64 = 1u64 << 44;
const TAG_MASK_6BIT: u64 = 0x3F;
const CANONICAL_NAN: u64 = 0x7FF8_0000_0000_0000;
const TAG_NIL: u64 = 0;
const TAG_FALSE: u64 = 1;
const TAG_TRUE: u64 = 2;
const TAG_INT_SMALL: u64 = 3;
const TAG_CHAR: u64 = 4;
const TAG_SYMBOL: u64 = 5;
const TAG_KEYWORD: u64 = 6;
const TAG_INT_BIG: u64 = 7;
const TAG_STRING: u64 = 8;
const TAG_LIST: u64 = 9;
const TAG_VECTOR: u64 = 10;
const TAG_MAP: u64 = 11;
const TAG_HASHMAP: u64 = 12;
const TAG_LAMBDA: u64 = 13;
const TAG_MACRO: u64 = 14;
pub const TAG_NATIVE_FN: u64 = 15;
const TAG_PROMPT: u64 = 16;
const TAG_MESSAGE: u64 = 17;
const TAG_CONVERSATION: u64 = 18;
const TAG_TOOL_DEF: u64 = 19;
const TAG_AGENT: u64 = 20;
const TAG_THUNK: u64 = 21;
const TAG_RECORD: u64 = 22;
const TAG_BYTEVECTOR: u64 = 23;
const TAG_MULTIMETHOD: u64 = 24;
const TAG_STREAM: u64 = 25;
const TAG_F64_ARRAY: u64 = 26;
const TAG_I64_ARRAY: u64 = 27;
const SMALL_INT_MIN: i64 = -(1i64 << 44);
const SMALL_INT_MAX: i64 = (1i64 << 44) - 1;
pub const NAN_TAG_MASK: u64 = BOX_MASK | (TAG_MASK_6BIT << 45);
pub const NAN_INT_SMALL_PATTERN: u64 = BOX_MASK | (TAG_INT_SMALL << 45);
pub const NAN_PAYLOAD_MASK: u64 = PAYLOAD_MASK;
pub const NAN_INT_SIGN_BIT: u64 = INT_SIGN_BIT;
pub const NAN_PAYLOAD_BITS: u32 = 45;
#[inline(always)]
fn make_boxed(tag: u64, payload: u64) -> u64 {
BOX_MASK | (tag << 45) | (payload & PAYLOAD_MASK)
}
#[inline(always)]
fn is_boxed(bits: u64) -> bool {
(bits & BOX_MASK) == BOX_MASK
}
#[inline(always)]
fn get_tag(bits: u64) -> u64 {
(bits >> 45) & TAG_MASK_6BIT
}
#[inline(always)]
fn get_payload(bits: u64) -> u64 {
bits & PAYLOAD_MASK
}
#[inline(always)]
fn ptr_to_payload(ptr: *const u8) -> u64 {
let raw = ptr as u64;
debug_assert!(raw & 0x7 == 0, "pointer not 8-byte aligned: 0x{:x}", raw);
debug_assert!(
raw >> 48 == 0,
"pointer exceeds 48-bit VA space: 0x{:x}",
raw
);
raw >> 3
}
#[inline(always)]
fn payload_to_ptr(payload: u64) -> *const u8 {
(payload << 3) as *const u8
}
pub enum ValueView {
Nil,
Bool(bool),
Int(i64),
Float(f64),
String(Rc<String>),
Symbol(Spur),
Keyword(Spur),
Char(char),
List(Rc<Vec<Value>>),
Vector(Rc<Vec<Value>>),
Map(Rc<BTreeMap<Value, Value>>),
HashMap(Rc<hashbrown::HashMap<Value, Value>>),
Lambda(Rc<Lambda>),
Macro(Rc<Macro>),
NativeFn(Rc<NativeFn>),
Prompt(Rc<Prompt>),
Message(Rc<Message>),
Conversation(Rc<Conversation>),
ToolDef(Rc<ToolDefinition>),
Agent(Rc<Agent>),
Thunk(Rc<Thunk>),
Record(Rc<Record>),
Bytevector(Rc<Vec<u8>>),
MultiMethod(Rc<MultiMethod>),
Stream(Rc<StreamBox>),
F64Array(Rc<Vec<f64>>),
I64Array(Rc<Vec<i64>>),
}
#[repr(transparent)]
pub struct Value(u64);
impl Value {
pub const NIL: Value = Value(make_boxed_const(TAG_NIL, 0));
pub const TRUE: Value = Value(make_boxed_const(TAG_TRUE, 0));
pub const FALSE: Value = Value(make_boxed_const(TAG_FALSE, 0));
#[inline(always)]
pub fn nil() -> Value {
Value::NIL
}
#[inline(always)]
pub fn bool(b: bool) -> Value {
if b {
Value::TRUE
} else {
Value::FALSE
}
}
#[inline(always)]
pub fn int(n: i64) -> Value {
if (SMALL_INT_MIN..=SMALL_INT_MAX).contains(&n) {
let payload = (n as u64) & PAYLOAD_MASK;
Value(make_boxed(TAG_INT_SMALL, payload))
} else {
let rc = Rc::new(n);
let ptr = Rc::into_raw(rc) as *const u8;
Value(make_boxed(TAG_INT_BIG, ptr_to_payload(ptr)))
}
}
#[inline(always)]
pub fn float(f: f64) -> Value {
let bits = f.to_bits();
if f.is_nan() {
Value(CANONICAL_NAN)
} else {
debug_assert!(
!is_boxed(bits),
"non-NaN float collides with boxed pattern: {:?} = 0x{:016x}",
f,
bits
);
Value(bits)
}
}
#[inline(always)]
pub fn char(c: char) -> Value {
Value(make_boxed(TAG_CHAR, c as u64))
}
#[inline(always)]
pub fn symbol_from_spur(spur: Spur) -> Value {
let bits: u32 = unsafe { std::mem::transmute(spur) };
Value(make_boxed(TAG_SYMBOL, bits as u64))
}
pub fn symbol(s: &str) -> Value {
Value::symbol_from_spur(intern(s))
}
#[inline(always)]
pub fn keyword_from_spur(spur: Spur) -> Value {
let bits: u32 = unsafe { std::mem::transmute(spur) };
Value(make_boxed(TAG_KEYWORD, bits as u64))
}
pub fn keyword(s: &str) -> Value {
Value::keyword_from_spur(intern(s))
}
fn from_rc_ptr<T>(tag: u64, rc: Rc<T>) -> Value {
let ptr = Rc::into_raw(rc) as *const u8;
Value(make_boxed(tag, ptr_to_payload(ptr)))
}
pub fn string(s: &str) -> Value {
Value::from_rc_ptr(TAG_STRING, Rc::new(s.to_string()))
}
pub fn string_from_rc(rc: Rc<String>) -> Value {
Value::from_rc_ptr(TAG_STRING, rc)
}
pub fn list(v: Vec<Value>) -> Value {
Value::from_rc_ptr(TAG_LIST, Rc::new(v))
}
pub fn list_from_rc(rc: Rc<Vec<Value>>) -> Value {
Value::from_rc_ptr(TAG_LIST, rc)
}
pub fn vector(v: Vec<Value>) -> Value {
Value::from_rc_ptr(TAG_VECTOR, Rc::new(v))
}
pub fn vector_from_rc(rc: Rc<Vec<Value>>) -> Value {
Value::from_rc_ptr(TAG_VECTOR, rc)
}
pub fn map(m: BTreeMap<Value, Value>) -> Value {
Value::from_rc_ptr(TAG_MAP, Rc::new(m))
}
pub fn map_from_rc(rc: Rc<BTreeMap<Value, Value>>) -> Value {
Value::from_rc_ptr(TAG_MAP, rc)
}
pub fn hashmap(entries: Vec<(Value, Value)>) -> Value {
let map: hashbrown::HashMap<Value, Value> = entries.into_iter().collect();
Value::from_rc_ptr(TAG_HASHMAP, Rc::new(map))
}
pub fn hashmap_from_rc(rc: Rc<hashbrown::HashMap<Value, Value>>) -> Value {
Value::from_rc_ptr(TAG_HASHMAP, rc)
}
pub fn lambda(l: Lambda) -> Value {
Value::from_rc_ptr(TAG_LAMBDA, Rc::new(l))
}
pub fn lambda_from_rc(rc: Rc<Lambda>) -> Value {
Value::from_rc_ptr(TAG_LAMBDA, rc)
}
pub fn macro_val(m: Macro) -> Value {
Value::from_rc_ptr(TAG_MACRO, Rc::new(m))
}
pub fn macro_from_rc(rc: Rc<Macro>) -> Value {
Value::from_rc_ptr(TAG_MACRO, rc)
}
pub fn native_fn(f: NativeFn) -> Value {
Value::from_rc_ptr(TAG_NATIVE_FN, Rc::new(f))
}
pub fn native_fn_from_rc(rc: Rc<NativeFn>) -> Value {
Value::from_rc_ptr(TAG_NATIVE_FN, rc)
}
pub fn prompt(p: Prompt) -> Value {
Value::from_rc_ptr(TAG_PROMPT, Rc::new(p))
}
pub fn prompt_from_rc(rc: Rc<Prompt>) -> Value {
Value::from_rc_ptr(TAG_PROMPT, rc)
}
pub fn message(m: Message) -> Value {
Value::from_rc_ptr(TAG_MESSAGE, Rc::new(m))
}
pub fn message_from_rc(rc: Rc<Message>) -> Value {
Value::from_rc_ptr(TAG_MESSAGE, rc)
}
pub fn conversation(c: Conversation) -> Value {
Value::from_rc_ptr(TAG_CONVERSATION, Rc::new(c))
}
pub fn conversation_from_rc(rc: Rc<Conversation>) -> Value {
Value::from_rc_ptr(TAG_CONVERSATION, rc)
}
pub fn tool_def(t: ToolDefinition) -> Value {
Value::from_rc_ptr(TAG_TOOL_DEF, Rc::new(t))
}
pub fn tool_def_from_rc(rc: Rc<ToolDefinition>) -> Value {
Value::from_rc_ptr(TAG_TOOL_DEF, rc)
}
pub fn agent(a: Agent) -> Value {
Value::from_rc_ptr(TAG_AGENT, Rc::new(a))
}
pub fn agent_from_rc(rc: Rc<Agent>) -> Value {
Value::from_rc_ptr(TAG_AGENT, rc)
}
pub fn thunk(t: Thunk) -> Value {
Value::from_rc_ptr(TAG_THUNK, Rc::new(t))
}
pub fn thunk_from_rc(rc: Rc<Thunk>) -> Value {
Value::from_rc_ptr(TAG_THUNK, rc)
}
pub fn record(r: Record) -> Value {
Value::from_rc_ptr(TAG_RECORD, Rc::new(r))
}
pub fn record_from_rc(rc: Rc<Record>) -> Value {
Value::from_rc_ptr(TAG_RECORD, rc)
}
pub fn bytevector(bytes: Vec<u8>) -> Value {
Value::from_rc_ptr(TAG_BYTEVECTOR, Rc::new(bytes))
}
pub fn bytevector_from_rc(rc: Rc<Vec<u8>>) -> Value {
Value::from_rc_ptr(TAG_BYTEVECTOR, rc)
}
pub fn f64_array(data: Vec<f64>) -> Value {
Value::from_rc_ptr(TAG_F64_ARRAY, Rc::new(data))
}
pub fn f64_array_from_rc(rc: Rc<Vec<f64>>) -> Value {
Value::from_rc_ptr(TAG_F64_ARRAY, rc)
}
pub fn i64_array(data: Vec<i64>) -> Value {
Value::from_rc_ptr(TAG_I64_ARRAY, Rc::new(data))
}
pub fn i64_array_from_rc(rc: Rc<Vec<i64>>) -> Value {
Value::from_rc_ptr(TAG_I64_ARRAY, rc)
}
pub fn multimethod(m: MultiMethod) -> Value {
Value::from_rc_ptr(TAG_MULTIMETHOD, Rc::new(m))
}
pub fn multimethod_from_rc(rc: Rc<MultiMethod>) -> Value {
Value::from_rc_ptr(TAG_MULTIMETHOD, rc)
}
pub fn stream(s: impl SemaStream + 'static) -> Value {
Value::from_rc_ptr(TAG_STREAM, Rc::new(StreamBox::new(s)))
}
pub fn stream_from_rc(rc: Rc<StreamBox>) -> Value {
Value::from_rc_ptr(TAG_STREAM, rc)
}
}
const fn make_boxed_const(tag: u64, payload: u64) -> u64 {
BOX_MASK | (tag << 45) | (payload & PAYLOAD_MASK)
}
impl Value {
#[inline(always)]
pub fn raw_bits(&self) -> u64 {
self.0
}
#[inline(always)]
pub unsafe fn from_raw_bits(bits: u64) -> Value {
Value(bits)
}
#[inline(always)]
pub fn raw_tag(&self) -> Option<u64> {
if is_boxed(self.0) {
Some(get_tag(self.0))
} else {
None
}
}
#[inline(always)]
pub fn as_native_fn_ref(&self) -> Option<&NativeFn> {
if is_boxed(self.0) && get_tag(self.0) == TAG_NATIVE_FN {
Some(unsafe { self.borrow_ref::<NativeFn>() })
} else {
None
}
}
#[inline(always)]
pub fn is_float(&self) -> bool {
!is_boxed(self.0)
}
#[inline(always)]
unsafe fn get_rc<T>(&self) -> Rc<T> {
let payload = get_payload(self.0);
let ptr = payload_to_ptr(payload) as *const T;
Rc::increment_strong_count(ptr);
Rc::from_raw(ptr)
}
#[inline(always)]
unsafe fn borrow_ref<T>(&self) -> &T {
let payload = get_payload(self.0);
let ptr = payload_to_ptr(payload) as *const T;
&*ptr
}
pub fn view(&self) -> ValueView {
if !is_boxed(self.0) {
return ValueView::Float(f64::from_bits(self.0));
}
let tag = get_tag(self.0);
match tag {
TAG_NIL => ValueView::Nil,
TAG_FALSE => ValueView::Bool(false),
TAG_TRUE => ValueView::Bool(true),
TAG_INT_SMALL => {
let payload = get_payload(self.0);
let val = if payload & INT_SIGN_BIT != 0 {
(payload | !PAYLOAD_MASK) as i64
} else {
payload as i64
};
ValueView::Int(val)
}
TAG_CHAR => {
let payload = get_payload(self.0);
ValueView::Char(unsafe { char::from_u32_unchecked(payload as u32) })
}
TAG_SYMBOL => {
let payload = get_payload(self.0);
let spur: Spur = unsafe { std::mem::transmute(payload as u32) };
ValueView::Symbol(spur)
}
TAG_KEYWORD => {
let payload = get_payload(self.0);
let spur: Spur = unsafe { std::mem::transmute(payload as u32) };
ValueView::Keyword(spur)
}
TAG_INT_BIG => {
let val = unsafe { *self.borrow_ref::<i64>() };
ValueView::Int(val)
}
TAG_STRING => ValueView::String(unsafe { self.get_rc::<String>() }),
TAG_LIST => ValueView::List(unsafe { self.get_rc::<Vec<Value>>() }),
TAG_VECTOR => ValueView::Vector(unsafe { self.get_rc::<Vec<Value>>() }),
TAG_MAP => ValueView::Map(unsafe { self.get_rc::<BTreeMap<Value, Value>>() }),
TAG_HASHMAP => {
ValueView::HashMap(unsafe { self.get_rc::<hashbrown::HashMap<Value, Value>>() })
}
TAG_LAMBDA => ValueView::Lambda(unsafe { self.get_rc::<Lambda>() }),
TAG_MACRO => ValueView::Macro(unsafe { self.get_rc::<Macro>() }),
TAG_NATIVE_FN => ValueView::NativeFn(unsafe { self.get_rc::<NativeFn>() }),
TAG_PROMPT => ValueView::Prompt(unsafe { self.get_rc::<Prompt>() }),
TAG_MESSAGE => ValueView::Message(unsafe { self.get_rc::<Message>() }),
TAG_CONVERSATION => ValueView::Conversation(unsafe { self.get_rc::<Conversation>() }),
TAG_TOOL_DEF => ValueView::ToolDef(unsafe { self.get_rc::<ToolDefinition>() }),
TAG_AGENT => ValueView::Agent(unsafe { self.get_rc::<Agent>() }),
TAG_THUNK => ValueView::Thunk(unsafe { self.get_rc::<Thunk>() }),
TAG_RECORD => ValueView::Record(unsafe { self.get_rc::<Record>() }),
TAG_BYTEVECTOR => ValueView::Bytevector(unsafe { self.get_rc::<Vec<u8>>() }),
TAG_MULTIMETHOD => ValueView::MultiMethod(unsafe { self.get_rc::<MultiMethod>() }),
TAG_STREAM => ValueView::Stream(unsafe { self.get_rc::<StreamBox>() }),
TAG_F64_ARRAY => ValueView::F64Array(unsafe { self.get_rc::<Vec<f64>>() }),
TAG_I64_ARRAY => ValueView::I64Array(unsafe { self.get_rc::<Vec<i64>>() }),
_ => unreachable!("invalid NaN-boxed tag: {}", tag),
}
}
pub fn type_name(&self) -> &'static str {
if !is_boxed(self.0) {
return "float";
}
match get_tag(self.0) {
TAG_NIL => "nil",
TAG_FALSE | TAG_TRUE => "bool",
TAG_INT_SMALL | TAG_INT_BIG => "int",
TAG_CHAR => "char",
TAG_SYMBOL => "symbol",
TAG_KEYWORD => "keyword",
TAG_STRING => "string",
TAG_LIST => "list",
TAG_VECTOR => "vector",
TAG_MAP => "map",
TAG_HASHMAP => "hashmap",
TAG_LAMBDA => "lambda",
TAG_MACRO => "macro",
TAG_NATIVE_FN => "native-fn",
TAG_PROMPT => "prompt",
TAG_MESSAGE => "message",
TAG_CONVERSATION => "conversation",
TAG_TOOL_DEF => "tool",
TAG_AGENT => "agent",
TAG_THUNK => "promise",
TAG_RECORD => "record",
TAG_BYTEVECTOR => "bytevector",
TAG_MULTIMETHOD => "multimethod",
TAG_STREAM => "stream",
TAG_F64_ARRAY => "f64-array",
TAG_I64_ARRAY => "i64-array",
_ => "unknown",
}
}
#[inline(always)]
pub fn is_nil(&self) -> bool {
self.0 == Value::NIL.0
}
#[inline(always)]
pub fn is_truthy(&self) -> bool {
self.0 != Value::NIL.0 && self.0 != Value::FALSE.0
}
#[inline(always)]
pub fn is_falsy(&self) -> bool {
!self.is_truthy()
}
#[inline(always)]
pub fn is_bool(&self) -> bool {
self.0 == Value::TRUE.0 || self.0 == Value::FALSE.0
}
#[inline(always)]
pub fn is_int(&self) -> bool {
is_boxed(self.0) && matches!(get_tag(self.0), TAG_INT_SMALL | TAG_INT_BIG)
}
#[inline(always)]
pub fn is_symbol(&self) -> bool {
is_boxed(self.0) && get_tag(self.0) == TAG_SYMBOL
}
#[inline(always)]
pub fn is_keyword(&self) -> bool {
is_boxed(self.0) && get_tag(self.0) == TAG_KEYWORD
}
#[inline(always)]
pub fn is_string(&self) -> bool {
is_boxed(self.0) && get_tag(self.0) == TAG_STRING
}
#[inline(always)]
pub fn is_list(&self) -> bool {
is_boxed(self.0) && get_tag(self.0) == TAG_LIST
}
#[inline(always)]
pub fn is_pair(&self) -> bool {
if let Some(items) = self.as_list() {
!items.is_empty()
} else {
false
}
}
#[inline(always)]
pub fn is_vector(&self) -> bool {
is_boxed(self.0) && get_tag(self.0) == TAG_VECTOR
}
#[inline(always)]
pub fn is_map(&self) -> bool {
is_boxed(self.0) && matches!(get_tag(self.0), TAG_MAP | TAG_HASHMAP)
}
#[inline(always)]
pub fn is_lambda(&self) -> bool {
is_boxed(self.0) && get_tag(self.0) == TAG_LAMBDA
}
#[inline(always)]
pub fn is_native_fn(&self) -> bool {
is_boxed(self.0) && get_tag(self.0) == TAG_NATIVE_FN
}
#[inline(always)]
pub fn is_thunk(&self) -> bool {
is_boxed(self.0) && get_tag(self.0) == TAG_THUNK
}
#[inline(always)]
pub fn is_record(&self) -> bool {
is_boxed(self.0) && get_tag(self.0) == TAG_RECORD
}
#[inline(always)]
pub fn as_int(&self) -> Option<i64> {
if !is_boxed(self.0) {
return None;
}
match get_tag(self.0) {
TAG_INT_SMALL => {
let payload = get_payload(self.0);
let val = if payload & INT_SIGN_BIT != 0 {
(payload | !PAYLOAD_MASK) as i64
} else {
payload as i64
};
Some(val)
}
TAG_INT_BIG => Some(unsafe { *self.borrow_ref::<i64>() }),
_ => None,
}
}
#[inline(always)]
pub fn as_float(&self) -> Option<f64> {
if !is_boxed(self.0) {
return Some(f64::from_bits(self.0));
}
match get_tag(self.0) {
TAG_INT_SMALL => {
let payload = get_payload(self.0);
let val = if payload & INT_SIGN_BIT != 0 {
(payload | !PAYLOAD_MASK) as i64
} else {
payload as i64
};
Some(val as f64)
}
TAG_INT_BIG => Some(unsafe { *self.borrow_ref::<i64>() } as f64),
_ => None,
}
}
#[inline(always)]
pub fn as_bool(&self) -> Option<bool> {
if self.0 == Value::TRUE.0 {
Some(true)
} else if self.0 == Value::FALSE.0 {
Some(false)
} else {
None
}
}
pub fn as_str(&self) -> Option<&str> {
if is_boxed(self.0) && get_tag(self.0) == TAG_STRING {
Some(unsafe { self.borrow_ref::<String>() })
} else {
None
}
}
pub fn as_string_rc(&self) -> Option<Rc<String>> {
if is_boxed(self.0) && get_tag(self.0) == TAG_STRING {
Some(unsafe { self.get_rc::<String>() })
} else {
None
}
}
pub fn as_symbol(&self) -> Option<String> {
self.as_symbol_spur().map(resolve)
}
pub fn as_symbol_spur(&self) -> Option<Spur> {
if is_boxed(self.0) && get_tag(self.0) == TAG_SYMBOL {
let payload = get_payload(self.0);
Some(unsafe { std::mem::transmute::<u32, Spur>(payload as u32) })
} else {
None
}
}
pub fn as_keyword(&self) -> Option<String> {
self.as_keyword_spur().map(resolve)
}
pub fn as_keyword_spur(&self) -> Option<Spur> {
if is_boxed(self.0) && get_tag(self.0) == TAG_KEYWORD {
let payload = get_payload(self.0);
Some(unsafe { std::mem::transmute::<u32, Spur>(payload as u32) })
} else {
None
}
}
pub fn as_char(&self) -> Option<char> {
if is_boxed(self.0) && get_tag(self.0) == TAG_CHAR {
let payload = get_payload(self.0);
char::from_u32(payload as u32)
} else {
None
}
}
pub fn as_list(&self) -> Option<&[Value]> {
if is_boxed(self.0) && get_tag(self.0) == TAG_LIST {
Some(unsafe { self.borrow_ref::<Vec<Value>>() })
} else {
None
}
}
pub fn as_list_rc(&self) -> Option<Rc<Vec<Value>>> {
if is_boxed(self.0) && get_tag(self.0) == TAG_LIST {
Some(unsafe { self.get_rc::<Vec<Value>>() })
} else {
None
}
}
pub fn as_seq(&self) -> Option<&[Value]> {
self.as_list().or_else(|| self.as_vector())
}
pub fn as_vector(&self) -> Option<&[Value]> {
if is_boxed(self.0) && get_tag(self.0) == TAG_VECTOR {
Some(unsafe { self.borrow_ref::<Vec<Value>>() })
} else {
None
}
}
pub fn as_vector_rc(&self) -> Option<Rc<Vec<Value>>> {
if is_boxed(self.0) && get_tag(self.0) == TAG_VECTOR {
Some(unsafe { self.get_rc::<Vec<Value>>() })
} else {
None
}
}
pub fn as_map_rc(&self) -> Option<Rc<BTreeMap<Value, Value>>> {
if is_boxed(self.0) && get_tag(self.0) == TAG_MAP {
Some(unsafe { self.get_rc::<BTreeMap<Value, Value>>() })
} else {
None
}
}
pub fn as_hashmap_rc(&self) -> Option<Rc<hashbrown::HashMap<Value, Value>>> {
if is_boxed(self.0) && get_tag(self.0) == TAG_HASHMAP {
Some(unsafe { self.get_rc::<hashbrown::HashMap<Value, Value>>() })
} else {
None
}
}
#[inline(always)]
pub fn as_hashmap_ref(&self) -> Option<&hashbrown::HashMap<Value, Value>> {
if is_boxed(self.0) && get_tag(self.0) == TAG_HASHMAP {
Some(unsafe { self.borrow_ref::<hashbrown::HashMap<Value, Value>>() })
} else {
None
}
}
#[inline(always)]
pub fn as_map_ref(&self) -> Option<&BTreeMap<Value, Value>> {
if is_boxed(self.0) && get_tag(self.0) == TAG_MAP {
Some(unsafe { self.borrow_ref::<BTreeMap<Value, Value>>() })
} else {
None
}
}
#[inline(always)]
pub fn with_hashmap_mut_if_unique<R>(
&self,
f: impl FnOnce(&mut hashbrown::HashMap<Value, Value>) -> R,
) -> Option<R> {
if !is_boxed(self.0) || get_tag(self.0) != TAG_HASHMAP {
return None;
}
let payload = get_payload(self.0);
let ptr = payload_to_ptr(payload) as *const hashbrown::HashMap<Value, Value>;
let rc = std::mem::ManuallyDrop::new(unsafe { Rc::from_raw(ptr) });
if Rc::strong_count(&rc) != 1 {
return None;
}
let ptr_mut = ptr as *mut hashbrown::HashMap<Value, Value>;
Some(f(unsafe { &mut *ptr_mut }))
}
#[inline(always)]
pub fn with_map_mut_if_unique<R>(
&self,
f: impl FnOnce(&mut BTreeMap<Value, Value>) -> R,
) -> Option<R> {
if !is_boxed(self.0) || get_tag(self.0) != TAG_MAP {
return None;
}
let payload = get_payload(self.0);
let ptr = payload_to_ptr(payload) as *const BTreeMap<Value, Value>;
let rc = std::mem::ManuallyDrop::new(unsafe { Rc::from_raw(ptr) });
if Rc::strong_count(&rc) != 1 {
return None;
}
let ptr_mut = ptr as *mut BTreeMap<Value, Value>;
Some(f(unsafe { &mut *ptr_mut }))
}
pub fn into_hashmap_rc(self) -> Result<Rc<hashbrown::HashMap<Value, Value>>, Value> {
if is_boxed(self.0) && get_tag(self.0) == TAG_HASHMAP {
let payload = get_payload(self.0);
let ptr = payload_to_ptr(payload) as *const hashbrown::HashMap<Value, Value>;
std::mem::forget(self);
Ok(unsafe { Rc::from_raw(ptr) })
} else {
Err(self)
}
}
pub fn into_map_rc(self) -> Result<Rc<BTreeMap<Value, Value>>, Value> {
if is_boxed(self.0) && get_tag(self.0) == TAG_MAP {
let payload = get_payload(self.0);
let ptr = payload_to_ptr(payload) as *const BTreeMap<Value, Value>;
std::mem::forget(self);
Ok(unsafe { Rc::from_raw(ptr) })
} else {
Err(self)
}
}
pub fn as_lambda_rc(&self) -> Option<Rc<Lambda>> {
if is_boxed(self.0) && get_tag(self.0) == TAG_LAMBDA {
Some(unsafe { self.get_rc::<Lambda>() })
} else {
None
}
}
pub fn as_macro_rc(&self) -> Option<Rc<Macro>> {
if is_boxed(self.0) && get_tag(self.0) == TAG_MACRO {
Some(unsafe { self.get_rc::<Macro>() })
} else {
None
}
}
pub fn as_native_fn_rc(&self) -> Option<Rc<NativeFn>> {
if is_boxed(self.0) && get_tag(self.0) == TAG_NATIVE_FN {
Some(unsafe { self.get_rc::<NativeFn>() })
} else {
None
}
}
pub fn as_thunk_rc(&self) -> Option<Rc<Thunk>> {
if is_boxed(self.0) && get_tag(self.0) == TAG_THUNK {
Some(unsafe { self.get_rc::<Thunk>() })
} else {
None
}
}
pub fn as_record(&self) -> Option<&Record> {
if is_boxed(self.0) && get_tag(self.0) == TAG_RECORD {
Some(unsafe { self.borrow_ref::<Record>() })
} else {
None
}
}
pub fn as_record_rc(&self) -> Option<Rc<Record>> {
if is_boxed(self.0) && get_tag(self.0) == TAG_RECORD {
Some(unsafe { self.get_rc::<Record>() })
} else {
None
}
}
pub fn as_bytevector(&self) -> Option<&[u8]> {
if is_boxed(self.0) && get_tag(self.0) == TAG_BYTEVECTOR {
Some(unsafe { self.borrow_ref::<Vec<u8>>() })
} else {
None
}
}
pub fn as_bytevector_rc(&self) -> Option<Rc<Vec<u8>>> {
if is_boxed(self.0) && get_tag(self.0) == TAG_BYTEVECTOR {
Some(unsafe { self.get_rc::<Vec<u8>>() })
} else {
None
}
}
pub fn as_f64_array(&self) -> Option<&[f64]> {
if is_boxed(self.0) && get_tag(self.0) == TAG_F64_ARRAY {
Some(unsafe { self.borrow_ref::<Vec<f64>>() })
} else {
None
}
}
pub fn as_f64_array_rc(&self) -> Option<Rc<Vec<f64>>> {
if is_boxed(self.0) && get_tag(self.0) == TAG_F64_ARRAY {
Some(unsafe { self.get_rc::<Vec<f64>>() })
} else {
None
}
}
pub fn as_i64_array(&self) -> Option<&[i64]> {
if is_boxed(self.0) && get_tag(self.0) == TAG_I64_ARRAY {
Some(unsafe { self.borrow_ref::<Vec<i64>>() })
} else {
None
}
}
pub fn as_i64_array_rc(&self) -> Option<Rc<Vec<i64>>> {
if is_boxed(self.0) && get_tag(self.0) == TAG_I64_ARRAY {
Some(unsafe { self.get_rc::<Vec<i64>>() })
} else {
None
}
}
pub fn as_stream(&self) -> Option<&StreamBox> {
if is_boxed(self.0) && get_tag(self.0) == TAG_STREAM {
Some(unsafe { self.borrow_ref::<StreamBox>() })
} else {
None
}
}
pub fn as_stream_rc(&self) -> Option<Rc<StreamBox>> {
if is_boxed(self.0) && get_tag(self.0) == TAG_STREAM {
Some(unsafe { self.get_rc::<StreamBox>() })
} else {
None
}
}
pub fn as_prompt_rc(&self) -> Option<Rc<Prompt>> {
if is_boxed(self.0) && get_tag(self.0) == TAG_PROMPT {
Some(unsafe { self.get_rc::<Prompt>() })
} else {
None
}
}
pub fn as_message_rc(&self) -> Option<Rc<Message>> {
if is_boxed(self.0) && get_tag(self.0) == TAG_MESSAGE {
Some(unsafe { self.get_rc::<Message>() })
} else {
None
}
}
pub fn as_conversation_rc(&self) -> Option<Rc<Conversation>> {
if is_boxed(self.0) && get_tag(self.0) == TAG_CONVERSATION {
Some(unsafe { self.get_rc::<Conversation>() })
} else {
None
}
}
pub fn as_tool_def_rc(&self) -> Option<Rc<ToolDefinition>> {
if is_boxed(self.0) && get_tag(self.0) == TAG_TOOL_DEF {
Some(unsafe { self.get_rc::<ToolDefinition>() })
} else {
None
}
}
pub fn as_agent_rc(&self) -> Option<Rc<Agent>> {
if is_boxed(self.0) && get_tag(self.0) == TAG_AGENT {
Some(unsafe { self.get_rc::<Agent>() })
} else {
None
}
}
pub fn as_multimethod_rc(&self) -> Option<Rc<MultiMethod>> {
if is_boxed(self.0) && get_tag(self.0) == TAG_MULTIMETHOD {
Some(unsafe { self.get_rc::<MultiMethod>() })
} else {
None
}
}
}
impl Clone for Value {
#[inline(always)]
fn clone(&self) -> Self {
if !is_boxed(self.0) {
return Value(self.0);
}
let tag = get_tag(self.0);
match tag {
TAG_NIL | TAG_FALSE | TAG_TRUE | TAG_INT_SMALL | TAG_CHAR | TAG_SYMBOL
| TAG_KEYWORD => Value(self.0),
_ => {
let payload = get_payload(self.0);
let ptr = payload_to_ptr(payload);
unsafe {
match tag {
TAG_INT_BIG => Rc::increment_strong_count(ptr as *const i64),
TAG_STRING => Rc::increment_strong_count(ptr as *const String),
TAG_LIST | TAG_VECTOR => {
Rc::increment_strong_count(ptr as *const Vec<Value>)
}
TAG_MAP => Rc::increment_strong_count(ptr as *const BTreeMap<Value, Value>),
TAG_HASHMAP => Rc::increment_strong_count(
ptr as *const hashbrown::HashMap<Value, Value>,
),
TAG_LAMBDA => Rc::increment_strong_count(ptr as *const Lambda),
TAG_MACRO => Rc::increment_strong_count(ptr as *const Macro),
TAG_NATIVE_FN => Rc::increment_strong_count(ptr as *const NativeFn),
TAG_PROMPT => Rc::increment_strong_count(ptr as *const Prompt),
TAG_MESSAGE => Rc::increment_strong_count(ptr as *const Message),
TAG_CONVERSATION => Rc::increment_strong_count(ptr as *const Conversation),
TAG_TOOL_DEF => Rc::increment_strong_count(ptr as *const ToolDefinition),
TAG_AGENT => Rc::increment_strong_count(ptr as *const Agent),
TAG_THUNK => Rc::increment_strong_count(ptr as *const Thunk),
TAG_RECORD => Rc::increment_strong_count(ptr as *const Record),
TAG_BYTEVECTOR => Rc::increment_strong_count(ptr as *const Vec<u8>),
TAG_MULTIMETHOD => Rc::increment_strong_count(ptr as *const MultiMethod),
TAG_STREAM => Rc::increment_strong_count(ptr as *const StreamBox),
TAG_F64_ARRAY => Rc::increment_strong_count(ptr as *const Vec<f64>),
TAG_I64_ARRAY => Rc::increment_strong_count(ptr as *const Vec<i64>),
_ => unreachable!("invalid heap tag in clone: {}", tag),
}
}
Value(self.0)
}
}
}
}
impl Drop for Value {
#[inline(always)]
fn drop(&mut self) {
if !is_boxed(self.0) {
return; }
let tag = get_tag(self.0);
match tag {
TAG_NIL | TAG_FALSE | TAG_TRUE | TAG_INT_SMALL | TAG_CHAR | TAG_SYMBOL
| TAG_KEYWORD => {}
_ => {
let payload = get_payload(self.0);
let ptr = payload_to_ptr(payload);
unsafe {
match tag {
TAG_INT_BIG => drop(Rc::from_raw(ptr as *const i64)),
TAG_STRING => drop(Rc::from_raw(ptr as *const String)),
TAG_LIST | TAG_VECTOR => drop(Rc::from_raw(ptr as *const Vec<Value>)),
TAG_MAP => drop(Rc::from_raw(ptr as *const BTreeMap<Value, Value>)),
TAG_HASHMAP => {
drop(Rc::from_raw(ptr as *const hashbrown::HashMap<Value, Value>))
}
TAG_LAMBDA => drop(Rc::from_raw(ptr as *const Lambda)),
TAG_MACRO => drop(Rc::from_raw(ptr as *const Macro)),
TAG_NATIVE_FN => drop(Rc::from_raw(ptr as *const NativeFn)),
TAG_PROMPT => drop(Rc::from_raw(ptr as *const Prompt)),
TAG_MESSAGE => drop(Rc::from_raw(ptr as *const Message)),
TAG_CONVERSATION => drop(Rc::from_raw(ptr as *const Conversation)),
TAG_TOOL_DEF => drop(Rc::from_raw(ptr as *const ToolDefinition)),
TAG_AGENT => drop(Rc::from_raw(ptr as *const Agent)),
TAG_THUNK => drop(Rc::from_raw(ptr as *const Thunk)),
TAG_RECORD => drop(Rc::from_raw(ptr as *const Record)),
TAG_BYTEVECTOR => drop(Rc::from_raw(ptr as *const Vec<u8>)),
TAG_MULTIMETHOD => drop(Rc::from_raw(ptr as *const MultiMethod)),
TAG_STREAM => drop(Rc::from_raw(ptr as *const StreamBox)),
TAG_F64_ARRAY => drop(Rc::from_raw(ptr as *const Vec<f64>)),
TAG_I64_ARRAY => drop(Rc::from_raw(ptr as *const Vec<i64>)),
_ => {} }
}
}
}
}
}
impl PartialEq for Value {
fn eq(&self, other: &Self) -> bool {
if self.0 == other.0 {
if !is_boxed(self.0) {
let f = f64::from_bits(self.0);
if f.is_nan() {
return false;
}
return true;
}
return true;
}
match (self.view(), other.view()) {
(ValueView::Nil, ValueView::Nil) => true,
(ValueView::Bool(a), ValueView::Bool(b)) => a == b,
(ValueView::Int(a), ValueView::Int(b)) => a == b,
(ValueView::Float(a), ValueView::Float(b)) => a == b,
(ValueView::String(a), ValueView::String(b)) => a == b,
(ValueView::Symbol(a), ValueView::Symbol(b)) => a == b,
(ValueView::Keyword(a), ValueView::Keyword(b)) => a == b,
(ValueView::Char(a), ValueView::Char(b)) => a == b,
(ValueView::List(a), ValueView::List(b)) => a == b,
(ValueView::Vector(a), ValueView::Vector(b)) => a == b,
(ValueView::Map(a), ValueView::Map(b)) => a == b,
(ValueView::HashMap(a), ValueView::HashMap(b)) => a == b,
(ValueView::Record(a), ValueView::Record(b)) => {
a.type_tag == b.type_tag && a.fields == b.fields
}
(ValueView::Bytevector(a), ValueView::Bytevector(b)) => a == b,
(ValueView::F64Array(a), ValueView::F64Array(b)) => {
a.len() == b.len()
&& a.iter()
.zip(b.iter())
.all(|(x, y)| x.to_bits() == y.to_bits())
}
(ValueView::I64Array(a), ValueView::I64Array(b)) => a == b,
(ValueView::Stream(a), ValueView::Stream(b)) => Rc::ptr_eq(&a, &b),
_ => false,
}
}
}
impl Eq for Value {}
impl Hash for Value {
fn hash<H: Hasher>(&self, state: &mut H) {
match self.view() {
ValueView::Nil => 0u8.hash(state),
ValueView::Bool(b) => {
1u8.hash(state);
b.hash(state);
}
ValueView::Int(n) => {
2u8.hash(state);
n.hash(state);
}
ValueView::Float(f) => {
3u8.hash(state);
let bits = if f == 0.0 { 0u64 } else { f.to_bits() };
bits.hash(state);
}
ValueView::String(s) => {
4u8.hash(state);
s.hash(state);
}
ValueView::Symbol(s) => {
5u8.hash(state);
s.hash(state);
}
ValueView::Keyword(s) => {
6u8.hash(state);
s.hash(state);
}
ValueView::Char(c) => {
7u8.hash(state);
c.hash(state);
}
ValueView::List(l) => {
8u8.hash(state);
l.hash(state);
}
ValueView::Vector(v) => {
9u8.hash(state);
v.hash(state);
}
ValueView::Record(r) => {
10u8.hash(state);
r.type_tag.hash(state);
r.fields.hash(state);
}
ValueView::Bytevector(bv) => {
11u8.hash(state);
bv.hash(state);
}
ValueView::F64Array(arr) => {
26u8.hash(state);
for v in arr.iter() {
v.to_bits().hash(state);
}
}
ValueView::I64Array(arr) => {
27u8.hash(state);
arr.hash(state);
}
ValueView::Stream(s) => {
25u8.hash(state);
(Rc::as_ptr(&s) as usize).hash(state);
}
_ => {}
}
}
}
impl PartialOrd for Value {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for Value {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
use std::cmp::Ordering;
fn type_order(v: &Value) -> u8 {
match v.view() {
ValueView::Nil => 0,
ValueView::Bool(_) => 1,
ValueView::Int(_) => 2,
ValueView::Float(_) => 3,
ValueView::Char(_) => 4,
ValueView::String(_) => 5,
ValueView::Symbol(_) => 6,
ValueView::Keyword(_) => 7,
ValueView::List(_) => 8,
ValueView::Vector(_) => 9,
ValueView::Map(_) => 10,
ValueView::HashMap(_) => 11,
ValueView::Record(_) => 12,
ValueView::Bytevector(_) => 13,
ValueView::F64Array(_) => 14,
ValueView::I64Array(_) => 15,
ValueView::Stream(_) => 16,
_ => 17,
}
}
match (self.view(), other.view()) {
(ValueView::Nil, ValueView::Nil) => Ordering::Equal,
(ValueView::Bool(a), ValueView::Bool(b)) => a.cmp(&b),
(ValueView::Int(a), ValueView::Int(b)) => a.cmp(&b),
(ValueView::Float(a), ValueView::Float(b)) => a.total_cmp(&b),
(ValueView::String(a), ValueView::String(b)) => a.cmp(&b),
(ValueView::Symbol(a), ValueView::Symbol(b)) => compare_spurs(a, b),
(ValueView::Keyword(a), ValueView::Keyword(b)) => compare_spurs(a, b),
(ValueView::Char(a), ValueView::Char(b)) => a.cmp(&b),
(ValueView::List(a), ValueView::List(b)) => a.cmp(&b),
(ValueView::Vector(a), ValueView::Vector(b)) => a.cmp(&b),
(ValueView::Record(a), ValueView::Record(b)) => {
compare_spurs(a.type_tag, b.type_tag).then_with(|| a.fields.cmp(&b.fields))
}
(ValueView::Bytevector(a), ValueView::Bytevector(b)) => a.cmp(&b),
(ValueView::I64Array(a), ValueView::I64Array(b)) => a.cmp(&b),
(ValueView::F64Array(a), ValueView::F64Array(b)) => a
.iter()
.zip(b.iter())
.map(|(x, y)| x.total_cmp(y))
.find(|o| *o != std::cmp::Ordering::Equal)
.unwrap_or_else(|| a.len().cmp(&b.len())),
_ => type_order(self).cmp(&type_order(other)),
}
}
}
fn truncate(s: &str, max: usize) -> String {
let mut iter = s.chars();
let prefix: String = iter.by_ref().take(max).collect();
if iter.next().is_none() {
prefix
} else {
format!("{prefix}...")
}
}
impl fmt::Display for Value {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.view() {
ValueView::Nil => write!(f, "nil"),
ValueView::Bool(true) => write!(f, "#t"),
ValueView::Bool(false) => write!(f, "#f"),
ValueView::Int(n) => write!(f, "{n}"),
ValueView::Float(n) => {
if n.fract() == 0.0 {
write!(f, "{n:.1}")
} else {
write!(f, "{n}")
}
}
ValueView::String(s) => write!(f, "\"{s}\""),
ValueView::Symbol(s) => with_resolved(s, |name| write!(f, "{name}")),
ValueView::Keyword(s) => with_resolved(s, |name| write!(f, ":{name}")),
ValueView::Char(c) => match c {
' ' => write!(f, "#\\space"),
'\n' => write!(f, "#\\newline"),
'\t' => write!(f, "#\\tab"),
'\r' => write!(f, "#\\return"),
'\0' => write!(f, "#\\nul"),
_ => write!(f, "#\\{c}"),
},
ValueView::List(items) => {
write!(f, "(")?;
for (i, item) in items.iter().enumerate() {
if i > 0 {
write!(f, " ")?;
}
write!(f, "{item}")?;
}
write!(f, ")")
}
ValueView::Vector(items) => {
write!(f, "[")?;
for (i, item) in items.iter().enumerate() {
if i > 0 {
write!(f, " ")?;
}
write!(f, "{item}")?;
}
write!(f, "]")
}
ValueView::Map(map) => {
write!(f, "{{")?;
for (i, (k, v)) in map.iter().enumerate() {
if i > 0 {
write!(f, " ")?;
}
write!(f, "{k} {v}")?;
}
write!(f, "}}")
}
ValueView::HashMap(map) => {
let mut entries: Vec<_> = map.iter().collect();
entries.sort_by(|(k1, _), (k2, _)| k1.cmp(k2));
write!(f, "{{")?;
for (i, (k, v)) in entries.iter().enumerate() {
if i > 0 {
write!(f, " ")?;
}
write!(f, "{k} {v}")?;
}
write!(f, "}}")
}
ValueView::Lambda(l) => {
if let Some(name) = &l.name {
with_resolved(*name, |n| write!(f, "<lambda {n}>"))
} else {
write!(f, "<lambda>")
}
}
ValueView::Macro(m) => with_resolved(m.name, |n| write!(f, "<macro {n}>")),
ValueView::NativeFn(n) => write!(f, "<native-fn {}>", n.name),
ValueView::Prompt(p) => write!(f, "<prompt {} messages>", p.messages.len()),
ValueView::Message(m) => {
write!(f, "<message {} \"{}\">", m.role, truncate(&m.content, 40))
}
ValueView::Conversation(c) => {
write!(f, "<conversation {} messages>", c.messages.len())
}
ValueView::ToolDef(t) => write!(f, "<tool {}>", t.name),
ValueView::Agent(a) => write!(f, "<agent {}>", a.name),
ValueView::Thunk(t) => {
if t.forced.borrow().is_some() {
write!(f, "<promise (forced)>")
} else {
write!(f, "<promise>")
}
}
ValueView::Record(r) => {
with_resolved(r.type_tag, |tag| write!(f, "#<record {tag}"))?;
for field in &r.fields {
write!(f, " {field}")?;
}
write!(f, ">")
}
ValueView::Bytevector(bv) => {
write!(f, "#u8(")?;
for (i, byte) in bv.iter().enumerate() {
if i > 0 {
write!(f, " ")?;
}
write!(f, "{byte}")?;
}
write!(f, ")")
}
ValueView::F64Array(arr) => {
write!(f, "#f64(")?;
for (i, v) in arr.iter().enumerate() {
if i > 0 {
write!(f, " ")?;
}
write!(f, "{v}")?;
}
write!(f, ")")
}
ValueView::I64Array(arr) => {
write!(f, "#i64(")?;
for (i, v) in arr.iter().enumerate() {
if i > 0 {
write!(f, " ")?;
}
write!(f, "{v}")?;
}
write!(f, ")")
}
ValueView::MultiMethod(m) => with_resolved(m.name, |n| write!(f, "<multimethod {n}>")),
ValueView::Stream(s) => write!(f, "<stream:{}>", s.stream_type()),
}
}
}
pub fn pretty_print(value: &Value, max_width: usize) -> String {
let compact = format!("{value}");
if compact.len() <= max_width {
return compact;
}
let mut buf = String::new();
pp_value(value, 0, max_width, &mut buf);
buf
}
fn pp_value(value: &Value, indent: usize, max_width: usize, buf: &mut String) {
let compact = format!("{value}");
let remaining = max_width.saturating_sub(indent);
if compact.len() <= remaining {
buf.push_str(&compact);
return;
}
match value.view() {
ValueView::List(items) => {
pp_seq(items.iter(), '(', ')', indent, max_width, buf);
}
ValueView::Vector(items) => {
pp_seq(items.iter(), '[', ']', indent, max_width, buf);
}
ValueView::Map(map) => {
pp_map(
map.iter().map(|(k, v)| (k.clone(), v.clone())),
indent,
max_width,
buf,
);
}
ValueView::HashMap(map) => {
let mut entries: Vec<_> = map.iter().map(|(k, v)| (k.clone(), v.clone())).collect();
entries.sort_by(|(k1, _), (k2, _)| k1.cmp(k2));
pp_map(entries.into_iter(), indent, max_width, buf);
}
_ => buf.push_str(&compact),
}
}
fn pp_seq<'a>(
items: impl Iterator<Item = &'a Value>,
open: char,
close: char,
indent: usize,
max_width: usize,
buf: &mut String,
) {
buf.push(open);
let child_indent = indent + 1;
let pad = " ".repeat(child_indent);
for (i, item) in items.enumerate() {
if i > 0 {
buf.push('\n');
buf.push_str(&pad);
}
pp_value(item, child_indent, max_width, buf);
}
buf.push(close);
}
fn pp_map(
entries: impl Iterator<Item = (Value, Value)>,
indent: usize,
max_width: usize,
buf: &mut String,
) {
buf.push('{');
let child_indent = indent + 1;
let pad = " ".repeat(child_indent);
for (i, (k, v)) in entries.enumerate() {
if i > 0 {
buf.push('\n');
buf.push_str(&pad);
}
let key_str = format!("{k}");
buf.push_str(&key_str);
let inline_indent = child_indent + key_str.len() + 1;
let compact_val = format!("{v}");
let remaining = max_width.saturating_sub(inline_indent);
if compact_val.len() <= remaining {
buf.push(' ');
buf.push_str(&compact_val);
} else if is_compound(&v) {
let nested_indent = child_indent + 2;
let nested_pad = " ".repeat(nested_indent);
buf.push('\n');
buf.push_str(&nested_pad);
pp_value(&v, nested_indent, max_width, buf);
} else {
buf.push(' ');
buf.push_str(&compact_val);
}
}
buf.push('}');
}
fn is_compound(value: &Value) -> bool {
matches!(
value.view(),
ValueView::List(_) | ValueView::Vector(_) | ValueView::Map(_) | ValueView::HashMap(_)
)
}
impl fmt::Debug for Value {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.view() {
ValueView::Nil => write!(f, "Nil"),
ValueView::Bool(b) => write!(f, "Bool({b})"),
ValueView::Int(n) => write!(f, "Int({n})"),
ValueView::Float(n) => write!(f, "Float({n})"),
ValueView::String(s) => write!(f, "String({:?})", &**s),
ValueView::Symbol(s) => write!(f, "Symbol({})", resolve(s)),
ValueView::Keyword(s) => write!(f, "Keyword({})", resolve(s)),
ValueView::Char(c) => write!(f, "Char({c:?})"),
ValueView::List(items) => write!(f, "List({items:?})"),
ValueView::Vector(items) => write!(f, "Vector({items:?})"),
ValueView::Map(map) => write!(f, "Map({map:?})"),
ValueView::HashMap(map) => write!(f, "HashMap({map:?})"),
ValueView::Lambda(l) => write!(f, "{l:?}"),
ValueView::Macro(m) => write!(f, "{m:?}"),
ValueView::NativeFn(n) => write!(f, "{n:?}"),
ValueView::Prompt(p) => write!(f, "{p:?}"),
ValueView::Message(m) => write!(f, "{m:?}"),
ValueView::Conversation(c) => write!(f, "{c:?}"),
ValueView::ToolDef(t) => write!(f, "{t:?}"),
ValueView::Agent(a) => write!(f, "{a:?}"),
ValueView::Thunk(t) => write!(f, "{t:?}"),
ValueView::Record(r) => write!(f, "{r:?}"),
ValueView::Bytevector(bv) => write!(f, "Bytevector({bv:?})"),
ValueView::F64Array(arr) => write!(f, "F64Array({arr:?})"),
ValueView::I64Array(arr) => write!(f, "I64Array({arr:?})"),
ValueView::MultiMethod(m) => write!(f, "{m:?}"),
ValueView::Stream(s) => write!(f, "Stream({:?})", s.stream_type()),
}
}
}
#[derive(Debug, Clone)]
pub struct Env {
pub bindings: Rc<RefCell<SpurMap<Spur, Value>>>,
pub parent: Option<Rc<Env>>,
pub version: Cell<u64>,
}
impl Env {
pub fn new() -> Self {
Env {
bindings: Rc::new(RefCell::new(SpurMap::new())),
parent: None,
version: Cell::new(0),
}
}
pub fn with_parent(parent: Rc<Env>) -> Self {
Env {
bindings: Rc::new(RefCell::new(SpurMap::new())),
parent: Some(parent),
version: Cell::new(0),
}
}
fn bump_version(&self) {
self.version.set(self.version.get().wrapping_add(1));
}
pub fn get(&self, name: Spur) -> Option<Value> {
if let Some(val) = self.bindings.borrow().get(&name) {
Some(val.clone())
} else if let Some(parent) = &self.parent {
parent.get(name)
} else {
None
}
}
pub fn get_str(&self, name: &str) -> Option<Value> {
self.get(intern(name))
}
pub fn set(&self, name: Spur, val: Value) {
self.bindings.borrow_mut().insert(name, val);
self.bump_version();
}
pub fn set_str(&self, name: &str, val: Value) {
self.set(intern(name), val);
}
pub fn update(&self, name: Spur, val: Value) {
let mut bindings = self.bindings.borrow_mut();
if let Some(entry) = bindings.get_mut(&name) {
*entry = val;
} else {
bindings.insert(name, val);
}
drop(bindings);
self.bump_version();
}
pub fn take(&self, name: Spur) -> Option<Value> {
let result = self.bindings.borrow_mut().remove(&name);
if result.is_some() {
self.bump_version();
}
result
}
pub fn take_anywhere(&self, name: Spur) -> Option<Value> {
if let Some(val) = self.bindings.borrow_mut().remove(&name) {
self.bump_version();
Some(val)
} else if let Some(parent) = &self.parent {
parent.take_anywhere(name)
} else {
None
}
}
pub fn set_existing(&self, name: Spur, val: Value) -> bool {
let mut bindings = self.bindings.borrow_mut();
if let Some(entry) = bindings.get_mut(&name) {
*entry = val;
drop(bindings);
self.bump_version();
true
} else {
drop(bindings);
if let Some(parent) = &self.parent {
parent.set_existing(name, val)
} else {
false
}
}
}
pub fn all_names(&self) -> Vec<Spur> {
let mut names: Vec<Spur> = self.bindings.borrow().keys().copied().collect();
if let Some(parent) = &self.parent {
names.extend(parent.all_names());
}
names.sort_unstable();
names.dedup();
names
}
pub fn iter_bindings(&self, mut f: impl FnMut(Spur, &Value)) {
let bindings = self.bindings.borrow();
for (&spur, value) in bindings.iter() {
f(spur, value);
}
}
pub fn get_local(&self, name: Spur) -> Option<Value> {
self.bindings.borrow().get(&name).cloned()
}
pub fn replace_bindings(&self, new_bindings: impl IntoIterator<Item = (Spur, Value)>) {
let mut bindings = self.bindings.borrow_mut();
bindings.clear();
for (spur, value) in new_bindings {
bindings.insert(spur, value);
}
drop(bindings);
self.bump_version();
}
}
impl Default for Env {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_size_of_value() {
assert_eq!(std::mem::size_of::<Value>(), 8);
}
#[test]
fn test_nil() {
let v = Value::nil();
assert!(v.is_nil());
assert!(!v.is_truthy());
assert_eq!(v.type_name(), "nil");
assert_eq!(format!("{v}"), "nil");
}
#[test]
fn test_bool() {
let t = Value::bool(true);
let f = Value::bool(false);
assert!(t.is_truthy());
assert!(!f.is_truthy());
assert_eq!(t.as_bool(), Some(true));
assert_eq!(f.as_bool(), Some(false));
assert_eq!(format!("{t}"), "#t");
assert_eq!(format!("{f}"), "#f");
}
#[test]
fn test_small_int() {
let v = Value::int(42);
assert_eq!(v.as_int(), Some(42));
assert_eq!(v.type_name(), "int");
assert_eq!(format!("{v}"), "42");
let neg = Value::int(-100);
assert_eq!(neg.as_int(), Some(-100));
assert_eq!(format!("{neg}"), "-100");
let zero = Value::int(0);
assert_eq!(zero.as_int(), Some(0));
}
#[test]
fn test_small_int_boundaries() {
let max = Value::int(SMALL_INT_MAX);
assert_eq!(max.as_int(), Some(SMALL_INT_MAX));
let min = Value::int(SMALL_INT_MIN);
assert_eq!(min.as_int(), Some(SMALL_INT_MIN));
}
#[test]
fn test_big_int() {
let big = Value::int(i64::MAX);
assert_eq!(big.as_int(), Some(i64::MAX));
assert_eq!(big.type_name(), "int");
let big_neg = Value::int(i64::MIN);
assert_eq!(big_neg.as_int(), Some(i64::MIN));
let just_over = Value::int(SMALL_INT_MAX + 1);
assert_eq!(just_over.as_int(), Some(SMALL_INT_MAX + 1));
}
#[test]
fn test_float() {
let v = Value::float(3.14);
assert_eq!(v.as_float(), Some(3.14));
assert_eq!(v.type_name(), "float");
let neg = Value::float(-0.5);
assert_eq!(neg.as_float(), Some(-0.5));
let inf = Value::float(f64::INFINITY);
assert_eq!(inf.as_float(), Some(f64::INFINITY));
let neg_inf = Value::float(f64::NEG_INFINITY);
assert_eq!(neg_inf.as_float(), Some(f64::NEG_INFINITY));
}
#[test]
fn test_float_nan() {
let nan = Value::float(f64::NAN);
let f = nan.as_float().unwrap();
assert!(f.is_nan());
}
#[test]
fn test_string() {
let v = Value::string("hello");
assert_eq!(v.as_str(), Some("hello"));
assert_eq!(v.type_name(), "string");
assert_eq!(format!("{v}"), "\"hello\"");
}
#[test]
fn test_symbol() {
let v = Value::symbol("foo");
assert!(v.as_symbol_spur().is_some());
assert_eq!(v.as_symbol(), Some("foo".to_string()));
assert_eq!(v.type_name(), "symbol");
assert_eq!(format!("{v}"), "foo");
}
#[test]
fn test_keyword() {
let v = Value::keyword("bar");
assert!(v.as_keyword_spur().is_some());
assert_eq!(v.as_keyword(), Some("bar".to_string()));
assert_eq!(v.type_name(), "keyword");
assert_eq!(format!("{v}"), ":bar");
}
#[test]
fn test_char() {
let v = Value::char('λ');
assert_eq!(v.as_char(), Some('λ'));
assert_eq!(v.type_name(), "char");
}
#[test]
fn test_list() {
let v = Value::list(vec![Value::int(1), Value::int(2), Value::int(3)]);
assert_eq!(v.as_list().unwrap().len(), 3);
assert_eq!(v.type_name(), "list");
assert_eq!(format!("{v}"), "(1 2 3)");
}
#[test]
fn test_clone_immediate() {
let v = Value::int(42);
let v2 = v.clone();
assert_eq!(v.as_int(), v2.as_int());
}
#[test]
fn test_clone_heap() {
let v = Value::string("hello");
let v2 = v.clone();
assert_eq!(v.as_str(), v2.as_str());
assert_eq!(format!("{v}"), format!("{v2}"));
}
#[test]
fn test_equality() {
assert_eq!(Value::int(42), Value::int(42));
assert_ne!(Value::int(42), Value::int(43));
assert_eq!(Value::nil(), Value::nil());
assert_eq!(Value::bool(true), Value::bool(true));
assert_ne!(Value::bool(true), Value::bool(false));
assert_eq!(Value::string("a"), Value::string("a"));
assert_ne!(Value::string("a"), Value::string("b"));
assert_eq!(Value::symbol("x"), Value::symbol("x"));
}
#[test]
fn test_big_int_equality() {
assert_eq!(Value::int(i64::MAX), Value::int(i64::MAX));
assert_ne!(Value::int(i64::MAX), Value::int(i64::MIN));
}
#[test]
fn test_view_pattern_matching() {
let v = Value::int(42);
match v.view() {
ValueView::Int(n) => assert_eq!(n, 42),
_ => panic!("expected int"),
}
let v = Value::string("hello");
match v.view() {
ValueView::String(s) => assert_eq!(&**s, "hello"),
_ => panic!("expected string"),
}
}
#[test]
fn test_env() {
let env = Env::new();
env.set_str("x", Value::int(42));
assert_eq!(env.get_str("x"), Some(Value::int(42)));
}
#[test]
fn test_native_fn_simple() {
let f = NativeFn::simple("add1", |args| Ok(args[0].clone()));
let ctx = EvalContext::new();
assert!((f.func)(&ctx, &[Value::int(42)]).is_ok());
}
#[test]
fn test_native_fn_with_ctx() {
let f = NativeFn::with_ctx("get-depth", |ctx, _args| {
Ok(Value::int(ctx.eval_depth.get() as i64))
});
let ctx = EvalContext::new();
assert_eq!((f.func)(&ctx, &[]).unwrap(), Value::int(0));
}
#[test]
fn test_drop_doesnt_leak() {
for _ in 0..10000 {
let _ = Value::string("test");
let _ = Value::list(vec![Value::int(1), Value::int(2)]);
let _ = Value::int(i64::MAX); }
}
#[test]
fn test_is_truthy() {
assert!(!Value::nil().is_truthy());
assert!(!Value::bool(false).is_truthy());
assert!(Value::bool(true).is_truthy());
assert!(Value::int(0).is_truthy());
assert!(Value::int(1).is_truthy());
assert!(Value::string("").is_truthy());
assert!(Value::list(vec![]).is_truthy());
}
#[test]
fn test_as_float_from_int() {
assert_eq!(Value::int(42).as_float(), Some(42.0));
assert_eq!(Value::float(3.14).as_float(), Some(3.14));
}
#[test]
fn test_next_gensym_unique() {
let a = next_gensym("x");
let b = next_gensym("x");
let c = next_gensym("y");
assert_ne!(a, b);
assert_ne!(a, c);
assert_ne!(b, c);
assert!(a.starts_with("x__"));
assert!(b.starts_with("x__"));
assert!(c.starts_with("y__"));
}
#[test]
fn test_next_gensym_counter_does_not_panic_near_max() {
GENSYM_COUNTER.with(|c| c.set(u64::MAX - 1));
let a = next_gensym("z");
assert!(a.contains(&(u64::MAX - 1).to_string()));
let b = next_gensym("z");
assert!(b.contains(&u64::MAX.to_string()));
let c = next_gensym("z");
assert!(c.contains("__0"));
}
}