feat: stack var (WIP)
This commit is contained in:
@@ -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" }
|
||||
|
||||
@@ -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()))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
)));
|
||||
|
||||
@@ -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(),
|
||||
));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user