feat: TODO

This commit is contained in:
2025-08-28 18:18:35 +08:00
parent 2fbd2a26a9
commit f7131079e5
26 changed files with 580 additions and 580 deletions

View File

@@ -12,9 +12,9 @@ use std::rc::Rc;
use hashbrown::HashMap;
use nixjit_error::{Error, Result};
use nixjit_ir::{self as ir, ExprId, PrimOpId, StackIdx};
use nixjit_ir::{self as ir, ExprId, PrimOpId, SymId};
use nixjit_lir as lir;
use nixjit_value::{Const, format_symbol};
use nixjit_value::format_symbol;
pub use crate::value::*;
@@ -22,12 +22,13 @@ mod value;
/// A trait defining the context in which LIR expressions are evaluated.
pub trait EvalContext {
fn eval_root(self, expr: ExprId) -> Result<Value>;
fn eval(&mut self, id: ExprId) -> Result<Value>;
/// Evaluates an expression by its ID.
fn eval(&mut self, expr: ExprId) -> Result<Value>;
fn resolve(&mut self, id: ExprId) -> Result<ValueId>;
fn call(&mut self, func: ExprId, arg: Option<Value>, frame: StackFrame) -> Result<Value>;
fn force(&mut self, id: ValueId) -> Result<Value>;
fn call(&mut self, func: ValueId, arg: Value) -> Result<Value>;
/// Enters a `with` scope for the duration of a closure's execution.
fn with_with_env<T>(
@@ -36,18 +37,14 @@ pub trait EvalContext {
f: impl FnOnce(&mut Self) -> T,
) -> T;
/// Looks up a stack slot on the current stack frame.
fn lookup_stack(&self, idx: StackIdx) -> &Value;
fn capture_stack(&self) -> &StackFrame;
/// Looks up an identifier in the current `with` scope chain.
fn lookup_with<'a>(&'a self, ident: &str) -> Option<&'a Value>;
fn lookup_with<'a>(&'a self, ident: SymId) -> Option<&'a Value>;
/// Calls a primitive operation (builtin) by its ID.
fn call_primop(&mut self, id: PrimOpId, args: Args) -> Result<Value>;
fn get_primop_arity(&self, id: PrimOpId) -> usize;
fn new_sym(&mut self, sym: String) -> SymId;
fn get_sym(&self, id: SymId) -> &str;
}
/// A trait for types that can be evaluated within an `EvalContext`.
@@ -83,18 +80,12 @@ impl<Ctx: EvalContext> Evaluate<Ctx> for lir::Lir {
Str(x) => x.eval(ctx),
Var(x) => x.eval(ctx),
Path(x) => x.eval(ctx),
&StackRef(idx) => {
let mut val = ctx.lookup_stack(idx).clone();
val.force(ctx)?;
Ok(val)
}
&ExprRef(expr) => ctx.eval(expr),
&FuncRef(body) => Ok(Value::Closure(
Closure::new(body, ctx.capture_stack().clone()).into(),
)),
&FuncRef(body) => ctx.resolve(body).map(Value::Closure),
&Arg(_) => unreachable!(),
&PrimOp(primop) => Ok(Value::PrimOp(primop)),
&Thunk(id) => Ok(Value::Thunk(id)),
&Thunk(id) => ctx.resolve(id).map(Value::Thunk),
&StackRef(idx) => todo!(),
}
}
}
@@ -105,15 +96,17 @@ impl<Ctx: EvalContext> Evaluate<Ctx> for ir::AttrSet {
let mut attrs = AttrSet::new(
self.stcs
.iter()
.map(|(k, v)| {
.map(|(&k, &v)| {
let eval_result = v.eval(ctx);
Ok((k.clone(), eval_result?))
Ok((k, eval_result?))
})
.collect::<Result<_>>()?,
);
for (k, v) in self.dyns.iter() {
let v = v.eval(ctx)?;
attrs.push_attr(k.eval(ctx)?.force_string_no_ctx()?, v)?;
let sym = k.eval(ctx)?.force_string_no_ctx()?;
let sym = ctx.new_sym(sym);
attrs.push_attr(sym, v, ctx)?;
}
let result = Value::AttrSet(attrs.into());
Ok(result)
@@ -138,9 +131,14 @@ impl<Ctx: EvalContext> Evaluate<Ctx> for ir::HasAttr {
fn eval(&self, ctx: &mut Ctx) -> Result<Value> {
use ir::Attr::*;
let mut val = self.lhs.eval(ctx)?;
val.has_attr(self.rhs.iter().map(|attr| match attr {
Str(ident) => Ok(Value::String(ident.clone())),
Dynamic(expr) => expr.eval(ctx),
val.has_attr(self.rhs.iter().map(|attr| {
match attr {
&Str(ident) => Ok(ident),
Dynamic(expr) => expr
.eval(ctx)?
.force_string_no_ctx()
.map(|sym| ctx.new_sym(sym)),
}
}))?;
Ok(val)
}
@@ -165,9 +163,9 @@ impl<Ctx: EvalContext> Evaluate<Ctx> for ir::BinOp {
}
Mul => lhs.mul(rhs)?,
Div => lhs.div(rhs)?,
Eq => Value::eq(&mut lhs, rhs),
Eq => lhs.eq(rhs),
Neq => {
Value::eq(&mut lhs, rhs);
lhs.eq(rhs);
let _ = lhs.not();
}
Lt => lhs.lt(rhs)?,
@@ -182,12 +180,12 @@ impl<Ctx: EvalContext> Evaluate<Ctx> for ir::BinOp {
}
Geq => {
lhs.lt(rhs)?;
let _ = lhs.not()?;
let _ = lhs.not();
}
And => lhs.and(rhs)?,
Or => lhs.or(rhs)?,
Impl => {
let _ = lhs.not();
lhs.not()?;
lhs.or(rhs)?;
}
Con => lhs.concat(rhs)?,
@@ -226,12 +224,11 @@ impl<Ctx: EvalContext> Evaluate<Ctx> for ir::Select {
use ir::Attr::*;
let mut val = self.expr.eval(ctx)?;
for attr in self.attrpath.iter() {
let name_val;
let name = match attr {
Str(name) => name,
&Str(name) => name,
Dynamic(expr) => {
name_val = expr.eval(ctx)?;
&*name_val.force_string_no_ctx()?
let sym = expr.eval(ctx)?.force_string_no_ctx()?;
ctx.new_sym(sym)
}
};
if let Some(default) = self.default {
@@ -338,11 +335,9 @@ impl<Ctx: EvalContext> Evaluate<Ctx> for ir::Var {
/// Evaluates a `Var` by looking it up in the `with` scope chain.
/// This is for variables that could not be resolved statically.
fn eval(&self, ctx: &mut Ctx) -> Result<Value> {
ctx.lookup_with(&self.sym)
.ok_or_else(|| {
Error::eval_error(format!("undefined variable '{}'", format_symbol(&self.sym)))
})
.map(|val| val.clone())
ctx.lookup_with(self.sym).cloned().ok_or_else(|| {
Error::eval_error(format!("undefined variable '{}'", format_symbol(ctx.get_sym(self.sym))))
})
}
}

View File

@@ -10,7 +10,7 @@ use hashbrown::hash_map::Entry;
use itertools::Itertools;
use nixjit_error::{Error, Result};
use nixjit_ir::ExprId;
use nixjit_ir::{ExprId, SymId};
use nixjit_value::{self as p, format_symbol};
use crate::EvalContext;
@@ -24,7 +24,7 @@ use super::Value;
#[repr(transparent)]
#[derive(Clone, Constructor)]
pub struct AttrSet {
data: HashMap<String, Value>,
data: HashMap<SymId, Value>,
}
impl Debug for AttrSet {
@@ -33,23 +33,23 @@ impl Debug for AttrSet {
write!(f, "{{ ")?;
for (k, v) in self.data.iter() {
match v {
List(_) => write!(f, "{} = [ ... ]; ", format_symbol(k))?,
AttrSet(_) => write!(f, "{} = {{ ... }}; ", format_symbol(k))?,
v => write!(f, "{} = {v:?}; ", format_symbol(k))?,
List(_) => write!(f, "{:?} = [ ... ]; ", k)?,
AttrSet(_) => write!(f, "{:?} = {{ ... }}; ", k)?,
v => write!(f, "{:?} = {v:?}; ", k)?,
}
}
write!(f, "}}")
}
}
impl From<HashMap<String, Value>> for AttrSet {
fn from(data: HashMap<String, Value>) -> Self {
impl From<HashMap<SymId, Value>> for AttrSet {
fn from(data: HashMap<SymId, Value>) -> Self {
Self { data }
}
}
impl Deref for AttrSet {
type Target = HashMap<String, Value>;
type Target = HashMap<SymId, Value>;
fn deref(&self) -> &Self::Target {
&self.data
}
@@ -64,16 +64,16 @@ impl AttrSet {
}
/// Inserts an attribute, overwriting any existing attribute with the same name.
pub fn push_attr_force(&mut self, sym: String, val: Value) {
pub fn push_attr_force(&mut self, sym: SymId, val: Value) {
self.data.insert(sym, val);
}
/// Inserts an attribute, returns an error if the attribute is already defined.
pub fn push_attr(&mut self, sym: String, val: Value) -> Result<()> {
pub fn push_attr(&mut self, sym: SymId, val: Value, ctx: &mut impl EvalContext) -> Result<()> {
match self.data.entry(sym) {
Entry::Occupied(occupied) => Err(Error::eval_error(format!(
"attribute '{}' already defined",
format_symbol(occupied.key())
format_symbol(ctx.get_sym(*occupied.key()))
))),
Entry::Vacant(vacant) => {
vacant.insert(val);
@@ -82,29 +82,29 @@ impl AttrSet {
}
}
pub fn select(&self, name: &str, ctx: &mut impl EvalContext) -> Result<Value> {
pub fn select(&self, name: SymId, ctx: &mut impl EvalContext) -> Result<Value> {
self.data
.get(name)
.get(&name)
.cloned()
.map(|attr| match attr {
Value::Thunk(id) => ctx.eval(id),
Value::Thunk(id) => ctx.force(id),
val => Ok(val),
})
.ok_or_else(|| {
Error::eval_error(format!("attribute '{}' not found", format_symbol(name)))
Error::eval_error(format!("attribute '{}' not found", format_symbol(ctx.get_sym(name))))
})?
}
pub fn select_or(
&self,
name: &str,
name: SymId,
default: ExprId,
ctx: &mut impl EvalContext,
) -> Result<Value> {
self.data
.get(name)
.get(&name)
.map(|attr| match attr {
&Value::Thunk(id) => ctx.eval(id),
&Value::Thunk(id) => ctx.force(id),
val => Ok(val.clone()),
})
.unwrap_or_else(|| ctx.eval(default))
@@ -113,19 +113,19 @@ impl AttrSet {
/// Checks if an attribute path exists within the set.
pub fn has_attr(
&self,
mut path: impl DoubleEndedIterator<Item = Result<Value>>,
mut path: impl DoubleEndedIterator<Item = Result<SymId>>,
) -> Result<Value> {
let mut data = &self.data;
let last = path.nth_back(0).unwrap();
for item in path {
let Some(Value::AttrSet(attrs)) = data.get(&item.unwrap().force_string_no_ctx()?)
let Some(Value::AttrSet(attrs)) = data.get(&item?)
else {
return Ok(Value::Bool(false));
};
data = attrs.as_inner();
}
Ok(Value::Bool(
data.get(&last.unwrap().force_string_no_ctx()?).is_some(),
data.get(&last?).is_some(),
))
}
@@ -138,24 +138,18 @@ impl AttrSet {
}
/// Returns a reference to the inner `HashMap`.
pub fn as_inner(&self) -> &HashMap<String, Value> {
pub fn as_inner(&self) -> &HashMap<SymId, Value> {
&self.data
}
/// Converts an `Rc<AttrSet>` to an `Rc<HashMap<String, Value>>` without allocation.
///
/// # Safety
///
/// This is safe because `AttrSet` is `#[repr(transparent)]`.
pub fn into_inner(self: Rc<Self>) -> Rc<HashMap<String, Value>> {
// SAFETY: This is safe because `AttrSet` is `#[repr(transparent)]` over
// `HashMap<String, Value>`, so `Rc<Self>` has the same layout as
// `Rc<HashMap<String, Value>>`.
unsafe { core::mem::transmute(self) }
}
/// Creates an `AttrSet` from a `HashMap`.
pub fn from_inner(data: HashMap<String, Value>) -> Self {
Self { data }
}
/// Performs a deep equality comparison between two `AttrSet`s.
///
/// It recursively compares the contents of both sets, ensuring that both keys
@@ -171,11 +165,11 @@ impl AttrSet {
}
/// Converts the `AttrSet` to its public-facing representation.
pub fn to_public(self) -> p::Value {
pub fn to_public(self, ctx: &mut impl EvalContext) -> p::Value {
p::Value::AttrSet(p::AttrSet::new(
self.data
.into_iter()
.map(|(sym, value)| (sym.into(), value.to_public()))
.map(|(sym, value)| (ctx.get_sym(sym).into(), value.to_public(ctx)))
.collect(),
))
}

View File

@@ -1,29 +0,0 @@
//! Defines the runtime representation of a partially applied function.
use std::rc::Rc;
use derive_more::Constructor;
use nixjit_error::Result;
use nixjit_ir::ExprId;
use super::Value;
use crate::EvalContext;
pub type StackFrame = smallvec::SmallVec<[Value; 5]>;
#[derive(Debug, Clone, Constructor)]
pub struct Closure {
pub body: ExprId,
pub frame: StackFrame,
}
impl Closure {
pub fn call<Ctx: EvalContext>(
self: Rc<Self>,
arg: Option<Value>,
ctx: &mut Ctx,
) -> Result<Value> {
let Self { body: func, frame } = Rc::unwrap_or_clone(self);
ctx.call(func, arg, frame)
}
}

View File

@@ -70,7 +70,7 @@ impl List {
self.data
.get(idx)
.map(|elem| match elem {
&Value::Thunk(id) => ctx.eval(id),
&Value::Thunk(id) => ctx.force(id),
val => Ok(val.clone()),
})
.ok_or_else(|| {
@@ -93,11 +93,11 @@ impl List {
}
/// Converts the `List` to its public-facing representation.
pub fn to_public(&self) -> PubValue {
pub fn to_public(&self, ctx: &mut impl EvalContext) -> PubValue {
PubValue::List(PubList::new(
self.data
.iter()
.map(|value| value.clone().to_public())
.map(|value| value.clone().to_public(ctx))
.collect(),
))
}

View File

@@ -13,6 +13,7 @@ use nixjit_ir::ExprId;
use nixjit_ir::PrimOpId;
use nixjit_error::{Error, Result};
use nixjit_ir::SymId;
use nixjit_value::Const;
use nixjit_value::Value as PubValue;
use replace_with::replace_with_and_return;
@@ -21,13 +22,11 @@ use smallvec::smallvec;
use crate::EvalContext;
mod attrset;
mod closure;
mod list;
mod primop;
mod string;
pub use attrset::AttrSet;
pub use closure::*;
pub use list::List;
pub use primop::*;
@@ -45,14 +44,12 @@ pub enum Value {
Bool(bool) = Self::BOOL,
String(String) = Self::STRING,
Null = Self::NULL,
Thunk(ExprId) = Self::THUNK,
ClosureThunk(Rc<Closure>) = Self::CLOSURE_THUNK,
Thunk(ValueId) = Self::THUNK,
AttrSet(Rc<AttrSet>) = Self::ATTRSET,
List(Rc<List>) = Self::LIST,
PrimOp(PrimOpId) = Self::PRIMOP,
PrimOpApp(Rc<PrimOpApp>) = Self::PRIMOP_APP,
Closure(Rc<Closure>) = Self::CLOSURE,
Blackhole,
Closure(ValueId) = Self::CLOSURE,
}
impl Debug for Value {
@@ -67,11 +64,9 @@ impl Debug for Value {
AttrSet(x) => write!(f, "{x:?}"),
List(x) => write!(f, "{x:?}"),
Thunk(thunk) => write!(f, "<THUNK {thunk:?}>"),
ClosureThunk(_) => write!(f, "<THUNK>"),
Closure(func) => write!(f, "<LAMBDA-APP {:?}>", func.body),
Closure(func) => write!(f, "<LAMBDA-APP {:?}>", func),
PrimOp(_) => write!(f, "<PRIMOP>"),
PrimOpApp(_) => write!(f, "<PRIMOP-APP>"),
Blackhole => write!(f, "<BLACKHOLE>"),
}
}
}
@@ -129,13 +124,11 @@ impl Value {
String(_) => "string",
Null => "null",
Thunk(_) => "thunk",
ClosureThunk(_) => "thunk",
AttrSet(_) => "set",
List(_) => "list",
PrimOp(_) => "lambda",
PrimOpApp(_) => "lambda",
Closure(..) => "lambda",
Blackhole => unreachable!(),
}
}
@@ -148,8 +141,7 @@ impl Value {
self,
|| Value::Null,
|val| match val {
Value::Thunk(id) => map(ctx.eval(id)),
Value::ClosureThunk(thunk) => map(thunk.call(None, ctx)),
Value::Thunk(id) => map(ctx.force(id)),
val => (Ok(()), val),
},
)
@@ -170,23 +162,9 @@ impl Value {
self,
|| Null,
|func| match func {
PrimOp(id) => {
let arity = ctx.get_primop_arity(id);
if arity == 1 {
map(ctx.call_primop(id, smallvec![arg]))
} else {
(
Ok(()),
Value::PrimOpApp(Rc::new(self::PrimOpApp::new(
arity - 1,
id,
smallvec![arg],
))),
)
}
}
PrimOpApp(func) => map(func.call(arg, ctx)),
Closure(func) => map(func.call(Some(arg), ctx)),
PrimOp(id) => map(ctx.call_primop(id, smallvec![arg])),
PrimOpApp(primop) => map(primop.call(arg, ctx)),
Closure(func) => map(ctx.call(func, arg)),
_ => (
Err(Error::eval_error(
"attempt to call something which is not a function but ...".to_string(),
@@ -240,10 +218,7 @@ impl Value {
}
pub fn eq(&mut self, other: Self) {
use Value::Bool;
*self = match (&*self, other) {
(s, other) => Bool(s.eq_impl(&other)),
};
*self = Value::Bool(self.eq_impl(&other));
}
pub fn lt(&mut self, other: Self) -> Result<()> {
@@ -378,7 +353,7 @@ impl Value {
}
}
pub fn select(&mut self, name: &str, ctx: &mut impl EvalContext) -> Result<()> {
pub fn select(&mut self, name: SymId, ctx: &mut impl EvalContext) -> Result<()> {
use Value::*;
let val = match self {
AttrSet(attrs) => attrs.select(name, ctx),
@@ -393,7 +368,7 @@ impl Value {
pub fn select_or<Ctx: EvalContext>(
&mut self,
name: &str,
name: SymId,
default: ExprId,
ctx: &mut Ctx,
) -> Result<()> {
@@ -411,7 +386,7 @@ impl Value {
Ok(())
}
pub fn has_attr(&mut self, path: impl DoubleEndedIterator<Item = Result<Value>>) -> Result<()> {
pub fn has_attr(&mut self, path: impl DoubleEndedIterator<Item = Result<SymId>>) -> Result<()> {
use Value::*;
if let AttrSet(attrs) = self {
let val = attrs.has_attr(path)?;
@@ -436,22 +411,46 @@ impl Value {
/// Converts the internal `Value` to its public-facing, serializable
/// representation from the `nixjit_value` crate.
pub fn to_public(self) -> PubValue {
pub fn to_public(self, ctx: &mut impl EvalContext) -> PubValue {
use Value::*;
match self {
AttrSet(attrs) => Rc::unwrap_or_clone(attrs).to_public(),
List(list) => Rc::unwrap_or_clone(list.clone()).to_public(),
AttrSet(attrs) => Rc::unwrap_or_clone(attrs).to_public(ctx),
List(list) => Rc::unwrap_or_clone(list.clone()).to_public(ctx),
Int(x) => PubValue::Const(Const::Int(x)),
Float(x) => PubValue::Const(Const::Float(x)),
Bool(x) => PubValue::Const(Const::Bool(x)),
String(x) => PubValue::String(x),
Null => PubValue::Const(Const::Null),
Thunk(_) => PubValue::Thunk,
ClosureThunk(_) => PubValue::Thunk,
PrimOp(_) => PubValue::PrimOp,
PrimOpApp(_) => PubValue::PrimOpApp,
Closure(..) => PubValue::Func,
Blackhole => unreachable!(),
}
}
}
#[repr(transparent)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Default)]
pub struct ValueId(usize);
impl ValueId {
/// Returns the raw `usize` index.
///
/// # Safety
///
/// The caller is responsible for using this index correctly and not causing out-of-bounds access.
#[inline(always)]
pub unsafe fn raw(self) -> usize {
self.0
}
/// Creates an `ExprId` from a raw `usize` index.
///
/// # Safety
///
/// The caller must ensure that the provided index is valid for the expression table.
#[inline(always)]
pub unsafe fn from_raw(id: usize) -> Self {
Self(id)
}
}

View File

@@ -18,8 +18,6 @@ pub type Args = smallvec::SmallVec<[Value; 2]>;
/// all, of its required arguments.
#[derive(Debug, Clone, Constructor)]
pub struct PrimOpApp {
/// The number of remaining arguments the primop expects.
arity: usize,
/// The unique ID of the primop.
id: PrimOpId,
/// The arguments that have already been applied.
@@ -27,20 +25,9 @@ pub struct PrimOpApp {
}
impl PrimOpApp {
/// Applies more arguments to a partially applied primop.
///
/// If enough arguments are provided to satisfy the primop's arity, it is
/// executed. Otherwise, it returns a new `PrimOpApp` with the combined
/// arguments.
pub fn call(self: Rc<Self>, arg: Value, ctx: &mut impl EvalContext) -> Result<Value> {
let mut primop = Rc::unwrap_or_clone(self);
if primop.arity == 1 {
primop.args.push(arg);
ctx.call_primop(primop.id, primop.args)
} else {
primop.args.push(arg);
primop.arity -= 1;
Ok(Value::PrimOpApp(primop.into()))
}
let PrimOpApp { id, mut args } = Rc::unwrap_or_clone(self);
args.push(arg);
ctx.call_primop(id, args)
}
}