feat: recursive builtins

This commit is contained in:
2025-05-05 16:43:14 +08:00
parent bd783f1b96
commit 550ad54f30
11 changed files with 134 additions and 126 deletions

View File

@@ -6,7 +6,7 @@ use nixjit::*;
fn main() -> Result<()> { fn main() -> Result<()> {
let mut rl = DefaultEditor::new()?; let mut rl = DefaultEditor::new()?;
loop { loop {
let readline = rl.readline(">> "); let readline = rl.readline("nixjit-repl> ");
match readline { match readline {
Ok(expr) => { Ok(expr) => {
if expr.trim().is_empty() { if expr.trim().is_empty() {

View File

@@ -1,7 +1,8 @@
use std::cell::RefCell;
use std::sync::Arc; use std::sync::Arc;
use crate::ty::common::Symbol; use crate::ty::common::Symbol;
use crate::ty::internal::{AttrSet, Const, PrimOp, Value}; use crate::ty::internal::{Const, PrimOp, RecAttrSet, Thunk, Value, _Thunk};
use crate::vm::Env; use crate::vm::Env;
pub fn env() -> Arc<Env> { pub fn env() -> Arc<Env> {
@@ -42,15 +43,20 @@ pub fn env() -> Arc<Env> {
}), }),
]; ];
let mut builtins = AttrSet::empty(); let builtins_env = Env::empty();
let map = builtins_env.enter_rec();
for primop in primops { for primop in primops {
env.insert( env.insert(
Symbol::from(format!("__{}", primop.name)), Symbol::from(format!("__{}", primop.name)),
Value::PrimOp(primop.clone()), Value::PrimOp(primop.clone()),
); );
builtins.push_attr(Symbol::from(primop.name), Value::PrimOp(primop)); map.borrow_mut().insert_mut(Symbol::from(primop.name), Value::PrimOp(primop));
} }
let builtins = Value::RecAttrSet(RecAttrSet::from_inner(map.clone()));
let thunk= Thunk { thunk: RefCell::new(_Thunk::Value(Box::new(builtins.clone()))), env: RefCell::default() };
thunk.capture(Arc::new(builtins_env));
map.borrow_mut().insert_mut(Symbol::from("builtins"), Value::Thunk(thunk));
env.insert(Symbol::from("builtins"), Value::AttrSet(builtins)); env.insert(Symbol::from("builtins"), builtins);
env env
} }

View File

@@ -337,27 +337,6 @@ pub enum Param {
}, },
} }
impl Into<i::Param> for Param {
fn into(self) -> i::Param {
use i::Param::*;
match self {
Param::Ident(ident) => Ident(ident),
Param::Formals {
formals,
ellipsis,
alias,
} => Formals {
formals: formals
.into_iter()
.map(|(ident, default)| (ident, default.map(|thunk| thunk.idx)))
.collect(),
ellipsis,
alias,
},
}
}
}
trait Downgrade trait Downgrade
where where
Self: Sized, Self: Sized,

View File

@@ -146,6 +146,10 @@ impl RecAttrSet {
self.data.borrow().clone() self.data.borrow().clone()
} }
pub fn from_inner(data: Arc<RefCell<HashTrieMapSync<Symbol, Value>>>) -> Self {
RecAttrSet { data }
}
pub fn force_deep(&mut self, vm: &VM) -> Result<()> { pub fn force_deep(&mut self, vm: &VM) -> Result<()> {
let mut map: Vec<_> = self let mut map: Vec<_> = self
.data .data

View File

@@ -2,7 +2,7 @@ use ecow::EcoString;
use itertools::Itertools; use itertools::Itertools;
use rpds::HashTrieMap; use rpds::HashTrieMap;
use crate::bytecode::{OpCodes, ThunkIdx}; use crate::bytecode::OpCodes;
use crate::ty::internal::{Thunk, Value}; use crate::ty::internal::{Thunk, Value};
use crate::vm::{CapturedEnv, VM}; use crate::vm::{CapturedEnv, VM};
@@ -11,7 +11,7 @@ use crate::vm::{CapturedEnv, VM};
pub enum Param { pub enum Param {
Ident(EcoString), Ident(EcoString),
Formals { Formals {
formals: Vec<(EcoString, Option<ThunkIdx>)>, formals: Vec<(EcoString, Option<Thunk>)>,
ellipsis: bool, ellipsis: bool,
alias: Option<EcoString>, alias: Option<EcoString>,
}, },
@@ -60,7 +60,7 @@ impl Func {
for (formal, default) in formals { for (formal, default) in formals {
let arg = arg let arg = arg
.select(formal.clone().into()) .select(formal.clone().into())
.or_else(|| default.map(|idx| Value::Thunk(Thunk(idx)))) .or_else(|| default.map(|thunk| Value::Thunk(thunk)))
.unwrap(); .unwrap();
new.insert_mut(formal.into(), arg); new.insert_mut(formal.into(), arg);
} }
@@ -89,7 +89,7 @@ impl Func {
formals.push((param, None)); formals.push((param, None));
} }
pub fn push_default_param(&mut self, default: ThunkIdx) { pub fn push_default_param(&mut self, default: Thunk) {
let Param::Formals { formals, .. } = self.param.as_mut().unwrap() else { let Param::Formals { formals, .. } = self.param.as_mut().unwrap() else {
panic!() panic!()
}; };

View File

@@ -1,12 +1,17 @@
use anyhow::Result; use std::cell::RefCell;
use derive_more::{Constructor, IsVariant, Unwrap}; use std::sync::Arc;
use anyhow::{anyhow, Result};
use derive_more::{IsVariant, Unwrap};
use super::common::Catchable;
use super::common as c; use super::common as c;
use super::public as p; use super::public as p;
use c::Symbol; use c::Symbol;
use crate::vm::VM; use crate::vm::{VM, Env};
use crate::bytecode::OpCodes;
mod attrset; mod attrset;
mod cnst; mod cnst;
@@ -26,8 +31,75 @@ pub trait ToPublic {
fn to_public(self, vm: &VM) -> p::Value; fn to_public(self, vm: &VM) -> p::Value;
} }
#[derive(Debug, PartialEq, Eq, Clone, Copy, Constructor)] #[derive(Debug, Clone)]
pub struct Thunk(usize); pub struct Thunk {
pub thunk: RefCell<_Thunk>,
pub env: RefCell<Option<Arc<Env>>>,
}
#[derive(Debug, IsVariant, Unwrap, Clone)]
pub enum _Thunk {
Code(OpCodes),
SuspendedFrom(*const Thunk),
Value(Box<Value>),
}
impl Thunk {
pub fn new(opcodes: OpCodes) -> Thunk {
Thunk {
thunk: RefCell::new(_Thunk::Code(opcodes)),
env: RefCell::new(None),
}
}
pub fn unwrap_code(&self) -> OpCodes {
self.thunk.borrow().clone().unwrap_code()
}
pub fn capture(&self, env: Arc<Env>) {
*self.env.borrow_mut() = Some(env);
}
pub fn force(&self, vm: &VM) -> Result<Value> {
{
match &*self.thunk.borrow() {
_Thunk::Value(value) => return Ok(value.as_ref().clone()),
_Thunk::SuspendedFrom(from) => {
return Err(anyhow!(
"already suspended from {from:p} (infinite recursion encountered)"
));
}
_Thunk::Code(_) => (),
}
}
let opcodes = std::mem::replace(
&mut *self.thunk.borrow_mut(),
_Thunk::SuspendedFrom(self as *const Thunk),
)
.unwrap_code();
let value = vm
.eval(opcodes, self.env.borrow().clone().unwrap())
.unwrap();
let _ = std::mem::replace(
&mut *self.thunk.borrow_mut(),
_Thunk::Value(value.clone().into()),
);
Ok(value)
}
pub fn value(&self) -> Option<Value> {
match &*self.thunk.borrow() {
_Thunk::Value(value) => Some(value.as_ref().clone()),
_ => None,
}
}
}
impl PartialEq for Thunk {
fn eq(&self, _: &Self) -> bool {
false
}
}
#[derive(Debug, IsVariant, Unwrap, Clone, PartialEq)] #[derive(Debug, IsVariant, Unwrap, Clone, PartialEq)]
pub enum Value { pub enum Value {
@@ -104,6 +176,21 @@ impl Value {
use Value::Const as VmConst; use Value::Const as VmConst;
impl Value { impl Value {
pub fn typename(&self) -> &'static str {
use Value::*;
match self {
Const(_) => todo!(),
Thunk(_) => "thunk",
AttrSet(_) => "set",
RecAttrSet(_) => "set",
List(_) => "list",
Catchable(_) => todo!(),
PrimOp(_) => "lambda",
PartialPrimOp(_) => "lambda",
Func(_) => "lambda"
}
}
pub fn callable(&self) -> bool { pub fn callable(&self) -> bool {
match self { match self {
Value::PrimOp(_) | Value::PartialPrimOp(_) | Value::Func(_) => true, Value::PrimOp(_) | Value::PartialPrimOp(_) | Value::Func(_) => true,
@@ -133,6 +220,7 @@ impl Value {
} }
func func
} }
x @ Catchable(_) => x,
_ => todo!(), _ => todo!(),
} }
} }
@@ -141,6 +229,7 @@ impl Value {
use Const::*; use Const::*;
match self { match self {
VmConst(Bool(bool)) => VmConst(Bool(!bool)), VmConst(Bool(bool)) => VmConst(Bool(!bool)),
x @ Value::Catchable(_) => x,
_ => todo!(), _ => todo!(),
} }
} }
@@ -291,7 +380,7 @@ impl Value {
))))); )))));
*self = val; *self = val;
} else { } else {
todo!() *self = Value::Catchable(Catchable::new(Some(format!("cannot select from {:?}", self.typename()))))
} }
self self
} }
@@ -333,7 +422,7 @@ impl Value {
pub fn force(&mut self, vm: &VM) -> Result<&mut Self> { pub fn force(&mut self, vm: &VM) -> Result<&mut Self> {
if let Value::Thunk(thunk) = self { if let Value::Thunk(thunk) = self {
let value = vm.get_thunk_value(thunk.0)?; let value = thunk.force(vm)?;
*self = value *self = value
} }
Ok(self) Ok(self)
@@ -342,7 +431,7 @@ impl Value {
pub fn force_deep(&mut self, vm: &VM) -> Result<&mut Self> { pub fn force_deep(&mut self, vm: &VM) -> Result<&mut Self> {
match self { match self {
Value::Thunk(thunk) => { Value::Thunk(thunk) => {
let mut value = vm.get_thunk_value(thunk.0)?; let mut value = thunk.force(vm)?;
value.force_deep(vm)?; value.force_deep(vm)?;
*self = value; *self = value;
} }

View File

@@ -16,9 +16,14 @@ pub struct AttrSet {
impl Debug for AttrSet { impl Debug for AttrSet {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
use Value::*;
write!(f, "{{ ")?; write!(f, "{{ ")?;
for (k, v) in self.data.iter() { for (k, v) in self.data.iter() {
write!(f, "{k:?} = {v:?}; ")?; match v {
List(_) => write!(f, "{k:?} = [ ... ]; ")?,
AttrSet(_) => write!(f, "{k:?} = {{ ... }}; ")?,
v => write!(f, "{k:?} = {v:?}; ")?
}
} }
write!(f, "}}") write!(f, "}}")
} }

View File

@@ -1,7 +1,6 @@
mod env; mod env;
mod stack; mod stack;
mod vm; mod vm;
mod vmthunk;
#[cfg(test)] #[cfg(test)]
mod test; mod test;

View File

@@ -149,7 +149,7 @@ fn test_attrs() {
); );
test_expr("{ a.b = 1; } ? a.b", boolean!(true)); test_expr("{ a.b = 1; } ? a.b", boolean!(true));
test_expr( test_expr(
"{ a.b = 1; } // { a.c = 2 }", "{ a.b = 1; } // { a.c = 2; }",
attrs! { symbol!("a") => attrs!{ symbol!("c") => thunk!() } }, attrs! { symbol!("a") => attrs!{ symbol!("c") => thunk!() } },
); );
} }

View File

@@ -10,7 +10,6 @@ use crate::ty::public as p;
use super::env::Env; use super::env::Env;
use super::stack::{STACK_SIZE, Stack}; use super::stack::{STACK_SIZE, Stack};
use super::vmthunk::*;
pub fn run(prog: Program) -> Result<p::Value> { pub fn run(prog: Program) -> Result<p::Value> {
let vm = VM::new(prog.thunks); let vm = VM::new(prog.thunks);
@@ -18,20 +17,20 @@ pub fn run(prog: Program) -> Result<p::Value> {
} }
pub struct VM { pub struct VM {
thunks: Box<[VmThunk]>, thunks: Box<[Thunk]>,
} }
impl VM { impl VM {
fn new(thunks: Thunks) -> Self { fn new(thunks: Thunks) -> Self {
let thunks = thunks let thunks = thunks
.into_iter() .into_iter()
.map(|bytecode::Thunk { opcodes }| VmThunk::new(opcodes)) .map(|bytecode::Thunk { opcodes }| Thunk::new(opcodes))
.collect(); .collect();
VM { thunks } VM { thunks }
} }
pub fn get_thunk_value(&self, idx: usize) -> Result<Value> { pub fn get_thunk(&self, idx: usize) -> Thunk {
self.thunks.get(idx).unwrap().force(self) self.thunks[idx].clone()
} }
pub fn eval(&self, opcodes: OpCodes, env: Arc<Env>) -> Result<Value> { pub fn eval(&self, opcodes: OpCodes, env: Arc<Env>) -> Result<Value> {
@@ -63,10 +62,10 @@ impl VM {
OpCode::Const { value } => stack.push(Value::Const(value))?, OpCode::Const { value } => stack.push(Value::Const(value))?,
OpCode::LoadThunk { idx } => { OpCode::LoadThunk { idx } => {
self.thunks[idx].capture(env); self.thunks[idx].capture(env);
stack.push(Value::Thunk(Thunk::new(idx)))? stack.push(Value::Thunk(self.thunks[idx].clone()))?
} }
OpCode::LoadValue { idx } => { OpCode::LoadValue { idx } => {
stack.push(self.get_thunk_value(idx)?)?; stack.push(self.get_thunk(idx).force(self)?)?;
} }
OpCode::ForceValue => { OpCode::ForceValue => {
stack.tos_mut()?.force(self)?; stack.tos_mut()?.force(self)?;
@@ -116,7 +115,7 @@ impl VM {
.tos_mut()? .tos_mut()?
.as_mut() .as_mut()
.unwrap_func() .unwrap_func()
.push_default_param(idx); .push_default_param(self.get_thunk(idx));
} }
OpCode::SetEllipsis => { OpCode::SetEllipsis => {
stack.tos_mut()?.as_mut().unwrap_func().set_ellipsis(); stack.tos_mut()?.as_mut().unwrap_func().set_ellipsis();
@@ -187,6 +186,7 @@ impl VM {
let default = stack.pop()?; let default = stack.pop()?;
stack stack
.tos_mut()? .tos_mut()?
.force(self)?
.select_with_default(Symbol::new(sym), default); .select_with_default(Symbol::new(sym), default);
} }
OpCode::SelectDynamic => { OpCode::SelectDynamic => {
@@ -202,16 +202,16 @@ impl VM {
val.force(self)?; val.force(self)?;
val.coerce_to_string(); val.coerce_to_string();
let sym = val.unwrap_const().unwrap_string().into(); let sym = val.unwrap_const().unwrap_string().into();
stack.tos_mut()?.select_with_default(sym, default); stack.tos_mut()?.force(self)?.select_with_default(sym, default);
} }
OpCode::HasAttr { sym } => { OpCode::HasAttr { sym } => {
stack.tos_mut()?.has_attr(Symbol::new(sym)); stack.tos_mut()?.force(self)?.has_attr(Symbol::new(sym));
} }
OpCode::HasDynamicAttr => { OpCode::HasDynamicAttr => {
let mut val = stack.pop().unwrap(); let mut val = stack.pop().unwrap();
val.coerce_to_string(); val.coerce_to_string();
let sym = val.unwrap_const().unwrap_string().into(); let sym = val.unwrap_const().unwrap_string().into();
stack.tos_mut()?.has_attr(sym); stack.tos_mut()?.force(self)?.has_attr(sym);
} }
OpCode::LookUp { sym } => { OpCode::LookUp { sym } => {
stack.push( stack.push(

View File

@@ -1,74 +0,0 @@
use std::cell::RefCell;
use std::sync::Arc;
use anyhow::{Result, anyhow};
use derive_more::{IsVariant, Unwrap};
use crate::bytecode::OpCodes;
use super::env::Env;
use super::vm::VM;
use crate::ty::internal::Value;
pub struct VmThunk {
thunk: RefCell<_VmThunk>,
env: RefCell<Option<Arc<Env>>>,
}
#[derive(IsVariant, Unwrap, Clone)]
enum _VmThunk {
Code(OpCodes),
SuspendedFrom(*const VmThunk),
Value(Value),
}
impl VmThunk {
pub fn new(opcodes: OpCodes) -> VmThunk {
VmThunk {
thunk: RefCell::new(_VmThunk::Code(opcodes)),
env: RefCell::new(None),
}
}
pub fn unwrap_code(&self) -> OpCodes {
self.thunk.borrow().clone().unwrap_code()
}
pub fn capture(&self, env: Arc<Env>) {
*self.env.borrow_mut() = Some(env);
}
pub fn force(&self, vm: &VM) -> Result<Value> {
{
match &*self.thunk.borrow() {
_VmThunk::Value(value) => return Ok(value.clone()),
_VmThunk::SuspendedFrom(from) => {
return Err(anyhow!(
"already suspended from {from:p} (infinite recursion encountered)"
));
}
_VmThunk::Code(_) => (),
}
}
let opcodes = std::mem::replace(
&mut *self.thunk.borrow_mut(),
_VmThunk::SuspendedFrom(self as *const VmThunk),
)
.unwrap_code();
let value = vm
.eval(opcodes, self.env.borrow().clone().unwrap())
.unwrap();
let _ = std::mem::replace(
&mut *self.thunk.borrow_mut(),
_VmThunk::Value(value.clone()),
);
Ok(value)
}
pub fn value(&self) -> Option<Value> {
match &*self.thunk.borrow() {
_VmThunk::Value(value) => Some(value.clone()),
_ => None,
}
}
}