This commit is contained in:
2026-02-08 01:19:18 +08:00
parent 6b46e466c2
commit 1346ae5405
8 changed files with 593 additions and 629 deletions

View File

@@ -2,6 +2,7 @@ use std::fmt::{self, Write as _};
use std::path::Path; use std::path::Path;
use crate::ir::*; use crate::ir::*;
use crate::value::Symbol;
pub(crate) struct CodeBuffer { pub(crate) struct CodeBuffer {
buf: String, buf: String,
@@ -83,7 +84,7 @@ fn joined<Ctx: CodegenContext, I: Iterator, F: Fn(&Ctx, &mut CodeBuffer, I::Item
} }
macro_rules! code { macro_rules! code {
($buf:expr, $ctx:expr; $($item:expr),* $(,)?) => {{ ($buf:expr, $ctx:expr; $($item:expr)*) => {{
$( $(
($item).compile($ctx, $buf); ($item).compile($ctx, $buf);
)* )*
@@ -99,8 +100,8 @@ macro_rules! code {
write!($buf, $fmt, $($arg)*).unwrap() write!($buf, $fmt, $($arg)*).unwrap()
}; };
($buf:expr, $lit:literal) => { ($buf:expr, $fmt:literal) => {
$buf.push_str($lit) write!($buf, $fmt).unwrap()
}; };
} }
@@ -206,7 +207,7 @@ impl<Ctx: CodegenContext> Compile<Ctx> for rnix::TextRange {
pub(crate) trait CodegenContext { pub(crate) trait CodegenContext {
fn get_ir(&self, id: ExprId) -> &Ir; fn get_ir(&self, id: ExprId) -> &Ir;
fn get_sym(&self, id: SymId) -> &str; fn get_sym(&self, id: SymId) -> Symbol<'_>;
fn get_current_dir(&self) -> &Path; fn get_current_dir(&self) -> &Path;
fn get_store_dir(&self) -> &str; fn get_store_dir(&self) -> &str;
fn get_current_source_id(&self) -> usize; fn get_current_source_id(&self) -> usize;
@@ -219,6 +220,12 @@ impl<Ctx: CodegenContext> Compile<Ctx> for ExprId {
} }
} }
impl<Ctx: CodegenContext> Compile<Ctx> for Symbol<'_> {
fn compile(&self, ctx: &Ctx, buf: &mut CodeBuffer) {
quoted(self).compile(ctx, buf);
}
}
impl<Ctx: CodegenContext> Compile<Ctx> for Ir { impl<Ctx: CodegenContext> Compile<Ctx> for Ir {
fn compile(&self, ctx: &Ctx, buf: &mut CodeBuffer) { fn compile(&self, ctx: &Ctx, buf: &mut CodeBuffer) {
match self { match self {
@@ -260,8 +267,8 @@ impl<Ctx: CodegenContext> Compile<Ctx> for Ir {
} }
&Ir::Builtin(Builtin { inner: name, .. }) => { &Ir::Builtin(Builtin { inner: name, .. }) => {
code!(buf, ctx; code!(buf, ctx;
"Nix.builtins[", "Nix.builtins["
quoted(ctx.get_sym(name)), ctx.get_sym(name)
"]" "]"
); );
} }
@@ -277,37 +284,37 @@ impl<Ctx: CodegenContext> Compile<Ctx> for Ir {
let assertion_span = assertion_ir.span(); let assertion_span = assertion_ir.span();
code!(buf, ctx; code!(buf, ctx;
"Nix.assert(Nix.withContext(\"while evaluating the condition of the assert statement\",", "Nix.assert(Nix.withContext(\"while evaluating the condition of the assert statement\","
assertion_span, assertion_span
",()=>(", ",()=>("
assertion_ir, assertion_ir
")),", ")),"
ctx.get_ir(expr), ctx.get_ir(expr)
",", ","
quoted(assertion_raw), quoted(assertion_raw)
",", ","
assert_span, assert_span
")" ")"
); );
} }
Ir::CurPos(cur_pos) => { Ir::CurPos(cur_pos) => {
code!(buf, ctx; code!(buf, ctx;
"Nix.mkPos(", "Nix.mkPos("
cur_pos.span, cur_pos.span
")" ")"
); );
} }
&Ir::ReplBinding(ReplBinding { inner: name, .. }) => { &Ir::ReplBinding(ReplBinding { inner: name, .. }) => {
code!(buf, ctx; code!(buf, ctx;
"Nix.getReplBinding(" "Nix.getReplBinding("
quoted(ctx.get_sym(name)) ctx.get_sym(name)
")" ")"
); );
} }
&Ir::ScopedImportBinding(ScopedImportBinding { inner: name, .. }) => { &Ir::ScopedImportBinding(ScopedImportBinding { inner: name, .. }) => {
code!(buf, ctx; code!(buf, ctx;
"__scope[" "__scope["
quoted(ctx.get_sym(name)) ctx.get_sym(name)
"]" "]"
); );
} }
@@ -397,22 +404,10 @@ impl<Ctx: CodegenContext> Compile<Ctx> for BinOp {
); );
} }
PipeL => { PipeL => {
code!(buf, ctx; code!(buf, ctx; "Nix.call(" rhs "," lhs ")");
"Nix.call(",
rhs,
",",
lhs,
")"
);
} }
PipeR => { PipeR => {
code!(buf, ctx; code!(buf, ctx; "Nix.call(" lhs "," rhs ")");
"Nix.call(",
lhs,
",",
rhs,
")"
);
} }
} }
} }
@@ -421,20 +416,13 @@ impl<Ctx: CodegenContext> Compile<Ctx> for BinOp {
impl<Ctx: CodegenContext> Compile<Ctx> for UnOp { impl<Ctx: CodegenContext> Compile<Ctx> for UnOp {
fn compile(&self, ctx: &Ctx, buf: &mut CodeBuffer) { fn compile(&self, ctx: &Ctx, buf: &mut CodeBuffer) {
use UnOpKind::*; use UnOpKind::*;
let rhs = ctx.get_ir(self.rhs);
match self.kind { match self.kind {
Neg => { Neg => {
code!(buf, ctx; code!(buf, ctx; "Nix.op.sub(0n," rhs ")");
"Nix.op.sub(0n,",
ctx.get_ir(self.rhs),
")"
);
} }
Not => { Not => {
code!(buf, ctx; code!(buf, ctx; "Nix.op.bnot(" ctx.get_ir(self.rhs) ")");
"Nix.op.bnot(",
ctx.get_ir(self.rhs),
")"
);
} }
} }
} }
@@ -454,32 +442,33 @@ impl<Ctx: CodegenContext> Compile<Ctx> for Func {
{ {
code!(buf, "Nix.mkFunction(arg{}=>", id); code!(buf, "Nix.mkFunction(arg{}=>", id);
if has_thunks { if has_thunks {
code!(buf, ctx; "{", &self.thunks, "return ", self.body, "}"); code!(buf, ctx; "{" self.thunks "return " self.body "}");
} else { } else {
code!(buf, ctx; "(", self.body, ")"); code!(buf, ctx; "(" self.body ")");
} }
code!(buf, ctx; code!(buf, ctx;
",[" ",["
joined(required.iter(), ",", |ctx: &Ctx, buf, &(sym, _)| { joined(required.iter(), ",", |ctx: &Ctx, buf, &(sym, _)| {
code!(buf, ctx; quoted(ctx.get_sym(sym))); code!(buf, ctx; ctx.get_sym(sym));
}) })
"],[" "],["
joined(optional.iter(), ",", |ctx: &Ctx, buf, &(sym, _)| { joined(optional.iter(), ",", |ctx: &Ctx, buf, &(sym, _)| {
code!(buf, ctx; quoted(ctx.get_sym(sym))); code!(buf, ctx; ctx.get_sym(sym));
}) })
"],{" "],{"
joined(required.iter().chain(optional.iter()), ",", |ctx: &Ctx, buf, &(sym, span)| { joined(required.iter().chain(optional.iter()), ",", |ctx: &Ctx, buf, &(sym, span)| {
code!(buf, ctx; quoted(ctx.get_sym(sym)), ":", span); code!(buf, ctx; ctx.get_sym(sym) ":" span);
}) })
"}," "},"
ellipsis
")"
); );
code!(buf, "{})", ellipsis);
} else { } else {
code!(buf, "arg{}=>", id); code!(buf, "arg{}=>", id);
if has_thunks { if has_thunks {
code!(buf, ctx; "{", &self.thunks, "return ", self.body, "}"); code!(buf, ctx; "{" self.thunks "return " self.body "}");
} else { } else {
code!(buf, ctx; "(", self.body, ")"); code!(buf, ctx; "(" self.body ")");
} }
} }
} }
@@ -488,12 +477,12 @@ impl<Ctx: CodegenContext> Compile<Ctx> for Func {
impl<Ctx: CodegenContext> Compile<Ctx> for Call { impl<Ctx: CodegenContext> Compile<Ctx> for Call {
fn compile(&self, ctx: &Ctx, buf: &mut CodeBuffer) { fn compile(&self, ctx: &Ctx, buf: &mut CodeBuffer) {
code!(buf, ctx; code!(buf, ctx;
"Nix.call(", "Nix.call("
ctx.get_ir(self.func), ctx.get_ir(self.func)
",", ","
ctx.get_ir(self.arg), ctx.get_ir(self.arg)
",", ","
self.span, self.span
")" ")"
); );
} }
@@ -527,11 +516,8 @@ impl<Ctx: CodegenContext> Compile<Ctx> for TopLevel {
if self.thunks.is_empty() { if self.thunks.is_empty() {
ctx.get_ir(self.body).compile(ctx, buf); ctx.get_ir(self.body).compile(ctx, buf);
} else { } else {
code!(buf, "(()=>{"); let body = ctx.get_ir(self.body);
code!(buf, ctx; &self.thunks); code!(buf, ctx; "(()=>{" self.thunks "return " body "})()");
code!(buf, "return ");
ctx.get_ir(self.body).compile(ctx, buf);
code!(buf, "})()");
} }
} }
} }
@@ -545,7 +531,7 @@ impl<Ctx: CodegenContext> Compile<Ctx> for Select {
",[" ",["
joined(self.attrpath.iter(), ",", |ctx: &Ctx, buf, attr| { joined(self.attrpath.iter(), ",", |ctx: &Ctx, buf, attr| {
match attr { match attr {
Attr::Str(sym, _) => code!(buf, ctx; quoted(ctx.get_sym(*sym))), Attr::Str(sym, _) => code!(buf, ctx; ctx.get_sym(*sym)),
Attr::Dynamic(expr_id, _) => code!(buf, ctx; ctx.get_ir(*expr_id)), Attr::Dynamic(expr_id, _) => code!(buf, ctx; ctx.get_ir(*expr_id)),
} }
}) })
@@ -562,7 +548,7 @@ impl<Ctx: CodegenContext> Compile<Ctx> for Select {
",[" ",["
joined(self.attrpath.iter(), ",", |ctx: &Ctx, buf, attr| { joined(self.attrpath.iter(), ",", |ctx: &Ctx, buf, attr| {
match attr { match attr {
Attr::Str(sym, _) => code!(buf, ctx; quoted(ctx.get_sym(*sym))), Attr::Str(sym, _) => code!(buf, ctx; ctx.get_sym(*sym)),
Attr::Dynamic(expr_id, _) => code!(buf, ctx; ctx.get_ir(*expr_id)), Attr::Dynamic(expr_id, _) => code!(buf, ctx; ctx.get_ir(*expr_id)),
} }
}) })
@@ -585,12 +571,12 @@ impl<Ctx: CodegenContext> Compile<Ctx> for AttrSet {
code!( code!(
buf, ctx; buf, ctx;
quoted(key) ":Nix.withContext(\"while evaluating the attribute '" escaped(key) "'\"," val.span() ",()=>(" val "))" key ":Nix.withContext(\"while evaluating the attribute '" escaped(&key) "'\"," val.span() ",()=>(" val "))"
); );
}) })
"},{" "},{"
joined(self.stcs.iter(), ",", |ctx: &Ctx, buf, (&sym, &(_, span))| { joined(self.stcs.iter(), ",", |ctx: &Ctx, buf, (&sym, &(_, span))| {
code!(buf, ctx; quoted(ctx.get_sym(sym)) ":" span); code!(buf, ctx; ctx.get_sym(sym) ":" span);
}) })
"},{dynKeys:[" "},{dynKeys:["
joined(self.dyns.iter(), ",", |ctx: &Ctx, buf, (key, _, _)| { joined(self.dyns.iter(), ",", |ctx: &Ctx, buf, (key, _, _)| {
@@ -619,12 +605,12 @@ impl<Ctx: CodegenContext> Compile<Ctx> for AttrSet {
code!( code!(
buf, ctx; buf, ctx;
quoted(key) ":Nix.withContext(\"while evaluating the attribute '" escaped(key) "'\"," val.span() ",()=>(" val "))" key ":Nix.withContext(\"while evaluating the attribute '" escaped(&key) "'\"," val.span() ",()=>(" val "))"
); );
}) })
"},{" "},{"
joined(self.stcs.iter(), ",", |ctx: &Ctx, buf, (&sym, &(_, span))| { joined(self.stcs.iter(), ",", |ctx: &Ctx, buf, (&sym, &(_, span))| {
code!(buf, ctx; quoted(ctx.get_sym(sym)) ":" span); code!(buf, ctx; ctx.get_sym(sym) ":" span);
}) })
"})" "})"
); );
@@ -674,7 +660,7 @@ impl<Ctx: CodegenContext> Compile<Ctx> for HasAttr {
",[" ",["
joined(self.rhs.iter(), ",", |ctx: &Ctx, buf, attr| { joined(self.rhs.iter(), ",", |ctx: &Ctx, buf, attr| {
match attr { match attr {
Attr::Str(sym, _) => code!(buf, ctx; quoted(ctx.get_sym(*sym))), Attr::Str(sym, _) => code!(buf, ctx; ctx.get_sym(*sym)),
Attr::Dynamic(expr_id, _) => code!(buf, ctx; ctx.get_ir(*expr_id)), Attr::Dynamic(expr_id, _) => code!(buf, ctx; ctx.get_ir(*expr_id)),
} }
}) })

View File

@@ -7,14 +7,15 @@ use rnix::TextRange;
use string_interner::DefaultStringInterner; use string_interner::DefaultStringInterner;
use crate::codegen::{CodegenContext, compile}; use crate::codegen::{CodegenContext, compile};
use crate::downgrade::*;
use crate::error::{Error, Result, Source}; use crate::error::{Error, Result, Source};
use crate::ir::{ use crate::ir::{
Arg, ArgId, Bool, Builtin, Downgrade as _, DowngradeContext, ExprId, Ir, Null, ReplBinding, Arg, ArgId, Bool, Builtin, ExprId, Ir, Null, ReplBinding, ScopedImportBinding, SymId, Thunk,
ScopedImportBinding, SymId, Thunk, ToIr as _, ToIr as _,
}; };
use crate::runtime::{Runtime, RuntimeContext}; use crate::runtime::{Runtime, RuntimeContext};
use crate::store::{Store, StoreBackend, StoreConfig}; use crate::store::{Store, StoreBackend, StoreConfig};
use crate::value::Value; use crate::value::{Symbol, Value};
pub struct Context { pub struct Context {
ctx: Ctx, ctx: Ctx,
@@ -54,7 +55,8 @@ impl Context {
let code = self.ctx.compile(source, Some(Scope::Repl(scope)))?; let code = self.ctx.compile(source, Some(Scope::Repl(scope)))?;
tracing::debug!("Executing JavaScript"); tracing::debug!("Executing JavaScript");
self.runtime.eval(format!("Nix.forceShallow({})", code), &mut self.ctx) self.runtime
.eval(format!("Nix.forceShallow({})", code), &mut self.ctx)
} }
pub fn compile(&mut self, source: Source) -> Result<String> { pub fn compile(&mut self, source: Source) -> Result<String> {
@@ -65,7 +67,12 @@ impl Context {
self.ctx.get_store_dir() self.ctx.get_store_dir()
} }
pub fn add_binding<'a>(&'a mut self, name: &str, expr: &str, scope: &'a mut HashSet<SymId>) -> Result<Value> { pub fn add_binding<'a>(
&'a mut self,
name: &str,
expr: &str,
scope: &'a mut HashSet<SymId>,
) -> Result<Value> {
let source = Source::new_repl(expr.to_string())?; let source = Source::new_repl(expr.to_string())?;
let code = self.ctx.compile(source, Some(Scope::Repl(scope)))?; let code = self.ctx.compile(source, Some(Scope::Repl(scope)))?;
@@ -186,10 +193,7 @@ impl Ctx {
}) })
} }
fn downgrade_ctx<'a>( fn downgrade_ctx<'a>(&'a mut self, extra_scope: Option<Scope<'a>>) -> DowngradeCtx<'a> {
&'a mut self,
extra_scope: Option<Scope<'a>>,
) -> DowngradeCtx<'a> {
let global_ref = unsafe { self.global.as_ref() }; let global_ref = unsafe { self.global.as_ref() };
DowngradeCtx::new(self, global_ref, extra_scope) DowngradeCtx::new(self, global_ref, extra_scope)
} }
@@ -236,11 +240,7 @@ impl Ctx {
Ok(code) Ok(code)
} }
pub(crate) fn compile_scoped( pub(crate) fn compile_scoped(&mut self, source: Source, scope: Vec<String>) -> Result<String> {
&mut self,
source: Source,
scope: Vec<String>,
) -> Result<String> {
use crate::codegen::compile_scoped; use crate::codegen::compile_scoped;
tracing::debug!("Parsing Nix expression for scoped import"); tracing::debug!("Parsing Nix expression for scoped import");
@@ -262,7 +262,9 @@ impl Ctx {
); );
#[allow(clippy::unwrap_used)] #[allow(clippy::unwrap_used)]
let root = self.downgrade_ctx(Some(scope)).downgrade(root.tree().expr().unwrap())?; let root = self
.downgrade_ctx(Some(scope))
.downgrade(root.tree().expr().unwrap())?;
tracing::debug!("Generating JavaScript code for scoped import"); tracing::debug!("Generating JavaScript code for scoped import");
let code = compile_scoped(self.get_ir(root), self); let code = compile_scoped(self.get_ir(root), self);
@@ -275,8 +277,11 @@ impl CodegenContext for Ctx {
fn get_ir(&self, id: ExprId) -> &Ir { fn get_ir(&self, id: ExprId) -> &Ir {
self.irs.get(id.0).expect("ExprId out of bounds") self.irs.get(id.0).expect("ExprId out of bounds")
} }
fn get_sym(&self, id: SymId) -> &str { fn get_sym(&self, id: SymId) -> Symbol<'_> {
self.symbols.resolve(id).expect("SymId out of bounds") self.symbols
.resolve(id)
.expect("SymId out of bounds")
.into()
} }
fn get_current_dir(&self) -> &std::path::Path { fn get_current_dir(&self) -> &std::path::Path {
self.get_current_dir() self.get_current_dir()
@@ -420,7 +425,7 @@ impl DowngradeContext for DowngradeCtx<'_> {
self.ctx.symbols.get_or_intern(sym) self.ctx.symbols.get_or_intern(sym)
} }
fn get_sym(&self, id: SymId) -> &str { fn get_sym(&self, id: SymId) -> Symbol<'_> {
self.ctx.get_sym(id) self.ctx.get_sym(id)
} }

