feat: thunk caching (WIP)
This commit is contained in:
@@ -89,3 +89,4 @@ globalThis.$og = op.gt;
|
||||
globalThis.$oc = op.concat;
|
||||
globalThis.$ou = op.update;
|
||||
globalThis.$b = builtins;
|
||||
globalThis.$e = new Map();
|
||||
|
||||
@@ -43,11 +43,6 @@ export const printValue = (value: NixValue, seen: WeakSet<object> = new WeakSet(
|
||||
return "«repeated»";
|
||||
}
|
||||
|
||||
if (seen.has(value)) {
|
||||
return "«repeated»";
|
||||
}
|
||||
seen.add(value);
|
||||
|
||||
if (isNixPath(value)) {
|
||||
return value.value;
|
||||
}
|
||||
@@ -57,10 +52,23 @@ export const printValue = (value: NixValue, seen: WeakSet<object> = new WeakSet(
|
||||
}
|
||||
|
||||
if (Array.isArray(value)) {
|
||||
if (value.length > 0) {
|
||||
if (seen.has(value)) {
|
||||
return "«repeated»";
|
||||
}
|
||||
seen.add(value);
|
||||
}
|
||||
const items = value.map((v) => printValue(v, seen)).join(" ");
|
||||
return `[ ${items} ]`;
|
||||
}
|
||||
|
||||
if (seen.has(value)) {
|
||||
return "«repeated»";
|
||||
}
|
||||
if (value.size > 0) {
|
||||
seen.add(value);
|
||||
}
|
||||
|
||||
const entries = [...value.entries()]
|
||||
.map(([k, v]) => `${printSymbol(k)} = ${printValue(v, seen)};`)
|
||||
.join(" ");
|
||||
|
||||
@@ -132,12 +132,9 @@ export const forceDeep = (value: NixValue, seen: WeakSet<object> = new WeakSet()
|
||||
}
|
||||
|
||||
if (seen.has(forced)) {
|
||||
if (Array.isArray(forced)) {
|
||||
return [CYCLE_MARKER];
|
||||
}
|
||||
return CYCLE_MARKER;
|
||||
}
|
||||
if (isAttrs(forced) || isList(forced)) {
|
||||
if ((isAttrs(forced) && forced.size > 0) || (isList(forced) && forced.length > 0)) {
|
||||
seen.add(forced);
|
||||
}
|
||||
|
||||
|
||||
1
nix-js/runtime-ts/src/types/global.d.ts
vendored
1
nix-js/runtime-ts/src/types/global.d.ts
vendored
@@ -43,6 +43,7 @@ declare global {
|
||||
var $oc: typeof op.concat;
|
||||
var $ou: typeof op.update;
|
||||
var $b: typeof builtins;
|
||||
var $e: NixAttrs;
|
||||
var $gb: typeof Nix.getReplBinding;
|
||||
|
||||
namespace Deno {
|
||||
|
||||
@@ -294,7 +294,7 @@ impl<Ctx: CodegenContext> Compile<Ctx> for Ir<'_> {
|
||||
")"
|
||||
);
|
||||
}
|
||||
Ir::WithExpr(x) => x.compile(ctx, buf),
|
||||
Ir::With(x) => x.compile(ctx, buf),
|
||||
&Ir::WithLookup(WithLookup { inner: name, .. }) => {
|
||||
// Nix.lookupWith
|
||||
code!(buf, ctx;
|
||||
@@ -487,7 +487,7 @@ impl<Ctx: CodegenContext> Compile<Ctx> for TopLevel<'_> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<Ctx: CodegenContext> Compile<Ctx> for WithExpr<'_> {
|
||||
impl<Ctx: CodegenContext> Compile<Ctx> for With<'_> {
|
||||
fn compile(&self, ctx: &Ctx, buf: &mut CodeBuffer) {
|
||||
let namespace = self.namespace;
|
||||
let body = self.body;
|
||||
@@ -585,7 +585,7 @@ impl<Ctx: CodegenContext> Compile<Ctx> for AttrSet<'_> {
|
||||
"]))"
|
||||
);
|
||||
} else {
|
||||
code!(buf, ctx; "new Map()");
|
||||
code!(buf, ctx; "$e");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ use std::cell::UnsafeCell;
|
||||
use std::path::Path;
|
||||
|
||||
use bumpalo::Bump;
|
||||
use hashbrown::{HashMap, HashSet};
|
||||
use hashbrown::{DefaultHashBuilder, HashMap, HashSet};
|
||||
use rnix::TextRange;
|
||||
use string_interner::DefaultStringInterner;
|
||||
|
||||
@@ -10,8 +10,8 @@ use crate::codegen::{CodegenContext, compile, compile_scoped};
|
||||
use crate::downgrade::*;
|
||||
use crate::error::{Error, Result, Source};
|
||||
use crate::ir::{
|
||||
Arg, ArgId, Bool, Builtin, Ir, IrRef, Null, ReplBinding, ScopedImportBinding, SymId, Thunk,
|
||||
ThunkId, ToIr as _, WithLookup,
|
||||
Arg, ArgId, Bool, Builtin, Ir, IrKey, IrRef, Null, ReplBinding, ScopedImportBinding, SymId,
|
||||
Thunk, ThunkId, ToIr as _, WithLookup,
|
||||
};
|
||||
#[cfg(feature = "inspector")]
|
||||
use crate::runtime::inspector::InspectorServer;
|
||||
@@ -450,6 +450,38 @@ impl<'ir, 'ctx> ScopeGuard<'_, 'ctx, 'ir> {
|
||||
}
|
||||
}
|
||||
|
||||
struct ThunkScope<'ir> {
|
||||
bindings: bumpalo::collections::Vec<'ir, (ThunkId, IrRef<'ir>)>,
|
||||
cache: HashMap<IrKey<'ir>, ThunkId>,
|
||||
hasher: DefaultHashBuilder,
|
||||
}
|
||||
|
||||
impl<'ir> ThunkScope<'ir> {
|
||||
fn new_in(bump: &'ir Bump) -> Self {
|
||||
Self {
|
||||
bindings: bumpalo::collections::Vec::new_in(bump),
|
||||
cache: HashMap::new(),
|
||||
hasher: DefaultHashBuilder::default(),
|
||||
}
|
||||
}
|
||||
|
||||
fn lookup_cache(&self, key: IrKey<'ir>) -> Option<ThunkId> {
|
||||
self.cache.get(&key).copied()
|
||||
}
|
||||
|
||||
fn add_binding(&mut self, id: ThunkId, ir: IrRef<'ir>) {
|
||||
self.bindings.push((id, ir));
|
||||
}
|
||||
|
||||
fn extend_bindings(&mut self, iter: impl IntoIterator<Item = (ThunkId, IrRef<'ir>)>) {
|
||||
self.bindings.extend(iter);
|
||||
}
|
||||
|
||||
fn add_cache(&mut self, key: IrKey<'ir>, cache: ThunkId) {
|
||||
self.cache.insert(key, cache);
|
||||
}
|
||||
}
|
||||
|
||||
struct DowngradeCtx<'ctx, 'ir> {
|
||||
bump: &'ir Bump,
|
||||
symbols: &'ctx mut DefaultStringInterner,
|
||||
@@ -458,7 +490,7 @@ struct DowngradeCtx<'ctx, 'ir> {
|
||||
with_scope_count: usize,
|
||||
arg_count: usize,
|
||||
thunk_count: &'ctx mut usize,
|
||||
thunk_scopes: Vec<bumpalo::collections::Vec<'ir, (ThunkId, IrRef<'ir>)>>,
|
||||
thunk_scopes: Vec<ThunkScope<'ir>>,
|
||||
}
|
||||
|
||||
fn should_thunk(ir: IrRef<'_>) -> bool {
|
||||
@@ -494,7 +526,7 @@ impl<'ctx, 'ir> DowngradeCtx<'ctx, 'ir> {
|
||||
thunk_count,
|
||||
arg_count: 0,
|
||||
with_scope_count: 0,
|
||||
thunk_scopes: vec![bumpalo::collections::Vec::new_in(bump)],
|
||||
thunk_scopes: vec![ThunkScope::new_in(bump)],
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -517,13 +549,22 @@ impl<'ctx: 'ir, 'ir> DowngradeContext<'ir> for DowngradeCtx<'ctx, 'ir> {
|
||||
|
||||
fn maybe_thunk(&mut self, ir: IrRef<'ir>) -> IrRef<'ir> {
|
||||
if should_thunk(ir) {
|
||||
let scope = self.thunk_scopes.last_mut().expect("no active cache scope");
|
||||
let key = IrKey(ir);
|
||||
if let Some(id) = scope.lookup_cache(key) {
|
||||
return self.new_expr(
|
||||
Thunk {
|
||||
inner: id,
|
||||
span: ir.span(),
|
||||
}
|
||||
.to_ir(),
|
||||
);
|
||||
}
|
||||
let span = ir.span();
|
||||
let id = ThunkId(*self.thunk_count);
|
||||
*self.thunk_count += 1;
|
||||
self.thunk_scopes
|
||||
.last_mut()
|
||||
.expect("no active thunk scope")
|
||||
.push((id, ir));
|
||||
scope.add_binding(id, ir);
|
||||
scope.add_cache(key, id);
|
||||
self.new_expr(Thunk { inner: id, span }.to_ir())
|
||||
} else {
|
||||
ir
|
||||
@@ -601,7 +642,7 @@ impl<'ctx: 'ir, 'ir> DowngradeContext<'ir> for DowngradeCtx<'ctx, 'ir> {
|
||||
};
|
||||
assert_eq!(keys.len(), vals.len());
|
||||
let scope = self.thunk_scopes.last_mut().expect("no active thunk scope");
|
||||
scope.extend((base..base + keys.len()).map(ThunkId).zip(vals));
|
||||
scope.extend_bindings((base..base + keys.len()).map(ThunkId).zip(vals));
|
||||
Ok(ret)
|
||||
}
|
||||
|
||||
@@ -631,12 +672,14 @@ impl<'ctx: 'ir, 'ir> DowngradeContext<'ir> for DowngradeCtx<'ctx, 'ir> {
|
||||
where
|
||||
F: FnOnce(&mut Self) -> R,
|
||||
{
|
||||
self.thunk_scopes
|
||||
.push(bumpalo::collections::Vec::new_in(self.bump));
|
||||
self.thunk_scopes.push(ThunkScope::new_in(self.bump));
|
||||
let ret = f(self);
|
||||
(
|
||||
ret,
|
||||
self.thunk_scopes.pop().expect("no thunk scope left???"),
|
||||
self.thunk_scopes
|
||||
.pop()
|
||||
.expect("no thunk scope left???")
|
||||
.bindings,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -650,7 +693,11 @@ impl<'ir, 'ctx: 'ir> DowngradeCtx<'ctx, 'ir> {
|
||||
use crate::ir::TopLevel;
|
||||
let body = root.downgrade(&mut self)?;
|
||||
let span = body.span();
|
||||
let thunks = self.thunk_scopes.pop().expect("no thunk scope left???");
|
||||
let thunks = self
|
||||
.thunk_scopes
|
||||
.pop()
|
||||
.expect("no thunk scope left???")
|
||||
.bindings;
|
||||
Ok(self.new_expr(TopLevel { body, thunks, span }.to_ir()))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -447,7 +447,7 @@ impl<'ir, Ctx: DowngradeContext<'ir>> Downgrade<'ir, Ctx> for ast::With {
|
||||
let body = body?;
|
||||
|
||||
Ok(ctx.new_expr(
|
||||
WithExpr {
|
||||
With {
|
||||
namespace,
|
||||
body,
|
||||
thunks,
|
||||
|
||||
330
nix-js/src/ir.rs
330
nix-js/src/ir.rs
@@ -1,3 +1,5 @@
|
||||
use std::hash::{Hash, Hasher};
|
||||
|
||||
use bumpalo::{Bump, boxed::Box, collections::Vec};
|
||||
use rnix::{TextRange, ast};
|
||||
use string_interner::symbol::SymbolU32;
|
||||
@@ -10,6 +12,7 @@ pub type IrRef<'ir> = &'ir Ir<'ir>;
|
||||
ir! {
|
||||
Ir<'ir>;
|
||||
|
||||
// Literals
|
||||
Int(i64),
|
||||
Float(f64),
|
||||
Bool(bool),
|
||||
@@ -17,27 +20,37 @@ ir! {
|
||||
Str { inner: Box<'ir, String> },
|
||||
AttrSet { stcs: HashMap<'ir, SymId, (IrRef<'ir>, TextRange)>, dyns: Vec<'ir, (IrRef<'ir>, IrRef<'ir>, TextRange)> },
|
||||
List { items: Vec<'ir, IrRef<'ir>> },
|
||||
|
||||
HasAttr { lhs: IrRef<'ir>, rhs: Vec<'ir, Attr<'ir>> },
|
||||
BinOp { lhs: IrRef<'ir>, rhs: IrRef<'ir>, kind: BinOpKind },
|
||||
UnOp { rhs: IrRef<'ir>, kind: UnOpKind },
|
||||
Select { expr: IrRef<'ir>, attrpath: Vec<'ir, Attr<'ir>>, default: Option<IrRef<'ir>> },
|
||||
If { cond: IrRef<'ir>, consq: IrRef<'ir>, alter: IrRef<'ir> },
|
||||
Call { func: IrRef<'ir>, arg: IrRef<'ir> },
|
||||
Assert { assertion: IrRef<'ir>, expr: IrRef<'ir>, assertion_raw: String },
|
||||
ConcatStrings { parts: Vec<'ir, IrRef<'ir>>, force_string: bool },
|
||||
Path { expr: IrRef<'ir> },
|
||||
ConcatStrings { parts: Vec<'ir, IrRef<'ir>>, force_string: bool },
|
||||
|
||||
// OPs
|
||||
UnOp { rhs: IrRef<'ir>, kind: UnOpKind },
|
||||
BinOp { lhs: IrRef<'ir>, rhs: IrRef<'ir>, kind: BinOpKind },
|
||||
HasAttr { lhs: IrRef<'ir>, rhs: Vec<'ir, Attr<'ir>> },
|
||||
Select { expr: IrRef<'ir>, attrpath: Vec<'ir, Attr<'ir>>, default: Option<IrRef<'ir>> },
|
||||
|
||||
// Conditionals
|
||||
If { cond: IrRef<'ir>, consq: IrRef<'ir>, alter: IrRef<'ir> },
|
||||
Assert { assertion: IrRef<'ir>, expr: IrRef<'ir>, assertion_raw: String },
|
||||
|
||||
With { namespace: IrRef<'ir>, body: IrRef<'ir>, thunks: Vec<'ir, (ThunkId, IrRef<'ir>)> },
|
||||
WithLookup(SymId),
|
||||
|
||||
// Function related
|
||||
Func { body: IrRef<'ir>, param: Option<Param<'ir>>, arg: IrRef<'ir>, thunks: Vec<'ir, (ThunkId, IrRef<'ir>)> },
|
||||
TopLevel { body: IrRef<'ir>, thunks: Vec<'ir, (ThunkId, IrRef<'ir>)> },
|
||||
Arg(ArgId),
|
||||
Thunk(ThunkId),
|
||||
Call { func: IrRef<'ir>, arg: IrRef<'ir> },
|
||||
|
||||
// Builtins
|
||||
Builtins,
|
||||
Builtin(SymId),
|
||||
|
||||
// Misc
|
||||
TopLevel { body: IrRef<'ir>, thunks: Vec<'ir, (ThunkId, IrRef<'ir>)> },
|
||||
Thunk(ThunkId),
|
||||
CurPos,
|
||||
ReplBinding(SymId),
|
||||
ScopedImportBinding(SymId),
|
||||
WithExpr { namespace: IrRef<'ir>, body: IrRef<'ir>, thunks: Vec<'ir, (ThunkId, IrRef<'ir>)> },
|
||||
WithLookup(SymId),
|
||||
}
|
||||
|
||||
#[repr(transparent)]
|
||||
@@ -63,7 +76,7 @@ pub enum Attr<'ir> {
|
||||
}
|
||||
|
||||
/// The kinds of binary operations supported in Nix.
|
||||
#[derive(Clone, Debug)]
|
||||
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
|
||||
pub enum BinOpKind {
|
||||
// Arithmetic
|
||||
Add,
|
||||
@@ -120,7 +133,7 @@ impl From<ast::BinOpKind> for BinOpKind {
|
||||
}
|
||||
|
||||
/// The kinds of unary operations.
|
||||
#[derive(Clone, Debug)]
|
||||
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
|
||||
pub enum UnOpKind {
|
||||
Neg, // Negation (`-`)
|
||||
Not, // Logical not (`!`)
|
||||
@@ -142,3 +155,290 @@ pub struct Param<'ir> {
|
||||
pub optional: Vec<'ir, (SymId, TextRange)>,
|
||||
pub ellipsis: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub(crate) struct IrKey<'ir>(pub IrRef<'ir>);
|
||||
|
||||
impl std::hash::Hash for IrKey<'_> {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
ir_content_hash(self.0, state);
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for IrKey<'_> {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
ir_content_eq(self.0, other.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for IrKey<'_> {}
|
||||
|
||||
fn attr_content_hash(attr: &Attr<'_>, state: &mut impl Hasher) {
|
||||
core::mem::discriminant(attr).hash(state);
|
||||
match attr {
|
||||
Attr::Dynamic(expr, _) => ir_content_hash(expr, state),
|
||||
Attr::Str(sym, _) => sym.hash(state),
|
||||
}
|
||||
}
|
||||
|
||||
fn attr_content_eq(a: &Attr<'_>, b: &Attr<'_>) -> bool {
|
||||
match (a, b) {
|
||||
(Attr::Dynamic(ae, _), Attr::Dynamic(be, _)) => ir_content_eq(ae, be),
|
||||
(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(thunks: &[(ThunkId, IrRef<'_>)], state: &mut impl Hasher) {
|
||||
thunks.len().hash(state);
|
||||
for (id, ir) in thunks {
|
||||
id.hash(state);
|
||||
ir_content_hash(ir, state);
|
||||
}
|
||||
}
|
||||
|
||||
fn thunks_content_eq(a: &[(ThunkId, IrRef<'_>)], b: &[(ThunkId, IrRef<'_>)]) -> bool {
|
||||
a.len() == b.len()
|
||||
&& a.iter()
|
||||
.zip(b.iter())
|
||||
.all(|((ai, ae), (bi, be))| ai == bi && ir_content_eq(ae, be))
|
||||
}
|
||||
|
||||
fn ir_content_hash(ir: &Ir<'_>, state: &mut impl Hasher) {
|
||||
core::mem::discriminant(ir).hash(state);
|
||||
match ir {
|
||||
Ir::Int(x) => x.inner.hash(state),
|
||||
Ir::Float(x) => x.inner.to_bits().hash(state),
|
||||
Ir::Bool(x) => x.inner.hash(state),
|
||||
Ir::Null(_) => {}
|
||||
Ir::Str(x) => x.inner.hash(state),
|
||||
Ir::AttrSet(x) => {
|
||||
x.stcs.len().hash(state);
|
||||
let mut combined: u64 = 0;
|
||||
for (&key, (val, _)) in x.stcs.iter() {
|
||||
let mut h = std::hash::DefaultHasher::new();
|
||||
key.hash(&mut h);
|
||||
ir_content_hash(val, &mut h);
|
||||
combined = combined.wrapping_add(h.finish());
|
||||
}
|
||||
combined.hash(state);
|
||||
x.dyns.len().hash(state);
|
||||
for (k, v, _) in x.dyns.iter() {
|
||||
ir_content_hash(k, state);
|
||||
ir_content_hash(v, state);
|
||||
}
|
||||
}
|
||||
Ir::List(x) => {
|
||||
x.items.len().hash(state);
|
||||
for item in x.items.iter() {
|
||||
ir_content_hash(item, state);
|
||||
}
|
||||
}
|
||||
Ir::HasAttr(x) => {
|
||||
ir_content_hash(x.lhs, state);
|
||||
x.rhs.len().hash(state);
|
||||
for attr in x.rhs.iter() {
|
||||
attr_content_hash(attr, state);
|
||||
}
|
||||
}
|
||||
Ir::BinOp(x) => {
|
||||
ir_content_hash(x.lhs, state);
|
||||
ir_content_hash(x.rhs, state);
|
||||
x.kind.hash(state);
|
||||
}
|
||||
Ir::UnOp(x) => {
|
||||
ir_content_hash(x.rhs, state);
|
||||
x.kind.hash(state);
|
||||
}
|
||||
Ir::Select(x) => {
|
||||
ir_content_hash(x.expr, state);
|
||||
x.attrpath.len().hash(state);
|
||||
for attr in x.attrpath.iter() {
|
||||
attr_content_hash(attr, state);
|
||||
}
|
||||
x.default.is_some().hash(state);
|
||||
if let Some(d) = x.default {
|
||||
ir_content_hash(d, state);
|
||||
}
|
||||
}
|
||||
Ir::If(x) => {
|
||||
ir_content_hash(x.cond, state);
|
||||
ir_content_hash(x.consq, state);
|
||||
ir_content_hash(x.alter, state);
|
||||
}
|
||||
Ir::Call(x) => {
|
||||
ir_content_hash(x.func, state);
|
||||
ir_content_hash(x.arg, state);
|
||||
}
|
||||
Ir::Assert(x) => {
|
||||
ir_content_hash(x.assertion, state);
|
||||
ir_content_hash(x.expr, state);
|
||||
x.assertion_raw.hash(state);
|
||||
}
|
||||
Ir::ConcatStrings(x) => {
|
||||
x.force_string.hash(state);
|
||||
x.parts.len().hash(state);
|
||||
for part in x.parts.iter() {
|
||||
ir_content_hash(part, state);
|
||||
}
|
||||
}
|
||||
Ir::Path(x) => ir_content_hash(x.expr, state),
|
||||
Ir::Func(x) => {
|
||||
ir_content_hash(x.body, state);
|
||||
ir_content_hash(x.arg, state);
|
||||
x.param.is_some().hash(state);
|
||||
if let Some(p) = &x.param {
|
||||
param_content_hash(p, state);
|
||||
}
|
||||
thunks_content_hash(&x.thunks, state);
|
||||
}
|
||||
Ir::TopLevel(x) => {
|
||||
ir_content_hash(x.body, state);
|
||||
thunks_content_hash(&x.thunks, state);
|
||||
}
|
||||
Ir::Arg(x) => x.inner.hash(state),
|
||||
Ir::Thunk(x) => x.inner.hash(state),
|
||||
Ir::Builtins(_) => {}
|
||||
Ir::Builtin(x) => x.inner.hash(state),
|
||||
Ir::CurPos(x) => x.span.hash(state),
|
||||
Ir::ReplBinding(x) => x.inner.hash(state),
|
||||
Ir::ScopedImportBinding(x) => x.inner.hash(state),
|
||||
Ir::With(x) => {
|
||||
ir_content_hash(x.namespace, state);
|
||||
ir_content_hash(x.body, state);
|
||||
thunks_content_hash(&x.thunks, state);
|
||||
}
|
||||
Ir::WithLookup(x) => x.inner.hash(state),
|
||||
}
|
||||
}
|
||||
|
||||
fn ir_content_eq(a: &Ir<'_>, b: &Ir<'_>) -> bool {
|
||||
std::ptr::eq(a, b)
|
||||
|| match (a, b) {
|
||||
(Ir::Int(a), Ir::Int(b)) => a.inner == b.inner,
|
||||
(Ir::Float(a), Ir::Float(b)) => a.inner.to_bits() == b.inner.to_bits(),
|
||||
(Ir::Bool(a), Ir::Bool(b)) => a.inner == b.inner,
|
||||
(Ir::Null(_), Ir::Null(_)) => true,
|
||||
(Ir::Str(a), Ir::Str(b)) => *a.inner == *b.inner,
|
||||
(Ir::AttrSet(a), Ir::AttrSet(b)) => {
|
||||
a.stcs.len() == b.stcs.len()
|
||||
&& a.dyns.len() == b.dyns.len()
|
||||
&& a.stcs.iter().all(|(&k, (v, _))| {
|
||||
b.stcs.get(&k).is_some_and(|(bv, _)| ir_content_eq(v, bv))
|
||||
})
|
||||
&& a.dyns
|
||||
.iter()
|
||||
.zip(b.dyns.iter())
|
||||
.all(|((ak, av, _), (bk, bv, _))| {
|
||||
ir_content_eq(ak, bk) && ir_content_eq(av, bv)
|
||||
})
|
||||
}
|
||||
(Ir::List(a), Ir::List(b)) => {
|
||||
a.items.len() == b.items.len()
|
||||
&& a.items
|
||||
.iter()
|
||||
.zip(b.items.iter())
|
||||
.all(|(a, b)| ir_content_eq(a, b))
|
||||
}
|
||||
(Ir::HasAttr(a), Ir::HasAttr(b)) => {
|
||||
ir_content_eq(a.lhs, b.lhs)
|
||||
&& a.rhs.len() == b.rhs.len()
|
||||
&& a.rhs
|
||||
.iter()
|
||||
.zip(b.rhs.iter())
|
||||
.all(|(a, b)| attr_content_eq(a, b))
|
||||
}
|
||||
(Ir::BinOp(a), Ir::BinOp(b)) => {
|
||||
a.kind == b.kind && ir_content_eq(a.lhs, b.lhs) && ir_content_eq(a.rhs, b.rhs)
|
||||
}
|
||||
(Ir::UnOp(a), Ir::UnOp(b)) => a.kind == b.kind && ir_content_eq(a.rhs, b.rhs),
|
||||
(Ir::Select(a), Ir::Select(b)) => {
|
||||
ir_content_eq(a.expr, b.expr)
|
||||
&& a.attrpath.len() == b.attrpath.len()
|
||||
&& a.attrpath
|
||||
.iter()
|
||||
.zip(b.attrpath.iter())
|
||||
.all(|(a, b)| attr_content_eq(a, b))
|
||||
&& match (a.default, b.default) {
|
||||
(Some(a), Some(b)) => ir_content_eq(a, b),
|
||||
(None, None) => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
(Ir::If(a), Ir::If(b)) => {
|
||||
ir_content_eq(a.cond, b.cond)
|
||||
&& ir_content_eq(a.consq, b.consq)
|
||||
&& ir_content_eq(a.alter, b.alter)
|
||||
}
|
||||
(Ir::Call(a), Ir::Call(b)) => {
|
||||
ir_content_eq(a.func, b.func) && ir_content_eq(a.arg, b.arg)
|
||||
}
|
||||
(Ir::Assert(a), Ir::Assert(b)) => {
|
||||
a.assertion_raw == b.assertion_raw
|
||||
&& ir_content_eq(a.assertion, b.assertion)
|
||||
&& ir_content_eq(a.expr, b.expr)
|
||||
}
|
||||
(Ir::ConcatStrings(a), Ir::ConcatStrings(b)) => {
|
||||
a.force_string == b.force_string
|
||||
&& a.parts.len() == b.parts.len()
|
||||
&& a.parts
|
||||
.iter()
|
||||
.zip(b.parts.iter())
|
||||
.all(|(a, b)| ir_content_eq(a, b))
|
||||
}
|
||||
(Ir::Path(a), Ir::Path(b)) => ir_content_eq(a.expr, b.expr),
|
||||
(Ir::Func(a), Ir::Func(b)) => {
|
||||
ir_content_eq(a.body, b.body)
|
||||
&& ir_content_eq(a.arg, b.arg)
|
||||
&& match (&a.param, &b.param) {
|
||||
(Some(a), Some(b)) => param_content_eq(a, b),
|
||||
(None, None) => true,
|
||||
_ => false,
|
||||
}
|
||||
&& thunks_content_eq(&a.thunks, &b.thunks)
|
||||
}
|
||||
(Ir::TopLevel(a), Ir::TopLevel(b)) => {
|
||||
ir_content_eq(a.body, b.body) && thunks_content_eq(&a.thunks, &b.thunks)
|
||||
}
|
||||
(Ir::Arg(a), Ir::Arg(b)) => a.inner == b.inner,
|
||||
(Ir::Thunk(a), Ir::Thunk(b)) => a.inner == b.inner,
|
||||
(Ir::Builtins(_), Ir::Builtins(_)) => true,
|
||||
(Ir::Builtin(a), Ir::Builtin(b)) => a.inner == b.inner,
|
||||
(Ir::CurPos(a), Ir::CurPos(b)) => a.span == b.span,
|
||||
(Ir::ReplBinding(a), Ir::ReplBinding(b)) => a.inner == b.inner,
|
||||
(Ir::ScopedImportBinding(a), Ir::ScopedImportBinding(b)) => a.inner == b.inner,
|
||||
(Ir::With(a), Ir::With(b)) => {
|
||||
ir_content_eq(a.namespace, b.namespace)
|
||||
&& ir_content_eq(a.body, b.body)
|
||||
&& thunks_content_eq(&a.thunks, &b.thunks)
|
||||
}
|
||||
(Ir::WithLookup(a), Ir::WithLookup(b)) => a.inner == b.inner,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -101,9 +101,9 @@ fn run_eval(context: &mut Context, src: ExprSource) -> Result<()> {
|
||||
} else {
|
||||
unreachable!()
|
||||
};
|
||||
match context.eval_shallow(src) {
|
||||
match context.eval_deep(src) {
|
||||
Ok(value) => {
|
||||
println!("{value}");
|
||||
println!("{}", value.display_compat());
|
||||
}
|
||||
Err(err) => {
|
||||
eprintln!("{:?}", miette::Report::new(*err));
|
||||
|
||||
@@ -1 +1 @@
|
||||
[ { } { a = 1; } { a = 1; } { a = "a"; } { m = 1; } { m = "m"; } { n = 1; } { n = "n"; } { n = 1; p = 2; } { n = "n"; p = "p"; } { n = 1; p = 2; } { n = "n"; p = "p"; } { a = "a"; b = "b"; c = "c"; d = "d"; e = "e"; f = "f"; g = "g"; h = "h"; i = "i"; j = "j"; k = "k"; l = "l"; m = "m"; n = "n"; o = "o"; p = "p"; q = "q"; r = "r"; s = "s"; t = "t"; u = "u"; v = "v"; w = "w"; x = "x"; y = "y"; z = "z"; } true ]
|
||||
[ { } { a = 1; } { a = 1; } { a = "a"; } { m = 1; } { m = "m"; } { n = 1; } { n = "n"; } { n = 1; p = 2; } { n = "n"; p = "p"; } «repeated» «repeated» { a = "a"; b = "b"; c = "c"; d = "d"; e = "e"; f = "f"; g = "g"; h = "h"; i = "i"; j = "j"; k = "k"; l = "l"; m = "m"; n = "n"; o = "o"; p = "p"; q = "q"; r = "r"; s = "s"; t = "t"; u = "u"; v = "v"; w = "w"; x = "x"; y = "y"; z = "z"; } true ]
|
||||
|
||||
@@ -1 +1 @@
|
||||
[ null <PRIMOP> <PRIMOP-APP> <LAMBDA> [ [ «repeated» ] ] ]
|
||||
[ null <PRIMOP> <PRIMOP-APP> <LAMBDA> [ «repeated» ] ]
|
||||
|
||||
@@ -6,6 +6,8 @@ trace
|
||||
toString
|
||||
(deepSeq "x")
|
||||
(a: a)
|
||||
# [ «repeated» ] instead of [ [ «repeated» ] ]
|
||||
# matches Lix's behaviour
|
||||
(
|
||||
let
|
||||
x = [ x ];
|
||||
|
||||
@@ -1 +1 @@
|
||||
[ [ 42 77 147 249 483 526 ] [ 526 483 249 147 77 42 ] [ "bar" "fnord" "foo" "xyzzy" ] [ { key = 1; value = "foo"; } { key = 1; value = "fnord"; } { key = 2; value = "bar"; } ] [ [ ] [ ] [ 1 ] [ 1 4 ] [ 1 5 ] [ 1 6 ] [ 2 ] [ 2 3 ] [ 3 ] [ 3 ] ] ]
|
||||
[ [ 42 77 147 249 483 526 ] [ 526 483 249 147 77 42 ] [ "bar" "fnord" "foo" "xyzzy" ] [ { key = 1; value = "foo"; } { key = 1; value = "fnord"; } { key = 2; value = "bar"; } ] [ [ ] [ ] [ 1 ] [ 1 4 ] [ 1 5 ] [ 1 6 ] [ 2 ] [ 2 3 ] [ 3 ] «repeated» ] ]
|
||||
|
||||
Reference in New Issue
Block a user