refactor(codegen): less allocation
This commit is contained in:
@@ -1,34 +1,187 @@
|
|||||||
|
use std::fmt::{self, Write as _};
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
use itertools::Itertools as _;
|
|
||||||
|
|
||||||
use crate::ir::*;
|
use crate::ir::*;
|
||||||
|
|
||||||
pub(crate) fn compile(expr: &Ir, ctx: &impl CodegenContext) -> String {
|
pub(crate) struct CodeBuffer {
|
||||||
let code = expr.compile(ctx);
|
buf: String,
|
||||||
|
}
|
||||||
|
|
||||||
let mut debug_flags = Vec::new();
|
impl fmt::Write for CodeBuffer {
|
||||||
if std::env::var("NIX_JS_DEBUG_THUNKS").is_ok() {
|
#[inline]
|
||||||
debug_flags.push("Nix.DEBUG_THUNKS.enabled=true");
|
fn write_str(&mut self, s: &str) -> fmt::Result {
|
||||||
|
self.buf.push_str(s);
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
let debug_prefix = if debug_flags.is_empty() {
|
}
|
||||||
String::new()
|
|
||||||
} else {
|
impl CodeBuffer {
|
||||||
format!("{};", debug_flags.join(";"))
|
#[inline]
|
||||||
|
fn with_capacity(capacity: usize) -> Self {
|
||||||
|
Self {
|
||||||
|
buf: String::with_capacity(capacity),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn push_str(&mut self, s: &str) {
|
||||||
|
self.buf.push_str(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn into_string(self) -> String {
|
||||||
|
self.buf
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Quoted<'a>(&'a str);
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn quoted(s: &str) -> Quoted<'_> {
|
||||||
|
Quoted(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Escaped<'a>(&'a str);
|
||||||
|
|
||||||
|
impl<Ctx: CodegenContext> Compile<Ctx> for Escaped<'_> {
|
||||||
|
fn compile(&self, _ctx: &Ctx, buf: &mut CodeBuffer) {
|
||||||
|
for c in self.0.chars() {
|
||||||
|
let _ = match c {
|
||||||
|
'\\' => buf.write_str("\\\\"),
|
||||||
|
'"' => buf.write_str("\\\""),
|
||||||
|
'\n' => buf.write_str("\\n"),
|
||||||
|
'\r' => buf.write_str("\\r"),
|
||||||
|
'\t' => buf.write_str("\\t"),
|
||||||
|
_ => buf.write_char(c),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn escaped(s: &str) -> Escaped<'_> {
|
||||||
|
Escaped(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Joined<I, F> {
|
||||||
|
items: I,
|
||||||
|
sep: &'static str,
|
||||||
|
write_fn: F,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn joined<Ctx: CodegenContext, I: Iterator, F: Fn(&Ctx, &mut CodeBuffer, I::Item)>(
|
||||||
|
items: I,
|
||||||
|
sep: &'static str,
|
||||||
|
write_fn: F,
|
||||||
|
) -> Joined<I, F> {
|
||||||
|
Joined {
|
||||||
|
items,
|
||||||
|
sep,
|
||||||
|
write_fn,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! code {
|
||||||
|
($buf:expr, $ctx:expr; $($item:expr),* $(,)?) => {{
|
||||||
|
$(
|
||||||
|
($item).compile($ctx, $buf);
|
||||||
|
)*
|
||||||
|
}};
|
||||||
|
|
||||||
|
($buf:expr, $ctx:expr; $($item:expr)*) => {{
|
||||||
|
$(
|
||||||
|
($item).compile($ctx, $buf);
|
||||||
|
)*
|
||||||
|
}};
|
||||||
|
|
||||||
|
($buf:expr, $fmt:literal, $($arg:tt)*) => {
|
||||||
|
write!($buf, $fmt, $($arg)*).unwrap()
|
||||||
};
|
};
|
||||||
|
|
||||||
let cur_dir = ctx.get_current_dir().display().to_string().escape_quote();
|
($buf:expr, $lit:literal) => {
|
||||||
format!(
|
$buf.push_str($lit)
|
||||||
"(()=>{{{}Nix.builtins.storeDir={};const currentDir={};return {}}})()",
|
};
|
||||||
debug_prefix,
|
}
|
||||||
ctx.get_store_dir().escape_quote(),
|
|
||||||
cur_dir,
|
pub(crate) fn compile(expr: &Ir, ctx: &impl CodegenContext) -> String {
|
||||||
code
|
let mut buf = CodeBuffer::with_capacity(8192);
|
||||||
)
|
|
||||||
|
code!(&mut buf, ctx; "(()=>{");
|
||||||
|
|
||||||
|
if std::env::var("NIX_JS_DEBUG_THUNKS").is_ok() {
|
||||||
|
code!(&mut buf, ctx; "Nix.DEBUG_THUNKS.enabled=true;");
|
||||||
|
}
|
||||||
|
|
||||||
|
code!(&mut buf, ctx;
|
||||||
|
"Nix.builtins.storeDir="
|
||||||
|
quoted(ctx.get_store_dir())
|
||||||
|
";const currentDir="
|
||||||
|
quoted(&ctx.get_current_dir().display().to_string())
|
||||||
|
";return "
|
||||||
|
expr
|
||||||
|
"})()");
|
||||||
|
|
||||||
|
buf.into_string()
|
||||||
}
|
}
|
||||||
|
|
||||||
trait Compile<Ctx: CodegenContext> {
|
trait Compile<Ctx: CodegenContext> {
|
||||||
fn compile(&self, ctx: &Ctx) -> String;
|
fn compile(&self, ctx: &Ctx, buf: &mut CodeBuffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Ctx: CodegenContext> Compile<Ctx> for str {
|
||||||
|
fn compile(&self, _ctx: &Ctx, buf: &mut CodeBuffer) {
|
||||||
|
buf.push_str(self);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Ctx: CodegenContext> Compile<Ctx> for usize {
|
||||||
|
fn compile(&self, _ctx: &Ctx, buf: &mut CodeBuffer) {
|
||||||
|
let _ = write!(buf, "{self}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Ctx: CodegenContext> Compile<Ctx> for Quoted<'_> {
|
||||||
|
fn compile(&self, _ctx: &Ctx, buf: &mut CodeBuffer) {
|
||||||
|
buf.push_escaped_quote(self.0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Implement Compile for Span
|
||||||
|
// impl<Ctx: CodegenContext> Compile<Ctx> for Span<'_, Ctx> {
|
||||||
|
// fn compile(&self, _ctx: &Ctx, buf: &mut CodeBuffer) {
|
||||||
|
// write!(buf, "{}", self).unwrap();
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// Implement Compile for Joined
|
||||||
|
impl<Ctx: CodegenContext, I, F> Compile<Ctx> for Joined<I, F>
|
||||||
|
where
|
||||||
|
I: IntoIterator + Clone,
|
||||||
|
F: Fn(&Ctx, &mut CodeBuffer, I::Item) + Clone,
|
||||||
|
{
|
||||||
|
fn compile(&self, ctx: &Ctx, buf: &mut CodeBuffer) {
|
||||||
|
let mut iter = self.items.clone().into_iter();
|
||||||
|
if let Some(first) = iter.next() {
|
||||||
|
(self.write_fn)(ctx, buf, first);
|
||||||
|
for item in iter {
|
||||||
|
buf.push_str(self.sep);
|
||||||
|
(self.write_fn)(ctx, buf, item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Ctx: CodegenContext> Compile<Ctx> for rnix::TextRange {
|
||||||
|
fn compile(&self, ctx: &Ctx, buf: &mut CodeBuffer) {
|
||||||
|
code!(
|
||||||
|
buf,
|
||||||
|
"\"{}:{}:{}\"",
|
||||||
|
ctx.get_current_source_id(),
|
||||||
|
usize::from(self.start()),
|
||||||
|
usize::from(self.end())
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) trait CodegenContext {
|
pub(crate) trait CodegenContext {
|
||||||
@@ -40,179 +193,224 @@ pub(crate) trait CodegenContext {
|
|||||||
fn get_current_source(&self) -> crate::error::Source;
|
fn get_current_source(&self) -> crate::error::Source;
|
||||||
}
|
}
|
||||||
|
|
||||||
trait EscapeQuote {
|
impl<Ctx: CodegenContext> Compile<Ctx> for ExprId {
|
||||||
fn escape_quote(&self) -> String;
|
fn compile(&self, ctx: &Ctx, buf: &mut CodeBuffer) {
|
||||||
}
|
ctx.get_ir(*self).compile(ctx, buf);
|
||||||
|
|
||||||
impl EscapeQuote for str {
|
|
||||||
fn escape_quote(&self) -> String {
|
|
||||||
let mut escaped = String::with_capacity(self.len() + 2);
|
|
||||||
escaped.push('"');
|
|
||||||
for c in self.chars() {
|
|
||||||
match c {
|
|
||||||
'\\' => escaped.push_str("\\\\"),
|
|
||||||
'\"' => escaped.push_str("\\\""),
|
|
||||||
'\n' => escaped.push_str("\\n"),
|
|
||||||
'\r' => escaped.push_str("\\r"),
|
|
||||||
'\t' => escaped.push_str("\\t"),
|
|
||||||
_ => escaped.push(c),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
escaped.push('"');
|
|
||||||
escaped
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn encode_span(span: rnix::TextRange, ctx: &impl CodegenContext) -> String {
|
|
||||||
format!(
|
|
||||||
"\"{}:{}:{}\"",
|
|
||||||
ctx.get_current_source_id(),
|
|
||||||
usize::from(span.start()),
|
|
||||||
usize::from(span.end())
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<Ctx: CodegenContext> Compile<Ctx> for Ir {
|
impl<Ctx: CodegenContext> Compile<Ctx> for Ir {
|
||||||
fn compile(&self, ctx: &Ctx) -> String {
|
fn compile(&self, ctx: &Ctx, buf: &mut CodeBuffer) {
|
||||||
match self {
|
match self {
|
||||||
Ir::Int(int) => format!("{}n", int.inner), // Generate BigInt literal
|
Ir::Int(int) => {
|
||||||
Ir::Float(float) => float.inner.to_string(),
|
code!(buf, "{}n", int.inner);
|
||||||
Ir::Bool(bool) => bool.inner.to_string(),
|
}
|
||||||
Ir::Null(_) => "null".to_string(),
|
Ir::Float(float) => {
|
||||||
Ir::Str(s) => s.val.escape_quote(),
|
code!(buf, "{}", float.inner);
|
||||||
|
}
|
||||||
|
Ir::Bool(bool) => {
|
||||||
|
code!(buf, "{}", bool.inner);
|
||||||
|
}
|
||||||
|
Ir::Null(_) => {
|
||||||
|
code!(buf, ctx; "null");
|
||||||
|
}
|
||||||
|
Ir::Str(s) => {
|
||||||
|
code!(buf, ctx; quoted(&s.val));
|
||||||
|
}
|
||||||
Ir::Path(p) => {
|
Ir::Path(p) => {
|
||||||
// Path needs runtime resolution
|
code!(buf, ctx; "Nix.resolvePath(currentDir," ctx.get_ir(p.expr) ")");
|
||||||
let path_expr = ctx.get_ir(p.expr).compile(ctx);
|
|
||||||
format!("Nix.resolvePath(currentDir,{})", path_expr)
|
|
||||||
}
|
}
|
||||||
&Ir::If(If {
|
Ir::If(x) => x.compile(ctx, buf),
|
||||||
cond,
|
Ir::BinOp(x) => x.compile(ctx, buf),
|
||||||
consq,
|
Ir::UnOp(x) => x.compile(ctx, buf),
|
||||||
alter,
|
Ir::Func(x) => x.compile(ctx, buf),
|
||||||
span: _,
|
Ir::AttrSet(x) => x.compile(ctx, buf),
|
||||||
}) => {
|
Ir::List(x) => x.compile(ctx, buf),
|
||||||
let cond_code = ctx.get_ir(cond).compile(ctx);
|
Ir::Call(x) => x.compile(ctx, buf),
|
||||||
let consq = ctx.get_ir(consq).compile(ctx);
|
Ir::Arg(x) => {
|
||||||
let alter = ctx.get_ir(alter).compile(ctx);
|
code!(buf, "arg{}", x.inner.0);
|
||||||
let cond_span = encode_span(ctx.get_ir(cond).span(), ctx);
|
|
||||||
format!(
|
|
||||||
"(Nix.withContext(\"while evaluating a branch condition\",{},()=>Nix.forceBool({})))?({}):({})",
|
|
||||||
cond_span, cond_code, consq, alter
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
Ir::BinOp(x) => x.compile(ctx),
|
Ir::TopLevel(x) => x.compile(ctx, buf),
|
||||||
Ir::UnOp(x) => x.compile(ctx),
|
Ir::Select(x) => x.compile(ctx, buf),
|
||||||
Ir::Func(x) => x.compile(ctx),
|
|
||||||
Ir::AttrSet(x) => x.compile(ctx),
|
|
||||||
Ir::List(x) => x.compile(ctx),
|
|
||||||
Ir::Call(x) => x.compile(ctx),
|
|
||||||
Ir::Arg(x) => format!("arg{}", x.inner.0),
|
|
||||||
Ir::TopLevel(x) => x.compile(ctx),
|
|
||||||
Ir::Select(x) => x.compile(ctx),
|
|
||||||
&Ir::Thunk(Thunk { inner: expr_id, .. }) => {
|
&Ir::Thunk(Thunk { inner: expr_id, .. }) => {
|
||||||
format!("expr{}", expr_id.0)
|
code!(buf, "expr{}", expr_id.0);
|
||||||
|
}
|
||||||
|
Ir::Builtins(_) => {
|
||||||
|
code!(buf, ctx; "Nix.builtins");
|
||||||
}
|
}
|
||||||
Ir::Builtins(_) => "Nix.builtins".to_string(),
|
|
||||||
&Ir::Builtin(Builtin { inner: name, .. }) => {
|
&Ir::Builtin(Builtin { inner: name, .. }) => {
|
||||||
format!("Nix.builtins[{}]", ctx.get_sym(name).escape_quote())
|
code!(buf, ctx;
|
||||||
|
"Nix.builtins[",
|
||||||
|
quoted(ctx.get_sym(name)),
|
||||||
|
"]"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
Ir::ConcatStrings(x) => x.compile(ctx),
|
Ir::ConcatStrings(x) => x.compile(ctx, buf),
|
||||||
Ir::HasAttr(x) => x.compile(ctx),
|
Ir::HasAttr(x) => x.compile(ctx, buf),
|
||||||
&Ir::Assert(Assert {
|
&Ir::Assert(Assert {
|
||||||
assertion,
|
assertion,
|
||||||
expr,
|
expr,
|
||||||
ref assertion_raw,
|
ref assertion_raw,
|
||||||
span,
|
span: assert_span,
|
||||||
}) => {
|
}) => {
|
||||||
let assertion_code = ctx.get_ir(assertion).compile(ctx);
|
let assertion_ir = ctx.get_ir(assertion);
|
||||||
let expr = ctx.get_ir(expr).compile(ctx);
|
let assertion_span = assertion_ir.span();
|
||||||
let assertion_span = encode_span(ctx.get_ir(assertion).span(), ctx);
|
|
||||||
let span = encode_span(span, ctx);
|
code!(buf, ctx;
|
||||||
format!(
|
"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_code,
|
",()=>(",
|
||||||
expr,
|
assertion_ir,
|
||||||
assertion_raw.escape_quote(),
|
")),",
|
||||||
span
|
ctx.get_ir(expr),
|
||||||
)
|
",",
|
||||||
|
quoted(assertion_raw),
|
||||||
|
",",
|
||||||
|
assert_span,
|
||||||
|
")"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
Ir::CurPos(cur_pos) => {
|
Ir::CurPos(cur_pos) => {
|
||||||
let span_str = encode_span(cur_pos.span, ctx);
|
code!(buf, ctx;
|
||||||
format!("Nix.mkPos({})", span_str)
|
"Nix.mkPos(",
|
||||||
|
cur_pos.span,
|
||||||
|
")"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<Ctx: CodegenContext> Compile<Ctx> for If {
|
||||||
|
fn compile(&self, ctx: &Ctx, buf: &mut CodeBuffer) {
|
||||||
|
let &If {
|
||||||
|
cond,
|
||||||
|
consq,
|
||||||
|
alter,
|
||||||
|
span: _,
|
||||||
|
} = self;
|
||||||
|
let cond_ir = ctx.get_ir(cond);
|
||||||
|
let cond_span = cond_ir.span();
|
||||||
|
|
||||||
|
code!(buf, ctx;
|
||||||
|
"(Nix.withContext(\"while evaluating a branch condition\"," cond_span ",()=>Nix.forceBool(" cond_ir ")))"
|
||||||
|
"?(" consq "):(" alter ")"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<Ctx: CodegenContext> Compile<Ctx> for BinOp {
|
impl<Ctx: CodegenContext> Compile<Ctx> for BinOp {
|
||||||
fn compile(&self, ctx: &Ctx) -> String {
|
fn compile(&self, ctx: &Ctx, buf: &mut CodeBuffer) {
|
||||||
use BinOpKind::*;
|
use BinOpKind::*;
|
||||||
|
|
||||||
let lhs = ctx.get_ir(self.lhs).compile(ctx);
|
let lhs = ctx.get_ir(self.lhs);
|
||||||
let rhs = ctx.get_ir(self.rhs).compile(ctx);
|
let rhs = ctx.get_ir(self.rhs);
|
||||||
|
|
||||||
let with_ctx = |op_name: &str, op_call: String| {
|
|
||||||
let span = encode_span(self.span, ctx);
|
|
||||||
format!(
|
|
||||||
"Nix.withContext(\"while evaluating the {} operator\",{},()=>({}))",
|
|
||||||
op_name, span, op_call
|
|
||||||
)
|
|
||||||
};
|
|
||||||
|
|
||||||
match self.kind {
|
match self.kind {
|
||||||
Add => with_ctx("+", format!("Nix.op.add({},{})", lhs, rhs)),
|
Add | Sub | Mul | Div | Eq | Neq | Lt | Gt | Leq | Geq | Con | Upd => {
|
||||||
Sub => with_ctx("-", format!("Nix.op.sub({},{})", lhs, rhs)),
|
let op_name = match self.kind {
|
||||||
Mul => with_ctx("*", format!("Nix.op.mul({},{})", lhs, rhs)),
|
Add => "+",
|
||||||
Div => with_ctx("/", format!("Nix.op.div({},{})", lhs, rhs)),
|
Sub => "-",
|
||||||
Eq => with_ctx("==", format!("Nix.op.eq({},{})", lhs, rhs)),
|
Mul => "*",
|
||||||
Neq => with_ctx("!=", format!("Nix.op.neq({},{})", lhs, rhs)),
|
Div => "/",
|
||||||
Lt => with_ctx("<", format!("Nix.op.lt({},{})", lhs, rhs)),
|
Eq => "==",
|
||||||
Gt => with_ctx(">", format!("Nix.op.gt({},{})", lhs, rhs)),
|
Neq => "!=",
|
||||||
Leq => with_ctx("<=", format!("Nix.op.lte({},{})", lhs, rhs)),
|
Lt => "<",
|
||||||
Geq => with_ctx(">=", format!("Nix.op.gte({},{})", lhs, rhs)),
|
Gt => ">",
|
||||||
// Short-circuit operators: use JavaScript native && and ||
|
Leq => "<=",
|
||||||
And => with_ctx(
|
Geq => ">=",
|
||||||
"&&",
|
Con => "++",
|
||||||
format!("Nix.forceBool({})&&Nix.forceBool({})", lhs, rhs),
|
Upd => "//",
|
||||||
),
|
_ => unreachable!(),
|
||||||
Or => with_ctx(
|
};
|
||||||
"||",
|
let op_func = match self.kind {
|
||||||
format!("Nix.forceBool({})||Nix.forceBool({})", lhs, rhs),
|
Add => "Nix.op.add",
|
||||||
),
|
Sub => "Nix.op.sub",
|
||||||
Impl => with_ctx(
|
Mul => "Nix.op.mul",
|
||||||
"->",
|
Div => "Nix.op.div",
|
||||||
format!("(!Nix.forceBool({})||Nix.forceBool({}))", lhs, rhs),
|
Eq => "Nix.op.eq",
|
||||||
),
|
Neq => "Nix.op.neq",
|
||||||
Con => with_ctx("++", format!("Nix.op.concat({},{})", lhs, rhs)),
|
Lt => "Nix.op.lt",
|
||||||
Upd => with_ctx("//", format!("Nix.op.update({},{})", lhs, rhs)),
|
Gt => "Nix.op.gt",
|
||||||
PipeL => format!("Nix.call({},{})", rhs, lhs),
|
Leq => "Nix.op.lte",
|
||||||
PipeR => format!("Nix.call({},{})", lhs, rhs),
|
Geq => "Nix.op.gte",
|
||||||
|
Con => "Nix.op.concat",
|
||||||
|
Upd => "Nix.op.update",
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
|
||||||
|
code!(
|
||||||
|
buf, ctx;
|
||||||
|
"Nix.withContext(\"while evaluating the " op_name " operator\"," self.span ",()=>(" op_func "(" lhs "," rhs ")))"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
And => {
|
||||||
|
code!(
|
||||||
|
buf, ctx;
|
||||||
|
"Nix.withContext(\"while evaluating the && operator\"," self.span ",()=>(Nix.forceBool(" lhs ")&&Nix.forceBool(" rhs ")))"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Or => {
|
||||||
|
code!(
|
||||||
|
buf, ctx;
|
||||||
|
"Nix.withContext(\"while evaluating the || operator\"," self.span ",()=>(Nix.forceBool(" lhs ")||Nix.forceBool(" rhs ")))"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Impl => {
|
||||||
|
code!(
|
||||||
|
buf, ctx;
|
||||||
|
"Nix.withContext(\"while evaluating the || operator\"," self.span ",()=>(Nix.forceBool(" lhs ")||Nix.forceBool(" rhs ")))"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
PipeL => {
|
||||||
|
code!(buf, ctx;
|
||||||
|
"Nix.call(",
|
||||||
|
rhs,
|
||||||
|
",",
|
||||||
|
lhs,
|
||||||
|
")"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
PipeR => {
|
||||||
|
code!(buf, ctx;
|
||||||
|
"Nix.call(",
|
||||||
|
lhs,
|
||||||
|
",",
|
||||||
|
rhs,
|
||||||
|
")"
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Ctx: CodegenContext> Compile<Ctx> for UnOp {
|
impl<Ctx: CodegenContext> Compile<Ctx> for UnOp {
|
||||||
fn compile(&self, ctx: &Ctx) -> String {
|
fn compile(&self, ctx: &Ctx, buf: &mut CodeBuffer) {
|
||||||
use UnOpKind::*;
|
use UnOpKind::*;
|
||||||
let rhs = ctx.get_ir(self.rhs).compile(ctx);
|
|
||||||
match self.kind {
|
match self.kind {
|
||||||
Neg => format!("Nix.op.sub(0n,{rhs})"),
|
Neg => {
|
||||||
Not => format!("Nix.op.bnot({rhs})"),
|
code!(buf, ctx;
|
||||||
|
"Nix.op.sub(0n,",
|
||||||
|
ctx.get_ir(self.rhs),
|
||||||
|
")"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Not => {
|
||||||
|
code!(buf, ctx;
|
||||||
|
"Nix.op.bnot(",
|
||||||
|
ctx.get_ir(self.rhs),
|
||||||
|
")"
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Ctx: CodegenContext> Compile<Ctx> for Func {
|
impl<Ctx: CodegenContext> Compile<Ctx> for Func {
|
||||||
fn compile(&self, ctx: &Ctx) -> String {
|
fn compile(&self, ctx: &Ctx, buf: &mut CodeBuffer) {
|
||||||
let id = ctx.get_ir(self.arg).as_ref().unwrap_arg().inner.0;
|
let id = ctx.get_ir(self.arg).as_ref().unwrap_arg().inner.0;
|
||||||
let thunk_defs = compile_thunks(&self.thunks, ctx);
|
|
||||||
let body_code = ctx.get_ir(self.body).compile(ctx);
|
let has_thunks = !self.thunks.is_empty();
|
||||||
let body = if thunk_defs.is_empty() {
|
|
||||||
body_code
|
|
||||||
} else {
|
|
||||||
format!("{{{}return {}}}", thunk_defs, body_code)
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(Param {
|
if let Some(Param {
|
||||||
required,
|
required,
|
||||||
@@ -220,199 +418,230 @@ impl<Ctx: CodegenContext> Compile<Ctx> for Func {
|
|||||||
ellipsis,
|
ellipsis,
|
||||||
}) = &self.param
|
}) = &self.param
|
||||||
{
|
{
|
||||||
let mut required = required.iter().map(|&sym| ctx.get_sym(sym).escape_quote());
|
code!(buf, "Nix.mkFunction(arg{}=>", id);
|
||||||
let required = format!("[{}]", required.join(","));
|
if has_thunks {
|
||||||
let mut optional = optional.iter().map(|&sym| ctx.get_sym(sym).escape_quote());
|
code!(buf, ctx; "{", &self.thunks, "return ", self.body, "}");
|
||||||
let optional = format!("[{}]", optional.join(","));
|
|
||||||
if thunk_defs.is_empty() {
|
|
||||||
format!("Nix.mkFunction(arg{id}=>({body}),{required},{optional},{ellipsis})")
|
|
||||||
} else {
|
} else {
|
||||||
format!("Nix.mkFunction(arg{id}=>{body},{required},{optional},{ellipsis})")
|
code!(buf, ctx; "(", self.body, ")");
|
||||||
}
|
}
|
||||||
|
code!(buf, ctx;
|
||||||
|
",["
|
||||||
|
joined(required.iter(), ",", |ctx: &Ctx, buf, &sym| {
|
||||||
|
code!(buf, ctx; quoted(ctx.get_sym(sym)));
|
||||||
|
})
|
||||||
|
"],["
|
||||||
|
joined(optional.iter(), ",", |ctx: &Ctx, buf, &sym| {
|
||||||
|
code!(buf, ctx; quoted(ctx.get_sym(sym)));
|
||||||
|
})
|
||||||
|
"],"
|
||||||
|
);
|
||||||
|
code!(buf, "{})", ellipsis);
|
||||||
} else {
|
} else {
|
||||||
if thunk_defs.is_empty() {
|
code!(buf, "arg{}=>", id);
|
||||||
format!("arg{id}=>({body})")
|
if has_thunks {
|
||||||
|
code!(buf, ctx; "{", &self.thunks, "return ", self.body, "}");
|
||||||
} else {
|
} else {
|
||||||
format!("arg{id}=>{body}")
|
code!(buf, ctx; "(", self.body, ")");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Ctx: CodegenContext> Compile<Ctx> for Call {
|
impl<Ctx: CodegenContext> Compile<Ctx> for Call {
|
||||||
fn compile(&self, ctx: &Ctx) -> String {
|
fn compile(&self, ctx: &Ctx, buf: &mut CodeBuffer) {
|
||||||
let func = ctx.get_ir(self.func).compile(ctx);
|
code!(buf, ctx;
|
||||||
let arg = ctx.get_ir(self.arg).compile(ctx);
|
"Nix.call(",
|
||||||
let span_str = encode_span(self.span, ctx);
|
ctx.get_ir(self.func),
|
||||||
format!("Nix.call({func},{arg},{span_str})")
|
",",
|
||||||
|
ctx.get_ir(self.arg),
|
||||||
|
",",
|
||||||
|
self.span,
|
||||||
|
")"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn compile_thunks<Ctx: CodegenContext>(thunks: &[(ExprId, ExprId)], ctx: &Ctx) -> String {
|
impl<Ctx: CodegenContext> Compile<Ctx> for [(ExprId, ExprId)] {
|
||||||
if thunks.is_empty() {
|
fn compile(&self, ctx: &Ctx, buf: &mut CodeBuffer) {
|
||||||
return String::new();
|
if self.is_empty() {
|
||||||
}
|
return;
|
||||||
thunks
|
}
|
||||||
.iter()
|
|
||||||
.map(|&(slot, inner)| {
|
for &(slot, inner) in self {
|
||||||
let inner_code = ctx.get_ir(inner).compile(ctx);
|
let inner_ir = ctx.get_ir(inner);
|
||||||
let inner_span = ctx.get_ir(inner).span();
|
let inner_span = inner_ir.span();
|
||||||
format!(
|
|
||||||
"let expr{}=Nix.createThunk(()=>({}),\"expr{} {}:{}:{}\")",
|
code!(buf, "let expr{}=Nix.createThunk(()=>(", slot.0);
|
||||||
slot.0,
|
inner_ir.compile(ctx, buf);
|
||||||
inner_code,
|
code!(
|
||||||
|
buf,
|
||||||
|
"),\"expr{} {}:{}:{}\");",
|
||||||
slot.0,
|
slot.0,
|
||||||
ctx.get_current_source().get_name(),
|
ctx.get_current_source().get_name(),
|
||||||
usize::from(inner_span.start()),
|
usize::from(inner_span.start()),
|
||||||
usize::from(inner_span.end())
|
usize::from(inner_span.end())
|
||||||
)
|
);
|
||||||
})
|
}
|
||||||
.join(";")
|
}
|
||||||
+ ";"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Ctx: CodegenContext> Compile<Ctx> for TopLevel {
|
impl<Ctx: CodegenContext> Compile<Ctx> for TopLevel {
|
||||||
fn compile(&self, ctx: &Ctx) -> String {
|
fn compile(&self, ctx: &Ctx, buf: &mut CodeBuffer) {
|
||||||
let thunk_defs = compile_thunks(&self.thunks, ctx);
|
if self.thunks.is_empty() {
|
||||||
let body = ctx.get_ir(self.body).compile(ctx);
|
ctx.get_ir(self.body).compile(ctx, buf);
|
||||||
if thunk_defs.is_empty() {
|
|
||||||
body
|
|
||||||
} else {
|
} else {
|
||||||
format!("(()=>{{{}return {}}})()", thunk_defs, body)
|
code!(buf, "(()=>{");
|
||||||
|
code!(buf, ctx; &self.thunks);
|
||||||
|
code!(buf, "return ");
|
||||||
|
ctx.get_ir(self.body).compile(ctx, buf);
|
||||||
|
code!(buf, "})()");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Ctx: CodegenContext> Compile<Ctx> for Select {
|
impl<Ctx: CodegenContext> Compile<Ctx> for Select {
|
||||||
fn compile(&self, ctx: &Ctx) -> String {
|
fn compile(&self, ctx: &Ctx, buf: &mut CodeBuffer) {
|
||||||
let lhs = ctx.get_ir(self.expr).compile(ctx);
|
|
||||||
let attrpath = self
|
|
||||||
.attrpath
|
|
||||||
.iter()
|
|
||||||
.map(|attr| match attr {
|
|
||||||
Attr::Str(sym, _) => ctx.get_sym(*sym).escape_quote(),
|
|
||||||
Attr::Dynamic(expr_id, _) => ctx.get_ir(*expr_id).compile(ctx),
|
|
||||||
})
|
|
||||||
.join(",");
|
|
||||||
let span_str = encode_span(self.span, ctx);
|
|
||||||
if let Some(default) = self.default {
|
if let Some(default) = self.default {
|
||||||
format!(
|
code!(buf, ctx;
|
||||||
"Nix.selectWithDefault({lhs},[{attrpath}],{},{span_str})",
|
"Nix.selectWithDefault("
|
||||||
ctx.get_ir(default).compile(ctx)
|
ctx.get_ir(self.expr)
|
||||||
)
|
",["
|
||||||
|
joined(self.attrpath.iter(), ",", |ctx: &Ctx, buf, attr| {
|
||||||
|
match attr {
|
||||||
|
Attr::Str(sym, _) => code!(buf, ctx; quoted(ctx.get_sym(*sym))),
|
||||||
|
Attr::Dynamic(expr_id, _) => code!(buf, ctx; ctx.get_ir(*expr_id)),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
"],"
|
||||||
|
ctx.get_ir(default)
|
||||||
|
","
|
||||||
|
self.span
|
||||||
|
")"
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
format!("Nix.select({lhs},[{attrpath}],{span_str})")
|
code!(buf, ctx;
|
||||||
|
"Nix.select("
|
||||||
|
ctx.get_ir(self.expr)
|
||||||
|
",["
|
||||||
|
joined(self.attrpath.iter(), ",", |ctx: &Ctx, buf, attr| {
|
||||||
|
match attr {
|
||||||
|
Attr::Str(sym, _) => code!(buf, ctx; quoted(ctx.get_sym(*sym))),
|
||||||
|
Attr::Dynamic(expr_id, _) => code!(buf, ctx; ctx.get_ir(*expr_id)),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
"],"
|
||||||
|
self.span
|
||||||
|
")"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Ctx: CodegenContext> Compile<Ctx> for AttrSet {
|
impl<Ctx: CodegenContext> Compile<Ctx> for AttrSet {
|
||||||
fn compile(&self, ctx: &Ctx) -> String {
|
fn compile(&self, ctx: &Ctx, buf: &mut CodeBuffer) {
|
||||||
let mut attrs = Vec::new();
|
|
||||||
let mut attr_positions = Vec::new();
|
|
||||||
|
|
||||||
for (&sym, &(expr, attr_span)) in &self.stcs {
|
|
||||||
let key = ctx.get_sym(sym);
|
|
||||||
let value_code = ctx.get_ir(expr).compile(ctx);
|
|
||||||
|
|
||||||
let value_span = encode_span(ctx.get_ir(expr).span(), ctx);
|
|
||||||
let value = format!(
|
|
||||||
"Nix.withContext(\"while evaluating the attribute '{}'\",{},()=>({}))",
|
|
||||||
key, value_span, value_code
|
|
||||||
);
|
|
||||||
attrs.push(format!("{}:{}", key.escape_quote(), value));
|
|
||||||
|
|
||||||
let attr_pos_str = encode_span(attr_span, ctx);
|
|
||||||
attr_positions.push(format!("{}:{}", key.escape_quote(), attr_pos_str));
|
|
||||||
}
|
|
||||||
|
|
||||||
if !self.dyns.is_empty() {
|
if !self.dyns.is_empty() {
|
||||||
let (keys, vals, dyn_spans) = self
|
code!(buf, ctx;
|
||||||
.dyns
|
"Nix.mkAttrsWithPos({"
|
||||||
.iter()
|
joined(self.stcs.iter(), ",", |ctx: &Ctx, buf, (&sym, &(expr, _))| {
|
||||||
.map(|(key, val, attr_span)| {
|
let key = ctx.get_sym(sym);
|
||||||
let key = ctx.get_ir(*key).compile(ctx);
|
let val = ctx.get_ir(expr);
|
||||||
let val_expr = ctx.get_ir(*val);
|
|
||||||
let val = val_expr.compile(ctx);
|
code!(
|
||||||
let span = val_expr.span();
|
buf, ctx;
|
||||||
let span = encode_span(span, ctx);
|
quoted(key) ":Nix.withContext(\"while evaluating the attribute '" escaped(key) "'\"," val.span() ",()=>(" val "))"
|
||||||
let val = format!(
|
|
||||||
"Nix.withContext(\"while evaluating a dynamic attribute\",{},()=>({}))",
|
|
||||||
span, val
|
|
||||||
);
|
);
|
||||||
let dyn_span_str = encode_span(*attr_span, ctx);
|
|
||||||
(key, val, dyn_span_str)
|
|
||||||
})
|
})
|
||||||
.multiunzip::<(Vec<_>, Vec<_>, Vec<_>)>();
|
"},{"
|
||||||
format!(
|
joined(self.stcs.iter(), ",", |ctx: &Ctx, buf, (&sym, &(_, span))| {
|
||||||
"Nix.mkAttrsWithPos({{{}}},{{{}}},{{dynKeys:[{}],dynVals:[{}],dynSpans:[{}]}})",
|
code!(buf, ctx; quoted(ctx.get_sym(sym)) ":" span);
|
||||||
attrs.join(","),
|
})
|
||||||
attr_positions.join(","),
|
"},{dynKeys:["
|
||||||
keys.join(","),
|
joined(self.dyns.iter(), ",", |ctx: &Ctx, buf, (key, _, _)| {
|
||||||
vals.join(","),
|
code!(buf, ctx; ctx.get_ir(*key));
|
||||||
dyn_spans.join(",")
|
})
|
||||||
)
|
"],dynVals:["
|
||||||
} else if !attr_positions.is_empty() {
|
joined(self.dyns.iter(), ",", |ctx: &Ctx, buf, (_, val, _)| {
|
||||||
format!(
|
let val = ctx.get_ir(*val);
|
||||||
"Nix.mkAttrsWithPos({{{}}},{{{}}})",
|
code!(
|
||||||
attrs.join(","),
|
buf, ctx;
|
||||||
attr_positions.join(",")
|
"Nix.withContext(\"while evaluating a dynamic attribute\"," val.span() ",()=>(" val "))"
|
||||||
)
|
);
|
||||||
|
})
|
||||||
|
"],dynSpans:["
|
||||||
|
joined(self.dyns.iter(), ",", |ctx: &Ctx, buf, (_, _, attr_span)| {
|
||||||
|
code!(buf, ctx; attr_span);
|
||||||
|
})
|
||||||
|
"]})"
|
||||||
|
);
|
||||||
|
} else if !self.stcs.is_empty() {
|
||||||
|
code!(buf, ctx;
|
||||||
|
"Nix.mkAttrsWithPos({"
|
||||||
|
joined(self.stcs.iter(), ",", |ctx: &Ctx, buf, (&sym, &(expr, _))| {
|
||||||
|
let key = ctx.get_sym(sym);
|
||||||
|
let val = ctx.get_ir(expr);
|
||||||
|
|
||||||
|
code!(
|
||||||
|
buf, ctx;
|
||||||
|
quoted(key) ":Nix.withContext(\"while evaluating the attribute '" escaped(key) "'\"," val.span() ",()=>(" val "))"
|
||||||
|
);
|
||||||
|
})
|
||||||
|
"},{"
|
||||||
|
joined(self.stcs.iter(), ",", |ctx: &Ctx, buf, (&sym, &(_, span))| {
|
||||||
|
code!(buf, ctx; quoted(ctx.get_sym(sym)) ":" span);
|
||||||
|
})
|
||||||
|
"})"
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
format!("{{{}}}", attrs.join(","))
|
code!(buf, ctx; "{}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Ctx: CodegenContext> Compile<Ctx> for List {
|
impl<Ctx: CodegenContext> Compile<Ctx> for List {
|
||||||
fn compile(&self, ctx: &Ctx) -> String {
|
fn compile(&self, ctx: &Ctx, buf: &mut CodeBuffer) {
|
||||||
let list = self
|
code!(buf, ctx;
|
||||||
.items
|
"["
|
||||||
.iter()
|
joined(self.items.iter().enumerate(), ",", |ctx: &Ctx, buf, (idx, item)| {
|
||||||
.enumerate()
|
let item = ctx.get_ir(*item);
|
||||||
.map(|(idx, item)| {
|
code!(
|
||||||
let item_code = ctx.get_ir(*item).compile(ctx);
|
buf, ctx;
|
||||||
let item_span = encode_span(ctx.get_ir(*item).span(), ctx);
|
"Nix.withContext(\"while evaluating list element " idx "\"," item.span() ",()=>(" item "))"
|
||||||
format!(
|
);
|
||||||
"Nix.withContext(\"while evaluating list element {}\",{},()=>({}))",
|
|
||||||
idx, item_span, item_code
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
.join(",");
|
"]"
|
||||||
format!("[{list}]")
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Ctx: CodegenContext> Compile<Ctx> for ConcatStrings {
|
impl<Ctx: CodegenContext> Compile<Ctx> for ConcatStrings {
|
||||||
fn compile(&self, ctx: &Ctx) -> String {
|
fn compile(&self, ctx: &Ctx, buf: &mut CodeBuffer) {
|
||||||
let parts: Vec<String> = self
|
code!(buf, ctx;
|
||||||
.parts
|
"Nix.concatStringsWithContext(["
|
||||||
.iter()
|
joined(self.parts.iter(), ",", |ctx: &Ctx, buf, part| {
|
||||||
.map(|part| {
|
let part = ctx.get_ir(*part);
|
||||||
let part_code = ctx.get_ir(*part).compile(ctx);
|
code!(
|
||||||
let part_span = encode_span(ctx.get_ir(*part).span(), ctx);
|
buf, ctx;
|
||||||
format!(
|
"Nix.withContext(\"while evaluating a path segment\"," part.span() ",()=>(" part "))"
|
||||||
"Nix.withContext(\"while evaluating a path segment\",{},()=>({}))",
|
);
|
||||||
part_span, part_code
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
.collect();
|
"])"
|
||||||
|
);
|
||||||
format!("Nix.concatStringsWithContext([{}])", parts.join(","))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Ctx: CodegenContext> Compile<Ctx> for HasAttr {
|
impl<Ctx: CodegenContext> Compile<Ctx> for HasAttr {
|
||||||
fn compile(&self, ctx: &Ctx) -> String {
|
fn compile(&self, ctx: &Ctx, buf: &mut CodeBuffer) {
|
||||||
let lhs = ctx.get_ir(self.lhs).compile(ctx);
|
code!(buf, ctx;
|
||||||
let attrpath = self
|
"Nix.hasAttr("
|
||||||
.rhs
|
ctx.get_ir(self.lhs)
|
||||||
.iter()
|
",["
|
||||||
.map(|attr| match attr {
|
joined(self.rhs.iter(), ",", |ctx: &Ctx, buf, attr| {
|
||||||
Attr::Str(sym, _) => ctx.get_sym(*sym).escape_quote(),
|
match attr {
|
||||||
Attr::Dynamic(expr_id, _) => ctx.get_ir(*expr_id).compile(ctx),
|
Attr::Str(sym, _) => code!(buf, ctx; quoted(ctx.get_sym(*sym))),
|
||||||
|
Attr::Dynamic(expr_id, _) => code!(buf, ctx; ctx.get_ir(*expr_id)),
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.join(",");
|
"])"
|
||||||
format!("Nix.hasAttr({lhs},[{attrpath}])")
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,8 +4,8 @@
|
|||||||
use hashbrown::hash_map::Entry;
|
use hashbrown::hash_map::Entry;
|
||||||
use hashbrown::{HashMap, HashSet};
|
use hashbrown::{HashMap, HashSet};
|
||||||
use itertools::Itertools as _;
|
use itertools::Itertools as _;
|
||||||
use rnix::ast;
|
|
||||||
use rnix::TextRange;
|
use rnix::TextRange;
|
||||||
|
use rnix::ast;
|
||||||
use rowan::ast::AstNode;
|
use rowan::ast::AstNode;
|
||||||
|
|
||||||
use crate::error::{Error, Result};
|
use crate::error::{Error, Result};
|
||||||
@@ -388,11 +388,11 @@ where
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if attrs_vec.len() > 1 {
|
} else if attrs_vec.len() > 1
|
||||||
if let Some(ast::Attr::Ident(ident)) = attrs_vec.first() {
|
&& let Some(ast::Attr::Ident(ident)) = attrs_vec.first()
|
||||||
let sym = ctx.new_sym(ident.to_string());
|
{
|
||||||
binding_syms.insert(sym);
|
let sym = ctx.new_sym(ident.to_string());
|
||||||
}
|
binding_syms.insert(sym);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user