View File

@@ -5,14 +5,488 @@ use hashbrown::hash_map::Entry;
use hashbrown::{HashMap, HashSet}; use hashbrown::{HashMap, HashSet};
use itertools::Itertools as _; use itertools::Itertools as _;
use rnix::TextRange; use rnix::TextRange;
use rnix::ast::{self, HasEntry}; use rnix::ast::{self, AstToken, Expr, HasEntry};
use rowan::ast::AstNode; use rowan::ast::AstNode;
use crate::error::{Error, Result}; use crate::error::{Error, Result, Source};
use crate::ir::{Attr, AttrSet, ConcatStrings, ExprId, Select, Str, SymId}; use crate::ir::*;
use crate::value::format_symbol; use crate::value::Symbol;
use super::*; pub trait DowngradeContext {
fn downgrade(self, expr: rnix::ast::Expr) -> Result<ExprId>;
fn new_expr(&mut self, expr: Ir) -> ExprId;
fn new_arg(&mut self, span: TextRange) -> ExprId;
fn maybe_thunk(&mut self, id: ExprId) -> ExprId;
fn new_sym(&mut self, sym: String) -> SymId;
fn get_sym(&self, id: SymId) -> Symbol<'_>;
fn lookup(&mut self, sym: SymId, span: TextRange) -> Result<ExprId>;
fn get_ir(&self, id: ExprId) -> &Ir;
fn replace_ir(&mut self, id: ExprId, expr: Ir);
fn reserve_slots(&mut self, slots: usize) -> impl Iterator<Item = ExprId> + Clone + use<Self>;
fn get_current_source(&self) -> Source;
fn with_param_scope<F, R>(&mut self, param: SymId, arg: ExprId, f: F) -> R
where
F: FnOnce(&mut Self) -> R;
fn with_let_scope<F, R>(&mut self, bindings: HashMap<SymId, ExprId>, f: F) -> R
where
F: FnOnce(&mut Self) -> R;
fn with_with_scope<F, R>(&mut self, namespace: ExprId, f: F) -> R
where
F: FnOnce(&mut Self) -> R;
fn push_thunk_scope(&mut self);
fn pop_thunk_scope(&mut self) -> Vec<(ExprId, ExprId)>;
fn register_thunk(&mut self, slot: ExprId, inner: ExprId);
}
pub trait Downgrade<Ctx: DowngradeContext> {
fn downgrade(self, ctx: &mut Ctx) -> Result<ExprId>;
}
impl<Ctx: DowngradeContext> Downgrade<Ctx> for Expr {
fn downgrade(self, ctx: &mut Ctx) -> Result<ExprId> {
use Expr::*;
match self {
Apply(apply) => apply.downgrade(ctx),
Assert(assert) => assert.downgrade(ctx),
Error(error) => {
let span = error.syntax().text_range();
Err(self::Error::downgrade_error(
error.to_string(),
ctx.get_current_source(),
span,
))
}
IfElse(ifelse) => ifelse.downgrade(ctx),
Select(select) => select.downgrade(ctx),
Str(str) => str.downgrade(ctx),
Path(path) => path.downgrade(ctx),
Literal(lit) => lit.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),
Ident(ident) => ident.downgrade(ctx),
With(with) => with.downgrade(ctx),
HasAttr(has) => has.downgrade(ctx),
Paren(paren) => paren.expr().unwrap().downgrade(ctx),
Root(root) => root.expr().unwrap().downgrade(ctx),
}
}
}
impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::Assert {
fn downgrade(self, ctx: &mut Ctx) -> Result<ExprId> {
let assertion = self.condition().unwrap();
let assertion_raw = assertion.to_string();
let assertion = assertion.downgrade(ctx)?;
let expr = self.body().unwrap().downgrade(ctx)?;
let span = self.syntax().text_range();
Ok(ctx.new_expr(
Assert {
assertion,
expr,
assertion_raw,
span,
}
.to_ir(),
))
}
}
impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::IfElse {
fn downgrade(self, ctx: &mut Ctx) -> Result<ExprId> {
let cond = self.condition().unwrap().downgrade(ctx)?;
let consq = self.body().unwrap().downgrade(ctx)?;
let alter = self.else_body().unwrap().downgrade(ctx)?;
let span = self.syntax().text_range();
Ok(ctx.new_expr(
If {
cond,
consq,
alter,
span,
}
.to_ir(),
))
}
}
impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::Path {
fn downgrade(self, ctx: &mut Ctx) -> Result<ExprId> {
let span = self.syntax().text_range();
let parts = self
.parts()
.map(|part| match part {
ast::InterpolPart::Literal(lit) => Ok(ctx.new_expr(
Str {
val: lit.to_string(),
span: lit.syntax().text_range(),
}
.to_ir(),
)),
ast::InterpolPart::Interpolation(interpol) => {
interpol.expr().unwrap().downgrade(ctx)
}
})
.collect::<Result<Vec<_>>>()?;
let expr = if parts.len() == 1 {
let part = parts.into_iter().next().unwrap();
if let &Ir::Str(Str { ref val, span }) = ctx.get_ir(part)
&& let Some(path) = val.strip_prefix("<").map(|path| &path[..path.len() - 1])
{
ctx.replace_ir(
part,
Str {
val: path.to_string(),
span,
}
.to_ir(),
);
let sym = ctx.new_sym("findFile".into());
let find_file = ctx.new_expr(Builtin { inner: sym, span }.to_ir());
let sym = ctx.new_sym("nixPath".into());
let nix_path = ctx.new_expr(Builtin { inner: sym, span }.to_ir());
let call = ctx.new_expr(
Call {
func: find_file,
arg: nix_path,
span,
}
.to_ir(),
);
return Ok(ctx.new_expr(
Call {
func: call,
arg: part,
span,
}
.to_ir(),
));
} else {
part
}
} else {
ctx.new_expr(
ConcatStrings {
parts,
span,
force_string: false,
}
.to_ir(),
)
};
Ok(ctx.new_expr(Path { expr, span }.to_ir()))
}
}
impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::Str {
fn downgrade(self, ctx: &mut Ctx) -> Result<ExprId> {
let span = self.syntax().text_range();
let normalized = self.normalized_parts();
let is_single_literal = normalized.len() == 1
&& matches!(normalized.first(), Some(ast::InterpolPart::Literal(_)));
let parts = normalized
.into_iter()
.map(|part| match part {
ast::InterpolPart::Literal(lit) => Ok(ctx.new_expr(Str { val: lit, span }.to_ir())),
ast::InterpolPart::Interpolation(interpol) => {
let inner = interpol.expr().unwrap().downgrade(ctx)?;
Ok(ctx.maybe_thunk(inner))
}
})
.collect::<Result<Vec<_>>>()?;
Ok(if is_single_literal {
parts.into_iter().next().unwrap()
} else {
ctx.new_expr(
ConcatStrings {
parts,
span,
force_string: true,
}
.to_ir(),
)
})
}
}
impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::Literal {
fn downgrade(self, ctx: &mut Ctx) -> Result<ExprId> {
let span = self.syntax().text_range();
Ok(ctx.new_expr(match self.kind() {
ast::LiteralKind::Integer(int) => Int {
inner: int.value().unwrap(),
span,
}
.to_ir(),
ast::LiteralKind::Float(float) => Float {
inner: float.value().unwrap(),
span,
}
.to_ir(),
ast::LiteralKind::Uri(uri) => Str {
val: uri.to_string(),
span,
}
.to_ir(),
}))
}
}
impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::Ident {
fn downgrade(self, ctx: &mut Ctx) -> Result<ExprId> {
let text = self.ident_token().unwrap().to_string();
let span = self.syntax().text_range();
if text == "__curPos" {
return Ok(ctx.new_expr(CurPos { span }.to_ir()));
}
let sym = ctx.new_sym(text);
ctx.lookup(sym, span)
}
}
impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::AttrSet {
fn downgrade(self, ctx: &mut Ctx) -> Result<ExprId> {
let rec = self.rec_token().is_some();
let span = self.syntax().text_range();
if !rec {
let attrs = downgrade_attrs(self, ctx)?;
return Ok(ctx.new_expr(attrs.to_ir()));
}
// rec { a = 1; b = a; } => let a = 1; b = a; in { inherit a b; }
let entries: Vec<_> = self.entries().collect();
downgrade_rec_bindings(entries, ctx, span)
}
}
/// Downgrades a list.
impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::List {
fn downgrade(self, ctx: &mut Ctx) -> Result<ExprId> {
let items = self
.items()
.map(|item| {
let id = item.downgrade(ctx)?;
Ok(ctx.maybe_thunk(id))
})
.collect::<Result<_>>()?;
let span = self.syntax().text_range();
Ok(ctx.new_expr(List { items, span }.to_ir()))
}
}
/// Downgrades a binary operation.
impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::BinOp {
fn downgrade(self, ctx: &mut Ctx) -> Result<ExprId> {
let lhs = self.lhs().unwrap().downgrade(ctx)?;
let rhs = self.rhs().unwrap().downgrade(ctx)?;
let kind = self.operator().unwrap().into();
let span = self.syntax().text_range();
Ok(ctx.new_expr(
BinOp {
lhs,
rhs,
kind,
span,
}
.to_ir(),
))
}
}
/// Downgrades a "has attribute" (`?`) expression.
impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::HasAttr {
fn downgrade(self, ctx: &mut Ctx) -> Result<ExprId> {
let lhs = self.expr().unwrap().downgrade(ctx)?;
let rhs = downgrade_attrpath(self.attrpath().unwrap(), ctx)?;
let span = self.syntax().text_range();
Ok(ctx.new_expr(HasAttr { lhs, rhs, span }.to_ir()))
}
}
/// Downgrades a unary operation.
impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::UnaryOp {
fn downgrade(self, ctx: &mut Ctx) -> Result<ExprId> {
let rhs = self.expr().unwrap().downgrade(ctx)?;
let kind = self.operator().unwrap().into();
let span = self.syntax().text_range();
Ok(ctx.new_expr(UnOp { rhs, kind, span }.to_ir()))
}
}
/// Downgrades an attribute selection (`.`).
impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::Select {
fn downgrade(self, ctx: &mut Ctx) -> Result<ExprId> {
let expr = self.expr().unwrap().downgrade(ctx)?;
let attrpath = downgrade_attrpath(self.attrpath().unwrap(), ctx)?;
let default = if let Some(default) = self.default_expr() {
let default_expr = default.downgrade(ctx)?;
Some(ctx.maybe_thunk(default_expr))
} else {
None
};
let span = self.syntax().text_range();
Ok(ctx.new_expr(
Select {
expr,
attrpath,
default,
span,
}
.to_ir(),
))
}
}
/// Downgrades a `legacy let`, which is essentially a recursive attribute set.
/// 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 span = self.syntax().text_range();
let entries: Vec<_> = self.entries().collect();
let attrset_expr = downgrade_let_bindings(entries, ctx, span, |ctx, binding_keys| {
// Create plain attrset as body with inherit
let mut attrs = AttrSet {
stcs: HashMap::new(),
dyns: Vec::new(),
span,
};
for sym in binding_keys {
let expr = ctx.lookup(*sym, rnix::TextRange::default())?;
attrs.stcs.insert(*sym, (expr, rnix::TextRange::default()));
}
Ok(ctx.new_expr(attrs.to_ir()))
})?;
let body_sym = ctx.new_sym("body".to_string());
let select = Select {
expr: attrset_expr,
attrpath: vec![Attr::Str(body_sym, rnix::TextRange::default())],
default: None,
span,
};
Ok(ctx.new_expr(select.to_ir()))
}
}
/// Downgrades a `let ... in ...` expression.
impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::LetIn {
fn downgrade(self, ctx: &mut Ctx) -> Result<ExprId> {
let entries: Vec<_> = self.entries().collect();
let body_expr = self.body().unwrap();
let span = self.syntax().text_range();
downgrade_let_bindings(entries, ctx, span, |ctx, _binding_keys| {
body_expr.downgrade(ctx)
})
}
}
/// Downgrades a `with` expression.
impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::With {
fn downgrade(self, ctx: &mut Ctx) -> Result<ExprId> {
// with namespace; expr
let namespace = self.namespace().unwrap().downgrade(ctx)?;
// Downgrade body in With scope
let expr = ctx.with_with_scope(namespace, |ctx| self.body().unwrap().downgrade(ctx))?;
Ok(expr)
}
}
/// Downgrades a lambda (function) expression.
/// This involves desugaring pattern-matching arguments into `let` bindings.
impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::Lambda {
fn downgrade(self, ctx: &mut Ctx) -> Result<ExprId> {
let raw_param = self.param().unwrap();
let arg = ctx.new_arg(raw_param.syntax().text_range());
ctx.push_thunk_scope();
let param;
let body;
match raw_param {
ast::Param::IdentParam(id) => {
// Simple case: `x: body`
let param_sym = ctx.new_sym(id.to_string());
param = None;
// Downgrade body in Param scope
body = ctx
.with_param_scope(param_sym, arg, |ctx| self.body().unwrap().downgrade(ctx))?;
}
ast::Param::Pattern(pattern) => {
let alias = pattern
.pat_bind()
.map(|alias| ctx.new_sym(alias.ident().unwrap().to_string()));
let ellipsis = pattern.ellipsis_token().is_some();
let pat_entries = pattern.pat_entries();
let PatternBindings {
body: inner_body,
required,
optional,
} = downgrade_pattern_bindings(pat_entries, alias, arg, ctx, |ctx, _| {
self.body().unwrap().downgrade(ctx)
})?;
param = Some(Param {
required,
optional,
ellipsis,
});
body = inner_body;
}
}
let thunks = ctx.pop_thunk_scope();
let span = self.syntax().text_range();
Ok(ctx.new_expr(
Func {
body,
param,
arg,
thunks,
span,
}
.to_ir(),
))
}
}
/// Downgrades a function application.
/// 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 func = self.lambda().unwrap().downgrade(ctx)?;
let arg = self.argument().unwrap().downgrade(ctx)?;
let arg = ctx.maybe_thunk(arg);
let span = self.syntax().text_range();
Ok(ctx.new_expr(Call { func, arg, span }.to_ir()))
}
}
enum PendingValue { enum PendingValue {
Expr(ast::Expr), Expr(ast::Expr),
@@ -321,10 +795,7 @@ impl PendingAttrSet {
if self.stcs.contains_key(&sym) { if self.stcs.contains_key(&sym) {
return Err(Error::downgrade_error( return Err(Error::downgrade_error(
format!( format!("attribute '{}' already defined", ctx.get_sym(sym)),
"attribute '{}' already defined",
format_symbol(ctx.get_sym(sym))
),
ctx.get_current_source(), ctx.get_current_source(),
span, span,
)); ));
@@ -401,7 +872,7 @@ fn make_attrpath_value_entry(path: Vec<ast::Attr>, value: ast::Expr) -> ast::Ent
} }
/// Downgrades the entries of a non-recursive attribute set. /// Downgrades the entries of a non-recursive attribute set.
pub fn downgrade_attrs( fn downgrade_attrs(
attrs: impl ast::HasEntry + AstNode, attrs: impl ast::HasEntry + AstNode,
ctx: &mut impl DowngradeContext, ctx: &mut impl DowngradeContext,
) -> Result<AttrSet> { ) -> Result<AttrSet> {
@@ -412,7 +883,7 @@ pub fn downgrade_attrs(
} }
/// Downgrades a single attribute key (part of an attribute path). /// Downgrades a single attribute key (part of an attribute path).
pub fn downgrade_attr(attr: ast::Attr, ctx: &mut impl DowngradeContext) -> Result<Attr> { fn downgrade_attr(attr: ast::Attr, ctx: &mut impl DowngradeContext) -> Result<Attr> {
use ast::Attr::*; use ast::Attr::*;
use ast::InterpolPart::*; use ast::InterpolPart::*;
match attr { match attr {
@@ -462,7 +933,7 @@ pub fn downgrade_attr(attr: ast::Attr, ctx: &mut impl DowngradeContext) -> Resul
} }
/// Downgrades an attribute path (e.g., `a.b."${c}".d`) into a `Vec<Attr>`. /// Downgrades an attribute path (e.g., `a.b."${c}".d`) into a `Vec<Attr>`.
pub fn downgrade_attrpath( fn downgrade_attrpath(
attrpath: ast::Attrpath, attrpath: ast::Attrpath,
ctx: &mut impl DowngradeContext, ctx: &mut impl DowngradeContext,
) -> Result<Vec<Attr>> { ) -> Result<Vec<Attr>> {
@@ -472,14 +943,14 @@ pub fn downgrade_attrpath(
.collect::<Result<Vec<_>>>() .collect::<Result<Vec<_>>>()
} }
pub struct PatternBindings { struct PatternBindings {
pub body: ExprId, body: ExprId,
pub required: Vec<(SymId, TextRange)>, required: Vec<(SymId, TextRange)>,
pub optional: Vec<(SymId, TextRange)>, optional: Vec<(SymId, TextRange)>,
} }
/// Helper function for Lambda pattern parameters. /// Helper function for Lambda pattern parameters.
pub fn downgrade_pattern_bindings<Ctx>( fn downgrade_pattern_bindings<Ctx>(
pat_entries: impl Iterator<Item = ast::PatEntry>, pat_entries: impl Iterator<Item = ast::PatEntry>,
alias: Option<SymId>, alias: Option<SymId>,
arg: ExprId, arg: ExprId,
@@ -507,7 +978,7 @@ where
if !seen_params.insert(sym) { if !seen_params.insert(sym) {
return Err(Error::downgrade_error( return Err(Error::downgrade_error(
format!("duplicate parameter '{}'", format_symbol(ctx.get_sym(sym))), format!("duplicate parameter '{}'", ctx.get_sym(sym)),
ctx.get_current_source(), ctx.get_current_source(),
span, span,
)); ));
@@ -603,7 +1074,7 @@ where
/// Downgrades a `let...in` expression. This is a special case of rec attrs /// Downgrades a `let...in` expression. This is a special case of rec attrs
/// that disallows dynamic attributes and has a body expression. /// that disallows dynamic attributes and has a body expression.
pub fn downgrade_let_bindings<Ctx, F>( fn downgrade_let_bindings<Ctx, F>(
entries: Vec<ast::Entry>, entries: Vec<ast::Entry>,
ctx: &mut Ctx, ctx: &mut Ctx,
span: TextRange, span: TextRange,
@@ -619,7 +1090,7 @@ where
} }
/// Downgrades a `rec` attribute set. /// Downgrades a `rec` attribute set.
pub fn downgrade_rec_bindings<Ctx>( fn downgrade_rec_bindings<Ctx>(
entries: Vec<ast::Entry>, entries: Vec<ast::Entry>,
ctx: &mut Ctx, ctx: &mut Ctx,
span: TextRange, span: TextRange,
@@ -691,7 +1162,7 @@ where
} else { } else {
return Err(Error::internal(format!( return Err(Error::internal(format!(
"binding '{}' not found", "binding '{}' not found",
format_symbol(ctx.get_sym(sym)) ctx.get_sym(sym)
))); )));
} }
} }
@@ -733,10 +1204,7 @@ fn collect_binding_syms<Ctx: DowngradeContext, const ALLOW_DYN: bool>(
for (sym, (_, span)) in &pending.stcs { for (sym, (_, span)) in &pending.stcs {
if !binding_syms.insert(*sym) { if !binding_syms.insert(*sym) {
return Err(Error::downgrade_error( return Err(Error::downgrade_error(
format!( format!("attribute '{}' already defined", ctx.get_sym(*sym)),
"attribute '{}' already defined",
format_symbol(ctx.get_sym(*sym))
),
ctx.get_current_source(), ctx.get_current_source(),
*span, *span,
)); ));

View File

@@ -3,47 +3,8 @@ use hashbrown::HashMap;
use rnix::{TextRange, ast}; use rnix::{TextRange, ast};
use string_interner::symbol::SymbolU32; use string_interner::symbol::SymbolU32;
use crate::error::{Result, Source};
use nix_js_macros::ir; use nix_js_macros::ir;
mod downgrade;
mod utils;
use utils::*;
pub use downgrade::Downgrade;
pub trait DowngradeContext {
fn downgrade(self, expr: rnix::ast::Expr) -> Result<ExprId>;
fn new_expr(&mut self, expr: Ir) -> ExprId;
fn new_arg(&mut self, span: TextRange) -> ExprId;
fn maybe_thunk(&mut self, id: ExprId) -> ExprId;
fn new_sym(&mut self, sym: String) -> SymId;
fn get_sym(&self, id: SymId) -> &str;
fn lookup(&mut self, sym: SymId, span: TextRange) -> Result<ExprId>;
fn get_ir(&self, id: ExprId) -> &Ir;
fn replace_ir(&mut self, id: ExprId, expr: Ir);
fn reserve_slots(&mut self, slots: usize) -> impl Iterator<Item = ExprId> + Clone + use<Self>;
fn get_current_source(&self) -> Source;
fn with_param_scope<F, R>(&mut self, param: SymId, arg: ExprId, f: F) -> R
where
F: FnOnce(&mut Self) -> R;
fn with_let_scope<F, R>(&mut self, bindings: HashMap<SymId, ExprId>, f: F) -> R
where
F: FnOnce(&mut Self) -> R;
fn with_with_scope<F, R>(&mut self, namespace: ExprId, f: F) -> R
where
F: FnOnce(&mut Self) -> R;
fn push_thunk_scope(&mut self);
fn pop_thunk_scope(&mut self) -> Vec<(ExprId, ExprId)>;
fn register_thunk(&mut self, slot: ExprId, inner: ExprId);
}
ir! { ir! {
Ir, Ir,

View File

@@ -1,453 +0,0 @@
// Assume no parse error
#![allow(clippy::unwrap_used)]
use rnix::ast::{self, AstToken, Expr, HasEntry};
use rowan::ast::AstNode;
use super::*;
use crate::error::{Error, Result};
pub trait Downgrade<Ctx: DowngradeContext> {
fn downgrade(self, ctx: &mut Ctx) -> Result<ExprId>;
}
impl<Ctx: DowngradeContext> Downgrade<Ctx> for Expr {
fn downgrade(self, ctx: &mut Ctx) -> Result<ExprId> {
use Expr::*;
match self {
Apply(apply) => apply.downgrade(ctx),
Assert(assert) => assert.downgrade(ctx),
Error(error) => {
let span = error.syntax().text_range();
Err(self::Error::downgrade_error(
error.to_string(),
ctx.get_current_source(),
span,
))
}
IfElse(ifelse) => ifelse.downgrade(ctx),
Select(select) => select.downgrade(ctx),
Str(str) => str.downgrade(ctx),
Path(path) => path.downgrade(ctx),
Literal(lit) => lit.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),
Ident(ident) => ident.downgrade(ctx),
With(with) => with.downgrade(ctx),
HasAttr(has) => has.downgrade(ctx),
Paren(paren) => paren.expr().unwrap().downgrade(ctx),
Root(root) => root.expr().unwrap().downgrade(ctx),
}
}
}
impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::Assert {
fn downgrade(self, ctx: &mut Ctx) -> Result<ExprId> {
let assertion = self.condition().unwrap();
let assertion_raw = assertion.to_string();
let assertion = assertion.downgrade(ctx)?;
let expr = self.body().unwrap().downgrade(ctx)?;
let span = self.syntax().text_range();
Ok(ctx.new_expr(
Assert {
assertion,
expr,
assertion_raw,
span,
}
.to_ir(),
))
}
}
impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::IfElse {
fn downgrade(self, ctx: &mut Ctx) -> Result<ExprId> {
let cond = self.condition().unwrap().downgrade(ctx)?;
let consq = self.body().unwrap().downgrade(ctx)?;
let alter = self.else_body().unwrap().downgrade(ctx)?;
let span = self.syntax().text_range();
Ok(ctx.new_expr(
If {
cond,
consq,
alter,
span,
}
.to_ir(),
))
}
}
impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::Path {
fn downgrade(self, ctx: &mut Ctx) -> Result<ExprId> {
let span = self.syntax().text_range();
let parts = self
.parts()
.map(|part| match part {
ast::InterpolPart::Literal(lit) => Ok(ctx.new_expr(
Str {
val: lit.to_string(),
span: lit.syntax().text_range(),
}
.to_ir(),
)),
ast::InterpolPart::Interpolation(interpol) => {
interpol.expr().unwrap().downgrade(ctx)
}
})
.collect::<Result<Vec<_>>>()?;
let expr = if parts.len() == 1 {
let part = parts.into_iter().next().unwrap();
if let &Ir::Str(Str { ref val, span }) = ctx.get_ir(part)
&& let Some(path) = val.strip_prefix("<").map(|path| &path[..path.len() - 1])
{
ctx.replace_ir(
part,
Str {
val: path.to_string(),
span,
}
.to_ir(),
);
let sym = ctx.new_sym("findFile".into());
let find_file = ctx.new_expr(Builtin { inner: sym, span }.to_ir());
let sym = ctx.new_sym("nixPath".into());
let nix_path = ctx.new_expr(Builtin { inner: sym, span }.to_ir());
let call = ctx.new_expr(
Call {
func: find_file,
arg: nix_path,
span,
}
.to_ir(),
);
return Ok(ctx.new_expr(
Call {
func: call,
arg: part,
span,
}
.to_ir(),
));
} else {
part
}
} else {
ctx.new_expr(
ConcatStrings {
parts,
span,
force_string: false,
}
.to_ir(),
)
};
Ok(ctx.new_expr(Path { expr, span }.to_ir()))
}
}
impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::Str {
fn downgrade(self, ctx: &mut Ctx) -> Result<ExprId> {
let span = self.syntax().text_range();
let normalized = self.normalized_parts();
let is_single_literal = normalized.len() == 1
&& matches!(normalized.first(), Some(ast::InterpolPart::Literal(_)));
let parts = normalized
.into_iter()
.map(|part| match part {
ast::InterpolPart::Literal(lit) => Ok(ctx.new_expr(Str { val: lit, span }.to_ir())),
ast::InterpolPart::Interpolation(interpol) => {
let inner = interpol.expr().unwrap().downgrade(ctx)?;
Ok(ctx.maybe_thunk(inner))
}
})
.collect::<Result<Vec<_>>>()?;
Ok(if is_single_literal {
parts.into_iter().next().unwrap()
} else {
ctx.new_expr(
ConcatStrings {
parts,
span,
force_string: true,
}
.to_ir(),
)
})
}
}
impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::Literal {
fn downgrade(self, ctx: &mut Ctx) -> Result<ExprId> {
let span = self.syntax().text_range();
Ok(ctx.new_expr(match self.kind() {
ast::LiteralKind::Integer(int) => Int {
inner: int.value().unwrap(),
span,
}
.to_ir(),
ast::LiteralKind::Float(float) => Float {
inner: float.value().unwrap(),
span,
}
.to_ir(),
ast::LiteralKind::Uri(uri) => Str {
val: uri.to_string(),
span,
}
.to_ir(),
}))
}
}
impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::Ident {
fn downgrade(self, ctx: &mut Ctx) -> Result<ExprId> {
let text = self.ident_token().unwrap().to_string();
let span = self.syntax().text_range();
if text == "__curPos" {
return Ok(ctx.new_expr(CurPos { span }.to_ir()));
}
let sym = ctx.new_sym(text);
ctx.lookup(sym, span)
}
}
impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::AttrSet {
fn downgrade(self, ctx: &mut Ctx) -> Result<ExprId> {
let rec = self.rec_token().is_some();
let span = self.syntax().text_range();
if !rec {
let attrs = downgrade_attrs(self, ctx)?;
return Ok(ctx.new_expr(attrs.to_ir()));
}
// rec { a = 1; b = a; } => let a = 1; b = a; in { inherit a b; }
let entries: Vec<_> = self.entries().collect();
downgrade_rec_bindings(entries, ctx, span)
}
}
/// Downgrades a list.
impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::List {
fn downgrade(self, ctx: &mut Ctx) -> Result<ExprId> {
let items = self
.items()
.map(|item| {
let id = item.downgrade(ctx)?;
Ok(ctx.maybe_thunk(id))
})
.collect::<Result<_>>()?;
let span = self.syntax().text_range();
Ok(ctx.new_expr(List { items, span }.to_ir()))
}
}
/// Downgrades a binary operation.
impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::BinOp {
fn downgrade(self, ctx: &mut Ctx) -> Result<ExprId> {
let lhs = self.lhs().unwrap().downgrade(ctx)?;
let rhs = self.rhs().unwrap().downgrade(ctx)?;
let kind = self.operator().unwrap().into();
let span = self.syntax().text_range();
Ok(ctx.new_expr(
BinOp {
lhs,
rhs,
kind,
span,
}
.to_ir(),
))
}
}
/// Downgrades a "has attribute" (`?`) expression.
impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::HasAttr {
fn downgrade(self, ctx: &mut Ctx) -> Result<ExprId> {
let lhs = self.expr().unwrap().downgrade(ctx)?;
let rhs = downgrade_attrpath(self.attrpath().unwrap(), ctx)?;
let span = self.syntax().text_range();
Ok(ctx.new_expr(HasAttr { lhs, rhs, span }.to_ir()))
}
}
/// Downgrades a unary operation.
impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::UnaryOp {
fn downgrade(self, ctx: &mut Ctx) -> Result<ExprId> {
let rhs = self.expr().unwrap().downgrade(ctx)?;
let kind = self.operator().unwrap().into();
let span = self.syntax().text_range();
Ok(ctx.new_expr(UnOp { rhs, kind, span }.to_ir()))
}
}
/// Downgrades an attribute selection (`.`).
impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::Select {
fn downgrade(self, ctx: &mut Ctx) -> Result<ExprId> {
let expr = self.expr().unwrap().downgrade(ctx)?;
let attrpath = downgrade_attrpath(self.attrpath().unwrap(), ctx)?;
let default = if let Some(default) = self.default_expr() {
let default_expr = default.downgrade(ctx)?;
Some(ctx.maybe_thunk(default_expr))
} else {
None
};
let span = self.syntax().text_range();
Ok(ctx.new_expr(
Select {
expr,
attrpath,
default,
span,
}
.to_ir(),
))
}
}
/// Downgrades a `legacy let`, which is essentially a recursive attribute set.
/// 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 span = self.syntax().text_range();
let entries: Vec<_> = self.entries().collect();
let attrset_expr = downgrade_let_bindings(entries, ctx, span, |ctx, binding_keys| {
// Create plain attrset as body with inherit
let mut attrs = AttrSet {
stcs: HashMap::new(),
dyns: Vec::new(),
span,
};
for sym in binding_keys {
let expr = ctx.lookup(*sym, rnix::TextRange::default())?;
attrs.stcs.insert(*sym, (expr, rnix::TextRange::default()));
}
Ok(ctx.new_expr(attrs.to_ir()))
})?;
let body_sym = ctx.new_sym("body".to_string());
let select = Select {
expr: attrset_expr,
attrpath: vec![Attr::Str(body_sym, rnix::TextRange::default())],
default: None,
span,
};
Ok(ctx.new_expr(select.to_ir()))
}
}
/// Downgrades a `let ... in ...` expression.
impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::LetIn {
fn downgrade(self, ctx: &mut Ctx) -> Result<ExprId> {
let entries: Vec<_> = self.entries().collect();
let body_expr = self.body().unwrap();
let span = self.syntax().text_range();
downgrade_let_bindings(entries, ctx, span, |ctx, _binding_keys| {
body_expr.downgrade(ctx)
})
}
}
/// Downgrades a `with` expression.
impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::With {
fn downgrade(self, ctx: &mut Ctx) -> Result<ExprId> {
// with namespace; expr
let namespace = self.namespace().unwrap().downgrade(ctx)?;
// Downgrade body in With scope
let expr = ctx.with_with_scope(namespace, |ctx| self.body().unwrap().downgrade(ctx))?;
Ok(expr)
}
}
/// Downgrades a lambda (function) expression.
/// This involves desugaring pattern-matching arguments into `let` bindings.
impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::Lambda {
fn downgrade(self, ctx: &mut Ctx) -> Result<ExprId> {
let raw_param = self.param().unwrap();
let arg = ctx.new_arg(raw_param.syntax().text_range());
ctx.push_thunk_scope();
let param;
let body;
match raw_param {
ast::Param::IdentParam(id) => {
// Simple case: `x: body`
let param_sym = ctx.new_sym(id.to_string());
param = None;
// Downgrade body in Param scope
body = ctx
.with_param_scope(param_sym, arg, |ctx| self.body().unwrap().downgrade(ctx))?;
}
ast::Param::Pattern(pattern) => {
let alias = pattern
.pat_bind()
.map(|alias| ctx.new_sym(alias.ident().unwrap().to_string()));
let ellipsis = pattern.ellipsis_token().is_some();
let pat_entries = pattern.pat_entries();
let PatternBindings {
body: inner_body,
required,
optional,
} = downgrade_pattern_bindings(pat_entries, alias, arg, ctx, |ctx, _| {
self.body().unwrap().downgrade(ctx)
})?;
param = Some(Param {
required,
optional,
ellipsis,
});
body = inner_body;
}
}
let thunks = ctx.pop_thunk_scope();
let span = self.syntax().text_range();
Ok(ctx.new_expr(
Func {
body,
param,
arg,
thunks,
span,
}
.to_ir(),
))
}
}
/// Downgrades a function application.
/// 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 func = self.lambda().unwrap().downgrade(ctx)?;
let arg = self.argument().unwrap().downgrade(ctx)?;
let arg = ctx.maybe_thunk(arg);
let span = self.syntax().text_range();
Ok(ctx.new_expr(Call { func, arg, span }.to_ir()))
}
}

View File

@@ -6,6 +6,7 @@ pub mod logging;
pub mod value; pub mod value;
mod codegen; mod codegen;
mod downgrade;
mod fetcher; mod fetcher;
mod ir; mod ir;
mod nar; mod nar;

View File

@@ -332,7 +332,7 @@ fn to_value<'a>(
let val = val.get(scope, key).expect("infallible operation"); let val = val.get(scope, key).expect("infallible operation");
let key = key.to_rust_string_lossy(scope); let key = key.to_rust_string_lossy(scope);
( (
Symbol::new(key), Symbol::from(key),
to_value( to_value(
val, val,
scope, scope,

View File

@@ -10,11 +10,19 @@ use derive_more::{Constructor, IsVariant, Unwrap};
/// Represents a Nix symbol, which is used as a key in attribute sets. /// 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<'a>(Cow<'a, str>);
impl<T: Into<String>> From<T> for Symbol { pub type StaticSymbol = Symbol<'static>;
fn from(value: T) -> Self {
Symbol(value.into()) impl From<String> for Symbol<'_> {
fn from(value: String) -> Self {
Symbol(Cow::Owned(value))
}
}
impl<'a> From<&'a str> for Symbol<'a> {
fn from(value: &'a str) -> Self {
Symbol(Cow::Borrowed(value))
} }
} }
@@ -28,7 +36,7 @@ pub fn format_symbol<'a>(sym: impl Into<Cow<'a, str>>) -> Cow<'a, str> {
} }
} }
impl Display for Symbol { impl Display for Symbol<'_> {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
if self.normal() { if self.normal() {
write!(f, "{}", self.0) write!(f, "{}", self.0)
@@ -38,7 +46,7 @@ impl Display for Symbol {
} }
} }
impl Symbol { impl Symbol<'_> {
const NORMAL_REGEX: ere::Regex<1> = ere::compile_regex!("^[a-zA-Z_][a-zA-Z0-9_'-]*$"); const NORMAL_REGEX: ere::Regex<1> = ere::compile_regex!("^[a-zA-Z_][a-zA-Z0-9_'-]*$");
/// Checks if the symbol is a "normal" identifier that doesn't require quotes. /// Checks if the symbol is a "normal" identifier that doesn't require quotes.
fn normal(&self) -> bool { fn normal(&self) -> bool {
@@ -46,45 +54,33 @@ impl Symbol {
} }
} }
impl Deref for Symbol { impl Deref for Symbol<'_> {
type Target = str; type Target = str;
fn deref(&self) -> &Self::Target { fn deref(&self) -> &Self::Target {
&self.0 &self.0
} }
} }
impl Symbol {
/// Consumes the `Symbol`, returning its inner `String`.
pub fn into_inner(self) -> String {
self.0
}
/// Returns a reference to the inner `String`.
pub fn as_inner(&self) -> &String {
&self.0
}
}
/// Represents a Nix attribute set, which is a map from symbols to values. /// Represents a Nix attribute set, which is a map from symbols to values.
#[derive(Constructor, Default, Clone, PartialEq)] #[derive(Constructor, Default, Clone, PartialEq)]
pub struct AttrSet { pub struct AttrSet {
data: BTreeMap<Symbol, Value>, data: BTreeMap<StaticSymbol, Value>,
} }
impl AttrSet { impl AttrSet {
/// Gets a value by key (string or Symbol). /// Gets a value by key (string or Symbol).
pub fn get(&self, key: impl Into<Symbol>) -> Option<&Value> { pub fn get<'a, 'sym: 'a>(&'a self, key: impl Into<Symbol<'sym>>) -> Option<&'a Value> {
self.data.get(&key.into()) self.data.get(&key.into())
} }
/// Checks if a key exists in the attribute set. /// Checks if a key exists in the attribute set.
pub fn contains_key(&self, key: impl Into<Symbol>) -> bool { pub fn contains_key<'a, 'sym: 'a>(&'a self, key: impl Into<Symbol<'sym>>) -> bool {
self.data.contains_key(&key.into()) self.data.contains_key(&key.into())
} }
} }
impl Deref for AttrSet { impl Deref for AttrSet {
type Target = BTreeMap<Symbol, Value>; type Target = BTreeMap<StaticSymbol, Value>;
fn deref(&self) -> &Self::Target { fn deref(&self) -> &Self::Target {
&self.data &self.data
} }