feat: stack var (WIP)

This commit is contained in:
2025-08-09 08:12:53 +08:00
parent fd182b6233
commit d8ad7fe904
36 changed files with 1521 additions and 1058 deletions

View File

@@ -7,7 +7,6 @@ edition = "2024"
[dependencies]
derive_more = { version = "2.0", features = ["full"] }
hashbrown = "0.15"
itertools = "0.14"
rnix = "0.12"
nixjit_error = { path = "../nixjit_error" }

View File

@@ -10,7 +10,6 @@
use rnix::ast::{self, Expr};
use nixjit_error::{Error, Result};
use nixjit_ir as ir;
use super::*;
@@ -35,7 +34,7 @@ impl<Ctx: DowngradeContext> Downgrade<Ctx> for Expr {
// Dispatch to the specific implementation for each expression type.
Apply(apply) => apply.downgrade(ctx),
Assert(assert) => assert.downgrade(ctx),
Error(error) => Err(self::Error::DowngradeError(error.to_string())),
Error(error) => Err(self::Error::downgrade_error(error.to_string())),
IfElse(ifelse) => ifelse.downgrade(ctx),
Select(select) => select.downgrade(ctx),
Str(str) => str.downgrade(ctx),
@@ -159,19 +158,24 @@ impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::Ident {
impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::AttrSet {
fn downgrade(self, ctx: &mut Ctx) -> Result<ExprId> {
let rec = self.rec_token().is_some();
let mut attrs = downgrade_attrs(self, ctx)?;
attrs.rec = rec;
Ok(ctx.new_expr(attrs.to_hir()))
let attrs = downgrade_attrs(self, ctx)?;
let bindings = attrs.stcs.clone();
let body = ctx.new_expr(attrs.to_hir());
if rec {
Ok(ctx.new_expr(Let { bindings, body }.to_hir()))
} else {
Ok(body)
}
}
}
/// Downgrades a list.
impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::List {
fn downgrade(self, ctx: &mut Ctx) -> Result<ExprId> {
let mut items = Vec::with_capacity(self.items().size_hint().0);
for item in self.items() {
items.push(item.downgrade(ctx)?)
}
let items = self
.items()
.map(|item| maybe_thunk(item, ctx))
.collect::<Result<_>>()?;
Ok(ctx.new_expr(List { items }.to_hir()))
}
}
@@ -229,9 +233,10 @@ impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::Select {
/// The body of the `let` is accessed via `let.body`.
impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::LegacyLet {
fn downgrade(self, ctx: &mut Ctx) -> Result<ExprId> {
let mut attrs = downgrade_attrs(self, ctx)?;
attrs.rec = true;
let expr = ctx.new_expr(attrs.to_hir());
let attrs = downgrade_attrs(self, ctx)?;
let bindings = attrs.stcs.clone();
let body = ctx.new_expr(attrs.to_hir());
let expr = ctx.new_expr(Let { bindings, body }.to_hir());
// The result of a `legacy let` is the `body` attribute of the resulting set.
let attrpath = vec![Attr::Str("body".into())];
Ok(ctx.new_expr(
@@ -303,12 +308,12 @@ impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::Lambda {
// Desugar pattern matching in function arguments into a `let` expression.
// For example, `({ a, b ? 2 }): a + b` is desugared into:
// `arg: let a = arg.a; b = arg.b or 2; in a + b`
let arg = ctx.new_expr(Hir::Arg(Arg));
let mut bindings: HashMap<_, _> = formals
.into_iter()
.map(|(k, default)| {
// For each formal parameter, create a `Select` expression to extract it from the argument set.
// `Arg` represents the raw argument (the attribute set) passed to the function.
let arg = ctx.new_expr(Hir::Arg(Arg));
(
k.clone(),
ctx.new_expr(
@@ -324,10 +329,7 @@ impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::Lambda {
.collect();
// If there's an alias (`... }@alias`), bind the alias name to the raw argument set.
if let Some(alias) = alias {
bindings.insert(
alias.clone(),
ctx.new_expr(Var { sym: alias.clone() }.to_hir()),
);
bindings.insert(alias.clone(), arg);
}
// Wrap the original function body in the new `let` expression.
let let_ = Let { bindings, body };
@@ -346,21 +348,12 @@ impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::Lambda {
}
/// Downgrades a function application.
/// The `rnix` AST represents chained function calls as nested `Apply` nodes,
/// e.g., `f a b` is parsed as `(f a) b`. This implementation unnests these
/// calls into a single `Call` HIR node with a list of arguments.
/// In Nix, function application is left-associative, so `f a b` should be parsed as `((f a) b)`.
/// Each Apply node represents a single function call with one argument.
impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::Apply {
fn downgrade(self, ctx: &mut Ctx) -> Result<ExprId> {
let mut args = vec![self.argument().unwrap().downgrade(ctx)?];
let mut func = self.lambda().unwrap();
// Traverse the chain of nested `Apply` nodes to collect all arguments.
while let ast::Expr::Apply(call) = func {
func = call.lambda().unwrap();
args.push(call.argument().unwrap().downgrade(ctx)?);
}
let func = func.downgrade(ctx)?;
// The arguments were collected in reverse order, so fix that.
args.reverse();
Ok(ctx.new_expr(Call { func, args }.to_hir()))
let func = self.lambda().unwrap().downgrade(ctx)?;
let arg = maybe_thunk(self.argument().unwrap(), ctx)?;
Ok(ctx.new_expr(Call { func, arg }.to_hir()))
}
}

View File

@@ -37,11 +37,10 @@ pub trait DowngradeContext {
/// Allocates a new HIR expression in the context and returns its ID.
fn new_expr(&mut self, expr: Hir) -> ExprId;
/// Provides temporary access to an immutable expression for inspection or use.
fn with_expr<T>(&self, id: ExprId, f: impl FnOnce(&Hir, &Self) -> T) -> T;
/// Provides temporary mutable access to an expression.
fn with_expr_mut<T>(&mut self, id: ExprId, f: impl FnOnce(&mut Hir, &mut Self) -> T) -> T;
fn downgrade_root(self, expr: rnix::ast::Expr) -> Result<ExprId>;
}
// The `ir!` macro generates the `Hir` enum and related structs and traits.
@@ -85,6 +84,7 @@ ir! {
Let { pub bindings: HashMap<String, ExprId>, pub body: ExprId },
// Represents a function argument lookup within the body of a function.
Arg,
Thunk(ExprId)
}
/// A placeholder struct for the `Arg` HIR variant. It signifies that at this point
@@ -135,7 +135,7 @@ impl Attrs for AttrSet {
.try_unwrap_attr_set()
.map_err(|_| {
// This path segment exists but is not an attrset, which is an error.
Error::DowngradeError(format!(
Error::downgrade_error(format!(
"attribute '{}' already defined but is not an attribute set",
format_symbol(ident)
))
@@ -165,7 +165,7 @@ impl Attrs for AttrSet {
match name {
Attr::Str(ident) => {
if self.stcs.insert(ident.clone(), value).is_some() {
return Err(Error::DowngradeError(format!(
return Err(Error::downgrade_error(format!(
"attribute '{}' already defined",
format_symbol(ident)
)));

View File

@@ -12,9 +12,49 @@ use rnix::ast;
use nixjit_error::{Error, Result};
use nixjit_ir::{Attr, AttrSet, ConcatStrings, ExprId, Select, Str, Var};
use crate::Hir;
use super::downgrade::Downgrade;
use super::{Attrs, DowngradeContext, Param, ToHir};
pub fn maybe_thunk(mut expr: ast::Expr, ctx: &mut impl DowngradeContext) -> Result<ExprId> {
use ast::Expr::*;
let expr = loop {
expr = match expr {
Paren(paren) => paren.expr().unwrap(),
Root(root) => root.expr().unwrap(),
expr => break expr,
}
};
match expr {
Error(error) => return Err(self::Error::downgrade_error(error.to_string())),
Ident(ident) => return ident.downgrade(ctx),
Literal(lit) => return lit.downgrade(ctx),
Str(str) => return str.downgrade(ctx),
Path(path) => return path.downgrade(ctx),
_ => (),
}
let id = match expr {
Apply(apply) => apply.downgrade(ctx),
Assert(assert) => assert.downgrade(ctx),
IfElse(ifelse) => ifelse.downgrade(ctx),
Select(select) => select.downgrade(ctx),
Lambda(lambda) => lambda.downgrade(ctx),
LegacyLet(let_) => let_.downgrade(ctx),
LetIn(letin) => letin.downgrade(ctx),
List(list) => list.downgrade(ctx),
BinOp(op) => op.downgrade(ctx),
AttrSet(attrs) => attrs.downgrade(ctx),
UnaryOp(op) => op.downgrade(ctx),
With(with) => with.downgrade(ctx),
HasAttr(has) => has.downgrade(ctx),
_ => unreachable!(),
}?;
Ok(ctx.new_expr(Hir::Thunk(id)))
}
/// Downgrades a function parameter from the AST.
pub fn downgrade_param(param: ast::Param, ctx: &mut impl DowngradeContext) -> Result<Param> {
match param {
@@ -63,7 +103,6 @@ pub fn downgrade_attrs(
let mut attrs = AttrSet {
stcs: HashMap::new(),
dyns: Vec::new(),
rec: false,
};
for entry in entries {
@@ -87,7 +126,6 @@ pub fn downgrade_static_attrs(
let mut attrs = AttrSet {
stcs: HashMap::new(),
dyns: Vec::new(),
rec: false,
};
for entry in entries {
@@ -121,7 +159,7 @@ pub fn downgrade_inherit(
Attr::Str(ident) => ident,
_ => {
// `inherit` does not allow dynamic attributes.
return Err(Error::DowngradeError(
return Err(Error::downgrade_error(
"dynamic attributes not allowed in inherit".to_string(),
));
}
@@ -140,11 +178,13 @@ pub fn downgrade_inherit(
},
);
match stcs.entry(ident) {
Entry::Occupied(occupied) => return Err(Error::EvalError(format!(
"attribute '{}' already defined",
format_symbol(occupied.key())
))),
Entry::Vacant(vacant) => vacant.insert(ctx.new_expr(expr))
Entry::Occupied(occupied) => {
return Err(Error::eval_error(format!(
"attribute '{}' already defined",
format_symbol(occupied.key())
)));
}
Entry::Vacant(vacant) => vacant.insert(ctx.new_expr(expr)),
};
}
Ok(())
@@ -205,7 +245,7 @@ pub fn downgrade_attrpathvalue(
ctx: &mut impl DowngradeContext,
) -> Result<()> {
let path = downgrade_attrpath(value.attrpath().unwrap(), ctx)?;
let value = value.value().unwrap().downgrade(ctx)?;
let value = maybe_thunk(value.value().unwrap(), ctx)?;
attrs.insert(path, value, ctx)
}
@@ -218,7 +258,7 @@ pub fn downgrade_static_attrpathvalue(
) -> Result<()> {
let path = downgrade_attrpath(value.attrpath().unwrap(), ctx)?;
if path.iter().any(|attr| matches!(attr, Attr::Dynamic(_))) {
return Err(Error::DowngradeError(
return Err(Error::downgrade_error(
"dynamic attributes not allowed in let bindings".to_string(),
));
}