Compare commits

..

8 Commits

Author SHA1 Message Date
imxyy1soope1 ee54ab8895 implement WithLookup 2026-04-04 10:05:10 +08:00
imxyy1soope1 88dc8539b5 refactor test 2026-04-04 10:05:10 +08:00
imxyy1soope1 0c517e3c18 fix: nested return 2026-04-04 10:05:10 +08:00
imxyy1soope1 310f4acc89 remove ArgId and Ir caching mechanism 2026-04-04 10:05:10 +08:00
imxyy1soope1 95eea517e4 fix force 2026-04-04 10:05:10 +08:00
imxyy1soope1 e78e62795b small changes 2026-04-04 10:05:10 +08:00
imxyy1soope1 dc96e63b7c implement ForceMode 2026-04-04 10:04:58 +08:00
imxyy1soope1 e82369695c implement Select & SelectDefault 2026-04-04 10:04:15 +08:00
8 changed files with 704 additions and 1018 deletions
+14 -36
View File
@@ -6,7 +6,7 @@ use num_enum::TryFromPrimitive;
use rnix::TextRange; use rnix::TextRange;
use string_interner::Symbol as _; use string_interner::Symbol as _;
use crate::ir::{ArgId, Attr, BinOpKind, Ir, Param, RawIrRef, StringId, ThunkId, UnOpKind}; use crate::ir::{Attr, BinOpKind, Ir, Param, RawIrRef, StringId, ThunkId, UnOpKind};
use crate::runtime::BUILTINS; use crate::runtime::BUILTINS;
use crate::runtime::value::{Null, PrimOp, StaticValue}; use crate::runtime::value::{Null, PrimOp, StaticValue};
@@ -28,7 +28,7 @@ pub(crate) trait BytecodeContext {
} }
#[repr(u8)] #[repr(u8)]
#[derive(Clone, Copy, TryFromPrimitive)] #[derive(Debug, Clone, Copy, TryFromPrimitive)]
#[allow(clippy::enum_variant_names)] #[allow(clippy::enum_variant_names)]
pub enum Op { pub enum Op {
PushSmi, PushSmi,
@@ -102,7 +102,6 @@ pub enum Op {
struct ScopeInfo { struct ScopeInfo {
depth: u16, depth: u16,
arg_id: Option<ArgId>,
thunk_map: HashMap<ThunkId, u32>, thunk_map: HashMap<ThunkId, u32>,
} }
@@ -161,10 +160,10 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
let (layer, local) = self.resolve_thunk(id); let (layer, local) = self.resolve_thunk(id);
InlineOperand::Local { layer, local } InlineOperand::Local { layer, local }
} }
&Ir::Arg(id) => { &Ir::Arg { layer } => InlineOperand::Local {
let (layer, local) = self.resolve_arg(id); layer: layer.try_into().expect("scope too deep!"),
InlineOperand::Local { layer, local } local: 0,
} },
&Ir::Builtin(id) => { &Ir::Builtin(id) => {
let arity = BUILTINS[id as usize].1; let arity = BUILTINS[id as usize].1;
InlineOperand::Const(StaticValue::new_inline(PrimOp { id, arity })) InlineOperand::Const(StaticValue::new_inline(PrimOp { id, arity }))
@@ -286,16 +285,6 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
panic!("ThunkId {:?} not found in any scope", id); panic!("ThunkId {:?} not found in any scope", id);
} }
fn resolve_arg(&self, id: ArgId) -> (u16, u32) {
for scope in self.scope_stack.iter().rev() {
if scope.arg_id == Some(id) {
let layer = self.current_depth() - scope.depth;
return (layer, 0);
}
}
panic!("ArgId {:?} not found in any scope", id);
}
fn emit_load(&mut self, layer: u16, local: u32) { fn emit_load(&mut self, layer: u16, local: u32) {
if layer == 0 { if layer == 0 {
self.emit_op(Op::LoadLocal); self.emit_op(Op::LoadLocal);
@@ -433,7 +422,7 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
} }
} }
fn push_scope(&mut self, has_arg: bool, arg_id: Option<ArgId>, thunk_ids: &[ThunkId]) { fn push_scope(&mut self, has_arg: bool, thunk_ids: &[ThunkId]) {
let depth = self.scope_stack.len() as u16; let depth = self.scope_stack.len() as u16;
let thunk_base = if has_arg { 1u32 } else { 0u32 }; let thunk_base = if has_arg { 1u32 } else { 0u32 };
let thunk_map = thunk_ids let thunk_map = thunk_ids
@@ -441,11 +430,7 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
.enumerate() .enumerate()
.map(|(i, &id)| (id, thunk_base + i as u32)) .map(|(i, &id)| (id, thunk_base + i as u32))
.collect(); .collect();
self.scope_stack.push(ScopeInfo { self.scope_stack.push(ScopeInfo { depth, thunk_map });
depth,
arg_id,
thunk_map,
});
} }
fn pop_scope(&mut self) { fn pop_scope(&mut self) {
@@ -461,7 +446,7 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
let all_thunks = self.collect_all_thunks(thunks, body); let all_thunks = self.collect_all_thunks(thunks, body);
let thunk_ids: Vec<ThunkId> = all_thunks.iter().map(|&(id, _)| id).collect(); let thunk_ids: Vec<ThunkId> = all_thunks.iter().map(|&(id, _)| id).collect();
self.push_scope(false, None, &thunk_ids); self.push_scope(false, &thunk_ids);
if total_slots > 0 { if total_slots > 0 {
self.emit_op(Op::AllocLocals); self.emit_op(Op::AllocLocals);
@@ -475,7 +460,7 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
self.pop_scope(); self.pop_scope();
} }
_ => { _ => {
self.push_scope(false, None, &[]); self.push_scope(false, &[]);
self.emit_expr(ir); self.emit_expr(ir);
self.emit_op(Op::Return); self.emit_op(Op::Return);
self.pop_scope(); self.pop_scope();
@@ -485,9 +470,6 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
fn emit_scope_thunks(&mut self, thunks: &[(ThunkId, RawIrRef<'_>)]) { fn emit_scope_thunks(&mut self, thunks: &[(ThunkId, RawIrRef<'_>)]) {
for &(id, inner) in thunks { for &(id, inner) in thunks {
let label = format!("e{}", id.0);
let label_idx = self.ctx.intern_string(&label);
let skip_patch = self.emit_jump_placeholder(); let skip_patch = self.emit_jump_placeholder();
let entry_point = self.ctx.get_code_mut().len() as u32; let entry_point = self.ctx.get_code_mut().len() as u32;
self.emit_expr(inner); self.emit_expr(inner);
@@ -495,7 +477,6 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
self.patch_jump_target(skip_patch); self.patch_jump_target(skip_patch);
self.emit_op(Op::MakeThunk); self.emit_op(Op::MakeThunk);
self.emit_u32(entry_point); self.emit_u32(entry_point);
self.emit_str_id(label_idx);
let (_, local_idx) = self.resolve_thunk(id); let (_, local_idx) = self.resolve_thunk(id);
self.emit_op(Op::StoreLocal); self.emit_op(Op::StoreLocal);
self.emit_u32(local_idx); self.emit_u32(local_idx);
@@ -566,10 +547,9 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
&Ir::Func { &Ir::Func {
body, body,
ref param, ref param,
arg,
ref thunks, ref thunks,
} => { } => {
self.emit_func(arg, thunks, param, body); self.emit_func(thunks, param, body);
} }
Ir::AttrSet { stcs, dyns } => { Ir::AttrSet { stcs, dyns } => {
self.emit_attrset(stcs, dyns); self.emit_attrset(stcs, dyns);
@@ -593,9 +573,8 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
self.emit_op(Op::Call); self.emit_op(Op::Call);
self.emit_u32(span_id); self.emit_u32(span_id);
} }
&Ir::Arg(id) => { &Ir::Arg { layer } => {
let (layer, local) = self.resolve_arg(id); self.emit_load(layer.try_into().expect("scope too deep!"), 0);
self.emit_load(layer, local);
} }
&Ir::TopLevel { body, ref thunks } => { &Ir::TopLevel { body, ref thunks } => {
self.emit_toplevel_inner(body, thunks); self.emit_toplevel_inner(body, thunks);
@@ -777,7 +756,6 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
fn emit_func( fn emit_func(
&mut self, &mut self,
arg: ArgId,
thunks: &[(ThunkId, RawIrRef<'_>)], thunks: &[(ThunkId, RawIrRef<'_>)],
param: &Option<Param<'_>>, param: &Option<Param<'_>>,
body: RawIrRef<'_>, body: RawIrRef<'_>,
@@ -790,7 +768,7 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
let skip_patch = self.emit_jump_placeholder(); let skip_patch = self.emit_jump_placeholder();
let entry_point = self.ctx.get_code().len() as u32; let entry_point = self.ctx.get_code().len() as u32;
self.push_scope(true, Some(arg), &thunk_ids); self.push_scope(true, &thunk_ids);
self.emit_scope_thunks(thunks); self.emit_scope_thunks(thunks);
self.emit_expr(body); self.emit_expr(body);
self.emit_op(Op::Return); self.emit_op(Op::Return);
+12 -6
View File
@@ -140,15 +140,23 @@ impl<'a, Ctx: DisassemblerContext> Disassembler<'a, Ctx> {
temp temp
}; };
let extra_width = if start_pos > 0 { start_pos.ilog2() >> 4 } else { 0 }; let extra_width = if start_pos > 0 {
start_pos.ilog2() >> 4
} else {
0
};
if color { if color {
let _ = write!(out, " "); let _ = write!(out, " ");
for _ in 0..extra_width { let _ = write!(out, " "); } for _ in 0..extra_width {
let _ = write!(out, " ");
}
let _ = writeln!(out, " {:<14} |", bytes_str.green()); let _ = writeln!(out, " {:<14} |", bytes_str.green());
} else { } else {
let _ = write!(out, " "); let _ = write!(out, " ");
for _ in 0..extra_width { let _ = write!(out, " "); } for _ in 0..extra_width {
let _ = write!(out, " ");
}
let _ = writeln!(out, " {:<14} |", bytes_str); let _ = writeln!(out, " {:<14} |", bytes_str);
} }
} }
@@ -208,9 +216,7 @@ impl<'a, Ctx: DisassemblerContext> Disassembler<'a, Ctx> {
Op::MakeThunk => { Op::MakeThunk => {
let offset = self.read_u32(); let offset = self.read_u32();
let label_idx = self.read_u32(); ("MakeThunk", format!("-> {:04x}", offset))
let label = self.ctx.lookup_string(label_idx);
("MakeThunk", format!("-> {:04x} label={}", offset, label))
} }
Op::MakeClosure => { Op::MakeClosure => {
let offset = self.read_u32(); let offset = self.read_u32();
+8 -15
View File
@@ -48,7 +48,6 @@ impl<T: Sized> BoxIn for T {}
pub trait DowngradeContext<'id: 'ir, 'ir> { pub trait DowngradeContext<'id: 'ir, 'ir> {
fn new_expr(&self, expr: Ir<'ir, IrRef<'id, 'ir>>) -> IrRef<'id, 'ir>; fn new_expr(&self, expr: Ir<'ir, IrRef<'id, 'ir>>) -> IrRef<'id, 'ir>;
fn new_arg(&mut self) -> ArgId;
fn maybe_thunk(&mut self, ir: IrRef<'id, 'ir>) -> IrRef<'id, 'ir>; fn maybe_thunk(&mut self, ir: IrRef<'id, 'ir>) -> IrRef<'id, 'ir>;
fn new_sym(&mut self, sym: String) -> StringId; fn new_sym(&mut self, sym: String) -> StringId;
@@ -57,7 +56,7 @@ pub trait DowngradeContext<'id: 'ir, 'ir> {
fn get_current_source(&self) -> Source; fn get_current_source(&self) -> Source;
fn with_param_scope<F, R>(&mut self, param: StringId, arg: ArgId, f: F) -> R fn with_param_scope<F, R>(&mut self, param: StringId, f: F) -> R
where where
F: FnOnce(&mut Self) -> R; F: FnOnce(&mut Self) -> R;
fn with_let_scope<F, R>(&mut self, bindings: &[StringId], f: F) -> Result<R> fn with_let_scope<F, R>(&mut self, bindings: &[StringId], f: F) -> Result<R>
@@ -195,9 +194,7 @@ impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> fo
&& matches!(normalized.first(), Some(ast::InterpolPart::Literal(_))); && matches!(normalized.first(), Some(ast::InterpolPart::Literal(_)));
let bump = ctx.bump(); let bump = ctx.bump();
let parts = normalized let mut parts = normalized.into_iter().map(|part| match part {
.into_iter()
.map(|part| match part {
ast::InterpolPart::Literal(lit) => Ok(ctx.new_expr(Ir::Str(lit.box_in(bump)))), ast::InterpolPart::Literal(lit) => Ok(ctx.new_expr(Ir::Str(lit.box_in(bump)))),
ast::InterpolPart::Interpolation(interpol) => { ast::InterpolPart::Interpolation(interpol) => {
let inner = interpol let inner = interpol
@@ -206,12 +203,12 @@ impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> fo
.downgrade(ctx)?; .downgrade(ctx)?;
Ok(ctx.maybe_thunk(inner)) Ok(ctx.maybe_thunk(inner))
} }
}) });
.collect_in::<Result<Vec<'ir, _>>>(bump)?;
Ok(if is_single_literal { Ok(if is_single_literal {
parts.into_iter().next().expect("is_single_literal checked") parts.next().expect("is_single_literal checked")?
} else { } else {
let parts = parts.collect_in::<Result<_>>(bump)?;
ctx.new_expr(Ir::ConcatStrings { ctx.new_expr(Ir::ConcatStrings {
parts, parts,
force_string: true, force_string: true,
@@ -382,7 +379,6 @@ impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> fo
let span = self.syntax().text_range(); let span = self.syntax().text_range();
let raw_param = self.param().require(ctx, span)?; let raw_param = self.param().require(ctx, span)?;
let body_ast = self.body().require(ctx, span)?; let body_ast = self.body().require(ctx, span)?;
let arg = ctx.new_arg();
struct Ret<'id, 'ir> { struct Ret<'id, 'ir> {
param: Option<Param<'ir>>, param: Option<Param<'ir>>,
@@ -398,8 +394,7 @@ impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> fo
let param_sym = ctx.new_sym(id.to_string()); let param_sym = ctx.new_sym(id.to_string());
param = None; param = None;
body = ctx body = ctx.with_param_scope(param_sym, |ctx| body_ast.downgrade(ctx))?;
.with_param_scope(param_sym, arg, |ctx| body_ast.clone().downgrade(ctx))?;
} }
ast::Param::Pattern(pattern) => { ast::Param::Pattern(pattern) => {
let alias = pattern let alias = pattern
@@ -417,7 +412,7 @@ impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> fo
body: inner_body, body: inner_body,
required, required,
optional, optional,
} = downgrade_pattern_bindings(pat_entries, alias, arg, ctx, |ctx, _| { } = downgrade_pattern_bindings(pat_entries, alias, ctx, |ctx, _| {
body_ast.clone().downgrade(ctx) body_ast.clone().downgrade(ctx)
})?; })?;
@@ -438,7 +433,6 @@ impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> fo
Ok(ctx.new_expr(Ir::Func { Ok(ctx.new_expr(Ir::Func {
body, body,
param, param,
arg,
thunks, thunks,
})) }))
} }
@@ -938,14 +932,12 @@ struct PatternBindings<'id, 'ir> {
fn downgrade_pattern_bindings<'id, 'ir, Ctx>( fn downgrade_pattern_bindings<'id, 'ir, Ctx>(
pat_entries: impl Iterator<Item = ast::PatEntry>, pat_entries: impl Iterator<Item = ast::PatEntry>,
alias: Option<StringId>, alias: Option<StringId>,
arg: ArgId,
ctx: &mut Ctx, ctx: &mut Ctx,
body_fn: impl FnOnce(&mut Ctx, &[StringId]) -> Result<IrRef<'id, 'ir>>, body_fn: impl FnOnce(&mut Ctx, &[StringId]) -> Result<IrRef<'id, 'ir>>,
) -> Result<PatternBindings<'id, 'ir>> ) -> Result<PatternBindings<'id, 'ir>>
where where
Ctx: DowngradeContext<'id, 'ir>, Ctx: DowngradeContext<'id, 'ir>,
{ {
let arg = ctx.new_expr(Ir::Arg(arg));
struct Param { struct Param {
sym: StringId, sym: StringId,
sym_span: TextRange, sym_span: TextRange,
@@ -999,6 +991,7 @@ where
} }
} }
let arg = ctx.new_expr(Ir::Arg { layer: 0 });
ctx.with_let_scope(&keys, |ctx| { ctx.with_let_scope(&keys, |ctx| {
let vals = params let vals = params
.into_iter() .into_iter()
+4 -437
View File
@@ -1,4 +1,4 @@
use std::hash::{Hash, Hasher}; use std::hash::Hash;
use std::ops::Deref; use std::ops::Deref;
use bumpalo::Bump; use bumpalo::Bump;
@@ -126,10 +126,11 @@ pub enum Ir<'ir, Ref> {
Func { Func {
body: Ref, body: Ref,
param: Option<Param<'ir>>, param: Option<Param<'ir>>,
arg: ArgId,
thunks: Vec<'ir, (ThunkId, Ref)>, thunks: Vec<'ir, (ThunkId, Ref)>,
}, },
Arg(ArgId), Arg {
layer: usize,
},
Call { Call {
func: Ref, func: Ref,
arg: Ref, arg: Ref,
@@ -161,10 +162,6 @@ pub struct ThunkId(pub usize);
#[collect(require_static)] #[collect(require_static)]
pub struct StringId(pub SymbolU32); pub struct StringId(pub SymbolU32);
#[repr(transparent)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct ArgId(pub u32);
#[repr(transparent)] #[repr(transparent)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct SpanId(pub u32); pub struct SpanId(pub u32);
@@ -261,433 +258,3 @@ pub struct Param<'ir> {
pub optional: Vec<'ir, (StringId, TextRange)>, pub optional: Vec<'ir, (StringId, TextRange)>,
pub ellipsis: bool, pub ellipsis: bool,
} }
#[derive(Clone, Copy)]
pub(crate) struct IrKey<'id, 'ir, 'a>(pub IrRef<'id, 'ir>, pub &'a GhostToken<'id>);
impl std::hash::Hash for IrKey<'_, '_, '_> {
fn hash<H: Hasher>(&self, state: &mut H) {
ir_content_hash(self.0, self.1, state);
}
}
impl PartialEq for IrKey<'_, '_, '_> {
fn eq(&self, other: &Self) -> bool {
ir_content_eq(self.0, other.0, self.1)
}
}
impl Eq for IrKey<'_, '_, '_> {}
fn attr_content_hash<'id>(
attr: &Attr<IrRef<'id, '_>>,
token: &GhostToken<'id>,
state: &mut impl Hasher,
) {
core::mem::discriminant(attr).hash(state);
match attr {
Attr::Dynamic(expr, _) => ir_content_hash(*expr, token, state),
Attr::Str(sym, _) => sym.hash(state),
}
}
fn attr_content_eq<'id, 'ir>(
a: &Attr<IrRef<'id, 'ir>>,
b: &Attr<IrRef<'id, 'ir>>,
token: &GhostToken<'id>,
) -> bool {
match (a, b) {
(Attr::Dynamic(ae, _), Attr::Dynamic(be, _)) => ir_content_eq(*ae, *be, token),
(Attr::Str(a, _), Attr::Str(b, _)) => a == b,
_ => false,
}
}
fn param_content_hash(param: &Param<'_>, state: &mut impl Hasher) {
param.required.len().hash(state);
for (sym, _) in param.required.iter() {
sym.hash(state);
}
param.optional.len().hash(state);
for (sym, _) in param.optional.iter() {
sym.hash(state);
}
param.ellipsis.hash(state);
}
fn param_content_eq(a: &Param<'_>, b: &Param<'_>) -> bool {
a.ellipsis == b.ellipsis
&& a.required.len() == b.required.len()
&& a.optional.len() == b.optional.len()
&& a.required
.iter()
.zip(b.required.iter())
.all(|((a, _), (b, _))| a == b)
&& a.optional
.iter()
.zip(b.optional.iter())
.all(|((a, _), (b, _))| a == b)
}
fn thunks_content_hash<'id>(
thunks: &[(ThunkId, IrRef<'id, '_>)],
token: &GhostToken<'id>,
state: &mut impl Hasher,
) {
thunks.len().hash(state);
for &(id, ir) in thunks {
id.hash(state);
ir_content_hash(ir, token, state);
}
}
fn thunks_content_eq<'id, 'ir>(
a: &[(ThunkId, IrRef<'id, 'ir>)],
b: &[(ThunkId, IrRef<'id, 'ir>)],
token: &GhostToken<'id>,
) -> bool {
a.len() == b.len()
&& a.iter()
.zip(b.iter())
.all(|(&(ai, ae), &(bi, be))| ai == bi && ir_content_eq(ae, be, token))
}
fn ir_content_hash<'id>(ir: IrRef<'id, '_>, token: &GhostToken<'id>, state: &mut impl Hasher) {
let ir = ir.borrow(token);
core::mem::discriminant(ir).hash(state);
match ir {
Ir::Int(x) => x.hash(state),
Ir::Float(x) => x.to_bits().hash(state),
Ir::Bool(x) => x.hash(state),
Ir::Null => {}
Ir::Str(x) => x.hash(state),
Ir::AttrSet { stcs, dyns } => {
stcs.len().hash(state);
let mut combined: u64 = 0;
for (&key, &(val, _)) in stcs.iter() {
let mut h = std::hash::DefaultHasher::new();
key.hash(&mut h);
ir_content_hash(val, token, &mut h);
combined = combined.wrapping_add(h.finish());
}
combined.hash(state);
dyns.len().hash(state);
for &(k, v, _) in dyns.iter() {
ir_content_hash(k, token, state);
ir_content_hash(v, token, state);
}
}
Ir::List { items } => {
items.len().hash(state);
for &item in items.iter() {
ir_content_hash(item, token, state);
}
}
Ir::HasAttr { lhs, rhs } => {
ir_content_hash(*lhs, token, state);
rhs.len().hash(state);
for attr in rhs.iter() {
attr_content_hash(attr, token, state);
}
}
&Ir::BinOp { lhs, rhs, kind } => {
ir_content_hash(lhs, token, state);
ir_content_hash(rhs, token, state);
kind.hash(state);
}
&Ir::UnOp { rhs, kind } => {
ir_content_hash(rhs, token, state);
kind.hash(state);
}
Ir::Select {
expr,
attrpath,
default,
..
} => {
ir_content_hash(*expr, token, state);
attrpath.len().hash(state);
for attr in attrpath.iter() {
attr_content_hash(attr, token, state);
}
default.is_some().hash(state);
if let Some(d) = default {
ir_content_hash(*d, token, state);
}
}
&Ir::If { cond, consq, alter } => {
ir_content_hash(cond, token, state);
ir_content_hash(consq, token, state);
ir_content_hash(alter, token, state);
}
&Ir::Call { func, arg, .. } => {
ir_content_hash(func, token, state);
ir_content_hash(arg, token, state);
}
Ir::Assert {
assertion,
expr,
assertion_raw,
..
} => {
ir_content_hash(*assertion, token, state);
ir_content_hash(*expr, token, state);
assertion_raw.hash(state);
}
Ir::ConcatStrings {
force_string,
parts,
} => {
force_string.hash(state);
parts.len().hash(state);
for &part in parts.iter() {
ir_content_hash(part, token, state);
}
}
&Ir::Path(expr) => ir_content_hash(expr, token, state),
Ir::Func {
body,
arg,
param,
thunks,
} => {
ir_content_hash(*body, token, state);
arg.hash(state);
param.is_some().hash(state);
if let Some(p) = param {
param_content_hash(p, state);
}
thunks_content_hash(thunks, token, state);
}
Ir::TopLevel { body, thunks } => {
ir_content_hash(*body, token, state);
thunks_content_hash(thunks, token, state);
}
Ir::Arg(x) => x.hash(state),
Ir::Thunk(x) => x.hash(state),
Ir::Builtins => {}
Ir::Builtin(x) => x.hash(state),
Ir::BuiltinConst(x) => x.hash(state),
Ir::CurPos(x) => x.hash(state),
Ir::ReplBinding(x) => x.hash(state),
Ir::ScopedImportBinding(x) => x.hash(state),
&Ir::With {
namespace,
body,
ref thunks,
} => {
ir_content_hash(namespace, token, state);
ir_content_hash(body, token, state);
thunks_content_hash(thunks, token, state);
}
Ir::WithLookup(x) => x.hash(state),
}
}
pub(crate) fn ir_content_eq<'id, 'ir>(
a: IrRef<'id, 'ir>,
b: IrRef<'id, 'ir>,
token: &GhostToken<'id>,
) -> bool {
std::ptr::eq(a.0, b.0)
|| match (a.borrow(token), b.borrow(token)) {
(Ir::Int(a), Ir::Int(b)) => a == b,
(Ir::Float(a), Ir::Float(b)) => a.to_bits() == b.to_bits(),
(Ir::Bool(a), Ir::Bool(b)) => a == b,
(Ir::Null, Ir::Null) => true,
(Ir::Str(a), Ir::Str(b)) => **a == **b,
(
Ir::AttrSet {
stcs: a_stcs,
dyns: a_dyns,
},
Ir::AttrSet {
stcs: b_stcs,
dyns: b_dyns,
},
) => {
a_stcs.len() == b_stcs.len()
&& a_dyns.len() == b_dyns.len()
&& a_stcs.iter().all(|(&k, &(av, _))| {
b_stcs
.get(&k)
.is_some_and(|&(bv, _)| ir_content_eq(av, bv, token))
})
&& a_dyns
.iter()
.zip(b_dyns.iter())
.all(|(&(ak, av, _), &(bk, bv, _))| {
ir_content_eq(ak, bk, token) && ir_content_eq(av, bv, token)
})
}
(Ir::List { items: a }, Ir::List { items: b }) => {
a.len() == b.len()
&& a.iter()
.zip(b.iter())
.all(|(&a, &b)| ir_content_eq(a, b, token))
}
(Ir::HasAttr { lhs: al, rhs: ar }, Ir::HasAttr { lhs: bl, rhs: br }) => {
ir_content_eq(*al, *bl, token)
&& ar.len() == br.len()
&& ar
.iter()
.zip(br.iter())
.all(|(a, b)| attr_content_eq(a, b, token))
}
(
&Ir::BinOp {
lhs: al,
rhs: ar,
kind: ak,
},
&Ir::BinOp {
lhs: bl,
rhs: br,
kind: bk,
},
) => ak == bk && ir_content_eq(al, bl, token) && ir_content_eq(ar, br, token),
(&Ir::UnOp { rhs: ar, kind: ak }, &Ir::UnOp { rhs: br, kind: bk }) => {
ak == bk && ir_content_eq(ar, br, token)
}
(
Ir::Select {
expr: ae,
attrpath: aa,
default: ad,
..
},
Ir::Select {
expr: be,
attrpath: ba,
default: bd,
..
},
) => {
ir_content_eq(*ae, *be, token)
&& aa.len() == ba.len()
&& aa
.iter()
.zip(ba.iter())
.all(|(a, b)| attr_content_eq(a, b, token))
&& match (ad, bd) {
(Some(a), Some(b)) => ir_content_eq(*a, *b, token),
(None, None) => true,
_ => false,
}
}
(
&Ir::If {
cond: ac,
consq: acs,
alter: aa,
},
&Ir::If {
cond: bc,
consq: bcs,
alter: ba,
},
) => {
ir_content_eq(ac, bc, token)
&& ir_content_eq(acs, bcs, token)
&& ir_content_eq(aa, ba, token)
}
(
&Ir::Call {
func: af, arg: aa, ..
},
&Ir::Call {
func: bf, arg: ba, ..
},
) => ir_content_eq(af, bf, token) && ir_content_eq(aa, ba, token),
(
Ir::Assert {
assertion: aa,
expr: ae,
assertion_raw: ar,
..
},
Ir::Assert {
assertion: ba,
expr: be,
assertion_raw: br,
..
},
) => ar == br && ir_content_eq(*aa, *ba, token) && ir_content_eq(*ae, *be, token),
(
Ir::ConcatStrings {
force_string: af,
parts: ap,
},
Ir::ConcatStrings {
force_string: bf,
parts: bp,
},
) => {
af == bf
&& ap.len() == bp.len()
&& ap
.iter()
.zip(bp.iter())
.all(|(&a, &b)| ir_content_eq(a, b, token))
}
(&Ir::Path(a), &Ir::Path(b)) => ir_content_eq(a, b, token),
(
Ir::Func {
body: ab,
arg: aa,
param: ap,
thunks: at,
},
Ir::Func {
body: bb,
arg: ba,
param: bp,
thunks: bt,
},
) => {
ir_content_eq(*ab, *bb, token)
&& aa == ba
&& match (ap, bp) {
(Some(a), Some(b)) => param_content_eq(a, b),
(None, None) => true,
_ => false,
}
&& thunks_content_eq(at, bt, token)
}
(
Ir::TopLevel {
body: ab,
thunks: at,
},
Ir::TopLevel {
body: bb,
thunks: bt,
},
) => ir_content_eq(*ab, *bb, token) && thunks_content_eq(at, bt, token),
(Ir::Arg(a), Ir::Arg(b)) => a == b,
(Ir::Thunk(a), Ir::Thunk(b)) => a == b,
(Ir::Builtins, Ir::Builtins) => true,
(Ir::Builtin(a), Ir::Builtin(b)) => a == b,
(Ir::CurPos(a), Ir::CurPos(b)) => a == b,
(Ir::ReplBinding(a), Ir::ReplBinding(b)) => a == b,
(Ir::ScopedImportBinding(a), Ir::ScopedImportBinding(b)) => a == b,
(
Ir::With {
namespace: a_ns,
body: a_body,
thunks: a_thunks,
},
Ir::With {
namespace: b_ns,
body: b_body,
thunks: b_thunks,
},
) => {
ir_content_eq(*a_ns, *b_ns, token)
&& ir_content_eq(*a_body, *b_body, token)
&& thunks_content_eq(a_thunks, b_thunks, token)
}
(Ir::WithLookup(a), Ir::WithLookup(b)) => a == b,
_ => false,
}
}
+16 -40
View File
@@ -1,9 +1,7 @@
use std::hash::BuildHasher;
use bumpalo::Bump; use bumpalo::Bump;
use gc_arena::{Arena, Rootable}; use gc_arena::{Arena, Rootable};
use ghost_cell::{GhostCell, GhostToken}; use ghost_cell::{GhostCell, GhostToken};
use hashbrown::{DefaultHashBuilder, HashMap, HashSet, HashTable}; use hashbrown::{HashMap, HashSet};
use rnix::TextRange; use rnix::TextRange;
use string_interner::symbol::SymbolU32; use string_interner::symbol::SymbolU32;
use string_interner::{DefaultStringInterner, Symbol as _}; use string_interner::{DefaultStringInterner, Symbol as _};
@@ -12,7 +10,7 @@ use crate::codegen::{BytecodeContext, InstructionPtr};
use crate::disassembler::{Disassembler, DisassemblerContext}; use crate::disassembler::{Disassembler, DisassemblerContext};
use crate::downgrade::{Downgrade as _, DowngradeContext}; use crate::downgrade::{Downgrade as _, DowngradeContext};
use crate::error::{Error, Result, Source}; use crate::error::{Error, Result, Source};
use crate::ir::{ArgId, Ir, IrKey, IrRef, RawIrRef, StringId, ThunkId, ir_content_eq}; use crate::ir::{Ir, IrRef, RawIrRef, StringId, ThunkId};
use crate::runtime::builtins::init_builtins; use crate::runtime::builtins::init_builtins;
use crate::runtime::value::StaticValue; use crate::runtime::value::StaticValue;
use crate::runtime::vm::{ForceMode, new_gc_root}; use crate::runtime::vm::{ForceMode, new_gc_root};
@@ -282,26 +280,11 @@ impl<'ctx: 'ir, 'id, 'ir> DowngradeContext<'id, 'ir> for DowngradeCtx<'ctx, 'id,
IrRef::new(self.bump.alloc(GhostCell::new(expr))) IrRef::new(self.bump.alloc(GhostCell::new(expr)))
} }
fn new_arg(&mut self) -> ArgId {
self.arg_count += 1;
ArgId(self.arg_count - 1)
}
fn maybe_thunk(&mut self, ir: IrRef<'id, 'ir>) -> IrRef<'id, 'ir> { fn maybe_thunk(&mut self, ir: IrRef<'id, 'ir>) -> IrRef<'id, 'ir> {
if !should_thunk(ir, &self.token) { if !should_thunk(ir, &self.token) {
return ir; return ir;
} }
let cached = self
.thunk_scopes
.last()
.expect("no active cache scope")
.lookup_cache(ir, &self.token);
if let Some(id) = cached {
return IrRef::alloc(self.bump, Ir::Thunk(id));
}
let id = ThunkId(*self.thunk_count); let id = ThunkId(*self.thunk_count);
*self.thunk_count = self.thunk_count.checked_add(1).expect("thunk id overflow"); *self.thunk_count = self.thunk_count.checked_add(1).expect("thunk id overflow");
self.thunk_scopes self.thunk_scopes
@@ -349,9 +332,14 @@ impl<'ctx: 'ir, 'id, 'ir> DowngradeContext<'id, 'ir> for DowngradeCtx<'ctx, 'id,
return Ok(self.new_expr(Ir::Thunk(expr))); return Ok(self.new_expr(Ir::Thunk(expr)));
} }
} }
&Scope::Param(param_sym, id) => { &Scope::Param {
sym: param_sym,
abs_layer,
} => {
if param_sym == sym { if param_sym == sym {
return Ok(self.new_expr(Ir::Arg(id))); return Ok(self.new_expr(Ir::Arg {
layer: self.thunk_scopes.len() - abs_layer,
}));
} }
} }
} }
@@ -398,11 +386,14 @@ impl<'ctx: 'ir, 'id, 'ir> DowngradeContext<'id, 'ir> for DowngradeCtx<'ctx, 'id,
Ok(ret) Ok(ret)
} }
fn with_param_scope<F, R>(&mut self, param: StringId, arg: ArgId, f: F) -> R fn with_param_scope<F, R>(&mut self, sym: StringId, f: F) -> R
where where
F: FnOnce(&mut Self) -> R, F: FnOnce(&mut Self) -> R,
{ {
self.scopes.push(Scope::Param(param, arg)); self.scopes.push(Scope::Param {
sym,
abs_layer: self.thunk_scopes.len(),
});
let mut guard = ScopeGuard { ctx: self }; let mut guard = ScopeGuard { ctx: self };
f(guard.as_ctx()) f(guard.as_ctx())
} }
@@ -458,32 +449,17 @@ impl<'id, 'ir, 'ctx: 'ir> DowngradeCtx<'ctx, 'id, 'ir> {
struct ThunkScope<'id, 'ir> { struct ThunkScope<'id, 'ir> {
bindings: bumpalo::collections::Vec<'ir, (ThunkId, IrRef<'id, 'ir>)>, bindings: bumpalo::collections::Vec<'ir, (ThunkId, IrRef<'id, 'ir>)>,
cache: HashTable<(IrRef<'id, 'ir>, ThunkId)>,
hasher: DefaultHashBuilder,
} }
impl<'id, 'ir> ThunkScope<'id, 'ir> { impl<'id, 'ir> ThunkScope<'id, 'ir> {
fn new_in(bump: &'ir Bump) -> Self { fn new_in(bump: &'ir Bump) -> Self {
Self { Self {
bindings: bumpalo::collections::Vec::new_in(bump), bindings: bumpalo::collections::Vec::new_in(bump),
cache: HashTable::new(),
hasher: DefaultHashBuilder::default(),
} }
} }
fn lookup_cache(&self, key: IrRef<'id, 'ir>, token: &GhostToken<'id>) -> Option<ThunkId> { fn add_binding(&mut self, id: ThunkId, ir: IrRef<'id, 'ir>, _token: &GhostToken<'id>) {
let hash = self.hasher.hash_one(IrKey(key, token));
self.cache
.find(hash, |&(ir, _)| ir_content_eq(key, ir, token))
.map(|&(_, id)| id)
}
fn add_binding(&mut self, id: ThunkId, ir: IrRef<'id, 'ir>, token: &GhostToken<'id>) {
self.bindings.push((id, ir)); self.bindings.push((id, ir));
let hash = self.hasher.hash_one(IrKey(ir, token));
self.cache.insert_unique(hash, (ir, id), |&(ir, _)| {
self.hasher.hash_one(IrKey(ir, token))
});
} }
fn extend_bindings(&mut self, iter: impl IntoIterator<Item = (ThunkId, IrRef<'id, 'ir>)>) { fn extend_bindings(&mut self, iter: impl IntoIterator<Item = (ThunkId, IrRef<'id, 'ir>)>) {
@@ -496,7 +472,7 @@ enum Scope<'ctx> {
Repl(&'ctx HashSet<StringId>), Repl(&'ctx HashSet<StringId>),
ScopedImport(HashSet<StringId>), ScopedImport(HashSet<StringId>),
Let(HashMap<StringId, ThunkId>), Let(HashMap<StringId, ThunkId>),
Param(StringId, ArgId), Param { sym: StringId, abs_layer: usize },
} }
struct ScopeGuard<'a, 'ctx, 'id, 'ir> { struct ScopeGuard<'a, 'ctx, 'id, 'ir> {
+6
View File
@@ -478,6 +478,12 @@ impl<'gc> AttrSet<'gc> {
pub(crate) struct List<'gc> { pub(crate) struct List<'gc> {
pub(crate) inner: SmallVec<[Value<'gc>; 4]>, pub(crate) inner: SmallVec<[Value<'gc>; 4]>,
} }
impl<'gc> Deref for List<'gc> {
type Target = SmallVec<[Value<'gc>; 4]>;
fn deref(&self) -> &Self::Target {
&self.inner
}
}
pub(crate) type Thunk<'gc> = RefLock<ThunkState<'gc>>; pub(crate) type Thunk<'gc> = RefLock<ThunkState<'gc>>;
+400 -248
View File
@@ -8,7 +8,7 @@ use smallvec::SmallVec;
use string_interner::{DefaultStringInterner, Symbol as _}; use string_interner::{DefaultStringInterner, Symbol as _};
use super::Runtime; use super::Runtime;
use super::builtins::{BUILTINS, BuiltinId, PrimOpArgs, PrimOpStrictArgs}; use super::builtins::{BUILTINS, BuiltinId};
use super::stack::Stack; use super::stack::Stack;
use super::value::*; use super::value::*;
use crate::codegen::{ use crate::codegen::{
@@ -78,79 +78,6 @@ pub(super) fn new_gc_root<'gc>(
} }
impl<'gc> GcRoot<'gc> { impl<'gc> GcRoot<'gc> {
fn init_builtins(
mc: &Mutation<'gc>,
strings: &mut DefaultStringInterner,
) -> (Value<'gc>, HashMap<StringId, PrimOp>) {
let mut builtin_lookup = HashMap::new();
let mut entries = SmallVec::new();
for (id, &(name, arity)) in BUILTINS.iter().enumerate() {
let Some(sym) = strings.get(name) else {
continue;
};
let sid = StringId(sym);
let primop = PrimOp {
id: BuiltinId::try_from_primitive(id as u8).expect("invalid BuiltinId??"),
arity,
};
builtin_lookup.insert(sid, primop);
// Regular primop
if arity != 0 {
entries.push((sid, Value::new_inline(primop)));
}
}
// Add constant entries
macro_rules! add_const {
($name:expr, $val:expr) => {{
let sym = strings.get_or_intern($name);
entries.push((StringId(sym), $val));
}};
}
add_const!(
"currentSystem",
Value::new_gc(Gc::new(mc, NixString::new("x86_64-linux")))
);
add_const!("langVersion", Value::new_inline(6i32));
add_const!(
"nixVersion",
Value::new_gc(Gc::new(mc, NixString::new("2.24.0")))
);
add_const!(
"storeDir",
Value::new_gc(Gc::new(mc, NixString::new("/nix/store")))
);
add_const!(
"nixPath",
Value::new_gc(Gc::new(
mc,
List {
inner: SmallVec::new()
}
))
);
add_const!("null", Value::new_inline(Null));
add_const!("true", Value::new_inline(true));
add_const!("false", Value::new_inline(false));
// Self-reference thunk for builtins.builtins
let self_ref_thunk: Gc<'gc, Thunk<'gc>> = Gc::new(mc, RefLock::new(ThunkState::Blackhole));
let sym = strings.get_or_intern("builtins");
entries.push((StringId(sym), Value::new_gc(self_ref_thunk)));
entries.sort_by_key(|(k, _)| *k);
let builtins_set = Gc::new(mc, unsafe { AttrSet::from_sorted_unchecked(entries) });
let builtins_val = Value::new_gc(builtins_set);
// Populate the self-reference
*self_ref_thunk.borrow_mut(mc) = ThunkState::Evaluated(builtins_val);
(builtins_val, builtin_lookup)
}
#[inline(always)] #[inline(always)]
fn env(&self) -> Gc<'gc, RefLock<Env<'gc>>> { fn env(&self) -> Gc<'gc, RefLock<Env<'gc>>> {
self.current_env.expect("no current env") self.current_env.expect("no current env")
@@ -167,8 +94,13 @@ impl<'gc> GcRoot<'gc> {
} }
#[inline(always)] #[inline(always)]
#[track_caller]
pub(super) fn pop_stack_forced(&mut self) -> StrictValue<'gc> { pub(super) fn pop_stack_forced(&mut self) -> StrictValue<'gc> {
self.stack.pop().expect("stack underflow").restrict().expect("forced") self.stack
.pop()
.expect("stack underflow")
.restrict()
.expect("forced")
} }
} }
@@ -269,7 +201,7 @@ enum SelectKeyData {
} }
macro_rules! try_vm { macro_rules! try_vm {
($self:ident, $expr:expr) => { ($self:ident; $expr:expr) => {
match $expr { match $expr {
Ok(v) => v, Ok(v) => v,
Err(e) => return Runtime::handle_vm_error($self, e), Err(e) => return Runtime::handle_vm_error($self, e),
@@ -396,12 +328,12 @@ impl Runtime {
LoadLocal => { LoadLocal => {
let idx = self.read_u32() as usize; let idx = self.read_u32() as usize;
self.arena self.arena
.mutate_root(|mc, root| root.push_stack(root.env().borrow().locals[idx])) .mutate_root(|_, root| root.push_stack(root.env().borrow().locals[idx]))
} }
LoadOuter => { LoadOuter => {
let layer = self.read_u8(); let layer = self.read_u8();
let idx = self.read_u32() as usize; let idx = self.read_u32() as usize;
self.arena.mutate_root(|mc, root| { self.arena.mutate_root(|_, root| {
let mut cur = root.env(); let mut cur = root.env();
for _ in 0..layer { for _ in 0..layer {
let prev = cur.borrow().prev.expect("LoadOuter: env chain too short"); let prev = cur.borrow().prev.expect("LoadOuter: env chain too short");
@@ -430,7 +362,6 @@ impl Runtime {
MakeThunk => { MakeThunk => {
let entry_point = self.read_u32(); let entry_point = self.read_u32();
let _label = self.read_string_id();
self.arena.mutate_root(|mc, root| { self.arena.mutate_root(|mc, root| {
let thunk = Gc::new( let thunk = Gc::new(
mc, mc,
@@ -602,8 +533,7 @@ impl Runtime {
&SelectKeyData::Static(sid) => sid, &SelectKeyData::Static(sid) => sid,
SelectKeyData::Dynamic => { SelectKeyData::Dynamic => {
self.arena.mutate_root(|_, root| { self.arena.mutate_root(|_, root| {
let v = let v = root.temp_stack.pop().expect("missing dynamic key");
root.temp_stack.pop().expect("missing dynamic key");
root.push_stack(v); root.push_stack(v);
}); });
self.force_tos(); self.force_tos();
@@ -628,9 +558,7 @@ impl Runtime {
let result = self.arena.mutate_root(|_, root| { let result = self.arena.mutate_root(|_, root| {
let val = root.pop_stack(); let val = root.pop_stack();
let Some(attrset) = val.as_gc::<AttrSet<'_>>() else { let Some(attrset) = val.as_gc::<AttrSet<'_>>() else {
return Err(vm_err( return Err(vm_err("value is not a set while a set was expected"));
"value is not a set while a set was expected",
));
}; };
match attrset.lookup(key_sid) { match attrset.lookup(key_sid) {
Some(v) => { Some(v) => {
@@ -650,8 +578,7 @@ impl Runtime {
Ok(false) => { Ok(false) => {
self.arena self.arena
.mutate_root(|_, root| root.temp_stack.truncate(temp_base)); .mutate_root(|_, root| root.temp_stack.truncate(temp_base));
let name = let name = self.strings.resolve(key_sid.0).unwrap_or("«unknown»");
self.strings.resolve(key_sid.0).unwrap_or("«unknown»");
return Runtime::handle_vm_error( return Runtime::handle_vm_error(
self, self,
vm_err(format!("attribute '{name}' missing")), vm_err(format!("attribute '{name}' missing")),
@@ -699,8 +626,7 @@ impl Runtime {
&SelectKeyData::Static(sid) => sid, &SelectKeyData::Static(sid) => sid,
SelectKeyData::Dynamic => { SelectKeyData::Dynamic => {
self.arena.mutate_root(|_, root| { self.arena.mutate_root(|_, root| {
let v = let v = root.temp_stack.pop().expect("missing dynamic key");
root.temp_stack.pop().expect("missing dynamic key");
root.push_stack(v); root.push_stack(v);
}); });
self.force_tos(); self.force_tos();
@@ -724,13 +650,13 @@ impl Runtime {
let found = self.arena.mutate_root(|_, root| { let found = self.arena.mutate_root(|_, root| {
let val = root.pop_stack(); let val = root.pop_stack();
if let Some(attrset) = val.as_gc::<AttrSet<'_>>() { if let Some(attrset) = val.as_gc::<AttrSet<'_>>()
if let Some(v) = attrset.lookup(key_sid) { && let Some(v) = attrset.lookup(key_sid)
{
root.push_stack(v); root.push_stack(v);
return true; return true;
} }
} // Not a set or key missing: use default
// Not a set or key missing → use default
false false
}); });
@@ -757,7 +683,7 @@ impl Runtime {
} }
HasAttr => { HasAttr => {
let n = self.read_u16() as usize; let n = self.read_u16() as usize;
let keys = self.read_attr_keys(n); let _keys = self.read_attr_keys(n);
todo!("implement HasAttr (force + check)"); todo!("implement HasAttr (force + check)");
} }
@@ -780,24 +706,118 @@ impl Runtime {
self.push_empty_list(); self.push_empty_list();
} }
OpAdd | OpSub | OpMul | OpDiv | OpEq | OpNeq | OpLt | OpGt | OpLeq | OpGeq OpAdd => {
| OpConcat | OpUpdate => { self.force_n(2);
let tag = match op { let strings = &self.strings;
OpAdd => BinOpTag::Add, let res = self.arena.mutate_root(|mc, root| {
OpSub => BinOpTag::Sub, let rhs = root.pop_stack_forced();
OpMul => BinOpTag::Mul, let lhs = root.pop_stack_forced();
OpDiv => BinOpTag::Div, // FIXME: path & string context
OpEq => BinOpTag::Eq, if let (Some(ls), Some(rs)) = (
OpNeq => BinOpTag::Neq, Self::get_string(strings, lhs),
OpLt => BinOpTag::Lt, Self::get_string(strings, rhs),
OpGt => BinOpTag::Gt, ) {
OpLeq => BinOpTag::Leq, let ns = Gc::new(mc, NixString::new(format!("{ls}{rs}")));
OpGeq => BinOpTag::Geq, root.push_stack(Value::new_gc(ns));
OpConcat => BinOpTag::Concat, return Ok(());
OpUpdate => BinOpTag::Update, }
let res = Self::numeric_binop(lhs, rhs, mc, i64::wrapping_add, |a, b| a + b)?;
root.push_stack(res);
VmResult::Ok(())
});
try_vm!(self; res);
}
OpSub | OpMul => {
self.force_n(2);
#[allow(clippy::type_complexity)]
let func: (fn(i64, i64) -> i64, fn(f64, f64) -> f64) = match op {
OpSub => (i64::wrapping_sub, |a, b| a - b),
OpMul => (i64::wrapping_mul, |a, b| a * b),
_ => unreachable!(), _ => unreachable!(),
}; };
try_vm!(self, self.compute_binop(tag)) let res = self.arena.mutate_root(|mc, root| {
let rhs = root.pop_stack_forced();
let lhs = root.pop_stack_forced();
let res = Self::numeric_binop(lhs, rhs, mc, func.0, func.1)?;
root.push_stack(res);
VmResult::Ok(())
});
try_vm!(self; res);
}
OpDiv => {
self.force_n(2);
let res = self.arena.mutate_root(|mc, root| {
let rhs = root.pop_stack_forced();
let lhs = root.pop_stack_forced();
match (Self::get_num(lhs), Self::get_num(rhs)) {
(_, Some(NixNum::Int(0))) => Err(vm_err("division by zero")),
(_, Some(NixNum::Float(0.))) => Err(vm_err("division by zero")),
_ => Ok(()),
}?;
let res = Self::numeric_binop(lhs, rhs, mc, |a, b| a / b, |a, b| a / b)?;
root.push_stack(res);
VmResult::Ok(())
});
try_vm!(self; res);
}
OpEq | OpNeq => {
self.force_n(2);
let map: fn(bool) -> bool = match op {
OpEq => |a| a,
OpNeq => |a| !a,
_ => unreachable!(),
};
let eq = try_vm!(self; self.values_equal());
self.push_stack(|_| Value::new_inline(map(eq)));
}
OpLt | OpGt | OpLeq | OpGeq => {
use std::cmp::Ordering;
self.force_n(2);
let pred: fn(Ordering) -> bool = match op {
OpLt => Ordering::is_lt,
OpGt => Ordering::is_gt,
OpLeq => Ordering::is_le,
OpGeq => Ordering::is_ge,
_ => unreachable!(),
};
try_vm!(self; self.compare_values(pred));
}
OpConcat => {
self.force_n(2);
let res = self.arena.mutate_root(|mc, root| {
let rhs = root.pop_stack_forced();
let lhs = root.pop_stack_forced();
// TODO: better type-assert ergonomic
let Some(l) = lhs.as_gc::<List<'_>>() else {
return Err(vm_err("cannot concatenate: left operand is not a list"));
};
let Some(r) = rhs.as_gc::<List<'_>>() else {
return Err(vm_err("cannot concatenate: right operand is not a list"));
};
let mut items = SmallVec::new();
items.extend_from_slice(&l);
items.extend_from_slice(&r);
root.push_stack(Value::new_gc(Gc::new(mc, List { inner: items })));
VmResult::Ok(())
});
try_vm!(self; res);
}
OpUpdate => {
self.force_n(2);
let res = self.arena.mutate_root(|mc, root| {
let rhs = root.pop_stack_forced();
let lhs = root.pop_stack_forced();
// TODO: better type-assert ergonomic
let Some(l) = lhs.as_gc::<AttrSet<'_>>() else {
return Err(vm_err("cannot update: left operand is not a set"));
};
let Some(r) = rhs.as_gc::<AttrSet<'_>>() else {
return Err(vm_err("cannot update: right operand is not a set"));
};
root.push_stack(Value::new_gc(l.merge(&r, mc)));
VmResult::Ok(())
});
try_vm!(self; res);
} }
OpNeg => { OpNeg => {
@@ -846,8 +866,8 @@ impl Runtime {
} }
Assert => { Assert => {
let raw_idx = self.read_u32(); let _raw_idx = self.read_u32();
let span_id = self.read_u32(); let _span_id = self.read_u32();
todo!("implement Assert (force TOS)"); todo!("implement Assert (force TOS)");
} }
@@ -872,7 +892,59 @@ impl Runtime {
}), }),
WithLookup => { WithLookup => {
let name = self.read_string_id(); let name = self.read_string_id();
todo!("implement WithLookup (force with_scope)"); let mut depth = 0;
loop {
let found_scope = self.arena.mutate_root(|_, root| {
let mut cur = root.with_scope;
for _ in 0..depth {
if let Some(s) = cur {
cur = s.prev;
} else {
break;
}
}
if let Some(scope) = cur {
root.push_stack(scope.env);
true
} else {
false
}
});
if !found_scope {
let name_str = self.strings.resolve(name.0).unwrap_or("«unknown»");
return Runtime::handle_vm_error(
self,
vm_err(format!("undefined variable '{name_str}'")),
);
}
if let err @ Action::Done(Err(_)) = self.force_tos() {
return err;
}
let lookup_result = self.arena.mutate_root(|_, root| {
let val = root.pop_stack();
let Some(attrs) = val.as_gc::<AttrSet<'_>>() else {
return Err(vm_err("value in 'with' scope must be a set"));
};
if let Some(v) = attrs.lookup(name) {
root.push_stack(v);
Ok(true) // Found it
} else {
Ok(false) // Not in this scope, try the next one
}
});
match lookup_result {
Ok(true) => break, // Successfully resolved and pushed to stack
Ok(false) => depth += 1, // Move to the parent 'with' scope
Err(e) => return Runtime::handle_vm_error(self, e),
}
}
} }
LoadBuiltins => { LoadBuiltins => {
@@ -932,119 +1004,7 @@ impl Runtime {
} }
} }
fn compute_binop(&mut self, op: BinOpTag) -> VmResult<()> { #[inline]
self.force_n(2);
match op {
BinOpTag::Add => {
let strings = &self.strings;
self.arena.mutate_root(|mc, root| {
let rhs = root.pop_stack_forced();
let lhs = root.pop_stack_forced();
// FIXME: path & string context
if let (Some(ls), Some(rs)) = (
Self::get_string(strings, lhs),
Self::get_string(strings, rhs),
) {
let ns = Gc::new(mc, NixString::new(format!("{ls}{rs}")));
root.push_stack(Value::new_gc(ns));
return Ok(());
}
let res = Self::numeric_binop(lhs, rhs, mc, i64::wrapping_add, |a, b| a + b)?;
root.push_stack(res);
VmResult::Ok(())
})?;
}
BinOpTag::Sub => {
self.arena.mutate_root(|mc, root| {
let rhs = root.pop_stack_forced();
let lhs = root.pop_stack_forced();
let res = Self::numeric_binop(lhs, rhs, mc, i64::wrapping_sub, |a, b| a - b)?;
root.push_stack(res);
VmResult::Ok(())
})?;
}
BinOpTag::Mul => {
self.arena.mutate_root(|mc, root| {
let rhs = root.pop_stack_forced();
let lhs = root.pop_stack_forced();
let res = Self::numeric_binop(lhs, rhs, mc, i64::wrapping_mul, |a, b| a * b)?;
root.push_stack(res);
VmResult::Ok(())
})?;
}
BinOpTag::Div => {
self.arena.mutate_root(|mc, root| {
let rhs = root.pop_stack_forced();
let lhs = root.pop_stack_forced();
let res = match (Self::get_num(lhs), Self::get_num(rhs)) {
(_, Some(NixNum::Int(0))) => Err(vm_err("division by zero")),
(_, Some(NixNum::Float(0.))) => Err(vm_err("division by zero")),
(Some(NixNum::Int(a)), Some(NixNum::Int(b))) => {
Ok(Value::make_int(a.wrapping_div(b), mc))
}
(Some(NixNum::Float(a)), Some(NixNum::Float(b))) => {
Ok(Value::new_float(a / b))
}
(Some(NixNum::Int(a)), Some(NixNum::Float(b))) => {
Ok(Value::new_float(a as f64 / b))
}
(Some(NixNum::Float(a)), Some(NixNum::Int(b))) => {
Ok(Value::new_float(a / b as f64))
}
_ => Err(vm_err("cannot divide non-numbers")),
}?;
root.push_stack(res);
VmResult::Ok(())
})?;
}
BinOpTag::Eq => {
let eq = self.values_equal()?;
self.push_stack(|_| Value::new_inline(eq));
}
BinOpTag::Neq => {
let eq = self.values_equal()?;
self.push_stack(|_| Value::new_inline(!eq));
}
BinOpTag::Lt => self.compare_values(|o| o.is_lt())?,
BinOpTag::Gt => self.compare_values(|o| o.is_gt())?,
BinOpTag::Leq => self.compare_values(|o| o.is_le())?,
BinOpTag::Geq => self.compare_values(|o| o.is_ge())?,
BinOpTag::Concat => {
self.arena.mutate_root(|mc, root| {
let rhs = root.pop_stack_forced();
let lhs = root.pop_stack_forced();
let Some(l) = lhs.as_gc::<List<'_>>() else {
return Err(vm_err("cannot concatenate: left operand is not a list"));
};
let Some(r) = rhs.as_gc::<List<'_>>() else {
return Err(vm_err("cannot concatenate: right operand is not a list"));
};
let mut items = SmallVec::new();
items.extend(l.inner.iter().cloned());
items.extend(r.inner.iter().cloned());
root.push_stack(Value::new_gc(Gc::new(mc, List { inner: items })));
VmResult::Ok(())
})?;
}
BinOpTag::Update => {
self.arena.mutate_root(|mc, root| {
let rhs = root.pop_stack_forced();
let lhs = root.pop_stack_forced();
let Some(l) = lhs.as_gc::<AttrSet<'_>>() else {
return Err(vm_err("cannot update: left operand is not a set"));
};
let Some(r) = rhs.as_gc::<AttrSet<'_>>() else {
return Err(vm_err("cannot update: right operand is not a set"));
};
root.push_stack(Value::new_gc(l.merge(&r, mc)));
VmResult::Ok(())
})?;
}
}
Ok(())
}
fn numeric_binop<'gc>( fn numeric_binop<'gc>(
lhs: StrictValue<'gc>, lhs: StrictValue<'gc>,
rhs: StrictValue<'gc>, rhs: StrictValue<'gc>,
@@ -1130,8 +1090,8 @@ impl Runtime {
State::List(len) | State::AttrSet(len) => { State::List(len) | State::AttrSet(len) => {
for i in 0..len { for i in 0..len {
self.arena.mutate_root(|_, root| { self.arena.mutate_root(|_, root| {
let y = root.temp_stack.pop().unwrap(); let y = root.temp_stack.pop().expect("stack underflow");
let x = root.temp_stack.pop().unwrap(); let x = root.temp_stack.pop().expect("stack underflow");
root.push_stack(x); root.push_stack(x);
root.push_stack(y); root.push_stack(y);
}); });
@@ -1259,14 +1219,11 @@ impl Runtime {
pub(super) fn force_tos(&mut self) -> Action { pub(super) fn force_tos(&mut self) -> Action {
loop { loop {
let run = self.arena.mutate_root(|_mc, root| { let (run, target_depth) = self.arena.mutate_root(|_mc, root| {
let thunk = root let thunk = root.stack.tos_mut().expect("stack underflow");
.stack
.tos_mut()
.expect("stack underflow");
let Some(thunk_state) = thunk.as_gc::<Thunk>() else { let Some(thunk_state) = thunk.as_gc::<Thunk>() else {
return false return (false, 0);
}; };
match *thunk_state.borrow() { match *thunk_state.borrow() {
ThunkState::Pending { ip, env } => { ThunkState::Pending { ip, env } => {
@@ -1278,13 +1235,13 @@ impl Runtime {
.expect("call stack overflow"); .expect("call stack overflow");
self.pc = ip; self.pc = ip;
root.current_env = Some(env); root.current_env = Some(env);
true (true, root.frames.len())
} }
ThunkState::Apply { .. } => todo!("force_tos"), ThunkState::Apply { .. } => todo!("force_tos"),
ThunkState::Evaluated(val) => { ThunkState::Evaluated(val) => {
*thunk = val; *thunk = val;
false (false, 0)
}, }
ThunkState::Blackhole => todo!("force_tos"), ThunkState::Blackhole => todo!("force_tos"),
} }
}); });
@@ -1295,7 +1252,12 @@ impl Runtime {
self.check_gc(); self.check_gc();
match self.execute_one() { match self.execute_one() {
Action::Continue => (), Action::Continue => (),
Action::Return => break, Action::Return => {
let current_depth = self.arena.mutate_root(|_, root| root.frames.len());
if current_depth < target_depth {
break;
}
}
// FIXME: poison thunk // FIXME: poison thunk
Action::Done(err @ Err(_)) => return Action::Done(err), Action::Done(err @ Err(_)) => return Action::Done(err),
Action::Done(Ok(_)) => unreachable!(), Action::Done(Ok(_)) => unreachable!(),
@@ -1335,7 +1297,7 @@ impl Runtime {
pub(super) fn handle_return(&mut self) -> Action { pub(super) fn handle_return(&mut self) -> Action {
self.force_tos(); self.force_tos();
let done= self.arena.mutate_root(|_, root| { let done = self.arena.mutate_root(|_, root| {
let Some(frame) = root.frames.pop() else { let Some(frame) = root.frames.pop() else {
return true; return true;
}; };
@@ -1350,33 +1312,223 @@ impl Runtime {
ForceMode::AsIs => (), ForceMode::AsIs => (),
ForceMode::Shallow => { ForceMode::Shallow => {
if let done @ Action::Done(_) = self.force_tos_shallow() { if let done @ Action::Done(_) = self.force_tos_shallow() {
return done return done;
} }
} }
ForceMode::Deep => { ForceMode::Deep => {
if let done @ Action::Done(_) = self.force_tos_shallow() { if let done @ Action::Done(_) = self.force_tos_shallow() {
return done return done;
} }
} }
} }
let val = self.arena.mutate_root(|_, root| { let val = self.arena.mutate_root(|_, root| {
root.current_env = None; root.current_env = None;
convert_value( convert_value(root.stack.pop().expect("stack underflow"), &self.strings)
root.stack.pop().expect("stack underflow"),
&self.strings,
)
}); });
Action::Done(Ok(val)) Action::Done(Ok(val))
} }
pub(super) fn force_tos_shallow(&mut self) -> Action { pub(super) fn force_tos_shallow(&mut self) -> Action {
// FIXME: shallow if let err @ Action::Done(Err(_)) = self.force_tos() {
self.force_tos() return err;
}
let (is_list, is_attrs) = self.arena.mutate_root(|_, root| {
let tos = *root.stack.tos().expect("stack underflow");
(
tos.as_gc::<List<'_>>().is_some(),
tos.as_gc::<AttrSet<'_>>().is_some(),
)
});
if is_list {
let len = self.arena.mutate_root(|_, root| {
let list = root.pop_stack().as_gc::<List<'_>>().unwrap();
for &item in list.inner.iter() {
root.temp_stack.push(item);
}
list.inner.len()
});
let eval_base = self.arena.mutate_root(|_, root| root.temp_stack.len());
for i in 0..len {
self.arena.mutate_root(|_, root| {
let item = root.temp_stack[eval_base - len + i];
root.push_stack(item);
});
if let err @ Action::Done(Err(_)) = self.force_tos() {
self.arena
.mutate_root(|_, root| root.temp_stack.truncate(eval_base - len));
return err;
}
self.arena.mutate_root(|_, root| {
let eval_item = root.pop_stack();
root.temp_stack[eval_base - len + i] = eval_item;
});
}
self.arena.mutate_root(|mc, root| {
let items: SmallVec<[Value; 4]> = root.temp_stack[eval_base - len..eval_base]
.iter()
.copied()
.collect();
root.temp_stack.truncate(eval_base - len);
// Reconstruct List
let new_list = Gc::new(mc, List { inner: items });
root.push_stack(Value::new_gc(new_list));
});
} else if is_attrs {
let len = self.arena.mutate_root(|_, root| {
let attrs = root.pop_stack().as_gc::<AttrSet<'_>>().unwrap();
for &(key, item) in attrs.iter() {
root.temp_stack.push(Value::new_inline(key));
root.temp_stack.push(item);
}
attrs.len()
});
let eval_base = self.arena.mutate_root(|_, root| root.temp_stack.len());
for i in 0..len {
self.arena.mutate_root(|_, root| {
let item = root.temp_stack[eval_base - len * 2 + i * 2 + 1];
root.push_stack(item);
});
if let err @ Action::Done(Err(_)) = self.force_tos() {
self.arena
.mutate_root(|_, root| root.temp_stack.truncate(eval_base - len * 2));
return err;
}
self.arena.mutate_root(|_, root| {
let eval_item = root.pop_stack();
root.temp_stack[eval_base - len * 2 + i * 2 + 1] = eval_item;
});
}
self.arena.mutate_root(|mc, root| {
let mut kv = SmallVec::with_capacity(len);
let mut i = eval_base - len * 2;
while i < eval_base {
let key = root.temp_stack[i].as_inline::<StringId>().unwrap();
let val = root.temp_stack[i + 1];
kv.push((key, val));
i += 2;
}
kv.sort_by_key(|(k, _)| *k);
root.temp_stack.truncate(eval_base - len * 2);
let new_attrs = Gc::new(mc, unsafe { AttrSet::from_sorted_unchecked(kv) });
root.push_stack(Value::new_gc(new_attrs));
});
}
Action::Continue
} }
pub(super) fn force_tos_deep(&mut self) -> Action { pub(super) fn force_tos_deep(&mut self) -> Action {
// FIXME: deep if let err @ Action::Done(Err(_)) = self.force_tos() {
self.force_tos() return err;
}
let (is_list, is_attrs) = self.arena.mutate_root(|_, root| {
let tos = *root.stack.tos().expect("stack underflow");
(
tos.as_gc::<List<'_>>().is_some(),
tos.as_gc::<AttrSet<'_>>().is_some(),
)
});
if is_list {
let len = self.arena.mutate_root(|_, root| {
let list = root.pop_stack().as_gc::<List<'_>>().unwrap();
for &item in list.inner.iter() {
root.temp_stack.push(item);
}
list.inner.len()
});
let eval_base = self.arena.mutate_root(|_, root| root.temp_stack.len());
for i in 0..len {
self.arena.mutate_root(|_, root| {
let item = root.temp_stack[eval_base - len + i];
root.push_stack(item);
});
if let err @ Action::Done(Err(_)) = self.force_tos_deep() {
self.arena
.mutate_root(|_, root| root.temp_stack.truncate(eval_base - len));
return err;
}
self.arena.mutate_root(|_, root| {
let eval_item = root.pop_stack();
root.temp_stack[eval_base - len + i] = eval_item;
});
}
self.arena.mutate_root(|mc, root| {
let items: SmallVec<[Value; 4]> = root.temp_stack[eval_base - len..eval_base]
.iter()
.copied()
.collect();
root.temp_stack.truncate(eval_base - len);
let new_list = Gc::new(mc, List { inner: items });
root.push_stack(Value::new_gc(new_list));
});
} else if is_attrs {
let len = self.arena.mutate_root(|_, root| {
let attrs = root.pop_stack().as_gc::<AttrSet<'_>>().unwrap();
for &(key, item) in attrs.iter() {
root.temp_stack.push(Value::new_inline(key));
root.temp_stack.push(item);
}
attrs.len()
});
let eval_base = self.arena.mutate_root(|_, root| root.temp_stack.len());
for i in 0..len {
self.arena.mutate_root(|_, root| {
let item = root.temp_stack[eval_base - len * 2 + i * 2 + 1];
root.push_stack(item);
});
if let err @ Action::Done(Err(_)) = self.force_tos_deep() {
self.arena
.mutate_root(|_, root| root.temp_stack.truncate(eval_base - len * 2));
return err;
}
self.arena.mutate_root(|_, root| {
let eval_item = root.pop_stack();
root.temp_stack[eval_base - len * 2 + i * 2 + 1] = eval_item;
});
}
self.arena.mutate_root(|mc, root| {
let mut kv = SmallVec::with_capacity(len);
let mut i = eval_base - len * 2;
while i < eval_base {
let key = root.temp_stack[i].as_inline::<StringId>().unwrap();
let val = root.temp_stack[i + 1];
kv.push((key, val));
i += 2;
}
kv.sort_by_key(|(k, _)| *k);
root.temp_stack.truncate(eval_base - len * 2);
let new_attrs = Gc::new(mc, unsafe { AttrSet::from_sorted_unchecked(kv) });
root.push_stack(Value::new_gc(new_attrs));
});
}
Action::Continue
} }
fn handle_vm_error(&mut self, e: VmError) -> Action { fn handle_vm_error(&mut self, e: VmError) -> Action {
+212 -204
View File
@@ -89,230 +89,238 @@ macro_rules! eval_fail_test {
}; };
} }
eval_okay_test!(any_all); mod okay {
eval_okay_test!(arithmetic); use super::*;
eval_okay_test!(attrnames);
eval_okay_test!(attrs); eval_okay_test!(any_all);
eval_okay_test!(attrs2); eval_okay_test!(arithmetic);
eval_okay_test!(attrs3); eval_okay_test!(attrnames);
eval_okay_test!(attrs4); eval_okay_test!(attrs);
eval_okay_test!(attrs5); eval_okay_test!(attrs2);
eval_okay_test!( eval_okay_test!(attrs3);
eval_okay_test!(attrs4);
eval_okay_test!(attrs5);
eval_okay_test!(
#[ignore = "__overrides is not supported"] #[ignore = "__overrides is not supported"]
attrs6 attrs6
); );
eval_okay_test!( eval_okay_test!(
#[ignore = "requires --arg/--argstr CLI flags"] #[ignore = "requires --arg/--argstr CLI flags"]
autoargs autoargs
); );
eval_okay_test!(backslash_newline_1); eval_okay_test!(backslash_newline_1);
eval_okay_test!(backslash_newline_2); eval_okay_test!(backslash_newline_2);
eval_okay_test!(baseNameOf); eval_okay_test!(baseNameOf);
eval_okay_test!(builtins); eval_okay_test!(builtins);
eval_okay_test!(builtins_add); eval_okay_test!(builtins_add);
eval_okay_test!(callable_attrs); eval_okay_test!(callable_attrs);
eval_okay_test!(catattrs); eval_okay_test!(catattrs);
eval_okay_test!(closure); eval_okay_test!(closure);
eval_okay_test!(comments); eval_okay_test!(comments);
eval_okay_test!(concat); eval_okay_test!(concat);
eval_okay_test!(concatmap); eval_okay_test!(concatmap);
eval_okay_test!(concatstringssep); eval_okay_test!(concatstringssep);
eval_okay_test!(context); eval_okay_test!(context);
eval_okay_test!(context_introspection); eval_okay_test!(context_introspection);
eval_okay_test!(convertHash); eval_okay_test!(convertHash);
eval_okay_test!(curpos); eval_okay_test!(curpos);
eval_okay_test!(deepseq); eval_okay_test!(deepseq);
eval_okay_test!(delayed_with); eval_okay_test!(delayed_with);
eval_okay_test!(delayed_with_inherit); eval_okay_test!(delayed_with_inherit);
eval_okay_test!(deprecate_cursed_or); eval_okay_test!(deprecate_cursed_or);
eval_okay_test!(derivation_legacy); eval_okay_test!(derivation_legacy);
eval_okay_test!(dynamic_attrs); eval_okay_test!(dynamic_attrs);
eval_okay_test!(dynamic_attrs_2); eval_okay_test!(dynamic_attrs_2);
eval_okay_test!(dynamic_attrs_bare); eval_okay_test!(dynamic_attrs_bare);
eval_okay_test!(elem); eval_okay_test!(elem);
eval_okay_test!(empty_args); eval_okay_test!(empty_args);
eval_okay_test!(eq); eval_okay_test!(eq);
eval_okay_test!(eq_derivations); eval_okay_test!(eq_derivations);
eval_okay_test!(filter); eval_okay_test!(filter);
eval_okay_test!( eval_okay_test!(
#[ignore = "not implemented: flakeRefToString"] #[ignore = "not implemented: flakeRefToString"]
flake_ref_to_string flake_ref_to_string
); );
eval_okay_test!(flatten); eval_okay_test!(flatten);
eval_okay_test!(float); eval_okay_test!(float);
eval_okay_test!(floor_ceil); eval_okay_test!(floor_ceil);
eval_okay_test!(foldlStrict); eval_okay_test!(foldlStrict);
eval_okay_test!(foldlStrict_lazy_elements); eval_okay_test!(foldlStrict_lazy_elements);
eval_okay_test!(foldlStrict_lazy_initial_accumulator); eval_okay_test!(foldlStrict_lazy_initial_accumulator);
eval_okay_test!(fromjson); eval_okay_test!(fromjson);
eval_okay_test!(fromjson_escapes); eval_okay_test!(fromjson_escapes);
eval_okay_test!(fromTOML); eval_okay_test!(fromTOML);
eval_okay_test!( eval_okay_test!(
#[ignore = "timestamps are not supported"] #[ignore = "timestamps are not supported"]
fromTOML_timestamps fromTOML_timestamps
); );
eval_okay_test!(functionargs); eval_okay_test!(functionargs);
eval_okay_test!(hashfile); eval_okay_test!(hashfile);
eval_okay_test!(hashstring); eval_okay_test!(hashstring);
eval_okay_test!(getattrpos); eval_okay_test!(getattrpos);
eval_okay_test!(getattrpos_functionargs); eval_okay_test!(getattrpos_functionargs);
eval_okay_test!(getattrpos_undefined); eval_okay_test!(getattrpos_undefined);
eval_okay_test!(getenv, || { eval_okay_test!(getenv, || {
unsafe { std::env::set_var("TEST_VAR", "foo") }; unsafe { std::env::set_var("TEST_VAR", "foo") };
}); });
eval_okay_test!(groupBy); eval_okay_test!(groupBy);
eval_okay_test!(r#if); eval_okay_test!(r#if);
eval_okay_test!(ind_string); eval_okay_test!(ind_string);
eval_okay_test!(import); eval_okay_test!(import);
eval_okay_test!(inherit_attr_pos); eval_okay_test!(inherit_attr_pos);
eval_okay_test!( eval_okay_test!(
#[ignore = "__overrides is not supported"] #[ignore = "__overrides is not supported"]
inherit_from inherit_from
); );
eval_okay_test!(intersectAttrs); eval_okay_test!(intersectAttrs);
eval_okay_test!(r#let); eval_okay_test!(r#let);
eval_okay_test!(list); eval_okay_test!(list);
eval_okay_test!(listtoattrs); eval_okay_test!(listtoattrs);
eval_okay_test!(logic); eval_okay_test!(logic);
eval_okay_test!(map); eval_okay_test!(map);
eval_okay_test!(mapattrs); eval_okay_test!(mapattrs);
eval_okay_test!(merge_dynamic_attrs); eval_okay_test!(merge_dynamic_attrs);
eval_okay_test!(nested_with); eval_okay_test!(nested_with);
eval_okay_test!(new_let); eval_okay_test!(new_let);
eval_okay_test!(null_dynamic_attrs); eval_okay_test!(null_dynamic_attrs);
eval_okay_test!( eval_okay_test!(
#[ignore = "__overrides is not supported"] #[ignore = "__overrides is not supported"]
overrides overrides
); );
eval_okay_test!( eval_okay_test!(
#[ignore = "not implemented: parseFlakeRef"] #[ignore = "not implemented: parseFlakeRef"]
parse_flake_ref parse_flake_ref
); );
eval_okay_test!(partition); eval_okay_test!(partition);
eval_okay_test!(path); eval_okay_test!(path);
eval_okay_test!(pathexists); eval_okay_test!(pathexists);
eval_okay_test!(path_string_interpolation, || { eval_okay_test!(path_string_interpolation, || {
unsafe { unsafe {
std::env::set_var("HOME", "/fake-home"); std::env::set_var("HOME", "/fake-home");
} }
}); });
eval_okay_test!(patterns); eval_okay_test!(patterns);
eval_okay_test!(print); eval_okay_test!(print);
eval_okay_test!(readDir); eval_okay_test!(readDir);
eval_okay_test!(readfile); eval_okay_test!(readfile);
eval_okay_test!(readFileType); eval_okay_test!(readFileType);
eval_okay_test!(redefine_builtin); eval_okay_test!(redefine_builtin);
eval_okay_test!(regex_match); eval_okay_test!(regex_match);
eval_okay_test!(regex_split); eval_okay_test!(regex_split);
eval_okay_test!(regression_20220122); eval_okay_test!(regression_20220122);
eval_okay_test!(regression_20220125); eval_okay_test!(regression_20220125);
eval_okay_test!(regrettable_rec_attrset_merge); eval_okay_test!(regrettable_rec_attrset_merge);
eval_okay_test!(remove); eval_okay_test!(remove);
eval_okay_test!(repeated_empty_attrs); eval_okay_test!(repeated_empty_attrs);
eval_okay_test!(repeated_empty_list); eval_okay_test!(repeated_empty_list);
eval_okay_test!(replacestrings); eval_okay_test!(replacestrings);
eval_okay_test!( eval_okay_test!(
#[ignore = "requires -I CLI flags"] #[ignore = "requires -I CLI flags"]
search_path search_path
); );
eval_okay_test!(scope_1); eval_okay_test!(scope_1);
eval_okay_test!(scope_2); eval_okay_test!(scope_2);
eval_okay_test!(scope_3); eval_okay_test!(scope_3);
eval_okay_test!(scope_4); eval_okay_test!(scope_4);
eval_okay_test!(scope_6); eval_okay_test!(scope_6);
eval_okay_test!(scope_7); eval_okay_test!(scope_7);
eval_okay_test!(seq); eval_okay_test!(seq);
eval_okay_test!(sort); eval_okay_test!(sort);
eval_okay_test!(splitversion); eval_okay_test!(splitversion);
eval_okay_test!(string); eval_okay_test!(string);
eval_okay_test!(strings_as_attrs_names); eval_okay_test!(strings_as_attrs_names);
eval_okay_test!(substring); eval_okay_test!(substring);
eval_okay_test!(substring_context); eval_okay_test!(substring_context);
eval_okay_test!(symlink_resolution); eval_okay_test!(symlink_resolution);
eval_okay_test!( eval_okay_test!(
#[ignore = "TCO not implemented, also disabled in CppNix"] #[ignore = "TCO not implemented, also disabled in CppNix"]
tail_call_1 tail_call_1
); );
eval_okay_test!(tojson); eval_okay_test!(tojson);
eval_okay_test!(toxml); eval_okay_test!(toxml);
eval_okay_test!(toxml2); eval_okay_test!(toxml2);
eval_okay_test!(tryeval); eval_okay_test!(tryeval);
eval_okay_test!(types); eval_okay_test!(types);
eval_okay_test!(versions); eval_okay_test!(versions);
eval_okay_test!(with); eval_okay_test!(with);
eval_okay_test!(zipAttrsWith); eval_okay_test!(zipAttrsWith);
}
eval_fail_test!(fail_abort); mod fail {
eval_fail_test!(fail_addDrvOutputDependencies_empty_context); use super::*;
eval_fail_test!(fail_addDrvOutputDependencies_multi_elem_context);
eval_fail_test!(fail_addDrvOutputDependencies_wrong_element_kind); eval_fail_test!(abort);
eval_fail_test!(fail_addErrorRuntime_example); eval_fail_test!(addDrvOutputDependencies_empty_context);
eval_fail_test!(fail_assert); eval_fail_test!(addDrvOutputDependencies_multi_elem_context);
eval_fail_test!(fail_assert_equal_attrs_names); eval_fail_test!(addDrvOutputDependencies_wrong_element_kind);
eval_fail_test!(fail_assert_equal_attrs_names_2); eval_fail_test!(addErrorRuntime_example);
eval_fail_test!(fail_assert_equal_derivations); eval_fail_test!(assert);
eval_fail_test!(fail_assert_equal_derivations_extra); eval_fail_test!(assert_equal_attrs_names);
eval_fail_test!(fail_assert_equal_floats); eval_fail_test!(assert_equal_attrs_names_2);
eval_fail_test!(fail_assert_equal_function_direct); eval_fail_test!(assert_equal_derivations);
eval_fail_test!(fail_assert_equal_int_float); eval_fail_test!(assert_equal_derivations_extra);
eval_fail_test!(fail_assert_equal_ints); eval_fail_test!(assert_equal_floats);
eval_fail_test!(fail_assert_equal_list_length); eval_fail_test!(assert_equal_function_direct);
eval_fail_test!(fail_assert_equal_paths); eval_fail_test!(assert_equal_int_float);
eval_fail_test!(fail_assert_equal_type); eval_fail_test!(assert_equal_ints);
eval_fail_test!(fail_assert_equal_type_nested); eval_fail_test!(assert_equal_list_length);
eval_fail_test!(fail_assert_nested_bool); eval_fail_test!(assert_equal_paths);
eval_fail_test!(fail_attr_name_type); eval_fail_test!(assert_equal_type);
eval_fail_test!(fail_attrset_merge_drops_later_rec); eval_fail_test!(assert_equal_type_nested);
eval_fail_test!(fail_bad_string_interpolation_1); eval_fail_test!(assert_nested_bool);
eval_fail_test!(fail_bad_string_interpolation_2); eval_fail_test!(attr_name_type);
eval_fail_test!(fail_bad_string_interpolation_3); eval_fail_test!(attrset_merge_drops_later_rec);
eval_fail_test!(fail_bad_string_interpolation_4); eval_fail_test!(bad_string_interpolation_1);
eval_fail_test!(fail_blackhole); eval_fail_test!(bad_string_interpolation_2);
eval_fail_test!(fail_call_primop); eval_fail_test!(bad_string_interpolation_3);
eval_fail_test!(fail_deepseq); eval_fail_test!(bad_string_interpolation_4);
eval_fail_test!(fail_derivation_name); eval_fail_test!(blackhole);
eval_fail_test!(fail_dup_dynamic_attrs); eval_fail_test!(call_primop);
eval_fail_test!(fail_duplicate_traces); eval_fail_test!(deepseq);
eval_fail_test!(fail_eol_1); eval_fail_test!(derivation_name);
eval_fail_test!(fail_eol_2); eval_fail_test!(dup_dynamic_attrs);
eval_fail_test!(fail_eol_3); eval_fail_test!(duplicate_traces);
eval_fail_test!(fail_fetchTree_negative); eval_fail_test!(eol_1);
eval_fail_test!(fail_fetchurl_baseName); eval_fail_test!(eol_2);
eval_fail_test!(fail_fetchurl_baseName_attrs); eval_fail_test!(eol_3);
eval_fail_test!(fail_fetchurl_baseName_attrs_name); eval_fail_test!(fetchTree_negative);
eval_fail_test!(fail_flake_ref_to_string_negative_integer); eval_fail_test!(fetchurl_baseName);
eval_fail_test!(fail_foldlStrict_strict_op_application); eval_fail_test!(fetchurl_baseName_attrs);
eval_fail_test!(fail_fromJSON_keyWithNullByte); eval_fail_test!(fetchurl_baseName_attrs_name);
eval_fail_test!(fail_fromJSON_overflowing); eval_fail_test!(flake_ref_to_string_negative_integer);
eval_fail_test!(fail_fromJSON_valueWithNullByte); eval_fail_test!(foldlStrict_strict_op_application);
eval_fail_test!(fail_fromTOML_keyWithNullByte); eval_fail_test!(fromJSON_keyWithNullByte);
eval_fail_test!(fail_fromTOML_timestamps); eval_fail_test!(fromJSON_overflowing);
eval_fail_test!(fail_fromTOML_valueWithNullByte); eval_fail_test!(fromJSON_valueWithNullByte);
eval_fail_test!(fail_hashfile_missing); eval_fail_test!(fromTOML_keyWithNullByte);
eval_fail_test!(fail_infinite_recursion_lambda); eval_fail_test!(fromTOML_timestamps);
eval_fail_test!(fail_list); eval_fail_test!(fromTOML_valueWithNullByte);
eval_fail_test!(fail_missing_arg); eval_fail_test!(hashfile_missing);
eval_fail_test!(fail_mutual_recursion); eval_fail_test!(infinite_recursion_lambda);
eval_fail_test!(fail_nested_list_items); eval_fail_test!(list);
eval_fail_test!(fail_nonexist_path); eval_fail_test!(missing_arg);
eval_fail_test!(fail_not_throws); eval_fail_test!(mutual_recursion);
eval_fail_test!(fail_overflowing_add); eval_fail_test!(nested_list_items);
eval_fail_test!(fail_overflowing_div); eval_fail_test!(nonexist_path);
eval_fail_test!(fail_overflowing_mul); eval_fail_test!(not_throws);
eval_fail_test!(fail_overflowing_sub); eval_fail_test!(overflowing_add);
eval_fail_test!(fail_path_slash); eval_fail_test!(overflowing_div);
eval_fail_test!(fail_pipe_operators); eval_fail_test!(overflowing_mul);
eval_fail_test!(fail_recursion); eval_fail_test!(overflowing_sub);
eval_fail_test!(fail_remove); eval_fail_test!(path_slash);
eval_fail_test!(fail_scope_5); eval_fail_test!(pipe_operators);
eval_fail_test!(fail_seq); eval_fail_test!(recursion);
eval_fail_test!(fail_set); eval_fail_test!(remove);
eval_fail_test!(fail_set_override); eval_fail_test!(scope_5);
eval_fail_test!(fail_string_nul_1); eval_fail_test!(seq);
eval_fail_test!(fail_string_nul_2); eval_fail_test!(set);
eval_fail_test!(fail_substring); eval_fail_test!(set_override);
eval_fail_test!(fail_toJSON); eval_fail_test!(string_nul_1);
eval_fail_test!(fail_toJSON_non_utf_8); eval_fail_test!(string_nul_2);
eval_fail_test!(fail_to_path); eval_fail_test!(substring);
eval_fail_test!(fail_undeclared_arg); eval_fail_test!(toJSON);
eval_fail_test!(fail_using_set_as_attr_name); eval_fail_test!(toJSON_non_utf_8);
eval_fail_test!(to_path);
eval_fail_test!(undeclared_arg);
eval_fail_test!(using_set_as_attr_name);
}