chore: comment
This commit is contained in:
@@ -3,12 +3,6 @@
|
|||||||
//! This crate orchestrates the entire process of parsing, analyzing,
|
//! This crate orchestrates the entire process of parsing, analyzing,
|
||||||
//! and evaluating Nix expressions. It integrates all the other `nixjit_*`
|
//! and evaluating Nix expressions. It integrates all the other `nixjit_*`
|
||||||
//! components to provide a complete Nix evaluation environment.
|
//! components to provide a complete Nix evaluation environment.
|
||||||
//!
|
|
||||||
//! The primary workflow is demonstrated in the tests within `test.rs`:
|
|
||||||
//! 1. Parse Nix source code into an `rnix` AST.
|
|
||||||
//! 2. "Downgrade" the AST into the High-Level IR (HIR).
|
|
||||||
//! 3. "Resolve" the HIR into the Low-Level IR (LIR), handling variable lookups.
|
|
||||||
//! 4. "Evaluate" the LIR to produce a final value.
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test;
|
mod test;
|
||||||
|
|||||||
@@ -8,10 +8,7 @@ use nixjit_value::{AttrSet, Const, List, Symbol, Value};
|
|||||||
#[inline]
|
#[inline]
|
||||||
fn test_expr(expr: &str, expected: Value) {
|
fn test_expr(expr: &str, expected: Value) {
|
||||||
println!("{expr}");
|
println!("{expr}");
|
||||||
assert_eq!(
|
assert_eq!(Context::new().eval(expr).unwrap(), expected);
|
||||||
Context::new().eval(expr).unwrap(),
|
|
||||||
expected
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! map {
|
macro_rules! map {
|
||||||
|
|||||||
@@ -1,3 +1,13 @@
|
|||||||
|
//! The central evaluation context for the nixjit interpreter.
|
||||||
|
//!
|
||||||
|
//! This module defines the `Context` struct, which holds all the state
|
||||||
|
//! necessary for the evaluation of a Nix expression. It manages the
|
||||||
|
//! Intermediate Representations (IRs), scopes, evaluation stack, and
|
||||||
|
//! the Just-In-Time (JIT) compiler.
|
||||||
|
//!
|
||||||
|
//! The `Context` implements various traits (`DowngradeContext`, `ResolveContext`, etc.)
|
||||||
|
//! to provide the necessary services for each stage of the compilation and
|
||||||
|
//! evaluation pipeline.
|
||||||
use std::cell::{OnceCell, RefCell};
|
use std::cell::{OnceCell, RefCell};
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
@@ -19,15 +29,22 @@ use nixjit_lir::{Lir, LookupResult, Resolve, ResolveContext};
|
|||||||
use nixjit_jit::{JITCompiler, JITContext, JITFunc};
|
use nixjit_jit::{JITCompiler, JITContext, JITFunc};
|
||||||
use replace_with::replace_with_and_return;
|
use replace_with::replace_with_and_return;
|
||||||
|
|
||||||
|
/// Represents a lexical scope during name resolution.
|
||||||
enum Scope {
|
enum Scope {
|
||||||
|
/// A `with` expression scope.
|
||||||
With,
|
With,
|
||||||
|
/// A `let` binding scope, mapping variable names to their expression IDs.
|
||||||
Let(HashMap<String, ExprId>),
|
Let(HashMap<String, ExprId>),
|
||||||
|
/// A function argument scope. `Some` holds the name of the argument set if present.
|
||||||
Arg(Option<String>),
|
Arg(Option<String>),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Represents an expression at different stages of compilation.
|
||||||
#[derive(Debug, Unwrap)]
|
#[derive(Debug, Unwrap)]
|
||||||
enum Ir {
|
enum Ir {
|
||||||
|
/// An expression in the High-Level Intermediate Representation (HIR).
|
||||||
Hir(Hir),
|
Hir(Hir),
|
||||||
|
/// An expression in the Low-Level Intermediate Representation (LIR).
|
||||||
Lir(Lir),
|
Lir(Lir),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -81,20 +98,37 @@ impl Ir {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The main evaluation context.
|
||||||
|
///
|
||||||
|
/// This struct orchestrates the entire Nix expression evaluation process,
|
||||||
|
/// from parsing and semantic analysis to interpretation and JIT compilation.
|
||||||
pub struct Context {
|
pub struct Context {
|
||||||
|
/// Arena for all expressions, which can be either HIR or LIR.
|
||||||
|
/// `RefCell` is used for interior mutability to allow on-demand resolution.
|
||||||
irs: Vec<RefCell<Ir>>,
|
irs: Vec<RefCell<Ir>>,
|
||||||
|
/// Tracks whether an `ExprId` has been resolved from HIR to LIR.
|
||||||
resolved: Vec<bool>,
|
resolved: Vec<bool>,
|
||||||
|
/// The stack of lexical scopes used for name resolution.
|
||||||
scopes: Vec<Scope>,
|
scopes: Vec<Scope>,
|
||||||
|
/// The number of arguments in the current function call scope.
|
||||||
args_count: usize,
|
args_count: usize,
|
||||||
|
/// A table of primitive operation implementations.
|
||||||
primops: Vec<fn(&mut Context, Vec<Value>) -> Result<Value>>,
|
primops: Vec<fn(&mut Context, Vec<Value>) -> Result<Value>>,
|
||||||
|
/// Maps a function's body `ExprId` to its parameter definition.
|
||||||
funcs: HashMap<ExprId, Param>,
|
funcs: HashMap<ExprId, Param>,
|
||||||
|
/// A dependency graph between expressions.
|
||||||
graph: DiGraph<ExprId, ()>,
|
graph: DiGraph<ExprId, ()>,
|
||||||
|
/// Maps an `ExprId` to its corresponding `NodeIndex` in the dependency graph.
|
||||||
nodes: Vec<NodeIndex>,
|
nodes: Vec<NodeIndex>,
|
||||||
|
|
||||||
|
/// The call stack for function evaluation, where each frame holds arguments.
|
||||||
stack: Vec<Vec<Value>>,
|
stack: Vec<Vec<Value>>,
|
||||||
|
/// A stack of namespaces for `with` expressions during evaluation.
|
||||||
with_scopes: Vec<Rc<HashMap<String, Value>>>,
|
with_scopes: Vec<Rc<HashMap<String, Value>>>,
|
||||||
|
|
||||||
|
/// The Just-In-Time (JIT) compiler.
|
||||||
jit: JITCompiler<Self>,
|
jit: JITCompiler<Self>,
|
||||||
|
/// A cache for JIT-compiled functions, indexed by `ExprId`.
|
||||||
compiled: Vec<OnceCell<JITFunc<Self>>>,
|
compiled: Vec<OnceCell<JITFunc<Self>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -111,9 +145,7 @@ impl Default for Context {
|
|||||||
.enumerate()
|
.enumerate()
|
||||||
.map(|(id, (k, _))| (k.to_string(), unsafe { ExprId::from(id) }))
|
.map(|(id, (k, _))| (k.to_string(), unsafe { ExprId::from(id) }))
|
||||||
.chain(global.iter().enumerate().map(|(idx, (k, _, _))| {
|
.chain(global.iter().enumerate().map(|(idx, (k, _, _))| {
|
||||||
(k.to_string(), unsafe {
|
(k.to_string(), unsafe { ExprId::from(idx + CONSTS_LEN) })
|
||||||
ExprId::from(idx + CONSTS_LEN)
|
|
||||||
})
|
|
||||||
}))
|
}))
|
||||||
.chain(core::iter::once(("builtins".to_string(), unsafe {
|
.chain(core::iter::once(("builtins".to_string(), unsafe {
|
||||||
ExprId::from(CONSTS_LEN + GLOBAL_LEN + SCOPED_LEN)
|
ExprId::from(CONSTS_LEN + GLOBAL_LEN + SCOPED_LEN)
|
||||||
@@ -162,10 +194,18 @@ impl Default for Context {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Context {
|
impl Context {
|
||||||
|
/// Creates a new, default `Context`.
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self::default()
|
Self::default()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The main entry point for evaluating a Nix expression string.
|
||||||
|
///
|
||||||
|
/// This function performs the following steps:
|
||||||
|
/// 1. Parses the expression string into an `rnix` AST.
|
||||||
|
/// 2. Downgrades the AST to the High-Level IR (HIR).
|
||||||
|
/// 3. Resolves the HIR to the Low-Level IR (LIR).
|
||||||
|
/// 4. Evaluates the LIR to produce a final `Value`.
|
||||||
pub fn eval(mut self, expr: &str) -> Result<nixjit_value::Value> {
|
pub fn eval(mut self, expr: &str) -> Result<nixjit_value::Value> {
|
||||||
let root = rnix::Root::parse(expr);
|
let root = rnix::Root::parse(expr);
|
||||||
if !root.errors().is_empty() {
|
if !root.errors().is_empty() {
|
||||||
@@ -364,9 +404,7 @@ impl EvalContext for Context {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn call_primop(&mut self, id: nixjit_ir::PrimOpId, args: Vec<Value>) -> Result<Value> {
|
fn call_primop(&mut self, id: nixjit_ir::PrimOpId, args: Vec<Value>) -> Result<Value> {
|
||||||
unsafe {
|
unsafe { (self.primops.get_unchecked(id.raw()))(self, args) }
|
||||||
(self.primops.get_unchecked(id.raw()))(self, args)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,17 +1,43 @@
|
|||||||
|
//! This crate defines the centralized error types and `Result` alias used
|
||||||
|
//! throughout the entire `nixjit` evaluation pipeline. By consolidating error
|
||||||
|
//! handling here, we ensure a consistent approach to reporting failures across
|
||||||
|
//! different stages of processing, from parsing to final evaluation.
|
||||||
|
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
|
/// A specialized `Result` type used for all fallible operations within the
|
||||||
|
/// `nixjit` crates. It defaults to the crate's `Error` type.
|
||||||
pub type Result<T> = core::result::Result<T, Error>;
|
pub type Result<T> = core::result::Result<T, Error>;
|
||||||
|
|
||||||
|
/// The primary error enum, encompassing all potential failures that can occur
|
||||||
|
/// during the lifecycle of a Nix expression's evaluation.
|
||||||
#[derive(Error, Debug)]
|
#[derive(Error, Debug)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
|
/// An error occurred during the initial parsing phase. This typically
|
||||||
|
/// indicates a syntax error in the Nix source code, as detected by the
|
||||||
|
/// `rnix` parser.
|
||||||
#[error("error occurred during parse stage: {0}")]
|
#[error("error occurred during parse stage: {0}")]
|
||||||
ParseError(String),
|
ParseError(String),
|
||||||
|
|
||||||
|
/// An error occurred while "downgrading" the `rnix` AST to the
|
||||||
|
/// High-Level IR (HIR). This can happen if the AST has a structure that is
|
||||||
|
/// syntactically valid but semantically incorrect for our IR.
|
||||||
#[error("error occurred during downgrade stage: {0}")]
|
#[error("error occurred during downgrade stage: {0}")]
|
||||||
DowngradeError(String),
|
DowngradeError(String),
|
||||||
|
|
||||||
|
/// An error occurred during the variable resolution phase, where the HIR is
|
||||||
|
/// converted to the Low-Level IR (LIR). This is most commonly caused by
|
||||||
|
/// an unbound or undefined variable.
|
||||||
#[error("error occurred during variable resolve stage: {0}")]
|
#[error("error occurred during variable resolve stage: {0}")]
|
||||||
ResolutionError(String),
|
ResolutionError(String),
|
||||||
|
|
||||||
|
/// An error occurred during the final evaluation of the LIR. This covers
|
||||||
|
/// all runtime errors, such as type mismatches (e.g., adding a string to
|
||||||
|
/// an integer), division by zero, or failed `assert` statements.
|
||||||
#[error("error occurred during evaluation stage: {0}")]
|
#[error("error occurred during evaluation stage: {0}")]
|
||||||
EvalError(String),
|
EvalError(String),
|
||||||
#[error("unknown error")]
|
|
||||||
|
/// A catch-all for any error that does not fit into the other categories.
|
||||||
|
#[error("an unknown or unexpected error occurred")]
|
||||||
Unknown,
|
Unknown,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,12 @@
|
|||||||
|
//! This module defines the core traits and logic for evaluating the LIR.
|
||||||
|
//!
|
||||||
|
//! The central components are:
|
||||||
|
//! - `EvalContext`: A trait that defines the environment and operations needed for evaluation.
|
||||||
|
//! It manages the evaluation stack, scopes, and primop calls.
|
||||||
|
//! - `Evaluate`: A trait implemented by LIR nodes to define how they are evaluated.
|
||||||
|
//! - `Value`: An enum representing all possible values during evaluation. This is an
|
||||||
|
//! internal representation, distinct from the public-facing `nixjit_value::Value`.
|
||||||
|
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
use hashbrown::HashMap;
|
use hashbrown::HashMap;
|
||||||
@@ -5,37 +14,59 @@ use hashbrown::HashMap;
|
|||||||
use nixjit_error::{Error, Result};
|
use nixjit_error::{Error, Result};
|
||||||
use nixjit_ir::{self as ir, ArgIdx, ExprId, PrimOpId};
|
use nixjit_ir::{self as ir, ArgIdx, ExprId, PrimOpId};
|
||||||
use nixjit_lir as lir;
|
use nixjit_lir as lir;
|
||||||
use nixjit_value::{Const, Symbol};
|
use nixjit_value::{Const, Symbol, format_symbol};
|
||||||
|
|
||||||
pub use crate::value::*;
|
pub use crate::value::*;
|
||||||
|
|
||||||
mod value;
|
mod value;
|
||||||
|
|
||||||
|
/// A trait defining the context in which LIR expressions are evaluated.
|
||||||
pub trait EvalContext: Sized {
|
pub trait EvalContext: Sized {
|
||||||
|
/// Evaluates an expression by its ID.
|
||||||
fn eval(&mut self, expr: &ExprId) -> Result<Value>;
|
fn eval(&mut self, expr: &ExprId) -> Result<Value>;
|
||||||
|
|
||||||
|
/// Enters a `with` scope for the duration of a closure's execution.
|
||||||
fn with_with_env<T>(
|
fn with_with_env<T>(
|
||||||
&mut self,
|
&mut self,
|
||||||
namespace: Rc<HashMap<String, Value>>,
|
namespace: Rc<HashMap<String, Value>>,
|
||||||
f: impl FnOnce(&mut Self) -> T,
|
f: impl FnOnce(&mut Self) -> T,
|
||||||
) -> T;
|
) -> T;
|
||||||
fn with_args_env<T>(&mut self, args: Vec<Value>, f: impl FnOnce(&mut Self) -> T) -> (Vec<Value>, T);
|
|
||||||
|
/// Pushes a new set of arguments onto the stack for a function call.
|
||||||
|
fn with_args_env<T>(
|
||||||
|
&mut self,
|
||||||
|
args: Vec<Value>,
|
||||||
|
f: impl FnOnce(&mut Self) -> T,
|
||||||
|
) -> (Vec<Value>, T);
|
||||||
|
|
||||||
|
/// 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: &str) -> Option<&'a Value>;
|
||||||
|
|
||||||
|
/// Looks up a function argument by its index on the current stack frame.
|
||||||
fn lookup_arg<'a>(&'a self, idx: ArgIdx) -> &'a Value;
|
fn lookup_arg<'a>(&'a self, idx: ArgIdx) -> &'a Value;
|
||||||
|
|
||||||
|
/// Pops the current stack frame, returning the arguments.
|
||||||
fn pop_frame(&mut self) -> Vec<Value>;
|
fn pop_frame(&mut self) -> Vec<Value>;
|
||||||
|
|
||||||
|
/// Calls a primitive operation (builtin) by its ID.
|
||||||
fn call_primop(&mut self, id: PrimOpId, args: Vec<Value>) -> Result<Value>;
|
fn call_primop(&mut self, id: PrimOpId, args: Vec<Value>) -> Result<Value>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A trait for types that can be evaluated within an `EvalContext`.
|
||||||
pub trait Evaluate<Ctx: EvalContext> {
|
pub trait Evaluate<Ctx: EvalContext> {
|
||||||
|
/// Performs the evaluation.
|
||||||
fn eval(&self, ctx: &mut Ctx) -> Result<Value>;
|
fn eval(&self, ctx: &mut Ctx) -> Result<Value>;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Ctx: EvalContext> Evaluate<Ctx> for ExprId {
|
impl<Ctx: EvalContext> Evaluate<Ctx> for ExprId {
|
||||||
|
/// Evaluating an `ExprId` simply delegates to the context.
|
||||||
fn eval(&self, ctx: &mut Ctx) -> Result<Value> {
|
fn eval(&self, ctx: &mut Ctx) -> Result<Value> {
|
||||||
ctx.eval(self)
|
ctx.eval(self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Ctx: EvalContext> Evaluate<Ctx> for lir::Lir {
|
impl<Ctx: EvalContext> Evaluate<Ctx> for lir::Lir {
|
||||||
|
/// Evaluates an LIR node by dispatching to the specific implementation for its variant.
|
||||||
fn eval(&self, ctx: &mut Ctx) -> Result<Value> {
|
fn eval(&self, ctx: &mut Ctx) -> Result<Value> {
|
||||||
use lir::Lir::*;
|
use lir::Lir::*;
|
||||||
match self {
|
match self {
|
||||||
@@ -63,6 +94,7 @@ impl<Ctx: EvalContext> Evaluate<Ctx> for lir::Lir {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<Ctx: EvalContext> Evaluate<Ctx> for ir::AttrSet {
|
impl<Ctx: EvalContext> Evaluate<Ctx> for ir::AttrSet {
|
||||||
|
/// Evaluates an `AttrSet` by evaluating all its static and dynamic attributes.
|
||||||
fn eval(&self, ctx: &mut Ctx) -> Result<Value> {
|
fn eval(&self, ctx: &mut Ctx) -> Result<Value> {
|
||||||
let mut attrs = AttrSet::new(
|
let mut attrs = AttrSet::new(
|
||||||
self.stcs
|
self.stcs
|
||||||
@@ -79,24 +111,26 @@ impl<Ctx: EvalContext> Evaluate<Ctx> for ir::AttrSet {
|
|||||||
let v_eval_result = v.eval(ctx)?;
|
let v_eval_result = v.eval(ctx)?;
|
||||||
attrs.push_attr(k.unwrap_string(), v_eval_result);
|
attrs.push_attr(k.unwrap_string(), v_eval_result);
|
||||||
}
|
}
|
||||||
let result = Value::AttrSet(attrs.into()).ok();
|
let result = Value::AttrSet(attrs.into());
|
||||||
Ok(result.unwrap())
|
Ok(result)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Ctx: EvalContext> Evaluate<Ctx> for ir::List {
|
impl<Ctx: EvalContext> Evaluate<Ctx> for ir::List {
|
||||||
|
/// Evaluates a `List` by evaluating all its items.
|
||||||
fn eval(&self, ctx: &mut Ctx) -> Result<Value> {
|
fn eval(&self, ctx: &mut Ctx) -> Result<Value> {
|
||||||
let items = self
|
let items = self
|
||||||
.items
|
.items
|
||||||
.iter()
|
.iter()
|
||||||
.map(|val| val.eval(ctx))
|
.map(|val| val.eval(ctx))
|
||||||
.collect::<Result<Vec<_>>>()?;
|
.collect::<Result<Vec<_>>>()?;
|
||||||
let result = Value::List(List::from(items).into()).ok();
|
let result = Value::List(List::from(items).into());
|
||||||
Ok(result.unwrap())
|
Ok(result)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Ctx: EvalContext> Evaluate<Ctx> for ir::HasAttr {
|
impl<Ctx: EvalContext> Evaluate<Ctx> for ir::HasAttr {
|
||||||
|
/// Evaluates a `HasAttr` by evaluating the LHS and the attribute path, then performing the check.
|
||||||
fn eval(&self, ctx: &mut Ctx) -> Result<Value> {
|
fn eval(&self, ctx: &mut Ctx) -> Result<Value> {
|
||||||
use ir::Attr::*;
|
use ir::Attr::*;
|
||||||
let mut val = self.lhs.eval(ctx)?;
|
let mut val = self.lhs.eval(ctx)?;
|
||||||
@@ -110,12 +144,12 @@ impl<Ctx: EvalContext> Evaluate<Ctx> for ir::HasAttr {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}))?;
|
}))?;
|
||||||
let result = val.ok();
|
Ok(val)
|
||||||
Ok(result.unwrap())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Ctx: EvalContext> Evaluate<Ctx> for ir::BinOp {
|
impl<Ctx: EvalContext> Evaluate<Ctx> for ir::BinOp {
|
||||||
|
/// Evaluates a `BinOp` by evaluating the LHS and RHS, then performing the operation.
|
||||||
fn eval(&self, ctx: &mut Ctx) -> Result<Value> {
|
fn eval(&self, ctx: &mut Ctx) -> Result<Value> {
|
||||||
use ir::BinOpKind::*;
|
use ir::BinOpKind::*;
|
||||||
let mut lhs = self.lhs.eval(ctx)?;
|
let mut lhs = self.lhs.eval(ctx)?;
|
||||||
@@ -166,6 +200,7 @@ impl<Ctx: EvalContext> Evaluate<Ctx> for ir::BinOp {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<Ctx: EvalContext> Evaluate<Ctx> for ir::UnOp {
|
impl<Ctx: EvalContext> Evaluate<Ctx> for ir::UnOp {
|
||||||
|
/// Evaluates a `UnOp` by evaluating the RHS and performing the operation.
|
||||||
fn eval(&self, ctx: &mut Ctx) -> Result<Value> {
|
fn eval(&self, ctx: &mut Ctx) -> Result<Value> {
|
||||||
use ir::UnOpKind::*;
|
use ir::UnOpKind::*;
|
||||||
let mut rhs = self.rhs.eval(ctx)?;
|
let mut rhs = self.rhs.eval(ctx)?;
|
||||||
@@ -182,6 +217,8 @@ impl<Ctx: EvalContext> Evaluate<Ctx> for ir::UnOp {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<Ctx: EvalContext> Evaluate<Ctx> for ir::Select {
|
impl<Ctx: EvalContext> Evaluate<Ctx> for ir::Select {
|
||||||
|
/// Evaluates a `Select` by evaluating the expression, the path, and the default value (if any),
|
||||||
|
/// then performing the selection.
|
||||||
fn eval(&self, ctx: &mut Ctx) -> Result<Value> {
|
fn eval(&self, ctx: &mut Ctx) -> Result<Value> {
|
||||||
use ir::Attr::*;
|
use ir::Attr::*;
|
||||||
let mut val = self.expr.eval(ctx)?;
|
let mut val = self.expr.eval(ctx)?;
|
||||||
@@ -212,18 +249,20 @@ impl<Ctx: EvalContext> Evaluate<Ctx> for ir::Select {
|
|||||||
})
|
})
|
||||||
}))?;
|
}))?;
|
||||||
}
|
}
|
||||||
let result = val.ok();
|
Ok(val)
|
||||||
Ok(result.unwrap())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Ctx: EvalContext> Evaluate<Ctx> for ir::If {
|
impl<Ctx: EvalContext> Evaluate<Ctx> for ir::If {
|
||||||
|
/// Evaluates an `If` by evaluating the condition and then either the consequence or the alternative.
|
||||||
fn eval(&self, ctx: &mut Ctx) -> Result<Value> {
|
fn eval(&self, ctx: &mut Ctx) -> Result<Value> {
|
||||||
// TODO: Error Handling
|
|
||||||
let cond = self.cond.eval(ctx)?;
|
let cond = self.cond.eval(ctx)?;
|
||||||
let cond = cond
|
let cond = cond.as_ref().try_unwrap_bool().map_err(|_| {
|
||||||
.try_unwrap_bool()
|
Error::EvalError(format!(
|
||||||
.map_err(|_| Error::EvalError(format!("expected a boolean but found ...")))?;
|
"if-condition must be a boolean, but got {}",
|
||||||
|
cond.typename()
|
||||||
|
))
|
||||||
|
})?;
|
||||||
|
|
||||||
if cond {
|
if cond {
|
||||||
self.consq.eval(ctx)
|
self.consq.eval(ctx)
|
||||||
@@ -234,27 +273,27 @@ impl<Ctx: EvalContext> Evaluate<Ctx> for ir::If {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<Ctx: EvalContext> Evaluate<Ctx> for ir::Call {
|
impl<Ctx: EvalContext> Evaluate<Ctx> for ir::Call {
|
||||||
|
/// Evaluates a `Call` by evaluating the function and its arguments, then performing the call.
|
||||||
fn eval(&self, ctx: &mut Ctx) -> Result<Value> {
|
fn eval(&self, ctx: &mut Ctx) -> Result<Value> {
|
||||||
let mut func = self.func.eval(ctx)?;
|
let mut func = self.func.eval(ctx)?;
|
||||||
// FIXME: ?
|
|
||||||
let ctx_mut = unsafe { &mut *(ctx as *mut Ctx) };
|
let ctx_mut = unsafe { &mut *(ctx as *mut Ctx) };
|
||||||
func.call(
|
func.call(self.args.iter().map(|arg| arg.eval(ctx)), ctx_mut)?;
|
||||||
self.args
|
Ok(func)
|
||||||
.iter()
|
|
||||||
.map(|arg| arg.eval(ctx)),
|
|
||||||
ctx_mut,
|
|
||||||
)?;
|
|
||||||
Ok(func.ok().unwrap())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Ctx: EvalContext> Evaluate<Ctx> for ir::With {
|
impl<Ctx: EvalContext> Evaluate<Ctx> for ir::With {
|
||||||
|
/// Evaluates a `With` by evaluating the namespace, entering a `with` scope,
|
||||||
|
/// and then evaluating the body.
|
||||||
fn eval(&self, ctx: &mut Ctx) -> Result<Value> {
|
fn eval(&self, ctx: &mut Ctx) -> Result<Value> {
|
||||||
let namespace = self.namespace.eval(ctx)?;
|
let namespace = self.namespace.eval(ctx)?;
|
||||||
|
let typename = namespace.typename();
|
||||||
ctx.with_with_env(
|
ctx.with_with_env(
|
||||||
namespace
|
namespace
|
||||||
.try_unwrap_attr_set()
|
.try_unwrap_attr_set()
|
||||||
.map_err(|_| Error::EvalError(format!("expected a set but found ...")))?
|
.map_err(|_| {
|
||||||
|
Error::EvalError(format!("'with' expects a set, but got {}", typename))
|
||||||
|
})?
|
||||||
.into_inner(),
|
.into_inner(),
|
||||||
|ctx| self.expr.eval(ctx),
|
|ctx| self.expr.eval(ctx),
|
||||||
)
|
)
|
||||||
@@ -262,20 +301,27 @@ impl<Ctx: EvalContext> Evaluate<Ctx> for ir::With {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<Ctx: EvalContext> Evaluate<Ctx> for ir::Assert {
|
impl<Ctx: EvalContext> Evaluate<Ctx> for ir::Assert {
|
||||||
|
/// Evaluates an `Assert` by evaluating the condition. If true, it evaluates and
|
||||||
|
/// returns the body; otherwise, it returns an error.
|
||||||
fn eval(&self, ctx: &mut Ctx) -> Result<Value> {
|
fn eval(&self, ctx: &mut Ctx) -> Result<Value> {
|
||||||
let cond = self.assertion.eval(ctx)?;
|
let cond = self.assertion.eval(ctx)?;
|
||||||
let cond = cond
|
let cond = cond.as_ref().try_unwrap_bool().map_err(|_| {
|
||||||
.try_unwrap_bool()
|
Error::EvalError(format!(
|
||||||
.map_err(|_| Error::EvalError(format!("expected a boolean but found ...")))?;
|
"assertion condition must be a boolean, but got {}",
|
||||||
|
cond.typename()
|
||||||
|
))
|
||||||
|
})?;
|
||||||
if cond {
|
if cond {
|
||||||
self.expr.eval(ctx)
|
self.expr.eval(ctx)
|
||||||
} else {
|
} else {
|
||||||
Err(Error::EvalError("assertion failed".to_string()))
|
Ok(Value::Catchable("assertion failed".to_string()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Ctx: EvalContext> Evaluate<Ctx> for ir::ConcatStrings {
|
impl<Ctx: EvalContext> Evaluate<Ctx> for ir::ConcatStrings {
|
||||||
|
/// Evaluates a `ConcatStrings` by evaluating each part, coercing it to a string,
|
||||||
|
/// and then concatenating the results.
|
||||||
fn eval(&self, ctx: &mut Ctx) -> Result<Value> {
|
fn eval(&self, ctx: &mut Ctx) -> Result<Value> {
|
||||||
let mut parts = self
|
let mut parts = self
|
||||||
.parts
|
.parts
|
||||||
@@ -283,7 +329,7 @@ impl<Ctx: EvalContext> Evaluate<Ctx> for ir::ConcatStrings {
|
|||||||
.map(|part| {
|
.map(|part| {
|
||||||
let mut part = part.eval(ctx)?;
|
let mut part = part.eval(ctx)?;
|
||||||
part.coerce_to_string();
|
part.coerce_to_string();
|
||||||
part.ok()
|
Ok(part)
|
||||||
})
|
})
|
||||||
.collect::<Result<Vec<_>>>()?
|
.collect::<Result<Vec<_>>>()?
|
||||||
.into_iter();
|
.into_iter();
|
||||||
@@ -292,44 +338,44 @@ impl<Ctx: EvalContext> Evaluate<Ctx> for ir::ConcatStrings {
|
|||||||
a.concat_string(b);
|
a.concat_string(b);
|
||||||
a
|
a
|
||||||
});
|
});
|
||||||
Ok(result.ok().unwrap())
|
Ok(result)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Ctx: EvalContext> Evaluate<Ctx> for ir::Str {
|
impl<Ctx: EvalContext> Evaluate<Ctx> for ir::Str {
|
||||||
|
/// Evaluates a `Str` literal into a `Value::String`.
|
||||||
fn eval(&self, _: &mut Ctx) -> Result<Value> {
|
fn eval(&self, _: &mut Ctx) -> Result<Value> {
|
||||||
let result = Value::String(self.val.clone()).ok();
|
Ok(Value::String(self.val.clone()))
|
||||||
Ok(result.unwrap())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Ctx: EvalContext> Evaluate<Ctx> for ir::Const {
|
impl<Ctx: EvalContext> Evaluate<Ctx> for ir::Const {
|
||||||
|
/// Evaluates a `Const` literal into its corresponding `Value` variant.
|
||||||
fn eval(&self, _: &mut Ctx) -> Result<Value> {
|
fn eval(&self, _: &mut Ctx) -> Result<Value> {
|
||||||
let result = match self.val {
|
let result = match self.val {
|
||||||
Const::Null => Value::Null,
|
Const::Null => Value::Null,
|
||||||
Const::Int(x) => Value::Int(x),
|
Const::Int(x) => Value::Int(x),
|
||||||
Const::Float(x) => Value::Float(x),
|
Const::Float(x) => Value::Float(x),
|
||||||
Const::Bool(x) => Value::Bool(x),
|
Const::Bool(x) => Value::Bool(x),
|
||||||
}
|
};
|
||||||
.ok();
|
Ok(result)
|
||||||
Ok(result.unwrap())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Ctx: EvalContext> Evaluate<Ctx> for ir::Var {
|
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> {
|
fn eval(&self, ctx: &mut Ctx) -> Result<Value> {
|
||||||
ctx.lookup_with(&self.sym)
|
ctx.lookup_with(&self.sym)
|
||||||
.ok_or_else(|| {
|
.ok_or_else(|| {
|
||||||
Error::EvalError(format!(
|
Error::EvalError(format!("undefined variable '{}'", format_symbol(&self.sym)))
|
||||||
"variable {} not found",
|
|
||||||
Symbol::from(self.sym.clone())
|
|
||||||
))
|
|
||||||
})
|
})
|
||||||
.map(|val| val.clone())
|
.map(|val| val.clone())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Ctx: EvalContext> Evaluate<Ctx> for ir::Path {
|
impl<Ctx: EvalContext> Evaluate<Ctx> for ir::Path {
|
||||||
|
/// Evaluates a `Path`. (Currently a TODO).
|
||||||
fn eval(&self, ctx: &mut Ctx) -> Result<Value> {
|
fn eval(&self, ctx: &mut Ctx) -> Result<Value> {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
//! Defines the runtime representation of an attribute set (a map).
|
||||||
|
|
||||||
use core::ops::Deref;
|
use core::ops::Deref;
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
@@ -7,12 +9,16 @@ use hashbrown::{HashMap, HashSet};
|
|||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
|
|
||||||
use nixjit_error::{Error, Result};
|
use nixjit_error::{Error, Result};
|
||||||
use nixjit_value as p;
|
|
||||||
use nixjit_value::Symbol;
|
use nixjit_value::Symbol;
|
||||||
|
use nixjit_value::{self as p, format_symbol};
|
||||||
|
|
||||||
use super::Value;
|
use super::Value;
|
||||||
use crate::EvalContext;
|
use crate::EvalContext;
|
||||||
|
|
||||||
|
/// A wrapper around a `HashMap` representing a Nix attribute set.
|
||||||
|
///
|
||||||
|
/// It uses `#[repr(transparent)]` to ensure it has the same memory layout
|
||||||
|
/// as `HashMap<String, Value>`.
|
||||||
#[repr(transparent)]
|
#[repr(transparent)]
|
||||||
#[derive(Constructor, PartialEq)]
|
#[derive(Constructor, PartialEq)]
|
||||||
pub struct AttrSet {
|
pub struct AttrSet {
|
||||||
@@ -56,16 +62,24 @@ impl Deref for AttrSet {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl AttrSet {
|
impl AttrSet {
|
||||||
|
/// Creates a new `AttrSet` with a specified initial capacity.
|
||||||
pub fn with_capacity(cap: usize) -> Self {
|
pub fn with_capacity(cap: usize) -> Self {
|
||||||
AttrSet {
|
AttrSet {
|
||||||
data: HashMap::with_capacity(cap),
|
data: HashMap::with_capacity(cap),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 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: String, val: Value) {
|
||||||
self.data.insert(sym, val);
|
self.data.insert(sym, val);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Inserts an attribute.
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// This method currently uses `todo!()` and will panic if the attribute
|
||||||
|
/// already exists, indicating that duplicate attribute handling is not yet implemented.
|
||||||
pub fn push_attr(&mut self, sym: String, val: Value) {
|
pub fn push_attr(&mut self, sym: String, val: Value) {
|
||||||
if self.data.get(&sym).is_some() {
|
if self.data.get(&sym).is_some() {
|
||||||
todo!()
|
todo!()
|
||||||
@@ -73,6 +87,10 @@ impl AttrSet {
|
|||||||
self.data.insert(sym, val);
|
self.data.insert(sym, val);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Performs a deep selection of an attribute from a nested set.
|
||||||
|
///
|
||||||
|
/// It traverses the attribute path and returns the final value, or an error
|
||||||
|
/// if any intermediate attribute does not exist or is not a set.
|
||||||
pub fn select(
|
pub fn select(
|
||||||
&self,
|
&self,
|
||||||
mut path: impl DoubleEndedIterator<Item = Result<String>>,
|
mut path: impl DoubleEndedIterator<Item = Result<String>>,
|
||||||
@@ -84,7 +102,7 @@ impl AttrSet {
|
|||||||
let Some(Value::AttrSet(attrs)) = data.get(&item) else {
|
let Some(Value::AttrSet(attrs)) = data.get(&item) else {
|
||||||
return Err(Error::EvalError(format!(
|
return Err(Error::EvalError(format!(
|
||||||
"attribute '{}' not found",
|
"attribute '{}' not found",
|
||||||
Symbol::from(item)
|
format_symbol(item)
|
||||||
)));
|
)));
|
||||||
};
|
};
|
||||||
data = attrs.as_inner();
|
data = attrs.as_inner();
|
||||||
@@ -95,6 +113,7 @@ impl AttrSet {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Checks if an attribute path exists within the set.
|
||||||
pub fn has_attr(
|
pub fn has_attr(
|
||||||
&self,
|
&self,
|
||||||
mut path: impl DoubleEndedIterator<Item = Result<String>>,
|
mut path: impl DoubleEndedIterator<Item = Result<String>>,
|
||||||
@@ -110,24 +129,38 @@ impl AttrSet {
|
|||||||
Ok(data.get(&last?).is_some())
|
Ok(data.get(&last?).is_some())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Merges another `AttrSet` into this one, with attributes from `other`
|
||||||
|
/// overwriting existing ones. This corresponds to the `//` operator in Nix.
|
||||||
pub fn update(&mut self, other: &Self) {
|
pub fn update(&mut self, other: &Self) {
|
||||||
for (k, v) in other.data.iter() {
|
for (k, v) in other.data.iter() {
|
||||||
self.push_attr_force(k.clone(), v.clone())
|
self.push_attr_force(k.clone(), v.clone())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns a reference to the inner `HashMap`.
|
||||||
pub fn as_inner(&self) -> &HashMap<String, Value> {
|
pub fn as_inner(&self) -> &HashMap<String, Value> {
|
||||||
&self.data
|
&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>> {
|
pub fn into_inner(self: Rc<Self>) -> Rc<HashMap<String, Value>> {
|
||||||
unsafe { core::mem::transmute(self) }
|
unsafe { core::mem::transmute(self) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Creates an `AttrSet` from a `HashMap`.
|
||||||
pub fn from_inner(data: HashMap<String, Value>) -> Self {
|
pub fn from_inner(data: HashMap<String, Value>) -> Self {
|
||||||
Self { data }
|
Self { data }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Performs a deep equality comparison between two `AttrSet`s.
|
||||||
|
///
|
||||||
|
/// It recursively compares the contents of both sets, ensuring that both keys
|
||||||
|
/// and values are identical. The attributes are sorted before comparison to
|
||||||
|
/// ensure a consistent result.
|
||||||
pub fn eq_impl(&self, other: &Self) -> bool {
|
pub fn eq_impl(&self, other: &Self) -> bool {
|
||||||
self.data.iter().len() == other.data.iter().len()
|
self.data.iter().len() == other.data.iter().len()
|
||||||
&& std::iter::zip(
|
&& std::iter::zip(
|
||||||
@@ -137,6 +170,7 @@ impl AttrSet {
|
|||||||
.all(|((k1, v1), (k2, v2))| k1 == k2 && v1.eq_impl(v2))
|
.all(|((k1, v1), (k2, v2))| k1 == k2 && v1.eq_impl(v2))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Converts the `AttrSet` to its public-facing representation.
|
||||||
pub fn to_public(&self, seen: &mut HashSet<Value>) -> p::Value {
|
pub fn to_public(&self, seen: &mut HashSet<Value>) -> p::Value {
|
||||||
p::Value::AttrSet(p::AttrSet::new(
|
p::Value::AttrSet(p::AttrSet::new(
|
||||||
self.data
|
self.data
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
//! Defines the runtime representation of a partially applied function.
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
use derive_more::Constructor;
|
use derive_more::Constructor;
|
||||||
@@ -8,10 +9,17 @@ use nixjit_ir::ExprId;
|
|||||||
use super::Value;
|
use super::Value;
|
||||||
use crate::EvalContext;
|
use crate::EvalContext;
|
||||||
|
|
||||||
|
/// Represents a partially applied user-defined function.
|
||||||
|
///
|
||||||
|
/// This struct captures the state of a function that has received some, but not
|
||||||
|
/// all, of its expected arguments.
|
||||||
#[derive(Debug, Constructor)]
|
#[derive(Debug, Constructor)]
|
||||||
pub struct FuncApp {
|
pub struct FuncApp {
|
||||||
|
/// The expression ID of the function body to be executed.
|
||||||
pub body: ExprId,
|
pub body: ExprId,
|
||||||
|
/// The arguments that have already been applied to the function.
|
||||||
pub args: Vec<Value>,
|
pub args: Vec<Value>,
|
||||||
|
/// The lexical scope (stack frame) captured at the time of the initial call.
|
||||||
pub frame: Vec<Value>,
|
pub frame: Vec<Value>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -26,6 +34,10 @@ impl Clone for FuncApp {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl FuncApp {
|
impl FuncApp {
|
||||||
|
/// Applies more arguments to a partially applied function.
|
||||||
|
///
|
||||||
|
/// It takes an iterator of new arguments, appends them to the existing ones,
|
||||||
|
/// and re-evaluates the function body within its captured environment.
|
||||||
pub fn call<Ctx: EvalContext>(
|
pub fn call<Ctx: EvalContext>(
|
||||||
self: &mut Rc<Self>,
|
self: &mut Rc<Self>,
|
||||||
mut iter: impl Iterator<Item = Result<Value>> + ExactSizeIterator,
|
mut iter: impl Iterator<Item = Result<Value>> + ExactSizeIterator,
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
//! Defines the runtime representation of a list.
|
||||||
|
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
|
|
||||||
@@ -9,6 +11,7 @@ use nixjit_value::Value as PubValue;
|
|||||||
use super::Value;
|
use super::Value;
|
||||||
use crate::EvalContext;
|
use crate::EvalContext;
|
||||||
|
|
||||||
|
/// A wrapper around a `Vec<Value>` representing a Nix list.
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct List {
|
pub struct List {
|
||||||
data: Vec<Value>,
|
data: Vec<Value>,
|
||||||
@@ -46,35 +49,43 @@ impl Deref for List {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl List {
|
impl List {
|
||||||
|
/// Creates a new, empty `List`.
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
List { data: Vec::new() }
|
List { data: Vec::new() }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Creates a new `List` with a specified initial capacity.
|
||||||
pub fn with_capacity(cap: usize) -> Self {
|
pub fn with_capacity(cap: usize) -> Self {
|
||||||
List {
|
List {
|
||||||
data: Vec::with_capacity(cap),
|
data: Vec::with_capacity(cap),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Appends an element to the back of the list.
|
||||||
pub fn push(&mut self, elem: Value) {
|
pub fn push(&mut self, elem: Value) {
|
||||||
self.data.push(elem);
|
self.data.push(elem);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Appends all elements from another `List` to this one.
|
||||||
|
/// This corresponds to the `++` operator in Nix.
|
||||||
pub fn concat(&mut self, other: &Self) {
|
pub fn concat(&mut self, other: &Self) {
|
||||||
for elem in other.data.iter() {
|
for elem in other.data.iter() {
|
||||||
self.data.push(elem.clone());
|
self.data.push(elem.clone());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Consumes the `List` and returns the inner `Vec<Value>`.
|
||||||
pub fn into_inner(self) -> Vec<Value> {
|
pub fn into_inner(self) -> Vec<Value> {
|
||||||
self.data
|
self.data
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Performs a deep equality comparison between two `List`s.
|
||||||
pub fn eq_impl(&self, other: &Self) -> bool {
|
pub fn eq_impl(&self, other: &Self) -> bool {
|
||||||
self.len() == other.len()
|
self.len() == other.len()
|
||||||
&& core::iter::zip(self.iter(), other.iter()).all(|(a, b)| a.eq_impl(b))
|
&& core::iter::zip(self.iter(), other.iter()).all(|(a, b)| a.eq_impl(b))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Converts the `List` to its public-facing representation.
|
||||||
pub fn to_public(&self, seen: &mut HashSet<Value>) -> PubValue {
|
pub fn to_public(&self, seen: &mut HashSet<Value>) -> PubValue {
|
||||||
PubValue::List(PubList::new(
|
PubValue::List(PubList::new(
|
||||||
self.data
|
self.data
|
||||||
|
|||||||
@@ -1,3 +1,14 @@
|
|||||||
|
//! Defines the internal representation of values during evaluation.
|
||||||
|
//!
|
||||||
|
//! This module introduces the `Value` enum, which is the cornerstone of the
|
||||||
|
//! interpreter's runtime. It represents all possible data types that can exist
|
||||||
|
//! during the evaluation of a Nix expression. This is an internal, mutable
|
||||||
|
//! representation, distinct from the public-facing `nixjit_value::Value`.
|
||||||
|
//!
|
||||||
|
//! The module also provides `ValueAsRef` for non-owning references and
|
||||||
|
//! implementations for various operations like arithmetic, comparison, and
|
||||||
|
//! function calls.
|
||||||
|
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
use std::hash::Hash;
|
use std::hash::Hash;
|
||||||
use std::process::abort;
|
use std::process::abort;
|
||||||
@@ -26,6 +37,11 @@ pub use func::*;
|
|||||||
pub use list::List;
|
pub use list::List;
|
||||||
pub use primop::*;
|
pub use primop::*;
|
||||||
|
|
||||||
|
/// The internal, C-compatible representation of a Nix value during evaluation.
|
||||||
|
///
|
||||||
|
/// This enum is designed for efficient manipulation within the interpreter and
|
||||||
|
/// JIT-compiled code. It uses `#[repr(C, u64)]` to ensure a predictable layout,
|
||||||
|
/// with the discriminant serving as a type tag.
|
||||||
#[repr(C, u64)]
|
#[repr(C, u64)]
|
||||||
#[derive(IsVariant, TryUnwrap, Unwrap)]
|
#[derive(IsVariant, TryUnwrap, Unwrap)]
|
||||||
pub enum Value {
|
pub enum Value {
|
||||||
@@ -143,6 +159,9 @@ impl PartialEq for Value {
|
|||||||
|
|
||||||
impl Eq for Value {}
|
impl Eq for Value {}
|
||||||
|
|
||||||
|
/// A non-owning reference to a `Value`.
|
||||||
|
///
|
||||||
|
/// This is used to avoid unnecessary cloning when inspecting values.
|
||||||
#[derive(IsVariant, TryUnwrap, Unwrap, Clone)]
|
#[derive(IsVariant, TryUnwrap, Unwrap, Clone)]
|
||||||
pub enum ValueAsRef<'v> {
|
pub enum ValueAsRef<'v> {
|
||||||
Int(i64),
|
Int(i64),
|
||||||
@@ -161,6 +180,7 @@ pub enum ValueAsRef<'v> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Value {
|
impl Value {
|
||||||
|
/// Returns a `ValueAsRef`, providing a non-owning view of the value.
|
||||||
pub fn as_ref(&self) -> ValueAsRef<'_> {
|
pub fn as_ref(&self) -> ValueAsRef<'_> {
|
||||||
use Value::*;
|
use Value::*;
|
||||||
use ValueAsRef as R;
|
use ValueAsRef as R;
|
||||||
@@ -182,10 +202,12 @@ impl Value {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl Value {
|
impl Value {
|
||||||
|
/// Wraps the `Value` in a `Result::Ok`.
|
||||||
pub fn ok(self) -> Result<Self> {
|
pub fn ok(self) -> Result<Self> {
|
||||||
Ok(self)
|
Ok(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the name of the value's type.
|
||||||
pub fn typename(&self) -> &'static str {
|
pub fn typename(&self) -> &'static str {
|
||||||
use Value::*;
|
use Value::*;
|
||||||
match self {
|
match self {
|
||||||
@@ -205,6 +227,7 @@ impl Value {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Checks if the value is a callable entity (a function or primop).
|
||||||
pub fn callable(&self) -> bool {
|
pub fn callable(&self) -> bool {
|
||||||
match self {
|
match self {
|
||||||
Value::PrimOp(_) | Value::PrimOpApp(_) | Value::Func(_) => true,
|
Value::PrimOp(_) | Value::PrimOpApp(_) | Value::Func(_) => true,
|
||||||
@@ -213,7 +236,16 @@ impl Value {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn call<Ctx: EvalContext>(&mut self, mut iter: impl Iterator<Item = Result<Value>> + ExactSizeIterator, ctx: &mut Ctx) -> Result<()> {
|
/// Performs a function call on the `Value`.
|
||||||
|
///
|
||||||
|
/// This method handles calling functions, primops, and their partially
|
||||||
|
/// applied variants. It manages argument application and delegates to the
|
||||||
|
/// `EvalContext` for the actual execution.
|
||||||
|
pub fn call<Ctx: EvalContext>(
|
||||||
|
&mut self,
|
||||||
|
mut iter: impl Iterator<Item = Result<Value>> + ExactSizeIterator,
|
||||||
|
ctx: &mut Ctx,
|
||||||
|
) -> Result<()> {
|
||||||
use Value::*;
|
use Value::*;
|
||||||
*self = match self {
|
*self = match self {
|
||||||
&mut PrimOp(func) => {
|
&mut PrimOp(func) => {
|
||||||
@@ -254,7 +286,8 @@ impl Value {
|
|||||||
} else if let Value::FuncApp(func) = val {
|
} else if let Value::FuncApp(func) = val {
|
||||||
let mut func = Rc::unwrap_or_clone(func);
|
let mut func = Rc::unwrap_or_clone(func);
|
||||||
func.args.push(args.pop().unwrap());
|
func.args.push(args.pop().unwrap());
|
||||||
let (ret_args, ret) = ctx.with_args_env(func.args, |ctx| ctx.eval(&func.body));
|
let (ret_args, ret) =
|
||||||
|
ctx.with_args_env(func.args, |ctx| ctx.eval(&func.body));
|
||||||
args = ret_args;
|
args = ret_args;
|
||||||
val = ret?;
|
val = ret?;
|
||||||
}
|
}
|
||||||
@@ -264,7 +297,9 @@ impl Value {
|
|||||||
PrimOpApp(func) => func.call(iter.collect::<Result<_>>()?, ctx),
|
PrimOpApp(func) => func.call(iter.collect::<Result<_>>()?, ctx),
|
||||||
FuncApp(func) => func.call(iter, ctx),
|
FuncApp(func) => func.call(iter, ctx),
|
||||||
Catchable(_) => return Ok(()),
|
Catchable(_) => return Ok(()),
|
||||||
_ => Err(Error::EvalError("attempt to call something which is not a function but ...".to_string()))
|
_ => Err(Error::EvalError(
|
||||||
|
"attempt to call something which is not a function but ...".to_string(),
|
||||||
|
)),
|
||||||
}?;
|
}?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -520,6 +555,12 @@ impl Value {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Converts the internal `Value` to its public-facing, serializable
|
||||||
|
/// representation from the `nixjit_value` crate.
|
||||||
|
///
|
||||||
|
/// The `seen` set is used to detect and handle cycles in data structures
|
||||||
|
/// like attribute sets and lists, replacing subsequent encounters with
|
||||||
|
/// `PubValue::Repeated`.
|
||||||
pub fn to_public(&self, seen: &mut HashSet<Value>) -> PubValue {
|
pub fn to_public(&self, seen: &mut HashSet<Value>) -> PubValue {
|
||||||
use Value::*;
|
use Value::*;
|
||||||
if seen.contains(self) {
|
if seen.contains(self) {
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
//! Defines the runtime representation of a partially applied primitive operation.
|
||||||
|
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
use derive_more::Constructor;
|
use derive_more::Constructor;
|
||||||
@@ -8,15 +10,28 @@ use nixjit_ir::PrimOpId;
|
|||||||
use super::Value;
|
use super::Value;
|
||||||
use crate::EvalContext;
|
use crate::EvalContext;
|
||||||
|
|
||||||
|
/// Represents a partially applied primitive operation (builtin function).
|
||||||
|
///
|
||||||
|
/// This struct holds the state of a primop that has received some, but not
|
||||||
|
/// all, of its required arguments.
|
||||||
#[derive(Debug, Clone, Constructor)]
|
#[derive(Debug, Clone, Constructor)]
|
||||||
pub struct PrimOpApp {
|
pub struct PrimOpApp {
|
||||||
|
/// The name of the primitive operation.
|
||||||
pub name: &'static str,
|
pub name: &'static str,
|
||||||
|
/// The number of remaining arguments the primop expects.
|
||||||
arity: usize,
|
arity: usize,
|
||||||
|
/// The unique ID of the primop.
|
||||||
id: PrimOpId,
|
id: PrimOpId,
|
||||||
|
/// The arguments that have already been applied.
|
||||||
args: Vec<Value>,
|
args: Vec<Value>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl 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(
|
pub fn call(
|
||||||
self: &mut Rc<Self>,
|
self: &mut Rc<Self>,
|
||||||
args: Vec<Value>,
|
args: Vec<Value>,
|
||||||
|
|||||||
@@ -1,10 +1,19 @@
|
|||||||
|
//! Defines a placeholder for Nix's contextful strings.
|
||||||
|
//!
|
||||||
|
//! In Nix, strings can carry a "context" which affects how they are
|
||||||
|
//! handled, particularly with regards to path resolution. This module
|
||||||
|
//! provides the basic structures for this feature, although it is
|
||||||
|
//! currently a work in progress.
|
||||||
|
|
||||||
// TODO: Contextful String
|
// TODO: Contextful String
|
||||||
|
|
||||||
|
/// Represents the context associated with a string.
|
||||||
pub struct StringContext {
|
pub struct StringContext {
|
||||||
context: Vec<()>,
|
context: Vec<()>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl StringContext {
|
impl StringContext {
|
||||||
|
/// Creates a new, empty `StringContext`.
|
||||||
pub fn new() -> StringContext {
|
pub fn new() -> StringContext {
|
||||||
StringContext {
|
StringContext {
|
||||||
context: Vec::new(),
|
context: Vec::new(),
|
||||||
@@ -12,12 +21,14 @@ impl StringContext {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A string that carries an associated context.
|
||||||
pub struct ContextfulString {
|
pub struct ContextfulString {
|
||||||
string: String,
|
string: String,
|
||||||
context: StringContext,
|
context: StringContext,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ContextfulString {
|
impl ContextfulString {
|
||||||
|
/// Creates a new `ContextfulString` from a standard `String`.
|
||||||
pub fn new(string: String) -> ContextfulString {
|
pub fn new(string: String) -> ContextfulString {
|
||||||
ContextfulString {
|
ContextfulString {
|
||||||
string,
|
string,
|
||||||
|
|||||||
@@ -58,6 +58,7 @@ impl<Ctx: DowngradeContext> Downgrade<Ctx> for Expr {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Downgrades an `assert` expression.
|
||||||
impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::Assert {
|
impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::Assert {
|
||||||
fn downgrade(self, ctx: &mut Ctx) -> Result<ExprId> {
|
fn downgrade(self, ctx: &mut Ctx) -> Result<ExprId> {
|
||||||
let assertion = self.condition().unwrap().downgrade(ctx)?;
|
let assertion = self.condition().unwrap().downgrade(ctx)?;
|
||||||
@@ -66,6 +67,7 @@ impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::Assert {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Downgrades an `if-then-else` expression.
|
||||||
impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::IfElse {
|
impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::IfElse {
|
||||||
fn downgrade(self, ctx: &mut Ctx) -> Result<ExprId> {
|
fn downgrade(self, ctx: &mut Ctx) -> Result<ExprId> {
|
||||||
let cond = self.condition().unwrap().downgrade(ctx)?;
|
let cond = self.condition().unwrap().downgrade(ctx)?;
|
||||||
@@ -131,6 +133,7 @@ impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::Str {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Downgrades a literal value (integer, float, or URI).
|
||||||
impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::Literal {
|
impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::Literal {
|
||||||
fn downgrade(self, ctx: &mut Ctx) -> Result<ExprId> {
|
fn downgrade(self, ctx: &mut Ctx) -> Result<ExprId> {
|
||||||
Ok(ctx.new_expr(match self.kind() {
|
Ok(ctx.new_expr(match self.kind() {
|
||||||
@@ -144,6 +147,7 @@ impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::Literal {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Downgrades an identifier to a variable lookup.
|
||||||
impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::Ident {
|
impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::Ident {
|
||||||
fn downgrade(self, ctx: &mut Ctx) -> Result<ExprId> {
|
fn downgrade(self, ctx: &mut Ctx) -> Result<ExprId> {
|
||||||
let sym = self.ident_token().unwrap().to_string();
|
let sym = self.ident_token().unwrap().to_string();
|
||||||
@@ -151,6 +155,7 @@ impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::Ident {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Downgrades an attribute set.
|
||||||
impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::AttrSet {
|
impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::AttrSet {
|
||||||
fn downgrade(self, ctx: &mut Ctx) -> Result<ExprId> {
|
fn downgrade(self, ctx: &mut Ctx) -> Result<ExprId> {
|
||||||
let rec = self.rec_token().is_some();
|
let rec = self.rec_token().is_some();
|
||||||
@@ -160,6 +165,7 @@ impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::AttrSet {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Downgrades a list.
|
||||||
impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::List {
|
impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::List {
|
||||||
fn downgrade(self, ctx: &mut Ctx) -> Result<ExprId> {
|
fn downgrade(self, ctx: &mut Ctx) -> Result<ExprId> {
|
||||||
let mut items = Vec::with_capacity(self.items().size_hint().0);
|
let mut items = Vec::with_capacity(self.items().size_hint().0);
|
||||||
@@ -170,6 +176,7 @@ impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::List {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Downgrades a binary operation.
|
||||||
impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::BinOp {
|
impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::BinOp {
|
||||||
fn downgrade(self, ctx: &mut Ctx) -> Result<ExprId> {
|
fn downgrade(self, ctx: &mut Ctx) -> Result<ExprId> {
|
||||||
let lhs = self.lhs().unwrap().downgrade(ctx)?;
|
let lhs = self.lhs().unwrap().downgrade(ctx)?;
|
||||||
@@ -179,6 +186,7 @@ impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::BinOp {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Downgrades a "has attribute" (`?`) expression.
|
||||||
impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::HasAttr {
|
impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::HasAttr {
|
||||||
fn downgrade(self, ctx: &mut Ctx) -> Result<ExprId> {
|
fn downgrade(self, ctx: &mut Ctx) -> Result<ExprId> {
|
||||||
let lhs = self.expr().unwrap().downgrade(ctx)?;
|
let lhs = self.expr().unwrap().downgrade(ctx)?;
|
||||||
@@ -187,6 +195,7 @@ impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::HasAttr {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Downgrades a unary operation.
|
||||||
impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::UnaryOp {
|
impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::UnaryOp {
|
||||||
fn downgrade(self, ctx: &mut Ctx) -> Result<ExprId> {
|
fn downgrade(self, ctx: &mut Ctx) -> Result<ExprId> {
|
||||||
let rhs = self.expr().unwrap().downgrade(ctx)?;
|
let rhs = self.expr().unwrap().downgrade(ctx)?;
|
||||||
@@ -195,6 +204,7 @@ impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::UnaryOp {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Downgrades an attribute selection (`.`).
|
||||||
impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::Select {
|
impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::Select {
|
||||||
fn downgrade(self, ctx: &mut Ctx) -> Result<ExprId> {
|
fn downgrade(self, ctx: &mut Ctx) -> Result<ExprId> {
|
||||||
let expr = self.expr().unwrap().downgrade(ctx)?;
|
let expr = self.expr().unwrap().downgrade(ctx)?;
|
||||||
@@ -235,6 +245,7 @@ impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::LegacyLet {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Downgrades a `let ... in ...` expression.
|
||||||
impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::LetIn {
|
impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::LetIn {
|
||||||
fn downgrade(self, ctx: &mut Ctx) -> Result<ExprId> {
|
fn downgrade(self, ctx: &mut Ctx) -> Result<ExprId> {
|
||||||
let body = self.body().unwrap().downgrade(ctx)?;
|
let body = self.body().unwrap().downgrade(ctx)?;
|
||||||
@@ -243,6 +254,7 @@ impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::LetIn {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Downgrades a `with` expression.
|
||||||
impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::With {
|
impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::With {
|
||||||
fn downgrade(self, ctx: &mut Ctx) -> Result<ExprId> {
|
fn downgrade(self, ctx: &mut Ctx) -> Result<ExprId> {
|
||||||
let namespace = self.namespace().unwrap().downgrade(ctx)?;
|
let namespace = self.namespace().unwrap().downgrade(ctx)?;
|
||||||
@@ -251,6 +263,8 @@ impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::With {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Downgrades a lambda (function) expression.
|
||||||
|
/// This involves desugaring pattern-matching arguments into `let` bindings.
|
||||||
impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::Lambda {
|
impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::Lambda {
|
||||||
fn downgrade(self, ctx: &mut Ctx) -> Result<ExprId> {
|
fn downgrade(self, ctx: &mut Ctx) -> Result<ExprId> {
|
||||||
let param = downgrade_param(self.param().unwrap(), ctx)?;
|
let param = downgrade_param(self.param().unwrap(), ctx)?;
|
||||||
@@ -261,6 +275,7 @@ impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::Lambda {
|
|||||||
let allowed;
|
let allowed;
|
||||||
match param {
|
match param {
|
||||||
Param::Ident(id) => {
|
Param::Ident(id) => {
|
||||||
|
// Simple case: `x: body`
|
||||||
ident = Some(id);
|
ident = Some(id);
|
||||||
required = None;
|
required = None;
|
||||||
allowed = None;
|
allowed = None;
|
||||||
@@ -270,6 +285,7 @@ impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::Lambda {
|
|||||||
ellipsis,
|
ellipsis,
|
||||||
alias,
|
alias,
|
||||||
} => {
|
} => {
|
||||||
|
// Complex case: `{ a, b ? 2, ... }@args: body`
|
||||||
ident = alias.clone();
|
ident = alias.clone();
|
||||||
required = Some(
|
required = Some(
|
||||||
formals
|
formals
|
||||||
@@ -279,7 +295,7 @@ impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::Lambda {
|
|||||||
.collect(),
|
.collect(),
|
||||||
);
|
);
|
||||||
allowed = if ellipsis {
|
allowed = if ellipsis {
|
||||||
None
|
None // `...` means any attribute is allowed.
|
||||||
} else {
|
} else {
|
||||||
Some(formals.iter().map(|(k, _)| k.clone()).collect())
|
Some(formals.iter().map(|(k, _)| k.clone()).collect())
|
||||||
};
|
};
|
||||||
@@ -319,7 +335,7 @@ impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::Lambda {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let param = ir::Param {
|
let param = IrParam {
|
||||||
ident,
|
ident,
|
||||||
required,
|
required,
|
||||||
allowed,
|
allowed,
|
||||||
|
|||||||
@@ -6,10 +6,11 @@
|
|||||||
//! towards the lower-level IR (`nixjit_lir`).
|
//! towards the lower-level IR (`nixjit_lir`).
|
||||||
//!
|
//!
|
||||||
//! The key components are:
|
//! The key components are:
|
||||||
//! - `Hir`: An enum representing all possible expression types in the HIR.
|
//! - `Hir`: An enum representing all possible expression types in the HIR. This is
|
||||||
|
//! generated by the `ir!` macro.
|
||||||
//! - `Downgrade`: A trait for converting `rnix::ast` nodes into HIR expressions.
|
//! - `Downgrade`: A trait for converting `rnix::ast` nodes into HIR expressions.
|
||||||
//! - `DowngradeContext`: A trait that provides the necessary context for the conversion,
|
//! - `DowngradeContext`: A trait that provides the necessary context for the conversion,
|
||||||
//! such as allocating new expressions and functions.
|
//! such as allocating new expressions.
|
||||||
|
|
||||||
use derive_more::{IsVariant, TryUnwrap, Unwrap};
|
use derive_more::{IsVariant, TryUnwrap, Unwrap};
|
||||||
use hashbrown::HashMap;
|
use hashbrown::HashMap;
|
||||||
@@ -17,7 +18,7 @@ use hashbrown::HashMap;
|
|||||||
use nixjit_error::{Error, Result};
|
use nixjit_error::{Error, Result};
|
||||||
use nixjit_ir::{
|
use nixjit_ir::{
|
||||||
Assert, Attr, AttrSet, BinOp, Call, ConcatStrings, Const, ExprId, Func, HasAttr, If, List,
|
Assert, Attr, AttrSet, BinOp, Call, ConcatStrings, Const, ExprId, Func, HasAttr, If, List,
|
||||||
Path, Select, Str, UnOp, Var, With,
|
Param as IrParam, Path, Select, Str, UnOp, Var, With,
|
||||||
};
|
};
|
||||||
use nixjit_macros::ir;
|
use nixjit_macros::ir;
|
||||||
use nixjit_value::format_symbol;
|
use nixjit_value::format_symbol;
|
||||||
@@ -30,7 +31,7 @@ pub use downgrade::Downgrade;
|
|||||||
|
|
||||||
/// A context for the AST-to-HIR downgrading process.
|
/// A context for the AST-to-HIR downgrading process.
|
||||||
///
|
///
|
||||||
/// This trait abstracts the storage of HIR expressions and functions, allowing the
|
/// This trait abstracts the storage of HIR expressions, allowing the
|
||||||
/// `downgrade` implementations to be generic over the specific context implementation.
|
/// `downgrade` implementations to be generic over the specific context implementation.
|
||||||
pub trait DowngradeContext {
|
pub trait DowngradeContext {
|
||||||
/// Allocates a new HIR expression in the context and returns its ID.
|
/// Allocates a new HIR expression in the context and returns its ID.
|
||||||
@@ -43,6 +44,8 @@ pub trait DowngradeContext {
|
|||||||
fn with_expr_mut<T>(&mut self, id: &ExprId, f: impl FnOnce(&mut Hir, &mut Self) -> T) -> T;
|
fn with_expr_mut<T>(&mut self, id: &ExprId, f: impl FnOnce(&mut Hir, &mut Self) -> T) -> T;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The `ir!` macro generates the `Hir` enum and related structs and traits.
|
||||||
|
// This reduces boilerplate for defining the IR structure.
|
||||||
ir! {
|
ir! {
|
||||||
Hir,
|
Hir,
|
||||||
|
|
||||||
@@ -80,10 +83,12 @@ ir! {
|
|||||||
Path,
|
Path,
|
||||||
// Represents a `let ... in ...` binding.
|
// Represents a `let ... in ...` binding.
|
||||||
Let { pub bindings: HashMap<String, ExprId>, pub body: ExprId },
|
Let { pub bindings: HashMap<String, ExprId>, pub body: ExprId },
|
||||||
// Represents a function argument lookup.
|
// Represents a function argument lookup within the body of a function.
|
||||||
Arg,
|
Arg,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A placeholder struct for the `Arg` HIR variant. It signifies that at this point
|
||||||
|
/// in the expression tree, we should be looking up a function argument.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Arg;
|
pub struct Arg;
|
||||||
|
|
||||||
@@ -91,8 +96,8 @@ pub struct Arg;
|
|||||||
trait Attrs {
|
trait Attrs {
|
||||||
/// Inserts a value into the attribute set at a given path.
|
/// Inserts a value into the attribute set at a given path.
|
||||||
///
|
///
|
||||||
/// # Example
|
/// This method handles the creation of nested attribute sets as needed.
|
||||||
/// `insert([a, b], value)` corresponds to `a.b = value;`.
|
/// For example, `insert([a, b], value)` on an empty set results in `{ a = { b = value; }; }`.
|
||||||
fn insert(
|
fn insert(
|
||||||
&mut self,
|
&mut self,
|
||||||
path: Vec<Attr>,
|
path: Vec<Attr>,
|
||||||
@@ -129,10 +134,10 @@ impl Attrs for AttrSet {
|
|||||||
expr.as_mut()
|
expr.as_mut()
|
||||||
.try_unwrap_attr_set()
|
.try_unwrap_attr_set()
|
||||||
.map_err(|_| {
|
.map_err(|_| {
|
||||||
// This path segment exists but is not an attrset.
|
// This path segment exists but is not an attrset, which is an error.
|
||||||
Error::DowngradeError(format!(
|
Error::DowngradeError(format!(
|
||||||
r#"attribute '{}' already defined"#,
|
"attribute '{}' already defined but is not an attribute set",
|
||||||
format_symbol(&ident)
|
format_symbol(ident)
|
||||||
))
|
))
|
||||||
})
|
})
|
||||||
.and_then(|attrs| attrs._insert(path, name, value, ctx))
|
.and_then(|attrs| attrs._insert(path, name, value, ctx))
|
||||||
@@ -147,7 +152,8 @@ impl Attrs for AttrSet {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
Attr::Dynamic(dynamic) => {
|
Attr::Dynamic(dynamic) => {
|
||||||
// If the next attribute is a dynamic expression.
|
// If the next attribute is a dynamic expression, we must create a new sub-attrset.
|
||||||
|
// We cannot merge with existing dynamic attributes at this stage.
|
||||||
let mut attrs = AttrSet::default();
|
let mut attrs = AttrSet::default();
|
||||||
attrs._insert(path, name, value, ctx)?;
|
attrs._insert(path, name, value, ctx)?;
|
||||||
self.dyns.push((dynamic, ctx.new_expr(attrs.to_hir())));
|
self.dyns.push((dynamic, ctx.new_expr(attrs.to_hir())));
|
||||||
@@ -160,8 +166,8 @@ impl Attrs for AttrSet {
|
|||||||
Attr::Str(ident) => {
|
Attr::Str(ident) => {
|
||||||
if self.stcs.insert(ident.clone(), value).is_some() {
|
if self.stcs.insert(ident.clone(), value).is_some() {
|
||||||
return Err(Error::DowngradeError(format!(
|
return Err(Error::DowngradeError(format!(
|
||||||
r#"attribute '{}' already defined"#,
|
"attribute '{}' already defined",
|
||||||
format_symbol(&ident)
|
format_symbol(ident)
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -186,6 +192,7 @@ impl Attrs for AttrSet {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Represents the different kinds of parameters a function can have in the HIR stage.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
enum Param {
|
enum Param {
|
||||||
/// A simple parameter, e.g., `x: ...`.
|
/// A simple parameter, e.g., `x: ...`.
|
||||||
|
|||||||
@@ -10,9 +10,8 @@ use rnix::ast;
|
|||||||
use nixjit_error::{Error, Result};
|
use nixjit_error::{Error, Result};
|
||||||
use nixjit_ir::{Attr, AttrSet, ConcatStrings, ExprId, Select, Str, Var};
|
use nixjit_ir::{Attr, AttrSet, ConcatStrings, ExprId, Select, Str, Var};
|
||||||
|
|
||||||
use super::ToHir;
|
|
||||||
use super::downgrade::Downgrade;
|
use super::downgrade::Downgrade;
|
||||||
use super::{Attrs, DowngradeContext, Param};
|
use super::{Attrs, DowngradeContext, Param, ToHir};
|
||||||
|
|
||||||
/// Downgrades a function parameter from the AST.
|
/// Downgrades a function parameter from the AST.
|
||||||
pub fn downgrade_param(param: ast::Param, ctx: &mut impl DowngradeContext) -> Result<Param> {
|
pub fn downgrade_param(param: ast::Param, ctx: &mut impl DowngradeContext) -> Result<Param> {
|
||||||
@@ -215,7 +214,7 @@ pub fn downgrade_static_attrpathvalue(
|
|||||||
let path = downgrade_attrpath(value.attrpath().unwrap(), ctx)?;
|
let path = downgrade_attrpath(value.attrpath().unwrap(), ctx)?;
|
||||||
if path.iter().any(|attr| matches!(attr, Attr::Dynamic(_))) {
|
if path.iter().any(|attr| matches!(attr, Attr::Dynamic(_))) {
|
||||||
return Err(Error::DowngradeError(
|
return Err(Error::DowngradeError(
|
||||||
"dynamic attributes not allowed".to_string(),
|
"dynamic attributes not allowed in let bindings".to_string(),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
let value = value.value().unwrap().downgrade(ctx)?;
|
let value = value.value().unwrap().downgrade(ctx)?;
|
||||||
|
|||||||
@@ -17,21 +17,36 @@ use hashbrown::{HashMap, HashSet};
|
|||||||
use nixjit_value::Const as PubConst;
|
use nixjit_value::Const as PubConst;
|
||||||
|
|
||||||
/// A type-safe wrapper for an index into an expression table.
|
/// A type-safe wrapper for an index into an expression table.
|
||||||
|
///
|
||||||
|
/// Using a newtype wrapper like this prevents accidentally mixing up different kinds of indices.
|
||||||
#[repr(transparent)]
|
#[repr(transparent)]
|
||||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||||
pub struct ExprId(usize);
|
pub struct ExprId(usize);
|
||||||
|
|
||||||
impl ExprId {
|
impl ExprId {
|
||||||
|
/// Creates a clone of the `ExprId`.
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
/// This is a shallow copy of the index. The caller must ensure that the lifetime
|
||||||
|
/// and validity of the expression being referenced are handled correctly.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub unsafe fn clone(&self) -> Self {
|
pub unsafe fn clone(&self) -> Self {
|
||||||
Self(self.0)
|
Self(self.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the raw `usize` index.
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
/// The caller is responsible for using this index correctly and not causing out-of-bounds access.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub unsafe fn raw(self) -> usize {
|
pub unsafe fn raw(self) -> usize {
|
||||||
self.0
|
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)]
|
#[inline(always)]
|
||||||
pub unsafe fn from(id: usize) -> Self {
|
pub unsafe fn from(id: usize) -> Self {
|
||||||
Self(id)
|
Self(id)
|
||||||
@@ -44,11 +59,19 @@ impl ExprId {
|
|||||||
pub struct PrimOpId(usize);
|
pub struct PrimOpId(usize);
|
||||||
|
|
||||||
impl PrimOpId {
|
impl PrimOpId {
|
||||||
|
/// Returns the raw `usize` index.
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
/// The caller is responsible for using this index correctly.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub unsafe fn raw(self) -> usize {
|
pub unsafe fn raw(self) -> usize {
|
||||||
self.0
|
self.0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Creates a `PrimOpId` from a raw `usize` index.
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
/// The caller must ensure that the provided index is valid.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub unsafe fn from(id: usize) -> Self {
|
pub unsafe fn from(id: usize) -> Self {
|
||||||
Self(id)
|
Self(id)
|
||||||
@@ -61,11 +84,19 @@ impl PrimOpId {
|
|||||||
pub struct ArgIdx(usize);
|
pub struct ArgIdx(usize);
|
||||||
|
|
||||||
impl ArgIdx {
|
impl ArgIdx {
|
||||||
|
/// Returns the raw `usize` index.
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
/// The caller is responsible for using this index correctly.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub unsafe fn raw(self) -> usize {
|
pub unsafe fn raw(self) -> usize {
|
||||||
self.0
|
self.0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Creates an `ArgIdx` from a raw `usize` index.
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
/// The caller must ensure that the provided index is valid.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub unsafe fn from(idx: usize) -> Self {
|
pub unsafe fn from(idx: usize) -> Self {
|
||||||
Self(idx)
|
Self(idx)
|
||||||
@@ -87,8 +118,10 @@ pub struct AttrSet {
|
|||||||
#[derive(Debug, TryUnwrap)]
|
#[derive(Debug, TryUnwrap)]
|
||||||
pub enum Attr {
|
pub enum Attr {
|
||||||
/// A dynamic attribute key, which is an expression that must evaluate to a string.
|
/// A dynamic attribute key, which is an expression that must evaluate to a string.
|
||||||
|
/// Example: `attrs.${key}`
|
||||||
Dynamic(ExprId),
|
Dynamic(ExprId),
|
||||||
/// A static attribute key.
|
/// A static attribute key.
|
||||||
|
/// Example: `attrs.key`
|
||||||
Str(String),
|
Str(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -220,13 +253,20 @@ pub struct If {
|
|||||||
pub struct Func {
|
pub struct Func {
|
||||||
/// The body of the function
|
/// The body of the function
|
||||||
pub body: ExprId,
|
pub body: ExprId,
|
||||||
|
/// The parameter specification for the function.
|
||||||
pub param: Param,
|
pub param: Param,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Describes the parameters of a function.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Param {
|
pub struct Param {
|
||||||
|
/// The name of the argument if it's a simple identifier (e.g., `x: ...`).
|
||||||
|
/// Also used for the alias in a pattern (e.g., `args @ { ... }`).
|
||||||
pub ident: Option<String>,
|
pub ident: Option<String>,
|
||||||
|
/// The set of required parameter names for a pattern-matching function.
|
||||||
pub required: Option<Vec<String>>,
|
pub required: Option<Vec<String>>,
|
||||||
|
/// The set of all allowed parameter names for a non-ellipsis pattern-matching function.
|
||||||
|
/// If `None`, any attribute is allowed (ellipsis `...` is present).
|
||||||
pub allowed: Option<HashSet<String>>,
|
pub allowed: Option<HashSet<String>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,13 +1,29 @@
|
|||||||
|
//! This module defines the `JITCompile` trait and its implementations for
|
||||||
|
//! various IR types. It provides the translation from LIR to Cranelift IR.
|
||||||
|
|
||||||
use cranelift::codegen::ir::{self, StackSlot};
|
use cranelift::codegen::ir::{self, StackSlot};
|
||||||
use cranelift::prelude::*;
|
use cranelift::prelude::*;
|
||||||
|
|
||||||
use nixjit_eval::{EvalContext, Value};
|
use nixjit_eval::Value;
|
||||||
use nixjit_ir::*;
|
use nixjit_ir::*;
|
||||||
use nixjit_lir::Lir;
|
use nixjit_lir::Lir;
|
||||||
|
|
||||||
use super::{Context, JITContext};
|
use super::{Context, JITContext};
|
||||||
|
|
||||||
|
/// A trait for compiling IR nodes to Cranelift IR.
|
||||||
|
///
|
||||||
|
/// This trait defines how different IR nodes should be compiled to
|
||||||
|
/// Cranelift IR instructions that can be executed by the JIT compiler.
|
||||||
pub trait JITCompile<Ctx: JITContext> {
|
pub trait JITCompile<Ctx: JITContext> {
|
||||||
|
/// Compiles the IR node to Cranelift IR.
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
/// * `ctx` - The compilation context
|
||||||
|
/// * `engine` - The evaluation context value
|
||||||
|
/// * `env` - The environment value
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// A stack slot containing the compiled result
|
||||||
fn compile(&self, ctx: &mut Context<Ctx>, engine: ir::Value, env: ir::Value) -> StackSlot;
|
fn compile(&self, ctx: &mut Context<Ctx>, engine: ir::Value, env: ir::Value) -> StackSlot;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -24,6 +40,9 @@ impl<Ctx: JITContext> JITCompile<Ctx> for Lir {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<Ctx: JITContext> JITCompile<Ctx> for AttrSet {
|
impl<Ctx: JITContext> JITCompile<Ctx> for AttrSet {
|
||||||
|
/// Compiles an attribute set to Cranelift IR.
|
||||||
|
///
|
||||||
|
/// This creates a new attribute set and compiles all static attributes into it.
|
||||||
fn compile(&self, ctx: &mut Context<Ctx>, engine: ir::Value, env: ir::Value) -> StackSlot {
|
fn compile(&self, ctx: &mut Context<Ctx>, engine: ir::Value, env: ir::Value) -> StackSlot {
|
||||||
let attrs = ctx.create_attrs();
|
let attrs = ctx.create_attrs();
|
||||||
for (k, v) in self.stcs.iter() {
|
for (k, v) in self.stcs.iter() {
|
||||||
@@ -35,6 +54,9 @@ impl<Ctx: JITContext> JITCompile<Ctx> for AttrSet {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<Ctx: JITContext> JITCompile<Ctx> for List {
|
impl<Ctx: JITContext> JITCompile<Ctx> for List {
|
||||||
|
/// Compiles a list to Cranelift IR.
|
||||||
|
///
|
||||||
|
/// This creates a new list by compiling all items and storing them in an array.
|
||||||
fn compile(&self, ctx: &mut Context<Ctx>, engine: ir::Value, env: ir::Value) -> StackSlot {
|
fn compile(&self, ctx: &mut Context<Ctx>, engine: ir::Value, env: ir::Value) -> StackSlot {
|
||||||
let array = ctx.alloc_array(self.items.len());
|
let array = ctx.alloc_array(self.items.len());
|
||||||
for (i, item) in self.items.iter().enumerate() {
|
for (i, item) in self.items.iter().enumerate() {
|
||||||
@@ -67,6 +89,11 @@ impl<Ctx: JITContext> JITCompile<Ctx> for HasAttr {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<Ctx: JITContext> JITCompile<Ctx> for BinOp {
|
impl<Ctx: JITContext> JITCompile<Ctx> for BinOp {
|
||||||
|
/// Compiles a binary operation to Cranelift IR.
|
||||||
|
///
|
||||||
|
/// This implementation handles various binary operations like addition, subtraction,
|
||||||
|
/// division, logical AND/OR, and equality checks. It generates code that checks
|
||||||
|
/// the types of operands and performs the appropriate operation.
|
||||||
fn compile(&self, ctx: &mut Context<Ctx>, engine: ir::Value, env: ir::Value) -> StackSlot {
|
fn compile(&self, ctx: &mut Context<Ctx>, engine: ir::Value, env: ir::Value) -> StackSlot {
|
||||||
use BinOpKind::*;
|
use BinOpKind::*;
|
||||||
let lhs = self.lhs.compile(ctx, engine, env);
|
let lhs = self.lhs.compile(ctx, engine, env);
|
||||||
@@ -328,6 +355,9 @@ impl<Ctx: JITContext> JITCompile<Ctx> for UnOp {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<Ctx: JITContext> JITCompile<Ctx> for Attr {
|
impl<Ctx: JITContext> JITCompile<Ctx> for Attr {
|
||||||
|
/// Compiles an attribute key to Cranelift IR.
|
||||||
|
///
|
||||||
|
/// An attribute can be either a static string or a dynamic expression.
|
||||||
fn compile(&self, ctx: &mut Context<Ctx>, engine: ir::Value, env: ir::Value) -> StackSlot {
|
fn compile(&self, ctx: &mut Context<Ctx>, engine: ir::Value, env: ir::Value) -> StackSlot {
|
||||||
use Attr::*;
|
use Attr::*;
|
||||||
match self {
|
match self {
|
||||||
@@ -338,6 +368,10 @@ impl<Ctx: JITContext> JITCompile<Ctx> for Attr {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<Ctx: JITContext> JITCompile<Ctx> for Select {
|
impl<Ctx: JITContext> JITCompile<Ctx> for Select {
|
||||||
|
/// Compiles an attribute selection to Cranelift IR.
|
||||||
|
///
|
||||||
|
/// This compiles the expression to select from, builds the attribute path,
|
||||||
|
/// and calls the select helper function.
|
||||||
fn compile(&self, ctx: &mut Context<Ctx>, engine: ir::Value, env: ir::Value) -> StackSlot {
|
fn compile(&self, ctx: &mut Context<Ctx>, engine: ir::Value, env: ir::Value) -> StackSlot {
|
||||||
let val = self.expr.compile(ctx, engine, env);
|
let val = self.expr.compile(ctx, engine, env);
|
||||||
let attrpath = ctx.alloc_array(self.attrpath.len());
|
let attrpath = ctx.alloc_array(self.attrpath.len());
|
||||||
@@ -366,6 +400,10 @@ impl<Ctx: JITContext> JITCompile<Ctx> for Select {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<Ctx: JITContext> JITCompile<Ctx> for If {
|
impl<Ctx: JITContext> JITCompile<Ctx> for If {
|
||||||
|
/// Compiles an if-expression to Cranelift IR.
|
||||||
|
///
|
||||||
|
/// This generates code that evaluates the condition, checks that it's a boolean,
|
||||||
|
/// and then jumps to the appropriate branch (true or false).
|
||||||
fn compile(&self, ctx: &mut Context<Ctx>, engine: ir::Value, env: ir::Value) -> StackSlot {
|
fn compile(&self, ctx: &mut Context<Ctx>, engine: ir::Value, env: ir::Value) -> StackSlot {
|
||||||
let cond = self.cond.compile(ctx, engine, env);
|
let cond = self.cond.compile(ctx, engine, env);
|
||||||
let cond_type = ctx.builder.ins().stack_load(types::I64, cond, 0);
|
let cond_type = ctx.builder.ins().stack_load(types::I64, cond, 0);
|
||||||
@@ -424,6 +462,10 @@ impl<Ctx: JITContext> JITCompile<Ctx> for If {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<Ctx: JITContext> JITCompile<Ctx> for Call {
|
impl<Ctx: JITContext> JITCompile<Ctx> for Call {
|
||||||
|
/// Compiles a function call to Cranelift IR.
|
||||||
|
///
|
||||||
|
/// This compiles the function expression and all arguments, builds an argument array,
|
||||||
|
/// and calls the call helper function.
|
||||||
fn compile(&self, ctx: &mut Context<Ctx>, engine: ir::Value, env: ir::Value) -> StackSlot {
|
fn compile(&self, ctx: &mut Context<Ctx>, engine: ir::Value, env: ir::Value) -> StackSlot {
|
||||||
let func = self.func.compile(ctx, engine, env);
|
let func = self.func.compile(ctx, engine, env);
|
||||||
let args = ctx.alloc_array(self.args.len());
|
let args = ctx.alloc_array(self.args.len());
|
||||||
@@ -452,6 +494,10 @@ impl<Ctx: JITContext> JITCompile<Ctx> for Call {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<Ctx: JITContext> JITCompile<Ctx> for With {
|
impl<Ctx: JITContext> JITCompile<Ctx> for With {
|
||||||
|
/// Compiles a `with` expression to Cranelift IR.
|
||||||
|
///
|
||||||
|
/// This enters a new `with` scope with the compiled namespace, compiles the body expression,
|
||||||
|
/// and then exits the `with` scope.
|
||||||
fn compile(&self, ctx: &mut Context<Ctx>, engine: ir::Value, env: ir::Value) -> StackSlot {
|
fn compile(&self, ctx: &mut Context<Ctx>, engine: ir::Value, env: ir::Value) -> StackSlot {
|
||||||
let namespace = self.namespace.compile(ctx, engine, env);
|
let namespace = self.namespace.compile(ctx, engine, env);
|
||||||
ctx.enter_with(env, namespace);
|
ctx.enter_with(env, namespace);
|
||||||
@@ -475,6 +521,10 @@ impl<Ctx: JITContext> JITCompile<Ctx> for ConcatStrings {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<Ctx: JITContext> JITCompile<Ctx> for Const {
|
impl<Ctx: JITContext> JITCompile<Ctx> for Const {
|
||||||
|
/// Compiles a constant value to Cranelift IR.
|
||||||
|
///
|
||||||
|
/// This handles boolean, integer, float, and null constants by storing
|
||||||
|
/// their values and type tags in a stack slot.
|
||||||
fn compile(&self, ctx: &mut Context<Ctx>, engine: ir::Value, env: ir::Value) -> StackSlot {
|
fn compile(&self, ctx: &mut Context<Ctx>, engine: ir::Value, env: ir::Value) -> StackSlot {
|
||||||
use nixjit_value::Const::*;
|
use nixjit_value::Const::*;
|
||||||
let slot = ctx.alloca();
|
let slot = ctx.alloca();
|
||||||
@@ -507,12 +557,18 @@ impl<Ctx: JITContext> JITCompile<Ctx> for Const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<Ctx: JITContext> JITCompile<Ctx> for Str {
|
impl<Ctx: JITContext> JITCompile<Ctx> for Str {
|
||||||
|
/// Compiles a string literal to Cranelift IR.
|
||||||
|
///
|
||||||
|
/// This creates a string value from the string literal.
|
||||||
fn compile(&self, ctx: &mut Context<Ctx>, engine: ir::Value, env: ir::Value) -> StackSlot {
|
fn compile(&self, ctx: &mut Context<Ctx>, engine: ir::Value, env: ir::Value) -> StackSlot {
|
||||||
ctx.create_string(&self.val)
|
ctx.create_string(&self.val)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Ctx: JITContext> JITCompile<Ctx> for Var {
|
impl<Ctx: JITContext> JITCompile<Ctx> for Var {
|
||||||
|
/// Compiles a variable lookup to Cranelift IR.
|
||||||
|
///
|
||||||
|
/// This looks up a variable by its symbol in the current environment.
|
||||||
fn compile(&self, ctx: &mut Context<Ctx>, engine: ir::Value, env: ir::Value) -> StackSlot {
|
fn compile(&self, ctx: &mut Context<Ctx>, engine: ir::Value, env: ir::Value) -> StackSlot {
|
||||||
ctx.lookup(env, &self.sym)
|
ctx.lookup(env, &self.sym)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,8 @@
|
|||||||
|
//! Helper functions for the JIT compiler.
|
||||||
|
//!
|
||||||
|
//! These functions are called from JIT-compiled code to perform operations
|
||||||
|
//! that are difficult or unsafe to do directly in the generated IR.
|
||||||
|
|
||||||
use core::{slice, str};
|
use core::{slice, str};
|
||||||
use std::alloc::Layout;
|
use std::alloc::Layout;
|
||||||
use std::alloc::alloc;
|
use std::alloc::alloc;
|
||||||
@@ -10,6 +15,10 @@ use nixjit_eval::{AttrSet, EvalContext, List, Value};
|
|||||||
|
|
||||||
use super::JITContext;
|
use super::JITContext;
|
||||||
|
|
||||||
|
/// Helper function to call a function with arguments.
|
||||||
|
///
|
||||||
|
/// This function is called from JIT-compiled code to perform function calls.
|
||||||
|
/// It takes a function value and an array of arguments, and executes the call.
|
||||||
pub extern "C" fn helper_call<Ctx: JITContext>(
|
pub extern "C" fn helper_call<Ctx: JITContext>(
|
||||||
func: &mut Value,
|
func: &mut Value,
|
||||||
args_ptr: *mut Value,
|
args_ptr: *mut Value,
|
||||||
@@ -22,6 +31,9 @@ pub extern "C" fn helper_call<Ctx: JITContext>(
|
|||||||
func.call(args.into_iter().map(Ok), ctx).unwrap();
|
func.call(args.into_iter().map(Ok), ctx).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Helper function to look up a value in the evaluation stack.
|
||||||
|
///
|
||||||
|
/// This function is called from JIT-compiled code to access values in the evaluation stack.
|
||||||
pub extern "C" fn helper_lookup_stack<Ctx: JITContext>(
|
pub extern "C" fn helper_lookup_stack<Ctx: JITContext>(
|
||||||
ctx: &Ctx,
|
ctx: &Ctx,
|
||||||
offset: usize,
|
offset: usize,
|
||||||
@@ -30,6 +42,9 @@ pub extern "C" fn helper_lookup_stack<Ctx: JITContext>(
|
|||||||
ret.write(ctx.lookup_stack(offset).clone());
|
ret.write(ctx.lookup_stack(offset).clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Helper function to look up a function argument.
|
||||||
|
///
|
||||||
|
/// This function is called from JIT-compiled code to access function arguments.
|
||||||
pub extern "C" fn helper_lookup_arg<Ctx: JITContext>(
|
pub extern "C" fn helper_lookup_arg<Ctx: JITContext>(
|
||||||
ctx: &Ctx,
|
ctx: &Ctx,
|
||||||
offset: usize,
|
offset: usize,
|
||||||
@@ -38,6 +53,10 @@ pub extern "C" fn helper_lookup_arg<Ctx: JITContext>(
|
|||||||
ret.write(JITContext::lookup_arg(ctx, offset).clone());
|
ret.write(JITContext::lookup_arg(ctx, offset).clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Helper function to look up a variable by name.
|
||||||
|
///
|
||||||
|
/// This function is called from JIT-compiled code to perform variable lookups
|
||||||
|
/// in the current scope and `with` expression scopes.
|
||||||
pub extern "C" fn helper_lookup<Ctx: JITContext>(
|
pub extern "C" fn helper_lookup<Ctx: JITContext>(
|
||||||
ctx: &Ctx,
|
ctx: &Ctx,
|
||||||
sym_ptr: *const u8,
|
sym_ptr: *const u8,
|
||||||
@@ -56,6 +75,10 @@ pub extern "C" fn helper_lookup<Ctx: JITContext>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Helper function to perform attribute selection.
|
||||||
|
///
|
||||||
|
/// This function is called from JIT-compiled code to select attributes from
|
||||||
|
/// an attribute set using a path of attribute names.
|
||||||
pub extern "C" fn helper_select<Ctx: JITContext>(
|
pub extern "C" fn helper_select<Ctx: JITContext>(
|
||||||
val: &mut Value,
|
val: &mut Value,
|
||||||
path_ptr: *mut Value,
|
path_ptr: *mut Value,
|
||||||
@@ -70,6 +93,10 @@ pub extern "C" fn helper_select<Ctx: JITContext>(
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Helper function to perform attribute selection with a default value.
|
||||||
|
///
|
||||||
|
/// This function is called from JIT-compiled code to select attributes from
|
||||||
|
/// an attribute set, returning a default value if the selection fails.
|
||||||
pub extern "C" fn helper_select_with_default<Ctx: JITContext>(
|
pub extern "C" fn helper_select_with_default<Ctx: JITContext>(
|
||||||
val: &mut Value,
|
val: &mut Value,
|
||||||
path_ptr: *mut Value,
|
path_ptr: *mut Value,
|
||||||
@@ -88,10 +115,17 @@ pub extern "C" fn helper_select_with_default<Ctx: JITContext>(
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Helper function to check equality between two values.
|
||||||
|
///
|
||||||
|
/// This function is called from JIT-compiled code to perform equality comparisons.
|
||||||
pub extern "C" fn helper_eq<Ctx: JITContext>(lhs: &mut Value, rhs: &Value) {
|
pub extern "C" fn helper_eq<Ctx: JITContext>(lhs: &mut Value, rhs: &Value) {
|
||||||
lhs.eq(rhs);
|
lhs.eq(rhs);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Helper function to create a string value.
|
||||||
|
///
|
||||||
|
/// This function is called from JIT-compiled code to create string values
|
||||||
|
/// from raw byte arrays.
|
||||||
pub unsafe extern "C" fn helper_create_string<Ctx: JITContext>(
|
pub unsafe extern "C" fn helper_create_string<Ctx: JITContext>(
|
||||||
ptr: *const u8,
|
ptr: *const u8,
|
||||||
len: usize,
|
len: usize,
|
||||||
@@ -104,6 +138,10 @@ pub unsafe extern "C" fn helper_create_string<Ctx: JITContext>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Helper function to create a list value.
|
||||||
|
///
|
||||||
|
/// This function is called from JIT-compiled code to create list values
|
||||||
|
/// from arrays of values.
|
||||||
pub unsafe extern "C" fn helper_create_list<Ctx: JITContext>(
|
pub unsafe extern "C" fn helper_create_list<Ctx: JITContext>(
|
||||||
ptr: *mut Value,
|
ptr: *mut Value,
|
||||||
len: usize,
|
len: usize,
|
||||||
@@ -116,12 +154,19 @@ pub unsafe extern "C" fn helper_create_list<Ctx: JITContext>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Helper function to create an attribute set.
|
||||||
|
///
|
||||||
|
/// This function is called from JIT-compiled code to create a new, empty attribute set.
|
||||||
pub unsafe extern "C" fn helper_create_attrs<Ctx: JITContext>(
|
pub unsafe extern "C" fn helper_create_attrs<Ctx: JITContext>(
|
||||||
ret: &mut MaybeUninit<HashMap<String, Value>>,
|
ret: &mut MaybeUninit<HashMap<String, Value>>,
|
||||||
) {
|
) {
|
||||||
ret.write(HashMap::new());
|
ret.write(HashMap::new());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Helper function to add an attribute to an attribute set.
|
||||||
|
///
|
||||||
|
/// This function is called from JIT-compiled code to insert a key-value pair
|
||||||
|
/// into an attribute set.
|
||||||
pub unsafe extern "C" fn helper_push_attr<Ctx: JITContext>(
|
pub unsafe extern "C" fn helper_push_attr<Ctx: JITContext>(
|
||||||
attrs: &mut HashMap<String, Value>,
|
attrs: &mut HashMap<String, Value>,
|
||||||
sym_ptr: *const u8,
|
sym_ptr: *const u8,
|
||||||
@@ -136,6 +181,10 @@ pub unsafe extern "C" fn helper_push_attr<Ctx: JITContext>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Helper function to finalize an attribute set.
|
||||||
|
///
|
||||||
|
/// This function is called from JIT-compiled code to convert a HashMap into
|
||||||
|
/// a proper attribute set value.
|
||||||
pub unsafe extern "C" fn helper_finalize_attrs<Ctx: JITContext>(
|
pub unsafe extern "C" fn helper_finalize_attrs<Ctx: JITContext>(
|
||||||
attrs: NonNull<HashMap<String, Value>>,
|
attrs: NonNull<HashMap<String, Value>>,
|
||||||
ret: &mut MaybeUninit<Value>,
|
ret: &mut MaybeUninit<Value>,
|
||||||
@@ -145,6 +194,10 @@ pub unsafe extern "C" fn helper_finalize_attrs<Ctx: JITContext>(
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Helper function to enter a `with` expression scope.
|
||||||
|
///
|
||||||
|
/// This function is called from JIT-compiled code to enter a new `with` scope
|
||||||
|
/// with the given namespace.
|
||||||
pub unsafe extern "C" fn helper_enter_with<Ctx: JITContext>(
|
pub unsafe extern "C" fn helper_enter_with<Ctx: JITContext>(
|
||||||
ctx: &mut Ctx,
|
ctx: &mut Ctx,
|
||||||
namespace: NonNull<Value>,
|
namespace: NonNull<Value>,
|
||||||
@@ -152,14 +205,24 @@ pub unsafe extern "C" fn helper_enter_with<Ctx: JITContext>(
|
|||||||
ctx.enter_with(unsafe { namespace.read() }.unwrap_attr_set().into_inner());
|
ctx.enter_with(unsafe { namespace.read() }.unwrap_attr_set().into_inner());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Helper function to exit a `with` expression scope.
|
||||||
|
///
|
||||||
|
/// This function is called from JIT-compiled code to exit the current `with` scope.
|
||||||
pub unsafe extern "C" fn helper_exit_with<Ctx: JITContext>(ctx: &mut Ctx) {
|
pub unsafe extern "C" fn helper_exit_with<Ctx: JITContext>(ctx: &mut Ctx) {
|
||||||
ctx.exit_with();
|
ctx.exit_with();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Helper function to allocate an array of values.
|
||||||
|
///
|
||||||
|
/// This function is called from JIT-compiled code to allocate memory for
|
||||||
|
/// arrays of values, such as function arguments or list elements.
|
||||||
pub unsafe extern "C" fn helper_alloc_array<Ctx: JITContext>(len: usize) -> *mut u8 {
|
pub unsafe extern "C" fn helper_alloc_array<Ctx: JITContext>(len: usize) -> *mut u8 {
|
||||||
unsafe { alloc(Layout::array::<Value>(len).unwrap()) }
|
unsafe { alloc(Layout::array::<Value>(len).unwrap()) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Helper function for debugging.
|
||||||
|
///
|
||||||
|
/// This function is called from JIT-compiled code to print a value for debugging purposes.
|
||||||
pub extern "C" fn helper_dbg<Ctx: JITContext>(value: &Value) {
|
pub extern "C" fn helper_dbg<Ctx: JITContext>(value: &Value) {
|
||||||
println!("{value:?}")
|
println!("{value:?}")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,14 @@
|
|||||||
|
//! The Just-In-Time (JIT) compilation module for nixjit.
|
||||||
|
//!
|
||||||
|
//! This module provides functionality to compile Low-Level IR (LIR) expressions
|
||||||
|
//! into optimized machine code using Cranelift. The JIT compiler translates
|
||||||
|
//! Nix expressions into efficient native code for faster evaluation.
|
||||||
|
//!
|
||||||
|
//! The main components are:
|
||||||
|
//! - `JITCompiler`: The core compiler that manages the compilation process
|
||||||
|
//! - `JITContext`: A trait that provides the execution context for JIT-compiled code
|
||||||
|
//! - `Context`: An internal compilation context used during code generation
|
||||||
|
|
||||||
use std::marker::PhantomData;
|
use std::marker::PhantomData;
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
@@ -18,15 +29,33 @@ mod helpers;
|
|||||||
pub use compile::JITCompile;
|
pub use compile::JITCompile;
|
||||||
use helpers::*;
|
use helpers::*;
|
||||||
|
|
||||||
pub trait JITContext: EvalContext + Sized {
|
/// A trait that provides the execution context for JIT-compiled code.
|
||||||
|
///
|
||||||
|
/// This trait extends `EvalContext` with additional methods needed
|
||||||
|
/// for JIT compilation, such as stack and argument lookups, and
|
||||||
|
/// managing `with` expression scopes.
|
||||||
|
pub trait JITContext: EvalContext {
|
||||||
|
/// Looks up a value in the evaluation stack by offset.
|
||||||
fn lookup_stack(&self, offset: usize) -> &Value;
|
fn lookup_stack(&self, offset: usize) -> &Value;
|
||||||
|
/// Looks up a function argument by offset.
|
||||||
fn lookup_arg(&self, offset: usize) -> &Value;
|
fn lookup_arg(&self, offset: usize) -> &Value;
|
||||||
|
/// Enters a `with` expression scope with the given namespace.
|
||||||
fn enter_with(&mut self, namespace: Rc<HashMap<String, Value>>);
|
fn enter_with(&mut self, namespace: Rc<HashMap<String, Value>>);
|
||||||
|
/// Exits the current `with` expression scope.
|
||||||
fn exit_with(&mut self);
|
fn exit_with(&mut self);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Type alias for a JIT-compiled function.
|
||||||
|
///
|
||||||
|
/// This represents a function pointer to JIT-compiled code that takes
|
||||||
|
/// a context pointer and a mutable value pointer as arguments.
|
||||||
type F<Ctx> = unsafe extern "C" fn(*const Ctx, *mut Value);
|
type F<Ctx> = unsafe extern "C" fn(*const Ctx, *mut Value);
|
||||||
|
|
||||||
|
/// A JIT-compiled function.
|
||||||
|
///
|
||||||
|
/// This struct holds a function pointer to the compiled code and
|
||||||
|
/// a set of strings that were used during compilation, which need
|
||||||
|
/// to be kept alive for the function to work correctly.
|
||||||
pub struct JITFunc<Ctx: JITContext> {
|
pub struct JITFunc<Ctx: JITContext> {
|
||||||
func: F<Ctx>,
|
func: F<Ctx>,
|
||||||
strings: HashSet<String>,
|
strings: HashSet<String>,
|
||||||
@@ -39,10 +68,18 @@ impl<Ctx: JITContext> Deref for JITFunc<Ctx> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The internal compilation context used during code generation.
|
||||||
|
///
|
||||||
|
/// This context holds references to the compiler, the Cranelift function builder,
|
||||||
|
/// and manages resources like stack slots and string literals during compilation.
|
||||||
struct Context<'comp, 'ctx, Ctx: JITContext> {
|
struct Context<'comp, 'ctx, Ctx: JITContext> {
|
||||||
|
/// Reference to the JIT compiler.
|
||||||
pub compiler: &'comp mut JITCompiler<Ctx>,
|
pub compiler: &'comp mut JITCompiler<Ctx>,
|
||||||
|
/// The Cranelift function builder used to generate IR.
|
||||||
pub builder: FunctionBuilder<'ctx>,
|
pub builder: FunctionBuilder<'ctx>,
|
||||||
|
/// Stack slots available for reuse.
|
||||||
free_slots: Vec<StackSlot>,
|
free_slots: Vec<StackSlot>,
|
||||||
|
/// String literals used during compilation.
|
||||||
strings: HashSet<String>,
|
strings: HashSet<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -374,6 +411,7 @@ impl<'comp, 'ctx, Ctx: JITContext> Context<'comp, 'ctx, Ctx> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The main JIT compiler that manages the compilation process.
|
||||||
pub struct JITCompiler<Ctx: JITContext> {
|
pub struct JITCompiler<Ctx: JITContext> {
|
||||||
ctx: codegen::Context,
|
ctx: codegen::Context,
|
||||||
module: JITModule,
|
module: JITModule,
|
||||||
|
|||||||
@@ -1,3 +1,16 @@
|
|||||||
|
//! The Low-level Intermediate Representation (LIR) for nixjit.
|
||||||
|
//!
|
||||||
|
//! This module defines the LIR, which is a more resolved and explicit representation
|
||||||
|
//! than the HIR. The key transformation from HIR to LIR is the resolution of variable
|
||||||
|
//! lookups. In the LIR, variable references are either resolved to a specific expression,
|
||||||
|
//! a function argument, or are left as-is for dynamic lookup in a `with` environment.
|
||||||
|
//!
|
||||||
|
//! Key components:
|
||||||
|
//! - `Lir`: An enum representing all LIR expression types, generated by the `ir!` macro.
|
||||||
|
//! - `Resolve`: A trait for converting HIR nodes into LIR expressions.
|
||||||
|
//! - `ResolveContext`: A trait providing the context for resolution, including scope
|
||||||
|
//! management and dependency tracking.
|
||||||
|
|
||||||
use derive_more::{IsVariant, TryUnwrap, Unwrap};
|
use derive_more::{IsVariant, TryUnwrap, Unwrap};
|
||||||
|
|
||||||
use nixjit_error::{Error, Result};
|
use nixjit_error::{Error, Result};
|
||||||
@@ -6,6 +19,7 @@ use nixjit_ir::*;
|
|||||||
use nixjit_macros::ir;
|
use nixjit_macros::ir;
|
||||||
use nixjit_value::format_symbol;
|
use nixjit_value::format_symbol;
|
||||||
|
|
||||||
|
// The `ir!` macro generates the `Lir` enum and related structs and traits.
|
||||||
ir! {
|
ir! {
|
||||||
Lir,
|
Lir,
|
||||||
|
|
||||||
@@ -30,35 +44,58 @@ ir! {
|
|||||||
ArgRef(ArgIdx),
|
ArgRef(ArgIdx),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
/// Represents the result of a variable lookup within the `ResolveContext`.
|
||||||
pub struct Builtins;
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum LookupResult {
|
pub enum LookupResult {
|
||||||
|
/// The variable was found and resolved to a specific expression.
|
||||||
Expr(ExprId),
|
Expr(ExprId),
|
||||||
|
/// The variable was found and resolved to a function argument.
|
||||||
Arg(ArgIdx),
|
Arg(ArgIdx),
|
||||||
|
/// The variable could not be resolved statically, likely due to a `with` expression.
|
||||||
|
/// The lookup must be performed dynamically at evaluation time.
|
||||||
Unknown,
|
Unknown,
|
||||||
|
/// The variable was not found in any scope.
|
||||||
NotFound,
|
NotFound,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A context for the HIR-to-LIR resolution process.
|
||||||
|
///
|
||||||
|
/// This trait abstracts the environment in which expressions are resolved, managing
|
||||||
|
/// scopes, dependencies, and the resolution of expressions themselves.
|
||||||
pub trait ResolveContext {
|
pub trait ResolveContext {
|
||||||
|
/// Records a dependency of one expression on another.
|
||||||
fn new_dep(&mut self, expr: &ExprId, dep: ExprId);
|
fn new_dep(&mut self, expr: &ExprId, dep: ExprId);
|
||||||
|
|
||||||
|
/// Creates a new function, associating a parameter specification with a body expression.
|
||||||
fn new_func(&mut self, body: &ExprId, param: Param);
|
fn new_func(&mut self, body: &ExprId, param: Param);
|
||||||
|
|
||||||
|
/// Triggers the resolution of a given expression.
|
||||||
fn resolve(&mut self, expr: &ExprId) -> Result<()>;
|
fn resolve(&mut self, expr: &ExprId) -> Result<()>;
|
||||||
|
|
||||||
|
/// Looks up a variable by name in the current scope.
|
||||||
fn lookup(&self, name: &str) -> LookupResult;
|
fn lookup(&self, name: &str) -> LookupResult;
|
||||||
|
|
||||||
|
/// Enters a `with` scope for the duration of a closure's execution.
|
||||||
fn with_with_env<T>(&mut self, f: impl FnOnce(&mut Self) -> T) -> (bool, T);
|
fn with_with_env<T>(&mut self, f: impl FnOnce(&mut Self) -> T) -> (bool, T);
|
||||||
|
|
||||||
|
/// Enters a `let` scope with a given set of bindings for the duration of a closure.
|
||||||
fn with_let_env<'a, T>(
|
fn with_let_env<'a, T>(
|
||||||
&mut self,
|
&mut self,
|
||||||
bindings: impl Iterator<Item = (&'a String, &'a ExprId)>,
|
bindings: impl Iterator<Item = (&'a String, &'a ExprId)>,
|
||||||
f: impl FnOnce(&mut Self) -> T,
|
f: impl FnOnce(&mut Self) -> T,
|
||||||
) -> T;
|
) -> T;
|
||||||
|
|
||||||
|
/// Enters a function parameter scope for the duration of a closure.
|
||||||
fn with_param_env<T>(&mut self, ident: Option<String>, f: impl FnOnce(&mut Self) -> T) -> T;
|
fn with_param_env<T>(&mut self, ident: Option<String>, f: impl FnOnce(&mut Self) -> T) -> T;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A trait for converting (resolving) an HIR node into an LIR expression.
|
||||||
pub trait Resolve<Ctx: ResolveContext> {
|
pub trait Resolve<Ctx: ResolveContext> {
|
||||||
|
/// Performs the resolution.
|
||||||
fn resolve(self, ctx: &mut Ctx) -> Result<Lir>;
|
fn resolve(self, ctx: &mut Ctx) -> Result<Lir>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The main entry point for resolving any HIR expression.
|
||||||
impl<Ctx: ResolveContext> Resolve<Ctx> for hir::Hir {
|
impl<Ctx: ResolveContext> Resolve<Ctx> for hir::Hir {
|
||||||
fn resolve(self, ctx: &mut Ctx) -> Result<Lir> {
|
fn resolve(self, ctx: &mut Ctx) -> Result<Lir> {
|
||||||
use hir::Hir::*;
|
use hir::Hir::*;
|
||||||
@@ -80,14 +117,21 @@ impl<Ctx: ResolveContext> Resolve<Ctx> for hir::Hir {
|
|||||||
Var(x) => x.resolve(ctx),
|
Var(x) => x.resolve(ctx),
|
||||||
Path(x) => x.resolve(ctx),
|
Path(x) => x.resolve(ctx),
|
||||||
Let(x) => x.resolve(ctx),
|
Let(x) => x.resolve(ctx),
|
||||||
|
// The `Arg` in HIR is a placeholder. During resolution, it's replaced by
|
||||||
|
// a reference to the *current* function's argument. We assume index 0
|
||||||
|
// here, as the context manages the actual argument index.
|
||||||
Arg(_) => unsafe { Ok(Lir::ArgRef(ArgIdx::from(0))) },
|
Arg(_) => unsafe { Ok(Lir::ArgRef(ArgIdx::from(0))) },
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Resolves an `AttrSet`. If it's recursive, resolution is more complex (and currently a TODO).
|
||||||
|
/// Otherwise, it resolves all key and value expressions.
|
||||||
impl<Ctx: ResolveContext> Resolve<Ctx> for AttrSet {
|
impl<Ctx: ResolveContext> Resolve<Ctx> for AttrSet {
|
||||||
fn resolve(self, ctx: &mut Ctx) -> Result<Lir> {
|
fn resolve(self, ctx: &mut Ctx) -> Result<Lir> {
|
||||||
if self.rec {
|
if self.rec {
|
||||||
|
// TODO: Implement resolution for recursive attribute sets.
|
||||||
|
// This requires setting up a recursive scope where attributes can refer to each other.
|
||||||
todo!()
|
todo!()
|
||||||
} else {
|
} else {
|
||||||
for (_, v) in self.stcs.iter() {
|
for (_, v) in self.stcs.iter() {
|
||||||
@@ -102,6 +146,7 @@ impl<Ctx: ResolveContext> Resolve<Ctx> for AttrSet {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Resolves a `List` by resolving each of its items.
|
||||||
impl<Ctx: ResolveContext> Resolve<Ctx> for List {
|
impl<Ctx: ResolveContext> Resolve<Ctx> for List {
|
||||||
fn resolve(self, ctx: &mut Ctx) -> Result<Lir> {
|
fn resolve(self, ctx: &mut Ctx) -> Result<Lir> {
|
||||||
for item in self.items.iter() {
|
for item in self.items.iter() {
|
||||||
@@ -111,18 +156,20 @@ impl<Ctx: ResolveContext> Resolve<Ctx> for List {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Resolves a `HasAttr` expression by resolving the LHS and any dynamic attributes in the path.
|
||||||
impl<Ctx: ResolveContext> Resolve<Ctx> for HasAttr {
|
impl<Ctx: ResolveContext> Resolve<Ctx> for HasAttr {
|
||||||
fn resolve(self, ctx: &mut Ctx) -> Result<Lir> {
|
fn resolve(self, ctx: &mut Ctx) -> Result<Lir> {
|
||||||
ctx.resolve(&self.lhs)?;
|
ctx.resolve(&self.lhs)?;
|
||||||
for attr in self.rhs.iter() {
|
for attr in self.rhs.iter() {
|
||||||
if let Attr::Dynamic(expr) = attr {
|
if let Attr::Dynamic(expr) = attr {
|
||||||
ctx.resolve(&expr)?;
|
ctx.resolve(expr)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(self.to_lir())
|
Ok(self.to_lir())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Resolves a `BinOp` by resolving its left and right hand sides.
|
||||||
impl<Ctx: ResolveContext> Resolve<Ctx> for BinOp {
|
impl<Ctx: ResolveContext> Resolve<Ctx> for BinOp {
|
||||||
fn resolve(self, ctx: &mut Ctx) -> Result<Lir> {
|
fn resolve(self, ctx: &mut Ctx) -> Result<Lir> {
|
||||||
ctx.resolve(&self.lhs)?;
|
ctx.resolve(&self.lhs)?;
|
||||||
@@ -131,6 +178,7 @@ impl<Ctx: ResolveContext> Resolve<Ctx> for BinOp {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Resolves a `UnOp` by resolving its right hand side.
|
||||||
impl<Ctx: ResolveContext> Resolve<Ctx> for UnOp {
|
impl<Ctx: ResolveContext> Resolve<Ctx> for UnOp {
|
||||||
fn resolve(self, ctx: &mut Ctx) -> Result<Lir> {
|
fn resolve(self, ctx: &mut Ctx) -> Result<Lir> {
|
||||||
ctx.resolve(&self.rhs)?;
|
ctx.resolve(&self.rhs)?;
|
||||||
@@ -138,12 +186,14 @@ impl<Ctx: ResolveContext> Resolve<Ctx> for UnOp {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Resolves a `Select` by resolving the expression being selected from, any dynamic
|
||||||
|
/// attributes in the path, and the default value if it exists.
|
||||||
impl<Ctx: ResolveContext> Resolve<Ctx> for Select {
|
impl<Ctx: ResolveContext> Resolve<Ctx> for Select {
|
||||||
fn resolve(self, ctx: &mut Ctx) -> Result<Lir> {
|
fn resolve(self, ctx: &mut Ctx) -> Result<Lir> {
|
||||||
ctx.resolve(&self.expr)?;
|
ctx.resolve(&self.expr)?;
|
||||||
for attr in self.attrpath.iter() {
|
for attr in self.attrpath.iter() {
|
||||||
if let Attr::Dynamic(expr) = attr {
|
if let Attr::Dynamic(expr) = attr {
|
||||||
ctx.resolve(&expr)?;
|
ctx.resolve(expr)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if let Some(ref expr) = self.default {
|
if let Some(ref expr) = self.default {
|
||||||
@@ -153,6 +203,7 @@ impl<Ctx: ResolveContext> Resolve<Ctx> for Select {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Resolves an `If` expression by resolving the condition, consequence, and alternative.
|
||||||
impl<Ctx: ResolveContext> Resolve<Ctx> for If {
|
impl<Ctx: ResolveContext> Resolve<Ctx> for If {
|
||||||
fn resolve(self, ctx: &mut Ctx) -> Result<Lir> {
|
fn resolve(self, ctx: &mut Ctx) -> Result<Lir> {
|
||||||
ctx.resolve(&self.cond)?;
|
ctx.resolve(&self.cond)?;
|
||||||
@@ -162,6 +213,8 @@ impl<Ctx: ResolveContext> Resolve<Ctx> for If {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Resolves a `Func` by resolving its body within a new parameter scope.
|
||||||
|
/// It then registers the function with the context.
|
||||||
impl<Ctx: ResolveContext> Resolve<Ctx> for Func {
|
impl<Ctx: ResolveContext> Resolve<Ctx> for Func {
|
||||||
fn resolve(self, ctx: &mut Ctx) -> Result<Lir> {
|
fn resolve(self, ctx: &mut Ctx) -> Result<Lir> {
|
||||||
ctx.with_param_env(self.param.ident.clone(), |ctx| ctx.resolve(&self.body))?;
|
ctx.with_param_env(self.param.ident.clone(), |ctx| ctx.resolve(&self.body))?;
|
||||||
@@ -170,6 +223,7 @@ impl<Ctx: ResolveContext> Resolve<Ctx> for Func {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Resolves a `Call` by resolving the function and all of its arguments.
|
||||||
impl<Ctx: ResolveContext> Resolve<Ctx> for Call {
|
impl<Ctx: ResolveContext> Resolve<Ctx> for Call {
|
||||||
fn resolve(self, ctx: &mut Ctx) -> Result<Lir> {
|
fn resolve(self, ctx: &mut Ctx) -> Result<Lir> {
|
||||||
ctx.resolve(&self.func)?;
|
ctx.resolve(&self.func)?;
|
||||||
@@ -180,11 +234,15 @@ impl<Ctx: ResolveContext> Resolve<Ctx> for Call {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Resolves a `With` expression by resolving the namespace and the body.
|
||||||
|
/// The body is resolved within a special "with" scope.
|
||||||
impl<Ctx: ResolveContext> Resolve<Ctx> for With {
|
impl<Ctx: ResolveContext> Resolve<Ctx> for With {
|
||||||
fn resolve(self, ctx: &mut Ctx) -> Result<Lir> {
|
fn resolve(self, ctx: &mut Ctx) -> Result<Lir> {
|
||||||
ctx.resolve(&self.namespace)?;
|
ctx.resolve(&self.namespace)?;
|
||||||
let (env_used, res) = ctx.with_with_env(|ctx| ctx.resolve(&self.expr));
|
let (env_used, res) = ctx.with_with_env(|ctx| ctx.resolve(&self.expr));
|
||||||
res?;
|
res?;
|
||||||
|
// Optimization: if the `with` environment was not actually used by any variable
|
||||||
|
// lookup in the body, we can elide the `With` node entirely.
|
||||||
if env_used {
|
if env_used {
|
||||||
Ok(self.to_lir())
|
Ok(self.to_lir())
|
||||||
} else {
|
} else {
|
||||||
@@ -193,6 +251,7 @@ impl<Ctx: ResolveContext> Resolve<Ctx> for With {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Resolves an `Assert` by resolving the assertion condition and the body.
|
||||||
impl<Ctx: ResolveContext> Resolve<Ctx> for Assert {
|
impl<Ctx: ResolveContext> Resolve<Ctx> for Assert {
|
||||||
fn resolve(self, ctx: &mut Ctx) -> Result<Lir> {
|
fn resolve(self, ctx: &mut Ctx) -> Result<Lir> {
|
||||||
ctx.resolve(&self.assertion)?;
|
ctx.resolve(&self.assertion)?;
|
||||||
@@ -201,6 +260,7 @@ impl<Ctx: ResolveContext> Resolve<Ctx> for Assert {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Resolves a `ConcatStrings` by resolving each part.
|
||||||
impl<Ctx: ResolveContext> Resolve<Ctx> for ConcatStrings {
|
impl<Ctx: ResolveContext> Resolve<Ctx> for ConcatStrings {
|
||||||
fn resolve(self, ctx: &mut Ctx) -> Result<Lir> {
|
fn resolve(self, ctx: &mut Ctx) -> Result<Lir> {
|
||||||
for part in self.parts.iter() {
|
for part in self.parts.iter() {
|
||||||
@@ -210,6 +270,7 @@ impl<Ctx: ResolveContext> Resolve<Ctx> for ConcatStrings {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Resolves a `Var` by looking it up in the current context.
|
||||||
impl<Ctx: ResolveContext> Resolve<Ctx> for Var {
|
impl<Ctx: ResolveContext> Resolve<Ctx> for Var {
|
||||||
fn resolve(self, ctx: &mut Ctx) -> Result<Lir> {
|
fn resolve(self, ctx: &mut Ctx) -> Result<Lir> {
|
||||||
use LookupResult::*;
|
use LookupResult::*;
|
||||||
@@ -225,6 +286,7 @@ impl<Ctx: ResolveContext> Resolve<Ctx> for Var {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Resolves a `Path` by resolving the underlying expression that defines the path's content.
|
||||||
impl<Ctx: ResolveContext> Resolve<Ctx> for Path {
|
impl<Ctx: ResolveContext> Resolve<Ctx> for Path {
|
||||||
fn resolve(self, ctx: &mut Ctx) -> Result<Lir> {
|
fn resolve(self, ctx: &mut Ctx) -> Result<Lir> {
|
||||||
ctx.resolve(&self.expr)?;
|
ctx.resolve(&self.expr)?;
|
||||||
@@ -232,6 +294,8 @@ impl<Ctx: ResolveContext> Resolve<Ctx> for Path {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Resolves a `Let` expression by creating a new scope for the bindings, resolving
|
||||||
|
/// the bindings and the body, and then returning a reference to the body.
|
||||||
impl<Ctx: ResolveContext> Resolve<Ctx> for hir::Let {
|
impl<Ctx: ResolveContext> Resolve<Ctx> for hir::Let {
|
||||||
fn resolve(self, ctx: &mut Ctx) -> Result<Lir> {
|
fn resolve(self, ctx: &mut Ctx) -> Result<Lir> {
|
||||||
ctx.with_let_env(self.bindings.iter(), |ctx| {
|
ctx.with_let_env(self.bindings.iter(), |ctx| {
|
||||||
@@ -240,6 +304,7 @@ impl<Ctx: ResolveContext> Resolve<Ctx> for hir::Let {
|
|||||||
}
|
}
|
||||||
ctx.resolve(&self.body)
|
ctx.resolve(&self.body)
|
||||||
})?;
|
})?;
|
||||||
|
// The `let` expression itself evaluates to its body.
|
||||||
Ok(Lir::ExprRef(self.body))
|
Ok(Lir::ExprRef(self.body))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,25 @@
|
|||||||
|
//! Implements the `#[builtins]` procedural macro attribute.
|
||||||
|
//!
|
||||||
|
//! This macro simplifies the process of defining built-in functions (primops)
|
||||||
|
//! for the Nix interpreter. It inspects the functions inside a `mod` block
|
||||||
|
//! and generates the necessary boilerplate to make them callable from Nix code.
|
||||||
|
//!
|
||||||
|
//! Specifically, it generates:
|
||||||
|
//! 1. A `Builtins` struct containing arrays of constant values and function pointers.
|
||||||
|
//! 2. A wrapper function for each user-defined function. This wrapper handles:
|
||||||
|
//! - Arity (argument count) checking.
|
||||||
|
//! - Type conversion from the generic `nixjit_eval::Value` into the
|
||||||
|
//! specific types expected by the user's function.
|
||||||
|
//! - Calling the user's function with the converted arguments.
|
||||||
|
//! - Wrapping the return value back into a `Result<nixjit_eval::Value>`.
|
||||||
|
|
||||||
use convert_case::{Case, Casing};
|
use convert_case::{Case, Casing};
|
||||||
use proc_macro::TokenStream;
|
use proc_macro::TokenStream;
|
||||||
use proc_macro2::Span;
|
use proc_macro2::Span;
|
||||||
use quote::{ToTokens, format_ident, quote};
|
use quote::{ToTokens, format_ident, quote};
|
||||||
use syn::{
|
use syn::{FnArg, Item, ItemFn, ItemMod, Pat, PatType, Type, Visibility, parse_macro_input};
|
||||||
FnArg, Item, ItemFn, ItemMod, Pat, PatType, Type, Visibility, parse_macro_input,
|
|
||||||
};
|
|
||||||
|
|
||||||
|
/// The implementation of the `#[builtins]` macro.
|
||||||
pub fn builtins_impl(input: TokenStream) -> TokenStream {
|
pub fn builtins_impl(input: TokenStream) -> TokenStream {
|
||||||
let item_mod = parse_macro_input!(input as ItemMod);
|
let item_mod = parse_macro_input!(input as ItemMod);
|
||||||
let mod_name = &item_mod.ident;
|
let mod_name = &item_mod.ident;
|
||||||
@@ -29,9 +43,11 @@ pub fn builtins_impl(input: TokenStream) -> TokenStream {
|
|||||||
let mut scoped = Vec::new();
|
let mut scoped = Vec::new();
|
||||||
let mut wrappers = Vec::new();
|
let mut wrappers = Vec::new();
|
||||||
|
|
||||||
|
// Iterate over the items (functions, consts) in the user's module.
|
||||||
for item in &items {
|
for item in &items {
|
||||||
match item {
|
match item {
|
||||||
Item::Const(item_const) => {
|
Item::Const(item_const) => {
|
||||||
|
// Handle `const` definitions. These are exposed as constants in Nix.
|
||||||
let name_str = item_const
|
let name_str = item_const
|
||||||
.ident
|
.ident
|
||||||
.to_string()
|
.to_string()
|
||||||
@@ -47,10 +63,12 @@ pub fn builtins_impl(input: TokenStream) -> TokenStream {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
Item::Fn(item_fn) => {
|
Item::Fn(item_fn) => {
|
||||||
|
// Handle function definitions. These become primops.
|
||||||
let (primop, wrapper) = match generate_primop_wrapper(item_fn) {
|
let (primop, wrapper) = match generate_primop_wrapper(item_fn) {
|
||||||
Ok(result) => result,
|
Ok(result) => result,
|
||||||
Err(e) => return e.to_compile_error().into(),
|
Err(e) => return e.to_compile_error().into(),
|
||||||
};
|
};
|
||||||
|
// Public functions are added to the global scope, private ones to a scoped set.
|
||||||
if matches!(item_fn.vis, Visibility::Public(_)) {
|
if matches!(item_fn.vis, Visibility::Public(_)) {
|
||||||
global.push(primop);
|
global.push(primop);
|
||||||
pub_item_mod.push(quote! { #item_fn }.into());
|
pub_item_mod.push(quote! { #item_fn }.into());
|
||||||
@@ -65,6 +83,7 @@ pub fn builtins_impl(input: TokenStream) -> TokenStream {
|
|||||||
}
|
}
|
||||||
wrappers.push(wrapper);
|
wrappers.push(wrapper);
|
||||||
}
|
}
|
||||||
|
// Other items are passed through unchanged.
|
||||||
item => pub_item_mod.push(item.to_token_stream()),
|
item => pub_item_mod.push(item.to_token_stream()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -72,7 +91,10 @@ pub fn builtins_impl(input: TokenStream) -> TokenStream {
|
|||||||
let consts_len = consts.len();
|
let consts_len = consts.len();
|
||||||
let global_len = global.len();
|
let global_len = global.len();
|
||||||
let scoped_len = scoped.len();
|
let scoped_len = scoped.len();
|
||||||
|
|
||||||
|
// Assemble the final generated code.
|
||||||
let output = quote! {
|
let output = quote! {
|
||||||
|
// Re-create the user's module, now with generated wrappers.
|
||||||
#visibility mod #mod_name {
|
#visibility mod #mod_name {
|
||||||
#(#pub_item_mod)*
|
#(#pub_item_mod)*
|
||||||
#(#wrappers)*
|
#(#wrappers)*
|
||||||
@@ -81,13 +103,18 @@ pub fn builtins_impl(input: TokenStream) -> TokenStream {
|
|||||||
pub const SCOPED_LEN: usize = #scoped_len;
|
pub const SCOPED_LEN: usize = #scoped_len;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A struct containing all the built-in constants and functions.
|
||||||
pub struct Builtins<Ctx: BuiltinsContext> {
|
pub struct Builtins<Ctx: BuiltinsContext> {
|
||||||
|
/// Constant values available in the global scope.
|
||||||
pub consts: [(&'static str, ::nixjit_value::Const); #mod_name::CONSTS_LEN],
|
pub consts: [(&'static str, ::nixjit_value::Const); #mod_name::CONSTS_LEN],
|
||||||
|
/// Global functions available in the global scope.
|
||||||
pub global: [(&'static str, usize, fn(&mut Ctx, Vec<::nixjit_eval::Value>) -> ::nixjit_error::Result<::nixjit_eval::Value>); #mod_name::GLOBAL_LEN],
|
pub global: [(&'static str, usize, fn(&mut Ctx, Vec<::nixjit_eval::Value>) -> ::nixjit_error::Result<::nixjit_eval::Value>); #mod_name::GLOBAL_LEN],
|
||||||
|
/// Scoped functions, typically available under the `builtins` attribute set.
|
||||||
pub scoped: [(&'static str, usize, fn(&mut Ctx, Vec<::nixjit_eval::Value>) -> ::nixjit_error::Result<::nixjit_eval::Value>); #mod_name::SCOPED_LEN],
|
pub scoped: [(&'static str, usize, fn(&mut Ctx, Vec<::nixjit_eval::Value>) -> ::nixjit_error::Result<::nixjit_eval::Value>); #mod_name::SCOPED_LEN],
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Ctx: BuiltinsContext> Builtins<Ctx> {
|
impl<Ctx: BuiltinsContext> Builtins<Ctx> {
|
||||||
|
/// Creates a new instance of the `Builtins` struct.
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
consts: [#(#consts,)*],
|
consts: [#(#consts,)*],
|
||||||
@@ -101,6 +128,7 @@ pub fn builtins_impl(input: TokenStream) -> TokenStream {
|
|||||||
output.into()
|
output.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Generates the primop metadata and the wrapper function for a single user-defined function.
|
||||||
fn generate_primop_wrapper(
|
fn generate_primop_wrapper(
|
||||||
item_fn: &ItemFn,
|
item_fn: &ItemFn,
|
||||||
) -> syn::Result<(proc_macro2::TokenStream, proc_macro2::TokenStream)> {
|
) -> syn::Result<(proc_macro2::TokenStream, proc_macro2::TokenStream)> {
|
||||||
@@ -114,9 +142,10 @@ fn generate_primop_wrapper(
|
|||||||
|
|
||||||
let mut user_args = item_fn.sig.inputs.iter().peekable();
|
let mut user_args = item_fn.sig.inputs.iter().peekable();
|
||||||
|
|
||||||
|
// Check if the first argument is a context `&mut Ctx`.
|
||||||
let has_ctx = if let Some(FnArg::Typed(first_arg)) = user_args.peek() {
|
let has_ctx = if let Some(FnArg::Typed(first_arg)) = user_args.peek() {
|
||||||
if let Type::Reference(_) = *first_arg.ty {
|
if let Type::Reference(_) = *first_arg.ty {
|
||||||
user_args.next();
|
user_args.next(); // Consume the context argument
|
||||||
true
|
true
|
||||||
} else {
|
} else {
|
||||||
false
|
false
|
||||||
@@ -128,14 +157,18 @@ fn generate_primop_wrapper(
|
|||||||
));
|
));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Collect the remaining arguments.
|
||||||
let arg_pats: Vec<_> = user_args.rev().collect();
|
let arg_pats: Vec<_> = user_args.rev().collect();
|
||||||
let arg_count = arg_pats.len();
|
let arg_count = arg_pats.len();
|
||||||
|
|
||||||
|
// Generate code to unpack and convert arguments from the `Vec<Value>`.
|
||||||
let arg_unpacks = arg_pats.iter().enumerate().map(|(i, arg)| {
|
let arg_unpacks = arg_pats.iter().enumerate().map(|(i, arg)| {
|
||||||
let arg_name = match &arg {
|
let arg_name = match &arg {
|
||||||
FnArg::Typed(PatType { pat, .. }) => {
|
FnArg::Typed(PatType { pat, .. }) => {
|
||||||
if let Pat::Ident(pat_ident) = &**pat {
|
if let Pat::Ident(pat_ident) = &**pat {
|
||||||
pat_ident.ident.clone()
|
pat_ident.ident.clone()
|
||||||
} else {
|
} else {
|
||||||
|
// Create a placeholder name if the pattern is not a simple ident.
|
||||||
format_ident!("arg{}", i, span = Span::call_site())
|
format_ident!("arg{}", i, span = Span::call_site())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -152,6 +185,7 @@ fn generate_primop_wrapper(
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Get the names of the arguments to pass to the user's function.
|
||||||
let arg_names: Vec<_> = arg_pats
|
let arg_names: Vec<_> = arg_pats
|
||||||
.iter()
|
.iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
@@ -168,11 +202,13 @@ fn generate_primop_wrapper(
|
|||||||
.rev()
|
.rev()
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
|
// Construct the argument list for the final call.
|
||||||
let mut call_args = quote! { #(#arg_names),* };
|
let mut call_args = quote! { #(#arg_names),* };
|
||||||
if has_ctx {
|
if has_ctx {
|
||||||
call_args = quote! { ctx, #(#arg_names),* };
|
call_args = quote! { ctx, #(#arg_names),* };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if the user's function already returns a `Result`.
|
||||||
let returns_result = match &item_fn.sig.output {
|
let returns_result = match &item_fn.sig.output {
|
||||||
syn::ReturnType::Type(_, ty) => {
|
syn::ReturnType::Type(_, ty) => {
|
||||||
if let Type::Path(type_path) = &**ty {
|
if let Type::Path(type_path) = &**ty {
|
||||||
@@ -184,6 +220,7 @@ fn generate_primop_wrapper(
|
|||||||
_ => false,
|
_ => false,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Wrap the call expression in `Ok(...)` if it doesn't return a `Result`.
|
||||||
let call_expr = if returns_result {
|
let call_expr = if returns_result {
|
||||||
quote! { #fn_name(#call_args) }
|
quote! { #fn_name(#call_args) }
|
||||||
} else {
|
} else {
|
||||||
@@ -192,9 +229,11 @@ fn generate_primop_wrapper(
|
|||||||
|
|
||||||
let arity = arg_names.len();
|
let arity = arg_names.len();
|
||||||
let fn_type = quote! { fn(&mut Ctx, Vec<::nixjit_eval::Value>) -> ::nixjit_error::Result<::nixjit_eval::Value> };
|
let fn_type = quote! { fn(&mut Ctx, Vec<::nixjit_eval::Value>) -> ::nixjit_error::Result<::nixjit_eval::Value> };
|
||||||
let primop =
|
|
||||||
quote! { (#name_str, #arity, #mod_name::#wrapper_name as #fn_type) };
|
|
||||||
|
|
||||||
|
// The primop metadata tuple: (name, arity, wrapper_function_pointer)
|
||||||
|
let primop = quote! { (#name_str, #arity, #mod_name::#wrapper_name as #fn_type) };
|
||||||
|
|
||||||
|
// The generated wrapper function.
|
||||||
let wrapper = quote! {
|
let wrapper = quote! {
|
||||||
pub fn #wrapper_name<Ctx: BuiltinsContext>(ctx: &mut Ctx, mut args: Vec<::nixjit_eval::Value>) -> ::nixjit_error::Result<::nixjit_eval::Value> {
|
pub fn #wrapper_name<Ctx: BuiltinsContext>(ctx: &mut Ctx, mut args: Vec<::nixjit_eval::Value>) -> ::nixjit_error::Result<::nixjit_eval::Value> {
|
||||||
if args.len() != #arg_count {
|
if args.len() != #arg_count {
|
||||||
|
|||||||
@@ -1,3 +1,13 @@
|
|||||||
|
//! Implements the `ir!` procedural macro.
|
||||||
|
//!
|
||||||
|
//! This macro is designed to reduce the boilerplate associated with defining
|
||||||
|
//! an Intermediate Representation (IR) that follows a specific pattern. It generates:
|
||||||
|
//! 1. An enum representing the different kinds of IR nodes (e.g., `Hir`, `Lir`).
|
||||||
|
//! 2. Structs for each of the variants that have fields.
|
||||||
|
//! 3. `Ref` and `Mut` versions of the main enum for ergonomic pattern matching on references.
|
||||||
|
//! 4. `From` implementations to easily convert from a struct variant (e.g., `BinOp`) to the main enum (`Hir::BinOp`).
|
||||||
|
//! 5. A `To[IrName]` trait to provide a convenient `.to_hir()` or `.to_lir()` method on the variant structs.
|
||||||
|
|
||||||
use convert_case::{Case, Casing};
|
use convert_case::{Case, Casing};
|
||||||
use proc_macro::TokenStream;
|
use proc_macro::TokenStream;
|
||||||
use quote::{format_ident, quote};
|
use quote::{format_ident, quote};
|
||||||
@@ -8,14 +18,21 @@ use syn::{
|
|||||||
token,
|
token,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// Represents one of the variants passed to the `ir!` macro.
|
||||||
pub enum VariantInput {
|
pub enum VariantInput {
|
||||||
|
/// A unit-like variant, e.g., `Arg`.
|
||||||
Unit(Ident),
|
Unit(Ident),
|
||||||
|
/// A tuple-like variant with one unnamed field, e.g., `ExprRef(ExprId)`.
|
||||||
Tuple(Ident, Type),
|
Tuple(Ident, Type),
|
||||||
|
/// A struct-like variant with named fields, e.g., `BinOp { lhs: ExprId, rhs: ExprId, kind: BinOpKind }`.
|
||||||
Struct(Ident, FieldsNamed),
|
Struct(Ident, FieldsNamed),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The top-level input for the `ir!` macro.
|
||||||
pub struct MacroInput {
|
pub struct MacroInput {
|
||||||
|
/// The name of the main IR enum to be generated (e.g., `Hir`).
|
||||||
pub base_name: Ident,
|
pub base_name: Ident,
|
||||||
|
/// The list of variants for the enum.
|
||||||
pub variants: Punctuated<VariantInput, Token![,]>,
|
pub variants: Punctuated<VariantInput, Token![,]>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -24,6 +41,7 @@ impl Parse for VariantInput {
|
|||||||
let name: Ident = input.parse()?;
|
let name: Ident = input.parse()?;
|
||||||
|
|
||||||
if input.peek(token::Paren) {
|
if input.peek(token::Paren) {
|
||||||
|
// Parse a tuple-like variant: `Variant(Type)`
|
||||||
let content;
|
let content;
|
||||||
parenthesized!(content in input);
|
parenthesized!(content in input);
|
||||||
let ty: Type = content.parse()?;
|
let ty: Type = content.parse()?;
|
||||||
@@ -34,10 +52,11 @@ impl Parse for VariantInput {
|
|||||||
|
|
||||||
Ok(VariantInput::Tuple(name, ty))
|
Ok(VariantInput::Tuple(name, ty))
|
||||||
} else if input.peek(token::Brace) {
|
} else if input.peek(token::Brace) {
|
||||||
|
// Parse a struct-like variant: `Variant { field: Type, ... }`
|
||||||
let fields: FieldsNamed = input.parse()?;
|
let fields: FieldsNamed = input.parse()?;
|
||||||
|
|
||||||
Ok(VariantInput::Struct(name, fields))
|
Ok(VariantInput::Struct(name, fields))
|
||||||
} else {
|
} else {
|
||||||
|
// Parse a unit-like variant: `Variant`
|
||||||
Ok(VariantInput::Unit(name))
|
Ok(VariantInput::Unit(name))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -45,6 +64,7 @@ impl Parse for VariantInput {
|
|||||||
|
|
||||||
impl Parse for MacroInput {
|
impl Parse for MacroInput {
|
||||||
fn parse(input: ParseStream) -> Result<Self> {
|
fn parse(input: ParseStream) -> Result<Self> {
|
||||||
|
// The macro input is expected to be: `IrName, Variant1, Variant2, ...`
|
||||||
let base_name = input.parse()?;
|
let base_name = input.parse()?;
|
||||||
input.parse::<Token![,]>()?;
|
input.parse::<Token![,]>()?;
|
||||||
let variants = Punctuated::parse_terminated(input)?;
|
let variants = Punctuated::parse_terminated(input)?;
|
||||||
@@ -56,6 +76,7 @@ impl Parse for MacroInput {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The implementation of the `ir!` macro.
|
||||||
pub fn ir_impl(input: TokenStream) -> TokenStream {
|
pub fn ir_impl(input: TokenStream) -> TokenStream {
|
||||||
let parsed_input = syn::parse_macro_input!(input as MacroInput);
|
let parsed_input = syn::parse_macro_input!(input as MacroInput);
|
||||||
|
|
||||||
@@ -126,31 +147,38 @@ pub fn ir_impl(input: TokenStream) -> TokenStream {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Assemble the final generated code.
|
||||||
let expanded = quote! {
|
let expanded = quote! {
|
||||||
|
/// The main IR enum, generated by the `ir!` macro.
|
||||||
#[derive(Debug, IsVariant, Unwrap, TryUnwrap)]
|
#[derive(Debug, IsVariant, Unwrap, TryUnwrap)]
|
||||||
pub enum #base_name {
|
pub enum #base_name {
|
||||||
#( #enum_variants ),*
|
#( #enum_variants ),*
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The struct definitions for the enum variants.
|
||||||
#( #struct_defs )*
|
#( #struct_defs )*
|
||||||
|
|
||||||
|
/// An immutable reference version of the IR enum.
|
||||||
#[derive(Debug, IsVariant, Unwrap, TryUnwrap)]
|
#[derive(Debug, IsVariant, Unwrap, TryUnwrap)]
|
||||||
pub enum #ref_name<'a> {
|
pub enum #ref_name<'a> {
|
||||||
#( #ref_variants ),*
|
#( #ref_variants ),*
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A mutable reference version of the IR enum.
|
||||||
#[derive(Debug, IsVariant, Unwrap, TryUnwrap)]
|
#[derive(Debug, IsVariant, Unwrap, TryUnwrap)]
|
||||||
pub enum #mut_name<'a> {
|
pub enum #mut_name<'a> {
|
||||||
#( #mut_variants ),*
|
#( #mut_variants ),*
|
||||||
}
|
}
|
||||||
|
|
||||||
impl #base_name {
|
impl #base_name {
|
||||||
|
/// Converts a `&Ir` into a `IrRef`.
|
||||||
pub fn as_ref(&self) -> #ref_name<'_> {
|
pub fn as_ref(&self) -> #ref_name<'_> {
|
||||||
match self {
|
match self {
|
||||||
#( #as_ref_arms ),*
|
#( #as_ref_arms ),*
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Converts a `&mut Ir` into a `IrMut`.
|
||||||
pub fn as_mut(&mut self) -> #mut_name<'_> {
|
pub fn as_mut(&mut self) -> #mut_name<'_> {
|
||||||
match self {
|
match self {
|
||||||
#( #as_mut_arms ),*
|
#( #as_mut_arms ),*
|
||||||
@@ -158,12 +186,16 @@ pub fn ir_impl(input: TokenStream) -> TokenStream {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// `From` implementations for converting variant structs into the main enum.
|
||||||
#( #from_impls )*
|
#( #from_impls )*
|
||||||
|
|
||||||
|
/// A trait for converting a variant struct into the main IR enum.
|
||||||
pub trait #to_trait_name {
|
pub trait #to_trait_name {
|
||||||
|
/// Performs the conversion.
|
||||||
fn #to_trait_fn_name(self) -> #base_name;
|
fn #to_trait_fn_name(self) -> #base_name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Implement the `ToIr` trait for each variant struct.
|
||||||
#( #to_trait_impls )*
|
#( #to_trait_impls )*
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,13 +1,22 @@
|
|||||||
|
//! This crate provides procedural macros for the nixjit project.
|
||||||
use proc_macro::TokenStream;
|
use proc_macro::TokenStream;
|
||||||
|
|
||||||
mod builtins;
|
mod builtins;
|
||||||
mod ir;
|
mod ir;
|
||||||
|
|
||||||
|
/// A procedural macro to reduce boilerplate when defining an Intermediate Representation (IR).
|
||||||
|
///
|
||||||
|
/// It generates an enum for the IR, along with `Ref` and `Mut` variants,
|
||||||
|
/// `From` implementations, and a `ToHir` or `ToLir` trait.
|
||||||
#[proc_macro]
|
#[proc_macro]
|
||||||
pub fn ir(input: TokenStream) -> TokenStream {
|
pub fn ir(input: TokenStream) -> TokenStream {
|
||||||
ir::ir_impl(input)
|
ir::ir_impl(input)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A procedural macro attribute to simplify the definition of built-in functions.
|
||||||
|
///
|
||||||
|
/// It generates the necessary boilerplate to wrap functions and expose them
|
||||||
|
/// to the evaluation engine, handling argument type conversions and arity checking.
|
||||||
#[proc_macro_attribute]
|
#[proc_macro_attribute]
|
||||||
pub fn builtins(_attr: TokenStream, input: TokenStream) -> TokenStream {
|
pub fn builtins(_attr: TokenStream, input: TokenStream) -> TokenStream {
|
||||||
builtins::builtins_impl(input)
|
builtins::builtins_impl(input)
|
||||||
|
|||||||
@@ -1,3 +1,9 @@
|
|||||||
|
//! Defines the public-facing data structures for Nix values.
|
||||||
|
//!
|
||||||
|
//! These types are used to represent the final result of an evaluation and are
|
||||||
|
//! designed to be user-friendly and serializable. They are distinct from the
|
||||||
|
//! internal `Value` types used during evaluation in `nixjit_eval`.
|
||||||
|
|
||||||
use core::fmt::{Debug, Display, Formatter, Result as FmtResult};
|
use core::fmt::{Debug, Display, Formatter, Result as FmtResult};
|
||||||
use core::hash::Hash;
|
use core::hash::Hash;
|
||||||
use core::ops::Deref;
|
use core::ops::Deref;
|
||||||
@@ -9,8 +15,11 @@ use std::sync::LazyLock;
|
|||||||
use derive_more::{Constructor, IsVariant, Unwrap};
|
use derive_more::{Constructor, IsVariant, Unwrap};
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
|
|
||||||
|
/// Represents errors thrown by `assert` and `throw` expressions in Nix.
|
||||||
|
/// These errors can potentially be caught and handled by `builtins.tryEval`.
|
||||||
#[derive(Clone, Debug, PartialEq, Constructor, Hash)]
|
#[derive(Clone, Debug, PartialEq, Constructor, Hash)]
|
||||||
pub struct Catchable {
|
pub struct Catchable {
|
||||||
|
/// The error message.
|
||||||
msg: String,
|
msg: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -26,11 +35,16 @@ impl Display for Catchable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Represents a constant, primitive value in Nix.
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, IsVariant, Unwrap)]
|
#[derive(Debug, Clone, Copy, PartialEq, IsVariant, Unwrap)]
|
||||||
pub enum Const {
|
pub enum Const {
|
||||||
|
/// A boolean value (`true` or `false`).
|
||||||
Bool(bool),
|
Bool(bool),
|
||||||
|
/// A 64-bit signed integer.
|
||||||
Int(i64),
|
Int(i64),
|
||||||
|
/// A 64-bit floating-point number.
|
||||||
Float(f64),
|
Float(f64),
|
||||||
|
/// The `null` value.
|
||||||
Null,
|
Null,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -64,6 +78,7 @@ impl From<f64> for Const {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Represents a Nix symbol, which is used as a key in attribute sets.
|
||||||
#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Constructor)]
|
#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Constructor)]
|
||||||
pub struct Symbol(String);
|
pub struct Symbol(String);
|
||||||
|
|
||||||
@@ -73,9 +88,11 @@ impl<T: Into<String>> From<T> for Symbol {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn format_symbol<'a>(sym: &'a str) -> Cow<'a, str> {
|
/// Formats a string slice as a Nix symbol, quoting it if necessary.
|
||||||
if REGEX.is_match(sym) {
|
pub fn format_symbol<'a>(sym: impl Into<Cow<'a, str>>) -> Cow<'a, str> {
|
||||||
Cow::Borrowed(sym)
|
let sym = sym.into();
|
||||||
|
if REGEX.is_match(&sym) {
|
||||||
|
sym
|
||||||
} else {
|
} else {
|
||||||
Cow::Owned(format!(r#""{sym}""#))
|
Cow::Owned(format!(r#""{sym}""#))
|
||||||
}
|
}
|
||||||
@@ -92,8 +109,9 @@ impl Display for Symbol {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static REGEX: LazyLock<Regex> =
|
static REGEX: LazyLock<Regex> =
|
||||||
LazyLock::new(|| Regex::new(r#"^[a-zA-Z\_][a-zA-Z0-9\_\'\-]*$"#).unwrap());
|
LazyLock::new(|| Regex::new(r"^[a-zA-Z_][a-zA-Z0-9_'-]*$").unwrap());
|
||||||
impl Symbol {
|
impl Symbol {
|
||||||
|
/// Checks if the symbol is a "normal" identifier that doesn't require quotes.
|
||||||
fn normal(&self) -> bool {
|
fn normal(&self) -> bool {
|
||||||
REGEX.is_match(self)
|
REGEX.is_match(self)
|
||||||
}
|
}
|
||||||
@@ -107,15 +125,18 @@ impl Deref for Symbol {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Symbol {
|
impl Symbol {
|
||||||
|
/// Consumes the `Symbol`, returning its inner `String`.
|
||||||
pub fn into_inner(self) -> String {
|
pub fn into_inner(self) -> String {
|
||||||
self.0
|
self.0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns a reference to the inner `String`.
|
||||||
pub fn as_inner(&self) -> &String {
|
pub fn as_inner(&self) -> &String {
|
||||||
&self.0
|
&self.0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Represents a Nix attribute set, which is a map from symbols to values.
|
||||||
#[derive(Constructor, Clone, PartialEq)]
|
#[derive(Constructor, Clone, PartialEq)]
|
||||||
pub struct AttrSet {
|
pub struct AttrSet {
|
||||||
data: BTreeMap<Symbol, Value>,
|
data: BTreeMap<Symbol, Value>,
|
||||||
@@ -126,10 +147,11 @@ impl Debug for AttrSet {
|
|||||||
use Value::*;
|
use Value::*;
|
||||||
write!(f, "{{")?;
|
write!(f, "{{")?;
|
||||||
for (k, v) in self.data.iter() {
|
for (k, v) in self.data.iter() {
|
||||||
|
write!(f, " {k:?} = ")?;
|
||||||
match v {
|
match v {
|
||||||
List(_) => write!(f, "{k:?} = [ ... ]; ")?,
|
List(_) => write!(f, "[ ... ];")?,
|
||||||
AttrSet(_) => write!(f, "{k:?} = {{ ... }}; ")?,
|
AttrSet(_) => write!(f, "{{ ... }};")?,
|
||||||
v => write!(f, "{k:?} = {v:?}; ")?,
|
v => write!(f, "{v:?};")?,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
write!(f, " }}")
|
write!(f, " }}")
|
||||||
@@ -140,19 +162,24 @@ impl Display for AttrSet {
|
|||||||
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
|
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
|
||||||
use Value::*;
|
use Value::*;
|
||||||
write!(f, "{{ ")?;
|
write!(f, "{{ ")?;
|
||||||
|
let mut first = true;
|
||||||
for (k, v) in self.data.iter() {
|
for (k, v) in self.data.iter() {
|
||||||
|
if !first {
|
||||||
|
write!(f, "; ")?;
|
||||||
|
}
|
||||||
write!(f, "{k} = ")?;
|
write!(f, "{k} = ")?;
|
||||||
match v {
|
match v {
|
||||||
AttrSet(_) => write!(f, "{{ ... }}"),
|
AttrSet(_) => write!(f, "{{ ... }}"),
|
||||||
List(_) => write!(f, "[ ... ]"),
|
List(_) => write!(f, "[ ... ]"),
|
||||||
v => write!(f, "{v}"),
|
v => write!(f, "{v}"),
|
||||||
}?;
|
}?;
|
||||||
write!(f, "; ")?;
|
first = false;
|
||||||
}
|
}
|
||||||
write!(f, " }}")
|
write!(f, " }}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Represents a Nix list, which is a vector of values.
|
||||||
#[derive(Constructor, Clone, Debug, PartialEq)]
|
#[derive(Constructor, Clone, Debug, PartialEq)]
|
||||||
pub struct List {
|
pub struct List {
|
||||||
data: Vec<Value>,
|
data: Vec<Value>,
|
||||||
@@ -168,17 +195,29 @@ impl Display for List {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Represents any possible Nix value that can be returned from an evaluation.
|
||||||
#[derive(IsVariant, Unwrap, Clone, Debug, PartialEq)]
|
#[derive(IsVariant, Unwrap, Clone, Debug, PartialEq)]
|
||||||
pub enum Value {
|
pub enum Value {
|
||||||
|
/// A constant value (int, float, bool, null).
|
||||||
Const(Const),
|
Const(Const),
|
||||||
|
/// A string value.
|
||||||
String(String),
|
String(String),
|
||||||
|
/// An attribute set.
|
||||||
AttrSet(AttrSet),
|
AttrSet(AttrSet),
|
||||||
|
/// A list.
|
||||||
List(List),
|
List(List),
|
||||||
|
/// A catchable error.
|
||||||
Catchable(Catchable),
|
Catchable(Catchable),
|
||||||
|
/// A thunk, representing a delayed computation.
|
||||||
Thunk,
|
Thunk,
|
||||||
|
/// A function (lambda).
|
||||||
Func,
|
Func,
|
||||||
|
/// A primitive (built-in) operation.
|
||||||
PrimOp(&'static str),
|
PrimOp(&'static str),
|
||||||
|
/// A partially applied primitive operation.
|
||||||
PrimOpApp(&'static str),
|
PrimOpApp(&'static str),
|
||||||
|
/// A marker for a value that has been seen before during serialization, to break cycles.
|
||||||
|
/// This is used to prevent infinite recursion when printing or serializing cyclic data structures.
|
||||||
Repeated,
|
Repeated,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -187,7 +226,7 @@ impl Display for Value {
|
|||||||
use Value::*;
|
use Value::*;
|
||||||
match self {
|
match self {
|
||||||
Const(x) => write!(f, "{x}"),
|
Const(x) => write!(f, "{x}"),
|
||||||
String(x) => write!(f, "{x}"),
|
String(x) => write!(f, r#""{x}""#),
|
||||||
AttrSet(x) => write!(f, "{x}"),
|
AttrSet(x) => write!(f, "{x}"),
|
||||||
List(x) => write!(f, "{x}"),
|
List(x) => write!(f, "{x}"),
|
||||||
Catchable(x) => write!(f, "{x}"),
|
Catchable(x) => write!(f, "{x}"),
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
in
|
in
|
||||||
{
|
{
|
||||||
devShells = forAllSystems (system:
|
devShells = forAllSystems (system:
|
||||||
let pkgs = import nixpkgs { inherit system; }; in
|
let pkgs = import nixpkgs { inherit system; config.allowUnfree = true; }; in
|
||||||
{
|
{
|
||||||
default = pkgs.mkShell {
|
default = pkgs.mkShell {
|
||||||
packages = with pkgs; [
|
packages = with pkgs; [
|
||||||
@@ -26,6 +26,7 @@
|
|||||||
gdb
|
gdb
|
||||||
valgrind
|
valgrind
|
||||||
gemini-cli
|
gemini-cli
|
||||||
|
claude-code
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user